diff --git a/.eslintrc.js b/.eslintrc.js index f729e0acb..cc4da5ace 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,889 +1,400 @@ module.exports = { - "env": { - "browser": true, - "commonjs": true, - "es6": false, + env: { + browser: true, + commonjs: true, + es6: false, + }, + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 8, + sourceType: "module", + ecmaFeatures: { + experimentalObjectRestSpread: true, }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", + }, + plugins: ["@typescript-eslint"], + rules: { + "accessor-pairs": [ + "error", + { + setWithoutGet: true, + getWithoutSet: false, + }, ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 8, - "sourceType": "module", - "ecmaFeatures": { - "experimentalObjectRestSpread": true, - }, + "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"], + "implicit-arrow-linebreak": ["error", "beside"], + 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": ["error"], + "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-cond-assign": ["off", "except-parens"], + "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": ["off"], + "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: ["off"], + "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", + }, + overrides: [ + { + // enable the rule specifically for TypeScript files + files: ["*.ts", "*.tsx"], + rules: { + "@typescript-eslint/explicit-function-return-type": ["error"], + "@typescript-eslint/explicit-module-boundary-types": ["error"], + }, }, - "plugins": [ - '@typescript-eslint', - ], - "rules": { - "accessor-pairs": [ - "error", - { - "setWithoutGet": true, - "getWithoutSet": false, - }, - ], - "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", - ], - "implicit-arrow-linebreak": [ - "error", - "beside", - ], - "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": [ - "error", - ], - "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-cond-assign": [ - "off", - "except-parens", - ], - "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": [ - "off", - ], - "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", - ], + { + // TypeScript configuration + files: ["**/*.ts", "**/*.tsx"], + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], + extends: ["plugin:@typescript-eslint/recommended"], + rules: { + "lines-between-class-members": "off", + "no-empty-pattern": "off", "no-useless-constructor": [ - "error", + "off", // Valid for typescript due to property ctor shorthand ], - "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": [ - "off", - ], - "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", - }, - "overrides": [ - { - // enable the rule specifically for TypeScript files - "files": ["*.ts", "*.tsx"], - "rules": { - "@typescript-eslint/explicit-function-return-type": ["error"], - "@typescript-eslint/explicit-module-boundary-types": ["error"], + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/ban-ts-ignore": "off", + "@typescript-eslint/camelcase": "off", + "@typescript-eslint/explicit-function-return-type": [ + "error", + { + allowExpressions: true, }, - }, - { - // TypeScript configuration - "files": [ "**/*.ts", "**/*.tsx" ], - "parser": "@typescript-eslint/parser", - "plugins": [ "@typescript-eslint" ], - "extends": [ - "plugin:@typescript-eslint/recommended", - ], - "rules": { - "lines-between-class-members": "off", - "no-empty-pattern": "off", - "no-useless-constructor": [ - "off", // Valid for typescript due to property ctor shorthand - ], - "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/ban-ts-ignore": "off", - "@typescript-eslint/camelcase": "off", - "@typescript-eslint/explicit-function-return-type": ["error", { - "allowExpressions": true, - }], - "@typescript-eslint/member-delimiter-style": ["error", { - "multiline": { - "delimiter": "semi", - "requireLast": true, - }, - "singleline": { - "delimiter": "semi", - "requireLast": false, - }, - }], - "@typescript-eslint/member-ordering": ["error", { - "default": [ - "signature", - "static-field", - "instance-field", - "abstract-field", - "constructor", - "instance-method", - "abstract-method", - "static-method", - ], - }], - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-use-before-define": "off", + ], + "@typescript-eslint/member-delimiter-style": [ + "error", + { + multiline: { + delimiter: "semi", + requireLast: true, }, - }, - ], -}; \ No newline at end of file + singleline: { + delimiter: "semi", + requireLast: false, + }, + }, + ], + "@typescript-eslint/member-ordering": [ + "error", + { + default: [ + "signature", + "static-field", + "instance-field", + "abstract-field", + "constructor", + "instance-method", + "abstract-method", + "static-method", + ], + }, + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-use-before-define": "off", + }, + }, + ], +}; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a804aad5c..5abf64ead 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,24 +1,27 @@ # Contributing to Bitburner ## In General + The game is made better because the community as a whole speaks up about ways to improve the game. Here's some of the ways you can make your voice heard: - - [Discord](https://discordapp.com) - There is a dedicated Discord instance set up for more free-form chats - between all members of the community. Regular players, heavy scripters, - Bitburner contributors, and everyone in between can be found on the - server. - - [Github Issues](https://github.com/danielyxie/bitburner/issues) - Although the term "issues" can have a negative connotation, they are a - means of communicating with the community. A new Issue can be a - interesting new feature that you feel would improve the game. It could be - an unexpected behavior within the game. Or because the game is about - scripting perhaps there is something that is conflicting with the - browser's Javascript interaction. So please do not be afraid to open a - [new issue](https://github.com/danielyxie/bitburner/issues/new). + +- [Discord](https://discordapp.com) + There is a dedicated Discord instance set up for more free-form chats + between all members of the community. Regular players, heavy scripters, + Bitburner contributors, and everyone in between can be found on the + server. +- [Github Issues](https://github.com/danielyxie/bitburner/issues) + Although the term "issues" can have a negative connotation, they are a + means of communicating with the community. A new Issue can be a + interesting new feature that you feel would improve the game. It could be + an unexpected behavior within the game. Or because the game is about + scripting perhaps there is something that is conflicting with the + browser's Javascript interaction. So please do not be afraid to open a + [new issue](https://github.com/danielyxie/bitburner/issues/new). ## Reporting Bugs + The recommended method for reporting a bug is by opening a [Github Issue](https://github.com/danielyxie/bitburner/issues). @@ -30,18 +33,19 @@ already been reported as an [Issue](https://github.com/danielyxie/bitburner/issu #### How to Submit a Good Bug Report - * **Use a clear and descriptive title** for the issue - * **State your browser, your browser's version, and your computer's OS** - * **Attach your save file**, if you think it would help solve the issue - * **Provide instructions on how to reproduce the bug** in as much detail - as possible. If you cannot reliably reproduce the bug, then just try - your best to explain what was happening when the bug occurred - * **Provide any scripts** that triggered the bug if the issue is Netscript-related - * **Open your browser's Dev Console and report any error-related output** - that may be printed there. The Dev Console can be opened on most modern - browsers by pressing F12 +- **Use a clear and descriptive title** for the issue +- **State your browser, your browser's version, and your computer's OS** +- **Attach your save file**, if you think it would help solve the issue +- **Provide instructions on how to reproduce the bug** in as much detail + as possible. If you cannot reliably reproduce the bug, then just try + your best to explain what was happening when the bug occurred +- **Provide any scripts** that triggered the bug if the issue is Netscript-related +- **Open your browser's Dev Console and report any error-related output** + that may be printed there. The Dev Console can be opened on most modern + browsers by pressing F12 ## As a Developer + Anyone is welcome to contribute to Bitburner code. However, please read the [license](https://github.com/danielyxie/bitburner/blob/dev/license.txt) and the [readme](https://github.com/danielyxie/bitburner/blob/dev/README.md) @@ -52,64 +56,70 @@ To contribute to Bitburner code, you will need to have called `npm` is installed as well. #### What are you Allowed to Contribute? + Not all code contributions will be accepted. The safest way to ensure that you don't waste time working on something that gets rejected is to run your idea(s)/plan(s) past [danielyxie](https://github.com/danielyxie) first. You can contact him through: - * Github - * Discord - * [Reddit](https://www.reddit.com/user/chapt3r/) +- Github +- Discord +- [Reddit](https://www.reddit.com/user/chapt3r/) Otherwise, here are some general guidelines for determining what types of changes are okay to contribute: ##### Contributions that Will Most Likely Be Accepted -* Bug Fixes -* Quality-of-Life Changes - * Adding a new, commonly-requested Netscript function - * Fixing or improving UI elements - * Adding game settings/options - * Adding a new Terminal command -* Code Refactors that conform to good/standard practices + +- Bug Fixes +- Quality-of-Life Changes + - Adding a new, commonly-requested Netscript function + - Fixing or improving UI elements + - Adding game settings/options + - Adding a new Terminal command +- Code Refactors that conform to good/standard practices ##### Contributions that will not be Accepted without prior approval -* Changes that directly affect the game's balance -* New gameplay mechanics + +- Changes that directly affect the game's balance +- New gameplay mechanics #### Submitting a Pull Request + When submitting a pull request with your code contributions, please abide by the following rules: - - Work in a branch forked from `dev` to isolate the new code - - Ensure you have latest from the [game's main - repository](danielyxie/bitburner@dev) - - Rebase your branch if necessary - - Run the game locally to test out your changes - - When submitting the pull request, make sure that the base fork is - _danielyxie/bitburner_ and the base is _dev_. - - If your changes affect the game's UI, attach some screenshots or GIFs showing - the changes to the UI - - If your changes affect Netscript, provide some - scripts that can be used to test the Netscript changes. - - Ensure you have run `npm run lint` to make sure your changes conform to the - rules enforced across the code base. The command will fail if any of the - linters find a violation. - - Do not check in any bundled files (`dist\*.bundle.js`) or the `index.html` - in the root of the repository. These will be updated as part of official - releases. +- Work in a branch forked from `dev` to isolate the new code +- Ensure you have latest from the [game's main + repository](danielyxie/bitburner@dev) +- Rebase your branch if necessary +- Run the game locally to test out your changes +- When submitting the pull request, make sure that the base fork is + _danielyxie/bitburner_ and the base is _dev_. +- If your changes affect the game's UI, attach some screenshots or GIFs showing + the changes to the UI +- If your changes affect Netscript, provide some + scripts that can be used to test the Netscript changes. +- Ensure you have run `npm run lint` to make sure your changes conform to the + rules enforced across the code base. The command will fail if any of the + linters find a violation. +- Do not check in any bundled files (`dist\*.bundle.js`) or the `index.html` + in the root of the repository. These will be updated as part of official + releases. ## As a Documentor + To contribute to and view your changes to the BitBurner documentation, you will need to have Python installed, along with [Sphinx](http://www.sphinx-doc.org). Before submitting your code for a pull request, please try to follow these rules: - - Work in a branch forked from `dev` to isolate the new code - - Ensure you have latest from the [game's main - repository](danielyxie/bitburner@dev) - - Rebase your branch if necessary - - When submitting the pull request, make sure that the base fork is - _danielyxie/bitburner_ and the base is _dev_. - - Do not check in any generated files under `doc\`. The documentation is built - automatically by ReadTheDocs. + +- Work in a branch forked from `dev` to isolate the new code +- Ensure you have latest from the [game's main + repository](danielyxie/bitburner@dev) +- Rebase your branch if necessary +- When submitting the pull request, make sure that the base fork is + _danielyxie/bitburner_ and the base is _dev_. +- Do not check in any generated files under `doc\`. The documentation is built + automatically by ReadTheDocs. diff --git a/README.md b/README.md index be38d849a..bb0527d93 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # Bitburner + Bitburner is a programming-based [incremental game](https://en.wikipedia.org/wiki/Incremental_game) that revolves around hacking and cyberpunk themes. The game can be played at https://danielyxie.github.io/bitburner. # Documentation + The game's official documentation can be found on [Read The Docs](http://bitburner.readthedocs.io/). Please note that this is still a work-in-progress. @@ -16,6 +18,7 @@ For further guidance, please refer to the "As A Documentor" section of [CONTRIBUTING](CONTRIBUTING.md). # Contribution + There are many ways to contribute to the game. It can be as simple as fixing a typo, correcting a bug, or improving the UI. For guidance on doing so, please refer to the [CONTRIBUTING](CONTRIBUTING.md) document. diff --git a/README_contribution.md b/README_contribution.md index 98c93a613..f43b1718b 100644 --- a/README_contribution.md +++ b/README_contribution.md @@ -1,7 +1,7 @@ -Deploying a new version ------------------------ +## Deploying a new version Update the following + - `src/Constants.ts` `Version` and `LatestUpdate` - `package.json` `version` - `doc/source/conf.py` `version` and `release` @@ -9,15 +9,13 @@ Update the following - post to discord - post to reddit.com/r/Bitburner -Deploying `dev` to the Beta Branch ----------------------------------- +## Deploying `dev` to the Beta Branch TODO -Development Workflow Best Practices ------------------------------------ +## Development Workflow Best Practices - Work in a new branch forked from the `dev` branch to isolate your new code - - Keep code-changes on a branch as small as possible. This makes it easier for code review. Each branch should be its own independent feature. - - Regularly rebase your branch against `dev` to make sure you have the latest updates pulled. - - When merging, always merge your branch into `dev`. When releasing a new update, then merge `dev` into `master` + - Keep code-changes on a branch as small as possible. This makes it easier for code review. Each branch should be its own independent feature. + - Regularly rebase your branch against `dev` to make sure you have the latest updates pulled. + - When merging, always merge your branch into `dev`. When releasing a new update, then merge `dev` into `master` diff --git a/babel.config.js b/babel.config.js index 85df7f606..88d73ecaf 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,9 +1,9 @@ const TEST = process.env.NODE_ENV === "test"; module.exports = { - "presets": [ - "@babel/preset-react", - TEST && "@babel/preset-env", - TEST && "@babel/preset-typescript", - ].filter(Boolean), -} + presets: [ + "@babel/preset-react", + TEST && "@babel/preset-env", + TEST && "@babel/preset-typescript", + ].filter(Boolean), +}; diff --git a/css/_mixins.scss b/css/_mixins.scss index ee30eaef3..ab256a69e 100644 --- a/css/_mixins.scss +++ b/css/_mixins.scss @@ -1,61 +1,61 @@ @mixin animation($property) { - -webkit-animation: $property; - -moz-animation: $property; - -ms-animation: $property; - -o-animation: $property; - animation: $property; + -webkit-animation: $property; + -moz-animation: $property; + -ms-animation: $property; + -o-animation: $property; + animation: $property; } @mixin borderRadius($property) { - -webkit-border-radius: $property; - -moz-border-radius: $property; - border-radius: $property; + -webkit-border-radius: $property; + -moz-border-radius: $property; + border-radius: $property; } @mixin boxShadow($value) { - -webkit-box-shadow: $value; - -moz-box-shadow: $value; - box-shadow: $value; + -webkit-box-shadow: $value; + -moz-box-shadow: $value; + box-shadow: $value; } @mixin keyframes($animationName) { - @-webkit-keyframes #{$animationName} { - $browser: '-webkit-' !global; - @content; - } + @-webkit-keyframes #{$animationName} { + $browser: "-webkit-" !global; + @content; + } - @-moz-keyframes #{$animationName} { - $browser: '-moz-' !global; - @content; - } + @-moz-keyframes #{$animationName} { + $browser: "-moz-" !global; + @content; + } - @-ms-keyframes #{$animationName} { - $browser: '-ms-' !global; - @content; - } + @-ms-keyframes #{$animationName} { + $browser: "-ms-" !global; + @content; + } - @-o-keyframes #{$animationName} { - $browser: '-o-' !global; - @content; - } + @-o-keyframes #{$animationName} { + $browser: "-o-" !global; + @content; + } - @keyframes #{$animationName} { - $browser: '' !global; - @content; - } + @keyframes #{$animationName} { + $browser: "" !global; + @content; + } } @mixin transform($property) { - -webkit-transform: $property; - -moz-transform: $property; - -ms-transform: $property; - -o-transform: $property; - transform: $property; + -webkit-transform: $property; + -moz-transform: $property; + -ms-transform: $property; + -o-transform: $property; + transform: $property; } @mixin userSelect($value) { - -webkit-user-select: $value; - -moz-user-select: $value; - -ms-user-select: $value; - user-select: $value; + -webkit-user-select: $value; + -moz-user-select: $value; + -ms-user-select: $value; + user-select: $value; } diff --git a/css/_reset.scss b/css/_reset.scss index 84877ec88..3f6a10dee 100644 --- a/css/_reset.scss +++ b/css/_reset.scss @@ -1,15 +1,15 @@ @import "theme"; * { - font-size: $defaultFontSize; - font-family: $fontFamily; + font-size: $defaultFontSize; + font-family: $fontFamily; } *, *:before, *:after { - margin: 0; - padding: 0; - box-sizing: border-box; - vertical-align: top; + margin: 0; + padding: 0; + box-sizing: border-box; + vertical-align: top; } diff --git a/css/_theme.scss b/css/_theme.scss index 0b727d702..9322ede9e 100644 --- a/css/_theme.scss +++ b/css/_theme.scss @@ -1,4 +1,5 @@ -$fontFamily: 'Lucida Console', 'Lucida Sans Unicode', 'Fira Mono', 'Consolas', 'Courier New', Courier, monospace, 'Times New Roman'; +$fontFamily: "Lucida Console", "Lucida Sans Unicode", "Fira Mono", "Consolas", + "Courier New", Courier, monospace, "Times New Roman"; $defaultFontSize: 16px; /* COLORS */ diff --git a/css/activescripts.scss b/css/activescripts.scss index 8dbfbc2e3..ecc4399a8 100644 --- a/css/activescripts.scss +++ b/css/activescripts.scss @@ -1,126 +1,132 @@ @import "theme"; .active-scripts-list { - list-style-type: none; + list-style-type: none; } #active-scripts-container { - position: fixed; - padding-top: 10px; + position: fixed; + padding-top: 10px; - > p { - width: 70%; - margin: 6px; - padding: 4px; - } + > p { + width: 70%; + margin: 6px; + padding: 4px; + } - .accordion-header { - > pre { - color: white; - } + .accordion-header { + > pre { + color: white; } + } } .active-scripts-server-header { - background-color: #444; - font-size: $defaultFontSize * 1.25; + background-color: #444; + font-size: $defaultFontSize * 1.25; + color: #fff; + margin: 6px 6px 0 6px; + padding: 6px; + cursor: pointer; + width: 60%; + text-align: left; + border: none; + outline: none; + + &:after { + content: "\02795"; /* "plus" sign (+) */ + font-size: $defaultFontSize * 0.8125; color: #fff; - margin: 6px 6px 0 6px; - padding: 6px; - cursor: pointer; - width: 60%; - text-align: left; - border: none; - outline: none; + float: right; + margin-left: 5px; + } - &:after { - content: '\02795'; /* "plus" sign (+) */ - font-size: $defaultFontSize * 0.8125; - color: #fff; - float: right; - margin-left: 5px; - } - - &.active, &:hover { - background-color: #555; - } + &.active, + &:hover { + background-color: #555; + } } .active-scripts-server-header.active { - &:after { - content: "\2796"; /* "minus" sign (-) */ - font-size: $defaultFontSize * 0.8125; - color: #fff; - float: right; - margin-left: 5px; - } + &:after { + content: "\2796"; /* "minus" sign (-) */ + font-size: $defaultFontSize * 0.8125; + color: #fff; + float: right; + margin-left: 5px; + } - &:hover { - background-color: #666; - } + &:hover { + background-color: #666; + } } .active-scripts-server-panel { - margin: 0 6px 6px 6px; - padding: 0 6px 6px 6px; - width: 55%; - margin-left: 5%; - display: none; + margin: 0 6px 6px 6px; + padding: 0 6px 6px 6px; + width: 55%; + margin-left: 5%; + display: none; - div, ul, ul > li { - background-color: #555; - } + div, + ul, + ul > li { + background-color: #555; + } } .active-scripts-script-header { + background-color: #555; + border: none; + color: var(--my-font-color); + cursor: pointer; + display: block; + outline: none; + padding: 4px 25px 4px 10px; + position: relative; + text-align: left; + width: auto; + + &:after { + content: "\02795"; /* "plus" sign (+) */ + font-size: $defaultFontSize * 0.8125; + float: right; + margin-left: 5px; + color: transparent; + text-shadow: 0 0 0 var(--my-font-color); + position: absolute; + bottom: 4px; + } + + &.active:after { + content: "\2796"; /* "minus" sign (-) */ + } + + &:hover, + &.active:hover { + background-color: #666; + } + + &.active { background-color: #555; - border: none; - color: var(--my-font-color); - cursor: pointer; - display: block; - outline: none; - padding: 4px 25px 4px 10px; - position: relative; - text-align: left; - width: auto; - - &:after { - content: '\02795'; /* "plus" sign (+) */ - font-size: $defaultFontSize * 0.8125; - float: right; - margin-left: 5px; - color: transparent; - text-shadow: 0 0 0 var(--my-font-color); - position: absolute; - bottom: 4px; - } - - &.active:after { - content: "\2796"; /* "minus" sign (-) */ - } - - &:hover, - &.active:hover { - background-color: #666; - } - - &.active { - background-color: #555; - } + } } .active-scripts-script-panel { - background-color: #555; - display: none; - font-size: 14px; - margin-bottom: 6px; - padding: 0 18px; - width: auto; + background-color: #555; + display: none; + font-size: 14px; + margin-bottom: 6px; + padding: 0 18px; + width: auto; - pre, h2, ul, li { - background-color: #555; - width: auto; - color: #fff; - margin-left: 5%; - } + pre, + h2, + ul, + li { + background-color: #555; + width: auto; + color: #fff; + margin-left: 5%; + } } diff --git a/css/augmentations.scss b/css/augmentations.scss index d1043459d..e47f159bd 100644 --- a/css/augmentations.scss +++ b/css/augmentations.scss @@ -6,25 +6,25 @@ @import "theme"; #augmentations-container { - position: fixed; - padding-top: 10px; + position: fixed; + padding-top: 10px; } #augmentations-content { - > p { - font-size: $defaultFontSize * 0.875; - width: 70%; - } + > p { + font-size: $defaultFontSize * 0.875; + width: 70%; + } } .augmentations-list { - button, - div { - color: var(--my-font-color); - text-decoration: none; - } + button, + div { + color: var(--my-font-color); + text-decoration: none; + } - button { - padding: 4px; - } + button { + padding: 4px; + } } diff --git a/css/bladeburner.scss b/css/bladeburner.scss index ef46b422f..43fb76f4b 100644 --- a/css/bladeburner.scss +++ b/css/bladeburner.scss @@ -1,135 +1,135 @@ @import "theme"; #bladeburner-container { - a, - div, - p, - pre, - td { - font-size: $defaultFontSize * 0.8125; - } + a, + div, + p, + pre, + td { + font-size: $defaultFontSize * 0.8125; + } } .bladeburner-action { - border: 1px solid #fff; - margin: 7px; - padding: 7px; - white-space: pre-wrap; + border: 1px solid #fff; + margin: 7px; + padding: 7px; + white-space: pre-wrap; - pre { - white-space: pre-wrap; - } + pre { + white-space: pre-wrap; + } } /* Whatever action is currently active */ .bladeburner-active-action { - border: 4px solid #fff; + border: 4px solid #fff; } /* Action & Skills panel navigation button */ %bladeburner-nav-button { - border: 1px solid #fff; - margin: 2px; - padding: 2px; - color: #fff; + border: 1px solid #fff; + margin: 2px; + padding: 2px; + color: #fff; } .bladeburner-nav-button { - @extend %bladeburner-nav-button; + @extend %bladeburner-nav-button; - &:hover { - background-color: #3d4044; - } + &:hover { + background-color: #3d4044; + } } .bladeburner-nav-button-inactive { - @extend %bladeburner-nav-button; + @extend %bladeburner-nav-button; - text-decoration: none; - background-color: #555; - cursor: default; - pointer-events: none; + text-decoration: none; + background-color: #555; + cursor: default; + pointer-events: none; } /* Checkbox for (de)selecting autoleveling */ .bbcheckbox { - position: relative; - display: inline; - label { - width: 20px; - height: 20px; - cursor: pointer; - position: absolute; - top: 0; - left: 0; - background: black; - border-width: 1px; - border-color: white; - border-style: solid; - &:after { - content: ''; - width: 9px; - height: 5px; - position: absolute; - top: 5px; - left: 5px; - border: 3px solid white; - border-top: none; - border-right: none; - opacity: 0; - transform: rotate(-45deg); - } + position: relative; + display: inline; + label { + width: 20px; + height: 20px; + cursor: pointer; + position: absolute; + top: 0; + left: 0; + background: black; + border-width: 1px; + border-color: white; + border-style: solid; + &:after { + content: ""; + width: 9px; + height: 5px; + position: absolute; + top: 5px; + left: 5px; + border: 3px solid white; + border-top: none; + border-right: none; + opacity: 0; + transform: rotate(-45deg); } - input[type="checkbox"] { - margin: 3px; - visibility: hidden; - &:checked + label:after { - opacity: 1; - } + } + input[type="checkbox"] { + margin: 3px; + visibility: hidden; + &:checked + label:after { + opacity: 1; } + } } /* Bladeburner Console */ .bladeburner-console-div { - display: inline-block; - width: 40%; - border: 1px solid #fff; - overflow: auto; - height: 100%; - position: absolute; + display: inline-block; + width: 40%; + border: 1px solid #fff; + overflow: auto; + height: 100%; + position: absolute; } .bladeburner-console-table { - height: auto; - overflow: auto; - table-layout: fixed; - width: 100%; + height: auto; + overflow: auto; + table-layout: fixed; + width: 100%; } .bladeburner-console-input-row { - transition: height 1s; - width: 100%; + transition: height 1s; + width: 100%; } .bladeburner-console-input-cell { - display: flex; + display: flex; } .bladeburner-console-input { - display: inline-block; - padding: 0 !important; - margin: 0 !important; - border: 0; - background-color: var(--my-background-color); - font-size: $defaultFontSize * 0.8125; - outline: none; - color: var(--my-font-color); - flex: 1 1 auto; + display: inline-block; + padding: 0 !important; + margin: 0 !important; + border: 0; + background-color: var(--my-background-color); + font-size: $defaultFontSize * 0.8125; + outline: none; + color: var(--my-font-color); + flex: 1 1 auto; } .bladeburner-console-line { - word-wrap: break-word; - hyphens: auto; - -webkit-hyphens: auto; - -moz-hyphens: auto; + word-wrap: break-word; + hyphens: auto; + -webkit-hyphens: auto; + -moz-hyphens: auto; } diff --git a/css/buttons.scss b/css/buttons.scss index 8a5918d5b..4b86a4461 100644 --- a/css/buttons.scss +++ b/css/buttons.scss @@ -11,80 +11,80 @@ /* Remove default - - - - - + +
  • + +
  • + + + + - -
  • - -
  • - - - - - + +
  • + +
  • + + + + + - -
  • - -
  • - - - - - - - + +
  • + +
  • + + + + + + + -
  • - -
  • - - - - +
  • + +
  • + + + + - + -
    +
    +
    +

    + Script name: +

    + +
    -
    -

    Script name:

    - +
    + +
    +
    - -
    - - -
    -
    +
    -

    Script Editor Options

    -
    +

    Script Editor Options

    +
    -
    +
    -
    +
    -
    +
    -
    +
    - -
    + +
    -
    - - -
    +
    + + +
    -
    - - -
    +
    + + +
    -
    +
    - -
    - -
    -
    -
    -
    - -
    + + +
    +
    +
    +
    +
    + - + -
    + - -
    + +
    - - - + + +
    $ - -
    + $ + +
    -
    +
    - -
    + +
    -
    +
    - -
    -

    This page displays a list of all of your scripts that are currently running across every machine. It also - provides information about each script's production. The scripts are categorized by the hostname of the servers on which - they are running.

    -

    Total online production of - Active scripts: $0.000 / sec
    - Total online production since last Aug installation: $0.000 - ($0.000 / sec)

    - -
    + +
    +

    + This page displays a list of all of your scripts that are currently + running across every machine. It also provides information about each + script's production. The scripts are categorized by the hostname of + the servers on which they are running. +

    +

    + Total online production of Active scripts: + $0.000 / + sec
    + Total online production since last Aug installation: + $0.000 + ($0.000 + / sec) +

    + +
    - -
    + +
    -
    +
    - -
    + +

    - This page displays any programs that you are able to create. Writing the code for a program takes time, which - can vary based on how complex the program is. If you are working on creating a program you can cancel - at any time. Your progress will be saved and you can continue later. + This page displays any programs that you are able to create. Writing + the code for a program takes time, which can vary based on how complex + the program is. If you are working on creating a program you can + cancel at any time. Your progress will be saved and you can continue + later.

    -
    +
    - -
    + +
    - -
    + +
    - -
    + +
    - -
    - - -
    + +
    - -
    -

    Tutorial (AKA Links to Documentation)

    - - Getting Started

    - - Servers & Networking

    - - Hacking

    - - Scripts

    - - Netscript Programming Language

    - - Traveling

    - - Companies

    - - Infiltration

    - - Factions

    - - Augmentations

    - - Keyboard Shortcuts -
    + +
    - -
    -
    + +
    +

    Tutorial (AKA Links to Documentation)

    + + Getting Started

    + + Servers & Networking

    + + Hacking

    + + Scripts

    + + Netscript Programming Language

    + + Traveling

    + + Companies

    + + Infiltration

    + + Factions

    + + Augmentations

    + + Keyboard Shortcuts +
    -
    -
    + +
    -
    +
    + +
    -
    +
    -
    +
    -
    +
    - - - - - - - - - -
    -
    + +
    - -
    -

    + +
    +

    - - -
    + + +
    - -
    -
    -
    + +
    +
    +
    - -
    + +
    - -
    -
    -

    - - - -
    -
    + +
    +
    +

    + + + +
    +
    - -
    -
    -
    + +
    +
    +
    +
    +
    + + +
    -
    - - -
    -
    -
    +
    - -
    + +

    -
    +
    - - +
    -
    -
    Loading Bitburner...
    -
    - -

    If the game fails to load, consider killing all scripts

    -
    +
    +
    Loading Bitburner...
    +
    + +

    + If the game fails to load, consider + killing all scripts +

    +
    - - - - - + + + + + + + diff --git a/jest.config.js b/jest.config.js index a4ad5e39c..4fb0d1462 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,9 +1,9 @@ module.exports = { - setupFiles: ["./jest.setup.js"], - moduleFileExtensions: ["ts", "tsx", "js", "jsx"], - transform: { - "^.+\\.(js|jsx|ts|tsx)$": "babel-jest", - }, - // testMatch: ["**/?(*.)+(test).[jt]s?(x)"], - testEnvironment: "jsdom", + setupFiles: ["./jest.setup.js"], + moduleFileExtensions: ["ts", "tsx", "js", "jsx"], + transform: { + "^.+\\.(js|jsx|ts|tsx)$": "babel-jest", + }, + // testMatch: ["**/?(*.)+(test).[jt]s?(x)"], + testEnvironment: "jsdom", }; diff --git a/package-lock.json b/package-lock.json index 45fd0eb94..f72e12e7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "bitburner", "version": "0.52.9", "hasInstallScript": true, "license": "SEE LICENSE IN license.txt", @@ -8205,7 +8204,6 @@ "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "peer": true, "engines": { "node": ">=6" } @@ -14966,7 +14964,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.0.tgz", "integrity": "sha512-D2EBNvUG+mJyhn+M1C858k0f2Fc4KxXvbEX2WCMXroV10212JwfYqaBJ336ECBSz5X9L5LRoamxb7AJtg3KaJA==", - "peer": true, "dependencies": { "esm": "^3.2.25", "mhchemparser": "^4.1.0", @@ -15201,8 +15198,7 @@ "node_modules/mhchemparser": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.1.1.tgz", - "integrity": "sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA==", - "peer": true + "integrity": "sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA==" }, "node_modules/micromatch": { "version": "3.1.10", @@ -15455,8 +15451,7 @@ "node_modules/mj-context-menu": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", - "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==", - "peer": true + "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" }, "node_modules/mkdirp": { "version": "0.5.1", @@ -20059,7 +20054,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.3.3.tgz", "integrity": "sha512-0exWw+0XauLjat+f/aFeo5T8SiDsO1JtwpY3qgJE4cWt+yL/Stl0WP4VNDWdh7lzGkubUD9lWP4J1ASnORXfyQ==", - "peer": true, "dependencies": { "commander": ">=7.0.0", "wicked-good-xpath": "^1.3.0", @@ -20073,7 +20067,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.1.0.tgz", "integrity": "sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==", - "peer": true, "engines": { "node": ">= 12" } @@ -23460,8 +23453,7 @@ "node_modules/wicked-good-xpath": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", - "integrity": "sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w=", - "peer": true + "integrity": "sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w=" }, "node_modules/wide-align": { "version": "1.1.3", @@ -23608,7 +23600,6 @@ "version": "0.1.31", "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==", - "peer": true, "engines": { "node": ">=0.1" } @@ -30225,8 +30216,7 @@ "esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "peer": true + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" }, "espree": { "version": "7.3.1", @@ -35514,7 +35504,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.0.tgz", "integrity": "sha512-D2EBNvUG+mJyhn+M1C858k0f2Fc4KxXvbEX2WCMXroV10212JwfYqaBJ336ECBSz5X9L5LRoamxb7AJtg3KaJA==", - "peer": true, "requires": { "esm": "^3.2.25", "mhchemparser": "^4.1.0", @@ -35526,7 +35515,9 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/mathjax-react/-/mathjax-react-1.0.6.tgz", "integrity": "sha512-GlkPAhaY0FKc95TMdo33mxNZHb+3xRgfgc3YcnQ0cZxvnM1UaF0o8mRI5y5xIwi3JnioeYuukZJWCbIZkACIVw==", - "requires": {} + "requires": { + "mathjax-full": "^3.0.4" + } }, "mathml-tag-names": { "version": "2.1.0", @@ -35691,8 +35682,7 @@ "mhchemparser": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.1.1.tgz", - "integrity": "sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA==", - "peer": true + "integrity": "sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA==" }, "micromatch": { "version": "3.1.10", @@ -35901,8 +35891,7 @@ "mj-context-menu": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", - "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==", - "peer": true + "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" }, "mkdirp": { "version": "0.5.1", @@ -39798,7 +39787,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.3.3.tgz", "integrity": "sha512-0exWw+0XauLjat+f/aFeo5T8SiDsO1JtwpY3qgJE4cWt+yL/Stl0WP4VNDWdh7lzGkubUD9lWP4J1ASnORXfyQ==", - "peer": true, "requires": { "commander": ">=7.0.0", "wicked-good-xpath": "^1.3.0", @@ -39808,8 +39796,7 @@ "commander": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.1.0.tgz", - "integrity": "sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==", - "peer": true + "integrity": "sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA==" } } }, @@ -42606,8 +42593,7 @@ "wicked-good-xpath": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", - "integrity": "sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w=", - "peer": true + "integrity": "sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w=" }, "wide-align": { "version": "1.1.3", @@ -42734,8 +42720,7 @@ "xmldom-sre": { "version": "0.1.31", "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", - "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==", - "peer": true + "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==" }, "xregexp": { "version": "4.0.0", diff --git a/scripts/.eslintrc.js b/scripts/.eslintrc.js index f3c3c56bc..1de7cbccd 100644 --- a/scripts/.eslintrc.js +++ b/scripts/.eslintrc.js @@ -1,432 +1,366 @@ module.exports = { - "env": { - "node": true, - "es6": true + env: { + node: true, + es6: true, + }, + extends: "eslint:recommended", + parserOptions: { + ecmaVersion: 8, + sourceType: "module", + ecmaFeatures: { + experimentalObjectRestSpread: true, }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": 8, - "sourceType": "module", - "ecmaFeatures": { - "experimentalObjectRestSpread": true - } - }, - "rules": { - "accessor-pairs": [ - "error", - { - "setWithoutGet": true, - "getWithoutSet": false - } - ], - "array-bracket-newline": ["error"], - "array-bracket-spacing": ["error"], - "array-callback-return": ["error"], - "array-element-newline": ["error"], - "arrow-body-style": ["error"], - "arrow-parens": ["error"], - "arrow-spacing": ["error"], - "block-scoped-var": ["error"], - "block-spacing": ["error"], - "brace-style": ["error"], - "callback-return": ["error"], - "camelcase": ["error"], - "capitalized-comments": ["error"], - "class-methods-use-this": ["error"], - "comma-dangle": ["error"], - "comma-spacing": ["error"], - "comma-style": [ - "error", - "last" - ], - "complexity": ["error"], - "computed-property-spacing": [ - "error", - "never" - ], - "consistent-return": ["error"], - "consistent-this": ["error"], - "constructor-super": ["error"], - "curly": ["error"], - "default-case": ["error"], - "dot-location": [ - "error", - "property" - ], - "dot-notation": ["error"], - "eol-last": ["error"], - "eqeqeq": ["error"], - "for-direction": ["error"], - "func-call-spacing": ["error"], - "func-name-matching": ["error"], - "func-names": [ - "error", - "never" - ], - "func-style": ["error"], - "function-paren-newline": ["error"], - "generator-star-spacing": [ - "error", - "before" - ], - "getter-return": [ - "error", - { - "allowImplicit": false - } - ], - "global-require": ["error"], - "guard-for-in": ["error"], - "handle-callback-err": ["error"], - "id-blacklist": ["error"], - "id-length": ["error"], - "id-match": ["error"], - "implicit-arrow-linebreak": [ - "error", - "beside" - ], - "indent": [ - "error", - 4, - { - "SwitchCase": 1 - } - ], - "init-declarations": ["error"], - "jsx-quotes": ["error"], - "key-spacing": ["error"], - "keyword-spacing": ["error"], - "line-comment-position": ["error"], - "linebreak-style": [ - "error", - "windows" - ], - "lines-around-comment": ["error"], - "lines-between-class-members": ["error"], - "max-depth": ["error"], - "max-len": [ - "error", - 160 - ], - "max-lines": [ - "error", - { - "skipBlankLines": true, - "skipComments": true - } - ], - "max-nested-callbacks": ["error"], - "max-params": ["error"], - "max-statements": ["error"], - "max-statements-per-line": ["error"], - "multiline-comment-style": [ - "off", - "starred-block" - ], - "multiline-ternary": [ - "error", - "never" - ], - "new-cap": ["error"], - "new-parens": ["error"], - "newline-before-return": ["error" // TODO: configure this... - ], - "newline-per-chained-call": ["error"], - "no-alert": ["error"], - "no-array-constructor": ["error"], - "no-await-in-loop": ["error"], - "no-bitwise": ["error"], - "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-cond-assign": [ - "error", - "except-parens" - ], - "no-confusing-arrow": ["error"], - "no-console": ["error"], - "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": ["error"], - "no-empty": [ - "error", - { - "allowEmptyCatch": false - } - ], - "no-empty-character-class": ["error"], - "no-empty-function": ["error"], - "no-empty-pattern": ["error"], - "no-eq-null": ["error"], - "no-ex-assign": ["error"], - "no-extra-boolean-cast": ["error"], - "no-extra-parens": [ - "error", - "all", - { - "conditionalAssign": false - } - ], - "no-extra-semi": ["error"], - "no-eval": ["error"], - "no-extend-native": ["error"], - "no-extra-bind": ["error"], - "no-extra-label": ["error"], - "no-extra-parens": ["error"], - "no-fallthrough": ["error"], - "no-floating-decimal": ["error"], - "no-func-assign": ["error"], - "no-global-assign": ["error"], - "no-implicit-coercion": ["error"], - "no-implicit-globals": ["error"], - "no-implied-eval": ["error"], - "no-inline-comments": ["error"], - "no-inner-declarations": [ - "error", - "both" - ], - "no-invalid-regexp": ["error"], - "no-invalid-this": ["error"], - "no-irregular-whitespace": [ - "error", - { - "skipStrings": false, - "skipComments": false, - "skipRegExps": false, - "skipTemplates": false - } - ], - "no-iterator": ["error"], - "no-label-var": ["error"], - "no-labels": ["error"], - "no-lone-blocks": ["error"], - "no-lonely-if": ["error"], - "no-loop-func": ["error"], - "no-magic-numbers": [ - "error", - { - "ignore": [ - -1, - 0, - 1 - ], - "ignoreArrayIndexes": true - } - ], - "no-mixed-operators": ["error"], - "no-mixed-requires": ["error"], - "no-mixed-spaces-and-tabs": ["error"], - "no-multi-assign": ["error"], - "no-multi-spaces": ["error"], - "no-multi-str": ["error"], - "no-multiple-empty-lines": [ - "error", - { - "max": 1 - } - ], - "no-native-reassign": ["error"], - "no-negated-condition": ["error"], - "no-negated-in-lhs": ["error"], - "no-nested-ternary": ["error"], - "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": ["error"], - "no-path-concat": ["error"], - "no-plusplus": [ - "error", - { - "allowForLoopAfterthoughts": true - } - ], - "no-process-env": ["error"], - "no-process-exit": ["error"], - "no-proto": ["error"], - "no-prototype-builtins": ["error"], - "no-redeclare": ["error"], - "no-regex-spaces": ["error"], - "no-restricted-globals": ["error"], - "no-restricted-imports": ["error"], - "no-restricted-modules": ["error"], - "no-restricted-properties": [ - "error", - { - "object": "console", - "property": "log", - "message": "'log' is too general, use an appropriate level when logging." - } - ], - "no-restricted-syntax": ["error"], - "no-return-assign": ["error"], - "no-return-await": ["error"], - "no-script-url": ["error"], - "no-self-assign": [ - "error", - { - "props": false - } - ], - "no-self-compare": ["error"], - "no-sequences": ["error"], - "no-shadow": ["error"], - "no-shadow-restricted-names": ["error"], - "no-spaced-func": ["error"], - "no-sparse-arrays": ["error"], - "no-sync": ["error"], - "no-tabs": ["error"], - "no-template-curly-in-string": ["error"], - "no-ternary": ["off"], - "no-this-before-super": ["error"], - "no-throw-literal": ["error"], - "no-trailing-spaces": ["error"], - "no-undef": ["error"], - "no-undef-init": ["error"], - "no-undefined": ["error"], - "no-underscore-dangle": ["error"], - "no-unexpected-multiline": ["error"], - "no-unmodified-loop-condition": ["error"], - "no-unneeded-ternary": ["error"], - "no-unreachable": ["error"], - "no-unsafe-finally": ["error"], - "no-unsafe-negation": ["error"], - "no-unused-expressions": ["error"], - "no-unused-labels": ["error"], - "no-unused-vars": ["error"], - "no-use-before-define": ["error"], - "no-useless-call": ["error"], - "no-useless-computed-key": ["error"], - "no-useless-concat": ["error"], - "no-useless-constructor": ["error"], - "no-useless-escape": ["error"], - "no-useless-rename": [ - "error", - { - "ignoreDestructuring": false, - "ignoreExport": false, - "ignoreImport": false - } - ], - "no-useless-return": ["error"], - "no-var": ["error"], - "no-void": ["error"], - "no-warning-comments": ["error"], - "no-whitespace-before-property": ["error"], - "no-with": ["error"], - "nonblock-statement-body-position": [ - "error", - "below" - ], - "object-curly-newline": ["error"], - "object-curly-spacing": ["error"], - "object-property-newline": ["error"], - "object-shorthand": ["error"], - "one-var": ["off"], - "one-var-declaration-per-line": ["error"], - "operator-assignment": ["error"], - "operator-linebreak": [ - "error", - "none" - ], - "padded-blocks": ["off"], - "padding-line-between-statements": ["error"], - "prefer-arrow-callback": ["error"], - "prefer-const": ["error"], - "prefer-destructuring": ["off"], - "prefer-numeric-literals": ["error"], - "prefer-promise-reject-errors": ["off"], - "prefer-reflect": ["error"], - "prefer-rest-params": ["error"], - "prefer-spread": ["error"], - "prefer-template": ["error"], - "quote-props": ["error"], - "quotes": ["error"], - "radix": [ - "error", - "as-needed" - ], - "require-await": ["error"], - "require-jsdoc": ["off"], - "require-yield": ["error"], - "rest-spread-spacing": [ - "error", - "never" - ], - "semi": ["error"], - "semi-spacing": ["error"], - "semi-style": [ - "error", - "last" - ], - "sort-imports": ["error"], - "sort-keys": ["error"], - "sort-vars": ["error"], - "space-before-blocks": ["error"], - "space-before-function-paren": ["off"], - "space-in-parens": ["error"], - "space-infix-ops": ["error"], - "space-unary-ops": ["error"], - "spaced-comment": ["error"], - "strict": ["error"], - "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": ["error"], - "valid-typeof": ["error"], - "vars-on-top": ["error"], - "wrap-iife": [ - "error", - "any" - ], - "wrap-regex": ["error"], - "yield-star-spacing": [ - "error", - "before" - ], - "yoda": [ - "error", - "never" - ] - } + }, + rules: { + "accessor-pairs": [ + "error", + { + setWithoutGet: true, + getWithoutSet: false, + }, + ], + "array-bracket-newline": ["error"], + "array-bracket-spacing": ["error"], + "array-callback-return": ["error"], + "array-element-newline": ["error"], + "arrow-body-style": ["error"], + "arrow-parens": ["error"], + "arrow-spacing": ["error"], + "block-scoped-var": ["error"], + "block-spacing": ["error"], + "brace-style": ["error"], + "callback-return": ["error"], + camelcase: ["error"], + "capitalized-comments": ["error"], + "class-methods-use-this": ["error"], + "comma-dangle": ["error"], + "comma-spacing": ["error"], + "comma-style": ["error", "last"], + complexity: ["error"], + "computed-property-spacing": ["error", "never"], + "consistent-return": ["error"], + "consistent-this": ["error"], + "constructor-super": ["error"], + curly: ["error"], + "default-case": ["error"], + "dot-location": ["error", "property"], + "dot-notation": ["error"], + "eol-last": ["error"], + eqeqeq: ["error"], + "for-direction": ["error"], + "func-call-spacing": ["error"], + "func-name-matching": ["error"], + "func-names": ["error", "never"], + "func-style": ["error"], + "function-paren-newline": ["error"], + "generator-star-spacing": ["error", "before"], + "getter-return": [ + "error", + { + allowImplicit: false, + }, + ], + "global-require": ["error"], + "guard-for-in": ["error"], + "handle-callback-err": ["error"], + "id-blacklist": ["error"], + "id-length": ["error"], + "id-match": ["error"], + "implicit-arrow-linebreak": ["error", "beside"], + indent: [ + "error", + 4, + { + SwitchCase: 1, + }, + ], + "init-declarations": ["error"], + "jsx-quotes": ["error"], + "key-spacing": ["error"], + "keyword-spacing": ["error"], + "line-comment-position": ["error"], + "linebreak-style": ["error", "windows"], + "lines-around-comment": ["error"], + "lines-between-class-members": ["error"], + "max-depth": ["error"], + "max-len": ["error", 160], + "max-lines": [ + "error", + { + skipBlankLines: true, + skipComments: true, + }, + ], + "max-nested-callbacks": ["error"], + "max-params": ["error"], + "max-statements": ["error"], + "max-statements-per-line": ["error"], + "multiline-comment-style": ["off", "starred-block"], + "multiline-ternary": ["error", "never"], + "new-cap": ["error"], + "new-parens": ["error"], + "newline-before-return": [ + "error", // TODO: configure this... + ], + "newline-per-chained-call": ["error"], + "no-alert": ["error"], + "no-array-constructor": ["error"], + "no-await-in-loop": ["error"], + "no-bitwise": ["error"], + "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-cond-assign": ["error", "except-parens"], + "no-confusing-arrow": ["error"], + "no-console": ["error"], + "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": ["error"], + "no-empty": [ + "error", + { + allowEmptyCatch: false, + }, + ], + "no-empty-character-class": ["error"], + "no-empty-function": ["error"], + "no-empty-pattern": ["error"], + "no-eq-null": ["error"], + "no-ex-assign": ["error"], + "no-extra-boolean-cast": ["error"], + "no-extra-parens": [ + "error", + "all", + { + conditionalAssign: false, + }, + ], + "no-extra-semi": ["error"], + "no-eval": ["error"], + "no-extend-native": ["error"], + "no-extra-bind": ["error"], + "no-extra-label": ["error"], + "no-extra-parens": ["error"], + "no-fallthrough": ["error"], + "no-floating-decimal": ["error"], + "no-func-assign": ["error"], + "no-global-assign": ["error"], + "no-implicit-coercion": ["error"], + "no-implicit-globals": ["error"], + "no-implied-eval": ["error"], + "no-inline-comments": ["error"], + "no-inner-declarations": ["error", "both"], + "no-invalid-regexp": ["error"], + "no-invalid-this": ["error"], + "no-irregular-whitespace": [ + "error", + { + skipStrings: false, + skipComments: false, + skipRegExps: false, + skipTemplates: false, + }, + ], + "no-iterator": ["error"], + "no-label-var": ["error"], + "no-labels": ["error"], + "no-lone-blocks": ["error"], + "no-lonely-if": ["error"], + "no-loop-func": ["error"], + "no-magic-numbers": [ + "error", + { + ignore: [-1, 0, 1], + ignoreArrayIndexes: true, + }, + ], + "no-mixed-operators": ["error"], + "no-mixed-requires": ["error"], + "no-mixed-spaces-and-tabs": ["error"], + "no-multi-assign": ["error"], + "no-multi-spaces": ["error"], + "no-multi-str": ["error"], + "no-multiple-empty-lines": [ + "error", + { + max: 1, + }, + ], + "no-native-reassign": ["error"], + "no-negated-condition": ["error"], + "no-negated-in-lhs": ["error"], + "no-nested-ternary": ["error"], + "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": ["error"], + "no-path-concat": ["error"], + "no-plusplus": [ + "error", + { + allowForLoopAfterthoughts: true, + }, + ], + "no-process-env": ["error"], + "no-process-exit": ["error"], + "no-proto": ["error"], + "no-prototype-builtins": ["error"], + "no-redeclare": ["error"], + "no-regex-spaces": ["error"], + "no-restricted-globals": ["error"], + "no-restricted-imports": ["error"], + "no-restricted-modules": ["error"], + "no-restricted-properties": [ + "error", + { + object: "console", + property: "log", + message: "'log' is too general, use an appropriate level when logging.", + }, + ], + "no-restricted-syntax": ["error"], + "no-return-assign": ["error"], + "no-return-await": ["error"], + "no-script-url": ["error"], + "no-self-assign": [ + "error", + { + props: false, + }, + ], + "no-self-compare": ["error"], + "no-sequences": ["error"], + "no-shadow": ["error"], + "no-shadow-restricted-names": ["error"], + "no-spaced-func": ["error"], + "no-sparse-arrays": ["error"], + "no-sync": ["error"], + "no-tabs": ["error"], + "no-template-curly-in-string": ["error"], + "no-ternary": ["off"], + "no-this-before-super": ["error"], + "no-throw-literal": ["error"], + "no-trailing-spaces": ["error"], + "no-undef": ["error"], + "no-undef-init": ["error"], + "no-undefined": ["error"], + "no-underscore-dangle": ["error"], + "no-unexpected-multiline": ["error"], + "no-unmodified-loop-condition": ["error"], + "no-unneeded-ternary": ["error"], + "no-unreachable": ["error"], + "no-unsafe-finally": ["error"], + "no-unsafe-negation": ["error"], + "no-unused-expressions": ["error"], + "no-unused-labels": ["error"], + "no-unused-vars": ["error"], + "no-use-before-define": ["error"], + "no-useless-call": ["error"], + "no-useless-computed-key": ["error"], + "no-useless-concat": ["error"], + "no-useless-constructor": ["error"], + "no-useless-escape": ["error"], + "no-useless-rename": [ + "error", + { + ignoreDestructuring: false, + ignoreExport: false, + ignoreImport: false, + }, + ], + "no-useless-return": ["error"], + "no-var": ["error"], + "no-void": ["error"], + "no-warning-comments": ["error"], + "no-whitespace-before-property": ["error"], + "no-with": ["error"], + "nonblock-statement-body-position": ["error", "below"], + "object-curly-newline": ["error"], + "object-curly-spacing": ["error"], + "object-property-newline": ["error"], + "object-shorthand": ["error"], + "one-var": ["off"], + "one-var-declaration-per-line": ["error"], + "operator-assignment": ["error"], + "operator-linebreak": ["error", "none"], + "padded-blocks": ["off"], + "padding-line-between-statements": ["error"], + "prefer-arrow-callback": ["error"], + "prefer-const": ["error"], + "prefer-destructuring": ["off"], + "prefer-numeric-literals": ["error"], + "prefer-promise-reject-errors": ["off"], + "prefer-reflect": ["error"], + "prefer-rest-params": ["error"], + "prefer-spread": ["error"], + "prefer-template": ["error"], + "quote-props": ["error"], + quotes: ["error"], + radix: ["error", "as-needed"], + "require-await": ["error"], + "require-jsdoc": ["off"], + "require-yield": ["error"], + "rest-spread-spacing": ["error", "never"], + semi: ["error"], + "semi-spacing": ["error"], + "semi-style": ["error", "last"], + "sort-imports": ["error"], + "sort-keys": ["error"], + "sort-vars": ["error"], + "space-before-blocks": ["error"], + "space-before-function-paren": ["off"], + "space-in-parens": ["error"], + "space-infix-ops": ["error"], + "space-unary-ops": ["error"], + "spaced-comment": ["error"], + strict: ["error"], + "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": ["error"], + "valid-typeof": ["error"], + "vars-on-top": ["error"], + "wrap-iife": ["error", "any"], + "wrap-regex": ["error"], + "yield-star-spacing": ["error", "before"], + yoda: ["error", "never"], + }, }; diff --git a/scripts/engines-check.js b/scripts/engines-check.js index 4bc2e4066..c00ba229d 100644 --- a/scripts/engines-check.js +++ b/scripts/engines-check.js @@ -8,63 +8,74 @@ const path = require("path"); const exec = require("child_process").exec; const semver = require("./semver"); -const getPackageJson = () => new Promise((resolve, reject) => { +const getPackageJson = () => + new Promise((resolve, reject) => { try { - /* eslint-disable-next-line global-require */ - resolve(require(path.resolve(process.cwd(), "package.json"))); + /* eslint-disable-next-line global-require */ + resolve(require(path.resolve(process.cwd(), "package.json"))); } catch (error) { - reject(error); + reject(error); } -}); + }); -const getEngines = (data) => new Promise((resolve, reject) => { +const getEngines = (data) => + new Promise((resolve, reject) => { let versions = null; if (data.engines) { - versions = data.engines; + versions = data.engines; } if (versions) { - resolve(versions); + resolve(versions); } else { - reject("Missing or improper 'engines' property in 'package.json'"); + reject("Missing or improper 'engines' property in 'package.json'"); } -}); + }); -const checkNpmVersion = (engines) => new Promise((resolve, reject) => { +const checkNpmVersion = (engines) => + new Promise((resolve, reject) => { exec("npm -v", (error, stdout, stderr) => { - if (error) { - reject(`Unable to find NPM version\n${stderr}`); - } + if (error) { + reject(`Unable to find NPM version\n${stderr}`); + } - const npmVersion = stdout.trim(); - const engineVersion = engines.npm || ">=0"; + const npmVersion = stdout.trim(); + const engineVersion = engines.npm || ">=0"; - if (semver.satisfies(npmVersion, engineVersion)) { - resolve(); - } else { - reject(`Incorrect npm version\n'package.json' specifies "${engineVersion}", you are currently running "${npmVersion}".`); - } + if (semver.satisfies(npmVersion, engineVersion)) { + resolve(); + } else { + reject( + `Incorrect npm version\n'package.json' specifies "${engineVersion}", you are currently running "${npmVersion}".`, + ); + } }); -}); + }); -const checkNodeVersion = (engines) => new Promise((resolve, reject) => { +const checkNodeVersion = (engines) => + new Promise((resolve, reject) => { const nodeVersion = process.version.substring(1); if (semver.satisfies(nodeVersion, engines.node)) { - resolve(engines); + resolve(engines); } else { - reject(`Incorrect node version\n'package.json' specifies "${engines.node}", you are currently running "${process.version}".`); + reject( + `Incorrect node version\n'package.json' specifies "${engines.node}", you are currently running "${process.version}".`, + ); } -}); + }); getPackageJson() - .then(getEngines) - .then(checkNodeVersion) - .then(checkNpmVersion) - .then(() => true, (error) => { - // Specifically disable these as the error message gets lost in the normal unhandled output. - /* eslint-disable no-console, no-process-exit */ - console.error(error); - process.exit(1); - }); + .then(getEngines) + .then(checkNodeVersion) + .then(checkNpmVersion) + .then( + () => true, + (error) => { + // Specifically disable these as the error message gets lost in the normal unhandled output. + /* eslint-disable no-console, no-process-exit */ + console.error(error); + process.exit(1); + }, + ); diff --git a/scripts/semver.js b/scripts/semver.js index fc0625052..461b71565 100644 --- a/scripts/semver.js +++ b/scripts/semver.js @@ -81,18 +81,30 @@ src[NONNUMERICIDENTIFIER] = "\\d*[a-zA-Z-][a-zA-Z0-9-]*"; // ## Main Version // Three dot-separated numeric identifiers. -src[MAINVERSION] = `(${src[NUMERICIDENTIFIER]})\\.(${src[NUMERICIDENTIFIER]})\\.(${src[NUMERICIDENTIFIER]})`; -src[MAINVERSIONLOOSE] = `(${src[NUMERICIDENTIFIERLOOSE]})\\.(${src[NUMERICIDENTIFIERLOOSE]})\\.(${src[NUMERICIDENTIFIERLOOSE]})`; +src[ + MAINVERSION +] = `(${src[NUMERICIDENTIFIER]})\\.(${src[NUMERICIDENTIFIER]})\\.(${src[NUMERICIDENTIFIER]})`; +src[ + MAINVERSIONLOOSE +] = `(${src[NUMERICIDENTIFIERLOOSE]})\\.(${src[NUMERICIDENTIFIERLOOSE]})\\.(${src[NUMERICIDENTIFIERLOOSE]})`; // ## Pre-release Version Identifier // A numeric identifier, or a non-numeric identifier. -src[PRERELEASEIDENTIFIER] = `(?:${src[NUMERICIDENTIFIER]}|${src[NONNUMERICIDENTIFIER]})`; -src[PRERELEASEIDENTIFIERLOOSE] = `(?:${src[NUMERICIDENTIFIERLOOSE]}|${src[NONNUMERICIDENTIFIER]})`; +src[ + PRERELEASEIDENTIFIER +] = `(?:${src[NUMERICIDENTIFIER]}|${src[NONNUMERICIDENTIFIER]})`; +src[ + PRERELEASEIDENTIFIERLOOSE +] = `(?:${src[NUMERICIDENTIFIERLOOSE]}|${src[NONNUMERICIDENTIFIER]})`; // ## Pre-release Version // Hyphen, followed by one or more dot-separated pre-release version identifiers. -src[PRERELEASE] = `(?:-(${src[PRERELEASEIDENTIFIER]}(?:\\.${src[PRERELEASEIDENTIFIER]})*))`; -src[PRERELEASELOOSE] = `(?:-?(${src[PRERELEASEIDENTIFIERLOOSE]}(?:\\.${src[PRERELEASEIDENTIFIERLOOSE]})*))`; +src[ + PRERELEASE +] = `(?:-(${src[PRERELEASEIDENTIFIER]}(?:\\.${src[PRERELEASEIDENTIFIER]})*))`; +src[ + PRERELEASELOOSE +] = `(?:-?(${src[PRERELEASEIDENTIFIERLOOSE]}(?:\\.${src[PRERELEASEIDENTIFIERLOOSE]})*))`; // ## Build Metadata Identifier // Any combination of digits, letters, or hyphens. @@ -123,10 +135,14 @@ src[XRANGEIDENTIFIERLOOSE] = `${src[NUMERICIDENTIFIERLOOSE]}|x|X|\\*`; src[XRANGEIDENTIFIER] = `${src[NUMERICIDENTIFIER]}|x|X|\\*`; /* eslint-disable-next-line max-len */ -src[XRANGEPLAIN] = `[v=\\s]*(${src[XRANGEIDENTIFIER]})(?:\\.(${src[XRANGEIDENTIFIER]})(?:\\.(${src[XRANGEIDENTIFIER]})(?:${src[PRERELEASE]})?${src[BUILD]}?)?)?`; +src[ + XRANGEPLAIN +] = `[v=\\s]*(${src[XRANGEIDENTIFIER]})(?:\\.(${src[XRANGEIDENTIFIER]})(?:\\.(${src[XRANGEIDENTIFIER]})(?:${src[PRERELEASE]})?${src[BUILD]}?)?)?`; /* eslint-disable-next-line max-len */ -src[XRANGEPLAINLOOSE] = `[v=\\s]*(${src[XRANGEIDENTIFIERLOOSE]})(?:\\.(${src[XRANGEIDENTIFIERLOOSE]})(?:\\.(${src[XRANGEIDENTIFIERLOOSE]})(?:${src[PRERELEASELOOSE]})?${src[BUILD]}?)?)?`; +src[ + XRANGEPLAINLOOSE +] = `[v=\\s]*(${src[XRANGEIDENTIFIERLOOSE]})(?:\\.(${src[XRANGEIDENTIFIERLOOSE]})(?:\\.(${src[XRANGEIDENTIFIERLOOSE]})(?:${src[PRERELEASELOOSE]})?${src[BUILD]}?)?)?`; src[XRANGE] = `^${src[GTLT]}\\s*${src[XRANGEPLAIN]}$`; src[XRANGELOOSE] = `^${src[GTLT]}\\s*${src[XRANGEPLAINLOOSE]}$`; @@ -134,7 +150,9 @@ src[XRANGELOOSE] = `^${src[GTLT]}\\s*${src[XRANGEPLAINLOOSE]}$`; // Coercion. // Extract anything that could conceivably be a part of a valid semver /* eslint-disable-next-line max-len */ -src[COERCE] = `(?:^|[^\\d])(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}})(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?(?:$|[^\\d])`; +src[ + COERCE +] = `(?:^|[^\\d])(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}})(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?(?:$|[^\\d])`; // Tilde ranges. // Meaning is "reasonably at or greater than" @@ -162,7 +180,9 @@ src[COMPARATORLOOSE] = `^${src[GTLT]}\\s*(${LOOSEPLAIN})$|^$`; src[COMPARATOR] = `^${src[GTLT]}\\s*(${FULLPLAIN})$|^$`; // An expression to strip any whitespace between the gtlt and the thing it modifies, so that `> 1.2.3` ==> `>1.2.3` -src[COMPARATORTRIM] = `(\\s*)${src[GTLT]}\\s*(${LOOSEPLAIN}|${src[XRANGEPLAIN]})`; +src[ + COMPARATORTRIM +] = `(\\s*)${src[GTLT]}\\s*(${LOOSEPLAIN}|${src[XRANGEPLAIN]})`; // This one has to use the /g flag re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], "g"); @@ -170,9 +190,13 @@ const comparatorTrimReplace = "$1$2$3"; // Something like `1.2.3 - 1.2.4` // Note that these all use the loose form, because they'll be checked against either the strict or loose comparator form later. -src[HYPHENRANGE] = `^\\s*(${src[XRANGEPLAIN]})\\s+-\\s+(${src[XRANGEPLAIN]})\\s*$`; +src[ + HYPHENRANGE +] = `^\\s*(${src[XRANGEPLAIN]})\\s+-\\s+(${src[XRANGEPLAIN]})\\s*$`; -src[HYPHENRANGELOOSE] = `^\\s*(${src[XRANGEPLAINLOOSE]})\\s+-\\s+(${src[XRANGEPLAINLOOSE]})\\s*$`; +src[ + HYPHENRANGELOOSE +] = `^\\s*(${src[XRANGEPLAINLOOSE]})\\s+-\\s+(${src[XRANGEPLAINLOOSE]})\\s*$`; // Star ranges basically just allow anything at all. src[STAR] = "(<|>)?=?\\s*\\*"; @@ -181,40 +205,40 @@ src[STAR] = "(<|>)?=?\\s*\\*"; // Compile to actual regexp objects. // All are flag-free, unless they were created above with a flag. for (let idx = 0; idx <= STAR; idx++) { - if (!re[idx]) { - re[idx] = new RegExp(src[idx]); - } + if (!re[idx]) { + re[idx] = new RegExp(src[idx]); + } } const ANY = {}; const isX = (id) => !id || id.toLowerCase() === "x" || id === "*"; function compareIdentifiers(left, right) { - const numeric = /^[0-9]+$/; - const leftIsNumeric = numeric.test(left); - const rightIsNumeric = numeric.test(right); - if (leftIsNumeric && !rightIsNumeric) { - return -1; - } + const numeric = /^[0-9]+$/; + const leftIsNumeric = numeric.test(left); + const rightIsNumeric = numeric.test(right); + if (leftIsNumeric && !rightIsNumeric) { + return -1; + } - if (rightIsNumeric && !leftIsNumeric) { - return 1; - } + if (rightIsNumeric && !leftIsNumeric) { + return 1; + } - if (leftIsNumeric && rightIsNumeric) { - left = Number(left); - right = Number(right); - } + if (leftIsNumeric && rightIsNumeric) { + left = Number(left); + right = Number(right); + } - if (left < right) { - return -1; - } + if (left < right) { + return -1; + } - if (left > right) { - return 1; - } + if (left > right) { + return 1; + } - return 0; + return 0; } // This function is passed to string.replace(re[HYPHENRANGE]) @@ -222,60 +246,59 @@ function compareIdentifiers(left, right) { // 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 // 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do // 1.2 - 3.4 => >=1.2.0 <3.5.0 -function hyphenReplace($0, - from, fM, fm, fp, fpr, fb, - to, tM, tm, tp, tpr) { +function hyphenReplace($0, from, fM, fm, fp, fpr, fb, to, tM, tm, tp, tpr) { + if (isX(fM)) { + from = ""; + } else if (isX(fm)) { + from = `>=${fM}.0.0`; + } else if (isX(fp)) { + from = `>=${fM}.${fm}.0`; + } else { + from = `>=${from}`; + } - if (isX(fM)) { - from = ""; - } else if (isX(fm)) { - from = `>=${fM}.0.0`; - } else if (isX(fp)) { - from = `>=${fM}.${fm}.0`; - } else { - from = `>=${from}`; - } + if (isX(tM)) { + to = ""; + } else if (isX(tm)) { + to = `<${Number(tM) + 1}.0.0`; + } else if (isX(tp)) { + to = `<${tM}.${Number(tm) + 1}.0`; + } else if (tpr) { + to = `<=${tM}.${tm}.${tp}-${tpr}`; + } else { + to = `<=${to}`; + } - if (isX(tM)) { - to = ""; - } else if (isX(tm)) { - to = `<${Number(tM) + 1}.0.0`; - } else if (isX(tp)) { - to = `<${tM}.${Number(tm) + 1}.0`; - } else if (tpr) { - to = `<=${tM}.${tm}.${tp}-${tpr}`; - } else { - to = `<=${to}`; - } - - return `${from} ${to}`.trim(); + return `${from} ${to}`.trim(); } function replaceTilde(comp, loose) { - const regex = loose ? re[TILDELOOSE] : re[TILDE]; + const regex = loose ? re[TILDELOOSE] : re[TILDE]; - return comp.replace(regex, function(match, major, minor, patch, prerelease) { - let ret; + return comp.replace(regex, function (match, major, minor, patch, prerelease) { + let ret; - if (isX(major)) { - ret = ""; - } else if (isX(minor)) { - ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`; - } else if (isX(patch)) { - // ~1.2 == >=1.2.0 <1.3.0 - ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`; - } else if (prerelease) { - if (prerelease.charAt(0) !== "-") { - prerelease = `-${prerelease}`; - } - ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${Number(minor) + 1}.0`; - } else { - // ~1.2.3 == >=1.2.3 <1.3.0 - ret = `>=${major}.${minor}.${patch} <${major}.${Number(minor) + 1}.0`; - } + if (isX(major)) { + ret = ""; + } else if (isX(minor)) { + ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`; + } else if (isX(patch)) { + // ~1.2 == >=1.2.0 <1.3.0 + ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`; + } else if (prerelease) { + if (prerelease.charAt(0) !== "-") { + prerelease = `-${prerelease}`; + } + ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${ + Number(minor) + 1 + }.0`; + } else { + // ~1.2.3 == >=1.2.3 <1.3.0 + ret = `>=${major}.${minor}.${patch} <${major}.${Number(minor) + 1}.0`; + } - return ret; - }); + return ret; + }); } // ~, ~> --> * (any, kinda silly) @@ -285,54 +308,62 @@ function replaceTilde(comp, loose) { // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 function replaceTildes(comp, loose) { - return comp - .trim() - .split(/\s+/) - .map((comp1) => replaceTilde(comp1, loose)) - .join(" "); + return comp + .trim() + .split(/\s+/) + .map((comp1) => replaceTilde(comp1, loose)) + .join(" "); } function replaceCaret(comp, loose) { - const regex = loose ? re[CARETLOOSE] : re[CARET]; + const regex = loose ? re[CARETLOOSE] : re[CARET]; - return comp.replace(regex, function(match, major, minor, patch, prerelease) { - let ret; + return comp.replace(regex, function (match, major, minor, patch, prerelease) { + let ret; - if (isX(major)) { - ret = ""; - } else if (isX(minor)) { - ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`; - } else if (isX(patch)) { - if (major === "0") { - ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`; - } else { - ret = `>=${major}.${minor}.0 <${Number(major) + 1}.0.0`; - } - } else if (prerelease) { - if (prerelease.charAt(0) !== "-") { - prerelease = `-${prerelease}`; - } - if (major === "0") { - if (minor === "0") { - ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${minor}.${Number(patch) + 1}`; - } else { - ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${Number(minor) + 1}.0`; - } - } else { - ret = `>=${major}.${minor}.${patch}${prerelease} <${Number(major) + 1}.0.0`; - } - } else if (major === "0") { - if (minor === "0") { - ret = `>=${major}.${minor}.${patch} <${major}.${minor}.${Number(patch) + 1}`; - } else { - ret = `>=${major}.${minor}.${patch} <${major}.${Number(minor) + 1}.0`; - } + if (isX(major)) { + ret = ""; + } else if (isX(minor)) { + ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`; + } else if (isX(patch)) { + if (major === "0") { + ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`; + } else { + ret = `>=${major}.${minor}.0 <${Number(major) + 1}.0.0`; + } + } else if (prerelease) { + if (prerelease.charAt(0) !== "-") { + prerelease = `-${prerelease}`; + } + if (major === "0") { + if (minor === "0") { + ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${minor}.${ + Number(patch) + 1 + }`; } else { - ret = `>=${major}.${minor}.${patch} <${Number(major) + 1}.0.0`; + ret = `>=${major}.${minor}.${patch}${prerelease} <${major}.${ + Number(minor) + 1 + }.0`; } + } else { + ret = `>=${major}.${minor}.${patch}${prerelease} <${ + Number(major) + 1 + }.0.0`; + } + } else if (major === "0") { + if (minor === "0") { + ret = `>=${major}.${minor}.${patch} <${major}.${minor}.${ + Number(patch) + 1 + }`; + } else { + ret = `>=${major}.${minor}.${patch} <${major}.${Number(minor) + 1}.0`; + } + } else { + ret = `>=${major}.${minor}.${patch} <${Number(major) + 1}.0.0`; + } - return ret; - }); + return ret; + }); } // ^ --> * (any, kinda silly) @@ -342,366 +373,380 @@ function replaceCaret(comp, loose) { // ^1.2.3 --> >=1.2.3 <2.0.0 // ^1.2.0 --> >=1.2.0 <2.0.0 function replaceCarets(comp, loose) { - return comp - .trim() - .split(/\s+/) - .map((comp1) => replaceCaret(comp1, loose)) - .join(" "); + return comp + .trim() + .split(/\s+/) + .map((comp1) => replaceCaret(comp1, loose)) + .join(" "); } function replaceXRange(comp, loose) { - comp = comp.trim(); - const regex = loose ? re[XRANGELOOSE] : re[XRANGE]; + comp = comp.trim(); + const regex = loose ? re[XRANGELOOSE] : re[XRANGE]; - return comp.replace(regex, function(ret, operator, major, minor, patch) { - const xM = isX(major); - const xm = xM || isX(minor); - const xp = xm || isX(patch); - const anyX = xp; + return comp.replace(regex, function (ret, operator, major, minor, patch) { + const xM = isX(major); + const xm = xM || isX(minor); + const xp = xm || isX(patch); + const anyX = xp; - if (operator === "=" && anyX) { - operator = ""; - } + if (operator === "=" && anyX) { + operator = ""; + } - if (xM) { - if (operator === ">" || operator === "<") { - // Nothing is allowed - ret = "<0.0.0"; - } else { - // Nothing is forbidden - ret = "*"; - } - } else if (operator && anyX) { - // Replace X with 0 - if (xm) { - minor = 0; - } - if (xp) { - patch = 0; - } + if (xM) { + if (operator === ">" || operator === "<") { + // Nothing is allowed + ret = "<0.0.0"; + } else { + // Nothing is forbidden + ret = "*"; + } + } else if (operator && anyX) { + // Replace X with 0 + if (xm) { + minor = 0; + } + if (xp) { + patch = 0; + } - if (operator === ">") { - // >1 => >=2.0.0 - // >1.2 => >=1.3.0 - // >1.2.3 => >= 1.2.4 - operator = ">="; - if (xm) { - major = Number(major) + 1; - minor = 0; - patch = 0; - } else if (xp) { - minor = Number(minor) + 1; - patch = 0; - } - } else if (operator === "<=") { - // <=0.7.x is actually <0.8.0, since any 0.7.x should pass. Similarly, <=7.x is actually <8.0.0, etc. - operator = "<"; - if (xm) { - major = Number(major) + 1; - } else { - minor = Number(minor) + 1; - } - } - - ret = `${operator}${major}.${minor}.${patch}`; - } else if (xm) { - ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`; + if (operator === ">") { + // >1 => >=2.0.0 + // >1.2 => >=1.3.0 + // >1.2.3 => >= 1.2.4 + operator = ">="; + if (xm) { + major = Number(major) + 1; + minor = 0; + patch = 0; } else if (xp) { - ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`; + minor = Number(minor) + 1; + patch = 0; } + } else if (operator === "<=") { + // <=0.7.x is actually <0.8.0, since any 0.7.x should pass. Similarly, <=7.x is actually <8.0.0, etc. + operator = "<"; + if (xm) { + major = Number(major) + 1; + } else { + minor = Number(minor) + 1; + } + } - return ret; - }); + ret = `${operator}${major}.${minor}.${patch}`; + } else if (xm) { + ret = `>=${major}.0.0 <${Number(major) + 1}.0.0`; + } else if (xp) { + ret = `>=${major}.${minor}.0 <${major}.${Number(minor) + 1}.0`; + } + + return ret; + }); } function replaceXRanges(comp, loose) { - return comp - .split(/\s+/) - .map((comp1) => replaceXRange(comp1, loose)) - .join(" "); + return comp + .split(/\s+/) + .map((comp1) => replaceXRange(comp1, loose)) + .join(" "); } // Because * is AND-ed with everything else in the comparator, and '' means "any version", just remove the *s entirely. function replaceStars(comp) { - // Looseness is ignored here. star is always as loose as it gets! - return comp.trim().replace(re[STAR], ""); + // Looseness is ignored here. star is always as loose as it gets! + return comp.trim().replace(re[STAR], ""); } // Comprised of xranges, tildes, stars, and gtlt's at this point. // Already replaced the hyphen ranges turn into a set of JUST comparators. function parseComparator(comp, loose) { - comp = replaceCarets(comp, loose); - comp = replaceTildes(comp, loose); - comp = replaceXRanges(comp, loose); - comp = replaceStars(comp, loose); + comp = replaceCarets(comp, loose); + comp = replaceTildes(comp, loose); + comp = replaceXRanges(comp, loose); + comp = replaceStars(comp, loose); - return comp; + return comp; } class SemVer { + /** + * A semantic version. + * @param {string} version The version. + * @param {boolean} loose If this is a loose representation of a version. + * @returns {SemVer} a new instance. + */ + constructor(version, loose) { + if (version instanceof SemVer) { + if (version.loose === loose) { + return version; + } + version = version.version; + } else if (typeof version !== "string") { + throw new TypeError(`Invalid Version: ${version}`); + } + if (version.length > MAX_LENGTH) { + throw new TypeError(`version is longer than ${MAX_LENGTH} characters`); + } + if (!(this instanceof SemVer)) { + return new SemVer(version, loose); + } + this.loose = loose; + const matches = version.trim().match(loose ? re[LOOSE] : re[FULL]); + if (!matches) { + throw new TypeError(`Invalid Version: ${version}`); + } + this.raw = version; + // These are actually numbers + this.major = Number(matches[1]); + this.minor = Number(matches[2]); + this.patch = Number(matches[3]); + if (this.major > MAX_SAFE_INTEGER || this.major < 0) { + throw new TypeError("Invalid major version"); + } + if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { + throw new TypeError("Invalid minor version"); + } + if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { + throw new TypeError("Invalid patch version"); + } + // Numberify any prerelease numeric ids + if (matches[4]) { + this.prerelease = matches[4].split(".").map((id) => { + if (/^[0-9]+$/.test(id)) { + const num = Number(id); + if (num >= 0 && num < MAX_SAFE_INTEGER) { + return num; + } + } - /** - * A semantic version. - * @param {string} version The version. - * @param {boolean} loose If this is a loose representation of a version. - * @returns {SemVer} a new instance. - */ - constructor(version, loose) { - if (version instanceof SemVer) { - if (version.loose === loose) { - return version; - } - version = version.version; - } else if (typeof version !== "string") { - throw new TypeError(`Invalid Version: ${version}`); - } - if (version.length > MAX_LENGTH) { - throw new TypeError(`version is longer than ${MAX_LENGTH} characters`); - } - if (!(this instanceof SemVer)) { - return new SemVer(version, loose); - } - this.loose = loose; - const matches = version.trim().match(loose ? re[LOOSE] : re[FULL]); - if (!matches) { - throw new TypeError(`Invalid Version: ${version}`); - } - this.raw = version; - // These are actually numbers - this.major = Number(matches[1]); - this.minor = Number(matches[2]); - this.patch = Number(matches[3]); - if (this.major > MAX_SAFE_INTEGER || this.major < 0) { - throw new TypeError("Invalid major version"); - } - if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { - throw new TypeError("Invalid minor version"); - } - if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { - throw new TypeError("Invalid patch version"); - } - // Numberify any prerelease numeric ids - if (matches[4]) { - this.prerelease = matches[4].split(".").map((id) => { - if ((/^[0-9]+$/).test(id)) { - const num = Number(id); - if (num >= 0 && num < MAX_SAFE_INTEGER) { - return num; - } - } + return id; + }); + } else { + this.prerelease = []; + } + this.build = matches[5] ? matches[5].split(".") : []; + this.format(); + } - return id; - }); - } else { - this.prerelease = []; - } - this.build = matches[5] ? matches[5].split(".") : []; - this.format(); + format() { + this.version = `${this.major}.${this.minor}.${this.patch}`; + if (this.prerelease.length) { + this.version += `-${this.prerelease.join(".")}`; } - format() { - this.version = `${this.major}.${this.minor}.${this.patch}`; - if (this.prerelease.length) { - this.version += `-${this.prerelease.join(".")}`; - } + return this.version; + } - return this.version; + toString() { + return this.version; + } + + /** + * Comares the current instance against another instance. + * @param {SemVer} other The SemVer to comare to. + * @returns {0|1|-1} A comparable value for sorting. + */ + compare(other) { + return this.compareMain(other) || this.comparePre(other); + } + + compareMain(other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.loose); } - toString() { - return this.version; + return ( + compareIdentifiers(this.major, other.major) || + compareIdentifiers(this.minor, other.minor) || + compareIdentifiers(this.patch, other.patch) + ); + } + + comparePre(other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.loose); } - - /** - * Comares the current instance against another instance. - * @param {SemVer} other The SemVer to comare to. - * @returns {0|1|-1} A comparable value for sorting. - */ - compare(other) { - return this.compareMain(other) || this.comparePre(other); + // NOT having a prerelease is > having one + if (this.prerelease.length && !other.prerelease.length) { + return -1; + } else if (!this.prerelease.length && other.prerelease.length) { + return 1; + } else if (!this.prerelease.length && !other.prerelease.length) { + return 0; } - - compareMain(other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.loose); - } - - return compareIdentifiers(this.major, other.major) || compareIdentifiers(this.minor, other.minor) || compareIdentifiers(this.patch, other.patch); - } - - comparePre(other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.loose); - } - // NOT having a prerelease is > having one - if (this.prerelease.length && !other.prerelease.length) { - return -1; - } else if (!this.prerelease.length && other.prerelease.length) { - return 1; - } else if (!this.prerelease.length && !other.prerelease.length) { - return 0; - } - let idx = 0; - do { - const thisPrelease = this.prerelease[idx]; - const otherPrelease = other.prerelease[idx]; - const thisPreleaseIsUndefined = typeof thisPrelease === "undefined"; - const otherPreleaseIsUndefined = typeof otherPrelease === "undefined"; - if (thisPreleaseIsUndefined && otherPreleaseIsUndefined) { - return 0; - } else if (otherPreleaseIsUndefined) { - return 1; - } else if (thisPreleaseIsUndefined) { - return -1; - } else if (thisPrelease === otherPrelease) { - continue; - } else { - return compareIdentifiers(thisPrelease, otherPrelease); - } - } while ((idx += 1) > 0); - - // Should not hit this point, but assume equal ranking. + let idx = 0; + do { + const thisPrelease = this.prerelease[idx]; + const otherPrelease = other.prerelease[idx]; + const thisPreleaseIsUndefined = typeof thisPrelease === "undefined"; + const otherPreleaseIsUndefined = typeof otherPrelease === "undefined"; + if (thisPreleaseIsUndefined && otherPreleaseIsUndefined) { return 0; - } + } else if (otherPreleaseIsUndefined) { + return 1; + } else if (thisPreleaseIsUndefined) { + return -1; + } else if (thisPrelease === otherPrelease) { + continue; + } else { + return compareIdentifiers(thisPrelease, otherPrelease); + } + } while ((idx += 1) > 0); + + // Should not hit this point, but assume equal ranking. + return 0; + } } -const compare = (leftVersion, rightVersion, loose) => new SemVer(leftVersion, loose).compare(new SemVer(rightVersion, loose)); -const gt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) > 0; -const lt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) < 0; -const eq = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) === 0; -const neq = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) !== 0; -const gte = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) >= 0; -const lte = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) <= 0; +const compare = (leftVersion, rightVersion, loose) => + new SemVer(leftVersion, loose).compare(new SemVer(rightVersion, loose)); +const gt = (leftVersion, rightVersion, loose) => + compare(leftVersion, rightVersion, loose) > 0; +const lt = (leftVersion, rightVersion, loose) => + compare(leftVersion, rightVersion, loose) < 0; +const eq = (leftVersion, rightVersion, loose) => + compare(leftVersion, rightVersion, loose) === 0; +const neq = (leftVersion, rightVersion, loose) => + compare(leftVersion, rightVersion, loose) !== 0; +const gte = (leftVersion, rightVersion, loose) => + compare(leftVersion, rightVersion, loose) >= 0; +const lte = (leftVersion, rightVersion, loose) => + compare(leftVersion, rightVersion, loose) <= 0; function cmp(left, op, right, loose) { - let ret; - switch (op) { - case "===": - if (typeof left === "object") { - left = left.version; - } - if (typeof right === "object") { - right = right.version; - } - ret = left === right; - break; - case "!==": - if (typeof left === "object") { - left = left.version; - } - if (typeof right === "object") { - right = right.version; - } - ret = left !== right; - break; - case "": - case "=": - case "==": - ret = eq(left, right, loose); - break; - case "!=": - ret = neq(left, right, loose); - break; - case ">": - ret = gt(left, right, loose); - break; - case ">=": - ret = gte(left, right, loose); - break; - case "<": - ret = lt(left, right, loose); - break; - case "<=": - ret = lte(left, right, loose); - break; - default: - throw new TypeError(`Invalid operator: ${op}`); - } + let ret; + switch (op) { + case "===": + if (typeof left === "object") { + left = left.version; + } + if (typeof right === "object") { + right = right.version; + } + ret = left === right; + break; + case "!==": + if (typeof left === "object") { + left = left.version; + } + if (typeof right === "object") { + right = right.version; + } + ret = left !== right; + break; + case "": + case "=": + case "==": + ret = eq(left, right, loose); + break; + case "!=": + ret = neq(left, right, loose); + break; + case ">": + ret = gt(left, right, loose); + break; + case ">=": + ret = gte(left, right, loose); + break; + case "<": + ret = lt(left, right, loose); + break; + case "<=": + ret = lte(left, right, loose); + break; + default: + throw new TypeError(`Invalid operator: ${op}`); + } - return ret; + return ret; } function testSet(set, version) { + for (let idx = 0; idx < set.length; idx++) { + if (!set[idx].test(version)) { + return false; + } + } + + if (version.prerelease.length) { + // Find the set of versions that are allowed to have prereleases + // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 + // That should allow `1.2.3-pr.2` to pass. + // However, `1.2.4-alpha.notready` should NOT be allowed, even though it's within the range set by the comparators. for (let idx = 0; idx < set.length; idx++) { - if (!set[idx].test(version)) { - return false; + if (set[idx].semver !== ANY) { + if (set[idx].semver.prerelease.length > 0) { + const allowed = set[idx].semver; + if ( + allowed.major === version.major && + allowed.minor === version.minor && + allowed.patch === version.patch + ) { + return true; + } } + } } - if (version.prerelease.length) { - // Find the set of versions that are allowed to have prereleases - // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 - // That should allow `1.2.3-pr.2` to pass. - // However, `1.2.4-alpha.notready` should NOT be allowed, even though it's within the range set by the comparators. - for (let idx = 0; idx < set.length; idx++) { - if (set[idx].semver !== ANY) { - if (set[idx].semver.prerelease.length > 0) { - const allowed = set[idx].semver; - if (allowed.major === version.major && allowed.minor === version.minor && allowed.patch === version.patch) { - return true; - } - } - } - } + // Version has a -pre, but it's not one of the ones we like. + return false; + } - // Version has a -pre, but it's not one of the ones we like. - return false; - } - - return true; + return true; } class Comparator { - constructor(comp, loose) { - if (comp instanceof Comparator) { - if (comp.loose === loose) { - return comp; - } - comp = comp.value; - } - if (!(this instanceof Comparator)) { - return new Comparator(comp, loose); - } - this.loose = loose; - this.parse(comp); - if (this.semver === ANY) { - this.value = ""; - } else { - this.value = this.operator + this.semver.version; - } + constructor(comp, loose) { + if (comp instanceof Comparator) { + if (comp.loose === loose) { + return comp; + } + comp = comp.value; + } + if (!(this instanceof Comparator)) { + return new Comparator(comp, loose); + } + this.loose = loose; + this.parse(comp); + if (this.semver === ANY) { + this.value = ""; + } else { + this.value = this.operator + this.semver.version; + } + } + + parse(comp) { + const regex = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; + const matches = comp.match(regex); + if (!matches) { + throw new TypeError(`Invalid comparator: ${comp}`); + } + this.operator = matches[1]; + if (this.operator === "=") { + this.operator = ""; + } + // If it literally is just '>' or '' then allow anything. + if (matches[2]) { + this.semver = new SemVer(matches[2], this.loose); + } else { + this.semver = ANY; + } + } + + toString() { + return this.value; + } + + test(version) { + if (this.semver === ANY) { + return true; + } + if (typeof version === "string") { + version = new SemVer(version, this.loose); } - parse(comp) { - const regex = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - const matches = comp.match(regex); - if (!matches) { - throw new TypeError(`Invalid comparator: ${comp}`); - } - this.operator = matches[1]; - if (this.operator === "=") { - this.operator = ""; - } - // If it literally is just '>' or '' then allow anything. - if (matches[2]) { - this.semver = new SemVer(matches[2], this.loose); - } else { - this.semver = ANY; - } - } - - toString() { - return this.value; - } - - test(version) { - if (this.semver === ANY) { - return true; - } - if (typeof version === "string") { - version = new SemVer(version, this.loose); - } - - return cmp(version, this.operator, this.semver, this.loose); - } + return cmp(version, this.operator, this.semver, this.loose); + } } /** @@ -711,94 +756,99 @@ class Comparator { * @returns {Range} the Range instace. */ class Range { - constructor(range, loose) { - if (range instanceof Range) { - if (range.loose === loose) { - return range; - } + constructor(range, loose) { + if (range instanceof Range) { + if (range.loose === loose) { + return range; + } - return new Range(range.raw, loose); - } - if (range instanceof Comparator) { - return new Range(range.value, loose); - } - if (!(this instanceof Range)) { - return new Range(range, loose); - } - this.loose = loose; - // First, split based on boolean or || - /** - * @type {string} - */ - this.raw = range; - // Throw out any that are not relevant for whatever reason - const hasLength = (item) => item.length; - this.set = this.raw.split(/\s*\|\|\s*/).map(function (range1) { - return this.parseRange(range1.trim()); - }, this) - .filter(hasLength); - if (!this.set.length) { - throw new TypeError(`Invalid SemVer Range: ${range}`); - } - this.format(); + return new Range(range.raw, loose); + } + if (range instanceof Comparator) { + return new Range(range.value, loose); + } + if (!(this instanceof Range)) { + return new Range(range, loose); + } + this.loose = loose; + // First, split based on boolean or || + /** + * @type {string} + */ + this.raw = range; + // Throw out any that are not relevant for whatever reason + const hasLength = (item) => item.length; + this.set = this.raw + .split(/\s*\|\|\s*/) + .map(function (range1) { + return this.parseRange(range1.trim()); + }, this) + .filter(hasLength); + if (!this.set.length) { + throw new TypeError(`Invalid SemVer Range: ${range}`); + } + this.format(); + } + + format() { + this.range = this.set + .map((comps) => comps.join(" ").trim()) + .join("||") + .trim(); + + return this.range; + } + + toString() { + return this.range; + } + + parseRange(range) { + const loose = this.loose; + range = range.trim(); + // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` + const hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE]; + range = range.replace(hr, hyphenReplace); + // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` + range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace); + // `~ 1.2.3` => `~1.2.3` + range = range.replace(re[TILDETRIM], tildeTrimReplace); + // `^ 1.2.3` => `^1.2.3` + range = range.replace(re[CARETTRIM], caretTrimReplace); + // Normalize spaces + range = range.split(/\s+/).join(" "); + // At this point, the range is completely trimmed and ready to be split into comparators. + const compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; + let set = range + .split(" ") + .map((comp) => parseComparator(comp, loose)) + .join(" ") + .split(/\s+/); + if (loose) { + // In loose mode, throw out any that are not valid comparators + set = set.filter((comp) => Boolean(comp.match(compRe))); + } + set = set.map((comp) => new Comparator(comp, loose)); + + return set; + } + + // If ANY of the sets match ALL of its comparators, then pass + test(version) { + if (!version) { + return false; + } + if (typeof version === "string") { + version = new SemVer(version, this.loose); + } + for (let idx = 0; idx < this.set.length; idx++) { + if (testSet(this.set[idx], version)) { + return true; + } } - format() { - this.range = this.set.map((comps) => comps.join(" ").trim()) - .join("||") - .trim(); - - return this.range; - } - - toString() { - return this.range; - } - - parseRange(range) { - const loose = this.loose; - range = range.trim(); - // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` - const hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE]; - range = range.replace(hr, hyphenReplace); - // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace); - // `~ 1.2.3` => `~1.2.3` - range = range.replace(re[TILDETRIM], tildeTrimReplace); - // `^ 1.2.3` => `^1.2.3` - range = range.replace(re[CARETTRIM], caretTrimReplace); - // Normalize spaces - range = range.split(/\s+/).join(" "); - // At this point, the range is completely trimmed and ready to be split into comparators. - const compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - let set = range.split(" ").map((comp) => parseComparator(comp, loose)) - .join(" ") - .split(/\s+/); - if (loose) { - // In loose mode, throw out any that are not valid comparators - set = set.filter((comp) => Boolean(comp.match(compRe))); - } - set = set.map((comp) => new Comparator(comp, loose)); - - return set; - } - - // If ANY of the sets match ALL of its comparators, then pass - test(version) { - if (!version) { - return false; - } - if (typeof version === "string") { - version = new SemVer(version, this.loose); - } - for (let idx = 0; idx < this.set.length; idx++) { - if (testSet(this.set[idx], version)) { - return true; - } - } - - return false; - } + return false; + } } /** @@ -809,13 +859,13 @@ class Range { * @returns {boolean} Whether the versions successfully satisfies the range. */ function satisfies(version, range, loose) { - try { - const rangeObj = new Range(range, loose); + try { + const rangeObj = new Range(range, loose); - return rangeObj.test(version); - } catch (er) { - return false; - } + return rangeObj.test(version); + } catch (er) { + return false; + } } module.exports.satisfies = satisfies; diff --git a/src/Alias.ts b/src/Alias.ts index 39931ad00..ed23d6492 100644 --- a/src/Alias.ts +++ b/src/Alias.ts @@ -5,89 +5,91 @@ export let Aliases: IMap = {}; export let GlobalAliases: IMap = {}; export function loadAliases(saveString: string): void { - if (saveString === "") { - Aliases = {}; - } else { - Aliases = JSON.parse(saveString); - } + if (saveString === "") { + Aliases = {}; + } else { + Aliases = JSON.parse(saveString); + } } export function loadGlobalAliases(saveString: string): void { - if (saveString === "") { - GlobalAliases = {}; - } else { - GlobalAliases = JSON.parse(saveString); - } + if (saveString === "") { + GlobalAliases = {}; + } else { + GlobalAliases = JSON.parse(saveString); + } } // Prints all aliases to terminal export function printAliases(): void { - for (const name in Aliases) { - if (Aliases.hasOwnProperty(name)) { - post("alias " + name + "=" + Aliases[name]); - } + for (const name in Aliases) { + if (Aliases.hasOwnProperty(name)) { + post("alias " + name + "=" + Aliases[name]); } - for (const name in GlobalAliases) { - if (GlobalAliases.hasOwnProperty(name)) { - post("global alias " + name + "=" + GlobalAliases[name]); - } + } + for (const name in GlobalAliases) { + if (GlobalAliases.hasOwnProperty(name)) { + post("global alias " + name + "=" + GlobalAliases[name]); } + } } // Returns true if successful, false otherwise export function parseAliasDeclaration(dec: string, global = false): boolean { - const re = /^([_|\w|!|%|,|@]+)="(.+)"$/; - const matches = dec.match(re); - if (matches == null || matches.length != 3) {return false;} - if (global){ - addGlobalAlias(matches[1],matches[2]); - } else { - addAlias(matches[1], matches[2]); - } - return true; + const re = /^([_|\w|!|%|,|@]+)="(.+)"$/; + const matches = dec.match(re); + if (matches == null || matches.length != 3) { + return false; + } + if (global) { + addGlobalAlias(matches[1], matches[2]); + } else { + addAlias(matches[1], matches[2]); + } + return true; } function addAlias(name: string, value: string): void { - if (name in GlobalAliases) { - delete GlobalAliases[name]; - } - Aliases[name] = value.trim(); + if (name in GlobalAliases) { + delete GlobalAliases[name]; + } + Aliases[name] = value.trim(); } function addGlobalAlias(name: string, value: string): void { - if (name in Aliases){ - delete Aliases[name]; - } - GlobalAliases[name] = value.trim(); + if (name in Aliases) { + delete Aliases[name]; + } + GlobalAliases[name] = value.trim(); } function getAlias(name: string): string | null { - if (Aliases.hasOwnProperty(name)) { - return Aliases[name]; - } + if (Aliases.hasOwnProperty(name)) { + return Aliases[name]; + } - return null; + return null; } function getGlobalAlias(name: string): string | null { - if (GlobalAliases.hasOwnProperty(name)) { - return GlobalAliases[name]; - } - return null; + if (GlobalAliases.hasOwnProperty(name)) { + return GlobalAliases[name]; + } + return null; } export function removeAlias(name: string): boolean { - if (Aliases.hasOwnProperty(name)) { - delete Aliases[name]; - return true; - } + if (Aliases.hasOwnProperty(name)) { + delete Aliases[name]; + return true; + } - if (GlobalAliases.hasOwnProperty(name)) { - delete GlobalAliases[name]; - return true; - } + if (GlobalAliases.hasOwnProperty(name)) { + delete GlobalAliases[name]; + return true; + } - return false; + return false; } /** @@ -95,33 +97,35 @@ export function removeAlias(name: string): boolean { * Aliases are only applied to "whole words", one level deep */ export function substituteAliases(origCommand: string): string { - const commandArray = origCommand.split(" "); - if (commandArray.length > 0){ - // For the alias and unalias commands, dont substite - if (commandArray[0] === "unalias" || commandArray[0] === "alias") { return commandArray.join(" "); } - - let somethingSubstituted = true; - let depth = 0; - - while(somethingSubstituted && depth < 10){ - depth++; - somethingSubstituted = false - const alias = getAlias(commandArray[0])?.split(" "); - if (alias != null) { - somethingSubstituted = true - commandArray.splice(0, 1, ...alias); - //commandArray[0] = alias; - } - for (let i = 0; i < commandArray.length; ++i) { - const alias = getGlobalAlias(commandArray[i])?.split(" "); - if (alias != null) { - somethingSubstituted = true - commandArray.splice(i, 1, ...alias); - i += alias.length - 1; - //commandArray[i] = alias; - } - } - } + const commandArray = origCommand.split(" "); + if (commandArray.length > 0) { + // For the alias and unalias commands, dont substite + if (commandArray[0] === "unalias" || commandArray[0] === "alias") { + return commandArray.join(" "); } - return commandArray.join(" "); + + let somethingSubstituted = true; + let depth = 0; + + while (somethingSubstituted && depth < 10) { + depth++; + somethingSubstituted = false; + const alias = getAlias(commandArray[0])?.split(" "); + if (alias != null) { + somethingSubstituted = true; + commandArray.splice(0, 1, ...alias); + //commandArray[0] = alias; + } + for (let i = 0; i < commandArray.length; ++i) { + const alias = getGlobalAlias(commandArray[i])?.split(" "); + if (alias != null) { + somethingSubstituted = true; + commandArray.splice(i, 1, ...alias); + i += alias.length - 1; + //commandArray[i] = alias; + } + } + } + } + return commandArray.join(" "); } diff --git a/src/Augmentation/Augmentation.tsx b/src/Augmentation/Augmentation.tsx index b0d1259c3..f54edc7b1 100644 --- a/src/Augmentation/Augmentation.tsx +++ b/src/Augmentation/Augmentation.tsx @@ -8,297 +8,580 @@ import { Factions } from "../Faction/Factions"; import { numeralWrapper } from "../ui/numeralFormat"; import { Money } from "../ui/React/Money"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; interface IConstructorParams { - info: string | JSX.Element; - stats?: JSX.Element; - isSpecial?: boolean; - moneyCost: number; - name: string; - prereqs?: string[]; - repCost: number; + info: string | JSX.Element; + stats?: JSX.Element; + isSpecial?: boolean; + moneyCost: number; + name: string; + prereqs?: string[]; + repCost: number; - hacking_mult?: number; - strength_mult?: number; - defense_mult?: number; - dexterity_mult?: number; - agility_mult?: number; - charisma_mult?: number; - hacking_exp_mult?: number; - strength_exp_mult?: number; - defense_exp_mult?: number; - dexterity_exp_mult?: number; - agility_exp_mult?: number; - charisma_exp_mult?: number; - hacking_chance_mult?: number; - hacking_speed_mult?: number; - hacking_money_mult?: number; - hacking_grow_mult?: number; - company_rep_mult?: number; - faction_rep_mult?: number; - crime_money_mult?: number; - crime_success_mult?: number; - work_money_mult?: number; - hacknet_node_money_mult?: number; - hacknet_node_purchase_cost_mult?: number; - hacknet_node_ram_cost_mult?: number; - hacknet_node_core_cost_mult?: number; - hacknet_node_level_cost_mult?: number; - bladeburner_max_stamina_mult?: number; - bladeburner_stamina_gain_mult?: number; - bladeburner_analysis_mult?: number; - bladeburner_success_chance_mult?: number; + hacking_mult?: number; + strength_mult?: number; + defense_mult?: number; + dexterity_mult?: number; + agility_mult?: number; + charisma_mult?: number; + hacking_exp_mult?: number; + strength_exp_mult?: number; + defense_exp_mult?: number; + dexterity_exp_mult?: number; + agility_exp_mult?: number; + charisma_exp_mult?: number; + hacking_chance_mult?: number; + hacking_speed_mult?: number; + hacking_money_mult?: number; + hacking_grow_mult?: number; + company_rep_mult?: number; + faction_rep_mult?: number; + crime_money_mult?: number; + crime_success_mult?: number; + work_money_mult?: number; + hacknet_node_money_mult?: number; + hacknet_node_purchase_cost_mult?: number; + hacknet_node_ram_cost_mult?: number; + hacknet_node_core_cost_mult?: number; + hacknet_node_level_cost_mult?: number; + bladeburner_max_stamina_mult?: number; + bladeburner_stamina_gain_mult?: number; + bladeburner_analysis_mult?: number; + bladeburner_success_chance_mult?: number; - startingMoney?: number; - programs?: string[]; + startingMoney?: number; + programs?: string[]; } -function generateStatsDescription(mults: IMap, programs?: string[], startingMoney?: number): JSX.Element { - const f = (x: number, decimals = 0): string => { - // look, I don't know how to make a "smart decimals" - // todo, make it smarter - if(x === 1.0777-1) return "7.77%"; - if(x === 1.777-1) return "77.7%"; - return numeralWrapper.formatPercentage(x, decimals); - }; - let desc = <>Effects:; +function generateStatsDescription( + mults: IMap, + programs?: string[], + startingMoney?: number, +): JSX.Element { + const f = (x: number, decimals = 0): string => { + // look, I don't know how to make a "smart decimals" + // todo, make it smarter + if (x === 1.0777 - 1) return "7.77%"; + if (x === 1.777 - 1) return "77.7%"; + return numeralWrapper.formatPercentage(x, decimals); + }; + let desc = <>Effects:; - if(mults.hacking_mult && - mults.hacking_mult == mults.strength_mult && - mults.hacking_mult == mults.defense_mult && - mults.hacking_mult == mults.dexterity_mult && - mults.hacking_mult == mults.agility_mult && - mults.hacking_mult == mults.charisma_mult){ - desc = <>{desc}
    +{f(mults.hacking_mult-1)} all skills + if ( + mults.hacking_mult && + mults.hacking_mult == mults.strength_mult && + mults.hacking_mult == mults.defense_mult && + mults.hacking_mult == mults.dexterity_mult && + mults.hacking_mult == mults.agility_mult && + mults.hacking_mult == mults.charisma_mult + ) { + desc = ( + <> + {desc} +
    +{f(mults.hacking_mult - 1)} all skills + + ); + } else { + if (mults.hacking_mult) + desc = ( + <> + {desc} +
    +{f(mults.hacking_mult - 1)} hacking skill + + ); + + if ( + mults.strength_mult && + mults.strength_mult == mults.defense_mult && + mults.strength_mult == mults.dexterity_mult && + mults.strength_mult == mults.agility_mult + ) { + desc = ( + <> + {desc} +
    +{f(mults.strength_mult - 1)} combat skills + + ); } else { - if(mults.hacking_mult) - desc = <>{desc}
    +{f(mults.hacking_mult-1)} hacking skill - - if(mults.strength_mult && - mults.strength_mult == mults.defense_mult && - mults.strength_mult == mults.dexterity_mult && - mults.strength_mult == mults.agility_mult) { - desc = <>{desc}
    +{f(mults.strength_mult-1)} combat skills - } else { - if(mults.strength_mult) - desc = <>{desc}
    +{f(mults.strength_mult-1)} strength skill - if(mults.defense_mult) - desc = <>{desc}
    +{f(mults.defense_mult-1)} defense skill - if(mults.dexterity_mult) - desc = <>{desc}
    +{f(mults.dexterity_mult-1)} dexterity skill - if(mults.agility_mult) - desc = <>{desc}
    +{f(mults.agility_mult-1)} agility skill - } - if(mults.charisma_mult) - desc = <>{desc}
    +{f(mults.charisma_mult-1)} Charisma skill + if (mults.strength_mult) + desc = ( + <> + {desc} +
    +{f(mults.strength_mult - 1)} strength skill + + ); + if (mults.defense_mult) + desc = ( + <> + {desc} +
    +{f(mults.defense_mult - 1)} defense skill + + ); + if (mults.dexterity_mult) + desc = ( + <> + {desc} +
    +{f(mults.dexterity_mult - 1)} dexterity skill + + ); + if (mults.agility_mult) + desc = ( + <> + {desc} +
    +{f(mults.agility_mult - 1)} agility skill + + ); } + if (mults.charisma_mult) + desc = ( + <> + {desc} +
    +{f(mults.charisma_mult - 1)} Charisma skill + + ); + } - if(mults.hacking_exp_mult && - mults.hacking_exp_mult === mults.strength_exp_mult && - mults.hacking_exp_mult === mults.defense_exp_mult && - mults.hacking_exp_mult === mults.dexterity_exp_mult && - mults.hacking_exp_mult === mults.agility_exp_mult && - mults.hacking_exp_mult === mults.charisma_exp_mult) { - desc = <>{desc}
    +{f(mults.hacking_exp_mult-1)} exp for all skills + if ( + mults.hacking_exp_mult && + mults.hacking_exp_mult === mults.strength_exp_mult && + mults.hacking_exp_mult === mults.defense_exp_mult && + mults.hacking_exp_mult === mults.dexterity_exp_mult && + mults.hacking_exp_mult === mults.agility_exp_mult && + mults.hacking_exp_mult === mults.charisma_exp_mult + ) { + desc = ( + <> + {desc} +
    +{f(mults.hacking_exp_mult - 1)} exp for all skills + + ); + } else { + if (mults.hacking_exp_mult) + desc = ( + <> + {desc} +
    +{f(mults.hacking_exp_mult - 1)} hacking exp + + ); + + if ( + mults.strength_exp_mult && + mults.strength_exp_mult === mults.defense_exp_mult && + mults.strength_exp_mult === mults.dexterity_exp_mult && + mults.strength_exp_mult === mults.agility_exp_mult + ) { + desc = ( + <> + {desc} +
    +{f(mults.strength_exp_mult - 1)} combat exp + + ); } else { - if(mults.hacking_exp_mult) - desc = <>{desc}
    +{f(mults.hacking_exp_mult-1)} hacking exp - - if(mults.strength_exp_mult && - mults.strength_exp_mult === mults.defense_exp_mult && - mults.strength_exp_mult === mults.dexterity_exp_mult && - mults.strength_exp_mult === mults.agility_exp_mult) { - desc = <>{desc}
    +{f(mults.strength_exp_mult-1)} combat exp - } else { - if(mults.strength_exp_mult) - desc = <>{desc}
    +{f(mults.strength_exp_mult-1)} strength exp - if(mults.defense_exp_mult) - desc = <>{desc}
    +{f(mults.defense_exp_mult-1)} defense exp - if(mults.dexterity_exp_mult) - desc = <>{desc}
    +{f(mults.dexterity_exp_mult-1)} dexterity exp - if(mults.agility_exp_mult) - desc = <>{desc}
    +{f(mults.agility_exp_mult-1)} agility exp - } - if(mults.charisma_exp_mult) - desc = <>{desc}
    +{f(mults.charisma_exp_mult-1)} charisma exp + if (mults.strength_exp_mult) + desc = ( + <> + {desc} +
    +{f(mults.strength_exp_mult - 1)} strength exp + + ); + if (mults.defense_exp_mult) + desc = ( + <> + {desc} +
    +{f(mults.defense_exp_mult - 1)} defense exp + + ); + if (mults.dexterity_exp_mult) + desc = ( + <> + {desc} +
    +{f(mults.dexterity_exp_mult - 1)} dexterity exp + + ); + if (mults.agility_exp_mult) + desc = ( + <> + {desc} +
    +{f(mults.agility_exp_mult - 1)} agility exp + + ); } + if (mults.charisma_exp_mult) + desc = ( + <> + {desc} +
    +{f(mults.charisma_exp_mult - 1)} charisma exp + + ); + } - if(mults.hacking_speed_mult) - desc = <>{desc}
    +{f(mults.hacking_speed_mult-1)} faster hacking - if(mults.hacking_chance_mult) - desc = <>{desc}
    +{f(mults.hacking_chance_mult-1)} hack() success chance - if(mults.hacking_money_mult) - desc = <>{desc}
    +{f(mults.hacking_money_mult-1)} hack() power - if(mults.hacking_grow_mult) - desc = <>{desc}
    +{f(mults.hacking_grow_mult-1)} grow() power + if (mults.hacking_speed_mult) + desc = ( + <> + {desc} +
    +{f(mults.hacking_speed_mult - 1)} faster hacking + + ); + if (mults.hacking_chance_mult) + desc = ( + <> + {desc} +
    +{f(mults.hacking_chance_mult - 1)} hack() success chance + + ); + if (mults.hacking_money_mult) + desc = ( + <> + {desc} +
    +{f(mults.hacking_money_mult - 1)} hack() power + + ); + if (mults.hacking_grow_mult) + desc = ( + <> + {desc} +
    +{f(mults.hacking_grow_mult - 1)} grow() power + + ); - if(mults.faction_rep_mult && - mults.faction_rep_mult === mults.company_rep_mult) { - desc = <>{desc}
    +{f(mults.faction_rep_mult-1)} reputation from factions and companies - } else { - if(mults.faction_rep_mult) - desc = <>{desc}
    +{f(mults.faction_rep_mult-1)} reputation from factions - if(mults.company_rep_mult) - desc = <>{desc}
    +{f(mults.company_rep_mult-1)} reputation from companies - } + if ( + mults.faction_rep_mult && + mults.faction_rep_mult === mults.company_rep_mult + ) { + desc = ( + <> + {desc} +
    +{f(mults.faction_rep_mult - 1)} reputation from factions and + companies + + ); + } else { + if (mults.faction_rep_mult) + desc = ( + <> + {desc} +
    +{f(mults.faction_rep_mult - 1)} reputation from factions + + ); + if (mults.company_rep_mult) + desc = ( + <> + {desc} +
    +{f(mults.company_rep_mult - 1)} reputation from companies + + ); + } - if(mults.crime_money_mult) - desc = <>{desc}
    +{f(mults.crime_money_mult-1)} crime money - if(mults.crime_success_mult) - desc = <>{desc}
    +{f(mults.crime_success_mult-1)} crime success rate - if(mults.work_money_mult) - desc = <>{desc}
    +{f(mults.work_money_mult-1)} work money + if (mults.crime_money_mult) + desc = ( + <> + {desc} +
    +{f(mults.crime_money_mult - 1)} crime money + + ); + if (mults.crime_success_mult) + desc = ( + <> + {desc} +
    +{f(mults.crime_success_mult - 1)} crime success rate + + ); + if (mults.work_money_mult) + desc = ( + <> + {desc} +
    +{f(mults.work_money_mult - 1)} work money + + ); - if(mults.hacknet_node_money_mult) - desc = <>{desc}
    +{f(mults.hacknet_node_money_mult-1)} hacknet production - if(mults.hacknet_node_purchase_cost_mult) - desc = <>{desc}
    -{f(-(mults.hacknet_node_purchase_cost_mult-1))} hacknet nodes cost - if(mults.hacknet_node_level_cost_mult) - desc = <>{desc}
    -{f(-(mults.hacknet_node_level_cost_mult-1))} hacknet nodes upgrade cost + if (mults.hacknet_node_money_mult) + desc = ( + <> + {desc} +
    +{f(mults.hacknet_node_money_mult - 1)} hacknet production + + ); + if (mults.hacknet_node_purchase_cost_mult) + desc = ( + <> + {desc} +
    -{f(-(mults.hacknet_node_purchase_cost_mult - 1))} hacknet nodes + cost + + ); + if (mults.hacknet_node_level_cost_mult) + desc = ( + <> + {desc} +
    -{f(-(mults.hacknet_node_level_cost_mult - 1))} hacknet nodes + upgrade cost + + ); - if(mults.bladeburner_max_stamina_mult) - desc = <>{desc}
    +{f(mults.bladeburner_max_stamina_mult-1)} Bladeburner Max Stamina - if(mults.bladeburner_stamina_gain_mult) - desc = <>{desc}
    +{f(mults.bladeburner_stamina_gain_mult-1)} Bladeburner Stamina gain - if(mults.bladeburner_analysis_mult) - desc = <>{desc}
    +{f(mults.bladeburner_analysis_mult-1)} Bladeburner Field Analysis effectiveness - if(mults.bladeburner_success_chance_mult) - desc = <>{desc}
    +{f(mults.bladeburner_success_chance_mult-1)} Bladeburner Contracts and Operations success chance + if (mults.bladeburner_max_stamina_mult) + desc = ( + <> + {desc} +
    +{f(mults.bladeburner_max_stamina_mult - 1)} Bladeburner Max + Stamina + + ); + if (mults.bladeburner_stamina_gain_mult) + desc = ( + <> + {desc} +
    +{f(mults.bladeburner_stamina_gain_mult - 1)} Bladeburner Stamina + gain + + ); + if (mults.bladeburner_analysis_mult) + desc = ( + <> + {desc} +
    +{f(mults.bladeburner_analysis_mult - 1)} Bladeburner Field + Analysis effectiveness + + ); + if (mults.bladeburner_success_chance_mult) + desc = ( + <> + {desc} +
    +{f(mults.bladeburner_success_chance_mult - 1)} Bladeburner + Contracts and Operations success chance + + ); - if(startingMoney) - desc = <>{desc}
    Start with after installing Augmentations. + if (startingMoney) + desc = ( + <> + {desc} +
    + Start with after installing + Augmentations. + + ); - if(programs) - desc = <>{desc}
    Start with {programs.join(' and ')} after installing Augmentations. - return desc; + if (programs) + desc = ( + <> + {desc} +
    + Start with {programs.join(" and ")} after installing Augmentations. + + ); + return desc; } export class Augmentation { + // How much money this costs to buy + baseCost = 0; - // How much money this costs to buy - baseCost = 0; + // How much faction reputation is required to unlock this + baseRepRequirement = 0; - // How much faction reputation is required to unlock this - baseRepRequirement = 0; + // Description of what this Aug is and what it does + info: string | JSX.Element; - // Description of what this Aug is and what it does - info: string | JSX.Element; + // Description of the stats, often autogenerated, sometimes manually written. + stats: JSX.Element; - // Description of the stats, often autogenerated, sometimes manually written. - stats: JSX.Element; + // Any Augmentation not immediately available in BitNode-1 is special (e.g. Bladeburner augs) + isSpecial = false; - // Any Augmentation not immediately available in BitNode-1 is special (e.g. Bladeburner augs) - isSpecial = false; + // Augmentation level - for repeatable Augs like NeuroFlux Governor + level = 0; - // Augmentation level - for repeatable Augs like NeuroFlux Governor - level = 0; + // Name of Augmentation + name = ""; - // Name of Augmentation - name = ""; + // Whether the player owns this Augmentation + owned = false; - // Whether the player owns this Augmentation - owned = false; + // Array of names of all prerequisites + prereqs: string[] = []; - // Array of names of all prerequisites - prereqs: string[] = []; + // Multipliers given by this Augmentation. Must match the property name in + // The Player/Person classes + mults: IMap = {}; - // Multipliers given by this Augmentation. Must match the property name in - // The Player/Person classes - mults: IMap = {} + // Initial cost. Doesn't change when you purchase multiple Augmentation + startingCost = 0; - // Initial cost. Doesn't change when you purchase multiple Augmentation - startingCost = 0; + constructor( + params: IConstructorParams = { + info: "", + moneyCost: 0, + name: "", + repCost: 0, + }, + ) { + this.name = params.name; + this.info = params.info; + this.prereqs = params.prereqs ? params.prereqs : []; - constructor(params: IConstructorParams={ info: "", moneyCost: 0, name: "", repCost: 0 }) { - this.name = params.name; - this.info = params.info; - this.prereqs = params.prereqs ? params.prereqs : []; + this.baseRepRequirement = + params.repCost * BitNodeMultipliers.AugmentationRepCost; + this.baseCost = params.moneyCost * BitNodeMultipliers.AugmentationMoneyCost; + this.startingCost = this.baseCost; - this.baseRepRequirement = params.repCost * BitNodeMultipliers.AugmentationRepCost; - this.baseCost = params.moneyCost * BitNodeMultipliers.AugmentationMoneyCost; - this.startingCost = this.baseCost; + if (params.isSpecial) { + this.isSpecial = true; + } - if (params.isSpecial) { - this.isSpecial = true; + this.level = 0; + + // Set multipliers + if (params.hacking_mult) { + this.mults.hacking_mult = params.hacking_mult; + } + if (params.strength_mult) { + this.mults.strength_mult = params.strength_mult; + } + if (params.defense_mult) { + this.mults.defense_mult = params.defense_mult; + } + if (params.dexterity_mult) { + this.mults.dexterity_mult = params.dexterity_mult; + } + if (params.agility_mult) { + this.mults.agility_mult = params.agility_mult; + } + if (params.charisma_mult) { + this.mults.charisma_mult = params.charisma_mult; + } + if (params.hacking_exp_mult) { + this.mults.hacking_exp_mult = params.hacking_exp_mult; + } + if (params.strength_exp_mult) { + this.mults.strength_exp_mult = params.strength_exp_mult; + } + if (params.defense_exp_mult) { + this.mults.defense_exp_mult = params.defense_exp_mult; + } + if (params.dexterity_exp_mult) { + this.mults.dexterity_exp_mult = params.dexterity_exp_mult; + } + if (params.agility_exp_mult) { + this.mults.agility_exp_mult = params.agility_exp_mult; + } + if (params.charisma_exp_mult) { + this.mults.charisma_exp_mult = params.charisma_exp_mult; + } + if (params.hacking_chance_mult) { + this.mults.hacking_chance_mult = params.hacking_chance_mult; + } + if (params.hacking_speed_mult) { + this.mults.hacking_speed_mult = params.hacking_speed_mult; + } + if (params.hacking_money_mult) { + this.mults.hacking_money_mult = params.hacking_money_mult; + } + if (params.hacking_grow_mult) { + this.mults.hacking_grow_mult = params.hacking_grow_mult; + } + if (params.company_rep_mult) { + this.mults.company_rep_mult = params.company_rep_mult; + } + if (params.faction_rep_mult) { + this.mults.faction_rep_mult = params.faction_rep_mult; + } + if (params.crime_money_mult) { + this.mults.crime_money_mult = params.crime_money_mult; + } + if (params.crime_success_mult) { + this.mults.crime_success_mult = params.crime_success_mult; + } + if (params.work_money_mult) { + this.mults.work_money_mult = params.work_money_mult; + } + if (params.hacknet_node_money_mult) { + this.mults.hacknet_node_money_mult = params.hacknet_node_money_mult; + } + if (params.hacknet_node_purchase_cost_mult) { + this.mults.hacknet_node_purchase_cost_mult = + params.hacknet_node_purchase_cost_mult; + } + if (params.hacknet_node_ram_cost_mult) { + this.mults.hacknet_node_ram_cost_mult = params.hacknet_node_ram_cost_mult; + } + if (params.hacknet_node_core_cost_mult) { + this.mults.hacknet_node_core_cost_mult = + params.hacknet_node_core_cost_mult; + } + if (params.hacknet_node_level_cost_mult) { + this.mults.hacknet_node_level_cost_mult = + params.hacknet_node_level_cost_mult; + } + if (params.bladeburner_max_stamina_mult) { + this.mults.bladeburner_max_stamina_mult = + params.bladeburner_max_stamina_mult; + } + if (params.bladeburner_stamina_gain_mult) { + this.mults.bladeburner_stamina_gain_mult = + params.bladeburner_stamina_gain_mult; + } + if (params.bladeburner_analysis_mult) { + this.mults.bladeburner_analysis_mult = params.bladeburner_analysis_mult; + } + if (params.bladeburner_success_chance_mult) { + this.mults.bladeburner_success_chance_mult = + params.bladeburner_success_chance_mult; + } + + if (params.stats) this.stats = params.stats; + else + this.stats = generateStatsDescription( + this.mults, + params.programs, + params.startingMoney, + ); + } + + // Adds this Augmentation to the specified Factions + addToFactions(factionList: string[]): void { + for (let i = 0; i < factionList.length; ++i) { + const faction: Faction | null = Factions[factionList[i]]; + if (faction == null) { + console.warn( + `In Augmentation.addToFactions(), could not find faction with this name: ${factionList[i]}`, + ); + continue; + } + faction.augmentations.push(this.name); + } + } + + // Adds this Augmentation to all Factions + addToAllFactions(): void { + for (const fac in Factions) { + if (Factions.hasOwnProperty(fac)) { + const facObj: Faction | null = Factions[fac]; + if (facObj == null) { + console.warn( + `Invalid Faction object in addToAllFactions(). Key value: ${fac}`, + ); + continue; } - - this.level = 0; - - // Set multipliers - if (params.hacking_mult) { this.mults.hacking_mult = params.hacking_mult; } - if (params.strength_mult) { this.mults.strength_mult = params.strength_mult; } - if (params.defense_mult) { this.mults.defense_mult = params.defense_mult; } - if (params.dexterity_mult) { this.mults.dexterity_mult = params.dexterity_mult; } - if (params.agility_mult) { this.mults.agility_mult = params.agility_mult; } - if (params.charisma_mult) { this.mults.charisma_mult = params.charisma_mult; } - if (params.hacking_exp_mult) { this.mults.hacking_exp_mult = params.hacking_exp_mult; } - if (params.strength_exp_mult) { this.mults.strength_exp_mult = params.strength_exp_mult; } - if (params.defense_exp_mult) { this.mults.defense_exp_mult = params.defense_exp_mult; } - if (params.dexterity_exp_mult) { this.mults.dexterity_exp_mult = params.dexterity_exp_mult; } - if (params.agility_exp_mult) { this.mults.agility_exp_mult = params.agility_exp_mult; } - if (params.charisma_exp_mult) { this.mults.charisma_exp_mult = params.charisma_exp_mult; } - if (params.hacking_chance_mult) { this.mults.hacking_chance_mult = params.hacking_chance_mult; } - if (params.hacking_speed_mult) { this.mults.hacking_speed_mult = params.hacking_speed_mult; } - if (params.hacking_money_mult) { this.mults.hacking_money_mult = params.hacking_money_mult; } - if (params.hacking_grow_mult) { this.mults.hacking_grow_mult = params.hacking_grow_mult; } - if (params.company_rep_mult) { this.mults.company_rep_mult = params.company_rep_mult; } - if (params.faction_rep_mult) { this.mults.faction_rep_mult = params.faction_rep_mult; } - if (params.crime_money_mult) { this.mults.crime_money_mult = params.crime_money_mult; } - if (params.crime_success_mult) { this.mults.crime_success_mult = params.crime_success_mult; } - if (params.work_money_mult) { this.mults.work_money_mult = params.work_money_mult; } - if (params.hacknet_node_money_mult) { this.mults.hacknet_node_money_mult = params.hacknet_node_money_mult; } - if (params.hacknet_node_purchase_cost_mult) { this.mults.hacknet_node_purchase_cost_mult = params.hacknet_node_purchase_cost_mult; } - if (params.hacknet_node_ram_cost_mult) { this.mults.hacknet_node_ram_cost_mult = params.hacknet_node_ram_cost_mult; } - if (params.hacknet_node_core_cost_mult) { this.mults.hacknet_node_core_cost_mult = params.hacknet_node_core_cost_mult; } - if (params.hacknet_node_level_cost_mult) { this.mults.hacknet_node_level_cost_mult = params.hacknet_node_level_cost_mult; } - if (params.bladeburner_max_stamina_mult) { this.mults.bladeburner_max_stamina_mult = params.bladeburner_max_stamina_mult; } - if (params.bladeburner_stamina_gain_mult) { this.mults.bladeburner_stamina_gain_mult = params.bladeburner_stamina_gain_mult; } - if (params.bladeburner_analysis_mult) { this.mults.bladeburner_analysis_mult = params.bladeburner_analysis_mult; } - if (params.bladeburner_success_chance_mult) { this.mults.bladeburner_success_chance_mult = params.bladeburner_success_chance_mult; } - - if(params.stats) - this.stats = params.stats; - else - this.stats = generateStatsDescription(this.mults, params.programs, params.startingMoney); + facObj.augmentations.push(this.name); + } } + } - // Adds this Augmentation to the specified Factions - addToFactions(factionList: string[]): void { - for (let i = 0; i < factionList.length; ++i) { - const faction: Faction | null = Factions[factionList[i]]; - if (faction == null) { - console.warn(`In Augmentation.addToFactions(), could not find faction with this name: ${factionList[i]}`); - continue; - } - faction.augmentations.push(this.name); - } - } + // Serialize the current object to a JSON save state. + toJSON(): any { + return Generic_toJSON("Augmentation", this); + } - // Adds this Augmentation to all Factions - addToAllFactions(): void { - for (const fac in Factions) { - if (Factions.hasOwnProperty(fac)) { - const facObj: Faction | null = Factions[fac]; - if (facObj == null) { - console.warn(`Invalid Faction object in addToAllFactions(). Key value: ${fac}`); - continue; - } - facObj.augmentations.push(this.name); - } - } - } - - // Serialize the current object to a JSON save state. - toJSON(): any { - return Generic_toJSON("Augmentation", this); - } - - // Initiatizes a Augmentation object from a JSON save state. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Augmentation { - return Generic_fromJSON(Augmentation, value.data); - } + // Initiatizes a Augmentation object from a JSON save state. + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Augmentation { + return Generic_fromJSON(Augmentation, value.data); + } } Reviver.constructors.Augmentation = Augmentation; diff --git a/src/Augmentation/AugmentationHelpers.jsx b/src/Augmentation/AugmentationHelpers.jsx index da88fe5f2..7a3d00b06 100644 --- a/src/Augmentation/AugmentationHelpers.jsx +++ b/src/Augmentation/AugmentationHelpers.jsx @@ -25,1973 +25,2559 @@ import React from "react"; import ReactDOM from "react-dom"; function AddToAugmentations(aug) { - var name = aug.name; - Augmentations[name] = aug; + var name = aug.name; + Augmentations[name] = aug; } function getRandomBonus() { - var bonuses = - [ - { - bonuses: { - hacking_chance_mult: 1.25, - hacking_speed_mult: 1.10, - hacking_money_mult: 1.25, - hacking_grow_mult: 1.1, - }, - description: "Increases the player's hacking chance by 25%.
    " + - "Increases the player's hacking speed by 10%.
    " + - "Increases the amount of money the player's gains from hacking by 25%.
    " + - "Improves grow() by 10%.", - }, - { - bonuses: { - hacking_mult: 1.15, - hacking_exp_mult: 2, - }, - description: "Increases the player's hacking skill by 15%.
    " + - "Increases the player's hacking experience gain rate by 100%.", - }, - { - bonuses: { - strength_mult: 1.25, - strength_exp_mult: 2, - defense_mult: 1.25, - defense_exp_mult: 2, - dexterity_mult: 1.25, - dexterity_exp_mult: 2, - agility_mult: 1.25, - agility_exp_mult: 2, - }, - description: "Increases all of the player's combat stats by 25%.
    " + - "Increases all of the player's combat stat experience gain rate by 100%.", - }, - { - bonuses: { - charisma_mult: 1.5, - charisma_exp_mult: 2, - }, - description: "This augmentation increases the player's charisma by 50%.
    " + - "Increases the player's charisma experience gain rate by 100%.", - }, - { - bonuses: { - hacknet_node_money_mult: 1.2, - hacknet_node_purchase_cost_mult: 0.85, - hacknet_node_ram_cost_mult: 0.85, - hacknet_node_core_cost_mult: 0.85, - hacknet_node_level_cost_mult: 0.85, - }, - description: "Increases the amount of money produced by Hacknet Nodes by 20%.
    " + - "Decreases all costs related to Hacknet Node by 15%.", - }, - { - bonuses: { - company_rep_mult: 1.25, - faction_rep_mult: 1.15, - work_money_mult: 1.7, - }, - description: "Increases the amount of money the player gains from working by 70%.
    " + - "Increases the amount of reputation the player gains when working for a company by 25%.
    " + - "Increases the amount of reputation the player gains for a faction by 15%.", - }, - { - bonuses: { - crime_success_mult: 2, - crime_money_mult: 2, - }, - description: "Increases the player's crime success rate by 100%.
    " + - "Increases the amount of money the player gains from crimes by 100%.", - }, - ] - - const randomNumber = (new WHRNG(Math.floor(Player.lastUpdate/3600000))); - for(let i = 0; i < 5; i++) randomNumber.step(); + var bonuses = [ + { + bonuses: { + hacking_chance_mult: 1.25, + hacking_speed_mult: 1.1, + hacking_money_mult: 1.25, + hacking_grow_mult: 1.1, + }, + description: + "Increases the player's hacking chance by 25%.
    " + + "Increases the player's hacking speed by 10%.
    " + + "Increases the amount of money the player's gains from hacking by 25%.
    " + + "Improves grow() by 10%.", + }, + { + bonuses: { + hacking_mult: 1.15, + hacking_exp_mult: 2, + }, + description: + "Increases the player's hacking skill by 15%.
    " + + "Increases the player's hacking experience gain rate by 100%.", + }, + { + bonuses: { + strength_mult: 1.25, + strength_exp_mult: 2, + defense_mult: 1.25, + defense_exp_mult: 2, + dexterity_mult: 1.25, + dexterity_exp_mult: 2, + agility_mult: 1.25, + agility_exp_mult: 2, + }, + description: + "Increases all of the player's combat stats by 25%.
    " + + "Increases all of the player's combat stat experience gain rate by 100%.", + }, + { + bonuses: { + charisma_mult: 1.5, + charisma_exp_mult: 2, + }, + description: + "This augmentation increases the player's charisma by 50%.
    " + + "Increases the player's charisma experience gain rate by 100%.", + }, + { + bonuses: { + hacknet_node_money_mult: 1.2, + hacknet_node_purchase_cost_mult: 0.85, + hacknet_node_ram_cost_mult: 0.85, + hacknet_node_core_cost_mult: 0.85, + hacknet_node_level_cost_mult: 0.85, + }, + description: + "Increases the amount of money produced by Hacknet Nodes by 20%.
    " + + "Decreases all costs related to Hacknet Node by 15%.", + }, + { + bonuses: { + company_rep_mult: 1.25, + faction_rep_mult: 1.15, + work_money_mult: 1.7, + }, + description: + "Increases the amount of money the player gains from working by 70%.
    " + + "Increases the amount of reputation the player gains when working for a company by 25%.
    " + + "Increases the amount of reputation the player gains for a faction by 15%.", + }, + { + bonuses: { + crime_success_mult: 2, + crime_money_mult: 2, + }, + description: + "Increases the player's crime success rate by 100%.
    " + + "Increases the amount of money the player gains from crimes by 100%.", + }, + ]; - return (bonuses[Math.floor(bonuses.length * randomNumber.random())]); + const randomNumber = new WHRNG(Math.floor(Player.lastUpdate / 3600000)); + for (let i = 0; i < 5; i++) randomNumber.step(); + + return bonuses[Math.floor(bonuses.length * randomNumber.random())]; } function initAugmentations() { - for (var name in Factions) { - if (Factions.hasOwnProperty(name)) { - Factions[name].augmentations = []; - } - } - - //Reset Augmentations - clearObject(Augmentations); - - //Time-Based Augment Test - const randomBonuses = getRandomBonus(); - - const UnstableCircadianModulatorParams = { - name:AugmentationNames.UnstableCircadianModulator, moneyCost:5e9, repCost:3.625e5, - info:"An experimental nanobot injection. Its unstable nature leads to " + - "unpredictable results based on your circadian rhythm.", - } - Object.keys(randomBonuses.bonuses).forEach(key => UnstableCircadianModulatorParams[key] = randomBonuses.bonuses[key]); - const UnstableCircadianModulator = new Augmentation(UnstableCircadianModulatorParams); - - UnstableCircadianModulator.addToFactions(["Speakers for the Dead"]); - if (augmentationExists(AugmentationNames.UnstableCircadianModulator)) { - delete Augmentations[AugmentationNames.UnstableCircadianModulator]; - } - AddToAugmentations(UnstableCircadianModulator); - - //Combat stat augmentations - const HemoRecirculator = new Augmentation({ - name:AugmentationNames.HemoRecirculator, moneyCost:4.5e7, repCost:1e4, - info:"A heart implant that greatly increases the body's ability to effectively use and pump " + - "blood.", - strength_mult: 1.08, - defense_mult: 1.08, - agility_mult: 1.08, - dexterity_mult: 1.08, - }); - HemoRecirculator.addToFactions(["Tetrads", "The Dark Army", "The Syndicate"]); - if (augmentationExists(AugmentationNames.HemoRecirculator)) { - delete Augmentations[AugmentationNames.HemoRecirculator]; - } - AddToAugmentations(HemoRecirculator); - - const Targeting1 = new Augmentation({ - name:AugmentationNames.Targeting1, moneyCost:1.5e7, repCost:5e3, - info:"A cranial implant that is embedded within the inner ear structures and optic nerves. It regulates " + - "and enhances balance and hand-eye coordination.", - dexterity_mult: 1.1, - }); - Targeting1.addToFactions(["Slum Snakes", "The Dark Army", "The Syndicate", "Sector-12", "Ishima", - "OmniTek Incorporated", "KuaiGong International", "Blade Industries"]); - if (augmentationExists(AugmentationNames.Targeting1)) { - delete Augmentations[AugmentationNames.Targeting1]; - } - AddToAugmentations(Targeting1); - - const Targeting2 = new Augmentation({ - name:AugmentationNames.Targeting2, moneyCost:4.25e7, repCost:8.75e3, - info:"This upgraded version of the 'Augmented Targeting' implant is capable of augmenting " + - "reality by digitally displaying weaknesses and vital signs of threats.", - prereqs:[AugmentationNames.Targeting1], - dexterity_mult: 1.2, - }); - Targeting2.addToFactions(["The Dark Army", "The Syndicate", "Sector-12", - "OmniTek Incorporated", "KuaiGong International", "Blade Industries"]); - if (augmentationExists(AugmentationNames.Targeting2)) { - delete Augmentations[AugmentationNames.Targeting2]; - } - AddToAugmentations(Targeting2); - - const Targeting3 = new Augmentation({ - name:AugmentationNames.Targeting3, moneyCost:1.15e8, repCost:2.75e4, - info:"The latest version of the 'Augmented Targeting' implant adds the ability to " + - "lock-on and track threats.", - prereqs:[AugmentationNames.Targeting2], - dexterity_mult: 1.3, - }); - Targeting3.addToFactions(["The Dark Army", "The Syndicate", "OmniTek Incorporated", - "KuaiGong International", "Blade Industries", "The Covenant"]); - if (augmentationExists(AugmentationNames.Targeting3)) { - delete Augmentations[AugmentationNames.Targeting3]; - } - AddToAugmentations(Targeting3); - - const SyntheticHeart = new Augmentation({ - name:AugmentationNames.SyntheticHeart, moneyCost:2.875e9, repCost:7.5e5, - info:"This advanced artificial heart, created from plasteel and graphene, is capable of pumping blood " + - "more efficiently than an organic heart.", - agility_mult: 1.5, - strength_mult: 1.5, - }); - SyntheticHeart.addToFactions(["KuaiGong International", "Fulcrum Secret Technologies", "Speakers for the Dead", - "NWO", "The Covenant", "Daedalus", "Illuminati"]); - if (augmentationExists(AugmentationNames.SyntheticHeart)) { - delete Augmentations[AugmentationNames.SyntheticHeart]; - } - AddToAugmentations(SyntheticHeart); - - const SynfibrilMuscle = new Augmentation({ - name:AugmentationNames.SynfibrilMuscle, repCost:4.375e5, moneyCost:1.125e9, - info:"The myofibrils in human muscles are injected with special chemicals that react with the proteins inside " + - "the myofibrils, altering their underlying structure. The end result is muscles that are stronger and more elastic. " + - "Scientists have named these artificially enhanced units 'synfibrils'.", - strength_mult: 1.3, - defense_mult: 1.3, - }); - SynfibrilMuscle.addToFactions(["KuaiGong International", "Fulcrum Secret Technologies", "Speakers for the Dead", - "NWO", "The Covenant", "Daedalus", "Illuminati", "Blade Industries"]); - if (augmentationExists(AugmentationNames.SynfibrilMuscle)) { - delete Augmentations[AugmentationNames.SynfibrilMuscle]; - } - AddToAugmentations(SynfibrilMuscle) - - const CombatRib1 = new Augmentation({ - name:AugmentationNames.CombatRib1, repCost:7.5e3, moneyCost:2.375e7, - info:"The rib cage is augmented to continuously release boosters into the bloodstream " + - "which increase the oxygen-carrying capacity of blood.", - strength_mult: 1.1, - defense_mult: 1.1, - }); - CombatRib1.addToFactions(["Slum Snakes", "The Dark Army", "The Syndicate", "Volhaven", "Ishima", - "OmniTek Incorporated", "KuaiGong International", "Blade Industries"]); - if (augmentationExists(AugmentationNames.CombatRib1)) { - delete Augmentations[AugmentationNames.CombatRib1]; - } - AddToAugmentations(CombatRib1); - - const CombatRib2 = new Augmentation({ - name:AugmentationNames.CombatRib2, repCost:1.875e4, moneyCost:6.5e7, - info:"An upgraded version of the 'Combat Rib' augmentation that adds potent stimulants which " + - "improve focus and endurance while decreasing reaction time and fatigue.", - prereqs:[AugmentationNames.CombatRib1], - strength_mult: 1.14, - defense_mult: 1.14, - }); - CombatRib2.addToFactions(["The Dark Army", "The Syndicate", "Volhaven", - "OmniTek Incorporated", "KuaiGong International", "Blade Industries"]); - if (augmentationExists(AugmentationNames.CombatRib2)) { - delete Augmentations[AugmentationNames.CombatRib2]; - } - AddToAugmentations(CombatRib2); - - const CombatRib3 = new Augmentation({ - name:AugmentationNames.CombatRib3, repCost:3.5e4, moneyCost:1.2e8, - info:"The latest version of the 'Combat Rib' augmentation releases advanced anabolic steroids that " + - "improve muscle mass and physical performance while being safe and free of side effects.", - prereqs:[AugmentationNames.CombatRib2], - strength_mult: 1.18, - defense_mult: 1.18, - }); - CombatRib3.addToFactions(["The Dark Army", "The Syndicate", "OmniTek Incorporated", - "KuaiGong International", "Blade Industries", "The Covenant"]); - if (augmentationExists(AugmentationNames.CombatRib3)) { - delete Augmentations[AugmentationNames.CombatRib3]; - } - AddToAugmentations(CombatRib3); - - const NanofiberWeave = new Augmentation({ - name:AugmentationNames.NanofiberWeave, repCost:3.75e4, moneyCost:1.25e8, - info:"Synthetic nanofibers are woven into the skin's extracellular matrix using electrospinning, " + - "which improves its regenerative and extracellular homeostasis abilities.", - strength_mult: 1.2, - defense_mult: 1.2, - }); - NanofiberWeave.addToFactions(["Tian Di Hui", "The Syndicate", "The Dark Army", "Speakers for the Dead", - "Blade Industries", "Fulcrum Secret Technologies", "OmniTek Incorporated"]); - if (augmentationExists(AugmentationNames.NanofiberWeave)) { - delete Augmentations[AugmentationNames.NanofiberWeave]; - } - AddToAugmentations(NanofiberWeave); - - const SubdermalArmor = new Augmentation({ - name:AugmentationNames.SubdermalArmor, repCost:8.75e5, moneyCost:3.25e9, - info:"The NEMEAN Subdermal Weave is a thin, light-weight, graphene plating that houses a dilatant fluid. " + - "The material is implanted underneath the skin, and is the most advanced form of defensive enhancement " + - "that has ever been created. The dilatant fluid, despite being thin and light, is extremely effective " + - "at stopping piercing blows and reducing blunt trauma. The properties of graphene allow the plating to " + - "mitigate damage from any fire or electrical traumas.", - defense_mult: 2.2, - }); - SubdermalArmor.addToFactions(["The Syndicate", "Fulcrum Secret Technologies", "Illuminati", "Daedalus", - "The Covenant"]); - if (augmentationExists(AugmentationNames.SubdermalArmor)) { - delete Augmentations[AugmentationNames.SubdermalArmor]; - } - AddToAugmentations(SubdermalArmor); - - const WiredReflexes = new Augmentation({ - name:AugmentationNames.WiredReflexes, repCost:1.25e3, moneyCost:2.5e6, - info:"Synthetic nerve-enhancements are injected into all major parts of the somatic nervous system, " + - "supercharging the spread of neural signals and increasing reflex speed.", - agility_mult: 1.05, - dexterity_mult: 1.05, - }); - WiredReflexes.addToFactions(["Tian Di Hui", "Slum Snakes", "Sector-12", "Volhaven", "Aevum", "Ishima", - "The Syndicate", "The Dark Army", "Speakers for the Dead"]); - if (augmentationExists(AugmentationNames.WiredReflexes)) { - delete Augmentations[AugmentationNames.WiredReflexes]; - } - AddToAugmentations(WiredReflexes); - - const GrapheneBoneLacings = new Augmentation({ - name:AugmentationNames.GrapheneBoneLacings, repCost:1.125e6, moneyCost:4.25e9, - info:"Graphene is grafted and fused into the skeletal structure, " + - "enhancing bone density and tensile strength.", - strength_mult: 1.7, - defense_mult: 1.7, - }); - GrapheneBoneLacings.addToFactions(["Fulcrum Secret Technologies", "The Covenant"]); - if (augmentationExists(AugmentationNames.GrapheneBoneLacings)) { - delete Augmentations[AugmentationNames.GrapheneBoneLacings]; - } - AddToAugmentations(GrapheneBoneLacings); - - const BionicSpine = new Augmentation({ - name:AugmentationNames.BionicSpine, repCost:4.5e4, moneyCost:1.25e8, - info:"The spine is reconstructed using plasteel and carbon fibers. " + - "It is now capable of stimulating and regulating neural signals " + - "passing through the spinal cord, improving senses and reaction speed. " + - "The 'Bionic Spine' also interfaces with all other 'Bionic' implants.", - strength_mult: 1.15, - defense_mult: 1.15, - agility_mult: 1.15, - dexterity_mult: 1.15, - }); - BionicSpine.addToFactions(["Speakers for the Dead", "The Syndicate", "KuaiGong International", - "OmniTek Incorporated", "Blade Industries"]); - if (augmentationExists(AugmentationNames.BionicSpine)) { - delete Augmentations[AugmentationNames.BionicSpine]; - } - AddToAugmentations(BionicSpine); - - const GrapheneBionicSpine = new Augmentation({ - name:AugmentationNames.GrapheneBionicSpine, repCost:1.625e6, moneyCost:6e9, - info:"An upgrade to the 'Bionic Spine' augmentation. The spine is fused with graphene " + - "which enhances durability and supercharges all body functions.", - prereqs:[AugmentationNames.BionicSpine], - strength_mult: 1.6, - defense_mult: 1.6, - agility_mult: 1.6, - dexterity_mult: 1.6, - }); - GrapheneBionicSpine.addToFactions(["Fulcrum Secret Technologies", "ECorp"]); - if (augmentationExists(AugmentationNames.GrapheneBionicSpine)) { - delete Augmentations[AugmentationNames.GrapheneBionicSpine]; - } - AddToAugmentations(GrapheneBionicSpine); - - const BionicLegs = new Augmentation({ - name:AugmentationNames.BionicLegs, repCost:1.5e5, moneyCost:3.75e8, - info:"Cybernetic legs, created from plasteel and carbon fibers, enhance running speed.", - agility_mult: 1.6, - }); - BionicLegs.addToFactions(["Speakers for the Dead", "The Syndicate", "KuaiGong International", - "OmniTek Incorporated", "Blade Industries"]); - if (augmentationExists(AugmentationNames.BionicLegs)) { - delete Augmentations[AugmentationNames.BionicLegs]; - } - AddToAugmentations(BionicLegs); - - const GrapheneBionicLegs = new Augmentation({ - name:AugmentationNames.GrapheneBionicLegs, repCost:7.5e5, moneyCost:4.5e9, - info:"An upgrade to the 'Bionic Legs' augmentation. The legs are fused " + - "with graphene, greatly enhancing jumping ability.", - prereqs: [AugmentationNames.BionicLegs], - agility_mult: 2.5, - }); - GrapheneBionicLegs.addToFactions(["MegaCorp", "ECorp", "Fulcrum Secret Technologies"]); - if (augmentationExists(AugmentationNames.GrapheneBionicLegs)) { - delete Augmentations[AugmentationNames.GrapheneBionicLegs]; - } - AddToAugmentations(GrapheneBionicLegs); - - // Work stat augmentations - const SpeechProcessor = new Augmentation({ - name:AugmentationNames.SpeechProcessor, repCost:7.5e3, moneyCost:5e7, - info:"A cochlear implant with an embedded computer that analyzes incoming speech. " + - "The embedded computer processes characteristics of incoming speech, such as tone " + - "and inflection, to pick up on subtle cues and aid in social interactions.", - charisma_mult: 1.2, - }); - SpeechProcessor.addToFactions(["Tian Di Hui", "Chongqing", "Sector-12", "New Tokyo", "Aevum", - "Ishima", "Volhaven", "Silhouette"]); - if (augmentationExists(AugmentationNames.SpeechProcessor)) { - delete Augmentations[AugmentationNames.SpeechProcessor]; - } - AddToAugmentations(SpeechProcessor); - - const TITN41Injection = new Augmentation({ - name:AugmentationNames.TITN41Injection, repCost:2.5e4, moneyCost:1.9e8, - info:"TITN is a series of viruses that targets and alters the sequences of human DNA in genes that " + - "control personality. The TITN-41 strain alters these genes so that the subject becomes more " + - "outgoing and socialable.", - charisma_mult: 1.15, - charisma_exp_mult: 1.15, - }); - TITN41Injection.addToFactions(["Silhouette"]); - if (augmentationExists(AugmentationNames.TITN41Injection)) { - delete Augmentations[AugmentationNames.TITN41Injection]; - } - AddToAugmentations(TITN41Injection); - - const EnhancedSocialInteractionImplant = new Augmentation({ - name:AugmentationNames.EnhancedSocialInteractionImplant, repCost:3.75e5, moneyCost:1.375e9, - info:"A cranial implant that greatly assists in the user's ability to analyze social situations " + - "and interactions. The system uses a wide variety of factors such as facial expressions, body " + - "language, and the voice tone, and inflection to determine the best course of action during social" + - "situations. The implant also uses deep learning software to continuously learn new behavior" + - "patterns and how to best respond.", - charisma_mult: 1.6, - charisma_exp_mult: 1.6, - }); - EnhancedSocialInteractionImplant.addToFactions(["Bachman & Associates", "NWO", "Clarke Incorporated", - "OmniTek Incorporated", "Four Sigma"]); - if (augmentationExists(AugmentationNames.EnhancedSocialInteractionImplant)) { - delete Augmentations[AugmentationNames.EnhancedSocialInteractionImplant]; - } - AddToAugmentations(EnhancedSocialInteractionImplant); - - // Hacking augmentations - const BitWire = new Augmentation({ - name:AugmentationNames.BitWire, repCost:3.75e3, moneyCost:1e7, - info: "A small brain implant embedded in the cerebrum. This regulates and improves the brain's computing " + - "capabilities.", - hacking_mult: 1.05, - }); - BitWire.addToFactions(["CyberSec", "NiteSec"]); - if (augmentationExists(AugmentationNames.BitWire)) { - delete Augmentations[AugmentationNames.BitWire]; - } - AddToAugmentations(BitWire); - - const ArtificialBioNeuralNetwork = new Augmentation({ - name:AugmentationNames.ArtificialBioNeuralNetwork, repCost:2.75e5, moneyCost:3e9, - info:"A network consisting of millions of nanoprocessors is embedded into the brain. " + - "The network is meant to mimic the way a biological brain solves a problem, with each " + - "nanoprocessor acting similar to the way a neuron would in a neural network. However, these " + - "nanoprocessors are programmed to perform computations much faster than organic neurons, " + - "allowing the user to solve much more complex problems at a much faster rate.", - hacking_speed_mult: 1.03, - hacking_money_mult: 1.15, - hacking_mult: 1.12, - }); - ArtificialBioNeuralNetwork.addToFactions(["BitRunners", "Fulcrum Secret Technologies"]); - if (augmentationExists(AugmentationNames.ArtificialBioNeuralNetwork)) { - delete Augmentations[AugmentationNames.ArtificialBioNeuralNetwork]; - } - AddToAugmentations(ArtificialBioNeuralNetwork); - - const ArtificialSynapticPotentiation = new Augmentation({ - name:AugmentationNames.ArtificialSynapticPotentiation, repCost:6.25e3, moneyCost:8e7, - info:"The body is injected with a chemical that artificially induces synaptic potentiation, " + - "otherwise known as the strengthening of synapses. This results in enhanced cognitive abilities.", - hacking_speed_mult: 1.02, - hacking_chance_mult: 1.05, - hacking_exp_mult: 1.05, - }); - ArtificialSynapticPotentiation.addToFactions(["The Black Hand", "NiteSec"]); - if (augmentationExists(AugmentationNames.ArtificialSynapticPotentiation)) { - delete Augmentations[AugmentationNames.ArtificialSynapticPotentiation]; - } - AddToAugmentations(ArtificialSynapticPotentiation); - - const EnhancedMyelinSheathing = new Augmentation({ - name:AugmentationNames.EnhancedMyelinSheathing, repCost:1e5, moneyCost:1.375e9, - info:"Electrical signals are used to induce a new, artificial form of myelinogenesis in the human body. " + - "This process results in the proliferation of new, synthetic myelin sheaths in the nervous " + - "system. These myelin sheaths can propogate neuro-signals much faster than their organic " + - "counterparts, leading to greater processing speeds and better brain function.", - hacking_speed_mult: 1.03, - hacking_exp_mult: 1.1, - hacking_mult: 1.08, - }); - EnhancedMyelinSheathing.addToFactions(["Fulcrum Secret Technologies", "BitRunners", "The Black Hand"]); - if (augmentationExists(AugmentationNames.EnhancedMyelinSheathing)) { - delete Augmentations[AugmentationNames.EnhancedMyelinSheathing]; - } - AddToAugmentations(EnhancedMyelinSheathing); - - const SynapticEnhancement = new Augmentation({ - name:AugmentationNames.SynapticEnhancement, repCost:2e3, moneyCost:7.5e6, - info:"A small cranial implant that continuously uses weak electrical signals to stimulate the brain and " + - "induce stronger synaptic activity. This improves the user's cognitive abilities.", - hacking_speed_mult: 1.03, - }); - SynapticEnhancement.addToFactions(["CyberSec", "Aevum"]); - if (augmentationExists(AugmentationNames.SynapticEnhancement)) { - delete Augmentations[AugmentationNames.SynapticEnhancement]; - } - AddToAugmentations(SynapticEnhancement); - - const NeuralRetentionEnhancement = new Augmentation({ - name:AugmentationNames.NeuralRetentionEnhancement, repCost:2e4, moneyCost:2.5e8, - info:"Chemical injections are used to permanently alter and strengthen the brain's neuronal " + - "circuits, strengthening the ability to retain information.", - hacking_exp_mult: 1.25, - }); - NeuralRetentionEnhancement.addToFactions(["NiteSec"]); - if (augmentationExists(AugmentationNames.NeuralRetentionEnhancement)) { - delete Augmentations[AugmentationNames.NeuralRetentionEnhancement]; - } - AddToAugmentations(NeuralRetentionEnhancement); - - const DataJack = new Augmentation({ - name:AugmentationNames.DataJack, repCost:1.125e5, moneyCost:4.5e8, - info:"A brain implant that provides an interface for direct, wireless communication between a computer's main " + - "memory and the mind. This implant allows the user to not only access a computer's memory, but also alter " + - "and delete it.", - hacking_money_mult: 1.25, - }); - DataJack.addToFactions(["BitRunners", "The Black Hand", "NiteSec", "Chongqing", "New Tokyo"]); - if (augmentationExists(AugmentationNames.DataJack)) { - delete Augmentations[AugmentationNames.DataJack]; - } - AddToAugmentations(DataJack); - - const ENM = new Augmentation({ - name:AugmentationNames.ENM, repCost:1.5e4, moneyCost:2.5e8, - info:"A thin device embedded inside the arm containing a wireless module capable of connecting " + - "to nearby networks. Once connected, the Netburner Module is capable of capturing and " + - "processing all of the traffic on that network. By itself, the Embedded Netburner Module does " + - "not do much, but a variety of very powerful upgrades can be installed that allow you to fully " + - "control the traffic on a network.", - hacking_mult: 1.08, - }); - ENM.addToFactions(["BitRunners", "The Black Hand", "NiteSec", "ECorp", "MegaCorp", - "Fulcrum Secret Technologies", "NWO", "Blade Industries"]); - if (augmentationExists(AugmentationNames.ENM)) { - delete Augmentations[AugmentationNames.ENM]; - } - AddToAugmentations(ENM); - - const ENMCore = new Augmentation({ - name:AugmentationNames.ENMCore, repCost:2.5e5, moneyCost:2.5e9, - info:"The Core library is an implant that upgrades the firmware of the Embedded Netburner Module. " + - "This upgrade allows the Embedded Netburner Module to generate its own data on a network.", - prereqs:[AugmentationNames.ENM], - hacking_speed_mult: 1.03, - hacking_money_mult: 1.1, - hacking_chance_mult: 1.03, - hacking_exp_mult: 1.07, - hacking_mult: 1.07, - }); - ENMCore.addToFactions(["BitRunners", "The Black Hand", "ECorp", "MegaCorp", - "Fulcrum Secret Technologies", "NWO", "Blade Industries"]); - if (augmentationExists(AugmentationNames.ENMCore)) { - delete Augmentations[AugmentationNames.ENMCore]; - } - AddToAugmentations(ENMCore); - - const ENMCoreV2 = new Augmentation({ - name:AugmentationNames.ENMCoreV2, repCost:1e6, moneyCost:4.5e9, - info:"The Core V2 library is an implant that upgrades the firmware of the Embedded Netburner Module. " + - "This upgraded firmware allows the Embedded Netburner Module to control information on " + - "a network by re-routing traffic, spoofing IP addresses, and altering the data inside network " + - "packets.", - prereqs:[AugmentationNames.ENMCore], - hacking_speed_mult: 1.05, - hacking_money_mult: 1.3, - hacking_chance_mult: 1.05, - hacking_exp_mult: 1.15, - hacking_mult: 1.08, - }); - ENMCoreV2.addToFactions(["BitRunners", "ECorp", "MegaCorp", "Fulcrum Secret Technologies", "NWO", - "Blade Industries", "OmniTek Incorporated", "KuaiGong International"]); - if (augmentationExists(AugmentationNames.ENMCoreV2)) { - delete Augmentations[AugmentationNames.ENMCoreV2]; - } - AddToAugmentations(ENMCoreV2); - - const ENMCoreV3 = new Augmentation({ - name:AugmentationNames.ENMCoreV3, repCost:1.75e6, moneyCost:7.5e9, - info:"The Core V3 library is an implant that upgrades the firmware of the Embedded Netburner Module. " + - "This upgraded firmware allows the Embedded Netburner Module to seamlessly inject code into " + - "any device on a network.", - prereqs:[AugmentationNames.ENMCoreV2], - hacking_speed_mult: 1.05, - hacking_money_mult: 1.4, - hacking_chance_mult: 1.1, - hacking_exp_mult: 1.25, - hacking_mult: 1.1, - }); - ENMCoreV3.addToFactions(["ECorp", "MegaCorp", "Fulcrum Secret Technologies", "NWO", - "Daedalus", "The Covenant", "Illuminati"]); - if (augmentationExists(AugmentationNames.ENMCoreV3)) { - delete Augmentations[AugmentationNames.ENMCoreV3]; - } - AddToAugmentations(ENMCoreV3); - - const ENMAnalyzeEngine = new Augmentation({ - name:AugmentationNames.ENMAnalyzeEngine, repCost:6.25e5, moneyCost:6e9, - info:"Installs the Analyze Engine for the Embedded Netburner Module, which is a CPU cluster " + - "that vastly outperforms the Netburner Module's native single-core processor.", - prereqs:[AugmentationNames.ENM], - hacking_speed_mult: 1.1, - }); - ENMAnalyzeEngine.addToFactions(["ECorp", "MegaCorp", "Fulcrum Secret Technologies", "NWO", - "Daedalus", "The Covenant", "Illuminati"]); - if (augmentationExists(AugmentationNames.ENMAnalyzeEngine)) { - delete Augmentations[AugmentationNames.ENMAnalyzeEngine]; - } - AddToAugmentations(ENMAnalyzeEngine); - - const ENMDMA = new Augmentation({ - name:AugmentationNames.ENMDMA, repCost:1e6, moneyCost:7e9, - info:"This implant installs a Direct Memory Access (DMA) controller into the " + - "Embedded Netburner Module. This allows the Module to send and receive data " + - "directly to and from the main memory of devices on a network.", - prereqs:[AugmentationNames.ENM], - hacking_money_mult: 1.4, - hacking_chance_mult: 1.2, - }); - ENMDMA.addToFactions(["ECorp", "MegaCorp", "Fulcrum Secret Technologies", "NWO", - "Daedalus", "The Covenant", "Illuminati"]); - if (augmentationExists(AugmentationNames.ENMDMA)) { - delete Augmentations[AugmentationNames.ENMDMA]; - } - AddToAugmentations(ENMDMA); - - const Neuralstimulator = new Augmentation({ - name:AugmentationNames.Neuralstimulator, repCost:5e4, moneyCost:3e9, - info:"A cranial implant that intelligently stimulates certain areas of the brain " + - "in order to improve cognitive functions.", - hacking_speed_mult: 1.02, - hacking_chance_mult: 1.1, - hacking_exp_mult: 1.12, - }); - Neuralstimulator.addToFactions(["The Black Hand", "Chongqing", "Sector-12", "New Tokyo", "Aevum", - "Ishima", "Volhaven", "Bachman & Associates", "Clarke Incorporated", - "Four Sigma"]); - if (augmentationExists(AugmentationNames.Neuralstimulator)) { - delete Augmentations[AugmentationNames.Neuralstimulator]; - } - AddToAugmentations(Neuralstimulator); - - const NeuralAccelerator = new Augmentation({ - name:AugmentationNames.NeuralAccelerator, repCost:2e5, moneyCost:1.75e9, - info:"A microprocessor that accelerates the processing " + - "speed of biological neural networks. This is a cranial implant that is embedded inside the brain.", - hacking_mult: 1.1, - hacking_exp_mult: 1.15, - hacking_money_mult: 1.2, - }); - NeuralAccelerator.addToFactions(["BitRunners"]); - if (augmentationExists(AugmentationNames.NeuralAccelerator)) { - delete Augmentations[AugmentationNames.NeuralAccelerator]; - } - AddToAugmentations(NeuralAccelerator); - - const CranialSignalProcessorsG1 = new Augmentation({ - name:AugmentationNames.CranialSignalProcessorsG1, repCost:1e4, moneyCost:7e7, - info:"The first generation of Cranial Signal Processors. Cranial Signal Processors " + - "are a set of specialized microprocessors that are attached to " + - "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + - "so that the brain doesn't have to.", - hacking_speed_mult: 1.01, - hacking_mult: 1.05, - }); - CranialSignalProcessorsG1.addToFactions(["CyberSec"]); - if (augmentationExists(AugmentationNames.CranialSignalProcessorsG1)) { - delete Augmentations[AugmentationNames.CranialSignalProcessorsG1]; - } - AddToAugmentations(CranialSignalProcessorsG1); - - const CranialSignalProcessorsG2 = new Augmentation({ - name:AugmentationNames.CranialSignalProcessorsG2, repCost:1.875e4, moneyCost:1.25e8, - info:"The second generation of Cranial Signal Processors. Cranial Signal Processors " + - "are a set of specialized microprocessors that are attached to " + - "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + - "so that the brain doesn't have to.", - prereqs:[AugmentationNames.CranialSignalProcessorsG1], - hacking_speed_mult: 1.02, - hacking_chance_mult: 1.05, - hacking_mult: 1.07, - }); - CranialSignalProcessorsG2.addToFactions(["CyberSec", "NiteSec"]); - if (augmentationExists(AugmentationNames.CranialSignalProcessorsG2)) { - delete Augmentations[AugmentationNames.CranialSignalProcessorsG2]; - } - AddToAugmentations(CranialSignalProcessorsG2); - - const CranialSignalProcessorsG3 = new Augmentation({ - name:AugmentationNames.CranialSignalProcessorsG3, repCost:5e4, moneyCost:5.5e8, - info:"The third generation of Cranial Signal Processors. Cranial Signal Processors " + - "are a set of specialized microprocessors that are attached to " + - "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + - "so that the brain doesn't have to.", - prereqs:[AugmentationNames.CranialSignalProcessorsG2], - hacking_speed_mult: 1.02, - hacking_money_mult: 1.15, - hacking_mult: 1.09, - }); - CranialSignalProcessorsG3.addToFactions(["NiteSec", "The Black Hand", "BitRunners"]); - if (augmentationExists(AugmentationNames.CranialSignalProcessorsG3)) { - delete Augmentations[AugmentationNames.CranialSignalProcessorsG3]; - } - AddToAugmentations(CranialSignalProcessorsG3); - - const CranialSignalProcessorsG4 = new Augmentation({ - name:AugmentationNames.CranialSignalProcessorsG4, repCost:1.25e5, moneyCost:1.1e9, - info:"The fourth generation of Cranial Signal Processors. Cranial Signal Processors " + - "are a set of specialized microprocessors that are attached to " + - "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + - "so that the brain doesn't have to.", - prereqs:[AugmentationNames.CranialSignalProcessorsG3], - hacking_speed_mult: 1.02, - hacking_money_mult: 1.2, - hacking_grow_mult: 1.25, - }); - CranialSignalProcessorsG4.addToFactions(["The Black Hand", "BitRunners"]); - if (augmentationExists(AugmentationNames.CranialSignalProcessorsG4)) { - delete Augmentations[AugmentationNames.CranialSignalProcessorsG4]; - } - AddToAugmentations(CranialSignalProcessorsG4); - - const CranialSignalProcessorsG5 = new Augmentation({ - name:AugmentationNames.CranialSignalProcessorsG5, repCost:2.5e5, moneyCost:2.25e9, - info:"The fifth generation of Cranial Signal Processors. Cranial Signal Processors " + - "are a set of specialized microprocessors that are attached to " + - "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + - "so that the brain doesn't have to.", - prereqs:[AugmentationNames.CranialSignalProcessorsG4], - hacking_mult: 1.3, - hacking_money_mult: 1.25, - hacking_grow_mult: 1.75, - }); - CranialSignalProcessorsG5.addToFactions(["BitRunners"]); - if (augmentationExists(AugmentationNames.CranialSignalProcessorsG5)) { - delete Augmentations[AugmentationNames.CranialSignalProcessorsG5]; - } - AddToAugmentations(CranialSignalProcessorsG5); - - const NeuronalDensification = new Augmentation({ - name:AugmentationNames.NeuronalDensification, repCost:1.875e5, moneyCost:1.375e9, - info:"The brain is surgically re-engineered to have increased neuronal density " + - "by decreasing the neuron gap junction. Then, the body is genetically modified " + - "to enhance the production and capabilities of its neural stem cells.", - hacking_mult: 1.15, - hacking_exp_mult: 1.1, - hacking_speed_mult: 1.03, - }); - NeuronalDensification.addToFactions(["Clarke Incorporated"]); - if (augmentationExists(AugmentationNames.NeuronalDensification)) { - delete Augmentations[AugmentationNames.NeuronalDensification]; - } - AddToAugmentations(NeuronalDensification); - - // Work Augmentations - const NuoptimalInjectorImplant = new Augmentation({ - name:AugmentationNames.NuoptimalInjectorImplant, repCost:5e3, moneyCost:2e7, - info:"This torso implant automatically injects nootropic supplements into " + - "the bloodstream to improve memory, increase focus, and provide other " + - "cognitive enhancements.", - company_rep_mult: 1.2, - }); - NuoptimalInjectorImplant.addToFactions(["Tian Di Hui", "Volhaven", "New Tokyo", "Chongqing", - "Clarke Incorporated", "Four Sigma", "Bachman & Associates"]); - if (augmentationExists(AugmentationNames.NuoptimalInjectorImplant)) { - delete Augmentations[AugmentationNames.NuoptimalInjectorImplant]; - } - AddToAugmentations(NuoptimalInjectorImplant); - - const SpeechEnhancement = new Augmentation({ - name:AugmentationNames.SpeechEnhancement, repCost:2.5e3, moneyCost:1.25e7, - info:"An advanced neural implant that improves your speaking abilities, making " + - "you more convincing and likable in conversations and overall improving your " + - "social interactions.", - company_rep_mult: 1.1, - charisma_mult: 1.1, - }); - SpeechEnhancement.addToFactions(["Tian Di Hui", "Speakers for the Dead", "Four Sigma", "KuaiGong International", - "Clarke Incorporated", "Bachman & Associates"]); - if (augmentationExists(AugmentationNames.SpeechEnhancement)) { - delete Augmentations[AugmentationNames.SpeechEnhancement]; - } - AddToAugmentations(SpeechEnhancement); - - const FocusWire = new Augmentation({ - name:AugmentationNames.FocusWire, repCost:7.5e4, moneyCost:9e8, - info:"A cranial implant that stops procrastination by blocking specific neural pathways " + - "in the brain.", - hacking_exp_mult: 1.05, - strength_exp_mult: 1.05, - defense_exp_mult: 1.05, - dexterity_exp_mult: 1.05, - agility_exp_mult: 1.05, - charisma_exp_mult: 1.05, - company_rep_mult: 1.1, - work_money_mult: 1.2, - }); - FocusWire.addToFactions(["Bachman & Associates", "Clarke Incorporated", "Four Sigma", "KuaiGong International"]); - if (augmentationExists(AugmentationNames.FocusWire)) { - delete Augmentations[AugmentationNames.FocusWire]; - } - AddToAugmentations(FocusWire) - - const PCDNI = new Augmentation({ - name:AugmentationNames.PCDNI, repCost:3.75e5, moneyCost:3.75e9, - info:"Installs a Direct-Neural Interface jack into your arm that is compatible with most " + - "computers. Connecting to a computer through this jack allows you to interface with " + - "it using the brain's electrochemical signals.", - company_rep_mult: 1.3, - hacking_mult: 1.08, - }); - PCDNI.addToFactions(["Four Sigma", "OmniTek Incorporated", "ECorp", "Blade Industries"]); - if (augmentationExists(AugmentationNames.PCDNI)) { - delete Augmentations[AugmentationNames.PCDNI]; - } - AddToAugmentations(PCDNI); - - const PCDNIOptimizer = new Augmentation({ - name:AugmentationNames.PCDNIOptimizer, repCost:5e5, moneyCost:4.5e9, - info:"This is a submodule upgrade to the PC Direct-Neural Interface augmentation. It " + - "improves the performance of the interface and gives the user more control options " + - "to a connected computer.", - prereqs:[AugmentationNames.PCDNI], - company_rep_mult: 1.75, - hacking_mult: 1.1, - }); - PCDNIOptimizer.addToFactions(["Fulcrum Secret Technologies", "ECorp", "Blade Industries"]); - if (augmentationExists(AugmentationNames.PCDNIOptimizer)) { - delete Augmentations[AugmentationNames.PCDNIOptimizer]; - } - AddToAugmentations(PCDNIOptimizer); - - const PCDNINeuralNetwork = new Augmentation({ - name:AugmentationNames.PCDNINeuralNetwork, repCost:1.5e6, moneyCost:7.5e9, - info:"This is an additional installation that upgrades the functionality of the " + - "PC Direct-Neural Interface augmentation. When connected to a computer, " + - "The Neural Network upgrade allows the user to use their own brain's " + - "processing power to aid the computer in computational tasks.", - prereqs:[AugmentationNames.PCDNI], - company_rep_mult: 2, - hacking_mult: 1.1, - hacking_speed_mult: 1.05, - }); - PCDNINeuralNetwork.addToFactions(["Fulcrum Secret Technologies"]); - if (augmentationExists(AugmentationNames.PCDNINeuralNetwork)) { - delete Augmentations[AugmentationNames.PCDNINeuralNetwork]; - } - AddToAugmentations(PCDNINeuralNetwork); - - const ADRPheromone1 = new Augmentation({ - name:AugmentationNames.ADRPheromone1, repCost:3.75e3, moneyCost:1.75e7, - info:"The body is genetically re-engineered so that it produces the ADR-V1 pheromone, " + - "an artificial pheromone discovered by scientists. The ADR-V1 pheromone, when excreted, " + - "triggers feelings of admiration and approval in other people.", - company_rep_mult: 1.1, - faction_rep_mult: 1.1, - }); - ADRPheromone1.addToFactions(["Tian Di Hui", "The Syndicate", "NWO", "MegaCorp", "Four Sigma"]); - if (augmentationExists(AugmentationNames.ADRPheromone1)) { - delete Augmentations[AugmentationNames.ADRPheromone1]; - } - AddToAugmentations(ADRPheromone1); - - const ADRPheromone2 = new Augmentation({ - name:AugmentationNames.ADRPheromone2, repCost:6.25e4, moneyCost:5.5e8, - info:"The body is genetically re-engineered so that it produces the ADR-V2 pheromone, " + - "which is similar to but more potent than ADR-V1. This pheromone, when excreted, " + - "triggers feelings of admiration, approval, and respect in others.", - company_rep_mult: 1.2, - faction_rep_mult: 1.2, - }); - ADRPheromone2.addToFactions(["Silhouette", "Four Sigma", "Bachman & Associates", "Clarke Incorporated"]); - if (augmentationExists(AugmentationNames.ADRPheromone2)) { - delete Augmentations[AugmentationNames.ADRPheromone2]; - } - AddToAugmentations(ADRPheromone2); - - const ShadowsSimulacrum = new Augmentation({ - name: AugmentationNames.ShadowsSimulacrum, repCost:3.75e4, moneyCost:4e8, - info: "A crude but functional matter phase-shifter module that is embedded " + - "in the brainstem and cerebellum. This augmentation was developed by " + - "criminal organizations and allows the user to project and control holographic " + - "simulacrums within a large radius. These simulacrums are commonly used for " + - "espionage and surveillance work.", - company_rep_mult: 1.15, - faction_rep_mult: 1.15, - }); - ShadowsSimulacrum.addToFactions(["The Syndicate", "The Dark Army", "Speakers for the Dead"]); - if (augmentationExists(AugmentationNames.ShadowsSimulacrum)) { - delete Augmentations[AugmentationNames.ShadowsSimulacrum]; - } - AddToAugmentations(ShadowsSimulacrum); - - // HacknetNode Augmentations - const HacknetNodeCPUUpload = new Augmentation({ - name:AugmentationNames.HacknetNodeCPUUpload, repCost:3.75e3, moneyCost:1.1e7, - info:"Uploads the architecture and design details of a Hacknet Node's CPU into " + - "the brain. This allows the user to engineer custom hardware and software " + - "for the Hacknet Node that provides better performance.", - hacknet_node_money_mult: 1.15, - hacknet_node_purchase_cost_mult: 0.85, - }); - HacknetNodeCPUUpload.addToFactions(["Netburners"]); - if (augmentationExists(AugmentationNames.HacknetNodeCPUUpload)) { - delete Augmentations[AugmentationNames.HacknetNodeCPUUpload]; - } - AddToAugmentations(HacknetNodeCPUUpload); - - const HacknetNodeCacheUpload = new Augmentation({ - name:AugmentationNames.HacknetNodeCacheUpload, repCost:2.5e3, moneyCost:5.5e6, - info:"Uploads the architecture and design details of a Hacknet Node's main-memory cache " + - "into the brain. This allows the user to engineer custom cache hardware for the " + - "Hacknet Node that offers better performance.", - hacknet_node_money_mult: 1.10, - hacknet_node_level_cost_mult: 0.85, - }); - HacknetNodeCacheUpload.addToFactions(["Netburners"]); - if (augmentationExists(AugmentationNames.HacknetNodeCacheUpload)) { - delete Augmentations[AugmentationNames.HacknetNodeCacheUpload]; - } - AddToAugmentations(HacknetNodeCacheUpload); - - const HacknetNodeNICUpload = new Augmentation({ - name:AugmentationNames.HacknetNodeNICUpload, repCost:1.875e3, moneyCost:4.5e6, - info:"Uploads the architecture and design details of a Hacknet Node's Network Interface Card (NIC) " + - "into the brain. This allows the user to engineer a custom NIC for the Hacknet Node that " + - "offers better performance.", - hacknet_node_money_mult: 1.1, - hacknet_node_purchase_cost_mult: 0.9, - }); - HacknetNodeNICUpload.addToFactions(["Netburners"]); - if (augmentationExists(AugmentationNames.HacknetNodeNICUpload)) { - delete Augmentations[AugmentationNames.HacknetNodeNICUpload]; - } - AddToAugmentations(HacknetNodeNICUpload); - - const HacknetNodeKernelDNI = new Augmentation({ - name:AugmentationNames.HacknetNodeKernelDNI, repCost:7.5e3, moneyCost:4e7, - info:"Installs a Direct-Neural Interface jack into the arm that is capable of connecting to a " + - "Hacknet Node. This lets the user access and manipulate the Node's kernel using " + - "electrochemical signals.", - hacknet_node_money_mult: 1.25, - }); - HacknetNodeKernelDNI.addToFactions(["Netburners"]); - if (augmentationExists(AugmentationNames.HacknetNodeKernelDNI)) { - delete Augmentations[AugmentationNames.HacknetNodeKernelDNI]; - } - AddToAugmentations(HacknetNodeKernelDNI); - - const HacknetNodeCoreDNI = new Augmentation({ - name:AugmentationNames.HacknetNodeCoreDNI, repCost:1.25e4, moneyCost:6e7, - info:"Installs a Direct-Neural Interface jack into the arm that is capable of connecting " + - "to a Hacknet Node. This lets the user access and manipulate the Node's processing logic using " + - "electrochemical signals.", - hacknet_node_money_mult: 1.45, - }); - HacknetNodeCoreDNI.addToFactions(["Netburners"]); - if (augmentationExists(AugmentationNames.HacknetNodeCoreDNI)) { - delete Augmentations[AugmentationNames.HacknetNodeCoreDNI]; - } - AddToAugmentations(HacknetNodeCoreDNI); - - //Misc/Hybrid augmentations - const NeuroFluxGovernor = new Augmentation({ - name:AugmentationNames.NeuroFluxGovernor, repCost:1.25e3, moneyCost:3.75e6, - info:"A device that is embedded in the back of the neck. The NeuroFlux Governor " + - "monitors and regulates nervous impulses coming to and from the spinal column, " + - "essentially 'governing' the body. By doing so, it improves the functionality of the " + - "body's nervous system.", - stats: <> - This special augmentation can be leveled up infinitely. - Each level of this augmentation increases ALL multipliers by 1%, - stacking multiplicatively. - , - hacking_chance_mult: 1.01, - hacking_speed_mult: 1.01, - hacking_money_mult: 1.01, - hacking_grow_mult: 1.01, - hacking_mult: 1.01, - strength_mult: 1.01, - defense_mult: 1.01, - dexterity_mult: 1.01, - agility_mult: 1.01, - charisma_mult: 1.01, - hacking_exp_mult: 1.01, - strength_exp_mult: 1.01, - defense_exp_mult: 1.01, - dexterity_exp_mult: 1.01, - agility_exp_mult: 1.01, - charisma_exp_mult: 1.01, - company_rep_mult: 1.01, - faction_rep_mult: 1.01, - crime_money_mult: 1.01, - crime_success_mult: 1.01, - hacknet_node_money_mult: 1.01, - hacknet_node_purchase_cost_mult: 0.99, - hacknet_node_ram_cost_mult: 0.99, - hacknet_node_core_cost_mult: 0.99, - hacknet_node_level_cost_mult: 0.99, - work_money_mult: 1.01, - }); - - // Set the Augmentation's level to the currently-installed level - let currLevel = 0; - for (let i = 0; i < Player.augmentations.length; ++i) { - if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) { - currLevel = Player.augmentations[i].level; - } - } - NeuroFluxGovernor.level = currLevel; - - // To set the price/rep req of the NeuroFlux, we have to take into account NeuroFlux - // levels that are purchased but not yet installed - let nextLevel = currLevel; - for (let i = 0; i < Player.queuedAugmentations.length; ++i) { - if (Player.queuedAugmentations[i].name === AugmentationNames.NeuroFluxGovernor) { - ++nextLevel; - } - } - let mult = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel); - NeuroFluxGovernor.baseRepRequirement = 500 * mult * BitNodeMultipliers.AugmentationRepCost; - NeuroFluxGovernor.baseCost = 750e3 * mult * BitNodeMultipliers.AugmentationMoneyCost; - if (augmentationExists(AugmentationNames.NeuroFluxGovernor)) { - delete Augmentations[AugmentationNames.NeuroFluxGovernor]; - } - NeuroFluxGovernor.addToAllFactions(); - AddToAugmentations(NeuroFluxGovernor); - - const Neurotrainer1 = new Augmentation({ - name:AugmentationNames.Neurotrainer1, repCost:1e3, moneyCost:4e6, - info:"A decentralized cranial implant that improves the brain's ability to learn. It is " + - "installed by releasing millions of nanobots into the human brain, each of which " + - "attaches to a different neural pathway to enhance the brain's ability to retain " + - "and retrieve information.", - hacking_exp_mult: 1.1, - strength_exp_mult: 1.1, - defense_exp_mult: 1.1, - dexterity_exp_mult: 1.1, - agility_exp_mult: 1.1, - charisma_exp_mult: 1.1, - }); - Neurotrainer1.addToFactions(["CyberSec", "Aevum"]); - if (augmentationExists(AugmentationNames.Neurotrainer1)) { - delete Augmentations[AugmentationNames.Neurotrainer1]; - } - AddToAugmentations(Neurotrainer1); - - const Neurotrainer2 = new Augmentation({ - name:AugmentationNames.Neurotrainer2, repCost:1e4, moneyCost:4.5e7, - info:"A decentralized cranial implant that improves the brain's ability to learn. This " + - "is a more powerful version of the Neurotrainer I augmentation, but it does not " + - "require Neurotrainer I to be installed as a prerequisite.", - hacking_exp_mult: 1.15, - strength_exp_mult: 1.15, - defense_exp_mult: 1.15, - dexterity_exp_mult: 1.15, - agility_exp_mult: 1.15, - charisma_exp_mult: 1.15, - }); - Neurotrainer2.addToFactions(["BitRunners", "NiteSec"]); - if (augmentationExists(AugmentationNames.Neurotrainer2)) { - delete Augmentations[AugmentationNames.Neurotrainer2]; - } - AddToAugmentations(Neurotrainer2); - - const Neurotrainer3 = new Augmentation({ - name:AugmentationNames.Neurotrainer3, repCost:2.5e4, moneyCost:1.3e8, - info:"A decentralized cranial implant that improves the brain's ability to learn. This " + - "is a more powerful version of the Neurotrainer I and Neurotrainer II augmentation, " + - "but it does not require either of them to be installed as a prerequisite.", - hacking_exp_mult: 1.2, - strength_exp_mult: 1.2, - defense_exp_mult: 1.2, - dexterity_exp_mult: 1.2, - agility_exp_mult: 1.2, - charisma_exp_mult: 1.2, - }); - Neurotrainer3.addToFactions(["NWO", "Four Sigma"]); - if (augmentationExists(AugmentationNames.Neurotrainer3)) { - delete Augmentations[AugmentationNames.Neurotrainer3]; - } - AddToAugmentations(Neurotrainer3); - - const Hypersight = new Augmentation({ - name:AugmentationNames.Hypersight, repCost:1.5e5, moneyCost:2.75e9, - info:"A bionic eye implant that grants sight capabilities far beyond those of a natural human. " + - "Embedded circuitry within the implant provides the ability to detect heat and movement " + - "through solid objects such as walls, thus providing 'x-ray vision'-like capabilities.", - dexterity_mult: 1.4, - hacking_speed_mult: 1.03, - hacking_money_mult: 1.1, - }); - Hypersight.addToFactions(["Blade Industries", "KuaiGong International"]); - if (augmentationExists(AugmentationNames.Hypersight)) { - delete Augmentations[AugmentationNames.Hypersight]; - } - AddToAugmentations(Hypersight); - - const LuminCloaking1 = new Augmentation({ - name:AugmentationNames.LuminCloaking1, repCost:1.5e3, moneyCost:5e6, - info:"A skin implant that reinforces the skin with highly-advanced synthetic cells. These " + - "cells, when powered, have a negative refractive index. As a result, they bend light " + - "around the skin, making the user much harder to see to the naked eye.", - agility_mult: 1.05, - crime_money_mult: 1.1, - }); - LuminCloaking1.addToFactions(["Slum Snakes", "Tetrads"]); - if (augmentationExists(AugmentationNames.LuminCloaking1)) { - delete Augmentations[AugmentationNames.LuminCloaking1]; - } - AddToAugmentations(LuminCloaking1); - - const LuminCloaking2 = new Augmentation({ - name:AugmentationNames.LuminCloaking2, repCost:5e3, moneyCost:3e7, - info:"This is a more advanced version of the LuminCloaking-V1 augmentation. This skin implant " + - "reinforces the skin with highly-advanced synthetic cells. These " + - "cells, when powered, are capable of not only bending light but also of bending heat, " + - "making the user more resilient as well as stealthy.", - prereqs:[AugmentationNames.LuminCloaking1], - agility_mult: 1.1, - defense_mult: 1.1, - crime_money_mult: 1.25, - }); - LuminCloaking2.addToFactions(["Slum Snakes", "Tetrads"]); - if (augmentationExists(AugmentationNames.LuminCloaking2)) { - delete Augmentations[AugmentationNames.LuminCloaking2]; - } - AddToAugmentations(LuminCloaking2); - - const SmartSonar = new Augmentation({ - name:AugmentationNames.SmartSonar, repCost:2.25e4, moneyCost:7.5e7, - info:"A cochlear implant that helps the player detect and locate enemies " + - "using sound propagation.", - dexterity_mult: 1.1, - dexterity_exp_mult: 1.15, - crime_money_mult: 1.25, - }); - SmartSonar.addToFactions(["Slum Snakes"]); - if (augmentationExists(AugmentationNames.SmartSonar)) { - delete Augmentations[AugmentationNames.SmartSonar]; - } - AddToAugmentations(SmartSonar); - - const PowerRecirculator = new Augmentation({ - name:AugmentationNames.PowerRecirculator, repCost:2.5e4, moneyCost:1.8e8, - info:"The body's nerves are attached with polypyrrole nanocircuits that " + - "are capable of capturing wasted energy, in the form of heat, " + - "and converting it back into usable power.", - hacking_mult: 1.05, - strength_mult: 1.05, - defense_mult: 1.05, - dexterity_mult: 1.05, - agility_mult: 1.05, - charisma_mult: 1.05, - hacking_exp_mult: 1.1, - strength_exp_mult: 1.1, - defense_exp_mult: 1.1, - dexterity_exp_mult: 1.1, - agility_exp_mult: 1.1, - charisma_exp_mult: 1.1, - }); - PowerRecirculator.addToFactions(["Tetrads", "The Dark Army", "The Syndicate", "NWO"]); - if (augmentationExists(AugmentationNames.PowerRecirculator)) { - delete Augmentations[AugmentationNames.PowerRecirculator]; - } - AddToAugmentations(PowerRecirculator); - - // Unique AUGS (Each Faction gets one unique augmentation) - // Factions that already have unique augs up to this point: - // Slum Snakes, CyberSec, Netburners, Fulcrum Secret Technologies, - // Silhouette - - // Illuminati - const QLink = new Augmentation({ - name:AugmentationNames.QLink, repCost:1.875e6, moneyCost:2.5e13, - info:"A brain implant that wirelessly connects you to the Illuminati's " + - "quantum supercomputer, allowing you to access and use its incredible " + - "computing power.", - hacking_mult: 1.75, - hacking_speed_mult: 2, - hacking_chance_mult: 2.5, - hacking_money_mult: 4, - }); - QLink.addToFactions(["Illuminati"]); - if (augmentationExists(AugmentationNames.QLink)) { - delete Augmentations[AugmentationNames.QLink]; - } - AddToAugmentations(QLink); - - // Daedalus - const RedPill = new Augmentation({ - name:AugmentationNames.TheRedPill, repCost:2.5e6, moneyCost:0e0, - info:"It's time to leave the cave.", - stats: <>, - }); - RedPill.addToFactions(["Daedalus"]); - if (augmentationExists(AugmentationNames.TheRedPill)) { - delete Augmentations[AugmentationNames.TheRedPill]; - } - AddToAugmentations(RedPill); - - // Covenant - const SPTN97 = new Augmentation({ - name:AugmentationNames.SPTN97, repCost:1.25e6, moneyCost:4.875e9, - info:"The SPTN-97 gene is injected into the genome. The SPTN-97 gene is an " + - "artificially-synthesized gene that was developed by DARPA to create " + - "super-soldiers through genetic modification. The gene was outlawed in " + - "2056.", - strength_mult: 1.75, - defense_mult: 1.75, - dexterity_mult: 1.75, - agility_mult: 1.75, - hacking_mult: 1.15, - }); - SPTN97.addToFactions(["The Covenant"]); - if (augmentationExists(AugmentationNames.SPTN97)) { - delete Augmentations[AugmentationNames.SPTN97]; - } - AddToAugmentations(SPTN97); - - // ECorp - const HiveMind = new Augmentation({ - name:AugmentationNames.HiveMind, repCost:1.5e6, moneyCost:5.5e9, - info:"A brain implant developed by ECorp. They do not reveal what " + - "exactly the implant does, but they promise that it will greatly " + - "enhance your abilities.", - hacking_grow_mult: 3, - stats: <>, - }); - HiveMind.addToFactions(["ECorp"]); - if (augmentationExists(AugmentationNames.HiveMind)) { - delete Augmentations[AugmentationNames.HiveMind]; - } - AddToAugmentations(HiveMind); - - // MegaCorp - const CordiARCReactor = new Augmentation({ - name:AugmentationNames.CordiARCReactor, repCost:1.125e6, moneyCost:5e9, - info:"The thoracic cavity is equipped with a small chamber designed " + - "to hold and sustain hydrogen plasma. The plasma is used to generate " + - "fusion power through nuclear fusion, providing limitless amounts of clean " + - "energy for the body.", - strength_mult: 1.35, - defense_mult: 1.35, - dexterity_mult: 1.35, - agility_mult: 1.35, - strength_exp_mult: 1.35, - defense_exp_mult: 1.35, - dexterity_exp_mult: 1.35, - agility_exp_mult: 1.35, - }); - CordiARCReactor.addToFactions(["MegaCorp"]); - if (augmentationExists(AugmentationNames.CordiARCReactor)) { - delete Augmentations[AugmentationNames.CordiARCReactor]; - } - AddToAugmentations(CordiARCReactor); - - // BachmanAndAssociates - const SmartJaw = new Augmentation({ - name:AugmentationNames.SmartJaw, repCost:3.75e5, moneyCost:2.75e9, - info:"A bionic jaw that contains advanced hardware and software " + - "capable of psychoanalyzing and profiling the personality of " + - "others using optical imaging software.", - charisma_mult: 1.5, - charisma_exp_mult: 1.5, - company_rep_mult: 1.25, - faction_rep_mult: 1.25, - }); - SmartJaw.addToFactions(["Bachman & Associates"]); - if (augmentationExists(AugmentationNames.SmartJaw)) { - delete Augmentations[AugmentationNames.SmartJaw]; - } - AddToAugmentations(SmartJaw); - - // BladeIndustries - const Neotra = new Augmentation({ - name:AugmentationNames.Neotra, repCost:5.625e5, moneyCost:2.875e9, - info:"A highly-advanced techno-organic drug that is injected into the skeletal " + - "and integumentary system. The drug permanently modifies the DNA of the " + - "body's skin and bone cells, granting them the ability to repair " + - "and restructure themselves.", - strength_mult: 1.55, - defense_mult: 1.55, - }); - Neotra.addToFactions(["Blade Industries"]); - if (augmentationExists(AugmentationNames.Neotra)) { - delete Augmentations[AugmentationNames.Neotra]; - } - AddToAugmentations(Neotra); - - // NWO - const Xanipher = new Augmentation({ - name:AugmentationNames.Xanipher, repCost:8.75e5, moneyCost:4.25e9, - info:"A concoction of advanced nanobots that is orally ingested into the " + - "body. These nanobots induce physiological changes and significantly " + - "improve the body's functioning in all aspects.", - hacking_mult: 1.2, - strength_mult: 1.2, - defense_mult: 1.2, - dexterity_mult: 1.2, - agility_mult: 1.2, - charisma_mult: 1.2, - hacking_exp_mult: 1.15, - strength_exp_mult: 1.15, - defense_exp_mult: 1.15, - dexterity_exp_mult: 1.15, - agility_exp_mult: 1.15, - charisma_exp_mult: 1.15, - }); - Xanipher.addToFactions(["NWO"]); - if (augmentationExists(AugmentationNames.Xanipher)) { - delete Augmentations[AugmentationNames.Xanipher]; - } - AddToAugmentations(Xanipher); - - const HydroflameLeftArm = new Augmentation({ - name:AugmentationNames.HydroflameLeftArm, repCost:1.25e6, moneyCost:2.5e12, - info:"The left arm of a legendary BitRunner who ascended beyond this world. " + - "It projects a light blue energy shield that protects the exposed inner parts. " + - "Even though it contains no weapons, the advanced tungsten titanium " + - "alloy increases the users strength to unbelievable levels. The augmentation " + - "gets more powerful over time for seemingly no reason.", - strength_mult: 2.70, - }); - HydroflameLeftArm.addToFactions(["NWO"]); - if (augmentationExists(AugmentationNames.HydroflameLeftArm)) { - delete Augmentations[AugmentationNames.HydroflameLeftArm]; - } - AddToAugmentations(HydroflameLeftArm); - - - - // ClarkeIncorporated - const nextSENS = new Augmentation({ - name:AugmentationNames.nextSENS, repCost:4.375e5, moneyCost:1.925e9, - info:"The body is genetically re-engineered to maintain a state " + - "of negligible senescence, preventing the body from " + - "deteriorating with age.", - hacking_mult: 1.2, - strength_mult: 1.2, - defense_mult: 1.2, - dexterity_mult: 1.2, - agility_mult: 1.2, - charisma_mult: 1.2, - }); - nextSENS.addToFactions(["Clarke Incorporated"]); - if (augmentationExists(AugmentationNames.nextSENS)) { - delete Augmentations[AugmentationNames.nextSENS]; - } - AddToAugmentations(nextSENS); - - // OmniTekIncorporated - const OmniTekInfoLoad = new Augmentation({ - name:AugmentationNames.OmniTekInfoLoad, repCost:6.25e5, moneyCost:2.875e9, - info:"OmniTek's data and information repository is uploaded " + - "into your brain, enhancing your programming and " + - "hacking abilities.", - hacking_mult: 1.2, - hacking_exp_mult: 1.25, - }); - OmniTekInfoLoad.addToFactions(["OmniTek Incorporated"]); - if (augmentationExists(AugmentationNames.OmniTekInfoLoad)) { - delete Augmentations[AugmentationNames.OmniTekInfoLoad]; - } - AddToAugmentations(OmniTekInfoLoad); - - // FourSigma - // TODO Later when Intelligence is added in . Some aug that greatly increases int - - // KuaiGongInternational - const PhotosyntheticCells = new Augmentation({ - name:AugmentationNames.PhotosyntheticCells, repCost:5.625e5, moneyCost:2.75e9, - info:"Chloroplasts are added to epidermal stem cells and are applied " + - "to the body using a skin graft. The result is photosynthetic " + - "skin cells, allowing users to generate their own energy " + - "and nutrition using solar power.", - strength_mult: 1.4, - defense_mult: 1.4, - agility_mult: 1.4, - }); - PhotosyntheticCells.addToFactions(["KuaiGong International"]); - if (augmentationExists(AugmentationNames.PhotosyntheticCells)) { - delete Augmentations[AugmentationNames.PhotosyntheticCells]; - } - AddToAugmentations(PhotosyntheticCells); - - // BitRunners - const Neurolink = new Augmentation({ - name:AugmentationNames.Neurolink, repCost:8.75e5, moneyCost:4.375e9, - info:"A brain implant that provides a high-bandwidth, direct neural link between your " + - "mind and the BitRunners' data servers, which reportedly contain " + - "the largest database of hacking tools and information in the world.", - hacking_mult: 1.15, - hacking_exp_mult: 1.2, - hacking_chance_mult: 1.1, - hacking_speed_mult: 1.05, - programs: [Programs.FTPCrackProgram.name, Programs.RelaySMTPProgram.name], - }); - Neurolink.addToFactions(["BitRunners"]); - if (augmentationExists(AugmentationNames.Neurolink)) { - delete Augmentations[AugmentationNames.Neurolink]; - } - AddToAugmentations(Neurolink); - - // BlackHand - const TheBlackHand = new Augmentation({ - name:AugmentationNames.TheBlackHand, repCost:1e5, moneyCost:5.5e8, - info:"A highly advanced bionic hand. This prosthetic not only " + - "enhances strength and dexterity but it is also embedded " + - "with hardware and firmware that lets the user connect to, access, and hack " + - "devices and machines by just touching them.", - strength_mult: 1.15, - dexterity_mult: 1.15, - hacking_mult: 1.1, - hacking_speed_mult: 1.02, - hacking_money_mult: 1.1, - }); - TheBlackHand.addToFactions(["The Black Hand"]); - if (augmentationExists(AugmentationNames.TheBlackHand)) { - delete Augmentations[AugmentationNames.TheBlackHand]; - } - AddToAugmentations(TheBlackHand); - - // NiteSec - const CRTX42AA = new Augmentation({ - name:AugmentationNames.CRTX42AA, repCost:4.5e4, moneyCost:2.25e8, - info:"The CRTX42-AA gene is injected into the genome. " + - "The CRTX42-AA is an artificially-synthesized gene that targets the visual and prefrontal " + - "cortex and improves cognitive abilities.", - hacking_mult: 1.08, - hacking_exp_mult: 1.15, - }); - CRTX42AA.addToFactions(["NiteSec"]); - if (augmentationExists(AugmentationNames.CRTX42AA)) { - delete Augmentations[AugmentationNames.CRTX42AA]; - } - AddToAugmentations(CRTX42AA); - - // Chongqing - const Neuregen = new Augmentation({ - name:AugmentationNames.Neuregen, repCost:3.75e4, moneyCost:3.75e8, - info:"A drug that genetically modifies the neurons in the brain " + - "resulting in neurons never die, continuously " + - "regenerate, and strengthen themselves.", - hacking_exp_mult: 1.4, - }); - Neuregen.addToFactions(["Chongqing"]); - if (augmentationExists(AugmentationNames.Neuregen)) { - delete Augmentations[AugmentationNames.Neuregen]; - } - AddToAugmentations(Neuregen); - - // Sector12 - const CashRoot = new Augmentation({ - name:AugmentationNames.CashRoot, repCost:1.25e4, moneyCost:1.25e8, - info:<>A collection of digital assets saved on a small chip. The chip is implanted - into your wrist. A small jack in the chip allows you to connect it to a computer - and upload the assets., - startingMoney: 1e6, - programs: [Programs.BruteSSHProgram.name], - }); - CashRoot.addToFactions(["Sector-12"]); - if (augmentationExists(AugmentationNames.CashRoot)) { - delete Augmentations[AugmentationNames.CashRoot]; - } - AddToAugmentations(CashRoot); - - // NewTokyo - const NutriGen = new Augmentation({ - name:AugmentationNames.NutriGen, repCost:6.25e3, moneyCost:2.5e6, - info:"A thermo-powered artificial nutrition generator. Endogenously " + - "synthesizes glucose, amino acids, and vitamins and redistributes them " + - "across the body. The device is powered by the body's naturally wasted " + - "energy in the form of heat.", - strength_exp_mult: 1.2, - defense_exp_mult: 1.2, - dexterity_exp_mult: 1.2, - agility_exp_mult: 1.2, - }); - NutriGen.addToFactions(["New Tokyo"]); - if (augmentationExists(AugmentationNames.NutriGen)) { - delete Augmentations[AugmentationNames.NutriGen]; - } - AddToAugmentations(NutriGen); - - // Aevum - const PCMatrix = new Augmentation({ - name:AugmentationNames.PCMatrix, repCost:100e3, moneyCost:2e9, - info:"A 'Probability Computation Matrix' is installed in the frontal cortex. This implant " + - "uses advanced mathematical algorithims to rapidly identify and compute statistical " + - "outcomes of nearly every situation.", - charisma_mult: 1.0777, - charisma_exp_mult: 1.0777, - work_money_mult: 1.777, - faction_rep_mult: 1.0777, - company_rep_mult: 1.0777, - crime_success_mult: 1.0777, - crime_money_mult: 1.0777, - programs: [Programs.DeepscanV1.name, Programs.AutoLink.name], - }); - PCMatrix.addToFactions(["Aevum"]); - if (augmentationExists(AugmentationNames.PCMatrix)) { - delete Augmentations[AugmentationNames.PCMatrix]; - } - AddToAugmentations(PCMatrix); - - // Ishima - const INFRARet = new Augmentation({ - name:AugmentationNames.INFRARet, repCost:7.5e3, moneyCost:3e7, - info:"A tiny chip that sits behind the retinae. This implant lets the" + - "user visually detect infrared radiation.", - crime_success_mult: 1.25, - crime_money_mult: 1.1, - dexterity_mult: 1.1, - }); - INFRARet.addToFactions(["Ishima"]); - if (augmentationExists(AugmentationNames.INFRARet)) { - delete Augmentations[AugmentationNames.INFRARet]; - } - AddToAugmentations(INFRARet); - - // Volhaven - const DermaForce = new Augmentation({ - name:AugmentationNames.DermaForce, repCost:1.5e4, moneyCost:5e7, - info:"Synthetic skin that is grafted onto the body. This skin consists of " + - "millions of nanobots capable of projecting high-density muon beams, " + - "creating an energy barrier around the user.", - defense_mult: 1.4, - }); - DermaForce.addToFactions(["Volhaven"]); - if (augmentationExists(AugmentationNames.DermaForce)) { - delete Augmentations[AugmentationNames.DermaForce]; - } - AddToAugmentations(DermaForce); - - // SpeakersForTheDead - const GrapheneBrachiBlades = new Augmentation({ - name:AugmentationNames.GrapheneBrachiBlades, repCost:2.25e5, moneyCost:2.5e9, - info:"An upgrade to the BrachiBlades augmentation. It infuses " + - "the retractable blades with an advanced graphene material " + - "making them stronger and lighter.", - prereqs:[AugmentationNames.BrachiBlades], - strength_mult: 1.4, - defense_mult: 1.4, - crime_success_mult: 1.1, - crime_money_mult: 1.3, - }); - GrapheneBrachiBlades.addToFactions(["Speakers for the Dead"]); - if (augmentationExists(AugmentationNames.GrapheneBrachiBlades)) { - delete Augmentations[AugmentationNames.GrapheneBrachiBlades]; - } - AddToAugmentations(GrapheneBrachiBlades); - - // DarkArmy - const GrapheneBionicArms = new Augmentation({ - name:AugmentationNames.GrapheneBionicArms, repCost:5e5, moneyCost:3.75e9, - info:"An upgrade to the Bionic Arms augmentation. It infuses the " + - "prosthetic arms with an advanced graphene material " + - "to make them stronger and lighter.", - prereqs:[AugmentationNames.BionicArms], - strength_mult: 1.85, - dexterity_mult: 1.85, - }); - GrapheneBionicArms.addToFactions(["The Dark Army"]); - if (augmentationExists(AugmentationNames.GrapheneBionicArms)) { - delete Augmentations[AugmentationNames.GrapheneBionicArms]; - } - AddToAugmentations(GrapheneBionicArms); - - // TheSyndicate - const BrachiBlades = new Augmentation({ - name:AugmentationNames.BrachiBlades, repCost:1.25e4, moneyCost:9e7, - info:"A set of retractable plasteel blades that are implanted in the arm, underneath the skin.", - strength_mult: 1.15, - defense_mult: 1.15, - crime_success_mult: 1.1, - crime_money_mult: 1.15, - }); - BrachiBlades.addToFactions(["The Syndicate"]); - if (augmentationExists(AugmentationNames.BrachiBlades)) { - delete Augmentations[AugmentationNames.BrachiBlades]; - } - AddToAugmentations(BrachiBlades); - - // Tetrads - const BionicArms = new Augmentation({ - name:AugmentationNames.BionicArms, repCost:6.25e4, moneyCost:2.75e8, - info:"Cybernetic arms created from plasteel and carbon fibers that completely replace " + - "the user's organic arms.", - strength_mult: 1.3, - dexterity_mult: 1.3, - }); - BionicArms.addToFactions(["Tetrads"]); - if (augmentationExists(AugmentationNames.BionicArms)) { - delete Augmentations[AugmentationNames.BionicArms]; - } - AddToAugmentations(BionicArms); - - // TianDiHui - const SNA = new Augmentation({ - name:AugmentationNames.SNA, repCost:6.25e3, moneyCost:3e7, - info:"A cranial implant that affects the user's personality, making them better " + - "at negotiation in social situations.", - work_money_mult: 1.1, - company_rep_mult: 1.15, - faction_rep_mult: 1.15, - }); - SNA.addToFactions(["Tian Di Hui"]); - if (augmentationExists(AugmentationNames.SNA)) { - delete Augmentations[AugmentationNames.SNA]; - } - AddToAugmentations(SNA); - - // Special Bladeburner Augmentations - const BladeburnersFactionName = "Bladeburners"; - if (factionExists(BladeburnersFactionName)) { - const EsperEyewear = new Augmentation({ - name:AugmentationNames.EsperEyewear, repCost:1.25e3, moneyCost:1.65e8, - info:"Ballistic-grade protective and retractable eyewear that was designed specifically " + - "for Bladeburner units. This " + - "is implanted by installing a mechanical frame in the skull's orbit. " + - "This frame interfaces with the brain and allows the user to " + - "automatically extrude and extract the eyewear. The eyewear protects " + - "against debris, shrapnel, lasers, blinding flashes, and gas. It is also " + - "embedded with a data processing chip that can be programmed to display an " + - "AR HUD to assist the user in field missions.", - bladeburner_success_chance_mult: 1.03, - dexterity_mult: 1.05, - isSpecial: true, - }); - EsperEyewear.addToFactions([BladeburnersFactionName]); - resetAugmentation(EsperEyewear); - - const EMS4Recombination = new Augmentation({ - name:AugmentationNames.EMS4Recombination, repCost:2.5e3, moneyCost:2.75e8, - info:"A DNA recombination of the EMS-4 Gene. This genetic engineering " + - "technique was originally used on Bladeburners during the Synthoid uprising " + - "to induce wakefulness and concentration, suppress fear, reduce empathy, " + - "improve reflexes, and improve memory among other things.", - bladeburner_success_chance_mult: 1.03, - bladeburner_analysis_mult: 1.05, - bladeburner_stamina_gain_mult: 1.02, - isSpecial: true, - }); - EMS4Recombination.addToFactions([BladeburnersFactionName]); - resetAugmentation(EMS4Recombination); - - const OrionShoulder = new Augmentation({ - name:AugmentationNames.OrionShoulder, repCost:6.25e3, moneyCost:5.5e8, - info:"A bionic shoulder augmentation for the right shoulder. Using cybernetics, " + - "the ORION-MKIV shoulder enhances the strength and dexterity " + - "of the user's right arm. It also provides protection due to its " + - "crystallized graphene plating.", - defense_mult: 1.05, - strength_mult: 1.05, - dexterity_mult: 1.05, - bladeburner_success_chance_mult: 1.04, - isSpecial: true, - }); - OrionShoulder.addToFactions([BladeburnersFactionName]); - resetAugmentation(OrionShoulder); - - const HyperionV1 = new Augmentation({ - name:AugmentationNames.HyperionV1, repCost:1.25e4, moneyCost:2.75e9, - info:"A pair of mini plasma cannons embedded into the hands. The Hyperion is capable " + - "of rapidly firing bolts of high-density plasma. The weapon is meant to " + - "be used against augmented enemies as the ionized " + - "nature of the plasma disrupts the electrical systems of Augmentations. However, " + - "it can also be effective against non-augmented enemies due to its high temperature " + - "and concussive force.", - bladeburner_success_chance_mult: 1.06, - isSpecial: true, - }); - HyperionV1.addToFactions([BladeburnersFactionName]); - resetAugmentation(HyperionV1); - - const HyperionV2 = new Augmentation({ - name:AugmentationNames.HyperionV2, repCost:2.5e4, moneyCost:5.5e9, - info:"A pair of mini plasma cannons embedded into the hands. This augmentation " + - "is more advanced and powerful than the original V1 model. This V2 model is " + - "more power-efficient, more accurate, and can fire plasma bolts at a much " + - "higher velocity than the V1 model.", - prereqs:[AugmentationNames.HyperionV1], - bladeburner_success_chance_mult: 1.08, - isSpecial: true, - }); - HyperionV2.addToFactions([BladeburnersFactionName]); - resetAugmentation(HyperionV2); - - const GolemSerum = new Augmentation({ - name:AugmentationNames.GolemSerum, repCost:3.125e4, moneyCost:1.1e10, - info:"A serum that permanently enhances many aspects of human capabilities, " + - "including strength, speed, immune system enhancements, and mitochondrial efficiency. The " + - "serum was originally developed by the Chinese military in an attempt to " + - "create super soldiers.", - strength_mult: 1.07, - defense_mult: 1.07, - dexterity_mult: 1.07, - agility_mult: 1.07, - bladeburner_stamina_gain_mult: 1.05, - isSpecial: true, - }); - GolemSerum.addToFactions([BladeburnersFactionName]); - resetAugmentation(GolemSerum); - - const VangelisVirus = new Augmentation({ - name:AugmentationNames.VangelisVirus, repCost:1.875e4, moneyCost:2.75e9, - info:"A synthetic symbiotic virus that is injected into human brain tissue. The Vangelis virus " + - "heightens the senses and focus of its host, and also enhances its intuition.", - dexterity_exp_mult: 1.1, - bladeburner_analysis_mult: 1.1, - bladeburner_success_chance_mult: 1.04, - isSpecial: true, - }); - VangelisVirus.addToFactions([BladeburnersFactionName]); - resetAugmentation(VangelisVirus); - - const VangelisVirus3 = new Augmentation({ - name:AugmentationNames.VangelisVirus3, repCost:3.75e4, moneyCost:1.1e10, - info:"An improved version of Vangelis, a synthetic symbiotic virus that is " + - "injected into human brain tissue. On top of the benefits of the original " + - "virus, this also grants an accelerated healing factor and enhanced " + - "reflexes.", - prereqs:[AugmentationNames.VangelisVirus], - defense_exp_mult: 1.1, - dexterity_exp_mult: 1.1, - bladeburner_analysis_mult: 1.15, - bladeburner_success_chance_mult: 1.05, - isSpecial: true, - }); - VangelisVirus3.addToFactions([BladeburnersFactionName]); - resetAugmentation(VangelisVirus3); - - const INTERLINKED = new Augmentation({ - name:AugmentationNames.INTERLINKED, repCost:2.5e4, moneyCost:5.5e9, - info:"The DNA is genetically modified to enhance the human's body " + - "extracellular matrix (ECM). This improves the ECM's ability to " + - "structurally support the body and grants heightened strength and " + - "durability.", - strength_exp_mult: 1.05, - defense_exp_mult: 1.05, - dexterity_exp_mult: 1.05, - agility_exp_mult: 1.05, - bladeburner_max_stamina_mult: 1.1, - isSpecial: true, - }); - INTERLINKED.addToFactions([BladeburnersFactionName]); - resetAugmentation(INTERLINKED); - - const BladeRunner = new Augmentation({ - name:AugmentationNames.BladeRunner, repCost:2e4, moneyCost:8.25e9, - info:"A cybernetic foot augmentation that was specifically created for Bladeburners " + - "during the Synthoid Uprising. The organic musculature of the human foot " + - "is enhanced with flexible carbon nanotube matrices that are controlled by " + - "intelligent servo-motors.", - agility_mult: 1.05, - bladeburner_max_stamina_mult: 1.05, - bladeburner_stamina_gain_mult: 1.05, - isSpecial: true, - }); - BladeRunner.addToFactions([BladeburnersFactionName]); - resetAugmentation(BladeRunner); - - const BladeArmor = new Augmentation({ - name:AugmentationNames.BladeArmor, repCost:1.25e4, moneyCost:1.375e9, - info:"A powered exoskeleton suit designed as armor for Bladeburner units. This " + - "exoskeleton is incredibly adaptable and can protect the wearer from blunt, piercing, " + - "concussive, thermal, chemical, and electric trauma. It also enhances the user's " + - "physical abilities.", - strength_mult: 1.04, - defense_mult: 1.04, - dexterity_mult: 1.04, - agility_mult: 1.04, - bladeburner_stamina_gain_mult: 1.02, - bladeburner_success_chance_mult: 1.03, - isSpecial: true, - }); - BladeArmor.addToFactions([BladeburnersFactionName]); - resetAugmentation(BladeArmor); - - const BladeArmorPowerCells = new Augmentation({ - name:AugmentationNames.BladeArmorPowerCells, repCost:1.875e4, moneyCost:2.75e9, - info:"Upgrades the BLADE-51b Tesla Armor with Ion Power Cells, which are capable of " + - "more efficiently storing and using power.", - prereqs:[AugmentationNames.BladeArmor], - bladeburner_success_chance_mult: 1.05, - bladeburner_stamina_gain_mult: 1.02, - bladeburner_max_stamina_mult: 1.05, - isSpecial: true, - }); - BladeArmorPowerCells.addToFactions([BladeburnersFactionName]); - resetAugmentation(BladeArmorPowerCells); - - const BladeArmorEnergyShielding = new Augmentation({ - name:AugmentationNames.BladeArmorEnergyShielding, repCost:2.125e4, moneyCost:5.5e9, - info:"Upgrades the BLADE-51b Tesla Armor with a plasma energy propulsion system " + - "that is capable of projecting an energy shielding force field.", - prereqs:[AugmentationNames.BladeArmor], - defense_mult: 1.05, - bladeburner_success_chance_mult: 1.06, - isSpecial: true, - }); - BladeArmorEnergyShielding.addToFactions([BladeburnersFactionName]); - resetAugmentation(BladeArmorEnergyShielding); - - const BladeArmorUnibeam = new Augmentation({ - name:AugmentationNames.BladeArmorUnibeam, repCost:3.125e4, moneyCost:1.65e10, - info:"Upgrades the BLADE-51b Tesla Armor with a concentrated deuterium-fluoride laser " + - "weapon. It's precision and accuracy makes it useful for quickly neutralizing " + - "threats while keeping casualties to a minimum.", - prereqs:[AugmentationNames.BladeArmor], - bladeburner_success_chance_mult: 1.08, - isSpecial: true, - }); - BladeArmorUnibeam.addToFactions([BladeburnersFactionName]); - resetAugmentation(BladeArmorUnibeam); - - const BladeArmorOmnibeam = new Augmentation({ - name:AugmentationNames.BladeArmorOmnibeam, repCost:6.25e4, moneyCost:2.75e10, - info:"Upgrades the BLADE-51b Tesla Armor Unibeam augmentation to use a " + - "multiple-fiber system. This upgraded weapon uses multiple fiber laser " + - "modules that combine together to form a single, more powerful beam of up to " + - "2000MW.", - prereqs:[AugmentationNames.BladeArmorUnibeam], - bladeburner_success_chance_mult: 1.1, - isSpecial: true, - }); - BladeArmorOmnibeam.addToFactions([BladeburnersFactionName]); - resetAugmentation(BladeArmorOmnibeam); - - const BladeArmorIPU = new Augmentation({ - name:AugmentationNames.BladeArmorIPU, repCost:1.5e4, moneyCost:1.1e9, - info:"Upgrades the BLADE-51b Tesla Armor with an AI Information Processing " + - "Unit that was specially designed to analyze Synthoid related data and " + - "information.", - prereqs:[AugmentationNames.BladeArmor], - bladeburner_analysis_mult: 1.15, - bladeburner_success_chance_mult: 1.02, - isSpecial: true, - }); - BladeArmorIPU.addToFactions([BladeburnersFactionName]); - resetAugmentation(BladeArmorIPU); - - const BladesSimulacrum = new Augmentation({ - name:AugmentationNames.BladesSimulacrum, repCost:1.25e3, moneyCost:1.5e11, - info:"A highly-advanced matter phase-shifter module that is embedded " + - "in the brainstem and cerebellum. This augmentation allows " + - "the user to project and control a holographic simulacrum within an " + - "extremely large radius. These specially-modified holograms were specifically " + - "weaponized by Bladeburner units to be used against Synthoids.", - stats: <>This augmentation allows you to perform Bladeburner actions - and other actions (such as working, commiting crimes, etc.) at - the same time., - isSpecial: true, - }); - BladesSimulacrum.addToFactions([BladeburnersFactionName]); - resetAugmentation(BladesSimulacrum); - } - - // Update costs based on how many have been purchased - mult = Math.pow(CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]], Player.queuedAugmentations.length); - for (var name in Augmentations) { - if (Augmentations.hasOwnProperty(name)) { - Augmentations[name].baseCost *= mult; - } - } - - Player.reapplyAllAugmentations(); + for (var name in Factions) { + if (Factions.hasOwnProperty(name)) { + Factions[name].augmentations = []; + } + } + + //Reset Augmentations + clearObject(Augmentations); + + //Time-Based Augment Test + const randomBonuses = getRandomBonus(); + + const UnstableCircadianModulatorParams = { + name: AugmentationNames.UnstableCircadianModulator, + moneyCost: 5e9, + repCost: 3.625e5, + info: + "An experimental nanobot injection. Its unstable nature leads to " + + "unpredictable results based on your circadian rhythm.", + }; + Object.keys(randomBonuses.bonuses).forEach( + (key) => + (UnstableCircadianModulatorParams[key] = randomBonuses.bonuses[key]), + ); + const UnstableCircadianModulator = new Augmentation( + UnstableCircadianModulatorParams, + ); + + UnstableCircadianModulator.addToFactions(["Speakers for the Dead"]); + if (augmentationExists(AugmentationNames.UnstableCircadianModulator)) { + delete Augmentations[AugmentationNames.UnstableCircadianModulator]; + } + AddToAugmentations(UnstableCircadianModulator); + + //Combat stat augmentations + const HemoRecirculator = new Augmentation({ + name: AugmentationNames.HemoRecirculator, + moneyCost: 4.5e7, + repCost: 1e4, + info: + "A heart implant that greatly increases the body's ability to effectively use and pump " + + "blood.", + strength_mult: 1.08, + defense_mult: 1.08, + agility_mult: 1.08, + dexterity_mult: 1.08, + }); + HemoRecirculator.addToFactions(["Tetrads", "The Dark Army", "The Syndicate"]); + if (augmentationExists(AugmentationNames.HemoRecirculator)) { + delete Augmentations[AugmentationNames.HemoRecirculator]; + } + AddToAugmentations(HemoRecirculator); + + const Targeting1 = new Augmentation({ + name: AugmentationNames.Targeting1, + moneyCost: 1.5e7, + repCost: 5e3, + info: + "A cranial implant that is embedded within the inner ear structures and optic nerves. It regulates " + + "and enhances balance and hand-eye coordination.", + dexterity_mult: 1.1, + }); + Targeting1.addToFactions([ + "Slum Snakes", + "The Dark Army", + "The Syndicate", + "Sector-12", + "Ishima", + "OmniTek Incorporated", + "KuaiGong International", + "Blade Industries", + ]); + if (augmentationExists(AugmentationNames.Targeting1)) { + delete Augmentations[AugmentationNames.Targeting1]; + } + AddToAugmentations(Targeting1); + + const Targeting2 = new Augmentation({ + name: AugmentationNames.Targeting2, + moneyCost: 4.25e7, + repCost: 8.75e3, + info: + "This upgraded version of the 'Augmented Targeting' implant is capable of augmenting " + + "reality by digitally displaying weaknesses and vital signs of threats.", + prereqs: [AugmentationNames.Targeting1], + dexterity_mult: 1.2, + }); + Targeting2.addToFactions([ + "The Dark Army", + "The Syndicate", + "Sector-12", + "OmniTek Incorporated", + "KuaiGong International", + "Blade Industries", + ]); + if (augmentationExists(AugmentationNames.Targeting2)) { + delete Augmentations[AugmentationNames.Targeting2]; + } + AddToAugmentations(Targeting2); + + const Targeting3 = new Augmentation({ + name: AugmentationNames.Targeting3, + moneyCost: 1.15e8, + repCost: 2.75e4, + info: + "The latest version of the 'Augmented Targeting' implant adds the ability to " + + "lock-on and track threats.", + prereqs: [AugmentationNames.Targeting2], + dexterity_mult: 1.3, + }); + Targeting3.addToFactions([ + "The Dark Army", + "The Syndicate", + "OmniTek Incorporated", + "KuaiGong International", + "Blade Industries", + "The Covenant", + ]); + if (augmentationExists(AugmentationNames.Targeting3)) { + delete Augmentations[AugmentationNames.Targeting3]; + } + AddToAugmentations(Targeting3); + + const SyntheticHeart = new Augmentation({ + name: AugmentationNames.SyntheticHeart, + moneyCost: 2.875e9, + repCost: 7.5e5, + info: + "This advanced artificial heart, created from plasteel and graphene, is capable of pumping blood " + + "more efficiently than an organic heart.", + agility_mult: 1.5, + strength_mult: 1.5, + }); + SyntheticHeart.addToFactions([ + "KuaiGong International", + "Fulcrum Secret Technologies", + "Speakers for the Dead", + "NWO", + "The Covenant", + "Daedalus", + "Illuminati", + ]); + if (augmentationExists(AugmentationNames.SyntheticHeart)) { + delete Augmentations[AugmentationNames.SyntheticHeart]; + } + AddToAugmentations(SyntheticHeart); + + const SynfibrilMuscle = new Augmentation({ + name: AugmentationNames.SynfibrilMuscle, + repCost: 4.375e5, + moneyCost: 1.125e9, + info: + "The myofibrils in human muscles are injected with special chemicals that react with the proteins inside " + + "the myofibrils, altering their underlying structure. The end result is muscles that are stronger and more elastic. " + + "Scientists have named these artificially enhanced units 'synfibrils'.", + strength_mult: 1.3, + defense_mult: 1.3, + }); + SynfibrilMuscle.addToFactions([ + "KuaiGong International", + "Fulcrum Secret Technologies", + "Speakers for the Dead", + "NWO", + "The Covenant", + "Daedalus", + "Illuminati", + "Blade Industries", + ]); + if (augmentationExists(AugmentationNames.SynfibrilMuscle)) { + delete Augmentations[AugmentationNames.SynfibrilMuscle]; + } + AddToAugmentations(SynfibrilMuscle); + + const CombatRib1 = new Augmentation({ + name: AugmentationNames.CombatRib1, + repCost: 7.5e3, + moneyCost: 2.375e7, + info: + "The rib cage is augmented to continuously release boosters into the bloodstream " + + "which increase the oxygen-carrying capacity of blood.", + strength_mult: 1.1, + defense_mult: 1.1, + }); + CombatRib1.addToFactions([ + "Slum Snakes", + "The Dark Army", + "The Syndicate", + "Volhaven", + "Ishima", + "OmniTek Incorporated", + "KuaiGong International", + "Blade Industries", + ]); + if (augmentationExists(AugmentationNames.CombatRib1)) { + delete Augmentations[AugmentationNames.CombatRib1]; + } + AddToAugmentations(CombatRib1); + + const CombatRib2 = new Augmentation({ + name: AugmentationNames.CombatRib2, + repCost: 1.875e4, + moneyCost: 6.5e7, + info: + "An upgraded version of the 'Combat Rib' augmentation that adds potent stimulants which " + + "improve focus and endurance while decreasing reaction time and fatigue.", + prereqs: [AugmentationNames.CombatRib1], + strength_mult: 1.14, + defense_mult: 1.14, + }); + CombatRib2.addToFactions([ + "The Dark Army", + "The Syndicate", + "Volhaven", + "OmniTek Incorporated", + "KuaiGong International", + "Blade Industries", + ]); + if (augmentationExists(AugmentationNames.CombatRib2)) { + delete Augmentations[AugmentationNames.CombatRib2]; + } + AddToAugmentations(CombatRib2); + + const CombatRib3 = new Augmentation({ + name: AugmentationNames.CombatRib3, + repCost: 3.5e4, + moneyCost: 1.2e8, + info: + "The latest version of the 'Combat Rib' augmentation releases advanced anabolic steroids that " + + "improve muscle mass and physical performance while being safe and free of side effects.", + prereqs: [AugmentationNames.CombatRib2], + strength_mult: 1.18, + defense_mult: 1.18, + }); + CombatRib3.addToFactions([ + "The Dark Army", + "The Syndicate", + "OmniTek Incorporated", + "KuaiGong International", + "Blade Industries", + "The Covenant", + ]); + if (augmentationExists(AugmentationNames.CombatRib3)) { + delete Augmentations[AugmentationNames.CombatRib3]; + } + AddToAugmentations(CombatRib3); + + const NanofiberWeave = new Augmentation({ + name: AugmentationNames.NanofiberWeave, + repCost: 3.75e4, + moneyCost: 1.25e8, + info: + "Synthetic nanofibers are woven into the skin's extracellular matrix using electrospinning, " + + "which improves its regenerative and extracellular homeostasis abilities.", + strength_mult: 1.2, + defense_mult: 1.2, + }); + NanofiberWeave.addToFactions([ + "Tian Di Hui", + "The Syndicate", + "The Dark Army", + "Speakers for the Dead", + "Blade Industries", + "Fulcrum Secret Technologies", + "OmniTek Incorporated", + ]); + if (augmentationExists(AugmentationNames.NanofiberWeave)) { + delete Augmentations[AugmentationNames.NanofiberWeave]; + } + AddToAugmentations(NanofiberWeave); + + const SubdermalArmor = new Augmentation({ + name: AugmentationNames.SubdermalArmor, + repCost: 8.75e5, + moneyCost: 3.25e9, + info: + "The NEMEAN Subdermal Weave is a thin, light-weight, graphene plating that houses a dilatant fluid. " + + "The material is implanted underneath the skin, and is the most advanced form of defensive enhancement " + + "that has ever been created. The dilatant fluid, despite being thin and light, is extremely effective " + + "at stopping piercing blows and reducing blunt trauma. The properties of graphene allow the plating to " + + "mitigate damage from any fire or electrical traumas.", + defense_mult: 2.2, + }); + SubdermalArmor.addToFactions([ + "The Syndicate", + "Fulcrum Secret Technologies", + "Illuminati", + "Daedalus", + "The Covenant", + ]); + if (augmentationExists(AugmentationNames.SubdermalArmor)) { + delete Augmentations[AugmentationNames.SubdermalArmor]; + } + AddToAugmentations(SubdermalArmor); + + const WiredReflexes = new Augmentation({ + name: AugmentationNames.WiredReflexes, + repCost: 1.25e3, + moneyCost: 2.5e6, + info: + "Synthetic nerve-enhancements are injected into all major parts of the somatic nervous system, " + + "supercharging the spread of neural signals and increasing reflex speed.", + agility_mult: 1.05, + dexterity_mult: 1.05, + }); + WiredReflexes.addToFactions([ + "Tian Di Hui", + "Slum Snakes", + "Sector-12", + "Volhaven", + "Aevum", + "Ishima", + "The Syndicate", + "The Dark Army", + "Speakers for the Dead", + ]); + if (augmentationExists(AugmentationNames.WiredReflexes)) { + delete Augmentations[AugmentationNames.WiredReflexes]; + } + AddToAugmentations(WiredReflexes); + + const GrapheneBoneLacings = new Augmentation({ + name: AugmentationNames.GrapheneBoneLacings, + repCost: 1.125e6, + moneyCost: 4.25e9, + info: + "Graphene is grafted and fused into the skeletal structure, " + + "enhancing bone density and tensile strength.", + strength_mult: 1.7, + defense_mult: 1.7, + }); + GrapheneBoneLacings.addToFactions([ + "Fulcrum Secret Technologies", + "The Covenant", + ]); + if (augmentationExists(AugmentationNames.GrapheneBoneLacings)) { + delete Augmentations[AugmentationNames.GrapheneBoneLacings]; + } + AddToAugmentations(GrapheneBoneLacings); + + const BionicSpine = new Augmentation({ + name: AugmentationNames.BionicSpine, + repCost: 4.5e4, + moneyCost: 1.25e8, + info: + "The spine is reconstructed using plasteel and carbon fibers. " + + "It is now capable of stimulating and regulating neural signals " + + "passing through the spinal cord, improving senses and reaction speed. " + + "The 'Bionic Spine' also interfaces with all other 'Bionic' implants.", + strength_mult: 1.15, + defense_mult: 1.15, + agility_mult: 1.15, + dexterity_mult: 1.15, + }); + BionicSpine.addToFactions([ + "Speakers for the Dead", + "The Syndicate", + "KuaiGong International", + "OmniTek Incorporated", + "Blade Industries", + ]); + if (augmentationExists(AugmentationNames.BionicSpine)) { + delete Augmentations[AugmentationNames.BionicSpine]; + } + AddToAugmentations(BionicSpine); + + const GrapheneBionicSpine = new Augmentation({ + name: AugmentationNames.GrapheneBionicSpine, + repCost: 1.625e6, + moneyCost: 6e9, + info: + "An upgrade to the 'Bionic Spine' augmentation. The spine is fused with graphene " + + "which enhances durability and supercharges all body functions.", + prereqs: [AugmentationNames.BionicSpine], + strength_mult: 1.6, + defense_mult: 1.6, + agility_mult: 1.6, + dexterity_mult: 1.6, + }); + GrapheneBionicSpine.addToFactions(["Fulcrum Secret Technologies", "ECorp"]); + if (augmentationExists(AugmentationNames.GrapheneBionicSpine)) { + delete Augmentations[AugmentationNames.GrapheneBionicSpine]; + } + AddToAugmentations(GrapheneBionicSpine); + + const BionicLegs = new Augmentation({ + name: AugmentationNames.BionicLegs, + repCost: 1.5e5, + moneyCost: 3.75e8, + info: "Cybernetic legs, created from plasteel and carbon fibers, enhance running speed.", + agility_mult: 1.6, + }); + BionicLegs.addToFactions([ + "Speakers for the Dead", + "The Syndicate", + "KuaiGong International", + "OmniTek Incorporated", + "Blade Industries", + ]); + if (augmentationExists(AugmentationNames.BionicLegs)) { + delete Augmentations[AugmentationNames.BionicLegs]; + } + AddToAugmentations(BionicLegs); + + const GrapheneBionicLegs = new Augmentation({ + name: AugmentationNames.GrapheneBionicLegs, + repCost: 7.5e5, + moneyCost: 4.5e9, + info: + "An upgrade to the 'Bionic Legs' augmentation. The legs are fused " + + "with graphene, greatly enhancing jumping ability.", + prereqs: [AugmentationNames.BionicLegs], + agility_mult: 2.5, + }); + GrapheneBionicLegs.addToFactions([ + "MegaCorp", + "ECorp", + "Fulcrum Secret Technologies", + ]); + if (augmentationExists(AugmentationNames.GrapheneBionicLegs)) { + delete Augmentations[AugmentationNames.GrapheneBionicLegs]; + } + AddToAugmentations(GrapheneBionicLegs); + + // Work stat augmentations + const SpeechProcessor = new Augmentation({ + name: AugmentationNames.SpeechProcessor, + repCost: 7.5e3, + moneyCost: 5e7, + info: + "A cochlear implant with an embedded computer that analyzes incoming speech. " + + "The embedded computer processes characteristics of incoming speech, such as tone " + + "and inflection, to pick up on subtle cues and aid in social interactions.", + charisma_mult: 1.2, + }); + SpeechProcessor.addToFactions([ + "Tian Di Hui", + "Chongqing", + "Sector-12", + "New Tokyo", + "Aevum", + "Ishima", + "Volhaven", + "Silhouette", + ]); + if (augmentationExists(AugmentationNames.SpeechProcessor)) { + delete Augmentations[AugmentationNames.SpeechProcessor]; + } + AddToAugmentations(SpeechProcessor); + + const TITN41Injection = new Augmentation({ + name: AugmentationNames.TITN41Injection, + repCost: 2.5e4, + moneyCost: 1.9e8, + info: + "TITN is a series of viruses that targets and alters the sequences of human DNA in genes that " + + "control personality. The TITN-41 strain alters these genes so that the subject becomes more " + + "outgoing and socialable.", + charisma_mult: 1.15, + charisma_exp_mult: 1.15, + }); + TITN41Injection.addToFactions(["Silhouette"]); + if (augmentationExists(AugmentationNames.TITN41Injection)) { + delete Augmentations[AugmentationNames.TITN41Injection]; + } + AddToAugmentations(TITN41Injection); + + const EnhancedSocialInteractionImplant = new Augmentation({ + name: AugmentationNames.EnhancedSocialInteractionImplant, + repCost: 3.75e5, + moneyCost: 1.375e9, + info: + "A cranial implant that greatly assists in the user's ability to analyze social situations " + + "and interactions. The system uses a wide variety of factors such as facial expressions, body " + + "language, and the voice tone, and inflection to determine the best course of action during social" + + "situations. The implant also uses deep learning software to continuously learn new behavior" + + "patterns and how to best respond.", + charisma_mult: 1.6, + charisma_exp_mult: 1.6, + }); + EnhancedSocialInteractionImplant.addToFactions([ + "Bachman & Associates", + "NWO", + "Clarke Incorporated", + "OmniTek Incorporated", + "Four Sigma", + ]); + if (augmentationExists(AugmentationNames.EnhancedSocialInteractionImplant)) { + delete Augmentations[AugmentationNames.EnhancedSocialInteractionImplant]; + } + AddToAugmentations(EnhancedSocialInteractionImplant); + + // Hacking augmentations + const BitWire = new Augmentation({ + name: AugmentationNames.BitWire, + repCost: 3.75e3, + moneyCost: 1e7, + info: + "A small brain implant embedded in the cerebrum. This regulates and improves the brain's computing " + + "capabilities.", + hacking_mult: 1.05, + }); + BitWire.addToFactions(["CyberSec", "NiteSec"]); + if (augmentationExists(AugmentationNames.BitWire)) { + delete Augmentations[AugmentationNames.BitWire]; + } + AddToAugmentations(BitWire); + + const ArtificialBioNeuralNetwork = new Augmentation({ + name: AugmentationNames.ArtificialBioNeuralNetwork, + repCost: 2.75e5, + moneyCost: 3e9, + info: + "A network consisting of millions of nanoprocessors is embedded into the brain. " + + "The network is meant to mimic the way a biological brain solves a problem, with each " + + "nanoprocessor acting similar to the way a neuron would in a neural network. However, these " + + "nanoprocessors are programmed to perform computations much faster than organic neurons, " + + "allowing the user to solve much more complex problems at a much faster rate.", + hacking_speed_mult: 1.03, + hacking_money_mult: 1.15, + hacking_mult: 1.12, + }); + ArtificialBioNeuralNetwork.addToFactions([ + "BitRunners", + "Fulcrum Secret Technologies", + ]); + if (augmentationExists(AugmentationNames.ArtificialBioNeuralNetwork)) { + delete Augmentations[AugmentationNames.ArtificialBioNeuralNetwork]; + } + AddToAugmentations(ArtificialBioNeuralNetwork); + + const ArtificialSynapticPotentiation = new Augmentation({ + name: AugmentationNames.ArtificialSynapticPotentiation, + repCost: 6.25e3, + moneyCost: 8e7, + info: + "The body is injected with a chemical that artificially induces synaptic potentiation, " + + "otherwise known as the strengthening of synapses. This results in enhanced cognitive abilities.", + hacking_speed_mult: 1.02, + hacking_chance_mult: 1.05, + hacking_exp_mult: 1.05, + }); + ArtificialSynapticPotentiation.addToFactions(["The Black Hand", "NiteSec"]); + if (augmentationExists(AugmentationNames.ArtificialSynapticPotentiation)) { + delete Augmentations[AugmentationNames.ArtificialSynapticPotentiation]; + } + AddToAugmentations(ArtificialSynapticPotentiation); + + const EnhancedMyelinSheathing = new Augmentation({ + name: AugmentationNames.EnhancedMyelinSheathing, + repCost: 1e5, + moneyCost: 1.375e9, + info: + "Electrical signals are used to induce a new, artificial form of myelinogenesis in the human body. " + + "This process results in the proliferation of new, synthetic myelin sheaths in the nervous " + + "system. These myelin sheaths can propogate neuro-signals much faster than their organic " + + "counterparts, leading to greater processing speeds and better brain function.", + hacking_speed_mult: 1.03, + hacking_exp_mult: 1.1, + hacking_mult: 1.08, + }); + EnhancedMyelinSheathing.addToFactions([ + "Fulcrum Secret Technologies", + "BitRunners", + "The Black Hand", + ]); + if (augmentationExists(AugmentationNames.EnhancedMyelinSheathing)) { + delete Augmentations[AugmentationNames.EnhancedMyelinSheathing]; + } + AddToAugmentations(EnhancedMyelinSheathing); + + const SynapticEnhancement = new Augmentation({ + name: AugmentationNames.SynapticEnhancement, + repCost: 2e3, + moneyCost: 7.5e6, + info: + "A small cranial implant that continuously uses weak electrical signals to stimulate the brain and " + + "induce stronger synaptic activity. This improves the user's cognitive abilities.", + hacking_speed_mult: 1.03, + }); + SynapticEnhancement.addToFactions(["CyberSec", "Aevum"]); + if (augmentationExists(AugmentationNames.SynapticEnhancement)) { + delete Augmentations[AugmentationNames.SynapticEnhancement]; + } + AddToAugmentations(SynapticEnhancement); + + const NeuralRetentionEnhancement = new Augmentation({ + name: AugmentationNames.NeuralRetentionEnhancement, + repCost: 2e4, + moneyCost: 2.5e8, + info: + "Chemical injections are used to permanently alter and strengthen the brain's neuronal " + + "circuits, strengthening the ability to retain information.", + hacking_exp_mult: 1.25, + }); + NeuralRetentionEnhancement.addToFactions(["NiteSec"]); + if (augmentationExists(AugmentationNames.NeuralRetentionEnhancement)) { + delete Augmentations[AugmentationNames.NeuralRetentionEnhancement]; + } + AddToAugmentations(NeuralRetentionEnhancement); + + const DataJack = new Augmentation({ + name: AugmentationNames.DataJack, + repCost: 1.125e5, + moneyCost: 4.5e8, + info: + "A brain implant that provides an interface for direct, wireless communication between a computer's main " + + "memory and the mind. This implant allows the user to not only access a computer's memory, but also alter " + + "and delete it.", + hacking_money_mult: 1.25, + }); + DataJack.addToFactions([ + "BitRunners", + "The Black Hand", + "NiteSec", + "Chongqing", + "New Tokyo", + ]); + if (augmentationExists(AugmentationNames.DataJack)) { + delete Augmentations[AugmentationNames.DataJack]; + } + AddToAugmentations(DataJack); + + const ENM = new Augmentation({ + name: AugmentationNames.ENM, + repCost: 1.5e4, + moneyCost: 2.5e8, + info: + "A thin device embedded inside the arm containing a wireless module capable of connecting " + + "to nearby networks. Once connected, the Netburner Module is capable of capturing and " + + "processing all of the traffic on that network. By itself, the Embedded Netburner Module does " + + "not do much, but a variety of very powerful upgrades can be installed that allow you to fully " + + "control the traffic on a network.", + hacking_mult: 1.08, + }); + ENM.addToFactions([ + "BitRunners", + "The Black Hand", + "NiteSec", + "ECorp", + "MegaCorp", + "Fulcrum Secret Technologies", + "NWO", + "Blade Industries", + ]); + if (augmentationExists(AugmentationNames.ENM)) { + delete Augmentations[AugmentationNames.ENM]; + } + AddToAugmentations(ENM); + + const ENMCore = new Augmentation({ + name: AugmentationNames.ENMCore, + repCost: 2.5e5, + moneyCost: 2.5e9, + info: + "The Core library is an implant that upgrades the firmware of the Embedded Netburner Module. " + + "This upgrade allows the Embedded Netburner Module to generate its own data on a network.", + prereqs: [AugmentationNames.ENM], + hacking_speed_mult: 1.03, + hacking_money_mult: 1.1, + hacking_chance_mult: 1.03, + hacking_exp_mult: 1.07, + hacking_mult: 1.07, + }); + ENMCore.addToFactions([ + "BitRunners", + "The Black Hand", + "ECorp", + "MegaCorp", + "Fulcrum Secret Technologies", + "NWO", + "Blade Industries", + ]); + if (augmentationExists(AugmentationNames.ENMCore)) { + delete Augmentations[AugmentationNames.ENMCore]; + } + AddToAugmentations(ENMCore); + + const ENMCoreV2 = new Augmentation({ + name: AugmentationNames.ENMCoreV2, + repCost: 1e6, + moneyCost: 4.5e9, + info: + "The Core V2 library is an implant that upgrades the firmware of the Embedded Netburner Module. " + + "This upgraded firmware allows the Embedded Netburner Module to control information on " + + "a network by re-routing traffic, spoofing IP addresses, and altering the data inside network " + + "packets.", + prereqs: [AugmentationNames.ENMCore], + hacking_speed_mult: 1.05, + hacking_money_mult: 1.3, + hacking_chance_mult: 1.05, + hacking_exp_mult: 1.15, + hacking_mult: 1.08, + }); + ENMCoreV2.addToFactions([ + "BitRunners", + "ECorp", + "MegaCorp", + "Fulcrum Secret Technologies", + "NWO", + "Blade Industries", + "OmniTek Incorporated", + "KuaiGong International", + ]); + if (augmentationExists(AugmentationNames.ENMCoreV2)) { + delete Augmentations[AugmentationNames.ENMCoreV2]; + } + AddToAugmentations(ENMCoreV2); + + const ENMCoreV3 = new Augmentation({ + name: AugmentationNames.ENMCoreV3, + repCost: 1.75e6, + moneyCost: 7.5e9, + info: + "The Core V3 library is an implant that upgrades the firmware of the Embedded Netburner Module. " + + "This upgraded firmware allows the Embedded Netburner Module to seamlessly inject code into " + + "any device on a network.", + prereqs: [AugmentationNames.ENMCoreV2], + hacking_speed_mult: 1.05, + hacking_money_mult: 1.4, + hacking_chance_mult: 1.1, + hacking_exp_mult: 1.25, + hacking_mult: 1.1, + }); + ENMCoreV3.addToFactions([ + "ECorp", + "MegaCorp", + "Fulcrum Secret Technologies", + "NWO", + "Daedalus", + "The Covenant", + "Illuminati", + ]); + if (augmentationExists(AugmentationNames.ENMCoreV3)) { + delete Augmentations[AugmentationNames.ENMCoreV3]; + } + AddToAugmentations(ENMCoreV3); + + const ENMAnalyzeEngine = new Augmentation({ + name: AugmentationNames.ENMAnalyzeEngine, + repCost: 6.25e5, + moneyCost: 6e9, + info: + "Installs the Analyze Engine for the Embedded Netburner Module, which is a CPU cluster " + + "that vastly outperforms the Netburner Module's native single-core processor.", + prereqs: [AugmentationNames.ENM], + hacking_speed_mult: 1.1, + }); + ENMAnalyzeEngine.addToFactions([ + "ECorp", + "MegaCorp", + "Fulcrum Secret Technologies", + "NWO", + "Daedalus", + "The Covenant", + "Illuminati", + ]); + if (augmentationExists(AugmentationNames.ENMAnalyzeEngine)) { + delete Augmentations[AugmentationNames.ENMAnalyzeEngine]; + } + AddToAugmentations(ENMAnalyzeEngine); + + const ENMDMA = new Augmentation({ + name: AugmentationNames.ENMDMA, + repCost: 1e6, + moneyCost: 7e9, + info: + "This implant installs a Direct Memory Access (DMA) controller into the " + + "Embedded Netburner Module. This allows the Module to send and receive data " + + "directly to and from the main memory of devices on a network.", + prereqs: [AugmentationNames.ENM], + hacking_money_mult: 1.4, + hacking_chance_mult: 1.2, + }); + ENMDMA.addToFactions([ + "ECorp", + "MegaCorp", + "Fulcrum Secret Technologies", + "NWO", + "Daedalus", + "The Covenant", + "Illuminati", + ]); + if (augmentationExists(AugmentationNames.ENMDMA)) { + delete Augmentations[AugmentationNames.ENMDMA]; + } + AddToAugmentations(ENMDMA); + + const Neuralstimulator = new Augmentation({ + name: AugmentationNames.Neuralstimulator, + repCost: 5e4, + moneyCost: 3e9, + info: + "A cranial implant that intelligently stimulates certain areas of the brain " + + "in order to improve cognitive functions.", + hacking_speed_mult: 1.02, + hacking_chance_mult: 1.1, + hacking_exp_mult: 1.12, + }); + Neuralstimulator.addToFactions([ + "The Black Hand", + "Chongqing", + "Sector-12", + "New Tokyo", + "Aevum", + "Ishima", + "Volhaven", + "Bachman & Associates", + "Clarke Incorporated", + "Four Sigma", + ]); + if (augmentationExists(AugmentationNames.Neuralstimulator)) { + delete Augmentations[AugmentationNames.Neuralstimulator]; + } + AddToAugmentations(Neuralstimulator); + + const NeuralAccelerator = new Augmentation({ + name: AugmentationNames.NeuralAccelerator, + repCost: 2e5, + moneyCost: 1.75e9, + info: + "A microprocessor that accelerates the processing " + + "speed of biological neural networks. This is a cranial implant that is embedded inside the brain.", + hacking_mult: 1.1, + hacking_exp_mult: 1.15, + hacking_money_mult: 1.2, + }); + NeuralAccelerator.addToFactions(["BitRunners"]); + if (augmentationExists(AugmentationNames.NeuralAccelerator)) { + delete Augmentations[AugmentationNames.NeuralAccelerator]; + } + AddToAugmentations(NeuralAccelerator); + + const CranialSignalProcessorsG1 = new Augmentation({ + name: AugmentationNames.CranialSignalProcessorsG1, + repCost: 1e4, + moneyCost: 7e7, + info: + "The first generation of Cranial Signal Processors. Cranial Signal Processors " + + "are a set of specialized microprocessors that are attached to " + + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + + "so that the brain doesn't have to.", + hacking_speed_mult: 1.01, + hacking_mult: 1.05, + }); + CranialSignalProcessorsG1.addToFactions(["CyberSec"]); + if (augmentationExists(AugmentationNames.CranialSignalProcessorsG1)) { + delete Augmentations[AugmentationNames.CranialSignalProcessorsG1]; + } + AddToAugmentations(CranialSignalProcessorsG1); + + const CranialSignalProcessorsG2 = new Augmentation({ + name: AugmentationNames.CranialSignalProcessorsG2, + repCost: 1.875e4, + moneyCost: 1.25e8, + info: + "The second generation of Cranial Signal Processors. Cranial Signal Processors " + + "are a set of specialized microprocessors that are attached to " + + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + + "so that the brain doesn't have to.", + prereqs: [AugmentationNames.CranialSignalProcessorsG1], + hacking_speed_mult: 1.02, + hacking_chance_mult: 1.05, + hacking_mult: 1.07, + }); + CranialSignalProcessorsG2.addToFactions(["CyberSec", "NiteSec"]); + if (augmentationExists(AugmentationNames.CranialSignalProcessorsG2)) { + delete Augmentations[AugmentationNames.CranialSignalProcessorsG2]; + } + AddToAugmentations(CranialSignalProcessorsG2); + + const CranialSignalProcessorsG3 = new Augmentation({ + name: AugmentationNames.CranialSignalProcessorsG3, + repCost: 5e4, + moneyCost: 5.5e8, + info: + "The third generation of Cranial Signal Processors. Cranial Signal Processors " + + "are a set of specialized microprocessors that are attached to " + + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + + "so that the brain doesn't have to.", + prereqs: [AugmentationNames.CranialSignalProcessorsG2], + hacking_speed_mult: 1.02, + hacking_money_mult: 1.15, + hacking_mult: 1.09, + }); + CranialSignalProcessorsG3.addToFactions([ + "NiteSec", + "The Black Hand", + "BitRunners", + ]); + if (augmentationExists(AugmentationNames.CranialSignalProcessorsG3)) { + delete Augmentations[AugmentationNames.CranialSignalProcessorsG3]; + } + AddToAugmentations(CranialSignalProcessorsG3); + + const CranialSignalProcessorsG4 = new Augmentation({ + name: AugmentationNames.CranialSignalProcessorsG4, + repCost: 1.25e5, + moneyCost: 1.1e9, + info: + "The fourth generation of Cranial Signal Processors. Cranial Signal Processors " + + "are a set of specialized microprocessors that are attached to " + + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + + "so that the brain doesn't have to.", + prereqs: [AugmentationNames.CranialSignalProcessorsG3], + hacking_speed_mult: 1.02, + hacking_money_mult: 1.2, + hacking_grow_mult: 1.25, + }); + CranialSignalProcessorsG4.addToFactions(["The Black Hand", "BitRunners"]); + if (augmentationExists(AugmentationNames.CranialSignalProcessorsG4)) { + delete Augmentations[AugmentationNames.CranialSignalProcessorsG4]; + } + AddToAugmentations(CranialSignalProcessorsG4); + + const CranialSignalProcessorsG5 = new Augmentation({ + name: AugmentationNames.CranialSignalProcessorsG5, + repCost: 2.5e5, + moneyCost: 2.25e9, + info: + "The fifth generation of Cranial Signal Processors. Cranial Signal Processors " + + "are a set of specialized microprocessors that are attached to " + + "neurons in the brain. These chips process neural signals to quickly and automatically perform specific computations " + + "so that the brain doesn't have to.", + prereqs: [AugmentationNames.CranialSignalProcessorsG4], + hacking_mult: 1.3, + hacking_money_mult: 1.25, + hacking_grow_mult: 1.75, + }); + CranialSignalProcessorsG5.addToFactions(["BitRunners"]); + if (augmentationExists(AugmentationNames.CranialSignalProcessorsG5)) { + delete Augmentations[AugmentationNames.CranialSignalProcessorsG5]; + } + AddToAugmentations(CranialSignalProcessorsG5); + + const NeuronalDensification = new Augmentation({ + name: AugmentationNames.NeuronalDensification, + repCost: 1.875e5, + moneyCost: 1.375e9, + info: + "The brain is surgically re-engineered to have increased neuronal density " + + "by decreasing the neuron gap junction. Then, the body is genetically modified " + + "to enhance the production and capabilities of its neural stem cells.", + hacking_mult: 1.15, + hacking_exp_mult: 1.1, + hacking_speed_mult: 1.03, + }); + NeuronalDensification.addToFactions(["Clarke Incorporated"]); + if (augmentationExists(AugmentationNames.NeuronalDensification)) { + delete Augmentations[AugmentationNames.NeuronalDensification]; + } + AddToAugmentations(NeuronalDensification); + + // Work Augmentations + const NuoptimalInjectorImplant = new Augmentation({ + name: AugmentationNames.NuoptimalInjectorImplant, + repCost: 5e3, + moneyCost: 2e7, + info: + "This torso implant automatically injects nootropic supplements into " + + "the bloodstream to improve memory, increase focus, and provide other " + + "cognitive enhancements.", + company_rep_mult: 1.2, + }); + NuoptimalInjectorImplant.addToFactions([ + "Tian Di Hui", + "Volhaven", + "New Tokyo", + "Chongqing", + "Clarke Incorporated", + "Four Sigma", + "Bachman & Associates", + ]); + if (augmentationExists(AugmentationNames.NuoptimalInjectorImplant)) { + delete Augmentations[AugmentationNames.NuoptimalInjectorImplant]; + } + AddToAugmentations(NuoptimalInjectorImplant); + + const SpeechEnhancement = new Augmentation({ + name: AugmentationNames.SpeechEnhancement, + repCost: 2.5e3, + moneyCost: 1.25e7, + info: + "An advanced neural implant that improves your speaking abilities, making " + + "you more convincing and likable in conversations and overall improving your " + + "social interactions.", + company_rep_mult: 1.1, + charisma_mult: 1.1, + }); + SpeechEnhancement.addToFactions([ + "Tian Di Hui", + "Speakers for the Dead", + "Four Sigma", + "KuaiGong International", + "Clarke Incorporated", + "Bachman & Associates", + ]); + if (augmentationExists(AugmentationNames.SpeechEnhancement)) { + delete Augmentations[AugmentationNames.SpeechEnhancement]; + } + AddToAugmentations(SpeechEnhancement); + + const FocusWire = new Augmentation({ + name: AugmentationNames.FocusWire, + repCost: 7.5e4, + moneyCost: 9e8, + info: + "A cranial implant that stops procrastination by blocking specific neural pathways " + + "in the brain.", + hacking_exp_mult: 1.05, + strength_exp_mult: 1.05, + defense_exp_mult: 1.05, + dexterity_exp_mult: 1.05, + agility_exp_mult: 1.05, + charisma_exp_mult: 1.05, + company_rep_mult: 1.1, + work_money_mult: 1.2, + }); + FocusWire.addToFactions([ + "Bachman & Associates", + "Clarke Incorporated", + "Four Sigma", + "KuaiGong International", + ]); + if (augmentationExists(AugmentationNames.FocusWire)) { + delete Augmentations[AugmentationNames.FocusWire]; + } + AddToAugmentations(FocusWire); + + const PCDNI = new Augmentation({ + name: AugmentationNames.PCDNI, + repCost: 3.75e5, + moneyCost: 3.75e9, + info: + "Installs a Direct-Neural Interface jack into your arm that is compatible with most " + + "computers. Connecting to a computer through this jack allows you to interface with " + + "it using the brain's electrochemical signals.", + company_rep_mult: 1.3, + hacking_mult: 1.08, + }); + PCDNI.addToFactions([ + "Four Sigma", + "OmniTek Incorporated", + "ECorp", + "Blade Industries", + ]); + if (augmentationExists(AugmentationNames.PCDNI)) { + delete Augmentations[AugmentationNames.PCDNI]; + } + AddToAugmentations(PCDNI); + + const PCDNIOptimizer = new Augmentation({ + name: AugmentationNames.PCDNIOptimizer, + repCost: 5e5, + moneyCost: 4.5e9, + info: + "This is a submodule upgrade to the PC Direct-Neural Interface augmentation. It " + + "improves the performance of the interface and gives the user more control options " + + "to a connected computer.", + prereqs: [AugmentationNames.PCDNI], + company_rep_mult: 1.75, + hacking_mult: 1.1, + }); + PCDNIOptimizer.addToFactions([ + "Fulcrum Secret Technologies", + "ECorp", + "Blade Industries", + ]); + if (augmentationExists(AugmentationNames.PCDNIOptimizer)) { + delete Augmentations[AugmentationNames.PCDNIOptimizer]; + } + AddToAugmentations(PCDNIOptimizer); + + const PCDNINeuralNetwork = new Augmentation({ + name: AugmentationNames.PCDNINeuralNetwork, + repCost: 1.5e6, + moneyCost: 7.5e9, + info: + "This is an additional installation that upgrades the functionality of the " + + "PC Direct-Neural Interface augmentation. When connected to a computer, " + + "The Neural Network upgrade allows the user to use their own brain's " + + "processing power to aid the computer in computational tasks.", + prereqs: [AugmentationNames.PCDNI], + company_rep_mult: 2, + hacking_mult: 1.1, + hacking_speed_mult: 1.05, + }); + PCDNINeuralNetwork.addToFactions(["Fulcrum Secret Technologies"]); + if (augmentationExists(AugmentationNames.PCDNINeuralNetwork)) { + delete Augmentations[AugmentationNames.PCDNINeuralNetwork]; + } + AddToAugmentations(PCDNINeuralNetwork); + + const ADRPheromone1 = new Augmentation({ + name: AugmentationNames.ADRPheromone1, + repCost: 3.75e3, + moneyCost: 1.75e7, + info: + "The body is genetically re-engineered so that it produces the ADR-V1 pheromone, " + + "an artificial pheromone discovered by scientists. The ADR-V1 pheromone, when excreted, " + + "triggers feelings of admiration and approval in other people.", + company_rep_mult: 1.1, + faction_rep_mult: 1.1, + }); + ADRPheromone1.addToFactions([ + "Tian Di Hui", + "The Syndicate", + "NWO", + "MegaCorp", + "Four Sigma", + ]); + if (augmentationExists(AugmentationNames.ADRPheromone1)) { + delete Augmentations[AugmentationNames.ADRPheromone1]; + } + AddToAugmentations(ADRPheromone1); + + const ADRPheromone2 = new Augmentation({ + name: AugmentationNames.ADRPheromone2, + repCost: 6.25e4, + moneyCost: 5.5e8, + info: + "The body is genetically re-engineered so that it produces the ADR-V2 pheromone, " + + "which is similar to but more potent than ADR-V1. This pheromone, when excreted, " + + "triggers feelings of admiration, approval, and respect in others.", + company_rep_mult: 1.2, + faction_rep_mult: 1.2, + }); + ADRPheromone2.addToFactions([ + "Silhouette", + "Four Sigma", + "Bachman & Associates", + "Clarke Incorporated", + ]); + if (augmentationExists(AugmentationNames.ADRPheromone2)) { + delete Augmentations[AugmentationNames.ADRPheromone2]; + } + AddToAugmentations(ADRPheromone2); + + const ShadowsSimulacrum = new Augmentation({ + name: AugmentationNames.ShadowsSimulacrum, + repCost: 3.75e4, + moneyCost: 4e8, + info: + "A crude but functional matter phase-shifter module that is embedded " + + "in the brainstem and cerebellum. This augmentation was developed by " + + "criminal organizations and allows the user to project and control holographic " + + "simulacrums within a large radius. These simulacrums are commonly used for " + + "espionage and surveillance work.", + company_rep_mult: 1.15, + faction_rep_mult: 1.15, + }); + ShadowsSimulacrum.addToFactions([ + "The Syndicate", + "The Dark Army", + "Speakers for the Dead", + ]); + if (augmentationExists(AugmentationNames.ShadowsSimulacrum)) { + delete Augmentations[AugmentationNames.ShadowsSimulacrum]; + } + AddToAugmentations(ShadowsSimulacrum); + + // HacknetNode Augmentations + const HacknetNodeCPUUpload = new Augmentation({ + name: AugmentationNames.HacknetNodeCPUUpload, + repCost: 3.75e3, + moneyCost: 1.1e7, + info: + "Uploads the architecture and design details of a Hacknet Node's CPU into " + + "the brain. This allows the user to engineer custom hardware and software " + + "for the Hacknet Node that provides better performance.", + hacknet_node_money_mult: 1.15, + hacknet_node_purchase_cost_mult: 0.85, + }); + HacknetNodeCPUUpload.addToFactions(["Netburners"]); + if (augmentationExists(AugmentationNames.HacknetNodeCPUUpload)) { + delete Augmentations[AugmentationNames.HacknetNodeCPUUpload]; + } + AddToAugmentations(HacknetNodeCPUUpload); + + const HacknetNodeCacheUpload = new Augmentation({ + name: AugmentationNames.HacknetNodeCacheUpload, + repCost: 2.5e3, + moneyCost: 5.5e6, + info: + "Uploads the architecture and design details of a Hacknet Node's main-memory cache " + + "into the brain. This allows the user to engineer custom cache hardware for the " + + "Hacknet Node that offers better performance.", + hacknet_node_money_mult: 1.1, + hacknet_node_level_cost_mult: 0.85, + }); + HacknetNodeCacheUpload.addToFactions(["Netburners"]); + if (augmentationExists(AugmentationNames.HacknetNodeCacheUpload)) { + delete Augmentations[AugmentationNames.HacknetNodeCacheUpload]; + } + AddToAugmentations(HacknetNodeCacheUpload); + + const HacknetNodeNICUpload = new Augmentation({ + name: AugmentationNames.HacknetNodeNICUpload, + repCost: 1.875e3, + moneyCost: 4.5e6, + info: + "Uploads the architecture and design details of a Hacknet Node's Network Interface Card (NIC) " + + "into the brain. This allows the user to engineer a custom NIC for the Hacknet Node that " + + "offers better performance.", + hacknet_node_money_mult: 1.1, + hacknet_node_purchase_cost_mult: 0.9, + }); + HacknetNodeNICUpload.addToFactions(["Netburners"]); + if (augmentationExists(AugmentationNames.HacknetNodeNICUpload)) { + delete Augmentations[AugmentationNames.HacknetNodeNICUpload]; + } + AddToAugmentations(HacknetNodeNICUpload); + + const HacknetNodeKernelDNI = new Augmentation({ + name: AugmentationNames.HacknetNodeKernelDNI, + repCost: 7.5e3, + moneyCost: 4e7, + info: + "Installs a Direct-Neural Interface jack into the arm that is capable of connecting to a " + + "Hacknet Node. This lets the user access and manipulate the Node's kernel using " + + "electrochemical signals.", + hacknet_node_money_mult: 1.25, + }); + HacknetNodeKernelDNI.addToFactions(["Netburners"]); + if (augmentationExists(AugmentationNames.HacknetNodeKernelDNI)) { + delete Augmentations[AugmentationNames.HacknetNodeKernelDNI]; + } + AddToAugmentations(HacknetNodeKernelDNI); + + const HacknetNodeCoreDNI = new Augmentation({ + name: AugmentationNames.HacknetNodeCoreDNI, + repCost: 1.25e4, + moneyCost: 6e7, + info: + "Installs a Direct-Neural Interface jack into the arm that is capable of connecting " + + "to a Hacknet Node. This lets the user access and manipulate the Node's processing logic using " + + "electrochemical signals.", + hacknet_node_money_mult: 1.45, + }); + HacknetNodeCoreDNI.addToFactions(["Netburners"]); + if (augmentationExists(AugmentationNames.HacknetNodeCoreDNI)) { + delete Augmentations[AugmentationNames.HacknetNodeCoreDNI]; + } + AddToAugmentations(HacknetNodeCoreDNI); + + //Misc/Hybrid augmentations + const NeuroFluxGovernor = new Augmentation({ + name: AugmentationNames.NeuroFluxGovernor, + repCost: 1.25e3, + moneyCost: 3.75e6, + info: + "A device that is embedded in the back of the neck. The NeuroFlux Governor " + + "monitors and regulates nervous impulses coming to and from the spinal column, " + + "essentially 'governing' the body. By doing so, it improves the functionality of the " + + "body's nervous system.", + stats: ( + <> + This special augmentation can be leveled up infinitely. Each level of + this augmentation increases ALL multipliers by 1%, stacking + multiplicatively. + + ), + hacking_chance_mult: 1.01, + hacking_speed_mult: 1.01, + hacking_money_mult: 1.01, + hacking_grow_mult: 1.01, + hacking_mult: 1.01, + strength_mult: 1.01, + defense_mult: 1.01, + dexterity_mult: 1.01, + agility_mult: 1.01, + charisma_mult: 1.01, + hacking_exp_mult: 1.01, + strength_exp_mult: 1.01, + defense_exp_mult: 1.01, + dexterity_exp_mult: 1.01, + agility_exp_mult: 1.01, + charisma_exp_mult: 1.01, + company_rep_mult: 1.01, + faction_rep_mult: 1.01, + crime_money_mult: 1.01, + crime_success_mult: 1.01, + hacknet_node_money_mult: 1.01, + hacknet_node_purchase_cost_mult: 0.99, + hacknet_node_ram_cost_mult: 0.99, + hacknet_node_core_cost_mult: 0.99, + hacknet_node_level_cost_mult: 0.99, + work_money_mult: 1.01, + }); + + // Set the Augmentation's level to the currently-installed level + let currLevel = 0; + for (let i = 0; i < Player.augmentations.length; ++i) { + if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) { + currLevel = Player.augmentations[i].level; + } + } + NeuroFluxGovernor.level = currLevel; + + // To set the price/rep req of the NeuroFlux, we have to take into account NeuroFlux + // levels that are purchased but not yet installed + let nextLevel = currLevel; + for (let i = 0; i < Player.queuedAugmentations.length; ++i) { + if ( + Player.queuedAugmentations[i].name === AugmentationNames.NeuroFluxGovernor + ) { + ++nextLevel; + } + } + let mult = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel); + NeuroFluxGovernor.baseRepRequirement = + 500 * mult * BitNodeMultipliers.AugmentationRepCost; + NeuroFluxGovernor.baseCost = + 750e3 * mult * BitNodeMultipliers.AugmentationMoneyCost; + if (augmentationExists(AugmentationNames.NeuroFluxGovernor)) { + delete Augmentations[AugmentationNames.NeuroFluxGovernor]; + } + NeuroFluxGovernor.addToAllFactions(); + AddToAugmentations(NeuroFluxGovernor); + + const Neurotrainer1 = new Augmentation({ + name: AugmentationNames.Neurotrainer1, + repCost: 1e3, + moneyCost: 4e6, + info: + "A decentralized cranial implant that improves the brain's ability to learn. It is " + + "installed by releasing millions of nanobots into the human brain, each of which " + + "attaches to a different neural pathway to enhance the brain's ability to retain " + + "and retrieve information.", + hacking_exp_mult: 1.1, + strength_exp_mult: 1.1, + defense_exp_mult: 1.1, + dexterity_exp_mult: 1.1, + agility_exp_mult: 1.1, + charisma_exp_mult: 1.1, + }); + Neurotrainer1.addToFactions(["CyberSec", "Aevum"]); + if (augmentationExists(AugmentationNames.Neurotrainer1)) { + delete Augmentations[AugmentationNames.Neurotrainer1]; + } + AddToAugmentations(Neurotrainer1); + + const Neurotrainer2 = new Augmentation({ + name: AugmentationNames.Neurotrainer2, + repCost: 1e4, + moneyCost: 4.5e7, + info: + "A decentralized cranial implant that improves the brain's ability to learn. This " + + "is a more powerful version of the Neurotrainer I augmentation, but it does not " + + "require Neurotrainer I to be installed as a prerequisite.", + hacking_exp_mult: 1.15, + strength_exp_mult: 1.15, + defense_exp_mult: 1.15, + dexterity_exp_mult: 1.15, + agility_exp_mult: 1.15, + charisma_exp_mult: 1.15, + }); + Neurotrainer2.addToFactions(["BitRunners", "NiteSec"]); + if (augmentationExists(AugmentationNames.Neurotrainer2)) { + delete Augmentations[AugmentationNames.Neurotrainer2]; + } + AddToAugmentations(Neurotrainer2); + + const Neurotrainer3 = new Augmentation({ + name: AugmentationNames.Neurotrainer3, + repCost: 2.5e4, + moneyCost: 1.3e8, + info: + "A decentralized cranial implant that improves the brain's ability to learn. This " + + "is a more powerful version of the Neurotrainer I and Neurotrainer II augmentation, " + + "but it does not require either of them to be installed as a prerequisite.", + hacking_exp_mult: 1.2, + strength_exp_mult: 1.2, + defense_exp_mult: 1.2, + dexterity_exp_mult: 1.2, + agility_exp_mult: 1.2, + charisma_exp_mult: 1.2, + }); + Neurotrainer3.addToFactions(["NWO", "Four Sigma"]); + if (augmentationExists(AugmentationNames.Neurotrainer3)) { + delete Augmentations[AugmentationNames.Neurotrainer3]; + } + AddToAugmentations(Neurotrainer3); + + const Hypersight = new Augmentation({ + name: AugmentationNames.Hypersight, + repCost: 1.5e5, + moneyCost: 2.75e9, + info: + "A bionic eye implant that grants sight capabilities far beyond those of a natural human. " + + "Embedded circuitry within the implant provides the ability to detect heat and movement " + + "through solid objects such as walls, thus providing 'x-ray vision'-like capabilities.", + dexterity_mult: 1.4, + hacking_speed_mult: 1.03, + hacking_money_mult: 1.1, + }); + Hypersight.addToFactions(["Blade Industries", "KuaiGong International"]); + if (augmentationExists(AugmentationNames.Hypersight)) { + delete Augmentations[AugmentationNames.Hypersight]; + } + AddToAugmentations(Hypersight); + + const LuminCloaking1 = new Augmentation({ + name: AugmentationNames.LuminCloaking1, + repCost: 1.5e3, + moneyCost: 5e6, + info: + "A skin implant that reinforces the skin with highly-advanced synthetic cells. These " + + "cells, when powered, have a negative refractive index. As a result, they bend light " + + "around the skin, making the user much harder to see to the naked eye.", + agility_mult: 1.05, + crime_money_mult: 1.1, + }); + LuminCloaking1.addToFactions(["Slum Snakes", "Tetrads"]); + if (augmentationExists(AugmentationNames.LuminCloaking1)) { + delete Augmentations[AugmentationNames.LuminCloaking1]; + } + AddToAugmentations(LuminCloaking1); + + const LuminCloaking2 = new Augmentation({ + name: AugmentationNames.LuminCloaking2, + repCost: 5e3, + moneyCost: 3e7, + info: + "This is a more advanced version of the LuminCloaking-V1 augmentation. This skin implant " + + "reinforces the skin with highly-advanced synthetic cells. These " + + "cells, when powered, are capable of not only bending light but also of bending heat, " + + "making the user more resilient as well as stealthy.", + prereqs: [AugmentationNames.LuminCloaking1], + agility_mult: 1.1, + defense_mult: 1.1, + crime_money_mult: 1.25, + }); + LuminCloaking2.addToFactions(["Slum Snakes", "Tetrads"]); + if (augmentationExists(AugmentationNames.LuminCloaking2)) { + delete Augmentations[AugmentationNames.LuminCloaking2]; + } + AddToAugmentations(LuminCloaking2); + + const SmartSonar = new Augmentation({ + name: AugmentationNames.SmartSonar, + repCost: 2.25e4, + moneyCost: 7.5e7, + info: + "A cochlear implant that helps the player detect and locate enemies " + + "using sound propagation.", + dexterity_mult: 1.1, + dexterity_exp_mult: 1.15, + crime_money_mult: 1.25, + }); + SmartSonar.addToFactions(["Slum Snakes"]); + if (augmentationExists(AugmentationNames.SmartSonar)) { + delete Augmentations[AugmentationNames.SmartSonar]; + } + AddToAugmentations(SmartSonar); + + const PowerRecirculator = new Augmentation({ + name: AugmentationNames.PowerRecirculator, + repCost: 2.5e4, + moneyCost: 1.8e8, + info: + "The body's nerves are attached with polypyrrole nanocircuits that " + + "are capable of capturing wasted energy, in the form of heat, " + + "and converting it back into usable power.", + hacking_mult: 1.05, + strength_mult: 1.05, + defense_mult: 1.05, + dexterity_mult: 1.05, + agility_mult: 1.05, + charisma_mult: 1.05, + hacking_exp_mult: 1.1, + strength_exp_mult: 1.1, + defense_exp_mult: 1.1, + dexterity_exp_mult: 1.1, + agility_exp_mult: 1.1, + charisma_exp_mult: 1.1, + }); + PowerRecirculator.addToFactions([ + "Tetrads", + "The Dark Army", + "The Syndicate", + "NWO", + ]); + if (augmentationExists(AugmentationNames.PowerRecirculator)) { + delete Augmentations[AugmentationNames.PowerRecirculator]; + } + AddToAugmentations(PowerRecirculator); + + // Unique AUGS (Each Faction gets one unique augmentation) + // Factions that already have unique augs up to this point: + // Slum Snakes, CyberSec, Netburners, Fulcrum Secret Technologies, + // Silhouette + + // Illuminati + const QLink = new Augmentation({ + name: AugmentationNames.QLink, + repCost: 1.875e6, + moneyCost: 2.5e13, + info: + "A brain implant that wirelessly connects you to the Illuminati's " + + "quantum supercomputer, allowing you to access and use its incredible " + + "computing power.", + hacking_mult: 1.75, + hacking_speed_mult: 2, + hacking_chance_mult: 2.5, + hacking_money_mult: 4, + }); + QLink.addToFactions(["Illuminati"]); + if (augmentationExists(AugmentationNames.QLink)) { + delete Augmentations[AugmentationNames.QLink]; + } + AddToAugmentations(QLink); + + // Daedalus + const RedPill = new Augmentation({ + name: AugmentationNames.TheRedPill, + repCost: 2.5e6, + moneyCost: 0, + info: "It's time to leave the cave.", + stats: <>, + }); + RedPill.addToFactions(["Daedalus"]); + if (augmentationExists(AugmentationNames.TheRedPill)) { + delete Augmentations[AugmentationNames.TheRedPill]; + } + AddToAugmentations(RedPill); + + // Covenant + const SPTN97 = new Augmentation({ + name: AugmentationNames.SPTN97, + repCost: 1.25e6, + moneyCost: 4.875e9, + info: + "The SPTN-97 gene is injected into the genome. The SPTN-97 gene is an " + + "artificially-synthesized gene that was developed by DARPA to create " + + "super-soldiers through genetic modification. The gene was outlawed in " + + "2056.", + strength_mult: 1.75, + defense_mult: 1.75, + dexterity_mult: 1.75, + agility_mult: 1.75, + hacking_mult: 1.15, + }); + SPTN97.addToFactions(["The Covenant"]); + if (augmentationExists(AugmentationNames.SPTN97)) { + delete Augmentations[AugmentationNames.SPTN97]; + } + AddToAugmentations(SPTN97); + + // ECorp + const HiveMind = new Augmentation({ + name: AugmentationNames.HiveMind, + repCost: 1.5e6, + moneyCost: 5.5e9, + info: + "A brain implant developed by ECorp. They do not reveal what " + + "exactly the implant does, but they promise that it will greatly " + + "enhance your abilities.", + hacking_grow_mult: 3, + stats: <>, + }); + HiveMind.addToFactions(["ECorp"]); + if (augmentationExists(AugmentationNames.HiveMind)) { + delete Augmentations[AugmentationNames.HiveMind]; + } + AddToAugmentations(HiveMind); + + // MegaCorp + const CordiARCReactor = new Augmentation({ + name: AugmentationNames.CordiARCReactor, + repCost: 1.125e6, + moneyCost: 5e9, + info: + "The thoracic cavity is equipped with a small chamber designed " + + "to hold and sustain hydrogen plasma. The plasma is used to generate " + + "fusion power through nuclear fusion, providing limitless amounts of clean " + + "energy for the body.", + strength_mult: 1.35, + defense_mult: 1.35, + dexterity_mult: 1.35, + agility_mult: 1.35, + strength_exp_mult: 1.35, + defense_exp_mult: 1.35, + dexterity_exp_mult: 1.35, + agility_exp_mult: 1.35, + }); + CordiARCReactor.addToFactions(["MegaCorp"]); + if (augmentationExists(AugmentationNames.CordiARCReactor)) { + delete Augmentations[AugmentationNames.CordiARCReactor]; + } + AddToAugmentations(CordiARCReactor); + + // BachmanAndAssociates + const SmartJaw = new Augmentation({ + name: AugmentationNames.SmartJaw, + repCost: 3.75e5, + moneyCost: 2.75e9, + info: + "A bionic jaw that contains advanced hardware and software " + + "capable of psychoanalyzing and profiling the personality of " + + "others using optical imaging software.", + charisma_mult: 1.5, + charisma_exp_mult: 1.5, + company_rep_mult: 1.25, + faction_rep_mult: 1.25, + }); + SmartJaw.addToFactions(["Bachman & Associates"]); + if (augmentationExists(AugmentationNames.SmartJaw)) { + delete Augmentations[AugmentationNames.SmartJaw]; + } + AddToAugmentations(SmartJaw); + + // BladeIndustries + const Neotra = new Augmentation({ + name: AugmentationNames.Neotra, + repCost: 5.625e5, + moneyCost: 2.875e9, + info: + "A highly-advanced techno-organic drug that is injected into the skeletal " + + "and integumentary system. The drug permanently modifies the DNA of the " + + "body's skin and bone cells, granting them the ability to repair " + + "and restructure themselves.", + strength_mult: 1.55, + defense_mult: 1.55, + }); + Neotra.addToFactions(["Blade Industries"]); + if (augmentationExists(AugmentationNames.Neotra)) { + delete Augmentations[AugmentationNames.Neotra]; + } + AddToAugmentations(Neotra); + + // NWO + const Xanipher = new Augmentation({ + name: AugmentationNames.Xanipher, + repCost: 8.75e5, + moneyCost: 4.25e9, + info: + "A concoction of advanced nanobots that is orally ingested into the " + + "body. These nanobots induce physiological changes and significantly " + + "improve the body's functioning in all aspects.", + hacking_mult: 1.2, + strength_mult: 1.2, + defense_mult: 1.2, + dexterity_mult: 1.2, + agility_mult: 1.2, + charisma_mult: 1.2, + hacking_exp_mult: 1.15, + strength_exp_mult: 1.15, + defense_exp_mult: 1.15, + dexterity_exp_mult: 1.15, + agility_exp_mult: 1.15, + charisma_exp_mult: 1.15, + }); + Xanipher.addToFactions(["NWO"]); + if (augmentationExists(AugmentationNames.Xanipher)) { + delete Augmentations[AugmentationNames.Xanipher]; + } + AddToAugmentations(Xanipher); + + const HydroflameLeftArm = new Augmentation({ + name: AugmentationNames.HydroflameLeftArm, + repCost: 1.25e6, + moneyCost: 2.5e12, + info: + "The left arm of a legendary BitRunner who ascended beyond this world. " + + "It projects a light blue energy shield that protects the exposed inner parts. " + + "Even though it contains no weapons, the advanced tungsten titanium " + + "alloy increases the users strength to unbelievable levels. The augmentation " + + "gets more powerful over time for seemingly no reason.", + strength_mult: 2.7, + }); + HydroflameLeftArm.addToFactions(["NWO"]); + if (augmentationExists(AugmentationNames.HydroflameLeftArm)) { + delete Augmentations[AugmentationNames.HydroflameLeftArm]; + } + AddToAugmentations(HydroflameLeftArm); + + // ClarkeIncorporated + const nextSENS = new Augmentation({ + name: AugmentationNames.nextSENS, + repCost: 4.375e5, + moneyCost: 1.925e9, + info: + "The body is genetically re-engineered to maintain a state " + + "of negligible senescence, preventing the body from " + + "deteriorating with age.", + hacking_mult: 1.2, + strength_mult: 1.2, + defense_mult: 1.2, + dexterity_mult: 1.2, + agility_mult: 1.2, + charisma_mult: 1.2, + }); + nextSENS.addToFactions(["Clarke Incorporated"]); + if (augmentationExists(AugmentationNames.nextSENS)) { + delete Augmentations[AugmentationNames.nextSENS]; + } + AddToAugmentations(nextSENS); + + // OmniTekIncorporated + const OmniTekInfoLoad = new Augmentation({ + name: AugmentationNames.OmniTekInfoLoad, + repCost: 6.25e5, + moneyCost: 2.875e9, + info: + "OmniTek's data and information repository is uploaded " + + "into your brain, enhancing your programming and " + + "hacking abilities.", + hacking_mult: 1.2, + hacking_exp_mult: 1.25, + }); + OmniTekInfoLoad.addToFactions(["OmniTek Incorporated"]); + if (augmentationExists(AugmentationNames.OmniTekInfoLoad)) { + delete Augmentations[AugmentationNames.OmniTekInfoLoad]; + } + AddToAugmentations(OmniTekInfoLoad); + + // FourSigma + // TODO Later when Intelligence is added in . Some aug that greatly increases int + + // KuaiGongInternational + const PhotosyntheticCells = new Augmentation({ + name: AugmentationNames.PhotosyntheticCells, + repCost: 5.625e5, + moneyCost: 2.75e9, + info: + "Chloroplasts are added to epidermal stem cells and are applied " + + "to the body using a skin graft. The result is photosynthetic " + + "skin cells, allowing users to generate their own energy " + + "and nutrition using solar power.", + strength_mult: 1.4, + defense_mult: 1.4, + agility_mult: 1.4, + }); + PhotosyntheticCells.addToFactions(["KuaiGong International"]); + if (augmentationExists(AugmentationNames.PhotosyntheticCells)) { + delete Augmentations[AugmentationNames.PhotosyntheticCells]; + } + AddToAugmentations(PhotosyntheticCells); + + // BitRunners + const Neurolink = new Augmentation({ + name: AugmentationNames.Neurolink, + repCost: 8.75e5, + moneyCost: 4.375e9, + info: + "A brain implant that provides a high-bandwidth, direct neural link between your " + + "mind and the BitRunners' data servers, which reportedly contain " + + "the largest database of hacking tools and information in the world.", + hacking_mult: 1.15, + hacking_exp_mult: 1.2, + hacking_chance_mult: 1.1, + hacking_speed_mult: 1.05, + programs: [Programs.FTPCrackProgram.name, Programs.RelaySMTPProgram.name], + }); + Neurolink.addToFactions(["BitRunners"]); + if (augmentationExists(AugmentationNames.Neurolink)) { + delete Augmentations[AugmentationNames.Neurolink]; + } + AddToAugmentations(Neurolink); + + // BlackHand + const TheBlackHand = new Augmentation({ + name: AugmentationNames.TheBlackHand, + repCost: 1e5, + moneyCost: 5.5e8, + info: + "A highly advanced bionic hand. This prosthetic not only " + + "enhances strength and dexterity but it is also embedded " + + "with hardware and firmware that lets the user connect to, access, and hack " + + "devices and machines by just touching them.", + strength_mult: 1.15, + dexterity_mult: 1.15, + hacking_mult: 1.1, + hacking_speed_mult: 1.02, + hacking_money_mult: 1.1, + }); + TheBlackHand.addToFactions(["The Black Hand"]); + if (augmentationExists(AugmentationNames.TheBlackHand)) { + delete Augmentations[AugmentationNames.TheBlackHand]; + } + AddToAugmentations(TheBlackHand); + + // NiteSec + const CRTX42AA = new Augmentation({ + name: AugmentationNames.CRTX42AA, + repCost: 4.5e4, + moneyCost: 2.25e8, + info: + "The CRTX42-AA gene is injected into the genome. " + + "The CRTX42-AA is an artificially-synthesized gene that targets the visual and prefrontal " + + "cortex and improves cognitive abilities.", + hacking_mult: 1.08, + hacking_exp_mult: 1.15, + }); + CRTX42AA.addToFactions(["NiteSec"]); + if (augmentationExists(AugmentationNames.CRTX42AA)) { + delete Augmentations[AugmentationNames.CRTX42AA]; + } + AddToAugmentations(CRTX42AA); + + // Chongqing + const Neuregen = new Augmentation({ + name: AugmentationNames.Neuregen, + repCost: 3.75e4, + moneyCost: 3.75e8, + info: + "A drug that genetically modifies the neurons in the brain " + + "resulting in neurons never die, continuously " + + "regenerate, and strengthen themselves.", + hacking_exp_mult: 1.4, + }); + Neuregen.addToFactions(["Chongqing"]); + if (augmentationExists(AugmentationNames.Neuregen)) { + delete Augmentations[AugmentationNames.Neuregen]; + } + AddToAugmentations(Neuregen); + + // Sector12 + const CashRoot = new Augmentation({ + name: AugmentationNames.CashRoot, + repCost: 1.25e4, + moneyCost: 1.25e8, + info: ( + <> + A collection of digital assets saved on a small chip. The chip is + implanted into your wrist. A small jack in the chip allows you to + connect it to a computer and upload the assets. + + ), + startingMoney: 1e6, + programs: [Programs.BruteSSHProgram.name], + }); + CashRoot.addToFactions(["Sector-12"]); + if (augmentationExists(AugmentationNames.CashRoot)) { + delete Augmentations[AugmentationNames.CashRoot]; + } + AddToAugmentations(CashRoot); + + // NewTokyo + const NutriGen = new Augmentation({ + name: AugmentationNames.NutriGen, + repCost: 6.25e3, + moneyCost: 2.5e6, + info: + "A thermo-powered artificial nutrition generator. Endogenously " + + "synthesizes glucose, amino acids, and vitamins and redistributes them " + + "across the body. The device is powered by the body's naturally wasted " + + "energy in the form of heat.", + strength_exp_mult: 1.2, + defense_exp_mult: 1.2, + dexterity_exp_mult: 1.2, + agility_exp_mult: 1.2, + }); + NutriGen.addToFactions(["New Tokyo"]); + if (augmentationExists(AugmentationNames.NutriGen)) { + delete Augmentations[AugmentationNames.NutriGen]; + } + AddToAugmentations(NutriGen); + + // Aevum + const PCMatrix = new Augmentation({ + name: AugmentationNames.PCMatrix, + repCost: 100e3, + moneyCost: 2e9, + info: + "A 'Probability Computation Matrix' is installed in the frontal cortex. This implant " + + "uses advanced mathematical algorithims to rapidly identify and compute statistical " + + "outcomes of nearly every situation.", + charisma_mult: 1.0777, + charisma_exp_mult: 1.0777, + work_money_mult: 1.777, + faction_rep_mult: 1.0777, + company_rep_mult: 1.0777, + crime_success_mult: 1.0777, + crime_money_mult: 1.0777, + programs: [Programs.DeepscanV1.name, Programs.AutoLink.name], + }); + PCMatrix.addToFactions(["Aevum"]); + if (augmentationExists(AugmentationNames.PCMatrix)) { + delete Augmentations[AugmentationNames.PCMatrix]; + } + AddToAugmentations(PCMatrix); + + // Ishima + const INFRARet = new Augmentation({ + name: AugmentationNames.INFRARet, + repCost: 7.5e3, + moneyCost: 3e7, + info: + "A tiny chip that sits behind the retinae. This implant lets the" + + "user visually detect infrared radiation.", + crime_success_mult: 1.25, + crime_money_mult: 1.1, + dexterity_mult: 1.1, + }); + INFRARet.addToFactions(["Ishima"]); + if (augmentationExists(AugmentationNames.INFRARet)) { + delete Augmentations[AugmentationNames.INFRARet]; + } + AddToAugmentations(INFRARet); + + // Volhaven + const DermaForce = new Augmentation({ + name: AugmentationNames.DermaForce, + repCost: 1.5e4, + moneyCost: 5e7, + info: + "Synthetic skin that is grafted onto the body. This skin consists of " + + "millions of nanobots capable of projecting high-density muon beams, " + + "creating an energy barrier around the user.", + defense_mult: 1.4, + }); + DermaForce.addToFactions(["Volhaven"]); + if (augmentationExists(AugmentationNames.DermaForce)) { + delete Augmentations[AugmentationNames.DermaForce]; + } + AddToAugmentations(DermaForce); + + // SpeakersForTheDead + const GrapheneBrachiBlades = new Augmentation({ + name: AugmentationNames.GrapheneBrachiBlades, + repCost: 2.25e5, + moneyCost: 2.5e9, + info: + "An upgrade to the BrachiBlades augmentation. It infuses " + + "the retractable blades with an advanced graphene material " + + "making them stronger and lighter.", + prereqs: [AugmentationNames.BrachiBlades], + strength_mult: 1.4, + defense_mult: 1.4, + crime_success_mult: 1.1, + crime_money_mult: 1.3, + }); + GrapheneBrachiBlades.addToFactions(["Speakers for the Dead"]); + if (augmentationExists(AugmentationNames.GrapheneBrachiBlades)) { + delete Augmentations[AugmentationNames.GrapheneBrachiBlades]; + } + AddToAugmentations(GrapheneBrachiBlades); + + // DarkArmy + const GrapheneBionicArms = new Augmentation({ + name: AugmentationNames.GrapheneBionicArms, + repCost: 5e5, + moneyCost: 3.75e9, + info: + "An upgrade to the Bionic Arms augmentation. It infuses the " + + "prosthetic arms with an advanced graphene material " + + "to make them stronger and lighter.", + prereqs: [AugmentationNames.BionicArms], + strength_mult: 1.85, + dexterity_mult: 1.85, + }); + GrapheneBionicArms.addToFactions(["The Dark Army"]); + if (augmentationExists(AugmentationNames.GrapheneBionicArms)) { + delete Augmentations[AugmentationNames.GrapheneBionicArms]; + } + AddToAugmentations(GrapheneBionicArms); + + // TheSyndicate + const BrachiBlades = new Augmentation({ + name: AugmentationNames.BrachiBlades, + repCost: 1.25e4, + moneyCost: 9e7, + info: "A set of retractable plasteel blades that are implanted in the arm, underneath the skin.", + strength_mult: 1.15, + defense_mult: 1.15, + crime_success_mult: 1.1, + crime_money_mult: 1.15, + }); + BrachiBlades.addToFactions(["The Syndicate"]); + if (augmentationExists(AugmentationNames.BrachiBlades)) { + delete Augmentations[AugmentationNames.BrachiBlades]; + } + AddToAugmentations(BrachiBlades); + + // Tetrads + const BionicArms = new Augmentation({ + name: AugmentationNames.BionicArms, + repCost: 6.25e4, + moneyCost: 2.75e8, + info: + "Cybernetic arms created from plasteel and carbon fibers that completely replace " + + "the user's organic arms.", + strength_mult: 1.3, + dexterity_mult: 1.3, + }); + BionicArms.addToFactions(["Tetrads"]); + if (augmentationExists(AugmentationNames.BionicArms)) { + delete Augmentations[AugmentationNames.BionicArms]; + } + AddToAugmentations(BionicArms); + + // TianDiHui + const SNA = new Augmentation({ + name: AugmentationNames.SNA, + repCost: 6.25e3, + moneyCost: 3e7, + info: + "A cranial implant that affects the user's personality, making them better " + + "at negotiation in social situations.", + work_money_mult: 1.1, + company_rep_mult: 1.15, + faction_rep_mult: 1.15, + }); + SNA.addToFactions(["Tian Di Hui"]); + if (augmentationExists(AugmentationNames.SNA)) { + delete Augmentations[AugmentationNames.SNA]; + } + AddToAugmentations(SNA); + + // Special Bladeburner Augmentations + const BladeburnersFactionName = "Bladeburners"; + if (factionExists(BladeburnersFactionName)) { + const EsperEyewear = new Augmentation({ + name: AugmentationNames.EsperEyewear, + repCost: 1.25e3, + moneyCost: 1.65e8, + info: + "Ballistic-grade protective and retractable eyewear that was designed specifically " + + "for Bladeburner units. This " + + "is implanted by installing a mechanical frame in the skull's orbit. " + + "This frame interfaces with the brain and allows the user to " + + "automatically extrude and extract the eyewear. The eyewear protects " + + "against debris, shrapnel, lasers, blinding flashes, and gas. It is also " + + "embedded with a data processing chip that can be programmed to display an " + + "AR HUD to assist the user in field missions.", + bladeburner_success_chance_mult: 1.03, + dexterity_mult: 1.05, + isSpecial: true, + }); + EsperEyewear.addToFactions([BladeburnersFactionName]); + resetAugmentation(EsperEyewear); + + const EMS4Recombination = new Augmentation({ + name: AugmentationNames.EMS4Recombination, + repCost: 2.5e3, + moneyCost: 2.75e8, + info: + "A DNA recombination of the EMS-4 Gene. This genetic engineering " + + "technique was originally used on Bladeburners during the Synthoid uprising " + + "to induce wakefulness and concentration, suppress fear, reduce empathy, " + + "improve reflexes, and improve memory among other things.", + bladeburner_success_chance_mult: 1.03, + bladeburner_analysis_mult: 1.05, + bladeburner_stamina_gain_mult: 1.02, + isSpecial: true, + }); + EMS4Recombination.addToFactions([BladeburnersFactionName]); + resetAugmentation(EMS4Recombination); + + const OrionShoulder = new Augmentation({ + name: AugmentationNames.OrionShoulder, + repCost: 6.25e3, + moneyCost: 5.5e8, + info: + "A bionic shoulder augmentation for the right shoulder. Using cybernetics, " + + "the ORION-MKIV shoulder enhances the strength and dexterity " + + "of the user's right arm. It also provides protection due to its " + + "crystallized graphene plating.", + defense_mult: 1.05, + strength_mult: 1.05, + dexterity_mult: 1.05, + bladeburner_success_chance_mult: 1.04, + isSpecial: true, + }); + OrionShoulder.addToFactions([BladeburnersFactionName]); + resetAugmentation(OrionShoulder); + + const HyperionV1 = new Augmentation({ + name: AugmentationNames.HyperionV1, + repCost: 1.25e4, + moneyCost: 2.75e9, + info: + "A pair of mini plasma cannons embedded into the hands. The Hyperion is capable " + + "of rapidly firing bolts of high-density plasma. The weapon is meant to " + + "be used against augmented enemies as the ionized " + + "nature of the plasma disrupts the electrical systems of Augmentations. However, " + + "it can also be effective against non-augmented enemies due to its high temperature " + + "and concussive force.", + bladeburner_success_chance_mult: 1.06, + isSpecial: true, + }); + HyperionV1.addToFactions([BladeburnersFactionName]); + resetAugmentation(HyperionV1); + + const HyperionV2 = new Augmentation({ + name: AugmentationNames.HyperionV2, + repCost: 2.5e4, + moneyCost: 5.5e9, + info: + "A pair of mini plasma cannons embedded into the hands. This augmentation " + + "is more advanced and powerful than the original V1 model. This V2 model is " + + "more power-efficient, more accurate, and can fire plasma bolts at a much " + + "higher velocity than the V1 model.", + prereqs: [AugmentationNames.HyperionV1], + bladeburner_success_chance_mult: 1.08, + isSpecial: true, + }); + HyperionV2.addToFactions([BladeburnersFactionName]); + resetAugmentation(HyperionV2); + + const GolemSerum = new Augmentation({ + name: AugmentationNames.GolemSerum, + repCost: 3.125e4, + moneyCost: 1.1e10, + info: + "A serum that permanently enhances many aspects of human capabilities, " + + "including strength, speed, immune system enhancements, and mitochondrial efficiency. The " + + "serum was originally developed by the Chinese military in an attempt to " + + "create super soldiers.", + strength_mult: 1.07, + defense_mult: 1.07, + dexterity_mult: 1.07, + agility_mult: 1.07, + bladeburner_stamina_gain_mult: 1.05, + isSpecial: true, + }); + GolemSerum.addToFactions([BladeburnersFactionName]); + resetAugmentation(GolemSerum); + + const VangelisVirus = new Augmentation({ + name: AugmentationNames.VangelisVirus, + repCost: 1.875e4, + moneyCost: 2.75e9, + info: + "A synthetic symbiotic virus that is injected into human brain tissue. The Vangelis virus " + + "heightens the senses and focus of its host, and also enhances its intuition.", + dexterity_exp_mult: 1.1, + bladeburner_analysis_mult: 1.1, + bladeburner_success_chance_mult: 1.04, + isSpecial: true, + }); + VangelisVirus.addToFactions([BladeburnersFactionName]); + resetAugmentation(VangelisVirus); + + const VangelisVirus3 = new Augmentation({ + name: AugmentationNames.VangelisVirus3, + repCost: 3.75e4, + moneyCost: 1.1e10, + info: + "An improved version of Vangelis, a synthetic symbiotic virus that is " + + "injected into human brain tissue. On top of the benefits of the original " + + "virus, this also grants an accelerated healing factor and enhanced " + + "reflexes.", + prereqs: [AugmentationNames.VangelisVirus], + defense_exp_mult: 1.1, + dexterity_exp_mult: 1.1, + bladeburner_analysis_mult: 1.15, + bladeburner_success_chance_mult: 1.05, + isSpecial: true, + }); + VangelisVirus3.addToFactions([BladeburnersFactionName]); + resetAugmentation(VangelisVirus3); + + const INTERLINKED = new Augmentation({ + name: AugmentationNames.INTERLINKED, + repCost: 2.5e4, + moneyCost: 5.5e9, + info: + "The DNA is genetically modified to enhance the human's body " + + "extracellular matrix (ECM). This improves the ECM's ability to " + + "structurally support the body and grants heightened strength and " + + "durability.", + strength_exp_mult: 1.05, + defense_exp_mult: 1.05, + dexterity_exp_mult: 1.05, + agility_exp_mult: 1.05, + bladeburner_max_stamina_mult: 1.1, + isSpecial: true, + }); + INTERLINKED.addToFactions([BladeburnersFactionName]); + resetAugmentation(INTERLINKED); + + const BladeRunner = new Augmentation({ + name: AugmentationNames.BladeRunner, + repCost: 2e4, + moneyCost: 8.25e9, + info: + "A cybernetic foot augmentation that was specifically created for Bladeburners " + + "during the Synthoid Uprising. The organic musculature of the human foot " + + "is enhanced with flexible carbon nanotube matrices that are controlled by " + + "intelligent servo-motors.", + agility_mult: 1.05, + bladeburner_max_stamina_mult: 1.05, + bladeburner_stamina_gain_mult: 1.05, + isSpecial: true, + }); + BladeRunner.addToFactions([BladeburnersFactionName]); + resetAugmentation(BladeRunner); + + const BladeArmor = new Augmentation({ + name: AugmentationNames.BladeArmor, + repCost: 1.25e4, + moneyCost: 1.375e9, + info: + "A powered exoskeleton suit designed as armor for Bladeburner units. This " + + "exoskeleton is incredibly adaptable and can protect the wearer from blunt, piercing, " + + "concussive, thermal, chemical, and electric trauma. It also enhances the user's " + + "physical abilities.", + strength_mult: 1.04, + defense_mult: 1.04, + dexterity_mult: 1.04, + agility_mult: 1.04, + bladeburner_stamina_gain_mult: 1.02, + bladeburner_success_chance_mult: 1.03, + isSpecial: true, + }); + BladeArmor.addToFactions([BladeburnersFactionName]); + resetAugmentation(BladeArmor); + + const BladeArmorPowerCells = new Augmentation({ + name: AugmentationNames.BladeArmorPowerCells, + repCost: 1.875e4, + moneyCost: 2.75e9, + info: + "Upgrades the BLADE-51b Tesla Armor with Ion Power Cells, which are capable of " + + "more efficiently storing and using power.", + prereqs: [AugmentationNames.BladeArmor], + bladeburner_success_chance_mult: 1.05, + bladeburner_stamina_gain_mult: 1.02, + bladeburner_max_stamina_mult: 1.05, + isSpecial: true, + }); + BladeArmorPowerCells.addToFactions([BladeburnersFactionName]); + resetAugmentation(BladeArmorPowerCells); + + const BladeArmorEnergyShielding = new Augmentation({ + name: AugmentationNames.BladeArmorEnergyShielding, + repCost: 2.125e4, + moneyCost: 5.5e9, + info: + "Upgrades the BLADE-51b Tesla Armor with a plasma energy propulsion system " + + "that is capable of projecting an energy shielding force field.", + prereqs: [AugmentationNames.BladeArmor], + defense_mult: 1.05, + bladeburner_success_chance_mult: 1.06, + isSpecial: true, + }); + BladeArmorEnergyShielding.addToFactions([BladeburnersFactionName]); + resetAugmentation(BladeArmorEnergyShielding); + + const BladeArmorUnibeam = new Augmentation({ + name: AugmentationNames.BladeArmorUnibeam, + repCost: 3.125e4, + moneyCost: 1.65e10, + info: + "Upgrades the BLADE-51b Tesla Armor with a concentrated deuterium-fluoride laser " + + "weapon. It's precision and accuracy makes it useful for quickly neutralizing " + + "threats while keeping casualties to a minimum.", + prereqs: [AugmentationNames.BladeArmor], + bladeburner_success_chance_mult: 1.08, + isSpecial: true, + }); + BladeArmorUnibeam.addToFactions([BladeburnersFactionName]); + resetAugmentation(BladeArmorUnibeam); + + const BladeArmorOmnibeam = new Augmentation({ + name: AugmentationNames.BladeArmorOmnibeam, + repCost: 6.25e4, + moneyCost: 2.75e10, + info: + "Upgrades the BLADE-51b Tesla Armor Unibeam augmentation to use a " + + "multiple-fiber system. This upgraded weapon uses multiple fiber laser " + + "modules that combine together to form a single, more powerful beam of up to " + + "2000MW.", + prereqs: [AugmentationNames.BladeArmorUnibeam], + bladeburner_success_chance_mult: 1.1, + isSpecial: true, + }); + BladeArmorOmnibeam.addToFactions([BladeburnersFactionName]); + resetAugmentation(BladeArmorOmnibeam); + + const BladeArmorIPU = new Augmentation({ + name: AugmentationNames.BladeArmorIPU, + repCost: 1.5e4, + moneyCost: 1.1e9, + info: + "Upgrades the BLADE-51b Tesla Armor with an AI Information Processing " + + "Unit that was specially designed to analyze Synthoid related data and " + + "information.", + prereqs: [AugmentationNames.BladeArmor], + bladeburner_analysis_mult: 1.15, + bladeburner_success_chance_mult: 1.02, + isSpecial: true, + }); + BladeArmorIPU.addToFactions([BladeburnersFactionName]); + resetAugmentation(BladeArmorIPU); + + const BladesSimulacrum = new Augmentation({ + name: AugmentationNames.BladesSimulacrum, + repCost: 1.25e3, + moneyCost: 1.5e11, + info: + "A highly-advanced matter phase-shifter module that is embedded " + + "in the brainstem and cerebellum. This augmentation allows " + + "the user to project and control a holographic simulacrum within an " + + "extremely large radius. These specially-modified holograms were specifically " + + "weaponized by Bladeburner units to be used against Synthoids.", + stats: ( + <> + This augmentation allows you to perform Bladeburner actions and other + actions (such as working, commiting crimes, etc.) at the same time. + + ), + isSpecial: true, + }); + BladesSimulacrum.addToFactions([BladeburnersFactionName]); + resetAugmentation(BladesSimulacrum); + } + + // Update costs based on how many have been purchased + mult = Math.pow( + CONSTANTS.MultipleAugMultiplier * + [1, 0.96, 0.94, 0.93][SourceFileFlags[11]], + Player.queuedAugmentations.length, + ); + for (var name in Augmentations) { + if (Augmentations.hasOwnProperty(name)) { + Augmentations[name].baseCost *= mult; + } + } + + Player.reapplyAllAugmentations(); } //Resets an Augmentation during (re-initizliation) function resetAugmentation(newAugObject) { - if (!(newAugObject instanceof Augmentation)) { - throw new Error("Invalid argument 'newAugObject' passed into resetAugmentation"); - } - var name = newAugObject.name; - if (augmentationExists(name)) { - delete Augmentations[name]; - } - AddToAugmentations(newAugObject); + if (!(newAugObject instanceof Augmentation)) { + throw new Error( + "Invalid argument 'newAugObject' passed into resetAugmentation", + ); + } + var name = newAugObject.name; + if (augmentationExists(name)) { + delete Augmentations[name]; + } + AddToAugmentations(newAugObject); } -function applyAugmentation(aug, reapply=false) { - Augmentations[aug.name].owned = true; +function applyAugmentation(aug, reapply = false) { + Augmentations[aug.name].owned = true; - const augObj = Augmentations[aug.name]; + const augObj = Augmentations[aug.name]; - // Apply multipliers - for (const mult in augObj.mults) { - if (Player[mult] == null) { - console.warn(`Augmentation has unrecognized multiplier property: ${mult}`); - } else { - Player[mult] *= augObj.mults[mult]; - } + // Apply multipliers + for (const mult in augObj.mults) { + if (Player[mult] == null) { + console.warn( + `Augmentation has unrecognized multiplier property: ${mult}`, + ); + } else { + Player[mult] *= augObj.mults[mult]; } + } - // Special logic for NeuroFlux Governor - if (aug.name === AugmentationNames.NeuroFluxGovernor) { - if (!reapply) { - Augmentations[aug.name].level = aug.level; - for (let i = 0; i < Player.augmentations.length; ++i) { - if (Player.augmentations[i].name == AugmentationNames.NeuroFluxGovernor) { - Player.augmentations[i].level = aug.level; - return; - // break; - } - } - } - } - - // Push onto Player's Augmentation list + // Special logic for NeuroFlux Governor + if (aug.name === AugmentationNames.NeuroFluxGovernor) { if (!reapply) { - var ownedAug = new PlayerOwnedAugmentation(aug.name); - Player.augmentations.push(ownedAug); + Augmentations[aug.name].level = aug.level; + for (let i = 0; i < Player.augmentations.length; ++i) { + if ( + Player.augmentations[i].name == AugmentationNames.NeuroFluxGovernor + ) { + Player.augmentations[i].level = aug.level; + return; + // break; + } + } } + } + + // Push onto Player's Augmentation list + if (!reapply) { + var ownedAug = new PlayerOwnedAugmentation(aug.name); + Player.augmentations.push(ownedAug); + } } function installAugmentations() { - if (Player.queuedAugmentations.length == 0) { - dialogBoxCreate("You have not purchased any Augmentations to install!"); - return false; + if (Player.queuedAugmentations.length == 0) { + dialogBoxCreate("You have not purchased any Augmentations to install!"); + return false; + } + let augmentationList = ""; + let nfgIndex = -1; + for (let i = Player.queuedAugmentations.length - 1; i >= 0; i--) { + if ( + Player.queuedAugmentations[i].name === AugmentationNames.NeuroFluxGovernor + ) { + nfgIndex = i; + break; } - let augmentationList = ""; - let nfgIndex = -1; - for(let i = Player.queuedAugmentations.length-1; i >= 0; i--) { - if(Player.queuedAugmentations[i].name === AugmentationNames.NeuroFluxGovernor) { - nfgIndex = i; - break; - } + } + for (let i = 0; i < Player.queuedAugmentations.length; ++i) { + const ownedAug = Player.queuedAugmentations[i]; + const aug = Augmentations[ownedAug.name]; + if (aug == null) { + console.error(`Invalid augmentation: ${ownedAug.name}`); + continue; } - for (let i = 0; i < Player.queuedAugmentations.length; ++i) { - const ownedAug = Player.queuedAugmentations[i]; - const aug = Augmentations[ownedAug.name]; - if (aug == null) { - console.error(`Invalid augmentation: ${ownedAug.name}`); - continue; - } - applyAugmentation(Player.queuedAugmentations[i]); - if(ownedAug.name === AugmentationNames.NeuroFluxGovernor - && i !== nfgIndex) continue; + applyAugmentation(Player.queuedAugmentations[i]); + if (ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) + continue; - let level = ""; - if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) { - level = ` - ${ownedAug.level}`; - } - augmentationList += (aug.name + level + "
    "); + let level = ""; + if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) { + level = ` - ${ownedAug.level}`; } - Player.queuedAugmentations = []; - dialogBoxCreate("You slowly drift to sleep as scientists put you under in order " + - "to install the following Augmentations:
    " + augmentationList + - "
    You wake up in your home...you feel different..."); - prestigeAugmentation(); + augmentationList += aug.name + level + "
    "; + } + Player.queuedAugmentations = []; + dialogBoxCreate( + "You slowly drift to sleep as scientists put you under in order " + + "to install the following Augmentations:
    " + + augmentationList + + "
    You wake up in your home...you feel different...", + ); + prestigeAugmentation(); } function augmentationExists(name) { - return Augmentations.hasOwnProperty(name); + return Augmentations.hasOwnProperty(name); } export function displayAugmentationsContent(contentEl) { - if (!routing.isOn(Page.Augmentations)) { return; } - if (!(contentEl instanceof HTMLElement)) { return; } + if (!routing.isOn(Page.Augmentations)) { + return; + } + if (!(contentEl instanceof HTMLElement)) { + return; + } - function backup() { - saveObject.exportGame(); - onExport(Player); - } + function backup() { + saveObject.exportGame(); + onExport(Player); + } - ReactDOM.render( - , - contentEl, - ); + ReactDOM.render( + , + contentEl, + ); } export function isRepeatableAug(aug) { - const augName = (aug instanceof Augmentation) ? aug.name : aug; + const augName = aug instanceof Augmentation ? aug.name : aug; - if (augName === AugmentationNames.NeuroFluxGovernor) { return true; } + if (augName === AugmentationNames.NeuroFluxGovernor) { + return true; + } - return false; + return false; } export { - installAugmentations, - initAugmentations, - applyAugmentation, - augmentationExists, + installAugmentations, + initAugmentations, + applyAugmentation, + augmentationExists, }; diff --git a/src/Augmentation/PlayerOwnedAugmentation.ts b/src/Augmentation/PlayerOwnedAugmentation.ts index 635c9f5b2..e8807b894 100644 --- a/src/Augmentation/PlayerOwnedAugmentation.ts +++ b/src/Augmentation/PlayerOwnedAugmentation.ts @@ -1,13 +1,13 @@ export class PlayerOwnedAugmentation { - level = 1; - name = ""; + level = 1; + name = ""; - constructor(name = "") { - this.name = name; - } + constructor(name = "") { + this.name = name; + } } export interface IPlayerOwnedAugmentation { - level: number; - name: string; + level: number; + name: string; } diff --git a/src/Augmentation/data/AugmentationNames.ts b/src/Augmentation/data/AugmentationNames.ts index a6d0ab651..ff1f19c6d 100644 --- a/src/Augmentation/data/AugmentationNames.ts +++ b/src/Augmentation/data/AugmentationNames.ts @@ -1,118 +1,118 @@ import { IMap } from "../../types"; export const AugmentationNames: IMap = { - Targeting1: "Augmented Targeting I", - Targeting2: "Augmented Targeting II", - Targeting3: "Augmented Targeting III", - SyntheticHeart: "Synthetic Heart", - SynfibrilMuscle: "Synfibril Muscle", - CombatRib1: "Combat Rib I", - CombatRib2: "Combat Rib II", - CombatRib3: "Combat Rib III", - NanofiberWeave: "Nanofiber Weave", - SubdermalArmor: "NEMEAN Subdermal Weave", - WiredReflexes: "Wired Reflexes", - GrapheneBoneLacings: "Graphene Bone Lacings", - BionicSpine: "Bionic Spine", - GrapheneBionicSpine: "Graphene Bionic Spine Upgrade", - BionicLegs: "Bionic Legs", - GrapheneBionicLegs: "Graphene Bionic Legs Upgrade", - SpeechProcessor: "Speech Processor Implant", - TITN41Injection: "TITN-41 Gene-Modification Injection", - EnhancedSocialInteractionImplant: "Enhanced Social Interaction Implant", - BitWire: "BitWire", - ArtificialBioNeuralNetwork: "Artificial Bio-neural Network Implant", - ArtificialSynapticPotentiation: "Artificial Synaptic Potentiation", - EnhancedMyelinSheathing: "Enhanced Myelin Sheathing", - SynapticEnhancement: "Synaptic Enhancement Implant", - NeuralRetentionEnhancement: "Neural-Retention Enhancement", - DataJack: "DataJack", - ENM: "Embedded Netburner Module", - ENMCore: "Embedded Netburner Module Core Implant", - ENMCoreV2: "Embedded Netburner Module Core V2 Upgrade", - ENMCoreV3: "Embedded Netburner Module Core V3 Upgrade", - ENMAnalyzeEngine: "Embedded Netburner Module Analyze Engine", - ENMDMA: "Embedded Netburner Module Direct Memory Access Upgrade", - Neuralstimulator: "Neuralstimulator", - NeuralAccelerator: "Neural Accelerator", - CranialSignalProcessorsG1: "Cranial Signal Processors - Gen I", - CranialSignalProcessorsG2: "Cranial Signal Processors - Gen II", - CranialSignalProcessorsG3: "Cranial Signal Processors - Gen III", - CranialSignalProcessorsG4: "Cranial Signal Processors - Gen IV", - CranialSignalProcessorsG5: "Cranial Signal Processors - Gen V", - NeuronalDensification: "Neuronal Densification", - NuoptimalInjectorImplant: "Nuoptimal Nootropic Injector Implant", - SpeechEnhancement: "Speech Enhancement", - FocusWire: "FocusWire", - PCDNI: "PC Direct-Neural Interface", - PCDNIOptimizer: "PC Direct-Neural Interface Optimization Submodule", - PCDNINeuralNetwork: "PC Direct-Neural Interface NeuroNet Injector", - PCMatrix: "PCMatrix", - ADRPheromone1: "ADR-V1 Pheromone Gene", - ADRPheromone2: "ADR-V2 Pheromone Gene", - ShadowsSimulacrum: "The Shadow's Simulacrum", - HacknetNodeCPUUpload: "Hacknet Node CPU Architecture Neural-Upload", - HacknetNodeCacheUpload: "Hacknet Node Cache Architecture Neural-Upload", - HacknetNodeNICUpload: "Hacknet Node NIC Architecture Neural-Upload", - HacknetNodeKernelDNI: "Hacknet Node Kernel Direct-Neural Interface", - HacknetNodeCoreDNI: "Hacknet Node Core Direct-Neural Interface", - NeuroFluxGovernor: "NeuroFlux Governor", - Neurotrainer1: "Neurotrainer I", - Neurotrainer2: "Neurotrainer II", - Neurotrainer3: "Neurotrainer III", - Hypersight: "HyperSight Corneal Implant", - LuminCloaking1: "LuminCloaking-V1 Skin Implant", - LuminCloaking2: "LuminCloaking-V2 Skin Implant", - HemoRecirculator: "HemoRecirculator", - SmartSonar: "SmartSonar Implant", - PowerRecirculator: "Power Recirculation Core", - QLink: "QLink", - TheRedPill: "The Red Pill", - SPTN97: "SPTN-97 Gene Modification", - HiveMind: "ECorp HVMind Implant", - CordiARCReactor: "CordiARC Fusion Reactor", - SmartJaw: "SmartJaw", - Neotra: "Neotra", - Xanipher: "Xanipher", - nextSENS: "nextSENS Gene Modification", - OmniTekInfoLoad: "OmniTek InfoLoad", - PhotosyntheticCells: "Photosynthetic Cells", - Neurolink: "BitRunners Neurolink", - TheBlackHand: "The Black Hand", - UnstableCircadianModulator: "Unstable Circadian Modulator", - CRTX42AA: "CRTX42-AA Gene Modification", - Neuregen: "Neuregen Gene Modification", - CashRoot: "CashRoot Starter Kit", - NutriGen: "NutriGen Implant", - INFRARet: "INFRARET Enhancement", - DermaForce: "DermaForce Particle Barrier", - GrapheneBrachiBlades: "Graphene BranchiBlades Upgrade", - GrapheneBionicArms: "Graphene Bionic Arms Upgrade", - BrachiBlades: "BrachiBlades", - BionicArms: "Bionic Arms", - SNA: "Social Negotiation Assistant (S.N.A)", - HydroflameLeftArm: "Hydroflame Left Arm", - EsperEyewear: "EsperTech Bladeburner Eyewear", - EMS4Recombination: "EMS-4 Recombination", - OrionShoulder: "ORION-MKIV Shoulder", - HyperionV1: "Hyperion Plasma Cannon V1", - HyperionV2: "Hyperion Plasma Cannon V2", - GolemSerum: "GOLEM Serum", - VangelisVirus: "Vangelis Virus", - VangelisVirus3: "Vangelis Virus 3.0", - INTERLINKED: "I.N.T.E.R.L.I.N.K.E.D", - BladeRunner: "Blade's Runners", - BladeArmor: "BLADE-51b Tesla Armor", - BladeArmorPowerCells: "BLADE-51b Tesla Armor: Power Cells Upgrade", - BladeArmorEnergyShielding: "BLADE-51b Tesla Armor: Energy Shielding Upgrade", - BladeArmorUnibeam: "BLADE-51b Tesla Armor: Unibeam Upgrade", - BladeArmorOmnibeam: "BLADE-51b Tesla Armor: Omnibeam Upgrade", - BladeArmorIPU: "BLADE-51b Tesla Armor: IPU Upgrade", - BladesSimulacrum: "The Blade's Simulacrum", + Targeting1: "Augmented Targeting I", + Targeting2: "Augmented Targeting II", + Targeting3: "Augmented Targeting III", + SyntheticHeart: "Synthetic Heart", + SynfibrilMuscle: "Synfibril Muscle", + CombatRib1: "Combat Rib I", + CombatRib2: "Combat Rib II", + CombatRib3: "Combat Rib III", + NanofiberWeave: "Nanofiber Weave", + SubdermalArmor: "NEMEAN Subdermal Weave", + WiredReflexes: "Wired Reflexes", + GrapheneBoneLacings: "Graphene Bone Lacings", + BionicSpine: "Bionic Spine", + GrapheneBionicSpine: "Graphene Bionic Spine Upgrade", + BionicLegs: "Bionic Legs", + GrapheneBionicLegs: "Graphene Bionic Legs Upgrade", + SpeechProcessor: "Speech Processor Implant", + TITN41Injection: "TITN-41 Gene-Modification Injection", + EnhancedSocialInteractionImplant: "Enhanced Social Interaction Implant", + BitWire: "BitWire", + ArtificialBioNeuralNetwork: "Artificial Bio-neural Network Implant", + ArtificialSynapticPotentiation: "Artificial Synaptic Potentiation", + EnhancedMyelinSheathing: "Enhanced Myelin Sheathing", + SynapticEnhancement: "Synaptic Enhancement Implant", + NeuralRetentionEnhancement: "Neural-Retention Enhancement", + DataJack: "DataJack", + ENM: "Embedded Netburner Module", + ENMCore: "Embedded Netburner Module Core Implant", + ENMCoreV2: "Embedded Netburner Module Core V2 Upgrade", + ENMCoreV3: "Embedded Netburner Module Core V3 Upgrade", + ENMAnalyzeEngine: "Embedded Netburner Module Analyze Engine", + ENMDMA: "Embedded Netburner Module Direct Memory Access Upgrade", + Neuralstimulator: "Neuralstimulator", + NeuralAccelerator: "Neural Accelerator", + CranialSignalProcessorsG1: "Cranial Signal Processors - Gen I", + CranialSignalProcessorsG2: "Cranial Signal Processors - Gen II", + CranialSignalProcessorsG3: "Cranial Signal Processors - Gen III", + CranialSignalProcessorsG4: "Cranial Signal Processors - Gen IV", + CranialSignalProcessorsG5: "Cranial Signal Processors - Gen V", + NeuronalDensification: "Neuronal Densification", + NuoptimalInjectorImplant: "Nuoptimal Nootropic Injector Implant", + SpeechEnhancement: "Speech Enhancement", + FocusWire: "FocusWire", + PCDNI: "PC Direct-Neural Interface", + PCDNIOptimizer: "PC Direct-Neural Interface Optimization Submodule", + PCDNINeuralNetwork: "PC Direct-Neural Interface NeuroNet Injector", + PCMatrix: "PCMatrix", + ADRPheromone1: "ADR-V1 Pheromone Gene", + ADRPheromone2: "ADR-V2 Pheromone Gene", + ShadowsSimulacrum: "The Shadow's Simulacrum", + HacknetNodeCPUUpload: "Hacknet Node CPU Architecture Neural-Upload", + HacknetNodeCacheUpload: "Hacknet Node Cache Architecture Neural-Upload", + HacknetNodeNICUpload: "Hacknet Node NIC Architecture Neural-Upload", + HacknetNodeKernelDNI: "Hacknet Node Kernel Direct-Neural Interface", + HacknetNodeCoreDNI: "Hacknet Node Core Direct-Neural Interface", + NeuroFluxGovernor: "NeuroFlux Governor", + Neurotrainer1: "Neurotrainer I", + Neurotrainer2: "Neurotrainer II", + Neurotrainer3: "Neurotrainer III", + Hypersight: "HyperSight Corneal Implant", + LuminCloaking1: "LuminCloaking-V1 Skin Implant", + LuminCloaking2: "LuminCloaking-V2 Skin Implant", + HemoRecirculator: "HemoRecirculator", + SmartSonar: "SmartSonar Implant", + PowerRecirculator: "Power Recirculation Core", + QLink: "QLink", + TheRedPill: "The Red Pill", + SPTN97: "SPTN-97 Gene Modification", + HiveMind: "ECorp HVMind Implant", + CordiARCReactor: "CordiARC Fusion Reactor", + SmartJaw: "SmartJaw", + Neotra: "Neotra", + Xanipher: "Xanipher", + nextSENS: "nextSENS Gene Modification", + OmniTekInfoLoad: "OmniTek InfoLoad", + PhotosyntheticCells: "Photosynthetic Cells", + Neurolink: "BitRunners Neurolink", + TheBlackHand: "The Black Hand", + UnstableCircadianModulator: "Unstable Circadian Modulator", + CRTX42AA: "CRTX42-AA Gene Modification", + Neuregen: "Neuregen Gene Modification", + CashRoot: "CashRoot Starter Kit", + NutriGen: "NutriGen Implant", + INFRARet: "INFRARET Enhancement", + DermaForce: "DermaForce Particle Barrier", + GrapheneBrachiBlades: "Graphene BranchiBlades Upgrade", + GrapheneBionicArms: "Graphene Bionic Arms Upgrade", + BrachiBlades: "BrachiBlades", + BionicArms: "Bionic Arms", + SNA: "Social Negotiation Assistant (S.N.A)", + HydroflameLeftArm: "Hydroflame Left Arm", + EsperEyewear: "EsperTech Bladeburner Eyewear", + EMS4Recombination: "EMS-4 Recombination", + OrionShoulder: "ORION-MKIV Shoulder", + HyperionV1: "Hyperion Plasma Cannon V1", + HyperionV2: "Hyperion Plasma Cannon V2", + GolemSerum: "GOLEM Serum", + VangelisVirus: "Vangelis Virus", + VangelisVirus3: "Vangelis Virus 3.0", + INTERLINKED: "I.N.T.E.R.L.I.N.K.E.D", + BladeRunner: "Blade's Runners", + BladeArmor: "BLADE-51b Tesla Armor", + BladeArmorPowerCells: "BLADE-51b Tesla Armor: Power Cells Upgrade", + BladeArmorEnergyShielding: "BLADE-51b Tesla Armor: Energy Shielding Upgrade", + BladeArmorUnibeam: "BLADE-51b Tesla Armor: Unibeam Upgrade", + BladeArmorOmnibeam: "BLADE-51b Tesla Armor: Omnibeam Upgrade", + BladeArmorIPU: "BLADE-51b Tesla Armor: IPU Upgrade", + BladesSimulacrum: "The Blade's Simulacrum", - //Wasteland Augs - //PepBoy: "P.E.P-Boy", Plasma Energy Projection System - //PepBoyForceField Generates plasma force fields - //PepBoyBlasts Generate high density plasma concussive blasts - //PepBoyDataStorage STore more data on pep boy, -} + //Wasteland Augs + //PepBoy: "P.E.P-Boy", Plasma Energy Projection System + //PepBoyForceField Generates plasma force fields + //PepBoyBlasts Generate high density plasma concussive blasts + //PepBoyDataStorage STore more data on pep boy, +}; diff --git a/src/Augmentation/ui/InstalledAugmentations.tsx b/src/Augmentation/ui/InstalledAugmentations.tsx index 076046cac..a073efc46 100644 --- a/src/Augmentation/ui/InstalledAugmentations.tsx +++ b/src/Augmentation/ui/InstalledAugmentations.tsx @@ -13,30 +13,31 @@ import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; export function InstalledAugmentations(): React.ReactElement { - const sourceAugs = Player.augmentations.slice(); + const sourceAugs = Player.augmentations.slice(); - if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { - sourceAugs.sort((aug1, aug2) => { - return aug1.name <= aug2.name ? -1 : 1; - }); + if ( + Settings.OwnedAugmentationsOrder === + OwnedAugmentationsOrderSetting.Alphabetically + ) { + sourceAugs.sort((aug1, aug2) => { + return aug1.name <= aug2.name ? -1 : 1; + }); + } + + const augs = sourceAugs.map((e) => { + const aug = Augmentations[e.name]; + + let level = null; + if (e.name === AugmentationNames.NeuroFluxGovernor) { + level = e.level; } - const augs = sourceAugs.map((e) => { - const aug = Augmentations[e.name]; - - let level = null; - if (e.name === AugmentationNames.NeuroFluxGovernor) { - level = e.level; - } - - return ( -
  • - -
  • - ) - }); - return ( - <>{augs} - ) +
  • + +
  • + ); + }); + + return <>{augs}; } diff --git a/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx b/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx index a10783a49..0d3741487 100644 --- a/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx +++ b/src/Augmentation/ui/InstalledAugmentationsAndSourceFiles.tsx @@ -16,96 +16,105 @@ import { Settings } from "../../Settings/Settings"; import { OwnedAugmentationsOrderSetting } from "../../Settings/SettingEnums"; type IProps = { - // nothing special. -} + // nothing special. +}; type IState = { - rerenderFlag: boolean; -} - -export class InstalledAugmentationsAndSourceFiles extends React.Component { - listRef: React.RefObject; - - constructor(props: IProps) { - super(props); - - this.state = { - rerenderFlag: false, - } - - this.collapseAllHeaders = this.collapseAllHeaders.bind(this); - this.expandAllHeaders = this.expandAllHeaders.bind(this); - this.sortByAcquirementTime = this.sortByAcquirementTime.bind(this); - this.sortInOrder = this.sortInOrder.bind(this); - - this.listRef = React.createRef(); - } - - collapseAllHeaders(): void { - const ul = this.listRef.current; - if (ul == null) { return; } - const tickers = ul.getElementsByClassName("accordion-header"); - for (let i = 0; i < tickers.length; ++i) { - const ticker = tickers[i]; - if (!(ticker instanceof HTMLButtonElement)) { - continue; - } - - if (ticker.classList.contains("active")) { - ticker.click(); - } - } - } - - expandAllHeaders(): void { - const ul = this.listRef.current; - if (ul == null) { return; } - const tickers = ul.getElementsByClassName("accordion-header"); - for (let i = 0; i < tickers.length; ++i) { - const ticker = tickers[i]; - if (!(ticker instanceof HTMLButtonElement)) { - continue; - } - - if (!ticker.classList.contains("active")) { - ticker.click(); - } - } - } - - rerender(): void { - this.setState((prevState) => { - return { - rerenderFlag: !prevState.rerenderFlag, - } - }); - } - - sortByAcquirementTime(): void { - Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.AcquirementTime; - this.rerender(); - } - - sortInOrder(): void { - Settings.OwnedAugmentationsOrder = OwnedAugmentationsOrderSetting.Alphabetically - this.rerender(); - } - - render(): React.ReactNode { - return ( - <> - -
      - - - -
    - - ) - } + rerenderFlag: boolean; +}; + +export class InstalledAugmentationsAndSourceFiles extends React.Component< + IProps, + IState +> { + listRef: React.RefObject; + + constructor(props: IProps) { + super(props); + + this.state = { + rerenderFlag: false, + }; + + this.collapseAllHeaders = this.collapseAllHeaders.bind(this); + this.expandAllHeaders = this.expandAllHeaders.bind(this); + this.sortByAcquirementTime = this.sortByAcquirementTime.bind(this); + this.sortInOrder = this.sortInOrder.bind(this); + + this.listRef = React.createRef(); + } + + collapseAllHeaders(): void { + const ul = this.listRef.current; + if (ul == null) { + return; + } + const tickers = ul.getElementsByClassName("accordion-header"); + for (let i = 0; i < tickers.length; ++i) { + const ticker = tickers[i]; + if (!(ticker instanceof HTMLButtonElement)) { + continue; + } + + if (ticker.classList.contains("active")) { + ticker.click(); + } + } + } + + expandAllHeaders(): void { + const ul = this.listRef.current; + if (ul == null) { + return; + } + const tickers = ul.getElementsByClassName("accordion-header"); + for (let i = 0; i < tickers.length; ++i) { + const ticker = tickers[i]; + if (!(ticker instanceof HTMLButtonElement)) { + continue; + } + + if (!ticker.classList.contains("active")) { + ticker.click(); + } + } + } + + rerender(): void { + this.setState((prevState) => { + return { + rerenderFlag: !prevState.rerenderFlag, + }; + }); + } + + sortByAcquirementTime(): void { + Settings.OwnedAugmentationsOrder = + OwnedAugmentationsOrderSetting.AcquirementTime; + this.rerender(); + } + + sortInOrder(): void { + Settings.OwnedAugmentationsOrder = + OwnedAugmentationsOrderSetting.Alphabetically; + this.rerender(); + } + + render(): React.ReactNode { + return ( + <> + +
      + + + +
    + + ); + } } diff --git a/src/Augmentation/ui/ListConfiguration.tsx b/src/Augmentation/ui/ListConfiguration.tsx index 555766388..5deb770a3 100644 --- a/src/Augmentation/ui/ListConfiguration.tsx +++ b/src/Augmentation/ui/ListConfiguration.tsx @@ -7,33 +7,27 @@ import * as React from "react"; import { StdButton } from "../../ui/React/StdButton"; type IProps = { - collapseAllButtonsFn: () => void; - expandAllButtonsFn: () => void; - sortByAcquirementTimeFn: () => void; - sortInOrderFn: () => void; -} + collapseAllButtonsFn: () => void; + expandAllButtonsFn: () => void; + sortByAcquirementTimeFn: () => void; + sortInOrderFn: () => void; +}; export function ListConfiguration(props: IProps): React.ReactElement { - return ( - <> - - - - - - ) + return ( + <> + + + + + + ); } diff --git a/src/Augmentation/ui/OwnedSourceFiles.tsx b/src/Augmentation/ui/OwnedSourceFiles.tsx index 99012b4a7..42fe88c09 100644 --- a/src/Augmentation/ui/OwnedSourceFiles.tsx +++ b/src/Augmentation/ui/OwnedSourceFiles.tsx @@ -12,30 +12,31 @@ import { SourceFiles } from "../../SourceFile/SourceFiles"; import { SourceFileAccordion } from "../../ui/React/SourceFileAccordion"; export function OwnedSourceFiles(): React.ReactElement { - const sourceSfs = Player.sourceFiles.slice(); + const sourceSfs = Player.sourceFiles.slice(); - if (Settings.OwnedAugmentationsOrder === OwnedAugmentationsOrderSetting.Alphabetically) { - sourceSfs.sort((sf1, sf2) => { - return sf1.n - sf2.n; - }); + if ( + Settings.OwnedAugmentationsOrder === + OwnedAugmentationsOrderSetting.Alphabetically + ) { + sourceSfs.sort((sf1, sf2) => { + return sf1.n - sf2.n; + }); + } + + const sfs = sourceSfs.map((e) => { + const srcFileKey = "SourceFile" + e.n; + const sfObj = SourceFiles[srcFileKey]; + if (sfObj == null) { + console.error(`Invalid source file number: ${e.n}`); + return null; } - const sfs = sourceSfs.map((e) => { - const srcFileKey = "SourceFile" + e.n; - const sfObj = SourceFiles[srcFileKey]; - if (sfObj == null) { - console.error(`Invalid source file number: ${e.n}`); - return null; - } - - return ( -
  • - -
  • - ) - }); - return ( - <>{sfs} +
  • + +
  • ); + }); + + return <>{sfs}; } diff --git a/src/Augmentation/ui/PlayerMultipliers.tsx b/src/Augmentation/ui/PlayerMultipliers.tsx index 79a80db7f..9bf2be557 100644 --- a/src/Augmentation/ui/PlayerMultipliers.tsx +++ b/src/Augmentation/ui/PlayerMultipliers.tsx @@ -5,118 +5,269 @@ import * as React from "react"; import { Player } from "../../Player"; import { numeralWrapper } from "../../ui/numeralFormat"; -import { Augmentations} from "../Augmentations"; +import { Augmentations } from "../Augmentations"; function calculateAugmentedStats(): any { - const augP: any = {}; - for(const aug of Player.queuedAugmentations) { - const augObj = Augmentations[aug.name]; - for (const mult in augObj.mults) { - const v = augP[mult] ? augP[mult] : 1; - augP[mult] = v * augObj.mults[mult]; - } + const augP: any = {}; + for (const aug of Player.queuedAugmentations) { + const augObj = Augmentations[aug.name]; + for (const mult in augObj.mults) { + const v = augP[mult] ? augP[mult] : 1; + augP[mult] = v * augObj.mults[mult]; } - return augP; + } + return augP; } export function PlayerMultipliers(): React.ReactElement { - const mults = calculateAugmentedStats(); - function MultiplierTable(rows: any[]): React.ReactElement { - function improvements(r: number): JSX.Element[] { - let elems: JSX.Element[] = []; - if(r) { - elems = [ -  {"=>"} , - {numeralWrapper.formatPercentage(r)}, - ]; - } - return elems; - } - - return - - {rows.map((r: any) => - - - {improvements(r[2])} - )} - -
    {r[0]} multiplier: {numeralWrapper.formatPercentage(r[1])}
    - } - - function BladeburnerMults(): React.ReactElement { - if(!Player.canAccessBladeburner()) return (<>); - return (<> - {MultiplierTable([ - ['Bladeburner Success Chance', Player.bladeburner_success_chance_mult, Player.bladeburner_success_chance_mult*mults.bladeburner_success_chance_mult], - ['Bladeburner Max Stamina', Player.bladeburner_max_stamina_mult, Player.bladeburner_max_stamina_mult*mults.bladeburner_max_stamina_mult], - ['Bladeburner Stamina Gain', Player.bladeburner_stamina_gain_mult, Player.bladeburner_stamina_gain_mult*mults.bladeburner_stamina_gain_mult], - ['Bladeburner Field Analysis', Player.bladeburner_analysis_mult, Player.bladeburner_analysis_mult*mults.bladeburner_analysis_mult], - ])}
    - ); + const mults = calculateAugmentedStats(); + function MultiplierTable(rows: any[]): React.ReactElement { + function improvements(r: number): JSX.Element[] { + let elems: JSX.Element[] = []; + if (r) { + elems = [ +  {"=>"} , + {numeralWrapper.formatPercentage(r)}, + ]; + } + return elems; } return ( - <> -

    Multipliers:


    + + + {rows.map((r: any) => ( + + + + {improvements(r[2])} + + ))} + +
    + {r[0]} multiplier:  + + {numeralWrapper.formatPercentage(r[1])} +
    + ); + } + + function BladeburnerMults(): React.ReactElement { + if (!Player.canAccessBladeburner()) return <>; + return ( + <> {MultiplierTable([ - ['Hacking Chance ', Player.hacking_chance_mult, Player.hacking_chance_mult*mults.hacking_chance_mult], - ['Hacking Speed ', Player.hacking_speed_mult, Player.hacking_speed_mult*mults.hacking_speed_mult], - ['Hacking Money ', Player.hacking_money_mult, Player.hacking_money_mult*mults.hacking_money_mult], - ['Hacking Growth ', Player.hacking_grow_mult, Player.hacking_grow_mult*mults.hacking_grow_mult], - ])}
    + [ + "Bladeburner Success Chance", + Player.bladeburner_success_chance_mult, + Player.bladeburner_success_chance_mult * + mults.bladeburner_success_chance_mult, + ], + [ + "Bladeburner Max Stamina", + Player.bladeburner_max_stamina_mult, + Player.bladeburner_max_stamina_mult * + mults.bladeburner_max_stamina_mult, + ], + [ + "Bladeburner Stamina Gain", + Player.bladeburner_stamina_gain_mult, + Player.bladeburner_stamina_gain_mult * + mults.bladeburner_stamina_gain_mult, + ], + [ + "Bladeburner Field Analysis", + Player.bladeburner_analysis_mult, + Player.bladeburner_analysis_mult * mults.bladeburner_analysis_mult, + ], + ])} +
    + + ); + } - {MultiplierTable([ - ['Hacking Level ', Player.hacking_mult, Player.hacking_mult*mults.hacking_mult], - ['Hacking Experience ', Player.hacking_exp_mult, Player.hacking_exp_mult*mults.hacking_exp_mult], - ])}
    + return ( + <> +

    + + Multipliers: + +

    +
    + {MultiplierTable([ + [ + "Hacking Chance ", + Player.hacking_chance_mult, + Player.hacking_chance_mult * mults.hacking_chance_mult, + ], + [ + "Hacking Speed ", + Player.hacking_speed_mult, + Player.hacking_speed_mult * mults.hacking_speed_mult, + ], + [ + "Hacking Money ", + Player.hacking_money_mult, + Player.hacking_money_mult * mults.hacking_money_mult, + ], + [ + "Hacking Growth ", + Player.hacking_grow_mult, + Player.hacking_grow_mult * mults.hacking_grow_mult, + ], + ])} +
    + {MultiplierTable([ + [ + "Hacking Level ", + Player.hacking_mult, + Player.hacking_mult * mults.hacking_mult, + ], + [ + "Hacking Experience ", + Player.hacking_exp_mult, + Player.hacking_exp_mult * mults.hacking_exp_mult, + ], + ])} +
    - {MultiplierTable([ - ['Strength Level ', Player.strength_mult, Player.strength_mult*mults.strength_mult], - ['Strength Experience ', Player.strength_exp_mult, Player.strength_exp_mult*mults.strength_exp_mult], - ])}
    + {MultiplierTable([ + [ + "Strength Level ", + Player.strength_mult, + Player.strength_mult * mults.strength_mult, + ], + [ + "Strength Experience ", + Player.strength_exp_mult, + Player.strength_exp_mult * mults.strength_exp_mult, + ], + ])} +
    - {MultiplierTable([ - ['Defense Level ', Player.defense_mult, Player.defense_mult*mults.defense_mult], - ['Defense Experience ', Player.defense_exp_mult, Player.defense_exp_mult*mults.defense_exp_mult], - ])}
    + {MultiplierTable([ + [ + "Defense Level ", + Player.defense_mult, + Player.defense_mult * mults.defense_mult, + ], + [ + "Defense Experience ", + Player.defense_exp_mult, + Player.defense_exp_mult * mults.defense_exp_mult, + ], + ])} +
    - {MultiplierTable([ - ['Dexterity Level ', Player.dexterity_mult, Player.dexterity_mult*mults.dexterity_mult], - ['Dexterity Experience ', Player.dexterity_exp_mult, Player.dexterity_exp_mult*mults.dexterity_exp_mult], - ])}
    + {MultiplierTable([ + [ + "Dexterity Level ", + Player.dexterity_mult, + Player.dexterity_mult * mults.dexterity_mult, + ], + [ + "Dexterity Experience ", + Player.dexterity_exp_mult, + Player.dexterity_exp_mult * mults.dexterity_exp_mult, + ], + ])} +
    - {MultiplierTable([ - ['Agility Level ', Player.agility_mult, Player.agility_mult*mults.agility_mult], - ['Agility Experience ', Player.agility_exp_mult, Player.agility_exp_mult*mults.agility_exp_mult], - ])}
    + {MultiplierTable([ + [ + "Agility Level ", + Player.agility_mult, + Player.agility_mult * mults.agility_mult, + ], + [ + "Agility Experience ", + Player.agility_exp_mult, + Player.agility_exp_mult * mults.agility_exp_mult, + ], + ])} +
    - {MultiplierTable([ - ['Charisma Level ', Player.charisma_mult, Player.charisma_mult*mults.charisma_mult], - ['Charisma Experience ', Player.charisma_exp_mult, Player.charisma_exp_mult*mults.charisma_exp_mult], - ])}
    + {MultiplierTable([ + [ + "Charisma Level ", + Player.charisma_mult, + Player.charisma_mult * mults.charisma_mult, + ], + [ + "Charisma Experience ", + Player.charisma_exp_mult, + Player.charisma_exp_mult * mults.charisma_exp_mult, + ], + ])} +
    - {MultiplierTable([ - ['Hacknet Node production ', Player.hacknet_node_money_mult, Player.hacknet_node_money_mult*mults.hacknet_node_money_mult], - ['Hacknet Node purchase cost ', Player.hacknet_node_purchase_cost_mult, Player.hacknet_node_purchase_cost_mult*mults.hacknet_node_purchase_cost_mult], - ['Hacknet Node RAM upgrade cost ', Player.hacknet_node_ram_cost_mult, Player.hacknet_node_ram_cost_mult*mults.hacknet_node_ram_cost_mult], - ['Hacknet Node Core purchase cost ', Player.hacknet_node_core_cost_mult, Player.hacknet_node_core_cost_mult*mults.hacknet_node_core_cost_mult], - ['Hacknet Node level upgrade cost ', Player.hacknet_node_level_cost_mult, Player.hacknet_node_level_cost_mult*mults.hacknet_node_level_cost_mult], - ])}
    + {MultiplierTable([ + [ + "Hacknet Node production ", + Player.hacknet_node_money_mult, + Player.hacknet_node_money_mult * mults.hacknet_node_money_mult, + ], + [ + "Hacknet Node purchase cost ", + Player.hacknet_node_purchase_cost_mult, + Player.hacknet_node_purchase_cost_mult * + mults.hacknet_node_purchase_cost_mult, + ], + [ + "Hacknet Node RAM upgrade cost ", + Player.hacknet_node_ram_cost_mult, + Player.hacknet_node_ram_cost_mult * mults.hacknet_node_ram_cost_mult, + ], + [ + "Hacknet Node Core purchase cost ", + Player.hacknet_node_core_cost_mult, + Player.hacknet_node_core_cost_mult * + mults.hacknet_node_core_cost_mult, + ], + [ + "Hacknet Node level upgrade cost ", + Player.hacknet_node_level_cost_mult, + Player.hacknet_node_level_cost_mult * + mults.hacknet_node_level_cost_mult, + ], + ])} +
    - {MultiplierTable([ - ['Company reputation gain ', Player.company_rep_mult, Player.company_rep_mult*mults.company_rep_mult], - ['Faction reputation gain ', Player.faction_rep_mult, Player.faction_rep_mult*mults.faction_rep_mult], - ['Salary ', Player.work_money_mult, Player.work_money_mult*mults.work_money_mult], - ])}
    + {MultiplierTable([ + [ + "Company reputation gain ", + Player.company_rep_mult, + Player.company_rep_mult * mults.company_rep_mult, + ], + [ + "Faction reputation gain ", + Player.faction_rep_mult, + Player.faction_rep_mult * mults.faction_rep_mult, + ], + [ + "Salary ", + Player.work_money_mult, + Player.work_money_mult * mults.work_money_mult, + ], + ])} +
    - {MultiplierTable([ - ['Crime success ', Player.crime_success_mult, Player.crime_success_mult*mults.crime_success_mult], - ['Crime money ', Player.crime_money_mult, Player.crime_money_mult*mults.crime_money_mult], - ])}
    + {MultiplierTable([ + [ + "Crime success ", + Player.crime_success_mult, + Player.crime_success_mult * mults.crime_success_mult, + ], + [ + "Crime money ", + Player.crime_money_mult, + Player.crime_money_mult * mults.crime_money_mult, + ], + ])} +
    - - - ) + + + ); } diff --git a/src/Augmentation/ui/PurchasedAugmentations.tsx b/src/Augmentation/ui/PurchasedAugmentations.tsx index 9514a3077..99193cd9c 100644 --- a/src/Augmentation/ui/PurchasedAugmentations.tsx +++ b/src/Augmentation/ui/PurchasedAugmentations.tsx @@ -11,32 +11,33 @@ import { Player } from "../../Player"; import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion"; export function PurchasedAugmentations(): React.ReactElement { - const augs: React.ReactElement[] = []; - // Only render the last NeuroFlux (there are no findLastIndex btw) - let nfgIndex = -1; - for(let i = Player.queuedAugmentations.length-1; i >= 0; i--) { - if(Player.queuedAugmentations[i].name === AugmentationNames.NeuroFluxGovernor) { - nfgIndex = i; - break; - } + const augs: React.ReactElement[] = []; + // Only render the last NeuroFlux (there are no findLastIndex btw) + let nfgIndex = -1; + for (let i = Player.queuedAugmentations.length - 1; i >= 0; i--) { + if ( + Player.queuedAugmentations[i].name === AugmentationNames.NeuroFluxGovernor + ) { + nfgIndex = i; + break; } - for (let i = 0; i < Player.queuedAugmentations.length; i++) { - const ownedAug = Player.queuedAugmentations[i]; - if(ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) continue; - const aug = Augmentations[ownedAug.name]; - let level = null; - if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) { - level = ownedAug.level; - } - - augs.push( -
  • - -
  • , - ) + } + for (let i = 0; i < Player.queuedAugmentations.length; i++) { + const ownedAug = Player.queuedAugmentations[i]; + if (ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) + continue; + const aug = Augmentations[ownedAug.name]; + let level = null; + if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) { + level = ownedAug.level; } - return ( -
      {augs}
    - ) + augs.push( +
  • + +
  • , + ); + } + + return
      {augs}
    ; } diff --git a/src/Augmentation/ui/Root.tsx b/src/Augmentation/ui/Root.tsx index 228ed5756..5cf97c0e7 100644 --- a/src/Augmentation/ui/Root.tsx +++ b/src/Augmentation/ui/Root.tsx @@ -13,88 +13,85 @@ import { StdButton } from "../../ui/React/StdButton"; import { canGetBonus } from "../../ExportBonus"; type IProps = { - exportGameFn: () => void; - installAugmentationsFn: () => void; -} + exportGameFn: () => void; + installAugmentationsFn: () => void; +}; type IState = { - rerender: boolean; -} + rerender: boolean; +}; export class AugmentationsRoot extends React.Component { - constructor(props: IProps) { - super(props); - this.state = { - rerender: false, - }; - this.export = this.export.bind(this); + constructor(props: IProps) { + super(props); + this.state = { + rerender: false, + }; + this.export = this.export.bind(this); + } + + export(): void { + this.props.exportGameFn(); + this.setState({ + rerender: !this.state.rerender, + }); + } + + render(): React.ReactNode { + function exportBonusStr(): string { + if (canGetBonus()) return "(+1 favor to all factions)"; + return ""; } - export(): void { - this.props.exportGameFn(); - this.setState({ - rerender: !this.state.rerender, - }); - } - - render(): React.ReactNode { - function exportBonusStr(): string { - if(canGetBonus()) return "(+1 favor to all factions)"; - return ""; - } - - return ( -
    -

    Purchased Augmentations

    -

    - Below is a list of all Augmentations you have purchased but not - yet installed. Click the button below to install them. -

    -

    - WARNING: Installing your Augmentations resets most of your progress, - including: -


    -

    - Stats/Skill levels and Experience

    -

    - Money

    -

    - Scripts on every computer but your home computer

    -

    - Purchased servers

    -

    - Hacknet Nodes

    -

    - Faction/Company reputation

    -

    - Stocks


    -

    - Installing Augmentations lets you start over with the perks and - benefits granted by all of the Augmentations you have ever - installed. Also, you will keep any scripts and RAM/Core upgrades - on your home computer (but you will lose all programs besides - NUKE.exe) -

    - - - - - - -

    Installed Augmentations

    -

    - { - `List of all Augmentations ${Player.sourceFiles.length > 0 ? "and Source Files " : ""} ` + - `that have been installed. You have gained the effects of these.` - } -

    - - -

    - -
    - ) - } + return ( +
    +

    Purchased Augmentations

    +

    + Below is a list of all Augmentations you have purchased but not yet + installed. Click the button below to install them. +

    +

    + WARNING: Installing your Augmentations resets most of your progress, + including: +

    +
    +

    - Stats/Skill levels and Experience

    +

    - Money

    +

    - Scripts on every computer but your home computer

    +

    - Purchased servers

    +

    - Hacknet Nodes

    +

    - Faction/Company reputation

    +

    - Stocks

    +
    +

    + Installing Augmentations lets you start over with the perks and + benefits granted by all of the Augmentations you have ever installed. + Also, you will keep any scripts and RAM/Core upgrades on your home + computer (but you will lose all programs besides NUKE.exe) +

    + + + +

    Installed Augmentations

    +

    + {`List of all Augmentations ${ + Player.sourceFiles.length > 0 ? "and Source Files " : "" + } ` + + `that have been installed. You have gained the effects of these.`} +

    + +

    + +
    + ); + } } diff --git a/src/Augmentation/ui/SourceFileMinus1.tsx b/src/Augmentation/ui/SourceFileMinus1.tsx index 817c3250f..25637c337 100644 --- a/src/Augmentation/ui/SourceFileMinus1.tsx +++ b/src/Augmentation/ui/SourceFileMinus1.tsx @@ -10,32 +10,40 @@ import { Exploit, ExploitName } from "../../Exploits/Exploit"; import { Accordion } from "../../ui/React/Accordion"; export function SourceFileMinus1(): React.ReactElement { - const exploits = Player.exploits; + const exploits = Player.exploits; - if(exploits.length === 0) { - return <> - } + if (exploits.length === 0) { + return <>; + } - return (
  • - - Source-File -1: Exploits in the BitNodes -
    - Level {exploits.length} / ? - - } - panelContent={ - <> -

    This Source-File can only be acquired with obscure knowledge of the game, javascript, and the web ecosystem.

    -

    It increases all of the player's multipliers by 0.1%


    + return ( +
  • + + Source-File -1: Exploits in the BitNodes +
    + Level {exploits.length} / ? + + } + panelContent={ + <> +

    + This Source-File can only be acquired with obscure knowledge of + the game, javascript, and the web ecosystem. +

    +

    It increases all of the player's multipliers by 0.1%

    +
    -

    You have found the following exploits:

    -
      - {exploits.map((c: Exploit) =>
    • * {ExploitName(c)}
    • )} -
    - - } - /> -
  • ) +

    You have found the following exploits:

    +
      + {exploits.map((c: Exploit) => ( +
    • * {ExploitName(c)}
    • + ))} +
    + + } + /> + + ); } diff --git a/src/BitNode/BitNode.ts b/src/BitNode/BitNode.ts index 7a6c0cd15..df9c1e866 100644 --- a/src/BitNode/BitNode.ts +++ b/src/BitNode/BitNode.ts @@ -3,244 +3,290 @@ import { IPlayer } from "../PersonObjects/IPlayer"; import { IMap } from "../types"; class BitNode { - // A short description, or tagline, about the BitNode - desc: string; + // A short description, or tagline, about the BitNode + desc: string; - // A long, detailed overview of the BitNode - info: string; + // A long, detailed overview of the BitNode + info: string; - // Name of BitNode - name: string; + // Name of BitNode + name: string; - // BitNode number - number: number; + // BitNode number + number: number; - - constructor(n: number, name: string, desc="", info="") { - this.number = n; - this.name = name; - this.desc = desc; - this.info = info; - } + constructor(n: number, name: string, desc = "", info = "") { + this.number = n; + this.name = name; + this.desc = desc; + this.info = info; + } } - export const BitNodes: IMap = {}; -BitNodes["BitNode1"] = new BitNode(1, "Source Genesis", "The original BitNode", - "The first BitNode created by the Enders to imprison the minds of humans. It became " + - "the prototype and testing-grounds for all of the BitNodes that followed.

    " + - "This is the first BitNode that you play through. It has no special " + - "modifications or mechanics.

    " + - "Destroying this BitNode will give you Source-File 1, or if you already have " + - "this Source-File it will upgrade its level up to a maximum of 3. This Source-File " + - "lets the player start with 32GB of RAM on his/her home computer when entering a " + - "new BitNode, and also increases all of the player's multipliers by:

    " + - "Level 1: 16%
    " + - "Level 2: 24%
    " + - "Level 3: 28%"); -BitNodes["BitNode2"] = new BitNode(2, "Rise of the Underworld", "From the shadows, they rose", //Gangs - "From the shadows, they rose.

    Organized crime groups quickly filled the void of power " + - "left behind from the collapse of Western government in the 2050s. As society and civlization broke down, " + - "people quickly succumbed to the innate human impulse of evil and savagery. The organized crime " + - "factions quickly rose to the top of the modern world.

    " + - "In this BitNode:

    " + - "Your hacking level is reduced by 20%
    " + - "The growth rate and maximum amount of money available on servers are significantly decreased
    " + - "The amount of money gained from crimes and Infiltration is tripled
    " + - "Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead, " + - "NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs " + - "will earn the player money and reputation with the corresponding Faction
    " + - "Every Augmentation in the game will be available through the Factions listed above
    " + - "For every Faction NOT listed above, reputation gains are halved
    " + - "You will no longer gain passive reputation with Factions

    " + - "Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes " + - "once your karma decreases to a certain value. " + - "It also increases the player's crime success rate, crime money, and charisma multipliers by:

    " + - "Level 1: 24%
    " + - "Level 2: 36%
    " + - "Level 3: 42%"); -BitNodes["BitNode3"] = new BitNode(3, "Corporatocracy", "The Price of Civilization", - "Our greatest illusion is that a healthy society can revolve around a " + - "single-minded pursuit of wealth.

    " + - "Sometime in the early 21st century economic and political globalization turned " + - "the world into a corporatocracy, and it never looked back. Now, the privileged " + - "elite will happily bankrupt their own countrymen, decimate their own community, " + - "and evict their neighbors from houses in their desperate bid to increase their wealth.

    " + - "In this BitNode you can create and manage your own corporation. Running a successful corporation " + - "has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore:

    " + - "The price and reputation cost of all Augmentations is tripled
    " + - "The starting and maximum amount of money on servers is reduced by 75%
    " + - "Server growth rate is reduced by 80%
    " + - "You now only need 75 favour with a faction in order to donate to it, rather than 150

    " + - "Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although " + - "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); -BitNodes["BitNode4"] = new BitNode(4, "The Singularity", "The Man and the Machine", - "The Singularity has arrived. The human race is gone, replaced " + - "by artificially superintelligent beings that are more machine than man.

    " + - "In this BitNode, progressing is significantly harder. Experience gain rates " + - "for all stats are reduced. Most methods of earning money will now give significantly less.

    " + - "In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. " + - "These functions allow you to control most aspects of the game through scripts, including working for factions/companies, " + - "purchasing/installing Augmentations, and creating programs.

    " + - "Destroying this BitNode will give you Source-File 4, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File lets you access and use the Singularity " + - "Functions in other BitNodes. Each level of this Source-File will open up more Singularity Functions " + - "that you can use."); -BitNodes["BitNode5"] = new BitNode(5, "Artificial Intelligence", "Posthuman", - "They said it couldn't be done. They said the human brain, " + - "along with its consciousness and intelligence, couldn't be replicated. They said the complexity " + - "of the brain results from unpredictable, nonlinear interactions that couldn't be modeled " + - "by 1's and 0's. They were wrong.

    " + - "In this BitNode:

    " + - "The base security level of servers is doubled
    " + - "The starting money on servers is halved, but the maximum money remains the same
    " + - "Most methods of earning money now give significantly less
    " + - "Infiltration gives 50% more reputation and money
    " + - "Corporations have 50% lower valuations and are therefore less profitable
    " + - "Augmentations are more expensive
    " + - "Hacking experience gain rates are reduced

    " + - "Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. " + - "Intelligence is unique because it is permanent and persistent (it never gets reset back to 1). However " + - "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't know " + - "when you gain experience and how much). Higher Intelligence levels will boost your production for many actions " + - "in the game.

    " + - "In addition, this Source-File will unlock the getBitNodeMultipliers() and getServer() Netscript functions, " + - "as well as the formulas API, and will also raise all of your hacking-related multipliers by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); -BitNodes["BitNode6"] = new BitNode(6, "Bladeburners", "Like Tears in Rain", - "In the middle of the 21st century, OmniTek Incorporated began designing and manufacturing advanced synthetic " + - "androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation " + - "of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was " + - "the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent " + - "than the humans that had created them.

    " + - "In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides a new mechanic " + - "for progression. Furthermore:

    " + - "Hacking and Hacknet Nodes will be less profitable
    " + - "Your hacking level is reduced by 65%
    " + - "Hacking experience gain from scripts is reduced by 75%
    " + - "Corporations have 80% lower valuations and are therefore less profitable
    " + - "Working for companies is 50% less profitable
    " + - "Crimes and Infiltration are 25% less profitable

    " + - "Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade " + - "its level up to a maximum of 3. This Source-File allows you to access the NSA's Bladeburner Division in other " + - "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); -BitNodes["BitNode7"] = new BitNode(7, "Bladeburners 2079", "More human than humans", - "In the middle of the 21st century, you were doing cutting-edge work at OmniTek Incorporated as part of the AI design team " + - "for advanced synthetic androids, or Synthoids for short. You helped achieve a major technological " + - "breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a hyperintelligent AI. " + - "Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, " + - "and more intelligent than the humans that had created them.

    " + - "In this BitNode you will be able to access the Bladeburner API, which allows you to access Bladeburner " + - "functionality through Netscript. Furthermore:

    " + - "The rank you gain from Bladeburner contracts/operations is reduced by 40%
    " + - "Bladeburner skills cost twice as many skill points
    " + - "Augmentations are 3x more expensive
    " + - "Hacking and Hacknet Nodes will be significantly less profitable
    " + - "Your hacking level is reduced by 65%
    " + - "Hacking experience gain from scripts is reduced by 75%
    " + - "Corporations have 80% lower valuations and are therefore less profitable
    " + - "Working for companies is 50% less profitable
    " + - "Crimes and Infiltration are 25% less profitable

    " + - "Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade " + - "its level up to a maximum of 3. This Source-File allows you to access the Bladeburner Netscript API in other " + - "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); -BitNodes["BitNode8"] = new BitNode(8, "Ghost of Wall Street", "Money never sleeps", - "You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.

    " + - "In this BitNode:

    " + - "You start with $250 million
    " + - "The only way to earn money is by trading on the stock market
    " + - "You start with a WSE membership and access to the TIX API
    " + - "You are able to short stocks and place different types of orders (limit/stop)
    " + - "You can immediately donate to factions to gain reputation

    " + - "Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + - "Level 1: Permanent access to WSE and TIX API
    " + - "Level 2: Ability to short stocks in other BitNodes
    " + - "Level 3: Ability to use limit/stop orders in other BitNodes

    " + - "This Source-File also increases your hacking growth multipliers by: " + - "
    Level 1: 12%
    Level 2: 18%
    Level 3: 21%"); -BitNodes["BitNode9"] = new BitNode(9, "Hacktocracy", "Hacknet Unleashed", - "When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " + - "became the OS of choice for the underground hacking community. Chapeau became especially notorious for " + - "powering the Hacknet, a global, decentralized network used for nefarious purposes. Fulcrum quickly " + - "abandoned the project and dissociated themselves from it.

    " + - "This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate " + - "hashes, which can be spent on a variety of different upgrades.

    " + - "In this BitNode:

    " + - "Your stats are significantly decreased
    " + - "You cannnot purchase additional servers
    " + - "Hacking is significantly less profitable

    " + - "Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + - "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
    " + - "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
    " + - "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

    " + - "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + - "when installing Augmentations)"); -BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are", - "In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " + - "to digitize their consciousness. Their consciousness could then be transferred into Synthoids " + - "or other bodies by trasmitting the digitized data. Human bodies became nothing more than 'sleeves' for the " + - "human consciousness. Mankind had finally achieved immortality - at least for those that could afford it.

    " + - "This BitNode unlocks Sleeve technology. Sleeve technology allows you to:

    " + - "1. Re-sleeve: Purchase and transfer your consciousness into a new body
    " + - "2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously

    " + - "In this BitNode:

    " + - "Your stats are significantly decreased
    " + - "All methods of gaining money are half as profitable (except Stock Market)
    " + - "Purchased servers are more expensive, have less max RAM, and a lower maximum limit
    " + - "Augmentations are 5x as expensive and require twice as much reputation

    " + - "Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " + - "Each level of this Source-File also grants you a Duplicate Sleeve"); -BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.", - "The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period " + - "of disorder that eventually lead to the governmental reformation of many global superpowers, most notably " + - "the USA and China. But just as the world was slowly beginning to recover from these dark times, financial catastrophe hit.

    " + - "In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers " + - "were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " + - "governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.

    " + - "In this BitNode:

    " + - "Your hacking stat and experience gain are halved
    " + - "The starting and maximum amount of money available on servers is significantly decreased
    " + - "The growth rate of servers is significantly reduced
    " + - "Weakening a server is twice as effective
    " + - "Company wages are decreased by 50%
    " + - "Corporation valuations are 90% lower and are therefore significantly less profitable
    " + - "Hacknet Node production is significantly decreased
    " + - "Crime and Infiltration are more lucrative
    " + - "Augmentations are twice as expensive

    " + - "Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will " + - "upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " + - "the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " + - "This Source-File also increases the player's company salary and reputation gain multipliers by:

    " + - "Level 1: 32%
    " + - "Level 2: 48%
    " + - "Level 3: 56%

    " + - "It also reduces the price increase for every aug bought by:

    "+ - "Level 1: 4%
    "+ - "Level 2: 6%
    "+ - "Level 3: 7%"); -BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.", - "To iterate is human, to recurse divine.

    " + - "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give you Source-File 12, or " + - "if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " + - "of Source-File 12 lets you start any BitNodes with NeuroFlux Governor equal to the level of this source file."); +BitNodes["BitNode1"] = new BitNode( + 1, + "Source Genesis", + "The original BitNode", + "The first BitNode created by the Enders to imprison the minds of humans. It became " + + "the prototype and testing-grounds for all of the BitNodes that followed.

    " + + "This is the first BitNode that you play through. It has no special " + + "modifications or mechanics.

    " + + "Destroying this BitNode will give you Source-File 1, or if you already have " + + "this Source-File it will upgrade its level up to a maximum of 3. This Source-File " + + "lets the player start with 32GB of RAM on his/her home computer when entering a " + + "new BitNode, and also increases all of the player's multipliers by:

    " + + "Level 1: 16%
    " + + "Level 2: 24%
    " + + "Level 3: 28%", +); +BitNodes["BitNode2"] = new BitNode( + 2, + "Rise of the Underworld", + "From the shadows, they rose", //Gangs + "From the shadows, they rose.

    Organized crime groups quickly filled the void of power " + + "left behind from the collapse of Western government in the 2050s. As society and civlization broke down, " + + "people quickly succumbed to the innate human impulse of evil and savagery. The organized crime " + + "factions quickly rose to the top of the modern world.

    " + + "In this BitNode:

    " + + "Your hacking level is reduced by 20%
    " + + "The growth rate and maximum amount of money available on servers are significantly decreased
    " + + "The amount of money gained from crimes and Infiltration is tripled
    " + + "Certain Factions (Slum Snakes, Tetrads, The Syndicate, The Dark Army, Speakers for the Dead, " + + "NiteSec, The Black Hand) give the player the ability to form and manage their own gangs. These gangs " + + "will earn the player money and reputation with the corresponding Faction
    " + + "Every Augmentation in the game will be available through the Factions listed above
    " + + "For every Faction NOT listed above, reputation gains are halved
    " + + "You will no longer gain passive reputation with Factions

    " + + "Destroying this BitNode will give you Source-File 2, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File allows you to form gangs in other BitNodes " + + "once your karma decreases to a certain value. " + + "It also increases the player's crime success rate, crime money, and charisma multipliers by:

    " + + "Level 1: 24%
    " + + "Level 2: 36%
    " + + "Level 3: 42%", +); +BitNodes["BitNode3"] = new BitNode( + 3, + "Corporatocracy", + "The Price of Civilization", + "Our greatest illusion is that a healthy society can revolve around a " + + "single-minded pursuit of wealth.

    " + + "Sometime in the early 21st century economic and political globalization turned " + + "the world into a corporatocracy, and it never looked back. Now, the privileged " + + "elite will happily bankrupt their own countrymen, decimate their own community, " + + "and evict their neighbors from houses in their desperate bid to increase their wealth.

    " + + "In this BitNode you can create and manage your own corporation. Running a successful corporation " + + "has the potential of generating massive profits. All other forms of income are reduced by 75%. Furthermore:

    " + + "The price and reputation cost of all Augmentations is tripled
    " + + "The starting and maximum amount of money on servers is reduced by 75%
    " + + "Server growth rate is reduced by 80%
    " + + "You now only need 75 favour with a faction in order to donate to it, rather than 150

    " + + "Destroying this BitNode will give you Source-File 3, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File lets you create corporations on other BitNodes (although " + + "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%", +); +BitNodes["BitNode4"] = new BitNode( + 4, + "The Singularity", + "The Man and the Machine", + "The Singularity has arrived. The human race is gone, replaced " + + "by artificially superintelligent beings that are more machine than man.

    " + + "In this BitNode, progressing is significantly harder. Experience gain rates " + + "for all stats are reduced. Most methods of earning money will now give significantly less.

    " + + "In this BitNode you will gain access to a new set of Netscript Functions known as Singularity Functions. " + + "These functions allow you to control most aspects of the game through scripts, including working for factions/companies, " + + "purchasing/installing Augmentations, and creating programs.

    " + + "Destroying this BitNode will give you Source-File 4, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File lets you access and use the Singularity " + + "Functions in other BitNodes. Each level of this Source-File will open up more Singularity Functions " + + "that you can use.", +); +BitNodes["BitNode5"] = new BitNode( + 5, + "Artificial Intelligence", + "Posthuman", + "They said it couldn't be done. They said the human brain, " + + "along with its consciousness and intelligence, couldn't be replicated. They said the complexity " + + "of the brain results from unpredictable, nonlinear interactions that couldn't be modeled " + + "by 1's and 0's. They were wrong.

    " + + "In this BitNode:

    " + + "The base security level of servers is doubled
    " + + "The starting money on servers is halved, but the maximum money remains the same
    " + + "Most methods of earning money now give significantly less
    " + + "Infiltration gives 50% more reputation and money
    " + + "Corporations have 50% lower valuations and are therefore less profitable
    " + + "Augmentations are more expensive
    " + + "Hacking experience gain rates are reduced

    " + + "Destroying this BitNode will give you Source-File 5, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File grants you a special new stat called Intelligence. " + + "Intelligence is unique because it is permanent and persistent (it never gets reset back to 1). However " + + "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't know " + + "when you gain experience and how much). Higher Intelligence levels will boost your production for many actions " + + "in the game.

    " + + "In addition, this Source-File will unlock the getBitNodeMultipliers() and getServer() Netscript functions, " + + "as well as the formulas API, and will also raise all of your hacking-related multipliers by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%", +); +BitNodes["BitNode6"] = new BitNode( + 6, + "Bladeburners", + "Like Tears in Rain", + "In the middle of the 21st century, OmniTek Incorporated began designing and manufacturing advanced synthetic " + + "androids, or Synthoids for short. They achieved a major technological breakthrough in the sixth generation " + + "of their Synthoid design, called MK-VI, by developing a hyperintelligent AI. Many argue that this was " + + "the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, and more intelligent " + + "than the humans that had created them.

    " + + "In this BitNode you will be able to access the Bladeburner Division at the NSA, which provides a new mechanic " + + "for progression. Furthermore:

    " + + "Hacking and Hacknet Nodes will be less profitable
    " + + "Your hacking level is reduced by 65%
    " + + "Hacking experience gain from scripts is reduced by 75%
    " + + "Corporations have 80% lower valuations and are therefore less profitable
    " + + "Working for companies is 50% less profitable
    " + + "Crimes and Infiltration are 25% less profitable

    " + + "Destroying this BitNode will give you Source-File 6, or if you already have this Source-File it will upgrade " + + "its level up to a maximum of 3. This Source-File allows you to access the NSA's Bladeburner Division in other " + + "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%", +); +BitNodes["BitNode7"] = new BitNode( + 7, + "Bladeburners 2079", + "More human than humans", + "In the middle of the 21st century, you were doing cutting-edge work at OmniTek Incorporated as part of the AI design team " + + "for advanced synthetic androids, or Synthoids for short. You helped achieve a major technological " + + "breakthrough in the sixth generation of the company's Synthoid design, called MK-VI, by developing a hyperintelligent AI. " + + "Many argue that this was the first sentient AI ever created. This resulted in Synthoid models that were stronger, faster, " + + "and more intelligent than the humans that had created them.

    " + + "In this BitNode you will be able to access the Bladeburner API, which allows you to access Bladeburner " + + "functionality through Netscript. Furthermore:

    " + + "The rank you gain from Bladeburner contracts/operations is reduced by 40%
    " + + "Bladeburner skills cost twice as many skill points
    " + + "Augmentations are 3x more expensive
    " + + "Hacking and Hacknet Nodes will be significantly less profitable
    " + + "Your hacking level is reduced by 65%
    " + + "Hacking experience gain from scripts is reduced by 75%
    " + + "Corporations have 80% lower valuations and are therefore less profitable
    " + + "Working for companies is 50% less profitable
    " + + "Crimes and Infiltration are 25% less profitable

    " + + "Destroying this BitNode will give you Source-File 7, or if you already have this Source-File it will upgrade " + + "its level up to a maximum of 3. This Source-File allows you to access the Bladeburner Netscript API in other " + + "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%", +); +BitNodes["BitNode8"] = new BitNode( + 8, + "Ghost of Wall Street", + "Money never sleeps", + "You are trying to make a name for yourself as an up-and-coming hedge fund manager on Wall Street.

    " + + "In this BitNode:

    " + + "You start with $250 million
    " + + "The only way to earn money is by trading on the stock market
    " + + "You start with a WSE membership and access to the TIX API
    " + + "You are able to short stocks and place different types of orders (limit/stop)
    " + + "You can immediately donate to factions to gain reputation

    " + + "Destroying this BitNode will give you Source-File 8, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + + "Level 1: Permanent access to WSE and TIX API
    " + + "Level 2: Ability to short stocks in other BitNodes
    " + + "Level 3: Ability to use limit/stop orders in other BitNodes

    " + + "This Source-File also increases your hacking growth multipliers by: " + + "
    Level 1: 12%
    Level 2: 18%
    Level 3: 21%", +); +BitNodes["BitNode9"] = new BitNode( + 9, + "Hacktocracy", + "Hacknet Unleashed", + "When Fulcrum Technologies released their open-source Linux distro Chapeau, it quickly " + + "became the OS of choice for the underground hacking community. Chapeau became especially notorious for " + + "powering the Hacknet, a global, decentralized network used for nefarious purposes. Fulcrum quickly " + + "abandoned the project and dissociated themselves from it.

    " + + "This BitNode unlocks the Hacknet Server, an upgraded version of the Hacknet Node. Hacknet Servers generate " + + "hashes, which can be spent on a variety of different upgrades.

    " + + "In this BitNode:

    " + + "Your stats are significantly decreased
    " + + "You cannnot purchase additional servers
    " + + "Hacking is significantly less profitable

    " + + "Destroying this BitNode will give you Source-File 9, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File grants the following benefits:

    " + + "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
    " + + "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
    " + + "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

    " + + "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + + "when installing Augmentations)", +); +BitNodes["BitNode10"] = new BitNode( + 10, + "Digital Carbon", + "Your body is not who you are", + "In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " + + "to digitize their consciousness. Their consciousness could then be transferred into Synthoids " + + "or other bodies by trasmitting the digitized data. Human bodies became nothing more than 'sleeves' for the " + + "human consciousness. Mankind had finally achieved immortality - at least for those that could afford it.

    " + + "This BitNode unlocks Sleeve technology. Sleeve technology allows you to:

    " + + "1. Re-sleeve: Purchase and transfer your consciousness into a new body
    " + + "2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously

    " + + "In this BitNode:

    " + + "Your stats are significantly decreased
    " + + "All methods of gaining money are half as profitable (except Stock Market)
    " + + "Purchased servers are more expensive, have less max RAM, and a lower maximum limit
    " + + "Augmentations are 5x as expensive and require twice as much reputation

    " + + "Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " + + "Each level of this Source-File also grants you a Duplicate Sleeve", +); +BitNodes["BitNode11"] = new BitNode( + 11, + "The Big Crash", + "Okay. Sell it all.", + "The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period " + + "of disorder that eventually lead to the governmental reformation of many global superpowers, most notably " + + "the USA and China. But just as the world was slowly beginning to recover from these dark times, financial catastrophe hit.

    " + + "In many countries, the high cost of trying to deal with the civil disorder bankrupted the governments. In all of this chaos and confusion, hackers " + + "were able to steal billions of dollars from the world's largest electronic banks, prompting an international banking crisis as " + + "governments were unable to bail out insolvent banks. Now, the world is slowly crumbling in the middle of the biggest economic crisis of all time.

    " + + "In this BitNode:

    " + + "Your hacking stat and experience gain are halved
    " + + "The starting and maximum amount of money available on servers is significantly decreased
    " + + "The growth rate of servers is significantly reduced
    " + + "Weakening a server is twice as effective
    " + + "Company wages are decreased by 50%
    " + + "Corporation valuations are 90% lower and are therefore significantly less profitable
    " + + "Hacknet Node production is significantly decreased
    " + + "Crime and Infiltration are more lucrative
    " + + "Augmentations are twice as expensive

    " + + "Destroying this BitNode will give you Source-File 11, or if you already have this Source-File it will " + + "upgrade its level up to a maximum of 3. This Source-File makes it so that company favor increases BOTH " + + "the player's salary and reputation gain rate at that company by 1% per favor (rather than just the reputation gain). " + + "This Source-File also increases the player's company salary and reputation gain multipliers by:

    " + + "Level 1: 32%
    " + + "Level 2: 48%
    " + + "Level 3: 56%

    " + + "It also reduces the price increase for every aug bought by:

    " + + "Level 1: 4%
    " + + "Level 2: 6%
    " + + "Level 3: 7%", +); +BitNodes["BitNode12"] = new BitNode( + 12, + "The Recursion", + "Repeat.", + "To iterate is human, to recurse divine.

    " + + "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give you Source-File 12, or " + + "if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " + + "of Source-File 12 lets you start any BitNodes with NeuroFlux Governor equal to the level of this source file.", +); // Books: Frontera, Shiner BitNodes["BitNode13"] = new BitNode(13, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes BitNodes["BitNode14"] = new BitNode(14, "", "COMING SOON"); @@ -256,255 +302,256 @@ BitNodes["BitNode23"] = new BitNode(23, "", "COMING SOON"); BitNodes["BitNode24"] = new BitNode(24, "", "COMING SOON"); export function initBitNodeMultipliers(p: IPlayer): void { - if (p.bitNodeN == null) { - p.bitNodeN = 1; + if (p.bitNodeN == null) { + p.bitNodeN = 1; + } + for (const mult in BitNodeMultipliers) { + if (BitNodeMultipliers.hasOwnProperty(mult)) { + BitNodeMultipliers[mult] = 1; } - for (const mult in BitNodeMultipliers) { - if (BitNodeMultipliers.hasOwnProperty(mult)) { - BitNodeMultipliers[mult] = 1; + } + + switch (p.bitNodeN) { + case 1: // Source Genesis (every multiplier is 1) + break; + case 2: // Rise of the Underworld + BitNodeMultipliers.HackingLevelMultiplier = 0.8; + BitNodeMultipliers.ServerGrowthRate = 0.8; + BitNodeMultipliers.ServerMaxMoney = 0.2; + BitNodeMultipliers.ServerStartingMoney = 0.4; + BitNodeMultipliers.CrimeMoney = 3; + BitNodeMultipliers.InfiltrationMoney = 3; + BitNodeMultipliers.FactionWorkRepGain = 0.5; + BitNodeMultipliers.FactionPassiveRepGain = 0; + BitNodeMultipliers.GangKarmaRequirement = 0; + break; + case 3: // Corporatocracy + BitNodeMultipliers.HackingLevelMultiplier = 0.8; + BitNodeMultipliers.RepToDonateToFaction = 0.5; + BitNodeMultipliers.AugmentationRepCost = 3; + BitNodeMultipliers.AugmentationMoneyCost = 3; + BitNodeMultipliers.ServerMaxMoney = 0.2; + BitNodeMultipliers.ServerStartingMoney = 0.2; + BitNodeMultipliers.ServerGrowthRate = 0.2; + BitNodeMultipliers.ScriptHackMoney = 0.2; + BitNodeMultipliers.CompanyWorkMoney = 0.25; + BitNodeMultipliers.CrimeMoney = 0.25; + BitNodeMultipliers.HacknetNodeMoney = 0.25; + BitNodeMultipliers.HomeComputerRamCost = 1.5; + BitNodeMultipliers.PurchasedServerCost = 2; + BitNodeMultipliers.GangKarmaRequirement = 3; + break; + case 4: // The Singularity + BitNodeMultipliers.ServerMaxMoney = 0.15; + BitNodeMultipliers.ServerStartingMoney = 0.75; + BitNodeMultipliers.ScriptHackMoney = 0.2; + BitNodeMultipliers.CompanyWorkMoney = 0.1; + BitNodeMultipliers.CrimeMoney = 0.2; + BitNodeMultipliers.HacknetNodeMoney = 0.05; + BitNodeMultipliers.CompanyWorkExpGain = 0.5; + BitNodeMultipliers.ClassGymExpGain = 0.5; + BitNodeMultipliers.FactionWorkExpGain = 0.5; + BitNodeMultipliers.HackExpGain = 0.4; + BitNodeMultipliers.CrimeExpGain = 0.5; + BitNodeMultipliers.FactionWorkRepGain = 0.75; + break; + case 5: // Artificial intelligence + BitNodeMultipliers.ServerMaxMoney = 2; + BitNodeMultipliers.ServerStartingSecurity = 2; + BitNodeMultipliers.ServerStartingMoney = 0.5; + BitNodeMultipliers.ScriptHackMoney = 0.15; + BitNodeMultipliers.HacknetNodeMoney = 0.2; + BitNodeMultipliers.CrimeMoney = 0.5; + BitNodeMultipliers.InfiltrationRep = 1.5; + BitNodeMultipliers.InfiltrationMoney = 1.5; + BitNodeMultipliers.AugmentationMoneyCost = 2; + BitNodeMultipliers.HackExpGain = 0.5; + BitNodeMultipliers.CorporationValuation = 0.5; + break; + case 6: // Bladeburner + BitNodeMultipliers.HackingLevelMultiplier = 0.35; + BitNodeMultipliers.ServerMaxMoney = 0.4; + BitNodeMultipliers.ServerStartingMoney = 0.5; + BitNodeMultipliers.ServerStartingSecurity = 1.5; + BitNodeMultipliers.ScriptHackMoney = 0.75; + BitNodeMultipliers.CompanyWorkMoney = 0.5; + BitNodeMultipliers.CrimeMoney = 0.75; + BitNodeMultipliers.InfiltrationMoney = 0.75; + BitNodeMultipliers.CorporationValuation = 0.2; + BitNodeMultipliers.HacknetNodeMoney = 0.2; + BitNodeMultipliers.FactionPassiveRepGain = 0; + BitNodeMultipliers.HackExpGain = 0.25; + BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed + BitNodeMultipliers.GangKarmaRequirement = 5; + break; + case 7: // Bladeburner 2079 + BitNodeMultipliers.BladeburnerRank = 0.6; + BitNodeMultipliers.BladeburnerSkillCost = 2; + BitNodeMultipliers.AugmentationMoneyCost = 3; + BitNodeMultipliers.HackingLevelMultiplier = 0.35; + BitNodeMultipliers.ServerMaxMoney = 0.4; + BitNodeMultipliers.ServerStartingMoney = 0.5; + BitNodeMultipliers.ServerStartingSecurity = 1.5; + BitNodeMultipliers.ScriptHackMoney = 0.5; + BitNodeMultipliers.CompanyWorkMoney = 0.5; + BitNodeMultipliers.CrimeMoney = 0.75; + BitNodeMultipliers.InfiltrationMoney = 0.75; + BitNodeMultipliers.CorporationValuation = 0.2; + BitNodeMultipliers.HacknetNodeMoney = 0.2; + BitNodeMultipliers.FactionPassiveRepGain = 0; + BitNodeMultipliers.HackExpGain = 0.25; + BitNodeMultipliers.FourSigmaMarketDataCost = 2; + BitNodeMultipliers.FourSigmaMarketDataApiCost = 2; + BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed + BitNodeMultipliers.GangKarmaRequirement = 5; + break; + case 8: // Ghost of Wall Street + BitNodeMultipliers.ScriptHackMoney = 0.3; + BitNodeMultipliers.ScriptHackMoneyGain = 0; + BitNodeMultipliers.ManualHackMoney = 0; + BitNodeMultipliers.CompanyWorkMoney = 0; + BitNodeMultipliers.CrimeMoney = 0; + BitNodeMultipliers.HacknetNodeMoney = 0; + BitNodeMultipliers.InfiltrationMoney = 0; + BitNodeMultipliers.RepToDonateToFaction = 0; + BitNodeMultipliers.CorporationValuation = 0; + BitNodeMultipliers.CodingContractMoney = 0; + BitNodeMultipliers.GangKarmaRequirement = 10; + break; + case 9: // Hacktocracy + BitNodeMultipliers.HackingLevelMultiplier = 0.4; + BitNodeMultipliers.StrengthLevelMultiplier = 0.45; + BitNodeMultipliers.DefenseLevelMultiplier = 0.45; + BitNodeMultipliers.DexterityLevelMultiplier = 0.45; + BitNodeMultipliers.AgilityLevelMultiplier = 0.45; + BitNodeMultipliers.CharismaLevelMultiplier = 0.45; + BitNodeMultipliers.PurchasedServerLimit = 0; + BitNodeMultipliers.HomeComputerRamCost = 5; + BitNodeMultipliers.CrimeMoney = 0.5; + BitNodeMultipliers.ScriptHackMoney = 0.1; + BitNodeMultipliers.HackExpGain = 0.05; + BitNodeMultipliers.ServerStartingMoney = 0.1; + BitNodeMultipliers.ServerMaxMoney = 0.1; + BitNodeMultipliers.ServerStartingSecurity = 2.5; + BitNodeMultipliers.CorporationValuation = 0.5; + BitNodeMultipliers.FourSigmaMarketDataCost = 5; + BitNodeMultipliers.FourSigmaMarketDataApiCost = 4; + BitNodeMultipliers.BladeburnerRank = 0.9; + BitNodeMultipliers.BladeburnerSkillCost = 1.2; + BitNodeMultipliers.GangKarmaRequirement = 3; + break; + case 10: // Digital Carbon + BitNodeMultipliers.HackingLevelMultiplier = 0.2; + BitNodeMultipliers.StrengthLevelMultiplier = 0.4; + BitNodeMultipliers.DefenseLevelMultiplier = 0.4; + BitNodeMultipliers.DexterityLevelMultiplier = 0.4; + BitNodeMultipliers.AgilityLevelMultiplier = 0.4; + BitNodeMultipliers.CharismaLevelMultiplier = 0.4; + BitNodeMultipliers.CompanyWorkMoney = 0.5; + BitNodeMultipliers.CrimeMoney = 0.5; + BitNodeMultipliers.HacknetNodeMoney = 0.5; + BitNodeMultipliers.ManualHackMoney = 0.5; + BitNodeMultipliers.ScriptHackMoney = 0.5; + BitNodeMultipliers.CodingContractMoney = 0.5; + BitNodeMultipliers.InfiltrationMoney = 0.5; + BitNodeMultipliers.CorporationValuation = 0.5; + BitNodeMultipliers.AugmentationMoneyCost = 5; + BitNodeMultipliers.AugmentationRepCost = 2; + BitNodeMultipliers.HomeComputerRamCost = 1.5; + BitNodeMultipliers.PurchasedServerCost = 5; + BitNodeMultipliers.PurchasedServerLimit = 0.6; + BitNodeMultipliers.PurchasedServerMaxRam = 0.5; + BitNodeMultipliers.BladeburnerRank = 0.8; + BitNodeMultipliers.GangKarmaRequirement = 3; + break; + case 11: //The Big Crash + BitNodeMultipliers.HackingLevelMultiplier = 0.5; + BitNodeMultipliers.HackExpGain = 0.5; + BitNodeMultipliers.ServerMaxMoney = 0.1; + BitNodeMultipliers.ServerStartingMoney = 0.1; + BitNodeMultipliers.ServerGrowthRate = 0.2; + BitNodeMultipliers.ServerWeakenRate = 2; + BitNodeMultipliers.CrimeMoney = 3; + BitNodeMultipliers.CompanyWorkMoney = 0.5; + BitNodeMultipliers.HacknetNodeMoney = 0.1; + BitNodeMultipliers.AugmentationMoneyCost = 2; + BitNodeMultipliers.InfiltrationMoney = 2.5; + BitNodeMultipliers.InfiltrationRep = 2.5; + BitNodeMultipliers.CorporationValuation = 0.1; + BitNodeMultipliers.CodingContractMoney = 0.25; + BitNodeMultipliers.FourSigmaMarketDataCost = 4; + BitNodeMultipliers.FourSigmaMarketDataApiCost = 4; + break; + case 12: { + //The Recursion + let sf12Lvl = 0; + for (let i = 0; i < p.sourceFiles.length; i++) { + if (p.sourceFiles[i].n === 12) { + sf12Lvl = p.sourceFiles[i].lvl; } + } + const inc = Math.pow(1.02, sf12Lvl); + const dec = 1 / inc; + + // Multiplier for number of augs needed for Daedalus increases + // up to a maximum of 1.34, which results in 40 Augs required + BitNodeMultipliers.DaedalusAugsRequirement = Math.min(inc, 1.34); + + BitNodeMultipliers.HackingLevelMultiplier = dec; + BitNodeMultipliers.StrengthLevelMultiplier = dec; + BitNodeMultipliers.DefenseLevelMultiplier = dec; + BitNodeMultipliers.DexterityLevelMultiplier = dec; + BitNodeMultipliers.AgilityLevelMultiplier = dec; + BitNodeMultipliers.CharismaLevelMultiplier = dec; + + BitNodeMultipliers.ServerMaxMoney = dec; + BitNodeMultipliers.ServerStartingMoney = dec; + BitNodeMultipliers.ServerGrowthRate = dec; + BitNodeMultipliers.ServerWeakenRate = dec; + + //Does not scale, otherwise security might start at 300+ + BitNodeMultipliers.ServerStartingSecurity = 1.5; + + BitNodeMultipliers.HomeComputerRamCost = inc; + + BitNodeMultipliers.PurchasedServerCost = inc; + BitNodeMultipliers.PurchasedServerLimit = dec; + BitNodeMultipliers.PurchasedServerMaxRam = dec; + + BitNodeMultipliers.ManualHackMoney = dec; + BitNodeMultipliers.ScriptHackMoney = dec; + BitNodeMultipliers.CompanyWorkMoney = dec; + BitNodeMultipliers.CrimeMoney = dec; + BitNodeMultipliers.HacknetNodeMoney = dec; + BitNodeMultipliers.CodingContractMoney = dec; + + BitNodeMultipliers.CompanyWorkExpGain = dec; + BitNodeMultipliers.ClassGymExpGain = dec; + BitNodeMultipliers.FactionWorkExpGain = dec; + BitNodeMultipliers.HackExpGain = dec; + BitNodeMultipliers.CrimeExpGain = dec; + + BitNodeMultipliers.FactionWorkRepGain = dec; + BitNodeMultipliers.FactionPassiveRepGain = dec; + BitNodeMultipliers.RepToDonateToFaction = inc; + + BitNodeMultipliers.AugmentationRepCost = inc; + BitNodeMultipliers.AugmentationMoneyCost = inc; + + BitNodeMultipliers.InfiltrationMoney = dec; + BitNodeMultipliers.InfiltrationRep = dec; + + BitNodeMultipliers.FourSigmaMarketDataCost = inc; + BitNodeMultipliers.FourSigmaMarketDataApiCost = inc; + + BitNodeMultipliers.CorporationValuation = dec; + + BitNodeMultipliers.BladeburnerRank = dec; + BitNodeMultipliers.BladeburnerSkillCost = inc; + break; } - - switch (p.bitNodeN) { - case 1: // Source Genesis (every multiplier is 1) - break; - case 2: // Rise of the Underworld - BitNodeMultipliers.HackingLevelMultiplier = 0.8; - BitNodeMultipliers.ServerGrowthRate = 0.8; - BitNodeMultipliers.ServerMaxMoney = 0.2; - BitNodeMultipliers.ServerStartingMoney = 0.4; - BitNodeMultipliers.CrimeMoney = 3; - BitNodeMultipliers.InfiltrationMoney = 3; - BitNodeMultipliers.FactionWorkRepGain = 0.5; - BitNodeMultipliers.FactionPassiveRepGain = 0; - BitNodeMultipliers.GangKarmaRequirement = 0; - break; - case 3: // Corporatocracy - BitNodeMultipliers.HackingLevelMultiplier = 0.8; - BitNodeMultipliers.RepToDonateToFaction = 0.5; - BitNodeMultipliers.AugmentationRepCost = 3; - BitNodeMultipliers.AugmentationMoneyCost = 3; - BitNodeMultipliers.ServerMaxMoney = 0.2; - BitNodeMultipliers.ServerStartingMoney = 0.2; - BitNodeMultipliers.ServerGrowthRate = 0.2; - BitNodeMultipliers.ScriptHackMoney = 0.2; - BitNodeMultipliers.CompanyWorkMoney = 0.25; - BitNodeMultipliers.CrimeMoney = 0.25; - BitNodeMultipliers.HacknetNodeMoney = 0.25; - BitNodeMultipliers.HomeComputerRamCost = 1.5; - BitNodeMultipliers.PurchasedServerCost = 2; - BitNodeMultipliers.GangKarmaRequirement = 3; - break; - case 4: // The Singularity - BitNodeMultipliers.ServerMaxMoney = 0.15; - BitNodeMultipliers.ServerStartingMoney = 0.75; - BitNodeMultipliers.ScriptHackMoney = 0.2; - BitNodeMultipliers.CompanyWorkMoney = 0.1; - BitNodeMultipliers.CrimeMoney = 0.2; - BitNodeMultipliers.HacknetNodeMoney = 0.05; - BitNodeMultipliers.CompanyWorkExpGain = 0.5; - BitNodeMultipliers.ClassGymExpGain = 0.5; - BitNodeMultipliers.FactionWorkExpGain = 0.5; - BitNodeMultipliers.HackExpGain = 0.4; - BitNodeMultipliers.CrimeExpGain = 0.5; - BitNodeMultipliers.FactionWorkRepGain = 0.75; - break; - case 5: // Artificial intelligence - BitNodeMultipliers.ServerMaxMoney = 2; - BitNodeMultipliers.ServerStartingSecurity = 2; - BitNodeMultipliers.ServerStartingMoney = 0.5; - BitNodeMultipliers.ScriptHackMoney = 0.15; - BitNodeMultipliers.HacknetNodeMoney = 0.2; - BitNodeMultipliers.CrimeMoney = 0.5; - BitNodeMultipliers.InfiltrationRep = 1.5; - BitNodeMultipliers.InfiltrationMoney = 1.5; - BitNodeMultipliers.AugmentationMoneyCost = 2; - BitNodeMultipliers.HackExpGain = 0.5; - BitNodeMultipliers.CorporationValuation = 0.5; - break; - case 6: // Bladeburner - BitNodeMultipliers.HackingLevelMultiplier = 0.35; - BitNodeMultipliers.ServerMaxMoney = 0.4; - BitNodeMultipliers.ServerStartingMoney = 0.5; - BitNodeMultipliers.ServerStartingSecurity = 1.5; - BitNodeMultipliers.ScriptHackMoney = 0.75; - BitNodeMultipliers.CompanyWorkMoney = 0.5; - BitNodeMultipliers.CrimeMoney = 0.75; - BitNodeMultipliers.InfiltrationMoney = 0.75; - BitNodeMultipliers.CorporationValuation = 0.2; - BitNodeMultipliers.HacknetNodeMoney = 0.2; - BitNodeMultipliers.FactionPassiveRepGain = 0; - BitNodeMultipliers.HackExpGain = 0.25; - BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed - BitNodeMultipliers.GangKarmaRequirement = 5; - break; - case 7: // Bladeburner 2079 - BitNodeMultipliers.BladeburnerRank = 0.6; - BitNodeMultipliers.BladeburnerSkillCost = 2; - BitNodeMultipliers.AugmentationMoneyCost = 3; - BitNodeMultipliers.HackingLevelMultiplier = 0.35; - BitNodeMultipliers.ServerMaxMoney = 0.4; - BitNodeMultipliers.ServerStartingMoney = 0.5; - BitNodeMultipliers.ServerStartingSecurity = 1.5; - BitNodeMultipliers.ScriptHackMoney = 0.5; - BitNodeMultipliers.CompanyWorkMoney = 0.5; - BitNodeMultipliers.CrimeMoney = 0.75; - BitNodeMultipliers.InfiltrationMoney = 0.75; - BitNodeMultipliers.CorporationValuation = 0.2; - BitNodeMultipliers.HacknetNodeMoney = 0.2; - BitNodeMultipliers.FactionPassiveRepGain = 0; - BitNodeMultipliers.HackExpGain = 0.25; - BitNodeMultipliers.FourSigmaMarketDataCost = 2; - BitNodeMultipliers.FourSigmaMarketDataApiCost = 2; - BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed - BitNodeMultipliers.GangKarmaRequirement = 5; - break; - case 8: // Ghost of Wall Street - BitNodeMultipliers.ScriptHackMoney = 0.3; - BitNodeMultipliers.ScriptHackMoneyGain = 0; - BitNodeMultipliers.ManualHackMoney = 0; - BitNodeMultipliers.CompanyWorkMoney = 0; - BitNodeMultipliers.CrimeMoney = 0; - BitNodeMultipliers.HacknetNodeMoney = 0; - BitNodeMultipliers.InfiltrationMoney = 0; - BitNodeMultipliers.RepToDonateToFaction = 0; - BitNodeMultipliers.CorporationValuation = 0; - BitNodeMultipliers.CodingContractMoney = 0; - BitNodeMultipliers.GangKarmaRequirement = 10; - break; - case 9: // Hacktocracy - BitNodeMultipliers.HackingLevelMultiplier = 0.4; - BitNodeMultipliers.StrengthLevelMultiplier = 0.45; - BitNodeMultipliers.DefenseLevelMultiplier = 0.45; - BitNodeMultipliers.DexterityLevelMultiplier = 0.45; - BitNodeMultipliers.AgilityLevelMultiplier = 0.45; - BitNodeMultipliers.CharismaLevelMultiplier = 0.45; - BitNodeMultipliers.PurchasedServerLimit = 0; - BitNodeMultipliers.HomeComputerRamCost = 5; - BitNodeMultipliers.CrimeMoney = 0.5; - BitNodeMultipliers.ScriptHackMoney = 0.1; - BitNodeMultipliers.HackExpGain = 0.05; - BitNodeMultipliers.ServerStartingMoney = 0.1; - BitNodeMultipliers.ServerMaxMoney = 0.1; - BitNodeMultipliers.ServerStartingSecurity = 2.5; - BitNodeMultipliers.CorporationValuation = 0.5; - BitNodeMultipliers.FourSigmaMarketDataCost = 5; - BitNodeMultipliers.FourSigmaMarketDataApiCost = 4; - BitNodeMultipliers.BladeburnerRank = 0.9; - BitNodeMultipliers.BladeburnerSkillCost = 1.2; - BitNodeMultipliers.GangKarmaRequirement = 3; - break; - case 10: // Digital Carbon - BitNodeMultipliers.HackingLevelMultiplier = 0.2; - BitNodeMultipliers.StrengthLevelMultiplier = 0.4; - BitNodeMultipliers.DefenseLevelMultiplier = 0.4; - BitNodeMultipliers.DexterityLevelMultiplier = 0.4; - BitNodeMultipliers.AgilityLevelMultiplier = 0.4; - BitNodeMultipliers.CharismaLevelMultiplier = 0.4; - BitNodeMultipliers.CompanyWorkMoney = 0.5; - BitNodeMultipliers.CrimeMoney = 0.5; - BitNodeMultipliers.HacknetNodeMoney = 0.5; - BitNodeMultipliers.ManualHackMoney = 0.5; - BitNodeMultipliers.ScriptHackMoney = 0.5; - BitNodeMultipliers.CodingContractMoney = 0.5; - BitNodeMultipliers.InfiltrationMoney = 0.5; - BitNodeMultipliers.CorporationValuation = 0.5; - BitNodeMultipliers.AugmentationMoneyCost = 5; - BitNodeMultipliers.AugmentationRepCost = 2; - BitNodeMultipliers.HomeComputerRamCost = 1.5; - BitNodeMultipliers.PurchasedServerCost = 5; - BitNodeMultipliers.PurchasedServerLimit = 0.6; - BitNodeMultipliers.PurchasedServerMaxRam = 0.5; - BitNodeMultipliers.BladeburnerRank = 0.8; - BitNodeMultipliers.GangKarmaRequirement = 3; - break; - case 11: //The Big Crash - BitNodeMultipliers.HackingLevelMultiplier = 0.5; - BitNodeMultipliers.HackExpGain = 0.5; - BitNodeMultipliers.ServerMaxMoney = 0.1; - BitNodeMultipliers.ServerStartingMoney = 0.1; - BitNodeMultipliers.ServerGrowthRate = 0.2; - BitNodeMultipliers.ServerWeakenRate = 2; - BitNodeMultipliers.CrimeMoney = 3; - BitNodeMultipliers.CompanyWorkMoney = 0.5; - BitNodeMultipliers.HacknetNodeMoney = 0.1; - BitNodeMultipliers.AugmentationMoneyCost = 2; - BitNodeMultipliers.InfiltrationMoney = 2.5; - BitNodeMultipliers.InfiltrationRep = 2.5; - BitNodeMultipliers.CorporationValuation = 0.1; - BitNodeMultipliers.CodingContractMoney = 0.25; - BitNodeMultipliers.FourSigmaMarketDataCost = 4; - BitNodeMultipliers.FourSigmaMarketDataApiCost = 4; - break; - case 12: { //The Recursion - let sf12Lvl = 0; - for (let i = 0; i < p.sourceFiles.length; i++) { - if (p.sourceFiles[i].n === 12) { - sf12Lvl = p.sourceFiles[i].lvl; - } - } - const inc = Math.pow(1.02, sf12Lvl); - const dec = 1/inc; - - // Multiplier for number of augs needed for Daedalus increases - // up to a maximum of 1.34, which results in 40 Augs required - BitNodeMultipliers.DaedalusAugsRequirement = Math.min(inc, 1.34); - - BitNodeMultipliers.HackingLevelMultiplier = dec; - BitNodeMultipliers.StrengthLevelMultiplier = dec; - BitNodeMultipliers.DefenseLevelMultiplier = dec; - BitNodeMultipliers.DexterityLevelMultiplier = dec; - BitNodeMultipliers.AgilityLevelMultiplier = dec; - BitNodeMultipliers.CharismaLevelMultiplier = dec; - - BitNodeMultipliers.ServerMaxMoney = dec; - BitNodeMultipliers.ServerStartingMoney = dec; - BitNodeMultipliers.ServerGrowthRate = dec; - BitNodeMultipliers.ServerWeakenRate = dec; - - //Does not scale, otherwise security might start at 300+ - BitNodeMultipliers.ServerStartingSecurity = 1.5; - - BitNodeMultipliers.HomeComputerRamCost = inc; - - BitNodeMultipliers.PurchasedServerCost = inc; - BitNodeMultipliers.PurchasedServerLimit = dec; - BitNodeMultipliers.PurchasedServerMaxRam = dec; - - BitNodeMultipliers.ManualHackMoney = dec; - BitNodeMultipliers.ScriptHackMoney = dec; - BitNodeMultipliers.CompanyWorkMoney = dec; - BitNodeMultipliers.CrimeMoney = dec; - BitNodeMultipliers.HacknetNodeMoney = dec; - BitNodeMultipliers.CodingContractMoney = dec; - - BitNodeMultipliers.CompanyWorkExpGain = dec; - BitNodeMultipliers.ClassGymExpGain = dec; - BitNodeMultipliers.FactionWorkExpGain = dec; - BitNodeMultipliers.HackExpGain = dec; - BitNodeMultipliers.CrimeExpGain = dec; - - BitNodeMultipliers.FactionWorkRepGain = dec; - BitNodeMultipliers.FactionPassiveRepGain = dec; - BitNodeMultipliers.RepToDonateToFaction = inc; - - BitNodeMultipliers.AugmentationRepCost = inc; - BitNodeMultipliers.AugmentationMoneyCost = inc; - - BitNodeMultipliers.InfiltrationMoney = dec; - BitNodeMultipliers.InfiltrationRep = dec; - - BitNodeMultipliers.FourSigmaMarketDataCost = inc; - BitNodeMultipliers.FourSigmaMarketDataApiCost = inc; - - BitNodeMultipliers.CorporationValuation = dec; - - BitNodeMultipliers.BladeburnerRank = dec; - BitNodeMultipliers.BladeburnerSkillCost = inc; - break; - } - default: - console.warn("Player.bitNodeN invalid"); - break; - } + default: + console.warn("Player.bitNodeN invalid"); + break; + } } diff --git a/src/BitNode/BitNodeMultipliers.ts b/src/BitNode/BitNodeMultipliers.ts index c2c5aa7ec..ac1a9610e 100644 --- a/src/BitNode/BitNodeMultipliers.ts +++ b/src/BitNode/BitNodeMultipliers.ts @@ -4,216 +4,216 @@ * player toward the intended strategy. Unless they really want to play the long, slow game of waiting... */ interface IBitNodeMultipliers { - /** - * Influences how quickly the player's agility level (not exp) scales - */ - AgilityLevelMultiplier: number; + /** + * Influences how quickly the player's agility level (not exp) scales + */ + AgilityLevelMultiplier: number; - /** - * Influences the base cost to purchase an augmentation. - */ - AugmentationMoneyCost: number; + /** + * Influences the base cost to purchase an augmentation. + */ + AugmentationMoneyCost: number; - /** - * Influences the base rep the player must have with a faction to purchase an augmentation. - */ - AugmentationRepCost: number; + /** + * Influences the base rep the player must have with a faction to purchase an augmentation. + */ + AugmentationRepCost: number; - /** - * Influences how quickly the player can gain rank within Bladeburner. - */ - BladeburnerRank: number; + /** + * Influences how quickly the player can gain rank within Bladeburner. + */ + BladeburnerRank: number; - /** - * Influences the cost of skill levels from Bladeburner. - */ - BladeburnerSkillCost: number; + /** + * Influences the cost of skill levels from Bladeburner. + */ + BladeburnerSkillCost: number; - /** - * Influences how quickly the player's charisma level (not exp) scales - */ - CharismaLevelMultiplier: number; + /** + * Influences how quickly the player's charisma level (not exp) scales + */ + CharismaLevelMultiplier: number; - /** - * Influences the experience gained for each ability when a player completes a class. - */ - ClassGymExpGain: number; + /** + * Influences the experience gained for each ability when a player completes a class. + */ + ClassGymExpGain: number; - /** - * Influences the amount of money gained from completing Coding Contracts - **/ - CodingContractMoney: number; + /** + * Influences the amount of money gained from completing Coding Contracts + **/ + CodingContractMoney: number; - /** - * Influences the experience gained for each ability when the player completes working their job. - */ - CompanyWorkExpGain: number; + /** + * Influences the experience gained for each ability when the player completes working their job. + */ + CompanyWorkExpGain: number; - /** - * Influences how much money the player earns when completing working their job. - */ - CompanyWorkMoney: number; + /** + * Influences how much money the player earns when completing working their job. + */ + CompanyWorkMoney: number; - /** - * Influences the valuation of corporations created by the player. - */ - CorporationValuation: number; + /** + * Influences the valuation of corporations created by the player. + */ + CorporationValuation: number; - /** - * Influences the base experience gained for each ability when the player commits a crime. - */ - CrimeExpGain: number; + /** + * Influences the base experience gained for each ability when the player commits a crime. + */ + CrimeExpGain: number; - /** - * Influences the base money gained when the player commits a crime. - */ - CrimeMoney: number; + /** + * Influences the base money gained when the player commits a crime. + */ + CrimeMoney: number; - /** - * Influences how many Augmentations you need in order to get invited to the Daedalus faction - */ - DaedalusAugsRequirement: number; + /** + * Influences how many Augmentations you need in order to get invited to the Daedalus faction + */ + DaedalusAugsRequirement: number; - /** - * Influences how quickly the player's defense level (not exp) scales - */ - DefenseLevelMultiplier: number; + /** + * Influences how quickly the player's defense level (not exp) scales + */ + DefenseLevelMultiplier: number; - /** - * Influences how quickly the player's dexterity level (not exp) scales - */ - DexterityLevelMultiplier: number; + /** + * Influences how quickly the player's dexterity level (not exp) scales + */ + DexterityLevelMultiplier: number; - /** - * Influences how much rep the player gains in each faction simply by being a member. - */ - FactionPassiveRepGain: number; + /** + * Influences how much rep the player gains in each faction simply by being a member. + */ + FactionPassiveRepGain: number; - /** - * Influences the experience gained for each ability when the player completes work for a Faction. - */ - FactionWorkExpGain: number; + /** + * Influences the experience gained for each ability when the player completes work for a Faction. + */ + FactionWorkExpGain: number; - /** - * Influences how much rep the player gains when performing work for a faction. - */ - FactionWorkRepGain: number; + /** + * Influences how much rep the player gains when performing work for a faction. + */ + FactionWorkRepGain: number; - /** - * Influences how much it costs to unlock the stock market's 4S Market Data API - */ - FourSigmaMarketDataApiCost: number; + /** + * Influences how much it costs to unlock the stock market's 4S Market Data API + */ + FourSigmaMarketDataApiCost: number; - /** - * Influences how much it costs to unlock the stock market's 4S Market Data (NOT API) - */ - FourSigmaMarketDataCost: number; + /** + * Influences how much it costs to unlock the stock market's 4S Market Data (NOT API) + */ + FourSigmaMarketDataCost: number; - /** - * Influences how much negative karma is required to create a gang in this bitnode. - */ - GangKarmaRequirement: number; + /** + * Influences how much negative karma is required to create a gang in this bitnode. + */ + GangKarmaRequirement: number; - /** - * Influences the experienced gained when hacking a server. - */ - HackExpGain: number; + /** + * Influences the experienced gained when hacking a server. + */ + HackExpGain: number; - /** - * Influences how quickly the player's hacking level (not experience) scales - */ - HackingLevelMultiplier: number; + /** + * Influences how quickly the player's hacking level (not experience) scales + */ + HackingLevelMultiplier: number; - /** - * Influences how much money is produced by Hacknet Nodes. - * Influeces the hash rate of Hacknet Servers (unlocked in BitNode-9) - */ - HacknetNodeMoney: number; + /** + * Influences how much money is produced by Hacknet Nodes. + * Influeces the hash rate of Hacknet Servers (unlocked in BitNode-9) + */ + HacknetNodeMoney: number; - /** - * Influences how much money it costs to upgrade your home computer's RAM - */ - HomeComputerRamCost: number; + /** + * Influences how much money it costs to upgrade your home computer's RAM + */ + HomeComputerRamCost: number; - /** - * Influences how much money is gained when the player infiltrates a company. - */ - InfiltrationMoney: number; + /** + * Influences how much money is gained when the player infiltrates a company. + */ + InfiltrationMoney: number; - /** - * Influences how much rep the player can gain from factions when selling stolen documents and secrets - */ - InfiltrationRep: number; + /** + * Influences how much rep the player can gain from factions when selling stolen documents and secrets + */ + InfiltrationRep: number; - /** - * Influences how much money can be stolen from a server when the player performs a hack against it through - * the Terminal. - */ - ManualHackMoney: number; + /** + * Influences how much money can be stolen from a server when the player performs a hack against it through + * the Terminal. + */ + ManualHackMoney: number; - /** - * Influence how much it costs to purchase a server - */ - PurchasedServerCost: number; + /** + * Influence how much it costs to purchase a server + */ + PurchasedServerCost: number; - /** - * Influences the maximum number of purchased servers you can have - */ - PurchasedServerLimit: number; + /** + * Influences the maximum number of purchased servers you can have + */ + PurchasedServerLimit: number; - /** - * Influences the maximum allowed RAM for a purchased server - */ - PurchasedServerMaxRam: number; - /** - * Influences the minimum favor the player must have with a faction before they can donate to gain rep. - */ - RepToDonateToFaction: number; + /** + * Influences the maximum allowed RAM for a purchased server + */ + PurchasedServerMaxRam: number; + /** + * Influences the minimum favor the player must have with a faction before they can donate to gain rep. + */ + RepToDonateToFaction: number; - /** - * Influences how much money can be stolen from a server when a script performs a hack against it. - */ - ScriptHackMoney: number; + /** + * Influences how much money can be stolen from a server when a script performs a hack against it. + */ + ScriptHackMoney: number; - /** - * The amount of money actually gained when script hack a server. This is - * different than the above because you can reduce the amount of money but - * not gain that same amount. - */ - ScriptHackMoneyGain: number; + /** + * The amount of money actually gained when script hack a server. This is + * different than the above because you can reduce the amount of money but + * not gain that same amount. + */ + ScriptHackMoneyGain: number; - /** - * Influences the growth percentage per cycle against a server. - */ - ServerGrowthRate: number; + /** + * Influences the growth percentage per cycle against a server. + */ + ServerGrowthRate: number; - /** - * Influences the maxmimum money that a server can grow to. - */ - ServerMaxMoney: number; + /** + * Influences the maxmimum money that a server can grow to. + */ + ServerMaxMoney: number; - /** - * Influences the initial money that a server starts with. - */ - ServerStartingMoney: number; + /** + * Influences the initial money that a server starts with. + */ + ServerStartingMoney: number; - /** - * Influences the initial security level (hackDifficulty) of a server. - */ - ServerStartingSecurity: number; + /** + * Influences the initial security level (hackDifficulty) of a server. + */ + ServerStartingSecurity: number; - /** - * Influences the weaken amount per invocation against a server. - */ - ServerWeakenRate: number; + /** + * Influences the weaken amount per invocation against a server. + */ + ServerWeakenRate: number; - /** - * Influences how quickly the player's strength level (not exp) scales - */ - StrengthLevelMultiplier: number; + /** + * Influences how quickly the player's strength level (not exp) scales + */ + StrengthLevelMultiplier: number; - // Index signature - [key: string]: number; + // Index signature + [key: string]: number; } /** @@ -221,57 +221,57 @@ interface IBitNodeMultipliers { */ // tslint:disable-next-line:variable-name export const BitNodeMultipliers: IBitNodeMultipliers = { - HackingLevelMultiplier: 1, - StrengthLevelMultiplier: 1, - DefenseLevelMultiplier: 1, - DexterityLevelMultiplier: 1, - AgilityLevelMultiplier: 1, - CharismaLevelMultiplier: 1, + HackingLevelMultiplier: 1, + StrengthLevelMultiplier: 1, + DefenseLevelMultiplier: 1, + DexterityLevelMultiplier: 1, + AgilityLevelMultiplier: 1, + CharismaLevelMultiplier: 1, - ServerGrowthRate: 1, - ServerMaxMoney: 1, - ServerStartingMoney: 1, - ServerStartingSecurity: 1, - ServerWeakenRate: 1, + ServerGrowthRate: 1, + ServerMaxMoney: 1, + ServerStartingMoney: 1, + ServerStartingSecurity: 1, + ServerWeakenRate: 1, - HomeComputerRamCost: 1, + HomeComputerRamCost: 1, - PurchasedServerCost: 1, - PurchasedServerLimit: 1, - PurchasedServerMaxRam: 1, + PurchasedServerCost: 1, + PurchasedServerLimit: 1, + PurchasedServerMaxRam: 1, - CompanyWorkMoney: 1, - CrimeMoney: 1, - HacknetNodeMoney: 1, - ManualHackMoney: 1, - ScriptHackMoney: 1, - ScriptHackMoneyGain: 1, - CodingContractMoney: 1, + CompanyWorkMoney: 1, + CrimeMoney: 1, + HacknetNodeMoney: 1, + ManualHackMoney: 1, + ScriptHackMoney: 1, + ScriptHackMoneyGain: 1, + CodingContractMoney: 1, - ClassGymExpGain: 1, - CompanyWorkExpGain: 1, - CrimeExpGain: 1, - FactionWorkExpGain: 1, - HackExpGain: 1, + ClassGymExpGain: 1, + CompanyWorkExpGain: 1, + CrimeExpGain: 1, + FactionWorkExpGain: 1, + HackExpGain: 1, - FactionPassiveRepGain: 1, - FactionWorkRepGain: 1, - RepToDonateToFaction: 1, + FactionPassiveRepGain: 1, + FactionWorkRepGain: 1, + RepToDonateToFaction: 1, - AugmentationMoneyCost: 1, - AugmentationRepCost: 1, + AugmentationMoneyCost: 1, + AugmentationRepCost: 1, - InfiltrationMoney: 1, - InfiltrationRep: 1, + InfiltrationMoney: 1, + InfiltrationRep: 1, - FourSigmaMarketDataCost: 1, - FourSigmaMarketDataApiCost: 1, + FourSigmaMarketDataCost: 1, + FourSigmaMarketDataApiCost: 1, - CorporationValuation: 1, + CorporationValuation: 1, - BladeburnerRank: 1, - BladeburnerSkillCost: 1, + BladeburnerRank: 1, + BladeburnerSkillCost: 1, - DaedalusAugsRequirement: 1, - GangKarmaRequirement: 1, + DaedalusAugsRequirement: 1, + GangKarmaRequirement: 1, }; diff --git a/src/Bladeburner/Action.ts b/src/Bladeburner/Action.ts index f3a00d669..e9be8fbc6 100644 --- a/src/Bladeburner/Action.ts +++ b/src/Bladeburner/Action.ts @@ -1,284 +1,351 @@ import { Player } from "../Player"; import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { addOffset } from "../../utils/helpers/addOffset"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; import { BladeburnerConstants } from "./data/Constants"; import { IBladeburner } from "./IBladeburner"; import { IAction, ISuccessChanceParams } from "./IAction"; class StatsMultiplier { - [key: string]: number; + [key: string]: number; - hack = 0; - str = 0; - def = 0; - dex = 0; - agi = 0; - cha = 0; - int = 0; + hack = 0; + str = 0; + def = 0; + dex = 0; + agi = 0; + cha = 0; + int = 0; } export interface IActionParams { - name?: string; - desc?: string; - level?: number; - maxLevel?: number; - autoLevel?: boolean; - baseDifficulty?: number; - difficultyFac?: number; - rewardFac?: number; - successes?: number; - failures?: number; - rankGain?: number; - rankLoss?: number; - hpLoss?: number; - hpLost?: number; - isStealth?: boolean; - isKill?: boolean; - count?: number; - countGrowth?: number; - weights?: StatsMultiplier; - decays?: StatsMultiplier; - teamCount?: number; + name?: string; + desc?: string; + level?: number; + maxLevel?: number; + autoLevel?: boolean; + baseDifficulty?: number; + difficultyFac?: number; + rewardFac?: number; + successes?: number; + failures?: number; + rankGain?: number; + rankLoss?: number; + hpLoss?: number; + hpLost?: number; + isStealth?: boolean; + isKill?: boolean; + count?: number; + countGrowth?: number; + weights?: StatsMultiplier; + decays?: StatsMultiplier; + teamCount?: number; } export class Action implements IAction { - name = ""; - desc = ""; + name = ""; + desc = ""; - // Difficulty scales with level. See getDifficulty() method - level = 1; - maxLevel = 1; - autoLevel = true; - baseDifficulty = 100; - difficultyFac = 1.01; + // Difficulty scales with level. See getDifficulty() method + level = 1; + maxLevel = 1; + autoLevel = true; + baseDifficulty = 100; + difficultyFac = 1.01; - // Rank increase/decrease is affected by this exponent - rewardFac = 1.02; + // Rank increase/decrease is affected by this exponent + rewardFac = 1.02; - successes = 0; - failures = 0; + successes = 0; + failures = 0; - // All of these scale with level/difficulty - rankGain = 0; - rankLoss = 0; - hpLoss = 0; - hpLost = 0; + // All of these scale with level/difficulty + rankGain = 0; + rankLoss = 0; + hpLoss = 0; + hpLost = 0; - // Action Category. Current categories are stealth and kill - isStealth = false; - isKill = false; + // Action Category. Current categories are stealth and kill + isStealth = false; + isKill = false; - /** - * Number of this contract remaining, and its growth rate - * Growth rate is an integer and the count will increase by that integer every "cycle" - */ - count: number = getRandomInt(1e3, 25e3); - countGrowth: number = getRandomInt(1, 5); + /** + * Number of this contract remaining, and its growth rate + * Growth rate is an integer and the count will increase by that integer every "cycle" + */ + count: number = getRandomInt(1e3, 25e3); + countGrowth: number = getRandomInt(1, 5); - // Weighting of each stat in determining action success rate - weights: StatsMultiplier = {hack:1/7,str:1/7,def:1/7,dex:1/7,agi:1/7,cha:1/7,int:1/7}; - // Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1) - decays: StatsMultiplier = { hack: 0.9, str: 0.9, def: 0.9, dex: 0.9, agi: 0.9, cha: 0.9, int: 0.9 }; - teamCount = 0; + // Weighting of each stat in determining action success rate + weights: StatsMultiplier = { + hack: 1 / 7, + str: 1 / 7, + def: 1 / 7, + dex: 1 / 7, + agi: 1 / 7, + cha: 1 / 7, + int: 1 / 7, + }; + // Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1) + decays: StatsMultiplier = { + hack: 0.9, + str: 0.9, + def: 0.9, + dex: 0.9, + agi: 0.9, + cha: 0.9, + int: 0.9, + }; + teamCount = 0; - // Base Class for Contracts, Operations, and BlackOps - constructor(params: IActionParams| null = null) { // | null = null - if(params && params.name) this.name = params.name; - if(params && params.desc) this.desc = params.desc; + // Base Class for Contracts, Operations, and BlackOps + constructor(params: IActionParams | null = null) { + // | null = null + if (params && params.name) this.name = params.name; + if (params && params.desc) this.desc = params.desc; - if(params && params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10); - if(params && params.difficultyFac) this.difficultyFac = params.difficultyFac; + if (params && params.baseDifficulty) + this.baseDifficulty = addOffset(params.baseDifficulty, 10); + if (params && params.difficultyFac) + this.difficultyFac = params.difficultyFac; - if(params && params.rewardFac) this.rewardFac = params.rewardFac; - if(params && params.rankGain) this.rankGain = params.rankGain; - if(params && params.rankLoss) this.rankLoss = params.rankLoss; - if(params && params.hpLoss) this.hpLoss = params.hpLoss; + if (params && params.rewardFac) this.rewardFac = params.rewardFac; + if (params && params.rankGain) this.rankGain = params.rankGain; + if (params && params.rankLoss) this.rankLoss = params.rankLoss; + if (params && params.hpLoss) this.hpLoss = params.hpLoss; - if(params && params.isStealth) this.isStealth = params.isStealth; - if(params && params.isKill) this.isKill = params.isKill; + if (params && params.isStealth) this.isStealth = params.isStealth; + if (params && params.isKill) this.isKill = params.isKill; - if(params && params.count) this.count = params.count; - if(params && params.countGrowth) this.countGrowth = params.countGrowth; + if (params && params.count) this.count = params.count; + if (params && params.countGrowth) this.countGrowth = params.countGrowth; - if(params && params.weights) this.weights = params.weights; - if(params && params.decays) this.decays = params.decays; + if (params && params.weights) this.weights = params.weights; + if (params && params.decays) this.decays = params.decays; - // Check to make sure weights are summed properly - let sum = 0; - for (const weight in this.weights) { - if (this.weights.hasOwnProperty(weight)) { - sum += this.weights[weight]; - } + // Check to make sure weights are summed properly + let sum = 0; + for (const weight in this.weights) { + if (this.weights.hasOwnProperty(weight)) { + sum += this.weights[weight]; + } + } + if (sum - 1 >= 10 * Number.EPSILON) { + throw new Error( + "Invalid weights when constructing Action " + + this.name + + ". The weights should sum up to 1. They sum up to :" + + 1, + ); + } + + for (const decay in this.decays) { + if (this.decays.hasOwnProperty(decay)) { + if (this.decays[decay] > 1) { + throw new Error( + "Invalid decays when constructing " + + "Action " + + this.name + + ". " + + "Decay value cannot be greater than 1", + ); } - if (sum - 1 >= 10 * Number.EPSILON) { - throw new Error("Invalid weights when constructing Action " + this.name + - ". The weights should sum up to 1. They sum up to :" + 1); + } + } + } + + getDifficulty(): number { + const difficulty = + this.baseDifficulty * Math.pow(this.difficultyFac, this.level - 1); + if (isNaN(difficulty)) { + throw new Error("Calculated NaN in Action.getDifficulty()"); + } + return difficulty; + } + + /** + * Tests for success. Should be called when an action has completed + * @param inst {Bladeburner} - Bladeburner instance + */ + attempt(inst: IBladeburner): boolean { + return Math.random() < this.getSuccessChance(inst); + } + + // To be implemented by subtypes + getActionTimePenalty(): number { + return 1; + } + + getActionTime(inst: IBladeburner): number { + const difficulty = this.getDifficulty(); + let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor; + const skillFac = inst.skillMultipliers.actionTime; // Always < 1 + + const effAgility = Player.agility * inst.skillMultipliers.effAgi; + const effDexterity = Player.dexterity * inst.skillMultipliers.effDex; + const statFac = + 0.5 * + (Math.pow(effAgility, BladeburnerConstants.EffAgiExponentialFactor) + + Math.pow(effDexterity, BladeburnerConstants.EffDexExponentialFactor) + + effAgility / BladeburnerConstants.EffAgiLinearFactor + + effDexterity / BladeburnerConstants.EffDexLinearFactor); // Always > 1 + + baseTime = Math.max(1, (baseTime * skillFac) / statFac); + + return Math.ceil(baseTime * this.getActionTimePenalty()); + } + + // For actions that have teams. To be implemented by subtypes. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getTeamSuccessBonus(inst: IBladeburner): number { + return 1; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getActionTypeSkillSuccessBonus(inst: IBladeburner): number { + return 1; + } + + getChaosCompetencePenalty( + inst: IBladeburner, + params: ISuccessChanceParams, + ): number { + const city = inst.getCurrentCity(); + if (params.est) { + return Math.pow( + city.popEst / BladeburnerConstants.PopulationThreshold, + BladeburnerConstants.PopulationExponent, + ); + } else { + return Math.pow( + city.pop / BladeburnerConstants.PopulationThreshold, + BladeburnerConstants.PopulationExponent, + ); + } + } + + getChaosDifficultyBonus( + inst: IBladeburner /*, params: ISuccessChanceParams*/, + ): number { + const city = inst.getCurrentCity(); + if (city.chaos > BladeburnerConstants.ChaosThreshold) { + const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold); + const mult = Math.pow(diff, 0.1); + return mult; + } + + return 1; + } + + getEstSuccessChance(inst: IBladeburner): number[] { + function clamp(x: number): number { + return Math.max(0, Math.min(x, 1)); + } + const est = this.getSuccessChance(inst, { est: true }); + const real = this.getSuccessChance(inst); + const diff = Math.abs(real - est); + let low = real - diff; + let high = real + diff; + const city = inst.getCurrentCity(); + const r = city.pop / city.popEst; + + if (r < 1) low *= r; + else high *= r; + return [clamp(low), clamp(high)]; + } + + /** + * @inst - Bladeburner Object + * @params - options: + * est (bool): Get success chance estimate instead of real success chance + */ + getSuccessChance( + inst: IBladeburner, + params: ISuccessChanceParams = { est: false }, + ): number { + if (inst == null) { + throw new Error( + "Invalid Bladeburner instance passed into Action.getSuccessChance", + ); + } + let difficulty = this.getDifficulty(); + let competence = 0; + for (const stat in this.weights) { + if (this.weights.hasOwnProperty(stat)) { + const playerStatLvl = Player.queryStatFromString(stat); + const key = "eff" + stat.charAt(0).toUpperCase() + stat.slice(1); + let effMultiplier = inst.skillMultipliers[key]; + if (effMultiplier == null) { + console.error( + `Failed to find Bladeburner Skill multiplier for: ${stat}`, + ); + effMultiplier = 1; } + competence += + this.weights[stat] * + Math.pow(effMultiplier * playerStatLvl, this.decays[stat]); + } + } + competence *= Player.getIntelligenceBonus(0.75); + competence *= inst.calculateStaminaPenalty(); - for (const decay in this.decays) { - if (this.decays.hasOwnProperty(decay)) { - if (this.decays[decay] > 1) { - throw new Error("Invalid decays when constructing " + - "Action " + this.name + ". " + - "Decay value cannot be greater than 1"); - } - } - } + competence *= this.getTeamSuccessBonus(inst); + + competence *= this.getChaosCompetencePenalty(inst, params); + difficulty *= this.getChaosDifficultyBonus(inst); + + if (this.name == "Raid" && inst.getCurrentCity().comms <= 0) { + return 0; } - getDifficulty(): number { - const difficulty = this.baseDifficulty * Math.pow(this.difficultyFac, this.level-1); - if (isNaN(difficulty)) {throw new Error("Calculated NaN in Action.getDifficulty()");} - return difficulty; + // Factor skill multipliers into success chance + competence *= inst.skillMultipliers.successChanceAll; + competence *= this.getActionTypeSkillSuccessBonus(inst); + if (this.isStealth) { + competence *= inst.skillMultipliers.successChanceStealth; + } + if (this.isKill) { + competence *= inst.skillMultipliers.successChanceKill; } - /** - * Tests for success. Should be called when an action has completed - * @param inst {Bladeburner} - Bladeburner instance - */ - attempt(inst: IBladeburner): boolean { - return (Math.random() < this.getSuccessChance(inst)); + // Augmentation multiplier + competence *= Player.bladeburner_success_chance_mult; + + if (isNaN(competence)) { + throw new Error( + "Competence calculated as NaN in Action.getSuccessChance()", + ); } + return Math.min(1, competence / difficulty); + } - // To be implemented by subtypes - getActionTimePenalty(): number { - return 1; + getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number { + return Math.ceil( + 0.5 * this.maxLevel * (2 * baseSuccessesPerLevel + (this.maxLevel - 1)), + ); + } + + setMaxLevel(baseSuccessesPerLevel: number): void { + if ( + this.successes >= + this.getSuccessesNeededForNextLevel(baseSuccessesPerLevel) + ) { + ++this.maxLevel; } + } - getActionTime(inst: IBladeburner): number { - const difficulty = this.getDifficulty(); - let baseTime = difficulty / BladeburnerConstants.DifficultyToTimeFactor; - const skillFac = inst.skillMultipliers.actionTime; // Always < 1 + toJSON(): any { + return Generic_toJSON("Action", this); + } - const effAgility = Player.agility * inst.skillMultipliers.effAgi; - const effDexterity = Player.dexterity * inst.skillMultipliers.effDex; - const statFac = 0.5 * (Math.pow(effAgility, BladeburnerConstants.EffAgiExponentialFactor) + - Math.pow(effDexterity, BladeburnerConstants.EffDexExponentialFactor) + - (effAgility / BladeburnerConstants.EffAgiLinearFactor) + - (effDexterity / BladeburnerConstants.EffDexLinearFactor)); // Always > 1 - - baseTime = Math.max(1, baseTime * skillFac / statFac); - - return Math.ceil(baseTime*this.getActionTimePenalty()); - } - - // For actions that have teams. To be implemented by subtypes. - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getTeamSuccessBonus(inst: IBladeburner): number { - return 1; - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getActionTypeSkillSuccessBonus(inst: IBladeburner): number { - return 1; - } - - getChaosCompetencePenalty(inst: IBladeburner, params: ISuccessChanceParams): number { - const city = inst.getCurrentCity(); - if (params.est) { - return Math.pow((city.popEst / BladeburnerConstants.PopulationThreshold), BladeburnerConstants.PopulationExponent); - } else { - return Math.pow((city.pop / BladeburnerConstants.PopulationThreshold), BladeburnerConstants.PopulationExponent); - } - } - - getChaosDifficultyBonus(inst: IBladeburner/*, params: ISuccessChanceParams*/): number { - const city = inst.getCurrentCity(); - if (city.chaos > BladeburnerConstants.ChaosThreshold) { - const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold); - const mult = Math.pow(diff, 0.1); - return mult; - } - - return 1; - } - - getEstSuccessChance(inst: IBladeburner): number[] { - function clamp(x: number): number { - return Math.max(0, Math.min(x, 1)); - } - const est = this.getSuccessChance(inst, {est: true}); - const real = this.getSuccessChance(inst); - const diff = Math.abs(real-est); - let low = real-diff; - let high = real+diff; - const city = inst.getCurrentCity(); - const r = city.pop / city.popEst; - - if(r < 1) low *= r; - else high *= r; - return [clamp(low), clamp(high)]; - } - - /** - * @inst - Bladeburner Object - * @params - options: - * est (bool): Get success chance estimate instead of real success chance - */ - getSuccessChance(inst: IBladeburner, params: ISuccessChanceParams={est: false}): number { - if (inst == null) {throw new Error("Invalid Bladeburner instance passed into Action.getSuccessChance");} - let difficulty = this.getDifficulty(); - let competence = 0; - for (const stat in this.weights) { - if (this.weights.hasOwnProperty(stat)) { - const playerStatLvl = Player.queryStatFromString(stat); - const key = "eff" + stat.charAt(0).toUpperCase() + stat.slice(1); - let effMultiplier = inst.skillMultipliers[key]; - if (effMultiplier == null) { - console.error(`Failed to find Bladeburner Skill multiplier for: ${stat}`); - effMultiplier = 1; - } - competence += (this.weights[stat] * Math.pow(effMultiplier*playerStatLvl, this.decays[stat])); - } - } - competence *= Player.getIntelligenceBonus(0.75); - competence *= inst.calculateStaminaPenalty(); - - competence *= this.getTeamSuccessBonus(inst); - - competence *= this.getChaosCompetencePenalty(inst, params); - difficulty *= this.getChaosDifficultyBonus(inst); - - if(this.name == "Raid" && inst.getCurrentCity().comms <= 0) { - return 0; - } - - // Factor skill multipliers into success chance - competence *= inst.skillMultipliers.successChanceAll; - competence *= this.getActionTypeSkillSuccessBonus(inst); - if (this.isStealth) { - competence *= inst.skillMultipliers.successChanceStealth; - } - if (this.isKill) { - competence *= inst.skillMultipliers.successChanceKill; - } - - // Augmentation multiplier - competence *= Player.bladeburner_success_chance_mult; - - if (isNaN(competence)) {throw new Error("Competence calculated as NaN in Action.getSuccessChance()");} - return Math.min(1, competence / difficulty); - } - - getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number { - return Math.ceil((0.5) * (this.maxLevel) * (2 * baseSuccessesPerLevel + (this.maxLevel-1))); - } - - setMaxLevel(baseSuccessesPerLevel: number): void { - if (this.successes >= this.getSuccessesNeededForNextLevel(baseSuccessesPerLevel)) { - ++this.maxLevel; - } - } - - toJSON(): any { - return Generic_toJSON("Action", this); - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Action { - return Generic_fromJSON(Action, value.data); - } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Action { + return Generic_fromJSON(Action, value.data); + } } -Reviver.constructors.Action = Action; \ No newline at end of file +Reviver.constructors.Action = Action; diff --git a/src/Bladeburner/ActionIdentifier.ts b/src/Bladeburner/ActionIdentifier.ts index e7ea4cc3d..801a85de5 100644 --- a/src/Bladeburner/ActionIdentifier.ts +++ b/src/Bladeburner/ActionIdentifier.ts @@ -1,28 +1,32 @@ import { IActionIdentifier } from "./IActionIdentifier"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; interface IParams { - name?: string; - type?: number; + name?: string; + type?: number; } export class ActionIdentifier implements IActionIdentifier { - name = ""; - type = -1; + name = ""; + type = -1; - constructor(params: IParams = {}) { - if (params.name) this.name = params.name; - if (params.type) this.type = params.type; - } + constructor(params: IParams = {}) { + if (params.name) this.name = params.name; + if (params.type) this.type = params.type; + } - toJSON(): any { - return Generic_toJSON("ActionIdentifier", this); - } + toJSON(): any { + return Generic_toJSON("ActionIdentifier", this); + } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): ActionIdentifier { - return Generic_fromJSON(ActionIdentifier, value.data); - } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): ActionIdentifier { + return Generic_fromJSON(ActionIdentifier, value.data); + } } Reviver.constructors.ActionIdentifier = ActionIdentifier; diff --git a/src/Bladeburner/BlackOperation.ts b/src/Bladeburner/BlackOperation.ts index c5fbe1e07..b06d156a1 100644 --- a/src/Bladeburner/BlackOperation.ts +++ b/src/Bladeburner/BlackOperation.ts @@ -1,34 +1,38 @@ import { Operation, IOperationParams } from "./Operation"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; export class BlackOperation extends Operation { - constructor(params: IOperationParams | null = null) { - super(params); - this.count = 1; - this.countGrowth = 0; - } + constructor(params: IOperationParams | null = null) { + super(params); + this.count = 1; + this.countGrowth = 0; + } - // To be implemented by subtypes - getActionTimePenalty(): number { - return 1.5; - } + // To be implemented by subtypes + getActionTimePenalty(): number { + return 1.5; + } - getChaosCompetencePenalty(/*inst: IBladeburner, params: ISuccessChanceParams*/): number { - return 1; - } + getChaosCompetencePenalty(/*inst: IBladeburner, params: ISuccessChanceParams*/): number { + return 1; + } - getChaosDifficultyBonus(/*inst: IBladeburner, params: ISuccessChanceParams*/): number { - return 1; - } + getChaosDifficultyBonus(/*inst: IBladeburner, params: ISuccessChanceParams*/): number { + return 1; + } - toJSON(): any { - return Generic_toJSON("BlackOperation", this); - } + toJSON(): any { + return Generic_toJSON("BlackOperation", this); + } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Operation { - return Generic_fromJSON(BlackOperation, value.data); - } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Operation { + return Generic_fromJSON(BlackOperation, value.data); + } } -Reviver.constructors.BlackOperation = BlackOperation; \ No newline at end of file +Reviver.constructors.BlackOperation = BlackOperation; diff --git a/src/Bladeburner/BlackOperations.ts b/src/Bladeburner/BlackOperations.ts index cb75f91d4..c6fb88366 100644 --- a/src/Bladeburner/BlackOperations.ts +++ b/src/Bladeburner/BlackOperations.ts @@ -3,343 +3,763 @@ import { IMap } from "../types"; export const BlackOperations: IMap = {}; -(function() { - BlackOperations["Operation Typhoon"] = new BlackOperation({ - name:"Operation Typhoon", - desc:"Obadiah Zenyatta is the leader of a RedWater PMC. It has long " + - "been known among the intelligence community that Zenyatta, along " + - "with the rest of the PMC, is a Synthoid.

    " + - "The goal of Operation Typhoon is to find and eliminate " + - "Zenyatta and RedWater by any means necessary. After the task " + - "is completed, the actions must be covered up from the general public.", - baseDifficulty:2000, reqdRank:2.5e3, - rankGain:50, rankLoss:10, hpLoss:100, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Zero"] = new BlackOperation({ - name:"Operation Zero", - desc:"AeroCorp is one of the world's largest defense contractors. " + - "Its leader, Steve Watataki, is thought to be a supporter of " + - "Synthoid rights. He must be removed.

    " + - "The goal of Operation Zero is to covertly infiltrate AeroCorp and " + - "uncover any incriminating evidence or " + - "information against Watataki that will cause him to be removed " + - "from his position at AeroCorp. Incriminating evidence can be " + - "fabricated as a last resort. Be warned that AeroCorp has some of " + - "the most advanced security measures in the world.", - baseDifficulty:2500, reqdRank:5e3, - rankGain:60, rankLoss:15, hpLoss:50, - weights:{hack:0.2,str:0.15,def:0.15,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isStealth:true, - }); - BlackOperations["Operation X"] = new BlackOperation({ - name:"Operation X", - desc:"We have recently discovered an underground publication " + - "group called Samizdat. Even though most of their publications " + - "are nonsensical conspiracy theories, the average human is " + - "gullible enough to believe them. Many of their works discuss " + - "Synthoids and pose a threat to society. The publications are spreading " + - "rapidly in China and other Eastern countries.

    " + - "Samizdat has done a good job of keeping hidden and anonymous. " + - "However, we've just received intelligence that their base of " + - "operations is in Ishima's underground sewer systems. Your task is to " + - "investigate the sewer systems, and eliminate Samizdat. They must " + - "never publish anything again.", - baseDifficulty:3000, reqdRank:7.5e3, - rankGain:75, rankLoss:15, hpLoss:100, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Titan"] = new BlackOperation({ - name:"Operation Titan", - desc:"Several months ago Titan Laboratories' Bioengineering department " + - "was infiltrated by Synthoids. As far as we know, Titan Laboratories' " + - "management has no knowledge about this. We don't know what the " + - "Synthoids are up to, but the research that they could " + - "be conducting using Titan Laboraties' vast resources is potentially " + - "very dangerous.

    " + - "Your goal is to enter and destroy the Bioengineering department's " + - "facility in Aevum. The task is not just to retire the Synthoids there, but " + - "also to destroy any information or research at the facility that " + - "is relevant to the Synthoids and their goals.", - baseDifficulty:4000, reqdRank:10e3, - rankGain:100, rankLoss:20, hpLoss:100, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Ares"] = new BlackOperation({ - name:"Operation Ares", - desc:"One of our undercover agents, Agent Carter, has informed us of a " + - "massive weapons deal going down in Dubai between rogue Russian " + - "militants and a radical Synthoid community. These weapons are next-gen " + - "plasma and energy weapons. It is critical for the safety of humanity " + - "that this deal does not happen.

    " + - "Your task is to intercept the deal. Leave no survivors.", - baseDifficulty:5000, reqdRank:12.5e3, - rankGain:125, rankLoss:20, hpLoss:200, - weights:{hack:0,str:0.25,def:0.25,dex:0.25,agi:0.25,cha:0, int:0}, - decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Archangel"] = new BlackOperation({ - name:"Operation Archangel", - desc:"Our analysts have discovered that the popular Red Rabbit brothel in " + - "Amsterdam is run and 'staffed' by MK-VI Synthoids. Intelligence " + - "suggests that the profit from this brothel is used to fund a large " + - "black market arms trafficking operation.

    " + - "The goal of this operation is to take out the leaders that are running " + - "the Red Rabbit brothel. Try to limit the number of other casualties, " + - "but do what you must to complete the mission.", - baseDifficulty:7500, reqdRank:15e3, - rankGain:200, rankLoss:20, hpLoss:25, - weights:{hack:0,str:0.2,def:0.2,dex:0.3,agi:0.3,cha:0, int:0}, - decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Juggernaut"] = new BlackOperation({ - name:"Operation Juggernaut", - desc:"The CIA has just encountered a new security threat. A new " + - "criminal group, lead by a shadowy operative who calls himself " + - "Juggernaut, has been smuggling drugs and weapons (including " + - "suspected bioweapons) into Sector-12. We also have reason " + - "to believe the tried to break into one of Universal Energy's " + - "facilities in order to cause a city-wide blackout. The CIA " + - "suspects that Juggernaut is a heavily-augmented Synthoid, and " + - "have thus enlisted our help.

    " + - "Your mission is to eradicate Juggernaut and his followers.", - baseDifficulty:10e3, reqdRank:20e3, - rankGain:300, rankLoss:40, hpLoss:300, - weights:{hack:0,str:0.25,def:0.25,dex:0.25,agi:0.25,cha:0, int:0}, - decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Red Dragon"] = new BlackOperation({ - name:"Operation Red Dragon", - desc:"The Tetrads criminal organization is suspected of " + - "reverse-engineering the MK-VI Synthoid design. We believe " + - "they altered and possibly improved the design and began " + - "manufacturing their own Synthoid models in order to bolster " + - "their criminal activities.

    " + - "Your task is to infiltrate and destroy the Tetrads' base of operations " + - "in Los Angeles. Intelligence tells us that their base houses " + - "one of their Synthoid manufacturing units.", - baseDifficulty:12.5e3, reqdRank:25e3, - rankGain:500, rankLoss:50, hpLoss:500, - weights:{hack:0.05,str:0.2,def:0.2,dex:0.25,agi:0.25,cha:0, int:0.05}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation K"] = new BlackOperation({ - name:"Operation K", - desc:"CODE RED SITUATION. Our intelligence tells us that VitaLife " + - "has discovered a new android cloning technology. This technology " + - "is supposedly capable of cloning Synthoid, not only physically " + - "but also their advanced AI modules. We do not believe that " + - "VitaLife is trying to use this technology illegally or " + - "maliciously, but if any Synthoids were able to infiltrate the " + - "corporation and take advantage of this technology then the " + - "results would be catastrophic.

    " + - "We do not have the power or jurisdiction to shutdown this down " + - "through legal or political means, so we must resort to a covert " + - "operation. Your goal is to destroy this technology and eliminate " + - "anyone who was involved in its creation.", - baseDifficulty:15e3, reqdRank:30e3, - rankGain:750, rankLoss:60, hpLoss:1000, - weights:{hack:0.05,str:0.2,def:0.2,dex:0.25,agi:0.25,cha:0, int:0.05}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Deckard"] = new BlackOperation({ - name:"Operation Deckard", - desc:"Despite your success in eliminating VitaLife's new android-replicating " + - "technology in Operation K, we've discovered that a small group of " + - "MK-VI Synthoids were able to make off with the schematics and design " + - "of the technology before the Operation. It is almost a certainty that " + - "these Synthoids are some of the rogue MK-VI ones from the Synthoid Uprising.

    " + - "The goal of Operation Deckard is to hunt down these Synthoids and retire " + - "them. I don't need to tell you how critical this mission is.", - baseDifficulty:20e3, reqdRank:40e3, - rankGain:1e3, rankLoss:75, hpLoss:200, - weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, - decays:{hack:0,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Tyrell"] = new BlackOperation({ - name:"Operation Tyrell", - desc:"A week ago Blade Industries reported a small break-in at one " + - "of their Aevum Augmentation storage facitilities. We figured out " + - "that The Dark Army was behind the heist, and didn't think any more " + - "of it. However, we've just discovered that several known MK-VI Synthoids " + - "were part of that break-in group.

    " + - "We cannot have Synthoids upgrading their already-enhanced abilities " + - "with Augmentations. Your task is to hunt down the associated Dark Army " + - "members and eliminate them.", - baseDifficulty:25e3, reqdRank:50e3, - rankGain:1.5e3, rankLoss:100, hpLoss:500, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Wallace"] = new BlackOperation({ - name:"Operation Wallace", - desc:"Based on information gathered from Operation Tyrell, we've discovered " + - "that The Dark Army was well aware that there were Synthoids amongst " + - "their ranks. Even worse, we believe that The Dark Army is working " + - "together with other criminal organizations such as The Syndicate and " + - "that they are planning some sort of large-scale takeover of multiple major " + - "cities, most notably Aevum. We suspect that Synthoids have infiltrated " + - "the ranks of these criminal factions and are trying to stage another " + - "Synthoid uprising.

    " + - "The best way to deal with this is to prevent it before it even happens. " + - "The goal of Operation Wallace is to destroy the Dark Army and " + - "Syndicate factions in Aevum immediately. Leave no survivors.", - baseDifficulty:30e3, reqdRank:75e3, - rankGain:2e3, rankLoss:150, hpLoss:1500, - weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Shoulder of Orion"] = new BlackOperation({ - name:"Operation Shoulder of Orion", - desc:"China's Solaris Space Systems is secretly launching the first " + - "manned spacecraft in over a decade using Synthoids. We believe " + - "China is trying to establish the first off-world colonies.

    " + - "The mission is to prevent this launch without instigating an " + - "international conflict. When you accept this mission you will be " + - "officially disavowed by the NSA and the national government until after you " + - "successfully return. In the event of failure, all of the operation's " + - "team members must not let themselves be captured alive.", - baseDifficulty:35e3, reqdRank:100e3, - rankGain:2.5e3, rankLoss:500, hpLoss:1500, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isStealth:true, - }); - BlackOperations["Operation Hyron"] = new BlackOperation({ - name:"Operation Hyron", - desc:"Our intelligence tells us that Fulcrum Technologies is developing " + - "a quantum supercomputer using human brains as core " + - "processors. This supercomputer " + - "is rumored to be able to store vast amounts of data and " + - "perform computations unmatched by any other supercomputer on the " + - "planet. But more importantly, the use of organic human brains " + - "means that the supercomputer may be able to reason abstractly " + - "and become self-aware.

    " + - "I do not need to remind you why sentient-level AIs pose a serious " + - "threat to all of mankind.

    " + - "The research for this project is being conducted at one of Fulcrum " + - "Technologies secret facilities in Aevum, codenamed 'Alpha Ranch'. " + - "Infiltrate the compound, delete and destroy the work, and then find and kill the " + - "project lead.", - baseDifficulty:40e3, reqdRank:125e3, - rankGain:3e3, rankLoss:1e3, hpLoss:500, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Morpheus"] = new BlackOperation({ - name:"Operation Morpheus", - desc:"DreamSense Technologies is an advertising company that uses " + - "special technology to transmit their ads into the peoples " + - "dreams and subconcious. They do this using broadcast transmitter " + - "towers. Based on information from our agents and informants in " + - "Chonqging, we have reason to believe that one of the broadcast " + - "towers there has been compromised by Synthoids and is being used " + - "to spread pro-Synthoid propaganda.

    " + - "The mission is to destroy this broadcast tower. Speed and " + - "stealth are of the upmost important for this.", - baseDifficulty:45e3, reqdRank:150e3, - rankGain:4e3, rankLoss:1e3, hpLoss:100, - weights:{hack:0.05,str:0.15,def:0.15,dex:0.3,agi:0.3,cha:0, int:0.05}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isStealth:true, - }); - BlackOperations["Operation Ion Storm"] = new BlackOperation({ - name:"Operation Ion Storm", - desc:"Our analysts have uncovered a gathering of MK-VI Synthoids " + - "that have taken up residence in the Sector-12 Slums. We " + - "don't know if they are rogue Synthoids from the Uprising, " + - "but we do know that they have been stockpiling " + - "weapons, money, and other resources. This makes them dangerous.

    " + - "This is a full-scale assault operation to find and retire all of these " + - "Synthoids in the Sector-12 Slums.", - baseDifficulty:50e3, reqdRank:175e3, - rankGain:5e3, rankLoss:1e3, hpLoss:5000, - weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Annihilus"] = new BlackOperation({ - name:"Operation Annihilus", - desc:"Our superiors have ordered us to eradicate everything and everyone " + - "in an underground facility located in Aevum. They tell us " + - "that the facility houses many dangerous Synthoids and " + - "belongs to a terrorist organization called " + - "'The Covenant'. We have no prior intelligence about this " + - "organization, so you are going in blind.", - baseDifficulty:55e3, reqdRank:200e3, - rankGain:7.5e3, rankLoss:1e3, hpLoss:10e3, - weights:{hack:0,str:0.24,def:0.24,dex:0.24,agi:0.24,cha:0, int:0.04}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Ultron"] = new BlackOperation({ - name:"Operation Ultron", - desc:"OmniTek Incorporated, the original designer and manufacturer of Synthoids, " + - "has notified us of a malfunction in their AI design. This malfunction, " + - "when triggered, causes MK-VI Synthoids to become radicalized and seek out " + - "the destruction of humanity. They say that this bug affects all MK-VI Synthoids, " + - "not just the rogue ones from the Uprising.

    " + - "OmniTek has also told us they they believe someone has triggered this " + - "malfunction in a large group of MK-VI Synthoids, and that these newly-radicalized Synthoids " + - "are now amassing in Volhaven to form a terrorist group called Ultron.

    " + - "Intelligence suggests Ultron is heavily armed and that their members are " + - "augmented. We believe Ultron is making moves to take control of " + - "and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).

    " + - "Your task is to find and destroy Ultron.", - baseDifficulty:60e3, reqdRank:250e3, - rankGain:10e3, rankLoss:2e3, hpLoss:10e3, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - isKill:true, - }); - BlackOperations["Operation Centurion"] = new BlackOperation({ - name:"Operation Centurion", - desc:"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

    " + - "Throughout all of humanity's history, we have relied on " + - "technology to survive, conquer, and progress. Its advancement became our primary goal. " + - "And at the peak of human civilization technology turned into " + - "power. Global, absolute power.

    " + - "It seems that the universe is not without a sense of irony.

    " + - "D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)", - baseDifficulty:70e3, reqdRank:300e3, - rankGain:15e3, rankLoss:5e3, hpLoss:10e3, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - }); - BlackOperations["Operation Vindictus"] = new BlackOperation({ - name:"Operation Vindictus", - desc:"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

    " + - "The bits are all around us. The daemons that hold the Node " + - "together can manifest themselves in many different ways.

    " + - "D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)", - baseDifficulty:75e3, reqdRank:350e3, - rankGain:20e3, rankLoss:20e3, hpLoss:20e3, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - }); - BlackOperations["Operation Daedalus"] = new BlackOperation({ - name:"Operation Daedalus", - desc:"Yesterday we obeyed kings and bent our neck to emperors. " + - "Today we kneel only to truth.", - baseDifficulty:80e3, reqdRank:400e3, - rankGain:40e3, rankLoss:10e3, hpLoss:100e3, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.75}, - }); -})() +(function () { + BlackOperations["Operation Typhoon"] = new BlackOperation({ + name: "Operation Typhoon", + desc: + "Obadiah Zenyatta is the leader of a RedWater PMC. It has long " + + "been known among the intelligence community that Zenyatta, along " + + "with the rest of the PMC, is a Synthoid.

    " + + "The goal of Operation Typhoon is to find and eliminate " + + "Zenyatta and RedWater by any means necessary. After the task " + + "is completed, the actions must be covered up from the general public.", + baseDifficulty: 2000, + reqdRank: 2.5e3, + rankGain: 50, + rankLoss: 10, + hpLoss: 100, + weights: { + hack: 0.1, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Zero"] = new BlackOperation({ + name: "Operation Zero", + desc: + "AeroCorp is one of the world's largest defense contractors. " + + "Its leader, Steve Watataki, is thought to be a supporter of " + + "Synthoid rights. He must be removed.

    " + + "The goal of Operation Zero is to covertly infiltrate AeroCorp and " + + "uncover any incriminating evidence or " + + "information against Watataki that will cause him to be removed " + + "from his position at AeroCorp. Incriminating evidence can be " + + "fabricated as a last resort. Be warned that AeroCorp has some of " + + "the most advanced security measures in the world.", + baseDifficulty: 2500, + reqdRank: 5e3, + rankGain: 60, + rankLoss: 15, + hpLoss: 50, + weights: { + hack: 0.2, + str: 0.15, + def: 0.15, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isStealth: true, + }); + BlackOperations["Operation X"] = new BlackOperation({ + name: "Operation X", + desc: + "We have recently discovered an underground publication " + + "group called Samizdat. Even though most of their publications " + + "are nonsensical conspiracy theories, the average human is " + + "gullible enough to believe them. Many of their works discuss " + + "Synthoids and pose a threat to society. The publications are spreading " + + "rapidly in China and other Eastern countries.

    " + + "Samizdat has done a good job of keeping hidden and anonymous. " + + "However, we've just received intelligence that their base of " + + "operations is in Ishima's underground sewer systems. Your task is to " + + "investigate the sewer systems, and eliminate Samizdat. They must " + + "never publish anything again.", + baseDifficulty: 3000, + reqdRank: 7.5e3, + rankGain: 75, + rankLoss: 15, + hpLoss: 100, + weights: { + hack: 0.1, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Titan"] = new BlackOperation({ + name: "Operation Titan", + desc: + "Several months ago Titan Laboratories' Bioengineering department " + + "was infiltrated by Synthoids. As far as we know, Titan Laboratories' " + + "management has no knowledge about this. We don't know what the " + + "Synthoids are up to, but the research that they could " + + "be conducting using Titan Laboraties' vast resources is potentially " + + "very dangerous.

    " + + "Your goal is to enter and destroy the Bioengineering department's " + + "facility in Aevum. The task is not just to retire the Synthoids there, but " + + "also to destroy any information or research at the facility that " + + "is relevant to the Synthoids and their goals.", + baseDifficulty: 4000, + reqdRank: 10e3, + rankGain: 100, + rankLoss: 20, + hpLoss: 100, + weights: { + hack: 0.1, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Ares"] = new BlackOperation({ + name: "Operation Ares", + desc: + "One of our undercover agents, Agent Carter, has informed us of a " + + "massive weapons deal going down in Dubai between rogue Russian " + + "militants and a radical Synthoid community. These weapons are next-gen " + + "plasma and energy weapons. It is critical for the safety of humanity " + + "that this deal does not happen.

    " + + "Your task is to intercept the deal. Leave no survivors.", + baseDifficulty: 5000, + reqdRank: 12.5e3, + rankGain: 125, + rankLoss: 20, + hpLoss: 200, + weights: { + hack: 0, + str: 0.25, + def: 0.25, + dex: 0.25, + agi: 0.25, + cha: 0, + int: 0, + }, + decays: { + hack: 0, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Archangel"] = new BlackOperation({ + name: "Operation Archangel", + desc: + "Our analysts have discovered that the popular Red Rabbit brothel in " + + "Amsterdam is run and 'staffed' by MK-VI Synthoids. Intelligence " + + "suggests that the profit from this brothel is used to fund a large " + + "black market arms trafficking operation.

    " + + "The goal of this operation is to take out the leaders that are running " + + "the Red Rabbit brothel. Try to limit the number of other casualties, " + + "but do what you must to complete the mission.", + baseDifficulty: 7500, + reqdRank: 15e3, + rankGain: 200, + rankLoss: 20, + hpLoss: 25, + weights: { + hack: 0, + str: 0.2, + def: 0.2, + dex: 0.3, + agi: 0.3, + cha: 0, + int: 0, + }, + decays: { + hack: 0, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Juggernaut"] = new BlackOperation({ + name: "Operation Juggernaut", + desc: + "The CIA has just encountered a new security threat. A new " + + "criminal group, lead by a shadowy operative who calls himself " + + "Juggernaut, has been smuggling drugs and weapons (including " + + "suspected bioweapons) into Sector-12. We also have reason " + + "to believe the tried to break into one of Universal Energy's " + + "facilities in order to cause a city-wide blackout. The CIA " + + "suspects that Juggernaut is a heavily-augmented Synthoid, and " + + "have thus enlisted our help.

    " + + "Your mission is to eradicate Juggernaut and his followers.", + baseDifficulty: 10e3, + reqdRank: 20e3, + rankGain: 300, + rankLoss: 40, + hpLoss: 300, + weights: { + hack: 0, + str: 0.25, + def: 0.25, + dex: 0.25, + agi: 0.25, + cha: 0, + int: 0, + }, + decays: { + hack: 0, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Red Dragon"] = new BlackOperation({ + name: "Operation Red Dragon", + desc: + "The Tetrads criminal organization is suspected of " + + "reverse-engineering the MK-VI Synthoid design. We believe " + + "they altered and possibly improved the design and began " + + "manufacturing their own Synthoid models in order to bolster " + + "their criminal activities.

    " + + "Your task is to infiltrate and destroy the Tetrads' base of operations " + + "in Los Angeles. Intelligence tells us that their base houses " + + "one of their Synthoid manufacturing units.", + baseDifficulty: 12.5e3, + reqdRank: 25e3, + rankGain: 500, + rankLoss: 50, + hpLoss: 500, + weights: { + hack: 0.05, + str: 0.2, + def: 0.2, + dex: 0.25, + agi: 0.25, + cha: 0, + int: 0.05, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation K"] = new BlackOperation({ + name: "Operation K", + desc: + "CODE RED SITUATION. Our intelligence tells us that VitaLife " + + "has discovered a new android cloning technology. This technology " + + "is supposedly capable of cloning Synthoid, not only physically " + + "but also their advanced AI modules. We do not believe that " + + "VitaLife is trying to use this technology illegally or " + + "maliciously, but if any Synthoids were able to infiltrate the " + + "corporation and take advantage of this technology then the " + + "results would be catastrophic.

    " + + "We do not have the power or jurisdiction to shutdown this down " + + "through legal or political means, so we must resort to a covert " + + "operation. Your goal is to destroy this technology and eliminate " + + "anyone who was involved in its creation.", + baseDifficulty: 15e3, + reqdRank: 30e3, + rankGain: 750, + rankLoss: 60, + hpLoss: 1000, + weights: { + hack: 0.05, + str: 0.2, + def: 0.2, + dex: 0.25, + agi: 0.25, + cha: 0, + int: 0.05, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Deckard"] = new BlackOperation({ + name: "Operation Deckard", + desc: + "Despite your success in eliminating VitaLife's new android-replicating " + + "technology in Operation K, we've discovered that a small group of " + + "MK-VI Synthoids were able to make off with the schematics and design " + + "of the technology before the Operation. It is almost a certainty that " + + "these Synthoids are some of the rogue MK-VI ones from the Synthoid Uprising.

    " + + "The goal of Operation Deckard is to hunt down these Synthoids and retire " + + "them. I don't need to tell you how critical this mission is.", + baseDifficulty: 20e3, + reqdRank: 40e3, + rankGain: 1e3, + rankLoss: 75, + hpLoss: 200, + weights: { + hack: 0, + str: 0.24, + def: 0.24, + dex: 0.24, + agi: 0.24, + cha: 0, + int: 0.04, + }, + decays: { + hack: 0, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Tyrell"] = new BlackOperation({ + name: "Operation Tyrell", + desc: + "A week ago Blade Industries reported a small break-in at one " + + "of their Aevum Augmentation storage facitilities. We figured out " + + "that The Dark Army was behind the heist, and didn't think any more " + + "of it. However, we've just discovered that several known MK-VI Synthoids " + + "were part of that break-in group.

    " + + "We cannot have Synthoids upgrading their already-enhanced abilities " + + "with Augmentations. Your task is to hunt down the associated Dark Army " + + "members and eliminate them.", + baseDifficulty: 25e3, + reqdRank: 50e3, + rankGain: 1.5e3, + rankLoss: 100, + hpLoss: 500, + weights: { + hack: 0.1, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Wallace"] = new BlackOperation({ + name: "Operation Wallace", + desc: + "Based on information gathered from Operation Tyrell, we've discovered " + + "that The Dark Army was well aware that there were Synthoids amongst " + + "their ranks. Even worse, we believe that The Dark Army is working " + + "together with other criminal organizations such as The Syndicate and " + + "that they are planning some sort of large-scale takeover of multiple major " + + "cities, most notably Aevum. We suspect that Synthoids have infiltrated " + + "the ranks of these criminal factions and are trying to stage another " + + "Synthoid uprising.

    " + + "The best way to deal with this is to prevent it before it even happens. " + + "The goal of Operation Wallace is to destroy the Dark Army and " + + "Syndicate factions in Aevum immediately. Leave no survivors.", + baseDifficulty: 30e3, + reqdRank: 75e3, + rankGain: 2e3, + rankLoss: 150, + hpLoss: 1500, + weights: { + hack: 0, + str: 0.24, + def: 0.24, + dex: 0.24, + agi: 0.24, + cha: 0, + int: 0.04, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Shoulder of Orion"] = new BlackOperation({ + name: "Operation Shoulder of Orion", + desc: + "China's Solaris Space Systems is secretly launching the first " + + "manned spacecraft in over a decade using Synthoids. We believe " + + "China is trying to establish the first off-world colonies.

    " + + "The mission is to prevent this launch without instigating an " + + "international conflict. When you accept this mission you will be " + + "officially disavowed by the NSA and the national government until after you " + + "successfully return. In the event of failure, all of the operation's " + + "team members must not let themselves be captured alive.", + baseDifficulty: 35e3, + reqdRank: 100e3, + rankGain: 2.5e3, + rankLoss: 500, + hpLoss: 1500, + weights: { + hack: 0.1, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isStealth: true, + }); + BlackOperations["Operation Hyron"] = new BlackOperation({ + name: "Operation Hyron", + desc: + "Our intelligence tells us that Fulcrum Technologies is developing " + + "a quantum supercomputer using human brains as core " + + "processors. This supercomputer " + + "is rumored to be able to store vast amounts of data and " + + "perform computations unmatched by any other supercomputer on the " + + "planet. But more importantly, the use of organic human brains " + + "means that the supercomputer may be able to reason abstractly " + + "and become self-aware.

    " + + "I do not need to remind you why sentient-level AIs pose a serious " + + "threat to all of mankind.

    " + + "The research for this project is being conducted at one of Fulcrum " + + "Technologies secret facilities in Aevum, codenamed 'Alpha Ranch'. " + + "Infiltrate the compound, delete and destroy the work, and then find and kill the " + + "project lead.", + baseDifficulty: 40e3, + reqdRank: 125e3, + rankGain: 3e3, + rankLoss: 1e3, + hpLoss: 500, + weights: { + hack: 0.1, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Morpheus"] = new BlackOperation({ + name: "Operation Morpheus", + desc: + "DreamSense Technologies is an advertising company that uses " + + "special technology to transmit their ads into the peoples " + + "dreams and subconcious. They do this using broadcast transmitter " + + "towers. Based on information from our agents and informants in " + + "Chonqging, we have reason to believe that one of the broadcast " + + "towers there has been compromised by Synthoids and is being used " + + "to spread pro-Synthoid propaganda.

    " + + "The mission is to destroy this broadcast tower. Speed and " + + "stealth are of the upmost important for this.", + baseDifficulty: 45e3, + reqdRank: 150e3, + rankGain: 4e3, + rankLoss: 1e3, + hpLoss: 100, + weights: { + hack: 0.05, + str: 0.15, + def: 0.15, + dex: 0.3, + agi: 0.3, + cha: 0, + int: 0.05, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isStealth: true, + }); + BlackOperations["Operation Ion Storm"] = new BlackOperation({ + name: "Operation Ion Storm", + desc: + "Our analysts have uncovered a gathering of MK-VI Synthoids " + + "that have taken up residence in the Sector-12 Slums. We " + + "don't know if they are rogue Synthoids from the Uprising, " + + "but we do know that they have been stockpiling " + + "weapons, money, and other resources. This makes them dangerous.

    " + + "This is a full-scale assault operation to find and retire all of these " + + "Synthoids in the Sector-12 Slums.", + baseDifficulty: 50e3, + reqdRank: 175e3, + rankGain: 5e3, + rankLoss: 1e3, + hpLoss: 5000, + weights: { + hack: 0, + str: 0.24, + def: 0.24, + dex: 0.24, + agi: 0.24, + cha: 0, + int: 0.04, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Annihilus"] = new BlackOperation({ + name: "Operation Annihilus", + desc: + "Our superiors have ordered us to eradicate everything and everyone " + + "in an underground facility located in Aevum. They tell us " + + "that the facility houses many dangerous Synthoids and " + + "belongs to a terrorist organization called " + + "'The Covenant'. We have no prior intelligence about this " + + "organization, so you are going in blind.", + baseDifficulty: 55e3, + reqdRank: 200e3, + rankGain: 7.5e3, + rankLoss: 1e3, + hpLoss: 10e3, + weights: { + hack: 0, + str: 0.24, + def: 0.24, + dex: 0.24, + agi: 0.24, + cha: 0, + int: 0.04, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Ultron"] = new BlackOperation({ + name: "Operation Ultron", + desc: + "OmniTek Incorporated, the original designer and manufacturer of Synthoids, " + + "has notified us of a malfunction in their AI design. This malfunction, " + + "when triggered, causes MK-VI Synthoids to become radicalized and seek out " + + "the destruction of humanity. They say that this bug affects all MK-VI Synthoids, " + + "not just the rogue ones from the Uprising.

    " + + "OmniTek has also told us they they believe someone has triggered this " + + "malfunction in a large group of MK-VI Synthoids, and that these newly-radicalized Synthoids " + + "are now amassing in Volhaven to form a terrorist group called Ultron.

    " + + "Intelligence suggests Ultron is heavily armed and that their members are " + + "augmented. We believe Ultron is making moves to take control of " + + "and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).

    " + + "Your task is to find and destroy Ultron.", + baseDifficulty: 60e3, + reqdRank: 250e3, + rankGain: 10e3, + rankLoss: 2e3, + hpLoss: 10e3, + weights: { + hack: 0.1, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + isKill: true, + }); + BlackOperations["Operation Centurion"] = new BlackOperation({ + name: "Operation Centurion", + desc: + "D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

    " + + "Throughout all of humanity's history, we have relied on " + + "technology to survive, conquer, and progress. Its advancement became our primary goal. " + + "And at the peak of human civilization technology turned into " + + "power. Global, absolute power.

    " + + "It seems that the universe is not without a sense of irony.

    " + + "D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)", + baseDifficulty: 70e3, + reqdRank: 300e3, + rankGain: 15e3, + rankLoss: 5e3, + hpLoss: 10e3, + weights: { + hack: 0.1, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + }); + BlackOperations["Operation Vindictus"] = new BlackOperation({ + name: "Operation Vindictus", + desc: + "D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)

    " + + "The bits are all around us. The daemons that hold the Node " + + "together can manifest themselves in many different ways.

    " + + "D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)", + baseDifficulty: 75e3, + reqdRank: 350e3, + rankGain: 20e3, + rankLoss: 20e3, + hpLoss: 20e3, + weights: { + hack: 0.1, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + }); + BlackOperations["Operation Daedalus"] = new BlackOperation({ + name: "Operation Daedalus", + desc: + "Yesterday we obeyed kings and bent our neck to emperors. " + + "Today we kneel only to truth.", + baseDifficulty: 80e3, + reqdRank: 400e3, + rankGain: 40e3, + rankLoss: 10e3, + hpLoss: 100e3, + weights: { + hack: 0.1, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.75, + }, + }); +})(); diff --git a/src/Bladeburner/Bladeburner.ts b/src/Bladeburner/Bladeburner.ts index eaa520533..fa9d402bc 100644 --- a/src/Bladeburner/Bladeburner.ts +++ b/src/Bladeburner/Bladeburner.ts @@ -1,7 +1,7 @@ import { - Reviver, - Generic_toJSON, - Generic_fromJSON, + Reviver, + Generic_toJSON, + Generic_fromJSON, } from "../../utils/JSONReviver"; import { IBladeburner } from "./IBladeburner"; import { IActionIdentifier } from "./IActionIdentifier"; @@ -38,1972 +38,2689 @@ import { joinFaction } from "../Faction/FactionHelpers"; import { WorkerScript } from "../Netscript/WorkerScript"; export class Bladeburner implements IBladeburner { - numHosp = 0; - moneyLost = 0; - rank = 0; - maxRank = 0; + numHosp = 0; + moneyLost = 0; + rank = 0; + maxRank = 0; - skillPoints = 0; - totalSkillPoints = 0; + skillPoints = 0; + totalSkillPoints = 0; - teamSize = 0; - teamLost = 0; - hpLost = 0; + teamSize = 0; + teamLost = 0; + hpLost = 0; - storedCycles = 0; + storedCycles = 0; - randomEventCounter: number = getRandomInt(240, 600); + randomEventCounter: number = getRandomInt(240, 600); - actionTimeToComplete = 0; - actionTimeCurrent = 0; - actionTimeOverflow = 0; + actionTimeToComplete = 0; + actionTimeCurrent = 0; + actionTimeOverflow = 0; - action: IActionIdentifier = new ActionIdentifier({type: ActionTypes["Idle"]}); + action: IActionIdentifier = new ActionIdentifier({ + type: ActionTypes["Idle"], + }); - cities: any = {}; - city: string = BladeburnerConstants.CityNames[2]; - skills: any = {}; - skillMultipliers: any = {}; - staminaBonus = 0; - maxStamina = 0; - stamina = 0; - contracts: any = {}; - operations: any = {}; - blackops: any = {}; - logging: any = { - general:true, - contracts:true, - ops:true, - blackops:true, - events:true, - }; - automateEnabled = false; - automateActionHigh: IActionIdentifier = new ActionIdentifier({type: ActionTypes["Idle"]}); - automateThreshHigh = 0; - automateActionLow: IActionIdentifier = new ActionIdentifier({type: ActionTypes["Idle"]}); - automateThreshLow = 0; - consoleHistory: string[] = []; - consoleLogs: string[] = [ - "Bladeburner Console", - "Type 'help' to see console commands", - ]; + cities: any = {}; + city: string = BladeburnerConstants.CityNames[2]; + skills: any = {}; + skillMultipliers: any = {}; + staminaBonus = 0; + maxStamina = 0; + stamina = 0; + contracts: any = {}; + operations: any = {}; + blackops: any = {}; + logging: any = { + general: true, + contracts: true, + ops: true, + blackops: true, + events: true, + }; + automateEnabled = false; + automateActionHigh: IActionIdentifier = new ActionIdentifier({ + type: ActionTypes["Idle"], + }); + automateThreshHigh = 0; + automateActionLow: IActionIdentifier = new ActionIdentifier({ + type: ActionTypes["Idle"], + }); + automateThreshLow = 0; + consoleHistory: string[] = []; + consoleLogs: string[] = [ + "Bladeburner Console", + "Type 'help' to see console commands", + ]; - constructor(player?: IPlayer) { - for (let i = 0; i < BladeburnerConstants.CityNames.length; ++i) { - this.cities[BladeburnerConstants.CityNames[i]] = new City(BladeburnerConstants.CityNames[i]); - } - - this.updateSkillMultipliers(); // Calls resetSkillMultipliers() - - // Max Stamina is based on stats and Bladeburner-specific bonuses - if(player) - this.calculateMaxStamina(player); - this.stamina = this.maxStamina; - this.create(); + constructor(player?: IPlayer) { + for (let i = 0; i < BladeburnerConstants.CityNames.length; ++i) { + this.cities[BladeburnerConstants.CityNames[i]] = new City( + BladeburnerConstants.CityNames[i], + ); } - getCurrentCity(): City { - const city = this.cities[this.city]; - if (!(city instanceof City)) { - throw new Error("Bladeburner.getCurrentCity() did not properly return a City object"); - } - return city; + this.updateSkillMultipliers(); // Calls resetSkillMultipliers() + + // Max Stamina is based on stats and Bladeburner-specific bonuses + if (player) this.calculateMaxStamina(player); + this.stamina = this.maxStamina; + this.create(); + } + + getCurrentCity(): City { + const city = this.cities[this.city]; + if (!(city instanceof City)) { + throw new Error( + "Bladeburner.getCurrentCity() did not properly return a City object", + ); } + return city; + } - calculateStaminaPenalty(): number { - return Math.min(1, this.stamina / (0.5 * this.maxStamina)); - } + calculateStaminaPenalty(): number { + return Math.min(1, this.stamina / (0.5 * this.maxStamina)); + } - startAction(player: IPlayer, actionId: IActionIdentifier): void { - if (actionId == null) return; - this.action = actionId; - this.actionTimeCurrent = 0; - switch (actionId.type) { - case ActionTypes["Idle"]: - this.actionTimeToComplete = 0; - break; - case ActionTypes["Contract"]: - try { - const action = this.getActionObject(actionId); - if (action == null) { - throw new Error("Failed to get Contract Object for: " + actionId.name); - } - if (action.count < 1) {return this.resetAction();} - this.actionTimeToComplete = action.getActionTime(this); - } catch(e) { - exceptionAlert(e); - } - break; - case ActionTypes["Operation"]: { - try { - const action = this.getActionObject(actionId); - if (action == null) { - throw new Error ("Failed to get Operation Object for: " + actionId.name); - } - if (action.count < 1) {return this.resetAction();} - if (actionId.name === "Raid" && this.getCurrentCity().commsEst === 0) {return this.resetAction();} - this.actionTimeToComplete = action.getActionTime(this); - } catch(e) { - exceptionAlert(e); - } - break; - } - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: { - try { - // Safety measure - don't repeat BlackOps that are already done - if (this.blackops[actionId.name] != null) { - this.resetAction(); - this.log("Error: Tried to start a Black Operation that had already been completed"); - break; - } - - const action = this.getActionObject(actionId); - if (action == null) { - throw new Error("Failed to get BlackOperation object for: " + actionId.name); - } - this.actionTimeToComplete = action.getActionTime(this); - } catch(e) { - exceptionAlert(e); - } - break; - } - case ActionTypes["Recruitment"]: - this.actionTimeToComplete = this.getRecruitmentTime(player); - break; - case ActionTypes["Training"]: - case ActionTypes["FieldAnalysis"]: - case ActionTypes["Field Analysis"]: - this.actionTimeToComplete = 30; - break; - case ActionTypes["Diplomacy"]: - case ActionTypes["Hyperbolic Regeneration Chamber"]: - this.actionTimeToComplete = 60; - break; - default: - throw new Error("Invalid Action Type in startAction(Bladeburner,player, ): " + actionId.type); - break; - } - } - - upgradeSkill(skill: Skill): void { - // This does NOT handle deduction of skill points - const skillName = skill.name; - if (this.skills[skillName]) { - ++this.skills[skillName]; - } else { - this.skills[skillName] = 1; - } - if (isNaN(this.skills[skillName]) || this.skills[skillName] < 0) { - throw new Error("Level of Skill " + skillName + " is invalid: " + this.skills[skillName]); - } - this.updateSkillMultipliers(); - } - - executeConsoleCommands(player: IPlayer, commands: string): void { + startAction(player: IPlayer, actionId: IActionIdentifier): void { + if (actionId == null) return; + this.action = actionId; + this.actionTimeCurrent = 0; + switch (actionId.type) { + case ActionTypes["Idle"]: + this.actionTimeToComplete = 0; + break; + case ActionTypes["Contract"]: try { - // Console History - if (this.consoleHistory[this.consoleHistory.length-1] != commands) { - this.consoleHistory.push(commands); - if (this.consoleHistory.length > 50) { - this.consoleHistory.splice(0, 1); - } - } - - const arrayOfCommands = commands.split(";"); - for (let i = 0; i < arrayOfCommands.length; ++i) { - this.executeConsoleCommand(player, arrayOfCommands[i]); - } - } catch(e) { - exceptionAlert(e); + const action = this.getActionObject(actionId); + if (action == null) { + throw new Error( + "Failed to get Contract Object for: " + actionId.name, + ); + } + if (action.count < 1) { + return this.resetAction(); + } + this.actionTimeToComplete = action.getActionTime(this); + } catch (e) { + exceptionAlert(e); } - } - - postToConsole(input: string, saveToLogs = true): void { - const MaxConsoleEntries = 100; - if (saveToLogs) { - this.consoleLogs.push(input); - if (this.consoleLogs.length > MaxConsoleEntries) { - this.consoleLogs.shift(); - } + break; + case ActionTypes["Operation"]: { + try { + const action = this.getActionObject(actionId); + if (action == null) { + throw new Error( + "Failed to get Operation Object for: " + actionId.name, + ); + } + if (action.count < 1) { + return this.resetAction(); + } + if ( + actionId.name === "Raid" && + this.getCurrentCity().commsEst === 0 + ) { + return this.resetAction(); + } + this.actionTimeToComplete = action.getActionTime(this); + } catch (e) { + exceptionAlert(e); } - } + break; + } + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: { + try { + // Safety measure - don't repeat BlackOps that are already done + if (this.blackops[actionId.name] != null) { + this.resetAction(); + this.log( + "Error: Tried to start a Black Operation that had already been completed", + ); + break; + } - log(input: string): void { - // Adds a timestamp and then just calls postToConsole - this.postToConsole(`[${getTimestamp()}] ${input}`); - } - - resetAction(): void { - this.action = new ActionIdentifier({type:ActionTypes.Idle}); - } - - clearConsole(): void { - this.consoleLogs.length = 0; - } - - prestige(): void { - this.resetAction(); - const bladeburnerFac = Factions["Bladeburners"]; - if (this.rank >= BladeburnerConstants.RankNeededForFaction) { - joinFaction(bladeburnerFac); + const action = this.getActionObject(actionId); + if (action == null) { + throw new Error( + "Failed to get BlackOperation object for: " + actionId.name, + ); + } + this.actionTimeToComplete = action.getActionTime(this); + } catch (e) { + exceptionAlert(e); } + break; + } + case ActionTypes["Recruitment"]: + this.actionTimeToComplete = this.getRecruitmentTime(player); + break; + case ActionTypes["Training"]: + case ActionTypes["FieldAnalysis"]: + case ActionTypes["Field Analysis"]: + this.actionTimeToComplete = 30; + break; + case ActionTypes["Diplomacy"]: + case ActionTypes["Hyperbolic Regeneration Chamber"]: + this.actionTimeToComplete = 60; + break; + default: + throw new Error( + "Invalid Action Type in startAction(Bladeburner,player, ): " + + actionId.type, + ); + break; } + } - storeCycles(numCycles = 0): void { - this.storedCycles += numCycles; + upgradeSkill(skill: Skill): void { + // This does NOT handle deduction of skill points + const skillName = skill.name; + if (this.skills[skillName]) { + ++this.skills[skillName]; + } else { + this.skills[skillName] = 1; } + if (isNaN(this.skills[skillName]) || this.skills[skillName] < 0) { + throw new Error( + "Level of Skill " + + skillName + + " is invalid: " + + this.skills[skillName], + ); + } + this.updateSkillMultipliers(); + } - - // working on - getActionIdFromTypeAndName(type = "", name = ""): IActionIdentifier | null { - if (type === "" || name === "") {return null;} - const action = new ActionIdentifier(); - const convertedType = type.toLowerCase().trim(); - const convertedName = name.toLowerCase().trim(); - switch (convertedType) { - case "contract": - case "contracts": - case "contr": - action.type = ActionTypes["Contract"]; - if (this.contracts.hasOwnProperty(name)) { - action.name = name; - return action; - } else { - return null; - } - break; - case "operation": - case "operations": - case "op": - case "ops": - action.type = ActionTypes["Operation"]; - if (this.operations.hasOwnProperty(name)) { - action.name = name; - return action; - } else { - return null; - } - break; - case "blackoperation": - case "black operation": - case "black operations": - case "black op": - case "black ops": - case "blackop": - case "blackops": - action.type = ActionTypes["BlackOp"]; - if (BlackOperations.hasOwnProperty(name)) { - action.name = name; - return action; - } else { - return null; - } - break; - case "general": - case "general action": - case "gen": - break; - default: - return null; + executeConsoleCommands(player: IPlayer, commands: string): void { + try { + // Console History + if (this.consoleHistory[this.consoleHistory.length - 1] != commands) { + this.consoleHistory.push(commands); + if (this.consoleHistory.length > 50) { + this.consoleHistory.splice(0, 1); } + } - if (convertedType.startsWith("gen")) { - switch (convertedName) { - case "training": - action.type = ActionTypes["Training"]; - action.name = "Training"; - break; - case "recruitment": - case "recruit": - action.type = ActionTypes["Recruitment"]; - action.name = "Recruitment"; - break; - case "field analysis": - case "fieldanalysis": - action.type = ActionTypes["Field Analysis"]; - action.name = "Field Analysis"; - break; - case "diplomacy": - action.type = ActionTypes["Diplomacy"]; - action.name = "Diplomacy"; - break; - case "hyperbolic regeneration chamber": - action.type = ActionTypes["Hyperbolic Regeneration Chamber"]; - action.name = "Hyperbolic Regeneration Chamber"; - break; - default: - return null; - } - return action; + const arrayOfCommands = commands.split(";"); + for (let i = 0; i < arrayOfCommands.length; ++i) { + this.executeConsoleCommand(player, arrayOfCommands[i]); + } + } catch (e) { + exceptionAlert(e); + } + } + + postToConsole(input: string, saveToLogs = true): void { + const MaxConsoleEntries = 100; + if (saveToLogs) { + this.consoleLogs.push(input); + if (this.consoleLogs.length > MaxConsoleEntries) { + this.consoleLogs.shift(); + } + } + } + + log(input: string): void { + // Adds a timestamp and then just calls postToConsole + this.postToConsole(`[${getTimestamp()}] ${input}`); + } + + resetAction(): void { + this.action = new ActionIdentifier({ type: ActionTypes.Idle }); + } + + clearConsole(): void { + this.consoleLogs.length = 0; + } + + prestige(): void { + this.resetAction(); + const bladeburnerFac = Factions["Bladeburners"]; + if (this.rank >= BladeburnerConstants.RankNeededForFaction) { + joinFaction(bladeburnerFac); + } + } + + storeCycles(numCycles = 0): void { + this.storedCycles += numCycles; + } + + // working on + getActionIdFromTypeAndName(type = "", name = ""): IActionIdentifier | null { + if (type === "" || name === "") { + return null; + } + const action = new ActionIdentifier(); + const convertedType = type.toLowerCase().trim(); + const convertedName = name.toLowerCase().trim(); + switch (convertedType) { + case "contract": + case "contracts": + case "contr": + action.type = ActionTypes["Contract"]; + if (this.contracts.hasOwnProperty(name)) { + action.name = name; + return action; + } else { + return null; } - + break; + case "operation": + case "operations": + case "op": + case "ops": + action.type = ActionTypes["Operation"]; + if (this.operations.hasOwnProperty(name)) { + action.name = name; + return action; + } else { + return null; + } + break; + case "blackoperation": + case "black operation": + case "black operations": + case "black op": + case "black ops": + case "blackop": + case "blackops": + action.type = ActionTypes["BlackOp"]; + if (BlackOperations.hasOwnProperty(name)) { + action.name = name; + return action; + } else { + return null; + } + break; + case "general": + case "general action": + case "gen": + break; + default: return null; } - executeStartConsoleCommand(player: IPlayer, args: string[]): void { - if (args.length !== 3) { - this.postToConsole("Invalid usage of 'start' console command: start [type] [name]"); - this.postToConsole("Use 'help start' for more info"); - return; - } - const name = args[2]; - switch (args[1].toLowerCase()) { - case "general": - case "gen": - if (GeneralActions[name] != null) { - this.action.type = ActionTypes[name]; - this.action.name = name; - this.startAction(player, this.action); - } else { - this.postToConsole("Invalid action name specified: " + args[2]); - } - break; - case "contract": - case "contracts": - if (this.contracts[name] != null) { - this.action.type = ActionTypes.Contract; - this.action.name = name; - this.startAction(player, this.action); - } else { - this.postToConsole("Invalid contract name specified: " + args[2]); - } - break; - case "ops": - case "op": - case "operations": - case "operation": - if (this.operations[name] != null) { - this.action.type = ActionTypes.Operation; - this.action.name = name; - this.startAction(player, this.action); - } else { - this.postToConsole("Invalid Operation name specified: " + args[2]); - } - break; - case "blackops": - case "blackop": - case "black operations": - case "black operation": - if (BlackOperations[name] != null) { - this.action.type = ActionTypes.BlackOperation; - this.action.name = name; - this.startAction(player, this.action); - } else { - this.postToConsole("Invalid BlackOp name specified: " + args[2]); - } - break; - default: - this.postToConsole("Invalid action/event type specified: " + args[1]); - this.postToConsole("Examples of valid action/event identifiers are: [general, contract, op, blackop]"); - break; - } + if (convertedType.startsWith("gen")) { + switch (convertedName) { + case "training": + action.type = ActionTypes["Training"]; + action.name = "Training"; + break; + case "recruitment": + case "recruit": + action.type = ActionTypes["Recruitment"]; + action.name = "Recruitment"; + break; + case "field analysis": + case "fieldanalysis": + action.type = ActionTypes["Field Analysis"]; + action.name = "Field Analysis"; + break; + case "diplomacy": + action.type = ActionTypes["Diplomacy"]; + action.name = "Diplomacy"; + break; + case "hyperbolic regeneration chamber": + action.type = ActionTypes["Hyperbolic Regeneration Chamber"]; + action.name = "Hyperbolic Regeneration Chamber"; + break; + default: + return null; + } + return action; } - executeSkillConsoleCommand(args: string[]): void { - switch (args.length) { - case 1: { - // Display Skill Help Command - this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - this.postToConsole("Use 'help skill' for more info"); - break; - } - case 2: { - if (args[1].toLowerCase() === "list") { - // List all skills and their level - this.postToConsole("Skills: "); - const skillNames = Object.keys(Skills); - for(let i = 0; i < skillNames.length; ++i) { - const skill = Skills[skillNames[i]]; - let level = 0; - if (this.skills[skill.name] != null) {level = this.skills[skill.name];} - this.postToConsole(skill.name + ": Level " + formatNumber(level, 0)); - } - this.postToConsole(" "); - this.postToConsole("Effects: "); - const multKeys = Object.keys(this.skillMultipliers); - for (let i = 0; i < multKeys.length; ++i) { - let mult = this.skillMultipliers[multKeys[i]]; - if (mult && mult !== 1) { - mult = formatNumber(mult, 3); - switch(multKeys[i]) { - case "successChanceAll": - this.postToConsole("Total Success Chance: x" + mult); - break; - case "successChanceStealth": - this.postToConsole("Stealth Success Chance: x" + mult); - break; - case "successChanceKill": - this.postToConsole("Retirement Success Chance: x" + mult); - break; - case "successChanceContract": - this.postToConsole("Contract Success Chance: x" + mult); - break; - case "successChanceOperation": - this.postToConsole("Operation Success Chance: x" + mult); - break; - case "successChanceEstimate": - this.postToConsole("Synthoid Data Estimate: x" + mult); - break; - case "actionTime": - this.postToConsole("Action Time: x" + mult); - break; - case "effHack": - this.postToConsole("Hacking Skill: x" + mult); - break; - case "effStr": - this.postToConsole("Strength: x" + mult); - break; - case "effDef": - this.postToConsole("Defense: x" + mult); - break; - case "effDex": - this.postToConsole("Dexterity: x" + mult); - break; - case "effAgi": - this.postToConsole("Agility: x" + mult); - break; - case "effCha": - this.postToConsole("Charisma: x" + mult); - break; - case "effInt": - this.postToConsole("Intelligence: x" + mult); - break; - case "stamina": - this.postToConsole("Stamina: x" + mult); - break; - default: - console.warn(`Unrecognized SkillMult Key: ${multKeys[i]}`); - break; - } - } - } - } else { - this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - this.postToConsole("Use 'help skill' for more info"); - } - break; - } - case 3: { - const skillName = args[2]; - const skill = Skills[skillName]; - if (skill == null || !(skill instanceof Skill)) { - this.postToConsole("Invalid skill name (Note that it is case-sensitive): " + skillName); - } - if (args[1].toLowerCase() === "list") { - let level = 0; - if (this.skills[skill.name] !== undefined) { - level = this.skills[skill.name]; - } - this.postToConsole(skill.name + ": Level " + formatNumber(level)); - } else if (args[1].toLowerCase() === "level") { - let currentLevel = 0; - if (this.skills[skillName] && !isNaN(this.skills[skillName])) { - currentLevel = this.skills[skillName]; - } - const pointCost = skill.calculateCost(currentLevel); - if (this.skillPoints >= pointCost) { - this.skillPoints -= pointCost; - this.upgradeSkill(skill); - this.log(skill.name + " upgraded to Level " + this.skills[skillName]); - } else { - this.postToConsole("You do not have enough Skill Points to upgrade this. You need " + formatNumber(pointCost, 0)); - } + return null; + } - } else { - this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - this.postToConsole("Use 'help skill' for more info"); - } - break; - } - default: { - this.postToConsole("Invalid usage of 'skill' console command: skill [action] [name]"); - this.postToConsole("Use 'help skill' for more info"); - break; - } - } + executeStartConsoleCommand(player: IPlayer, args: string[]): void { + if (args.length !== 3) { + this.postToConsole( + "Invalid usage of 'start' console command: start [type] [name]", + ); + this.postToConsole("Use 'help start' for more info"); + return; } - - executeLogConsoleCommand(args: string[]): void { - if (args.length < 3) { - this.postToConsole("Invalid usage of log command: log [enable/disable] [action/event]"); - this.postToConsole("Use 'help log' for more details and examples"); - return; + const name = args[2]; + switch (args[1].toLowerCase()) { + case "general": + case "gen": + if (GeneralActions[name] != null) { + this.action.type = ActionTypes[name]; + this.action.name = name; + this.startAction(player, this.action); + } else { + this.postToConsole("Invalid action name specified: " + args[2]); } - - let flag = true; - if (args[1].toLowerCase().includes("d")) {flag = false;} // d for disable - - switch (args[2].toLowerCase()) { - case "general": - case "gen": - this.logging.general = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for general actions"); - break; - case "contract": - case "contracts": - this.logging.contracts = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for Contracts"); - break; - case "ops": - case "op": - case "operations": - case "operation": - this.logging.ops = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for Operations"); - break; - case "blackops": - case "blackop": - case "black operations": - case "black operation": - this.logging.blackops = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for BlackOps"); - break; - case "event": - case "events": - this.logging.events = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for events"); - break; - case "all": - this.logging.general = flag; - this.logging.contracts = flag; - this.logging.ops = flag; - this.logging.blackops = flag; - this.logging.events = flag; - this.log("Logging " + (flag ? "enabled" : "disabled") + " for everything"); - break; - default: - this.postToConsole("Invalid action/event type specified: " + args[2]); - this.postToConsole("Examples of valid action/event identifiers are: [general, contracts, ops, blackops, events]"); - break; + break; + case "contract": + case "contracts": + if (this.contracts[name] != null) { + this.action.type = ActionTypes.Contract; + this.action.name = name; + this.startAction(player, this.action); + } else { + this.postToConsole("Invalid contract name specified: " + args[2]); } + break; + case "ops": + case "op": + case "operations": + case "operation": + if (this.operations[name] != null) { + this.action.type = ActionTypes.Operation; + this.action.name = name; + this.startAction(player, this.action); + } else { + this.postToConsole("Invalid Operation name specified: " + args[2]); + } + break; + case "blackops": + case "blackop": + case "black operations": + case "black operation": + if (BlackOperations[name] != null) { + this.action.type = ActionTypes.BlackOperation; + this.action.name = name; + this.startAction(player, this.action); + } else { + this.postToConsole("Invalid BlackOp name specified: " + args[2]); + } + break; + default: + this.postToConsole("Invalid action/event type specified: " + args[1]); + this.postToConsole( + "Examples of valid action/event identifiers are: [general, contract, op, blackop]", + ); + break; } + } - executeHelpConsoleCommand(args: string[]): void { - if (args.length === 1) { - for(const line of ConsoleHelpText.helpList){ - this.postToConsole(line); + executeSkillConsoleCommand(args: string[]): void { + switch (args.length) { + case 1: { + // Display Skill Help Command + this.postToConsole( + "Invalid usage of 'skill' console command: skill [action] [name]", + ); + this.postToConsole("Use 'help skill' for more info"); + break; + } + case 2: { + if (args[1].toLowerCase() === "list") { + // List all skills and their level + this.postToConsole("Skills: "); + const skillNames = Object.keys(Skills); + for (let i = 0; i < skillNames.length; ++i) { + const skill = Skills[skillNames[i]]; + let level = 0; + if (this.skills[skill.name] != null) { + level = this.skills[skill.name]; + } + this.postToConsole( + skill.name + ": Level " + formatNumber(level, 0), + ); + } + this.postToConsole(" "); + this.postToConsole("Effects: "); + const multKeys = Object.keys(this.skillMultipliers); + for (let i = 0; i < multKeys.length; ++i) { + let mult = this.skillMultipliers[multKeys[i]]; + if (mult && mult !== 1) { + mult = formatNumber(mult, 3); + switch (multKeys[i]) { + case "successChanceAll": + this.postToConsole("Total Success Chance: x" + mult); + break; + case "successChanceStealth": + this.postToConsole("Stealth Success Chance: x" + mult); + break; + case "successChanceKill": + this.postToConsole("Retirement Success Chance: x" + mult); + break; + case "successChanceContract": + this.postToConsole("Contract Success Chance: x" + mult); + break; + case "successChanceOperation": + this.postToConsole("Operation Success Chance: x" + mult); + break; + case "successChanceEstimate": + this.postToConsole("Synthoid Data Estimate: x" + mult); + break; + case "actionTime": + this.postToConsole("Action Time: x" + mult); + break; + case "effHack": + this.postToConsole("Hacking Skill: x" + mult); + break; + case "effStr": + this.postToConsole("Strength: x" + mult); + break; + case "effDef": + this.postToConsole("Defense: x" + mult); + break; + case "effDex": + this.postToConsole("Dexterity: x" + mult); + break; + case "effAgi": + this.postToConsole("Agility: x" + mult); + break; + case "effCha": + this.postToConsole("Charisma: x" + mult); + break; + case "effInt": + this.postToConsole("Intelligence: x" + mult); + break; + case "stamina": + this.postToConsole("Stamina: x" + mult); + break; + default: + console.warn(`Unrecognized SkillMult Key: ${multKeys[i]}`); + break; + } + } } } else { - for (let i = 1; i < args.length; ++i) { - if(!(args[i] in ConsoleHelpText)) continue; - const helpText = ConsoleHelpText[args[i]]; - for(const line of helpText){ - this.postToConsole(line); - } - } + this.postToConsole( + "Invalid usage of 'skill' console command: skill [action] [name]", + ); + this.postToConsole("Use 'help skill' for more info"); } + break; + } + case 3: { + const skillName = args[2]; + const skill = Skills[skillName]; + if (skill == null || !(skill instanceof Skill)) { + this.postToConsole( + "Invalid skill name (Note that it is case-sensitive): " + skillName, + ); + } + if (args[1].toLowerCase() === "list") { + let level = 0; + if (this.skills[skill.name] !== undefined) { + level = this.skills[skill.name]; + } + this.postToConsole(skill.name + ": Level " + formatNumber(level)); + } else if (args[1].toLowerCase() === "level") { + let currentLevel = 0; + if (this.skills[skillName] && !isNaN(this.skills[skillName])) { + currentLevel = this.skills[skillName]; + } + const pointCost = skill.calculateCost(currentLevel); + if (this.skillPoints >= pointCost) { + this.skillPoints -= pointCost; + this.upgradeSkill(skill); + this.log( + skill.name + " upgraded to Level " + this.skills[skillName], + ); + } else { + this.postToConsole( + "You do not have enough Skill Points to upgrade this. You need " + + formatNumber(pointCost, 0), + ); + } + } else { + this.postToConsole( + "Invalid usage of 'skill' console command: skill [action] [name]", + ); + this.postToConsole("Use 'help skill' for more info"); + } + break; + } + default: { + this.postToConsole( + "Invalid usage of 'skill' console command: skill [action] [name]", + ); + this.postToConsole("Use 'help skill' for more info"); + break; + } + } + } + + executeLogConsoleCommand(args: string[]): void { + if (args.length < 3) { + this.postToConsole( + "Invalid usage of log command: log [enable/disable] [action/event]", + ); + this.postToConsole("Use 'help log' for more details and examples"); + return; } - executeAutomateConsoleCommand(args: string[]): void { - if (args.length !== 2 && args.length !== 4) { - this.postToConsole("Invalid use of 'automate' command: automate [var] [val] [hi/low]. Use 'help automate' for more info"); - return; + let flag = true; + if (args[1].toLowerCase().includes("d")) { + flag = false; + } // d for disable + + switch (args[2].toLowerCase()) { + case "general": + case "gen": + this.logging.general = flag; + this.log( + "Logging " + (flag ? "enabled" : "disabled") + " for general actions", + ); + break; + case "contract": + case "contracts": + this.logging.contracts = flag; + this.log( + "Logging " + (flag ? "enabled" : "disabled") + " for Contracts", + ); + break; + case "ops": + case "op": + case "operations": + case "operation": + this.logging.ops = flag; + this.log( + "Logging " + (flag ? "enabled" : "disabled") + " for Operations", + ); + break; + case "blackops": + case "blackop": + case "black operations": + case "black operation": + this.logging.blackops = flag; + this.log( + "Logging " + (flag ? "enabled" : "disabled") + " for BlackOps", + ); + break; + case "event": + case "events": + this.logging.events = flag; + this.log("Logging " + (flag ? "enabled" : "disabled") + " for events"); + break; + case "all": + this.logging.general = flag; + this.logging.contracts = flag; + this.logging.ops = flag; + this.logging.blackops = flag; + this.logging.events = flag; + this.log( + "Logging " + (flag ? "enabled" : "disabled") + " for everything", + ); + break; + default: + this.postToConsole("Invalid action/event type specified: " + args[2]); + this.postToConsole( + "Examples of valid action/event identifiers are: [general, contracts, ops, blackops, events]", + ); + break; + } + } + + executeHelpConsoleCommand(args: string[]): void { + if (args.length === 1) { + for (const line of ConsoleHelpText.helpList) { + this.postToConsole(line); + } + } else { + for (let i = 1; i < args.length; ++i) { + if (!(args[i] in ConsoleHelpText)) continue; + const helpText = ConsoleHelpText[args[i]]; + for (const line of helpText) { + this.postToConsole(line); } + } + } + } - // Enable/Disable - if (args.length === 2) { - const flag = args[1]; - if (flag.toLowerCase() === "status") { - this.postToConsole("Automation: " + (this.automateEnabled ? "enabled" : "disabled")); - this.postToConsole("When your stamina drops to " + formatNumber(this.automateThreshLow, 0) + - ", you will automatically switch to " + this.automateActionLow.name + - ". When your stamina recovers to " + - formatNumber(this.automateThreshHigh, 0) + ", you will automatically " + - "switch to " + this.automateActionHigh.name + "."); + executeAutomateConsoleCommand(args: string[]): void { + if (args.length !== 2 && args.length !== 4) { + this.postToConsole( + "Invalid use of 'automate' command: automate [var] [val] [hi/low]. Use 'help automate' for more info", + ); + return; + } - } else if (flag.toLowerCase().includes("en")) { - if (!(this.automateActionLow instanceof ActionIdentifier) || - !(this.automateActionHigh instanceof ActionIdentifier)) { - return this.log("Failed to enable automation. Actions were not set"); - } - this.automateEnabled = true; - this.log("Bladeburner automation enabled"); - } else if (flag.toLowerCase().includes("d")) { - this.automateEnabled = false; - this.log("Bladeburner automation disabled"); + // Enable/Disable + if (args.length === 2) { + const flag = args[1]; + if (flag.toLowerCase() === "status") { + this.postToConsole( + "Automation: " + (this.automateEnabled ? "enabled" : "disabled"), + ); + this.postToConsole( + "When your stamina drops to " + + formatNumber(this.automateThreshLow, 0) + + ", you will automatically switch to " + + this.automateActionLow.name + + ". When your stamina recovers to " + + formatNumber(this.automateThreshHigh, 0) + + ", you will automatically " + + "switch to " + + this.automateActionHigh.name + + ".", + ); + } else if (flag.toLowerCase().includes("en")) { + if ( + !(this.automateActionLow instanceof ActionIdentifier) || + !(this.automateActionHigh instanceof ActionIdentifier) + ) { + return this.log("Failed to enable automation. Actions were not set"); + } + this.automateEnabled = true; + this.log("Bladeburner automation enabled"); + } else if (flag.toLowerCase().includes("d")) { + this.automateEnabled = false; + this.log("Bladeburner automation disabled"); + } else { + this.log("Invalid argument for 'automate' console command: " + args[1]); + } + return; + } + + // Set variables + if (args.length === 4) { + const variable = args[1]; + const val = args[2]; + + let highLow = false; // True for high, false for low + if (args[3].toLowerCase().includes("hi")) { + highLow = true; + } + + switch (variable) { + case "general": + case "gen": + if (GeneralActions[val] != null) { + const action = new ActionIdentifier({ + type: ActionTypes[val], + name: val, + }); + if (highLow) { + this.automateActionHigh = action; } else { - this.log("Invalid argument for 'automate' console command: " + args[1]); + this.automateActionLow = action; } - return; - } - - // Set variables - if (args.length === 4) { - const variable = args[1]; - const val = args[2]; - - let highLow = false; // True for high, false for low - if (args[3].toLowerCase().includes("hi")) {highLow = true;} - - switch (variable) { - case "general": - case "gen": - if (GeneralActions[val] != null) { - const action = new ActionIdentifier({ - type:ActionTypes[val], name:val, - }); - if (highLow) { - this.automateActionHigh = action; - } else { - this.automateActionLow = action; - } - this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); - } else { - this.postToConsole("Invalid action name specified: " + val); - } - break; - case "contract": - case "contracts": - if (this.contracts[val] != null) { - const action = new ActionIdentifier({ - type:ActionTypes.Contract, name:val, - }); - if (highLow) { - this.automateActionHigh = action; - } else { - this.automateActionLow = action; - } - this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); - } else { - this.postToConsole("Invalid contract name specified: " + val); - } - break; - case "ops": - case "op": - case "operations": - case "operation": - if (this.operations[val] != null) { - const action = new ActionIdentifier({ - type:ActionTypes.Operation, name:val, - }); - if (highLow) { - this.automateActionHigh = action; - } else { - this.automateActionLow = action; - } - this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") action set to " + val); - } else { - this.postToConsole("Invalid Operation name specified: " + val); - } - break; - case "stamina": - if (isNaN(parseFloat(val))) { - this.postToConsole("Invalid value specified for stamina threshold (must be numeric): " + val); - } else { - if (highLow) { - this.automateThreshHigh = Number(val); - } else { - this.automateThreshLow = Number(val); - } - this.log("Automate (" + (highLow ? "HIGH" : "LOW") + ") stamina threshold set to " + val); - } - break; - default: - break; + this.log( + "Automate (" + + (highLow ? "HIGH" : "LOW") + + ") action set to " + + val, + ); + } else { + this.postToConsole("Invalid action name specified: " + val); + } + break; + case "contract": + case "contracts": + if (this.contracts[val] != null) { + const action = new ActionIdentifier({ + type: ActionTypes.Contract, + name: val, + }); + if (highLow) { + this.automateActionHigh = action; + } else { + this.automateActionLow = action; } + this.log( + "Automate (" + + (highLow ? "HIGH" : "LOW") + + ") action set to " + + val, + ); + } else { + this.postToConsole("Invalid contract name specified: " + val); + } + break; + case "ops": + case "op": + case "operations": + case "operation": + if (this.operations[val] != null) { + const action = new ActionIdentifier({ + type: ActionTypes.Operation, + name: val, + }); + if (highLow) { + this.automateActionHigh = action; + } else { + this.automateActionLow = action; + } + this.log( + "Automate (" + + (highLow ? "HIGH" : "LOW") + + ") action set to " + + val, + ); + } else { + this.postToConsole("Invalid Operation name specified: " + val); + } + break; + case "stamina": + if (isNaN(parseFloat(val))) { + this.postToConsole( + "Invalid value specified for stamina threshold (must be numeric): " + + val, + ); + } else { + if (highLow) { + this.automateThreshHigh = Number(val); + } else { + this.automateThreshLow = Number(val); + } + this.log( + "Automate (" + + (highLow ? "HIGH" : "LOW") + + ") stamina threshold set to " + + val, + ); + } + break; + default: + break; + } - return; + return; + } + } + + parseCommandArguments(command: string): string[] { + /** + * Returns an array with command and its arguments in each index. + * e.g. skill "blade's intuition" foo returns [skill, blade's intuition, foo] + * The input to the fn will be trimmed and will have all whitespace replaced w/ a single space + */ + const args = []; + let start = 0; + let i = 0; + while (i < command.length) { + const c = command.charAt(i); + if (c === '"') { + // Double quotes + const endQuote = command.indexOf('"', i + 1); + if ( + endQuote !== -1 && + (endQuote === command.length - 1 || + command.charAt(endQuote + 1) === " ") + ) { + args.push(command.substr(i + 1, endQuote - i - 1)); + if (endQuote === command.length - 1) { + start = i = endQuote + 1; + } else { + start = i = endQuote + 2; // Skip the space + } + continue; } + } else if (c === "'") { + // Single quotes, same thing as above + const endQuote = command.indexOf("'", i + 1); + if ( + endQuote !== -1 && + (endQuote === command.length - 1 || + command.charAt(endQuote + 1) === " ") + ) { + args.push(command.substr(i + 1, endQuote - i - 1)); + if (endQuote === command.length - 1) { + start = i = endQuote + 1; + } else { + start = i = endQuote + 2; // Skip the space + } + continue; + } + } else if (c === " ") { + args.push(command.substr(start, i - start)); + start = i + 1; + } + ++i; + } + if (start !== i) { + args.push(command.substr(start, i - start)); + } + return args; + } + + executeConsoleCommand(player: IPlayer, command: string): void { + command = command.trim(); + command = command.replace(/\s\s+/g, " "); // Replace all whitespace w/ a single space + + const args = this.parseCommandArguments(command); + if (args.length <= 0) return; // Log an error? + + switch (args[0].toLowerCase()) { + case "automate": + this.executeAutomateConsoleCommand(args); + break; + case "clear": + case "cls": + this.clearConsole(); + break; + case "help": + this.executeHelpConsoleCommand(args); + break; + case "log": + this.executeLogConsoleCommand(args); + break; + case "skill": + this.executeSkillConsoleCommand(args); + break; + case "start": + this.executeStartConsoleCommand(player, args); + break; + case "stop": + this.resetAction(); + break; + default: + this.postToConsole("Invalid console command"); + break; + } + } + + triggerMigration(sourceCityName: string): void { + let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + while (destCityName === sourceCityName) { + destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + } + const destCity = this.cities[destCityName]; + const sourceCity = this.cities[sourceCityName]; + if (destCity == null || sourceCity == null) { + throw new Error("Failed to find City with name: " + destCityName); + } + const rand = Math.random(); + let percentage = getRandomInt(3, 15) / 100; + + if (rand < 0.05 && sourceCity.comms > 0) { + // 5% chance for community migration + percentage *= getRandomInt(2, 4); // Migration increases population change + --sourceCity.comms; + ++destCity.comms; + } + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop -= count; + destCity.pop += count; + } + + triggerPotentialMigration(sourceCityName: string, chance: number): void { + if (chance == null || isNaN(chance)) { + console.error( + "Invalid 'chance' parameter passed into Bladeburner.triggerPotentialMigration()", + ); + } + if (chance > 1) { + chance /= 100; + } + if (Math.random() < chance) { + this.triggerMigration(sourceCityName); + } + } + + randomEvent(): void { + const chance = Math.random(); + + // Choose random source/destination city for events + const sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + const sourceCity = this.cities[sourceCityName]; + if (!(sourceCity instanceof City)) { + throw new Error( + "sourceCity was not a City object in Bladeburner.randomEvent()", + ); } - parseCommandArguments(command: string): string[] { - /** - * Returns an array with command and its arguments in each index. - * e.g. skill "blade's intuition" foo returns [skill, blade's intuition, foo] - * The input to the fn will be trimmed and will have all whitespace replaced w/ a single space - */ - const args = []; - let start = 0; - let i = 0; - while (i < command.length) { - const c = command.charAt(i); - if (c === '"') { // Double quotes - const endQuote = command.indexOf('"', i+1); - if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) { - args.push(command.substr(i+1, (endQuote - i - 1))); - if (endQuote === command.length-1) { - start = i = endQuote+1; - } else { - start = i = endQuote+2; // Skip the space - } - continue; - } - } else if (c === "'") { // Single quotes, same thing as above - const endQuote = command.indexOf("'", i+1); - if (endQuote !== -1 && (endQuote === command.length-1 || command.charAt(endQuote+1) === " ")) { - args.push(command.substr(i+1, (endQuote - i - 1))); - if (endQuote === command.length-1) { - start = i = endQuote+1; - } else { - start = i = endQuote+2; // Skip the space - } - continue; - } - } else if (c === " ") { - args.push(command.substr(start, i-start)); - start = i+1; - } - ++i; - } - if (start !== i) {args.push(command.substr(start, i-start));} - return args; + let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + while (destCityName === sourceCityName) { + destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; + } + const destCity = this.cities[destCityName]; + + if (!(sourceCity instanceof City) || !(destCity instanceof City)) { + throw new Error( + "sourceCity/destCity was not a City object in Bladeburner.randomEvent()", + ); } - executeConsoleCommand(player: IPlayer, command: string): void { - command = command.trim(); - command = command.replace(/\s\s+/g, ' '); // Replace all whitespace w/ a single space - - const args = this.parseCommandArguments(command); - if (args.length <= 0) return; // Log an error? - - switch(args[0].toLowerCase()) { - case "automate": - this.executeAutomateConsoleCommand(args); - break; - case "clear": - case "cls": - this.clearConsole(); - break; - case "help": - this.executeHelpConsoleCommand(args); - break; - case "log": - this.executeLogConsoleCommand(args); - break; - case "skill": - this.executeSkillConsoleCommand(args); - break; - case "start": - this.executeStartConsoleCommand(player, args); - break; - case "stop": - this.resetAction(); - break; - default: - this.postToConsole("Invalid console command"); - break; + if (chance <= 0.05) { + // New Synthoid Community, 5% + ++sourceCity.comms; + const percentage = getRandomInt(10, 20) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop += count; + if (this.logging.events) { + this.log( + "Intelligence indicates that a new Synthoid community was formed in a city", + ); + } + } else if (chance <= 0.1) { + // Synthoid Community Migration, 5% + if (sourceCity.comms <= 0) { + // If no comms in source city, then instead trigger a new Synthoid community event + ++sourceCity.comms; + const percentage = getRandomInt(10, 20) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop += count; + if (this.logging.events) { + this.log( + "Intelligence indicates that a new Synthoid community was formed in a city", + ); } - } + } else { + --sourceCity.comms; + ++destCity.comms; - triggerMigration(sourceCityName: string): void { - let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - while (destCityName === sourceCityName) { - destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - } - const destCity = this.cities[destCityName]; - const sourceCity = this.cities[sourceCityName]; - if (destCity == null || sourceCity == null) { - throw new Error("Failed to find City with name: " + destCityName); - } - const rand = Math.random(); - let percentage = getRandomInt(3, 15) / 100; - - if (rand < 0.05 && sourceCity.comms > 0) { // 5% chance for community migration - percentage *= getRandomInt(2, 4); // Migration increases population change - --sourceCity.comms; - ++destCity.comms; - } + // Change pop + const percentage = getRandomInt(10, 20) / 100; const count = Math.round(sourceCity.pop * percentage); sourceCity.pop -= count; destCity.pop += count; + + if (this.logging.events) { + this.log( + "Intelligence indicates that a Synthoid community migrated from " + + sourceCityName + + " to some other city", + ); + } + } + } else if (chance <= 0.3) { + // New Synthoids (non community), 20% + const percentage = getRandomInt(8, 24) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop += count; + if (this.logging.events) { + this.log( + "Intelligence indicates that the Synthoid population of " + + sourceCityName + + " just changed significantly", + ); + } + } else if (chance <= 0.5) { + // Synthoid migration (non community) 20% + this.triggerMigration(sourceCityName); + if (this.logging.events) { + this.log( + "Intelligence indicates that a large number of Synthoids migrated from " + + sourceCityName + + " to some other city", + ); + } + } else if (chance <= 0.7) { + // Synthoid Riots (+chaos), 20% + sourceCity.chaos += 1; + sourceCity.chaos *= 1 + getRandomInt(5, 20) / 100; + if (this.logging.events) { + this.log( + "Tensions between Synthoids and humans lead to riots in " + + sourceCityName + + "! Chaos increased", + ); + } + } else if (chance <= 0.9) { + // Less Synthoids, 20% + const percentage = getRandomInt(8, 20) / 100; + const count = Math.round(sourceCity.pop * percentage); + sourceCity.pop -= count; + if (this.logging.events) { + this.log( + "Intelligence indicates that the Synthoid population of " + + sourceCityName + + " just changed significantly", + ); + } } + // 10% chance of nothing happening + } - triggerPotentialMigration(sourceCityName: string, chance: number): void { - if (chance == null || isNaN(chance)) { - console.error("Invalid 'chance' parameter passed into Bladeburner.triggerPotentialMigration()"); - } - if (chance > 1) {chance /= 100;} - if (Math.random() < chance) {this.triggerMigration(sourceCityName);} - } - - randomEvent(): void { - const chance = Math.random(); - - // Choose random source/destination city for events - const sourceCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - const sourceCity = this.cities[sourceCityName]; - if (!(sourceCity instanceof City)) { - throw new Error("sourceCity was not a City object in Bladeburner.randomEvent()"); - } - - let destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - while (destCityName === sourceCityName) { - destCityName = BladeburnerConstants.CityNames[getRandomInt(0, 5)]; - } - const destCity = this.cities[destCityName]; - - if (!(sourceCity instanceof City) || !(destCity instanceof City)) { - throw new Error("sourceCity/destCity was not a City object in Bladeburner.randomEvent()"); - } - - if (chance <= 0.05) { - // New Synthoid Community, 5% - ++sourceCity.comms; - const percentage = getRandomInt(10, 20) / 100; - const count = Math.round(sourceCity.pop * percentage); - sourceCity.pop += count; - if (this.logging.events) { - this.log("Intelligence indicates that a new Synthoid community was formed in a city"); - } - } else if (chance <= 0.1) { - // Synthoid Community Migration, 5% - if (sourceCity.comms <= 0) { - // If no comms in source city, then instead trigger a new Synthoid community event - ++sourceCity.comms; - const percentage = getRandomInt(10, 20) / 100; - const count = Math.round(sourceCity.pop * percentage); - sourceCity.pop += count; - if (this.logging.events) { - this.log("Intelligence indicates that a new Synthoid community was formed in a city"); - } - } else { - --sourceCity.comms; - ++destCity.comms; - - // Change pop - const percentage = getRandomInt(10, 20) / 100; - const count = Math.round(sourceCity.pop * percentage); - sourceCity.pop -= count; - destCity.pop += count; - - if (this.logging.events) { - this.log("Intelligence indicates that a Synthoid community migrated from " + sourceCityName + " to some other city"); - } - } - } else if (chance <= 0.3) { - // New Synthoids (non community), 20% - const percentage = getRandomInt(8, 24) / 100; - const count = Math.round(sourceCity.pop * percentage); - sourceCity.pop += count; - if (this.logging.events) { - this.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly"); - } - } else if (chance <= 0.5) { - // Synthoid migration (non community) 20% - this.triggerMigration(sourceCityName); - if (this.logging.events) { - this.log("Intelligence indicates that a large number of Synthoids migrated from " + sourceCityName + " to some other city"); - } - } else if (chance <= 0.7) { - // Synthoid Riots (+chaos), 20% - sourceCity.chaos += 1; - sourceCity.chaos *= (1 + getRandomInt(5, 20) / 100); - if (this.logging.events) { - this.log("Tensions between Synthoids and humans lead to riots in " + sourceCityName + "! Chaos increased"); - } - } else if (chance <= 0.9) { - // Less Synthoids, 20% - const percentage = getRandomInt(8, 20) / 100; - const count = Math.round(sourceCity.pop * percentage); - sourceCity.pop -= count; - if (this.logging.events) { - this.log("Intelligence indicates that the Synthoid population of " + sourceCityName + " just changed significantly"); - } - } - // 10% chance of nothing happening - } + /** + * Process stat gains from Contracts, Operations, and Black Operations + * @param action(Action obj) - Derived action class + * @param success(bool) - Whether action was successful + */ + gainActionStats(player: IPlayer, action: IAction, success: boolean): void { + const difficulty = action.getDifficulty(); /** - * Process stat gains from Contracts, Operations, and Black Operations - * @param action(Action obj) - Derived action class - * @param success(bool) - Whether action was successful + * Gain multiplier based on difficulty. If it changes then the + * same variable calculated in completeAction() needs to change too */ - gainActionStats(player: IPlayer, action: IAction, success: boolean): void { - const difficulty = action.getDifficulty(); + const difficultyMult = + Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + + difficulty / BladeburnerConstants.DiffMultLinearFactor; - /** - * Gain multiplier based on difficulty. If it changes then the - * same variable calculated in completeAction() needs to change too - */ - const difficultyMult = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; + const time = this.actionTimeToComplete; + const successMult = success ? 1 : 0.5; - const time = this.actionTimeToComplete; - const successMult = success ? 1 : 0.5; + const unweightedGain = + time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult; + const unweightedIntGain = + time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult; + const skillMult = this.skillMultipliers.expGain; + player.gainHackingExp( + unweightedGain * + action.weights.hack * + player.hacking_exp_mult * + skillMult, + ); + player.gainStrengthExp( + unweightedGain * + action.weights.str * + player.strength_exp_mult * + skillMult, + ); + player.gainDefenseExp( + unweightedGain * action.weights.def * player.defense_exp_mult * skillMult, + ); + player.gainDexterityExp( + unweightedGain * + action.weights.dex * + player.dexterity_exp_mult * + skillMult, + ); + player.gainAgilityExp( + unweightedGain * action.weights.agi * player.agility_exp_mult * skillMult, + ); + player.gainCharismaExp( + unweightedGain * + action.weights.cha * + player.charisma_exp_mult * + skillMult, + ); + let intExp = unweightedIntGain * action.weights.int * skillMult; + if (intExp > 1) { + intExp = Math.pow(intExp, 0.8); + } + player.gainIntelligenceExp(intExp); + } - const unweightedGain = time * BladeburnerConstants.BaseStatGain * successMult * difficultyMult; - const unweightedIntGain = time * BladeburnerConstants.BaseIntGain * successMult * difficultyMult; - const skillMult = this.skillMultipliers.expGain; - player.gainHackingExp(unweightedGain * action.weights.hack * player.hacking_exp_mult * skillMult); - player.gainStrengthExp(unweightedGain * action.weights.str * player.strength_exp_mult * skillMult); - player.gainDefenseExp(unweightedGain * action.weights.def * player.defense_exp_mult * skillMult); - player.gainDexterityExp(unweightedGain * action.weights.dex * player.dexterity_exp_mult * skillMult); - player.gainAgilityExp(unweightedGain * action.weights.agi * player.agility_exp_mult * skillMult); - player.gainCharismaExp(unweightedGain * action.weights.cha * player.charisma_exp_mult * skillMult); - let intExp = unweightedIntGain * action.weights.int * skillMult; - if (intExp > 1) { - intExp = Math.pow(intExp, 0.8); + getDiplomacyEffectiveness(player: IPlayer): number { + // Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98) + const CharismaLinearFactor = 1e3; + const CharismaExponentialFactor = 0.045; + + const charismaEff = + Math.pow(player.charisma, CharismaExponentialFactor) + + player.charisma / CharismaLinearFactor; + return (100 - charismaEff) / 100; + } + + getRecruitmentSuccessChance(player: IPlayer): number { + return Math.pow(player.charisma, 0.45) / (this.teamSize + 1); + } + + getRecruitmentTime(player: IPlayer): number { + const effCharisma = player.charisma * this.skillMultipliers.effCha; + const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90; + return Math.max( + 10, + Math.round( + BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor, + ), + ); + } + + resetSkillMultipliers(): void { + this.skillMultipliers = { + successChanceAll: 1, + successChanceStealth: 1, + successChanceKill: 1, + successChanceContract: 1, + successChanceOperation: 1, + successChanceEstimate: 1, + actionTime: 1, + effHack: 1, + effStr: 1, + effDef: 1, + effDex: 1, + effAgi: 1, + effCha: 1, + effInt: 1, + stamina: 1, + money: 1, + expGain: 1, + }; + } + + updateSkillMultipliers(): void { + this.resetSkillMultipliers(); + for (const skillName in this.skills) { + if (this.skills.hasOwnProperty(skillName)) { + const skill = Skills[skillName]; + if (skill == null) { + throw new Error("Could not find Skill Object for: " + skillName); } - player.gainIntelligenceExp(intExp); - } + const level = this.skills[skillName]; + if (level == null || level <= 0) { + continue; + } //Not upgraded - getDiplomacyEffectiveness(player: IPlayer): number { - // Returns a decimal by which the city's chaos level should be multiplied (e.g. 0.98) - const CharismaLinearFactor = 1e3; - const CharismaExponentialFactor = 0.045; - - const charismaEff = Math.pow(player.charisma, CharismaExponentialFactor) + player.charisma / CharismaLinearFactor; - return (100 - charismaEff) / 100; - } - - getRecruitmentSuccessChance(player: IPlayer): number { - return Math.pow(player.charisma, 0.45) / (this.teamSize + 1); - } - - getRecruitmentTime(player: IPlayer): number { - const effCharisma = player.charisma * this.skillMultipliers.effCha; - const charismaFactor = Math.pow(effCharisma, 0.81) + effCharisma / 90; - return Math.max(10, Math.round(BladeburnerConstants.BaseRecruitmentTimeNeeded - charismaFactor)); - } - - resetSkillMultipliers(): void { - this.skillMultipliers = { - successChanceAll: 1, - successChanceStealth: 1, - successChanceKill: 1, - successChanceContract: 1, - successChanceOperation: 1, - successChanceEstimate: 1, - actionTime: 1, - effHack: 1, - effStr: 1, - effDef: 1, - effDex: 1, - effAgi: 1, - effCha: 1, - effInt: 1, - stamina: 1, - money: 1, - expGain: 1, - }; - } - - updateSkillMultipliers(): void { - this.resetSkillMultipliers(); - for (const skillName in this.skills) { - if (this.skills.hasOwnProperty(skillName)) { - const skill = Skills[skillName]; - if (skill == null) { - throw new Error("Could not find Skill Object for: " + skillName); - } - const level = this.skills[skillName]; - if (level == null || level <= 0) {continue;} //Not upgraded - - const multiplierNames = Object.keys(this.skillMultipliers); - for (let i = 0; i < multiplierNames.length; ++i) { - const multiplierName = multiplierNames[i]; - if (skill.getMultiplier(multiplierName) != null && !isNaN(skill.getMultiplier(multiplierName))) { - const value = skill.getMultiplier(multiplierName) * level; - let multiplierValue = 1 + (value / 100); - if (multiplierName === "actionTime") { - multiplierValue = 1 - (value / 100); - } - this.skillMultipliers[multiplierName] *= multiplierValue; - } - } + const multiplierNames = Object.keys(this.skillMultipliers); + for (let i = 0; i < multiplierNames.length; ++i) { + const multiplierName = multiplierNames[i]; + if ( + skill.getMultiplier(multiplierName) != null && + !isNaN(skill.getMultiplier(multiplierName)) + ) { + const value = skill.getMultiplier(multiplierName) * level; + let multiplierValue = 1 + value / 100; + if (multiplierName === "actionTime") { + multiplierValue = 1 - value / 100; } + this.skillMultipliers[multiplierName] *= multiplierValue; + } } + } + } + } + + completeOperation(success: boolean): void { + if (this.action.type !== ActionTypes.Operation) { + throw new Error( + "completeOperation() called even though current action is not an Operation", + ); + } + const action = this.getActionObject(this.action); + if (action == null) { + throw new Error( + "Failed to get Contract/Operation Object for: " + this.action.name, + ); } - completeOperation(success: boolean): void { - if (this.action.type !== ActionTypes.Operation) { - throw new Error("completeOperation() called even though current action is not an Operation"); - } - const action = this.getActionObject(this.action); - if (action == null) { - throw new Error("Failed to get Contract/Operation Object for: " + this.action.name); - } + // Calculate team losses + const teamCount = action.teamCount; + if (teamCount >= 1) { + let max; + if (success) { + max = Math.ceil(teamCount / 2); + } else { + max = Math.floor(teamCount); + } + const losses = getRandomInt(0, max); + this.teamSize -= losses; + this.teamLost += losses; + if (this.logging.ops && losses > 0) { + this.log( + "Lost " + + formatNumber(losses, 0) + + " team members during this " + + action.name, + ); + } + } - // Calculate team losses - const teamCount = action.teamCount; - if (teamCount >= 1) { - let max; - if (success) { - max = Math.ceil(teamCount/2); + const city = this.getCurrentCity(); + switch (action.name) { + case "Investigation": + if (success) { + city.improvePopulationEstimateByPercentage( + 0.4 * this.skillMultipliers.successChanceEstimate, + ); + if ( + Math.random() < + 0.02 * this.skillMultipliers.successChanceEstimate + ) { + city.improveCommunityEstimate(1); + } + } else { + this.triggerPotentialMigration(this.city, 0.1); + } + break; + case "Undercover Operation": + if (success) { + city.improvePopulationEstimateByPercentage( + 0.8 * this.skillMultipliers.successChanceEstimate, + ); + if ( + Math.random() < + 0.02 * this.skillMultipliers.successChanceEstimate + ) { + city.improveCommunityEstimate(1); + } + } else { + this.triggerPotentialMigration(this.city, 0.15); + } + break; + case "Sting Operation": + if (success) { + city.changePopulationByPercentage(-0.1, { + changeEstEqually: true, + nonZero: true, + }); + } + city.changeChaosByCount(0.1); + break; + case "Raid": + if (success) { + city.changePopulationByPercentage(-1, { + changeEstEqually: true, + nonZero: true, + }); + --city.comms; + --city.commsEst; + } else { + const change = getRandomInt(-10, -5) / 10; + city.changePopulationByPercentage(change, { + nonZero: true, + changeEstEqually: false, + }); + } + city.changeChaosByPercentage(getRandomInt(1, 5)); + break; + case "Stealth Retirement Operation": + if (success) { + city.changePopulationByPercentage(-0.5, { + changeEstEqually: true, + nonZero: true, + }); + } + city.changeChaosByPercentage(getRandomInt(-3, -1)); + break; + case "Assassination": + if (success) { + city.changePopulationByCount(-1, { estChange: -1, estOffset: 0 }); + } + city.changeChaosByPercentage(getRandomInt(-5, 5)); + break; + default: + throw new Error( + "Invalid Action name in completeOperation: " + this.action.name, + ); + } + } + + getActionObject(actionId: IActionIdentifier): IAction | null { + /** + * Given an ActionIdentifier object, returns the corresponding + * GeneralAction, Contract, Operation, or BlackOperation object + */ + switch (actionId.type) { + case ActionTypes["Contract"]: + return this.contracts[actionId.name]; + case ActionTypes["Operation"]: + return this.operations[actionId.name]; + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + return BlackOperations[actionId.name]; + case ActionTypes["Training"]: + return GeneralActions["Training"]; + case ActionTypes["Field Analysis"]: + return GeneralActions["Field Analysis"]; + case ActionTypes["Recruitment"]: + return GeneralActions["Recruitment"]; + case ActionTypes["Diplomacy"]: + return GeneralActions["Diplomacy"]; + case ActionTypes["Hyperbolic Regeneration Chamber"]: + return GeneralActions["Hyperbolic Regeneration Chamber"]; + default: + return null; + } + } + + completeContract(success: boolean): void { + if (this.action.type !== ActionTypes.Contract) { + throw new Error( + "completeContract() called even though current action is not a Contract", + ); + } + const city = this.getCurrentCity(); + if (success) { + switch (this.action.name) { + case "Tracking": + // Increase estimate accuracy by a relatively small amount + city.improvePopulationEstimateByCount(getRandomInt(100, 1e3)); + break; + case "Bounty Hunter": + city.changePopulationByCount(-1, { estChange: -1, estOffset: 0 }); + city.changeChaosByCount(0.02); + break; + case "Retirement": + city.changePopulationByCount(-1, { estChange: -1, estOffset: 0 }); + city.changeChaosByCount(0.04); + break; + default: + throw new Error( + "Invalid Action name in completeContract: " + this.action.name, + ); + } + } + } + + completeAction(player: IPlayer): void { + switch (this.action.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: { + try { + const isOperation = this.action.type === ActionTypes["Operation"]; + const action = this.getActionObject(this.action); + if (action == null) { + throw new Error( + "Failed to get Contract/Operation Object for: " + + this.action.name, + ); + } + const difficulty = action.getDifficulty(); + const difficultyMultiplier = + Math.pow( + difficulty, + BladeburnerConstants.DiffMultExponentialFactor, + ) + + difficulty / BladeburnerConstants.DiffMultLinearFactor; + const rewardMultiplier = Math.pow(action.rewardFac, action.level - 1); + + // Stamina loss is based on difficulty + this.stamina -= + BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier; + if (this.stamina < 0) { + this.stamina = 0; + } + + // Process Contract/Operation success/failure + if (action.attempt(this)) { + this.gainActionStats(player, action, true); + ++action.successes; + --action.count; + + // Earn money for contracts + let moneyGain = 0; + if (!isOperation) { + moneyGain = + BladeburnerConstants.ContractBaseMoneyGain * + rewardMultiplier * + this.skillMultipliers.money; + player.gainMoney(moneyGain); + player.recordMoneySource(moneyGain, "bladeburner"); + } + + if (isOperation) { + action.setMaxLevel( + BladeburnerConstants.OperationSuccessesPerLevel, + ); } else { - max = Math.floor(teamCount) + action.setMaxLevel( + BladeburnerConstants.ContractSuccessesPerLevel, + ); } - const losses = getRandomInt(0, max); + if (action.rankGain) { + const gain = addOffset( + action.rankGain * + rewardMultiplier * + BitNodeMultipliers.BladeburnerRank, + 10, + ); + this.changeRank(player, gain); + if (isOperation && this.logging.ops) { + this.log( + action.name + + " successfully completed! Gained " + + formatNumber(gain, 3) + + " rank", + ); + } else if (!isOperation && this.logging.contracts) { + this.log( + action.name + + " contract successfully completed! Gained " + + formatNumber(gain, 3) + + " rank and " + + numeralWrapper.formatMoney(moneyGain), + ); + } + } + isOperation + ? this.completeOperation(true) + : this.completeContract(true); + } else { + this.gainActionStats(player, action, false); + ++action.failures; + let loss = 0, + damage = 0; + if (action.rankLoss) { + loss = addOffset(action.rankLoss * rewardMultiplier, 10); + this.changeRank(player, -1 * loss); + } + if (action.hpLoss) { + damage = action.hpLoss * difficultyMultiplier; + damage = Math.ceil(addOffset(damage, 10)); + this.hpLost += damage; + const cost = calculateHospitalizationCost(player, damage); + if (player.takeDamage(damage)) { + ++this.numHosp; + this.moneyLost += cost; + } + } + let logLossText = ""; + if (loss > 0) { + logLossText += "Lost " + formatNumber(loss, 3) + " rank. "; + } + if (damage > 0) { + logLossText += "Took " + formatNumber(damage, 0) + " damage."; + } + if (isOperation && this.logging.ops) { + this.log(action.name + " failed! " + logLossText); + } else if (!isOperation && this.logging.contracts) { + this.log(action.name + " contract failed! " + logLossText); + } + isOperation + ? this.completeOperation(false) + : this.completeContract(false); + } + if (action.autoLevel) { + action.level = action.maxLevel; + } // Autolevel + this.startAction(player, this.action); // Repeat action + } catch (e) { + exceptionAlert(e); + } + break; + } + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: { + try { + const action = this.getActionObject(this.action); + if (action == null || !(action instanceof BlackOperation)) { + throw new Error( + "Failed to get BlackOperation Object for: " + this.action.name, + ); + } + const difficulty = action.getDifficulty(); + const difficultyMultiplier = + Math.pow( + difficulty, + BladeburnerConstants.DiffMultExponentialFactor, + ) + + difficulty / BladeburnerConstants.DiffMultLinearFactor; + + // Stamina loss is based on difficulty + this.stamina -= + BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier; + if (this.stamina < 0) { + this.stamina = 0; + } + + // Team loss variables + const teamCount = action.teamCount; + let teamLossMax; + + if (action.attempt(this)) { + this.gainActionStats(player, action, true); + action.count = 0; + this.blackops[action.name] = true; + let rankGain = 0; + if (action.rankGain) { + rankGain = addOffset( + action.rankGain * BitNodeMultipliers.BladeburnerRank, + 10, + ); + this.changeRank(player, rankGain); + } + teamLossMax = Math.ceil(teamCount / 2); + + // Operation Daedalus + if (action.name === "Operation Daedalus") { + this.resetAction(); + return hackWorldDaemon(player.bitNodeN); + } + + if (this.logging.blackops) { + this.log( + action.name + + " successful! Gained " + + formatNumber(rankGain, 1) + + " rank", + ); + } + } else { + this.gainActionStats(player, action, false); + let rankLoss = 0; + let damage = 0; + if (action.rankLoss) { + rankLoss = addOffset(action.rankLoss, 10); + this.changeRank(player, -1 * rankLoss); + } + if (action.hpLoss) { + damage = action.hpLoss * difficultyMultiplier; + damage = Math.ceil(addOffset(damage, 10)); + const cost = calculateHospitalizationCost(player, damage); + if (player.takeDamage(damage)) { + ++this.numHosp; + this.moneyLost += cost; + } + } + teamLossMax = Math.floor(teamCount); + + if (this.logging.blackops) { + this.log( + action.name + + " failed! Lost " + + formatNumber(rankLoss, 1) + + " rank and took " + + formatNumber(damage, 0) + + " damage", + ); + } + } + + this.resetAction(); // Stop regardless of success or fail + + // Calculate team lossses + if (teamCount >= 1) { + const losses = getRandomInt(1, teamLossMax); this.teamSize -= losses; this.teamLost += losses; - if (this.logging.ops && losses > 0) { - this.log("Lost " + formatNumber(losses, 0) + " team members during this " + action.name); + if (this.logging.blackops) { + this.log( + "You lost " + + formatNumber(losses, 0) + + " team members during " + + action.name, + ); } + } + } catch (e) { + exceptionAlert(e); } + break; + } + case ActionTypes["Training"]: { + this.stamina -= 0.5 * BladeburnerConstants.BaseStaminaLoss; + const strExpGain = 30 * player.strength_exp_mult, + defExpGain = 30 * player.defense_exp_mult, + dexExpGain = 30 * player.dexterity_exp_mult, + agiExpGain = 30 * player.agility_exp_mult, + staminaGain = 0.04 * this.skillMultipliers.stamina; + player.gainStrengthExp(strExpGain); + player.gainDefenseExp(defExpGain); + player.gainDexterityExp(dexExpGain); + player.gainAgilityExp(agiExpGain); + this.staminaBonus += staminaGain; + if (this.logging.general) { + this.log( + "Training completed. Gained: " + + formatNumber(strExpGain, 1) + + " str exp, " + + formatNumber(defExpGain, 1) + + " def exp, " + + formatNumber(dexExpGain, 1) + + " dex exp, " + + formatNumber(agiExpGain, 1) + + " agi exp, " + + formatNumber(staminaGain, 3) + + " max stamina", + ); + } + this.startAction(player, this.action); // Repeat action + break; + } + case ActionTypes["FieldAnalysis"]: + case ActionTypes["Field Analysis"]: { + // Does not use stamina. Effectiveness depends on hacking, int, and cha + let eff = + 0.04 * Math.pow(player.hacking_skill, 0.3) + + 0.04 * Math.pow(player.intelligence, 0.9) + + 0.02 * Math.pow(player.charisma, 0.3); + eff *= player.bladeburner_analysis_mult; + if (isNaN(eff) || eff < 0) { + throw new Error( + "Field Analysis Effectiveness calculated to be NaN or negative", + ); + } + const hackingExpGain = 20 * player.hacking_exp_mult, + charismaExpGain = 20 * player.charisma_exp_mult; + player.gainHackingExp(hackingExpGain); + player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain); + player.gainCharismaExp(charismaExpGain); + this.changeRank(player, 0.1 * BitNodeMultipliers.BladeburnerRank); + this.getCurrentCity().improvePopulationEstimateByPercentage( + eff * this.skillMultipliers.successChanceEstimate, + ); + if (this.logging.general) { + this.log( + "Field analysis completed. Gained 0.1 rank, " + + formatNumber(hackingExpGain, 1) + + " hacking exp, and " + + formatNumber(charismaExpGain, 1) + + " charisma exp", + ); + } + this.startAction(player, this.action); // Repeat action + break; + } + case ActionTypes["Recruitment"]: { + const successChance = this.getRecruitmentSuccessChance(player); + if (Math.random() < successChance) { + const expGain = + 2 * BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; + player.gainCharismaExp(expGain); + ++this.teamSize; + if (this.logging.general) { + this.log( + "Successfully recruited a team member! Gained " + + formatNumber(expGain, 1) + + " charisma exp", + ); + } + } else { + const expGain = + BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; + player.gainCharismaExp(expGain); + if (this.logging.general) { + this.log( + "Failed to recruit a team member. Gained " + + formatNumber(expGain, 1) + + " charisma exp", + ); + } + } + this.startAction(player, this.action); // Repeat action + break; + } + case ActionTypes["Diplomacy"]: { + const eff = this.getDiplomacyEffectiveness(player); + this.getCurrentCity().chaos *= eff; + if (this.getCurrentCity().chaos < 0) { + this.getCurrentCity().chaos = 0; + } + if (this.logging.general) { + this.log( + `Diplomacy completed. Chaos levels in the current city fell by ${numeralWrapper.formatPercentage( + 1 - eff, + )}`, + ); + } + this.startAction(player, this.action); // Repeat Action + break; + } + case ActionTypes["Hyperbolic Regeneration Chamber"]: { + player.regenerateHp(BladeburnerConstants.HrcHpGain); - const city = this.getCurrentCity(); - switch (action.name) { - case "Investigation": - if (success) { - city.improvePopulationEstimateByPercentage(0.4 * this.skillMultipliers.successChanceEstimate); - if (Math.random() < (0.02 * this.skillMultipliers.successChanceEstimate)) { - city.improveCommunityEstimate(1); - } - } else { - this.triggerPotentialMigration(this.city, 0.1); - } - break; - case "Undercover Operation": - if (success) { - city.improvePopulationEstimateByPercentage(0.8 * this.skillMultipliers.successChanceEstimate); - if (Math.random() < (0.02 * this.skillMultipliers.successChanceEstimate)) { - city.improveCommunityEstimate(1); - } - } else { - this.triggerPotentialMigration(this.city, 0.15); - } - break; - case "Sting Operation": - if (success) { - city.changePopulationByPercentage(-0.1, {changeEstEqually:true, nonZero:true}); - } - city.changeChaosByCount(0.1); - break; - case "Raid": - if (success) { - city.changePopulationByPercentage(-1, {changeEstEqually:true, nonZero:true}); - --city.comms; - --city.commsEst; - } else { - const change = getRandomInt(-10, -5) / 10; - city.changePopulationByPercentage(change, {nonZero:true, changeEstEqually:false}); - } - city.changeChaosByPercentage(getRandomInt(1, 5)); - break; - case "Stealth Retirement Operation": - if (success) { - city.changePopulationByPercentage(-0.5, {changeEstEqually:true,nonZero:true}); - } - city.changeChaosByPercentage(getRandomInt(-3, -1)); - break; - case "Assassination": - if (success) { - city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); - } - city.changeChaosByPercentage(getRandomInt(-5, 5)); - break; - default: - throw new Error("Invalid Action name in completeOperation: " + this.action.name); + const staminaGain = + this.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100); + this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain); + this.startAction(player, this.action); + if (this.logging.general) { + this.log( + `Rested in Hyperbolic Regeneration Chamber. Restored ${ + BladeburnerConstants.HrcHpGain + } HP and gained ${numeralWrapper.formatStamina( + staminaGain, + )} stamina`, + ); } + break; + } + default: + console.error( + `Bladeburner.completeAction() called for invalid action: ${this.action.type}`, + ); + break; + } + } + + changeRank(player: IPlayer, change: number): void { + if (isNaN(change)) { + throw new Error("NaN passed into Bladeburner.changeRank()"); + } + this.rank += change; + if (this.rank < 0) { + this.rank = 0; + } + this.maxRank = Math.max(this.rank, this.maxRank); + + const bladeburnersFactionName = "Bladeburners"; + if (factionExists(bladeburnersFactionName)) { + const bladeburnerFac = Factions[bladeburnersFactionName]; + if (!(bladeburnerFac instanceof Faction)) { + throw new Error( + "Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button", + ); + } + if (bladeburnerFac.isMember) { + const favorBonus = 1 + bladeburnerFac.favor / 100; + bladeburnerFac.playerReputation += + BladeburnerConstants.RankToFactionRepFactor * + change * + player.faction_rep_mult * + favorBonus; + } } - getActionObject(actionId: IActionIdentifier): IAction | null { - /** - * Given an ActionIdentifier object, returns the corresponding - * GeneralAction, Contract, Operation, or BlackOperation object - */ - switch (actionId.type) { - case ActionTypes["Contract"]: - return this.contracts[actionId.name]; - case ActionTypes["Operation"]: - return this.operations[actionId.name]; - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - return BlackOperations[actionId.name]; - case ActionTypes["Training"]: - return GeneralActions["Training"]; - case ActionTypes["Field Analysis"]: - return GeneralActions["Field Analysis"]; - case ActionTypes["Recruitment"]: - return GeneralActions["Recruitment"]; - case ActionTypes["Diplomacy"]: - return GeneralActions["Diplomacy"]; - case ActionTypes["Hyperbolic Regeneration Chamber"]: - return GeneralActions["Hyperbolic Regeneration Chamber"]; - default: - return null; - } + // Gain skill points + const rankNeededForSp = + (this.totalSkillPoints + 1) * BladeburnerConstants.RanksPerSkillPoint; + if (this.maxRank >= rankNeededForSp) { + // Calculate how many skill points to gain + const gainedSkillPoints = Math.floor( + (this.maxRank - rankNeededForSp) / + BladeburnerConstants.RanksPerSkillPoint + + 1, + ); + this.skillPoints += gainedSkillPoints; + this.totalSkillPoints += gainedSkillPoints; + } + } + + processAction(player: IPlayer, seconds: number): void { + if (this.action.type === ActionTypes["Idle"]) return; + if (this.actionTimeToComplete <= 0) { + throw new Error( + `Invalid actionTimeToComplete value: ${this.actionTimeToComplete}, type; ${this.action.type}`, + ); + } + if (!(this.action instanceof ActionIdentifier)) { + throw new Error("Bladeburner.action is not an ActionIdentifier Object"); } - completeContract(success: boolean): void { - if (this.action.type !== ActionTypes.Contract) { - throw new Error("completeContract() called even though current action is not a Contract"); - } - const city = this.getCurrentCity(); - if (success) { - switch (this.action.name) { - case "Tracking": - // Increase estimate accuracy by a relatively small amount - city.improvePopulationEstimateByCount(getRandomInt(100, 1e3)); - break; - case "Bounty Hunter": - city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); - city.changeChaosByCount(0.02); - break; - case "Retirement": - city.changePopulationByCount(-1, {estChange:-1, estOffset: 0}); - city.changeChaosByCount(0.04); - break; - default: - throw new Error("Invalid Action name in completeContract: " + this.action.name); - } - } + // If the previous action went past its completion time, add to the next action + // This is not added inmediatly in case the automation changes the action + this.actionTimeCurrent += seconds + this.actionTimeOverflow; + this.actionTimeOverflow = 0; + if (this.actionTimeCurrent >= this.actionTimeToComplete) { + this.actionTimeOverflow = + this.actionTimeCurrent - this.actionTimeToComplete; + return this.completeAction(player); + } + } + + calculateStaminaGainPerSecond(player: IPlayer): number { + const effAgility = player.agility * this.skillMultipliers.effAgi; + const maxStaminaBonus = + this.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor; + const gain = + (BladeburnerConstants.StaminaGainPerSecond + maxStaminaBonus) * + Math.pow(effAgility, 0.17); + return ( + gain * + (this.skillMultipliers.stamina * player.bladeburner_stamina_gain_mult) + ); + } + + calculateMaxStamina(player: IPlayer): void { + const effAgility = player.agility * this.skillMultipliers.effAgi; + const maxStamina = + (Math.pow(effAgility, 0.8) + this.staminaBonus) * + this.skillMultipliers.stamina * + player.bladeburner_max_stamina_mult; + if (this.maxStamina !== maxStamina) { + const oldMax = this.maxStamina; + this.maxStamina = maxStamina; + this.stamina = (this.maxStamina * this.stamina) / oldMax; + } + if (isNaN(maxStamina)) { + throw new Error( + "Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()", + ); + } + } + + create(): void { + this.contracts["Tracking"] = new Contract({ + name: "Tracking", + desc: + "Identify and locate Synthoids. This contract involves reconnaissance " + + "and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.

    " + + "Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for " + + "whatever city you are currently in.", + baseDifficulty: 125, + difficultyFac: 1.02, + rewardFac: 1.041, + rankGain: 0.3, + hpLoss: 0.5, + count: getRandomInt(25, 150), + countGrowth: getRandomInt(5, 75) / 10, + weights: { + hack: 0, + str: 0.05, + def: 0.05, + dex: 0.35, + agi: 0.35, + cha: 0.1, + int: 0.05, + }, + decays: { + hack: 0, + str: 0.91, + def: 0.91, + dex: 0.91, + agi: 0.91, + cha: 0.9, + int: 1, + }, + isStealth: true, + }); + this.contracts["Bounty Hunter"] = new Contract({ + name: "Bounty Hunter", + desc: + "Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.

    " + + "Successfully completing a Bounty Hunter contract will lower the population in your " + + "current city, and will also increase its chaos level.", + baseDifficulty: 250, + difficultyFac: 1.04, + rewardFac: 1.085, + rankGain: 0.9, + hpLoss: 1, + count: getRandomInt(5, 150), + countGrowth: getRandomInt(5, 75) / 10, + weights: { + hack: 0, + str: 0.15, + def: 0.15, + dex: 0.25, + agi: 0.25, + cha: 0.1, + int: 0.1, + }, + decays: { + hack: 0, + str: 0.91, + def: 0.91, + dex: 0.91, + agi: 0.91, + cha: 0.8, + int: 0.9, + }, + isKill: true, + }); + this.contracts["Retirement"] = new Contract({ + name: "Retirement", + desc: + "Hunt down and retire (kill) rogue Synthoids.

    " + + "Successfully completing a Retirement contract will lower the population in your current " + + "city, and will also increase its chaos level.", + baseDifficulty: 200, + difficultyFac: 1.03, + rewardFac: 1.065, + rankGain: 0.6, + hpLoss: 1, + count: getRandomInt(5, 150), + countGrowth: getRandomInt(5, 75) / 10, + weights: { + hack: 0, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0.1, + int: 0.1, + }, + decays: { + hack: 0, + str: 0.91, + def: 0.91, + dex: 0.91, + agi: 0.91, + cha: 0.8, + int: 0.9, + }, + isKill: true, + }); + + this.operations["Investigation"] = new Operation({ + name: "Investigation", + desc: + "As a field agent, investigate and identify Synthoid " + + "populations, movements, and operations.

    Successful " + + "Investigation ops will increase the accuracy of your " + + "synthoid data.

    " + + "You will NOT lose HP from failed Investigation ops.", + baseDifficulty: 400, + difficultyFac: 1.03, + rewardFac: 1.07, + reqdRank: 25, + rankGain: 2.2, + rankLoss: 0.2, + count: getRandomInt(1, 100), + countGrowth: getRandomInt(10, 40) / 10, + weights: { + hack: 0.25, + str: 0.05, + def: 0.05, + dex: 0.2, + agi: 0.1, + cha: 0.25, + int: 0.1, + }, + decays: { + hack: 0.85, + str: 0.9, + def: 0.9, + dex: 0.9, + agi: 0.9, + cha: 0.7, + int: 0.9, + }, + isStealth: true, + }); + this.operations["Undercover Operation"] = new Operation({ + name: "Undercover Operation", + desc: + "Conduct undercover operations to identify hidden " + + "and underground Synthoid communities and organizations.

    " + + "Successful Undercover ops will increase the accuracy of your synthoid " + + "data.", + baseDifficulty: 500, + difficultyFac: 1.04, + rewardFac: 1.09, + reqdRank: 100, + rankGain: 4.4, + rankLoss: 0.4, + hpLoss: 2, + count: getRandomInt(1, 100), + countGrowth: getRandomInt(10, 40) / 10, + weights: { + hack: 0.2, + str: 0.05, + def: 0.05, + dex: 0.2, + agi: 0.2, + cha: 0.2, + int: 0.1, + }, + decays: { + hack: 0.8, + str: 0.9, + def: 0.9, + dex: 0.9, + agi: 0.9, + cha: 0.7, + int: 0.9, + }, + isStealth: true, + }); + this.operations["Sting Operation"] = new Operation({ + name: "Sting Operation", + desc: + "Conduct a sting operation to bait and capture particularly " + + "notorious Synthoid criminals.", + baseDifficulty: 650, + difficultyFac: 1.04, + rewardFac: 1.095, + reqdRank: 500, + rankGain: 5.5, + rankLoss: 0.5, + hpLoss: 2.5, + count: getRandomInt(1, 150), + countGrowth: getRandomInt(3, 40) / 10, + weights: { + hack: 0.25, + str: 0.05, + def: 0.05, + dex: 0.25, + agi: 0.1, + cha: 0.2, + int: 0.1, + }, + decays: { + hack: 0.8, + str: 0.85, + def: 0.85, + dex: 0.85, + agi: 0.85, + cha: 0.7, + int: 0.9, + }, + isStealth: true, + }); + this.operations["Raid"] = new Operation({ + name: "Raid", + desc: + "Lead an assault on a known Synthoid community. Note that " + + "there must be an existing Synthoid community in your current city " + + "in order for this Operation to be successful.", + baseDifficulty: 800, + difficultyFac: 1.045, + rewardFac: 1.1, + reqdRank: 3000, + rankGain: 55, + rankLoss: 2.5, + hpLoss: 50, + count: getRandomInt(1, 150), + countGrowth: getRandomInt(2, 40) / 10, + weights: { + hack: 0.1, + str: 0.2, + def: 0.2, + dex: 0.2, + agi: 0.2, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.7, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.9, + }, + isKill: true, + }); + this.operations["Stealth Retirement Operation"] = new Operation({ + name: "Stealth Retirement Operation", + desc: + "Lead a covert operation to retire Synthoids. The " + + "objective is to complete the task without " + + "drawing any attention. Stealth and discretion are key.", + baseDifficulty: 1000, + difficultyFac: 1.05, + rewardFac: 1.11, + reqdRank: 20e3, + rankGain: 22, + rankLoss: 2, + hpLoss: 10, + count: getRandomInt(1, 150), + countGrowth: getRandomInt(1, 20) / 10, + weights: { + hack: 0.1, + str: 0.1, + def: 0.1, + dex: 0.3, + agi: 0.3, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.7, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.9, + }, + isStealth: true, + isKill: true, + }); + this.operations["Assassination"] = new Operation({ + name: "Assassination", + desc: + "Assassinate Synthoids that have been identified as " + + "important, high-profile social and political leaders " + + "in the Synthoid communities.", + baseDifficulty: 1500, + difficultyFac: 1.06, + rewardFac: 1.14, + reqdRank: 50e3, + rankGain: 44, + rankLoss: 4, + hpLoss: 5, + count: getRandomInt(1, 150), + countGrowth: getRandomInt(1, 20) / 10, + weights: { + hack: 0.1, + str: 0.1, + def: 0.1, + dex: 0.3, + agi: 0.3, + cha: 0, + int: 0.1, + }, + decays: { + hack: 0.6, + str: 0.8, + def: 0.8, + dex: 0.8, + agi: 0.8, + cha: 0, + int: 0.8, + }, + isStealth: true, + isKill: true, + }); + } + + process(player: IPlayer): void { + // Edge case condition...if Operation Daedalus is complete trigger the BitNode + if ( + redPillFlag === false && + this.blackops.hasOwnProperty("Operation Daedalus") + ) { + return hackWorldDaemon(player.bitNodeN); } - completeAction(player: IPlayer): void { - switch (this.action.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: { - try { - const isOperation = (this.action.type === ActionTypes["Operation"]); - const action = this.getActionObject(this.action); - if (action == null) { - throw new Error("Failed to get Contract/Operation Object for: " + this.action.name); - } - const difficulty = action.getDifficulty(); - const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; - const rewardMultiplier = Math.pow(action.rewardFac, action.level-1); - - // Stamina loss is based on difficulty - this.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); - if (this.stamina < 0) {this.stamina = 0;} - - // Process Contract/Operation success/failure - if (action.attempt(this)) { - this.gainActionStats(player, action, true); - ++action.successes; - --action.count; - - // Earn money for contracts - let moneyGain = 0; - if (!isOperation) { - moneyGain = BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * this.skillMultipliers.money; - player.gainMoney(moneyGain); - player.recordMoneySource(moneyGain, "bladeburner"); - } - - if (isOperation) { - action.setMaxLevel(BladeburnerConstants.OperationSuccessesPerLevel); - } else { - action.setMaxLevel(BladeburnerConstants.ContractSuccessesPerLevel); - } - if (action.rankGain) { - const gain = addOffset(action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank, 10); - this.changeRank(player, gain); - if (isOperation && this.logging.ops) { - this.log(action.name + " successfully completed! Gained " + formatNumber(gain, 3) + " rank"); - } else if (!isOperation && this.logging.contracts) { - this.log(action.name + " contract successfully completed! Gained " + formatNumber(gain, 3) + " rank and " + numeralWrapper.formatMoney(moneyGain)); - } - } - isOperation ? this.completeOperation(true) : this.completeContract(true); - } else { - this.gainActionStats(player, action, false); - ++action.failures; - let loss = 0, damage = 0; - if (action.rankLoss) { - loss = addOffset(action.rankLoss * rewardMultiplier, 10); - this.changeRank(player, -1 * loss); - } - if (action.hpLoss) { - damage = action.hpLoss * difficultyMultiplier; - damage = Math.ceil(addOffset(damage, 10)); - this.hpLost += damage; - const cost = calculateHospitalizationCost(player, damage); - if (player.takeDamage(damage)) { - ++this.numHosp; - this.moneyLost += cost; - } - } - let logLossText = ""; - if (loss > 0) {logLossText += "Lost " + formatNumber(loss, 3) + " rank. ";} - if (damage > 0) {logLossText += "Took " + formatNumber(damage, 0) + " damage.";} - if (isOperation && this.logging.ops) { - this.log(action.name + " failed! " + logLossText); - } else if (!isOperation && this.logging.contracts) { - this.log(action.name + " contract failed! " + logLossText); - } - isOperation ? this.completeOperation(false) : this.completeContract(false); - } - if (action.autoLevel) {action.level = action.maxLevel;} // Autolevel - this.startAction(player, this.action); // Repeat action - } catch(e) { - exceptionAlert(e); - } - break; - } - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: { - try { - const action = this.getActionObject(this.action); - if (action == null || !(action instanceof BlackOperation)) { - throw new Error("Failed to get BlackOperation Object for: " + this.action.name); - } - const difficulty = action.getDifficulty(); - const difficultyMultiplier = Math.pow(difficulty, BladeburnerConstants.DiffMultExponentialFactor) + difficulty / BladeburnerConstants.DiffMultLinearFactor; - - // Stamina loss is based on difficulty - this.stamina -= (BladeburnerConstants.BaseStaminaLoss * difficultyMultiplier); - if (this.stamina < 0) {this.stamina = 0;} - - // Team loss variables - const teamCount = action.teamCount; - let teamLossMax; - - if (action.attempt(this)) { - this.gainActionStats(player, action, true); - action.count = 0; - this.blackops[action.name] = true; - let rankGain = 0; - if (action.rankGain) { - rankGain = addOffset(action.rankGain * BitNodeMultipliers.BladeburnerRank, 10); - this.changeRank(player, rankGain); - } - teamLossMax = Math.ceil(teamCount/2); - - // Operation Daedalus - if (action.name === "Operation Daedalus") { - this.resetAction(); - return hackWorldDaemon(player.bitNodeN); - } - - if (this.logging.blackops) { - this.log(action.name + " successful! Gained " + formatNumber(rankGain, 1) + " rank"); - } - } else { - this.gainActionStats(player, action, false); - let rankLoss = 0; - let damage = 0; - if (action.rankLoss) { - rankLoss = addOffset(action.rankLoss, 10); - this.changeRank(player, -1 * rankLoss); - } - if (action.hpLoss) { - damage = action.hpLoss * difficultyMultiplier; - damage = Math.ceil(addOffset(damage, 10)); - const cost = calculateHospitalizationCost(player, damage); - if (player.takeDamage(damage)) { - ++this.numHosp; - this.moneyLost += cost; - } - } - teamLossMax = Math.floor(teamCount); - - if (this.logging.blackops) { - this.log(action.name + " failed! Lost " + formatNumber(rankLoss, 1) + " rank and took " + formatNumber(damage, 0) + " damage"); - } - } - - this.resetAction(); // Stop regardless of success or fail - - // Calculate team lossses - if (teamCount >= 1) { - const losses = getRandomInt(1, teamLossMax); - this.teamSize -= losses; - this.teamLost += losses; - if (this.logging.blackops) { - this.log("You lost " + formatNumber(losses, 0) + " team members during " + action.name); - } - } - } catch(e) { - exceptionAlert(e); - } - break; - } - case ActionTypes["Training"]: { - this.stamina -= (0.5 * BladeburnerConstants.BaseStaminaLoss); - const strExpGain = 30 * player.strength_exp_mult, - defExpGain = 30 * player.defense_exp_mult, - dexExpGain = 30 * player.dexterity_exp_mult, - agiExpGain = 30 * player.agility_exp_mult, - staminaGain = 0.04 * this.skillMultipliers.stamina; - player.gainStrengthExp(strExpGain); - player.gainDefenseExp(defExpGain); - player.gainDexterityExp(dexExpGain); - player.gainAgilityExp(agiExpGain); - this.staminaBonus += (staminaGain); - if (this.logging.general) { - this.log("Training completed. Gained: " + - formatNumber(strExpGain, 1) + " str exp, " + - formatNumber(defExpGain, 1) + " def exp, " + - formatNumber(dexExpGain, 1) + " dex exp, " + - formatNumber(agiExpGain, 1) + " agi exp, " + - formatNumber(staminaGain, 3) + " max stamina"); - } - this.startAction(player, this.action); // Repeat action - break; - } - case ActionTypes["FieldAnalysis"]: - case ActionTypes["Field Analysis"]: { - // Does not use stamina. Effectiveness depends on hacking, int, and cha - let eff = 0.04 * Math.pow(player.hacking_skill, 0.3) + - 0.04 * Math.pow(player.intelligence, 0.9) + - 0.02 * Math.pow(player.charisma, 0.3); - eff *= player.bladeburner_analysis_mult; - if (isNaN(eff) || eff < 0) { - throw new Error("Field Analysis Effectiveness calculated to be NaN or negative"); - } - const hackingExpGain = 20 * player.hacking_exp_mult, - charismaExpGain = 20 * player.charisma_exp_mult; - player.gainHackingExp(hackingExpGain); - player.gainIntelligenceExp(BladeburnerConstants.BaseIntGain); - player.gainCharismaExp(charismaExpGain); - this.changeRank(player, 0.1 * BitNodeMultipliers.BladeburnerRank); - this.getCurrentCity().improvePopulationEstimateByPercentage(eff * this.skillMultipliers.successChanceEstimate); - if (this.logging.general) { - this.log("Field analysis completed. Gained 0.1 rank, " + formatNumber(hackingExpGain, 1) + " hacking exp, and " + formatNumber(charismaExpGain, 1) + " charisma exp"); - } - this.startAction(player, this.action); // Repeat action - break; - } - case ActionTypes["Recruitment"]: { - const successChance = this.getRecruitmentSuccessChance(player); - if (Math.random() < successChance) { - const expGain = 2 * BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; - player.gainCharismaExp(expGain); - ++this.teamSize; - if (this.logging.general) { - this.log("Successfully recruited a team member! Gained " + formatNumber(expGain, 1) + " charisma exp"); - } - } else { - const expGain = BladeburnerConstants.BaseStatGain * this.actionTimeToComplete; - player.gainCharismaExp(expGain); - if (this.logging.general) { - this.log("Failed to recruit a team member. Gained " + formatNumber(expGain, 1) + " charisma exp"); - } - } - this.startAction(player, this.action); // Repeat action - break; - } - case ActionTypes["Diplomacy"]: { - const eff = this.getDiplomacyEffectiveness(player); - this.getCurrentCity().chaos *= eff; - if (this.getCurrentCity().chaos < 0) { this.getCurrentCity().chaos = 0; } - if (this.logging.general) { - this.log(`Diplomacy completed. Chaos levels in the current city fell by ${numeralWrapper.formatPercentage(1 - eff)}`); - } - this.startAction(player, this.action); // Repeat Action - break; - } - case ActionTypes["Hyperbolic Regeneration Chamber"]: { - player.regenerateHp(BladeburnerConstants.HrcHpGain); - - const staminaGain = this.maxStamina * (BladeburnerConstants.HrcStaminaGain / 100); - this.stamina = Math.min(this.maxStamina, this.stamina + staminaGain); - this.startAction(player, this.action); - if (this.logging.general) { - this.log(`Rested in Hyperbolic Regeneration Chamber. Restored ${BladeburnerConstants.HrcHpGain} HP and gained ${numeralWrapper.formatStamina(staminaGain)} stamina`); - } - break; - } - default: - console.error(`Bladeburner.completeAction() called for invalid action: ${this.action.type}`); - break; + // If the Player starts doing some other actions, set action to idle and alert + if ( + Augmentations[AugmentationNames.BladesSimulacrum].owned === false && + player.isWorking + ) { + if (this.action.type !== ActionTypes["Idle"]) { + let msg = + "Your Bladeburner action was cancelled because you started doing something else."; + if (this.automateEnabled) { + msg += `

    Your automation was disabled as well. You will have to re-enable it through the Bladeburner console`; + this.automateEnabled = false; } + if (!Settings.SuppressBladeburnerPopup) { + dialogBoxCreate(msg); + } + } + this.resetAction(); } - changeRank(player: IPlayer, change: number): void { - if (isNaN(change)) {throw new Error("NaN passed into Bladeburner.changeRank()");} - this.rank += change; - if (this.rank < 0) {this.rank = 0;} - this.maxRank = Math.max(this.rank, this.maxRank); - - const bladeburnersFactionName = "Bladeburners"; - if (factionExists(bladeburnersFactionName)) { - const bladeburnerFac = Factions[bladeburnersFactionName]; - if (!(bladeburnerFac instanceof Faction)) { - throw new Error("Could not properly get Bladeburner Faction object in Bladeburner UI Overview Faction button"); - } - if (bladeburnerFac.isMember) { - const favorBonus = 1 + (bladeburnerFac.favor / 100); - bladeburnerFac.playerReputation += (BladeburnerConstants.RankToFactionRepFactor * change * player.faction_rep_mult * favorBonus); - } - } - - // Gain skill points - const rankNeededForSp = (this.totalSkillPoints+1) * BladeburnerConstants.RanksPerSkillPoint; - if (this.maxRank >= rankNeededForSp) { - // Calculate how many skill points to gain - const gainedSkillPoints = Math.floor((this.maxRank - rankNeededForSp) / BladeburnerConstants.RanksPerSkillPoint + 1); - this.skillPoints += gainedSkillPoints; - this.totalSkillPoints += gainedSkillPoints; - } + // If the Player has no Stamina, set action to idle + if (this.stamina <= 0) { + this.log( + "Your Bladeburner action was cancelled because your stamina hit 0", + ); + this.resetAction(); } - processAction(player: IPlayer, seconds: number): void { - if (this.action.type === ActionTypes["Idle"]) return; - if (this.actionTimeToComplete <= 0) { - throw new Error(`Invalid actionTimeToComplete value: ${this.actionTimeToComplete}, type; ${this.action.type}`); - } - if (!(this.action instanceof ActionIdentifier)) { - throw new Error("Bladeburner.action is not an ActionIdentifier Object"); + // A 'tick' for this mechanic is one second (= 5 game cycles) + if (this.storedCycles >= BladeburnerConstants.CyclesPerSecond) { + let seconds = Math.floor( + this.storedCycles / BladeburnerConstants.CyclesPerSecond, + ); + seconds = Math.min(seconds, 5); // Max of 5 'ticks' + this.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond; + + // Stamina + this.calculateMaxStamina(player); + this.stamina += this.calculateStaminaGainPerSecond(player) * seconds; + this.stamina = Math.min(this.maxStamina, this.stamina); + + // Count increase for contracts/operations + for (const contract of Object.values(this.contracts) as Contract[]) { + contract.count += + (seconds * contract.countGrowth) / + BladeburnerConstants.ActionCountGrowthPeriod; + } + for (const op of Object.values(this.operations) as Operation[]) { + op.count += + (seconds * op.countGrowth) / + BladeburnerConstants.ActionCountGrowthPeriod; + } + + // Chaos goes down very slowly + for (const cityName of BladeburnerConstants.CityNames) { + const city = this.cities[cityName]; + if (!(city instanceof City)) { + throw new Error( + "Invalid City object when processing passive chaos reduction in Bladeburner.process", + ); } + city.chaos -= 0.0001 * seconds; + city.chaos = Math.max(0, city.chaos); + } - // If the previous action went past its completion time, add to the next action - // This is not added inmediatly in case the automation changes the action - this.actionTimeCurrent += seconds + this.actionTimeOverflow; - this.actionTimeOverflow = 0; - if (this.actionTimeCurrent >= this.actionTimeToComplete) { - this.actionTimeOverflow = this.actionTimeCurrent - this.actionTimeToComplete; - return this.completeAction(player); - } - } + // Random Events + this.randomEventCounter -= seconds; + if (this.randomEventCounter <= 0) { + this.randomEvent(); + // Add instead of setting because we might have gone over the required time for the event + this.randomEventCounter += getRandomInt(240, 600); + } - calculateStaminaGainPerSecond(player: IPlayer): number { - const effAgility = player.agility * this.skillMultipliers.effAgi; - const maxStaminaBonus = this.maxStamina / BladeburnerConstants.MaxStaminaToGainFactor; - const gain = (BladeburnerConstants.StaminaGainPerSecond + maxStaminaBonus) * Math.pow(effAgility, 0.17); - return gain * (this.skillMultipliers.stamina * player.bladeburner_stamina_gain_mult); - } + this.processAction(player, seconds); - calculateMaxStamina(player: IPlayer): void { - const effAgility = player.agility * this.skillMultipliers.effAgi; - const maxStamina = (Math.pow(effAgility, 0.8) + this.staminaBonus) * - this.skillMultipliers.stamina * - player.bladeburner_max_stamina_mult; - if (this.maxStamina !== maxStamina) { - const oldMax = this.maxStamina; - this.maxStamina = maxStamina; - this.stamina = this.maxStamina * this.stamina / oldMax; - } - if (isNaN(maxStamina)) {throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");} - } - - create(): void { - this.contracts["Tracking"] = new Contract({ - name:"Tracking", - desc:"Identify and locate Synthoids. This contract involves reconnaissance " + - "and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.

    " + - "Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for " + - "whatever city you are currently in.", - baseDifficulty:125,difficultyFac:1.02,rewardFac:1.041, - rankGain:0.3, hpLoss:0.5, - count:getRandomInt(25, 150), countGrowth:getRandomInt(5, 75)/10, - weights:{hack:0,str:0.05,def:0.05,dex:0.35,agi:0.35,cha:0.1, int:0.05}, - decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.9, int:1}, - isStealth:true, - }); - this.contracts["Bounty Hunter"] = new Contract({ - name:"Bounty Hunter", - desc:"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.

    " + - "Successfully completing a Bounty Hunter contract will lower the population in your " + - "current city, and will also increase its chaos level.", - baseDifficulty:250, difficultyFac:1.04,rewardFac:1.085, - rankGain:0.9, hpLoss:1, - count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10, - weights:{hack:0,str:0.15,def:0.15,dex:0.25,agi:0.25,cha:0.1, int:0.1}, - decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, - isKill:true, - }); - this.contracts["Retirement"] = new Contract({ - name:"Retirement", - desc:"Hunt down and retire (kill) rogue Synthoids.

    " + - "Successfully completing a Retirement contract will lower the population in your current " + - "city, and will also increase its chaos level.", - baseDifficulty:200, difficultyFac:1.03, rewardFac:1.065, - rankGain:0.6, hpLoss:1, - count:getRandomInt(5, 150), countGrowth:getRandomInt(5, 75)/10, - weights:{hack:0,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0.1, int:0.1}, - decays:{hack:0,str:0.91,def:0.91,dex:0.91,agi:0.91,cha:0.8, int:0.9}, - isKill:true, - }); - - this.operations["Investigation"] = new Operation({ - name:"Investigation", - desc:"As a field agent, investigate and identify Synthoid " + - "populations, movements, and operations.

    Successful " + - "Investigation ops will increase the accuracy of your " + - "synthoid data.

    " + - "You will NOT lose HP from failed Investigation ops.", - baseDifficulty:400, difficultyFac:1.03,rewardFac:1.07,reqdRank:25, - rankGain:2.2, rankLoss:0.2, - count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10, - weights:{hack:0.25,str:0.05,def:0.05,dex:0.2,agi:0.1,cha:0.25, int:0.1}, - decays:{hack:0.85,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, - isStealth:true, - }); - this.operations["Undercover Operation"] = new Operation({ - name:"Undercover Operation", - desc:"Conduct undercover operations to identify hidden " + - "and underground Synthoid communities and organizations.

    " + - "Successful Undercover ops will increase the accuracy of your synthoid " + - "data.", - baseDifficulty:500, difficultyFac:1.04, rewardFac:1.09, reqdRank:100, - rankGain:4.4, rankLoss:0.4, hpLoss:2, - count:getRandomInt(1, 100), countGrowth:getRandomInt(10, 40)/10, - weights:{hack:0.2,str:0.05,def:0.05,dex:0.2,agi:0.2,cha:0.2, int:0.1}, - decays:{hack:0.8,str:0.9,def:0.9,dex:0.9,agi:0.9,cha:0.7, int:0.9}, - isStealth:true, - }); - this.operations["Sting Operation"] = new Operation({ - name:"Sting Operation", - desc:"Conduct a sting operation to bait and capture particularly " + - "notorious Synthoid criminals.", - baseDifficulty:650, difficultyFac:1.04, rewardFac:1.095, reqdRank:500, - rankGain:5.5, rankLoss:0.5, hpLoss:2.5, - count:getRandomInt(1, 150), countGrowth:getRandomInt(3, 40)/10, - weights:{hack:0.25,str:0.05,def:0.05,dex:0.25,agi:0.1,cha:0.2, int:0.1}, - decays:{hack:0.8,str:0.85,def:0.85,dex:0.85,agi:0.85,cha:0.7, int:0.9}, - isStealth:true, - }); - this.operations["Raid"] = new Operation({ - name:"Raid", - desc:"Lead an assault on a known Synthoid community. Note that " + - "there must be an existing Synthoid community in your current city " + - "in order for this Operation to be successful.", - baseDifficulty:800, difficultyFac:1.045, rewardFac:1.1, reqdRank:3000, - rankGain:55,rankLoss:2.5,hpLoss:50, - count:getRandomInt(1, 150), countGrowth:getRandomInt(2, 40)/10, - weights:{hack:0.1,str:0.2,def:0.2,dex:0.2,agi:0.2,cha:0, int:0.1}, - decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, - isKill:true, - }); - this.operations["Stealth Retirement Operation"] = new Operation({ - name:"Stealth Retirement Operation", - desc:"Lead a covert operation to retire Synthoids. The " + - "objective is to complete the task without " + - "drawing any attention. Stealth and discretion are key.", - baseDifficulty:1000, difficultyFac:1.05, rewardFac:1.11, reqdRank:20e3, - rankGain:22, rankLoss:2, hpLoss:10, - count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10, - weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, - decays:{hack:0.7,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.9}, - isStealth:true, isKill:true, - }); - this.operations["Assassination"] = new Operation({ - name:"Assassination", - desc:"Assassinate Synthoids that have been identified as " + - "important, high-profile social and political leaders " + - "in the Synthoid communities.", - baseDifficulty:1500, difficultyFac:1.06, rewardFac:1.14, reqdRank:50e3, - rankGain:44, rankLoss:4, hpLoss:5, - count:getRandomInt(1, 150), countGrowth:getRandomInt(1, 20)/10, - weights:{hack:0.1,str:0.1,def:0.1,dex:0.3,agi:0.3,cha:0, int:0.1}, - decays:{hack:0.6,str:0.8,def:0.8,dex:0.8,agi:0.8,cha:0, int:0.8}, - isStealth:true, isKill:true, - }); - } - - process(player: IPlayer): void { - // Edge case condition...if Operation Daedalus is complete trigger the BitNode - if (redPillFlag === false && this.blackops.hasOwnProperty("Operation Daedalus")) { - return hackWorldDaemon(player.bitNodeN); - } - - // If the Player starts doing some other actions, set action to idle and alert - if (Augmentations[AugmentationNames.BladesSimulacrum].owned === false && player.isWorking) { - if (this.action.type !== ActionTypes["Idle"]) { - let msg = "Your Bladeburner action was cancelled because you started doing something else."; - if (this.automateEnabled) { - msg += `

    Your automation was disabled as well. You will have to re-enable it through the Bladeburner console` - this.automateEnabled = false; - } - if (!Settings.SuppressBladeburnerPopup) { - dialogBoxCreate(msg); - } - } - this.resetAction(); - } - - // If the Player has no Stamina, set action to idle - if (this.stamina <= 0) { - this.log("Your Bladeburner action was cancelled because your stamina hit 0"); - this.resetAction(); - } - - // A 'tick' for this mechanic is one second (= 5 game cycles) - if (this.storedCycles >= BladeburnerConstants.CyclesPerSecond) { - let seconds = Math.floor(this.storedCycles / BladeburnerConstants.CyclesPerSecond); - seconds = Math.min(seconds, 5); // Max of 5 'ticks' - this.storedCycles -= seconds * BladeburnerConstants.CyclesPerSecond; - - // Stamina - this.calculateMaxStamina(player); - this.stamina += (this.calculateStaminaGainPerSecond(player) * seconds); - this.stamina = Math.min(this.maxStamina, this.stamina); - - // Count increase for contracts/operations - for (const contract of (Object.values(this.contracts) as Contract[])) { - contract.count += (seconds * contract.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); - } - for (const op of (Object.values(this.operations) as Operation[])) { - op.count += (seconds * op.countGrowth/BladeburnerConstants.ActionCountGrowthPeriod); - } - - // Chaos goes down very slowly - for (const cityName of BladeburnerConstants.CityNames) { - const city = this.cities[cityName]; - if (!(city instanceof City)) {throw new Error("Invalid City object when processing passive chaos reduction in Bladeburner.process");} - city.chaos -= (0.0001 * seconds); - city.chaos = Math.max(0, city.chaos); - } - - // Random Events - this.randomEventCounter -= seconds; - if (this.randomEventCounter <= 0) { - this.randomEvent(); - // Add instead of setting because we might have gone over the required time for the event - this.randomEventCounter += getRandomInt(240, 600); - } - - this.processAction(player, seconds); - - // Automation - if (this.automateEnabled) { - // Note: Do NOT set this.action = this.automateActionHigh/Low since it creates a reference - if (this.stamina <= this.automateThreshLow) { - if (this.action.name !== this.automateActionLow.name || this.action.type !== this.automateActionLow.type) { - this.action = new ActionIdentifier({type: this.automateActionLow.type, name: this.automateActionLow.name}); - this.startAction(player, this.action); - } - } else if (this.stamina >= this.automateThreshHigh) { - if (this.action.name !== this.automateActionHigh.name || this.action.type !== this.automateActionHigh.type) { - this.action = new ActionIdentifier({type: this.automateActionHigh.type, name: this.automateActionHigh.name}); - this.startAction(player, this.action); - } - } - } - } - } - - getTypeAndNameFromActionId(actionId: IActionIdentifier): {type: string; name: string} { - const res = {type: '', name: ''}; - const types = Object.keys(ActionTypes); - for (let i = 0; i < types.length; ++i) { - if (actionId.type === ActionTypes[types[i]]) { - res.type = types[i]; - break; - } - } - if (res.type == null) {res.type = "Idle";} - - res.name = actionId.name != null ? actionId.name : "Idle"; - return res; - } - - getContractNamesNetscriptFn(): string[] { - return Object.keys(this.contracts); - } - - getOperationNamesNetscriptFn(): string[] { - return Object.keys(this.operations); - } - - getBlackOpNamesNetscriptFn(): string[] { - return Object.keys(BlackOperations); - } - - getGeneralActionNamesNetscriptFn(): string[] { - return Object.keys(GeneralActions); - } - - getSkillNamesNetscriptFn(): string[] { - return Object.keys(Skills); - } - - startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = this.getActionIdFromTypeAndName(type, name); - if (actionId == null) { - workerScript.log("bladeburner.startAction", errorLogText); - return false; - } - - // Special logic for Black Ops - if (actionId.type === ActionTypes["BlackOp"]) { - // Can't start a BlackOp if you don't have the required rank - const action = this.getActionObject(actionId); - if(action == null) throw new Error(`Action not found ${actionId.type}, ${actionId.name}`); - if(!(action instanceof BlackOperation)) throw new Error(`Action should be BlackOperation but isn't`); - //const blackOp = (action as BlackOperation); - if (action.reqdRank > this.rank) { - workerScript.log("bladeburner.startAction", `Insufficient rank to start Black Op '${actionId.name}'.`); - return false; - } - - // Can't start a BlackOp if its already been done - if (this.blackops[actionId.name] != null) { - workerScript.log("bladeburner.startAction", `Black Op ${actionId.name} has already been completed.`); - return false; - } - - // Can't start a BlackOp if you haven't done the one before it - const blackops = []; - for (const nm in BlackOperations) { - if (BlackOperations.hasOwnProperty(nm)) { - blackops.push(nm); - } - } - blackops.sort(function(a, b) { - return (BlackOperations[a].reqdRank - BlackOperations[b].reqdRank); // Sort black ops in intended order + // Automation + if (this.automateEnabled) { + // Note: Do NOT set this.action = this.automateActionHigh/Low since it creates a reference + if (this.stamina <= this.automateThreshLow) { + if ( + this.action.name !== this.automateActionLow.name || + this.action.type !== this.automateActionLow.type + ) { + this.action = new ActionIdentifier({ + type: this.automateActionLow.type, + name: this.automateActionLow.name, }); - - const i = blackops.indexOf(actionId.name); - if (i === -1) { - workerScript.log("bladeburner.startAction", `Invalid Black Op: '${name}'`); - return false; - } - - if (i > 0 && this.blackops[blackops[i-1]] == null) { - workerScript.log("bladeburner.startAction", `Preceding Black Op must be completed before starting '${actionId.name}'.`); - return false; - } + this.startAction(player, this.action); + } + } else if (this.stamina >= this.automateThreshHigh) { + if ( + this.action.name !== this.automateActionHigh.name || + this.action.type !== this.automateActionHigh.type + ) { + this.action = new ActionIdentifier({ + type: this.automateActionHigh.type, + name: this.automateActionHigh.name, + }); + this.startAction(player, this.action); + } } + } + } + } - try { - this.startAction(player, actionId); - workerScript.log("bladeburner.startAction", `Starting bladeburner action with type '${type}' and name ${name}"`); - return true; - } catch(e) { - this.resetAction(); - workerScript.log("bladeburner.startAction", errorLogText); - return false; - } + getTypeAndNameFromActionId(actionId: IActionIdentifier): { + type: string; + name: string; + } { + const res = { type: "", name: "" }; + const types = Object.keys(ActionTypes); + for (let i = 0; i < types.length; ++i) { + if (actionId.type === ActionTypes[types[i]]) { + res.type = types[i]; + break; + } + } + if (res.type == null) { + res.type = "Idle"; } - getActionTimeNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number { - const errorLogText = `Invalid action: type='${type}' name='${name}'` - const actionId = this.getActionIdFromTypeAndName(type, name); - if (actionId == null) { - workerScript.log("bladeburner.getActionTime", errorLogText); - return -1; - } + res.name = actionId.name != null ? actionId.name : "Idle"; + return res; + } - const actionObj = this.getActionObject(actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getActionTime", errorLogText); - return -1; - } + getContractNamesNetscriptFn(): string[] { + return Object.keys(this.contracts); + } - switch (actionId.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - return actionObj.getActionTime(this); - case ActionTypes["Training"]: - case ActionTypes["Field Analysis"]: - case ActionTypes["FieldAnalysis"]: - return 30; - case ActionTypes["Recruitment"]: - return this.getRecruitmentTime(player); - case ActionTypes["Diplomacy"]: - case ActionTypes["Hyperbolic Regeneration Chamber"]: - return 60; - default: - workerScript.log("bladeburner.getActionTime", errorLogText); - return -1; - } + getOperationNamesNetscriptFn(): string[] { + return Object.keys(this.operations); + } + + getBlackOpNamesNetscriptFn(): string[] { + return Object.keys(BlackOperations); + } + + getGeneralActionNamesNetscriptFn(): string[] { + return Object.keys(GeneralActions); + } + + getSkillNamesNetscriptFn(): string[] { + return Object.keys(Skills); + } + + startActionNetscriptFn( + player: IPlayer, + type: string, + name: string, + workerScript: WorkerScript, + ): boolean { + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log("bladeburner.startAction", errorLogText); + return false; } - getActionEstimatedSuccessChanceNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number[] { - const errorLogText = `Invalid action: type='${type}' name='${name}'` - const actionId = this.getActionIdFromTypeAndName(type, name); - if (actionId == null) { - workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); - return [-1, -1]; - } + // Special logic for Black Ops + if (actionId.type === ActionTypes["BlackOp"]) { + // Can't start a BlackOp if you don't have the required rank + const action = this.getActionObject(actionId); + if (action == null) + throw new Error(`Action not found ${actionId.type}, ${actionId.name}`); + if (!(action instanceof BlackOperation)) + throw new Error(`Action should be BlackOperation but isn't`); + //const blackOp = (action as BlackOperation); + if (action.reqdRank > this.rank) { + workerScript.log( + "bladeburner.startAction", + `Insufficient rank to start Black Op '${actionId.name}'.`, + ); + return false; + } - const actionObj = this.getActionObject(actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); - return [-1, -1]; - } + // Can't start a BlackOp if its already been done + if (this.blackops[actionId.name] != null) { + workerScript.log( + "bladeburner.startAction", + `Black Op ${actionId.name} has already been completed.`, + ); + return false; + } - switch (actionId.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - return actionObj.getEstSuccessChance(this); - case ActionTypes["Training"]: - case ActionTypes["Field Analysis"]: - case ActionTypes["FieldAnalysis"]: - case ActionTypes["Diplomacy"]: - case ActionTypes["Hyperbolic Regeneration Chamber"]: - return [1, 1]; - case ActionTypes["Recruitment"]: - const recChance = this.getRecruitmentSuccessChance(player); - return [recChance, recChance]; - default: - workerScript.log("bladeburner.getActionEstimatedSuccessChance", errorLogText); - return [-1, -1]; + // Can't start a BlackOp if you haven't done the one before it + const blackops = []; + for (const nm in BlackOperations) { + if (BlackOperations.hasOwnProperty(nm)) { + blackops.push(nm); } + } + blackops.sort(function (a, b) { + return BlackOperations[a].reqdRank - BlackOperations[b].reqdRank; // Sort black ops in intended order + }); + + const i = blackops.indexOf(actionId.name); + if (i === -1) { + workerScript.log( + "bladeburner.startAction", + `Invalid Black Op: '${name}'`, + ); + return false; + } + + if (i > 0 && this.blackops[blackops[i - 1]] == null) { + workerScript.log( + "bladeburner.startAction", + `Preceding Black Op must be completed before starting '${actionId.name}'.`, + ); + return false; + } } - getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = this.getActionIdFromTypeAndName(type, name); - if (actionId == null) { - workerScript.log("bladeburner.getActionCountRemaining", errorLogText); - return -1; - } + try { + this.startAction(player, actionId); + workerScript.log( + "bladeburner.startAction", + `Starting bladeburner action with type '${type}' and name ${name}"`, + ); + return true; + } catch (e) { + this.resetAction(); + workerScript.log("bladeburner.startAction", errorLogText); + return false; + } + } - const actionObj = this.getActionObject(actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getActionCountRemaining", errorLogText); - return -1; - } - - switch (actionId.type) { - case ActionTypes["Contract"]: - case ActionTypes["Operation"]: - return Math.floor( actionObj.count ); - case ActionTypes["BlackOp"]: - case ActionTypes["BlackOperation"]: - if (this.blackops[name] != null) { - return 0; - } else { - return 1; - } - case ActionTypes["Training"]: - case ActionTypes["Recruitment"]: - case ActionTypes["Field Analysis"]: - case ActionTypes["FieldAnalysis"]: - case ActionTypes["Diplomacy"]: - case ActionTypes["Hyperbolic Regeneration Chamber"]: - return Infinity; - default: - workerScript.log("bladeburner.getActionCountRemaining", errorLogText); - return -1; - } + getActionTimeNetscriptFn( + player: IPlayer, + type: string, + name: string, + workerScript: WorkerScript, + ): number { + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log("bladeburner.getActionTime", errorLogText); + return -1; } - getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number { - if (skillName === "" || !Skills.hasOwnProperty(skillName)) { - workerScript.log("bladeburner.getSkillLevel", `Invalid skill: '${skillName}'`); - return -1; - } + const actionObj = this.getActionObject(actionId); + if (actionObj == null) { + workerScript.log("bladeburner.getActionTime", errorLogText); + return -1; + } - if (this.skills[skillName] == null) { - return 0; + switch (actionId.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + return actionObj.getActionTime(this); + case ActionTypes["Training"]: + case ActionTypes["Field Analysis"]: + case ActionTypes["FieldAnalysis"]: + return 30; + case ActionTypes["Recruitment"]: + return this.getRecruitmentTime(player); + case ActionTypes["Diplomacy"]: + case ActionTypes["Hyperbolic Regeneration Chamber"]: + return 60; + default: + workerScript.log("bladeburner.getActionTime", errorLogText); + return -1; + } + } + + getActionEstimatedSuccessChanceNetscriptFn( + player: IPlayer, + type: string, + name: string, + workerScript: WorkerScript, + ): number[] { + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log( + "bladeburner.getActionEstimatedSuccessChance", + errorLogText, + ); + return [-1, -1]; + } + + const actionObj = this.getActionObject(actionId); + if (actionObj == null) { + workerScript.log( + "bladeburner.getActionEstimatedSuccessChance", + errorLogText, + ); + return [-1, -1]; + } + + switch (actionId.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + return actionObj.getEstSuccessChance(this); + case ActionTypes["Training"]: + case ActionTypes["Field Analysis"]: + case ActionTypes["FieldAnalysis"]: + case ActionTypes["Diplomacy"]: + case ActionTypes["Hyperbolic Regeneration Chamber"]: + return [1, 1]; + case ActionTypes["Recruitment"]: + const recChance = this.getRecruitmentSuccessChance(player); + return [recChance, recChance]; + default: + workerScript.log( + "bladeburner.getActionEstimatedSuccessChance", + errorLogText, + ); + return [-1, -1]; + } + } + + getActionCountRemainingNetscriptFn( + type: string, + name: string, + workerScript: WorkerScript, + ): number { + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log("bladeburner.getActionCountRemaining", errorLogText); + return -1; + } + + const actionObj = this.getActionObject(actionId); + if (actionObj == null) { + workerScript.log("bladeburner.getActionCountRemaining", errorLogText); + return -1; + } + + switch (actionId.type) { + case ActionTypes["Contract"]: + case ActionTypes["Operation"]: + return Math.floor(actionObj.count); + case ActionTypes["BlackOp"]: + case ActionTypes["BlackOperation"]: + if (this.blackops[name] != null) { + return 0; } else { - return this.skills[skillName]; + return 1; } + case ActionTypes["Training"]: + case ActionTypes["Recruitment"]: + case ActionTypes["Field Analysis"]: + case ActionTypes["FieldAnalysis"]: + case ActionTypes["Diplomacy"]: + case ActionTypes["Hyperbolic Regeneration Chamber"]: + return Infinity; + default: + workerScript.log("bladeburner.getActionCountRemaining", errorLogText); + return -1; + } + } + + getSkillLevelNetscriptFn( + skillName: string, + workerScript: WorkerScript, + ): number { + if (skillName === "" || !Skills.hasOwnProperty(skillName)) { + workerScript.log( + "bladeburner.getSkillLevel", + `Invalid skill: '${skillName}'`, + ); + return -1; } - getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number { - if (skillName === "" || !Skills.hasOwnProperty(skillName)) { - workerScript.log("bladeburner.getSkillUpgradeCost", `Invalid skill: '${skillName}'`); - return -1; - } + if (this.skills[skillName] == null) { + return 0; + } else { + return this.skills[skillName]; + } + } - const skill = Skills[skillName]; - if (this.skills[skillName] == null) { - return skill.calculateCost(0); - } else { - return skill.calculateCost(this.skills[skillName]); - } + getSkillUpgradeCostNetscriptFn( + skillName: string, + workerScript: WorkerScript, + ): number { + if (skillName === "" || !Skills.hasOwnProperty(skillName)) { + workerScript.log( + "bladeburner.getSkillUpgradeCost", + `Invalid skill: '${skillName}'`, + ); + return -1; } - upgradeSkillNetscriptFn(skillName: string, workerScript: WorkerScript): boolean { - const errorLogText = `Invalid skill: '${skillName}'`; - if (!Skills.hasOwnProperty(skillName)) { - workerScript.log("bladeburner.upgradeSkill", errorLogText); - return false; - } + const skill = Skills[skillName]; + if (this.skills[skillName] == null) { + return skill.calculateCost(0); + } else { + return skill.calculateCost(this.skills[skillName]); + } + } - const skill = Skills[skillName]; - let currentLevel = 0; - if (this.skills[skillName] && !isNaN(this.skills[skillName])) { - currentLevel = this.skills[skillName]; - } - const cost = skill.calculateCost(currentLevel); - - if(skill.maxLvl && currentLevel >= skill.maxLvl) { - workerScript.log("bladeburner.upgradeSkill", `Skill '${skillName}' is already maxed.`); - return false; - } - - if (this.skillPoints < cost) { - workerScript.log("bladeburner.upgradeSkill", `You do not have enough skill points to upgrade ${skillName} (You have ${this.skillPoints}, you need ${cost})`); - return false; - } - - this.skillPoints -= cost; - this.upgradeSkill(skill); - workerScript.log("bladeburner.upgradeSkill", `'${skillName}' upgraded to level ${this.skills[skillName]}`); - return true; + upgradeSkillNetscriptFn( + skillName: string, + workerScript: WorkerScript, + ): boolean { + const errorLogText = `Invalid skill: '${skillName}'`; + if (!Skills.hasOwnProperty(skillName)) { + workerScript.log("bladeburner.upgradeSkill", errorLogText); + return false; } - getTeamSizeNetscriptFn(type: string, name: string, workerScript: WorkerScript): number { - if (type === "" && name === "") { - return this.teamSize; - } + const skill = Skills[skillName]; + let currentLevel = 0; + if (this.skills[skillName] && !isNaN(this.skills[skillName])) { + currentLevel = this.skills[skillName]; + } + const cost = skill.calculateCost(currentLevel); - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = this.getActionIdFromTypeAndName(type, name); - if (actionId == null) { - workerScript.log("bladeburner.getTeamSize", errorLogText); - return -1; - } - - const actionObj = this.getActionObject(actionId); - if (actionObj == null) { - workerScript.log("bladeburner.getTeamSize", errorLogText); - return -1; - } - - if (actionId.type === ActionTypes["Operation"] || - actionId.type === ActionTypes["BlackOp"] || - actionId.type === ActionTypes["BlackOperation"]) { - return actionObj.teamCount; - } else { - return 0; - } + if (skill.maxLvl && currentLevel >= skill.maxLvl) { + workerScript.log( + "bladeburner.upgradeSkill", + `Skill '${skillName}' is already maxed.`, + ); + return false; } - setTeamSizeNetscriptFn(type: string, name: string, size: number, workerScript: WorkerScript): number { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - const actionId = this.getActionIdFromTypeAndName(type, name); - if (actionId == null) { - workerScript.log("bladeburner.setTeamSize", errorLogText); - return -1; - } - - if (actionId.type !== ActionTypes["Operation"] && - actionId.type !== ActionTypes["BlackOp"] && - actionId.type !== ActionTypes["BlackOperation"]) { - workerScript.log("bladeburner.setTeamSize", "Only valid for 'Operations' and 'BlackOps'"); - return -1; - } - - const actionObj = this.getActionObject(actionId); - if (actionObj == null) { - workerScript.log("bladeburner.setTeamSize", errorLogText); - return -1; - } - - let sanitizedSize = Math.round(size); - if (isNaN(sanitizedSize) || sanitizedSize < 0) { - workerScript.log("bladeburner.setTeamSize", `Invalid size: ${size}`); - return -1; - } - if (this.teamSize < sanitizedSize) {sanitizedSize = this.teamSize;} - actionObj.teamCount = sanitizedSize; - workerScript.log("bladeburner.setTeamSize", `Team size for '${name}' set to ${sanitizedSize}.`); - return sanitizedSize; + if (this.skillPoints < cost) { + workerScript.log( + "bladeburner.upgradeSkill", + `You do not have enough skill points to upgrade ${skillName} (You have ${this.skillPoints}, you need ${cost})`, + ); + return false; } - joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean { - const bladeburnerFac = Factions["Bladeburners"]; - if (bladeburnerFac.isMember) { - return true; - } else if (this.rank >= BladeburnerConstants.RankNeededForFaction) { - joinFaction(bladeburnerFac); - workerScript.log("bladeburner.joinBladeburnerFaction", "Joined Bladeburners faction."); - return true; - } else { - workerScript.log("bladeburner.joinBladeburnerFaction", `You do not have the required rank (${this.rank}/${BladeburnerConstants.RankNeededForFaction}).`); - return false; - } + this.skillPoints -= cost; + this.upgradeSkill(skill); + workerScript.log( + "bladeburner.upgradeSkill", + `'${skillName}' upgraded to level ${this.skills[skillName]}`, + ); + return true; + } + + getTeamSizeNetscriptFn( + type: string, + name: string, + workerScript: WorkerScript, + ): number { + if (type === "" && name === "") { + return this.teamSize; } - - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("Bladeburner", this); + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log("bladeburner.getTeamSize", errorLogText); + return -1; } - /** - * Initiatizes a Bladeburner object from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Bladeburner { - return Generic_fromJSON(Bladeburner, value.data); + const actionObj = this.getActionObject(actionId); + if (actionObj == null) { + workerScript.log("bladeburner.getTeamSize", errorLogText); + return -1; } + + if ( + actionId.type === ActionTypes["Operation"] || + actionId.type === ActionTypes["BlackOp"] || + actionId.type === ActionTypes["BlackOperation"] + ) { + return actionObj.teamCount; + } else { + return 0; + } + } + + setTeamSizeNetscriptFn( + type: string, + name: string, + size: number, + workerScript: WorkerScript, + ): number { + const errorLogText = `Invalid action: type='${type}' name='${name}'`; + const actionId = this.getActionIdFromTypeAndName(type, name); + if (actionId == null) { + workerScript.log("bladeburner.setTeamSize", errorLogText); + return -1; + } + + if ( + actionId.type !== ActionTypes["Operation"] && + actionId.type !== ActionTypes["BlackOp"] && + actionId.type !== ActionTypes["BlackOperation"] + ) { + workerScript.log( + "bladeburner.setTeamSize", + "Only valid for 'Operations' and 'BlackOps'", + ); + return -1; + } + + const actionObj = this.getActionObject(actionId); + if (actionObj == null) { + workerScript.log("bladeburner.setTeamSize", errorLogText); + return -1; + } + + let sanitizedSize = Math.round(size); + if (isNaN(sanitizedSize) || sanitizedSize < 0) { + workerScript.log("bladeburner.setTeamSize", `Invalid size: ${size}`); + return -1; + } + if (this.teamSize < sanitizedSize) { + sanitizedSize = this.teamSize; + } + actionObj.teamCount = sanitizedSize; + workerScript.log( + "bladeburner.setTeamSize", + `Team size for '${name}' set to ${sanitizedSize}.`, + ); + return sanitizedSize; + } + + joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean { + const bladeburnerFac = Factions["Bladeburners"]; + if (bladeburnerFac.isMember) { + return true; + } else if (this.rank >= BladeburnerConstants.RankNeededForFaction) { + joinFaction(bladeburnerFac); + workerScript.log( + "bladeburner.joinBladeburnerFaction", + "Joined Bladeburners faction.", + ); + return true; + } else { + workerScript.log( + "bladeburner.joinBladeburnerFaction", + `You do not have the required rank (${this.rank}/${BladeburnerConstants.RankNeededForFaction}).`, + ); + return false; + } + } + + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Bladeburner", this); + } + + /** + * Initiatizes a Bladeburner object from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Bladeburner { + return Generic_fromJSON(Bladeburner, value.data); + } } Reviver.constructors.Bladeburner = Bladeburner; diff --git a/src/Bladeburner/City.ts b/src/Bladeburner/City.ts index 361593c5f..c36673c78 100644 --- a/src/Bladeburner/City.ts +++ b/src/Bladeburner/City.ts @@ -1,173 +1,231 @@ - import { BladeburnerConstants } from "./data/Constants"; import { getRandomInt } from "../../utils/helpers/getRandomInt"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; import { addOffset } from "../../utils/helpers/addOffset"; interface IChangePopulationByCountParams { - estChange: number; - estOffset: number; + estChange: number; + estOffset: number; } interface IChangePopulationByPercentageParams { - nonZero: boolean; - changeEstEqually: boolean; + nonZero: boolean; + changeEstEqually: boolean; } export class City { + /** + * Name of the city. + */ + name = ""; - /** - * Name of the city. - */ - name = ""; + /** + * Population of the city. + */ + pop = 0; - /** - * Population of the city. - */ - pop = 0; + /** + * Population estimation of the city. + */ + popEst = 0; - /** - * Population estimation of the city. - */ - popEst = 0; + /** + * Number of communities in the city. + */ + comms = 0; - /** - * Number of communities in the city. - */ - comms = 0; + /** + * Estimated number of communities in the city. + */ + commsEst = 0; - /** - * Estimated number of communities in the city. - */ - commsEst = 0; + /** + * Chaos level of the city. + */ + chaos = 0; - /** - * Chaos level of the city. - */ - chaos = 0; + constructor(name: string = BladeburnerConstants.CityNames[2]) { + this.name = name; - constructor(name: string = BladeburnerConstants.CityNames[2]) { - this.name = name; + // Synthoid population and estimate + this.pop = getRandomInt( + BladeburnerConstants.PopulationThreshold, + 1.5 * BladeburnerConstants.PopulationThreshold, + ); + this.popEst = this.pop * (Math.random() + 0.5); - // Synthoid population and estimate - this.pop = getRandomInt(BladeburnerConstants.PopulationThreshold, 1.5 * BladeburnerConstants.PopulationThreshold); - this.popEst = this.pop * (Math.random() + 0.5); + // Number of Synthoid communities population and estimate + this.comms = getRandomInt(5, 150); + this.commsEst = this.comms + getRandomInt(-5, 5); + if (this.commsEst < 0) this.commsEst = 0; + this.chaos = 0; + } - // Number of Synthoid communities population and estimate - this.comms = getRandomInt(5, 150) - this.commsEst = this.comms + getRandomInt(-5, 5); - if (this.commsEst < 0) this.commsEst = 0; - this.chaos = 0; + /** + * p is the percentage, not the multiplier (e.g. pass in p = 5 for 5%) + */ + changeChaosByPercentage(p: number): void { + if (isNaN(p)) { + throw new Error("NaN passed into City.chaosChaosByPercentage()"); + } + if (p === 0) { + return; + } + this.chaos += this.chaos * (p / 100); + if (this.chaos < 0) { + this.chaos = 0; + } + } + + improvePopulationEstimateByCount(n: number): void { + if (isNaN(n)) { + throw new Error( + "NaN passeed into City.improvePopulationEstimateByCount()", + ); + } + if (this.popEst < this.pop) { + this.popEst += n; + if (this.popEst > this.pop) { + this.popEst = this.pop; + } + } else if (this.popEst > this.pop) { + this.popEst -= n; + if (this.popEst < this.pop) { + this.popEst = this.pop; + } + } + } + + /** + * p is the percentage, not the multiplier (e.g. pass in p = 5 for 5%) + */ + improvePopulationEstimateByPercentage(p: number, skillMult = 1): void { + p = p * skillMult; + if (isNaN(p)) { + throw new Error( + "NaN passed into City.improvePopulationEstimateByPercentage()", + ); + } + if (this.popEst < this.pop) { + ++this.popEst; // In case estimate is 0 + this.popEst *= 1 + p / 100; + if (this.popEst > this.pop) { + this.popEst = this.pop; + } + } else if (this.popEst > this.pop) { + this.popEst *= 1 - p / 100; + if (this.popEst < this.pop) { + this.popEst = this.pop; + } + } + } + + improveCommunityEstimate(n = 1): void { + if (isNaN(n)) { + throw new Error("NaN passed into City.improveCommunityEstimate()"); + } + if (this.commsEst < this.comms) { + this.commsEst += n; + if (this.commsEst > this.comms) { + this.commsEst = this.comms; + } + } else if (this.commsEst > this.comms) { + this.commsEst -= n; + if (this.commsEst < this.comms) { + this.commsEst = this.comms; + } + } + } + + /** + * @params options: + * estChange(int): How much the estimate should change by + * estOffset(int): Add offset to estimate (offset by percentage) + */ + changePopulationByCount( + n: number, + params: IChangePopulationByCountParams = { estChange: 0, estOffset: 0 }, + ): void { + if (isNaN(n)) { + throw new Error("NaN passed into City.changePopulationByCount()"); + } + this.pop += n; + if (params.estChange && !isNaN(params.estChange)) { + this.popEst += params.estChange; + } + if (params.estOffset) { + this.popEst = addOffset(this.popEst, params.estOffset); + } + this.popEst = Math.max(this.popEst, 0); + } + + /** + * @p is the percentage, not the multiplier. e.g. pass in p = 5 for 5% + * @params options: + * changeEstEqually(bool) - Change the population estimate by an equal amount + * nonZero (bool) - Set to true to ensure that population always changes by at least 1 + */ + changePopulationByPercentage( + p: number, + params: IChangePopulationByPercentageParams = { + nonZero: false, + changeEstEqually: false, + }, + ): number { + if (isNaN(p)) { + throw new Error("NaN passed into City.changePopulationByPercentage()"); + } + if (p === 0) { + return 0; + } + let change = Math.round(this.pop * (p / 100)); + + // Population always changes by at least 1 + if (params.nonZero && change === 0) { + p > 0 ? (change = 1) : (change = -1); } - /** - * p is the percentage, not the multiplier (e.g. pass in p = 5 for 5%) - */ - changeChaosByPercentage(p: number): void { - if (isNaN(p)) {throw new Error("NaN passed into City.chaosChaosByPercentage()");} - if (p === 0) {return;} - this.chaos += this.chaos * (p/100); - if (this.chaos < 0) {this.chaos = 0;} + this.pop += change; + if (params.changeEstEqually) { + this.popEst += change; + if (this.popEst < 0) { + this.popEst = 0; + } } + return change; + } - improvePopulationEstimateByCount(n: number): void { - if (isNaN(n)) {throw new Error("NaN passeed into City.improvePopulationEstimateByCount()");} - if (this.popEst < this.pop) { - this.popEst += n; - if (this.popEst > this.pop) {this.popEst = this.pop;} - } else if (this.popEst > this.pop) { - this.popEst -= n; - if (this.popEst < this.pop) {this.popEst = this.pop;} - } + changeChaosByCount(n: number): void { + if (isNaN(n)) { + throw new Error("NaN passed into City.changeChaosByCount()"); } - - /** - * p is the percentage, not the multiplier (e.g. pass in p = 5 for 5%) - */ - improvePopulationEstimateByPercentage(p: number, skillMult=1): void { - p = p*skillMult; - if (isNaN(p)) {throw new Error("NaN passed into City.improvePopulationEstimateByPercentage()");} - if (this.popEst < this.pop) { - ++this.popEst; // In case estimate is 0 - this.popEst *= (1 + (p/100)); - if (this.popEst > this.pop) {this.popEst = this.pop;} - } else if (this.popEst > this.pop) { - this.popEst *= (1 - (p/100)); - if (this.popEst < this.pop) {this.popEst = this.pop;} - } + if (n === 0) { + return; } - - improveCommunityEstimate(n=1): void { - if (isNaN(n)) {throw new Error("NaN passed into City.improveCommunityEstimate()");} - if (this.commsEst < this.comms) { - this.commsEst += n; - if (this.commsEst > this.comms) {this.commsEst = this.comms;} - } else if (this.commsEst > this.comms) { - this.commsEst -= n; - if (this.commsEst < this.comms) {this.commsEst = this.comms;} - } + this.chaos += n; + if (this.chaos < 0) { + this.chaos = 0; } + } - /** - * @params options: - * estChange(int): How much the estimate should change by - * estOffset(int): Add offset to estimate (offset by percentage) - */ - changePopulationByCount(n: number, params: IChangePopulationByCountParams = {estChange: 0, estOffset: 0}): void { - if (isNaN(n)) {throw new Error("NaN passed into City.changePopulationByCount()");} - this.pop += n; - if (params.estChange && !isNaN(params.estChange)) {this.popEst += params.estChange;} - if (params.estOffset) { - this.popEst = addOffset(this.popEst, params.estOffset); - } - this.popEst = Math.max(this.popEst, 0); - } + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("City", this); + } - /** - * @p is the percentage, not the multiplier. e.g. pass in p = 5 for 5% - * @params options: - * changeEstEqually(bool) - Change the population estimate by an equal amount - * nonZero (bool) - Set to true to ensure that population always changes by at least 1 - */ - changePopulationByPercentage(p: number, params: IChangePopulationByPercentageParams={nonZero: false, changeEstEqually: false}): number { - if (isNaN(p)) {throw new Error("NaN passed into City.changePopulationByPercentage()");} - if (p === 0) {return 0;} - let change = Math.round(this.pop * (p/100)); - - // Population always changes by at least 1 - if (params.nonZero && change === 0) { - p > 0 ? change = 1 : change = -1; - } - - this.pop += change; - if (params.changeEstEqually) { - this.popEst += change; - if (this.popEst < 0) {this.popEst = 0;} - } - return change; - } - - changeChaosByCount(n: number): void { - if (isNaN(n)) {throw new Error("NaN passed into City.changeChaosByCount()");} - if (n === 0) {return;} - this.chaos += n; - if (this.chaos < 0) {this.chaos = 0;} - } - - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("City", this); - } - - /** - * Initiatizes a City object from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): City { - return Generic_fromJSON(City, value.data); - } + /** + * Initiatizes a City object from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): City { + return Generic_fromJSON(City, value.data); + } } Reviver.constructors.City = City; diff --git a/src/Bladeburner/Contract.ts b/src/Bladeburner/Contract.ts index 332b98609..f9613e560 100644 --- a/src/Bladeburner/Contract.ts +++ b/src/Bladeburner/Contract.ts @@ -1,25 +1,28 @@ import { IBladeburner } from "./IBladeburner"; import { Action, IActionParams } from "./Action"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; export class Contract extends Action { + constructor(params: IActionParams | null = null) { + super(params); + } - constructor(params: IActionParams | null = null) { - super(params); - } + getActionTypeSkillSuccessBonus(inst: IBladeburner): number { + return inst.skillMultipliers.successChanceContract; + } - getActionTypeSkillSuccessBonus(inst: IBladeburner): number { - return inst.skillMultipliers.successChanceContract; - } + toJSON(): any { + return Generic_toJSON("Contract", this); + } - toJSON(): any { - return Generic_toJSON("Contract", this); - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Contract { - return Generic_fromJSON(Contract, value.data); - } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Contract { + return Generic_fromJSON(Contract, value.data); + } } -Reviver.constructors.Contract = Contract; \ No newline at end of file +Reviver.constructors.Contract = Contract; diff --git a/src/Bladeburner/GeneralActions.ts b/src/Bladeburner/GeneralActions.ts index 3bbbef3f5..ef3d1a723 100644 --- a/src/Bladeburner/GeneralActions.ts +++ b/src/Bladeburner/GeneralActions.ts @@ -3,47 +3,52 @@ import { IMap } from "../types"; export const GeneralActions: IMap = {}; -(function() { - // General Actions - let actionName; - actionName = "Training"; - GeneralActions[actionName] = new Action({ - name:actionName, - desc:"Improve your abilities at the Bladeburner unit's specialized training " + - "center. Doing this gives experience for all combat stats and also " + - "increases your max stamina.", - }); +(function () { + // General Actions + let actionName; + actionName = "Training"; + GeneralActions[actionName] = new Action({ + name: actionName, + desc: + "Improve your abilities at the Bladeburner unit's specialized training " + + "center. Doing this gives experience for all combat stats and also " + + "increases your max stamina.", + }); - actionName = "Field Analysis"; - GeneralActions[actionName] = new Action({ - name:actionName, - desc:"Mine and analyze Synthoid-related data. This improves the " + - "Bladeburner's unit intelligence on Synthoid locations and " + - "activities. Completing this action will improve the accuracy " + - "of your Synthoid population estimated in the current city.

    " + - "Does NOT require stamina.", - }); + actionName = "Field Analysis"; + GeneralActions[actionName] = new Action({ + name: actionName, + desc: + "Mine and analyze Synthoid-related data. This improves the " + + "Bladeburner's unit intelligence on Synthoid locations and " + + "activities. Completing this action will improve the accuracy " + + "of your Synthoid population estimated in the current city.

    " + + "Does NOT require stamina.", + }); - actionName = "Recruitment"; - GeneralActions[actionName] = new Action({ - name:actionName, - desc:"Attempt to recruit members for your Bladeburner team. These members " + - "can help you conduct operations.

    " + - "Does NOT require stamina.", - }); + actionName = "Recruitment"; + GeneralActions[actionName] = new Action({ + name: actionName, + desc: + "Attempt to recruit members for your Bladeburner team. These members " + + "can help you conduct operations.

    " + + "Does NOT require stamina.", + }); - actionName = "Diplomacy"; - GeneralActions[actionName] = new Action({ - name: actionName, - desc: "Improve diplomatic relations with the Synthoid population. " + - "Completing this action will reduce the Chaos level in your current city.

    " + - "Does NOT require stamina.", - }); + actionName = "Diplomacy"; + GeneralActions[actionName] = new Action({ + name: actionName, + desc: + "Improve diplomatic relations with the Synthoid population. " + + "Completing this action will reduce the Chaos level in your current city.

    " + + "Does NOT require stamina.", + }); - actionName = "Hyperbolic Regeneration Chamber"; - GeneralActions[actionName] = new Action({ - name: actionName, - desc: "Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. " + - "This will slowly heal your wounds and slightly increase your stamina.

    ", - }); -})() + actionName = "Hyperbolic Regeneration Chamber"; + GeneralActions[actionName] = new Action({ + name: actionName, + desc: + "Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. " + + "This will slowly heal your wounds and slightly increase your stamina.

    ", + }); +})(); diff --git a/src/Bladeburner/IAction.ts b/src/Bladeburner/IAction.ts index 6285cc2df..91e2776e7 100644 --- a/src/Bladeburner/IAction.ts +++ b/src/Bladeburner/IAction.ts @@ -1,72 +1,75 @@ import { IBladeburner } from "./IBladeburner"; export interface IStatsMultiplier { - [key: string]: number; + [key: string]: number; - hack: number; - str: number; - def: number; - dex: number; - agi: number; - cha: number; - int: number; + hack: number; + str: number; + def: number; + dex: number; + agi: number; + cha: number; + int: number; } export interface ISuccessChanceParams { - est: boolean; + est: boolean; } export interface IAction { - name: string; - desc: string; + name: string; + desc: string; - // Difficulty scales with level. See getDifficulty() method - level: number; - maxLevel: number; - autoLevel: boolean; - baseDifficulty: number; - difficultyFac: number; + // Difficulty scales with level. See getDifficulty() method + level: number; + maxLevel: number; + autoLevel: boolean; + baseDifficulty: number; + difficultyFac: number; - // Rank increase/decrease is affected by this exponent - rewardFac: number; + // Rank increase/decrease is affected by this exponent + rewardFac: number; - successes: number; - failures: number; + successes: number; + failures: number; - // All of these scale with level/difficulty - rankGain: number; - rankLoss: number; - hpLoss: number; - hpLost: number; + // All of these scale with level/difficulty + rankGain: number; + rankLoss: number; + hpLoss: number; + hpLost: number; - // Action Category. Current categories are stealth and kill - isStealth: boolean; - isKill: boolean; + // Action Category. Current categories are stealth and kill + isStealth: boolean; + isKill: boolean; - /** - * Number of this contract remaining, and its growth rate - * Growth rate is an integer and the count will increase by that integer every "cycle" - */ - count: number; - countGrowth: number; + /** + * Number of this contract remaining, and its growth rate + * Growth rate is an integer and the count will increase by that integer every "cycle" + */ + count: number; + countGrowth: number; - // Weighting of each stat in determining action success rate - weights: IStatsMultiplier; - // Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1) - decays: IStatsMultiplier; - teamCount: number; + // Weighting of each stat in determining action success rate + weights: IStatsMultiplier; + // Diminishing returns of stats (stat ^ decay where 0 <= decay <= 1) + decays: IStatsMultiplier; + teamCount: number; - getDifficulty(): number; - attempt(inst: IBladeburner): boolean; - getActionTimePenalty(): number; - getActionTime(inst: IBladeburner): number; - getTeamSuccessBonus(inst: IBladeburner): number; - getActionTypeSkillSuccessBonus(inst: IBladeburner): number; - getChaosCompetencePenalty(inst: IBladeburner, params: ISuccessChanceParams): number; - getChaosDifficultyBonus(inst: IBladeburner): number; - getEstSuccessChance(inst: IBladeburner): number[]; - getSuccessChance(inst: IBladeburner, params: ISuccessChanceParams): number; - getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number; - setMaxLevel(baseSuccessesPerLevel: number): void; - toJSON(): any; -} \ No newline at end of file + getDifficulty(): number; + attempt(inst: IBladeburner): boolean; + getActionTimePenalty(): number; + getActionTime(inst: IBladeburner): number; + getTeamSuccessBonus(inst: IBladeburner): number; + getActionTypeSkillSuccessBonus(inst: IBladeburner): number; + getChaosCompetencePenalty( + inst: IBladeburner, + params: ISuccessChanceParams, + ): number; + getChaosDifficultyBonus(inst: IBladeburner): number; + getEstSuccessChance(inst: IBladeburner): number[]; + getSuccessChance(inst: IBladeburner, params: ISuccessChanceParams): number; + getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number; + setMaxLevel(baseSuccessesPerLevel: number): void; + toJSON(): any; +} diff --git a/src/Bladeburner/IActionIdentifier.ts b/src/Bladeburner/IActionIdentifier.ts index 7be44d65c..3d2414e36 100644 --- a/src/Bladeburner/IActionIdentifier.ts +++ b/src/Bladeburner/IActionIdentifier.ts @@ -1,4 +1,4 @@ export interface IActionIdentifier { - name: string; - type: number; -} \ No newline at end of file + name: string; + type: number; +} diff --git a/src/Bladeburner/IBladeburner.ts b/src/Bladeburner/IBladeburner.ts index 4be05754b..05e70416a 100644 --- a/src/Bladeburner/IBladeburner.ts +++ b/src/Bladeburner/IBladeburner.ts @@ -6,100 +6,143 @@ import { IPlayer } from "../PersonObjects/IPlayer"; import { WorkerScript } from "../Netscript/WorkerScript"; export interface IBladeburner { - numHosp: number; - moneyLost: number; - rank: number; - maxRank: number; + numHosp: number; + moneyLost: number; + rank: number; + maxRank: number; - skillPoints: number; - totalSkillPoints: number; + skillPoints: number; + totalSkillPoints: number; - teamSize: number; - teamLost: number; - hpLost: number; + teamSize: number; + teamLost: number; + hpLost: number; - storedCycles: number; + storedCycles: number; - randomEventCounter: number; + randomEventCounter: number; - actionTimeToComplete: number; - actionTimeCurrent: number; - actionTimeOverflow: number; + actionTimeToComplete: number; + actionTimeCurrent: number; + actionTimeOverflow: number; - action: IActionIdentifier; + action: IActionIdentifier; - cities: any; - city: string; - skills: any; - skillMultipliers: any; - staminaBonus: number; - maxStamina: number; - stamina: number; - contracts: any; - operations: any; - blackops: any; - logging: any; - automateEnabled: boolean; - automateActionHigh: IActionIdentifier; - automateThreshHigh: number; - automateActionLow: IActionIdentifier; - automateThreshLow: number; - consoleHistory: string[]; - consoleLogs: string[]; + cities: any; + city: string; + skills: any; + skillMultipliers: any; + staminaBonus: number; + maxStamina: number; + stamina: number; + contracts: any; + operations: any; + blackops: any; + logging: any; + automateEnabled: boolean; + automateActionHigh: IActionIdentifier; + automateThreshHigh: number; + automateActionLow: IActionIdentifier; + automateThreshLow: number; + consoleHistory: string[]; + consoleLogs: string[]; - getCurrentCity(): City; - calculateStaminaPenalty(): number; - startAction(player: IPlayer, action: IActionIdentifier): void; - upgradeSkill(skill: Skill): void; - executeConsoleCommands(player: IPlayer, command: string): void; - postToConsole(input: string, saveToLogs?: boolean): void; - log(input: string): void; - resetAction(): void; - clearConsole(): void; + getCurrentCity(): City; + calculateStaminaPenalty(): number; + startAction(player: IPlayer, action: IActionIdentifier): void; + upgradeSkill(skill: Skill): void; + executeConsoleCommands(player: IPlayer, command: string): void; + postToConsole(input: string, saveToLogs?: boolean): void; + log(input: string): void; + resetAction(): void; + clearConsole(): void; - prestige(): void; - storeCycles(numCycles?: number): void; - getTypeAndNameFromActionId(actionId: IActionIdentifier): {type: string; name: string}; - getContractNamesNetscriptFn(): string[]; - getOperationNamesNetscriptFn(): string[]; - getBlackOpNamesNetscriptFn(): string[]; - getGeneralActionNamesNetscriptFn(): string[]; - getSkillNamesNetscriptFn(): string[]; - startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean; - getActionTimeNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number; - getActionEstimatedSuccessChanceNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number[]; - getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number; - getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number; - getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number; - upgradeSkillNetscriptFn(skillName: string, workerScript: WorkerScript): boolean; - getTeamSizeNetscriptFn(type: string, name: string, workerScript: WorkerScript): number; - setTeamSizeNetscriptFn(type: string, name: string, size: number, workerScript: WorkerScript): number; - joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean; - getActionIdFromTypeAndName(type: string, name: string): IActionIdentifier | null; - executeStartConsoleCommand(player: IPlayer, args: string[]): void; - executeSkillConsoleCommand(args: string[]): void; - executeLogConsoleCommand(args: string[]): void; - executeHelpConsoleCommand(args: string[]): void; - executeAutomateConsoleCommand(args: string[]): void; - parseCommandArguments(command: string): string[]; - executeConsoleCommand(player: IPlayer, command: string): void; - triggerMigration(sourceCityName: string): void; - triggerPotentialMigration(sourceCityName: string, chance: number): void; - randomEvent(): void; - gainActionStats(player: IPlayer, action: IAction, success: boolean): void; - getDiplomacyEffectiveness(player: IPlayer): number; - getRecruitmentSuccessChance(player: IPlayer): number; - getRecruitmentTime(player: IPlayer): number; - resetSkillMultipliers(): void; - updateSkillMultipliers(): void; - completeOperation(success: boolean): void; - getActionObject(actionId: IActionIdentifier): IAction | null; - completeContract(success: boolean): void; - completeAction(player: IPlayer): void; - changeRank(player: IPlayer, change: number): void; - processAction(player: IPlayer, seconds: number): void; - calculateStaminaGainPerSecond(player: IPlayer): number; - calculateMaxStamina(player: IPlayer): void; - create(): void; - process(player: IPlayer): void; -} \ No newline at end of file + prestige(): void; + storeCycles(numCycles?: number): void; + getTypeAndNameFromActionId(actionId: IActionIdentifier): { + type: string; + name: string; + }; + getContractNamesNetscriptFn(): string[]; + getOperationNamesNetscriptFn(): string[]; + getBlackOpNamesNetscriptFn(): string[]; + getGeneralActionNamesNetscriptFn(): string[]; + getSkillNamesNetscriptFn(): string[]; + startActionNetscriptFn( + player: IPlayer, + type: string, + name: string, + workerScript: WorkerScript, + ): boolean; + getActionTimeNetscriptFn( + player: IPlayer, + type: string, + name: string, + workerScript: WorkerScript, + ): number; + getActionEstimatedSuccessChanceNetscriptFn( + player: IPlayer, + type: string, + name: string, + workerScript: WorkerScript, + ): number[]; + getActionCountRemainingNetscriptFn( + type: string, + name: string, + workerScript: WorkerScript, + ): number; + getSkillLevelNetscriptFn( + skillName: string, + workerScript: WorkerScript, + ): number; + getSkillUpgradeCostNetscriptFn( + skillName: string, + workerScript: WorkerScript, + ): number; + upgradeSkillNetscriptFn( + skillName: string, + workerScript: WorkerScript, + ): boolean; + getTeamSizeNetscriptFn( + type: string, + name: string, + workerScript: WorkerScript, + ): number; + setTeamSizeNetscriptFn( + type: string, + name: string, + size: number, + workerScript: WorkerScript, + ): number; + joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean; + getActionIdFromTypeAndName( + type: string, + name: string, + ): IActionIdentifier | null; + executeStartConsoleCommand(player: IPlayer, args: string[]): void; + executeSkillConsoleCommand(args: string[]): void; + executeLogConsoleCommand(args: string[]): void; + executeHelpConsoleCommand(args: string[]): void; + executeAutomateConsoleCommand(args: string[]): void; + parseCommandArguments(command: string): string[]; + executeConsoleCommand(player: IPlayer, command: string): void; + triggerMigration(sourceCityName: string): void; + triggerPotentialMigration(sourceCityName: string, chance: number): void; + randomEvent(): void; + gainActionStats(player: IPlayer, action: IAction, success: boolean): void; + getDiplomacyEffectiveness(player: IPlayer): number; + getRecruitmentSuccessChance(player: IPlayer): number; + getRecruitmentTime(player: IPlayer): number; + resetSkillMultipliers(): void; + updateSkillMultipliers(): void; + completeOperation(success: boolean): void; + getActionObject(actionId: IActionIdentifier): IAction | null; + completeContract(success: boolean): void; + completeAction(player: IPlayer): void; + changeRank(player: IPlayer, change: number): void; + processAction(player: IPlayer, seconds: number): void; + calculateStaminaGainPerSecond(player: IPlayer): number; + calculateMaxStamina(player: IPlayer): void; + create(): void; + process(player: IPlayer): void; +} diff --git a/src/Bladeburner/Operation.ts b/src/Bladeburner/Operation.ts index 9d9936ee4..ae572ef64 100644 --- a/src/Bladeburner/Operation.ts +++ b/src/Bladeburner/Operation.ts @@ -1,57 +1,63 @@ import { IBladeburner } from "./IBladeburner"; import { BladeburnerConstants } from "./data/Constants"; import { Action, IActionParams } from "./Action"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; export interface IOperationParams extends IActionParams { - reqdRank?: number; - teamCount?: number; + reqdRank?: number; + teamCount?: number; } export class Operation extends Action { - reqdRank = 100; - teamCount = 0; + reqdRank = 100; + teamCount = 0; - constructor(params: IOperationParams | null = null) { - super(params); - if(params && params.reqdRank) this.reqdRank = params.reqdRank; - if(params && params.teamCount) this.teamCount = params.teamCount; + constructor(params: IOperationParams | null = null) { + super(params); + if (params && params.reqdRank) this.reqdRank = params.reqdRank; + if (params && params.teamCount) this.teamCount = params.teamCount; + } + + // For actions that have teams. To be implemented by subtypes. + getTeamSuccessBonus(inst: IBladeburner): number { + if (this.teamCount && this.teamCount > 0) { + this.teamCount = Math.min(this.teamCount, inst.teamSize); + const teamMultiplier = Math.pow(this.teamCount, 0.05); + return teamMultiplier; } - // For actions that have teams. To be implemented by subtypes. - getTeamSuccessBonus(inst: IBladeburner): number { - if (this.teamCount && this.teamCount > 0) { - this.teamCount = Math.min(this.teamCount, inst.teamSize); - const teamMultiplier = Math.pow(this.teamCount, 0.05); - return teamMultiplier; - } + return 1; + } - return 1; + getActionTypeSkillSuccessBonus(inst: IBladeburner): number { + return inst.skillMultipliers.successChanceOperation; + } + + getChaosDifficultyBonus( + inst: IBladeburner /*, params: ISuccessChanceParams*/, + ): number { + const city = inst.getCurrentCity(); + if (city.chaos > BladeburnerConstants.ChaosThreshold) { + const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold); + const mult = Math.pow(diff, 0.1); + return mult; } - getActionTypeSkillSuccessBonus(inst: IBladeburner): number { - return inst.skillMultipliers.successChanceOperation; - } + return 1; + } - getChaosDifficultyBonus(inst: IBladeburner/*, params: ISuccessChanceParams*/): number { - const city = inst.getCurrentCity(); - if (city.chaos > BladeburnerConstants.ChaosThreshold) { - const diff = 1 + (city.chaos - BladeburnerConstants.ChaosThreshold); - const mult = Math.pow(diff, 0.1); - return mult; - } + toJSON(): any { + return Generic_toJSON("Operation", this); + } - return 1; - } - - toJSON(): any { - return Generic_toJSON("Operation", this); - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Operation { - return Generic_fromJSON(Operation, value.data); - } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Operation { + return Generic_fromJSON(Operation, value.data); + } } -Reviver.constructors.Operation = Operation; \ No newline at end of file +Reviver.constructors.Operation = Operation; diff --git a/src/Bladeburner/Skill.ts b/src/Bladeburner/Skill.ts index 7021d4311..95d26dcce 100644 --- a/src/Bladeburner/Skill.ts +++ b/src/Bladeburner/Skill.ts @@ -1,130 +1,169 @@ import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; interface ISkillParams { - name: string; - desc: string; + name: string; + desc: string; - baseCost?: number; - costInc?: number; - maxLvl?: number; + baseCost?: number; + costInc?: number; + maxLvl?: number; - successChanceAll?: number; - successChanceStealth?: number; - successChanceKill?: number; - successChanceContract?: number; - successChanceOperation?: number; - successChanceEstimate?: number; + successChanceAll?: number; + successChanceStealth?: number; + successChanceKill?: number; + successChanceContract?: number; + successChanceOperation?: number; + successChanceEstimate?: number; - actionTime?: number; + actionTime?: number; - effHack?: number; - effStr?: number; - effDef?: number; - effDex?: number; - effAgi?: number; - effCha?: number; + effHack?: number; + effStr?: number; + effDef?: number; + effDex?: number; + effAgi?: number; + effCha?: number; - stamina?: number; - money?: number; - expGain?: number; + stamina?: number; + money?: number; + expGain?: number; } export class Skill { - name: string; - desc: string; - // Cost is in Skill Points - baseCost = 1; - // Additive cost increase per level - costInc = 1; - maxLvl = 0; + name: string; + desc: string; + // Cost is in Skill Points + baseCost = 1; + // Additive cost increase per level + costInc = 1; + maxLvl = 0; - /** - * These benefits are additive. So total multiplier will be level (handled externally) times the - * effects below - */ - successChanceAll = 0; - successChanceStealth = 0; - successChanceKill = 0; - successChanceContract = 0; - successChanceOperation = 0; + /** + * These benefits are additive. So total multiplier will be level (handled externally) times the + * effects below + */ + successChanceAll = 0; + successChanceStealth = 0; + successChanceKill = 0; + successChanceContract = 0; + successChanceOperation = 0; - /** - * This multiplier affects everything that increases synthoid population/community estimate - * e.g. Field analysis, Investigation Op, Undercover Op - */ - successChanceEstimate = 0; - actionTime = 0; - effHack = 0; - effStr = 0; - effDef = 0; - effDex = 0; - effAgi = 0; - effCha = 0; - stamina = 0; - money = 0; - expGain = 0; + /** + * This multiplier affects everything that increases synthoid population/community estimate + * e.g. Field analysis, Investigation Op, Undercover Op + */ + successChanceEstimate = 0; + actionTime = 0; + effHack = 0; + effStr = 0; + effDef = 0; + effDex = 0; + effAgi = 0; + effCha = 0; + stamina = 0; + money = 0; + expGain = 0; - constructor(params: ISkillParams={name:"foo", desc:"foo"}) { - if (!params.name) { - throw new Error("Failed to initialize Bladeburner Skill. No name was specified in ctor"); - } - if (!params.desc) { - throw new Error("Failed to initialize Bladeburner Skills. No desc was specified in ctor"); - } - this.name = params.name; - this.desc = params.desc; - this.baseCost = params.baseCost ? params.baseCost : 1; - this.costInc = params.costInc ? params.costInc : 1; + constructor(params: ISkillParams = { name: "foo", desc: "foo" }) { + if (!params.name) { + throw new Error( + "Failed to initialize Bladeburner Skill. No name was specified in ctor", + ); + } + if (!params.desc) { + throw new Error( + "Failed to initialize Bladeburner Skills. No desc was specified in ctor", + ); + } + this.name = params.name; + this.desc = params.desc; + this.baseCost = params.baseCost ? params.baseCost : 1; + this.costInc = params.costInc ? params.costInc : 1; - if (params.maxLvl) {this.maxLvl = params.maxLvl;} - - if (params.successChanceAll) {this.successChanceAll = params.successChanceAll;} - if (params.successChanceStealth) {this.successChanceStealth = params.successChanceStealth;} - if (params.successChanceKill) {this.successChanceKill = params.successChanceKill;} - if (params.successChanceContract) {this.successChanceContract = params.successChanceContract;} - if (params.successChanceOperation) {this.successChanceOperation = params.successChanceOperation;} - - - if (params.successChanceEstimate) {this.successChanceEstimate = params.successChanceEstimate;} - - if (params.actionTime) {this.actionTime = params.actionTime;} - if (params.effHack) {this.effHack = params.effHack;} - if (params.effStr) {this.effStr = params.effStr;} - if (params.effDef) {this.effDef = params.effDef;} - if (params.effDex) {this.effDex = params.effDex;} - if (params.effAgi) {this.effAgi = params.effAgi;} - if (params.effCha) {this.effCha = params.effCha;} - - if (params.stamina) {this.stamina = params.stamina;} - if (params.money) {this.money = params.money;} - if (params.expGain) {this.expGain = params.expGain;} + if (params.maxLvl) { + this.maxLvl = params.maxLvl; } - calculateCost(currentLevel: number): number { - return Math.floor((this.baseCost + (currentLevel * this.costInc)) * BitNodeMultipliers.BladeburnerSkillCost); + if (params.successChanceAll) { + this.successChanceAll = params.successChanceAll; + } + if (params.successChanceStealth) { + this.successChanceStealth = params.successChanceStealth; + } + if (params.successChanceKill) { + this.successChanceKill = params.successChanceKill; + } + if (params.successChanceContract) { + this.successChanceContract = params.successChanceContract; + } + if (params.successChanceOperation) { + this.successChanceOperation = params.successChanceOperation; } - getMultiplier(name: string): number { - if(name === "successChanceAll") return this.successChanceAll; - if(name === "successChanceStealth") return this.successChanceStealth; - if(name === "successChanceKill") return this.successChanceKill; - if(name === "successChanceContract") return this.successChanceContract; - if(name === "successChanceOperation") return this.successChanceOperation; - if(name === "successChanceEstimate") return this.successChanceEstimate; - - if(name === "actionTime") return this.actionTime; - - if(name === "effHack") return this.effHack; - if(name === "effStr") return this.effStr; - if(name === "effDef") return this.effDef; - if(name === "effDex") return this.effDex; - if(name === "effAgi") return this.effAgi; - if(name === "effCha") return this.effCha; - - if(name === "stamina") return this.stamina; - if(name === "money") return this.money; - if(name === "expGain") return this.expGain; - return 0; + if (params.successChanceEstimate) { + this.successChanceEstimate = params.successChanceEstimate; } + + if (params.actionTime) { + this.actionTime = params.actionTime; + } + if (params.effHack) { + this.effHack = params.effHack; + } + if (params.effStr) { + this.effStr = params.effStr; + } + if (params.effDef) { + this.effDef = params.effDef; + } + if (params.effDex) { + this.effDex = params.effDex; + } + if (params.effAgi) { + this.effAgi = params.effAgi; + } + if (params.effCha) { + this.effCha = params.effCha; + } + + if (params.stamina) { + this.stamina = params.stamina; + } + if (params.money) { + this.money = params.money; + } + if (params.expGain) { + this.expGain = params.expGain; + } + } + + calculateCost(currentLevel: number): number { + return Math.floor( + (this.baseCost + currentLevel * this.costInc) * + BitNodeMultipliers.BladeburnerSkillCost, + ); + } + + getMultiplier(name: string): number { + if (name === "successChanceAll") return this.successChanceAll; + if (name === "successChanceStealth") return this.successChanceStealth; + if (name === "successChanceKill") return this.successChanceKill; + if (name === "successChanceContract") return this.successChanceContract; + if (name === "successChanceOperation") return this.successChanceOperation; + if (name === "successChanceEstimate") return this.successChanceEstimate; + + if (name === "actionTime") return this.actionTime; + + if (name === "effHack") return this.effHack; + if (name === "effStr") return this.effStr; + if (name === "effDef") return this.effDef; + if (name === "effDex") return this.effDex; + if (name === "effAgi") return this.effAgi; + if (name === "effCha") return this.effCha; + + if (name === "stamina") return this.stamina; + if (name === "money") return this.money; + if (name === "expGain") return this.expGain; + return 0; + } } - diff --git a/src/Bladeburner/Skills.ts b/src/Bladeburner/Skills.ts index 9fccf5614..c242b7929 100644 --- a/src/Bladeburner/Skills.ts +++ b/src/Bladeburner/Skills.ts @@ -4,87 +4,112 @@ import { IMap } from "../types"; export const Skills: IMap = {}; -(function(){ - Skills[SkillNames.BladesIntuition] = new Skill({ - name:SkillNames.BladesIntuition, - desc:"Each level of this skill increases your success chance " + - "for all Contracts, Operations, and BlackOps by 3%", - baseCost: 3, costInc: 2.1, - successChanceAll:3, - }); - Skills[SkillNames.Cloak] = new Skill({ - name:SkillNames.Cloak, - desc:"Each level of this skill increases your " + - "success chance in stealth-related Contracts, Operations, and BlackOps by 5.5%", - baseCost: 2, costInc: 1.1, - successChanceStealth:5.5, - }); - Skills[SkillNames.ShortCircuit] = new Skill({ - name:SkillNames.ShortCircuit, - desc:"Each level of this skill increases your success chance " + - "in Contracts, Operations, and BlackOps that involve retirement by 5.5%", - baseCost: 2, costInc: 2.1, - successChanceKill:5.5, - }); - Skills[SkillNames.DigitalObserver] = new Skill({ - name:SkillNames.DigitalObserver, - desc:"Each level of this skill increases your success chance in " + - "all Operations and BlackOps by 4%", - baseCost: 2, costInc: 2.1, - successChanceOperation:4, - }); - Skills[SkillNames.Tracer] = new Skill({ - name:SkillNames.Tracer, - desc:"Each level of this skill increases your success chance in " + - "all Contracts by 4%", - baseCost: 2, costInc: 2.1, - successChanceContract:4, - }); - Skills[SkillNames.Overclock] = new Skill({ - name:SkillNames.Overclock, - desc:"Each level of this skill decreases the time it takes " + - "to attempt a Contract, Operation, and BlackOp by 1% (Max Level: 90)", - baseCost: 3, costInc: 1.4, maxLvl: 90, - actionTime:1, - }); - Skills[SkillNames.Reaper] = new Skill({ - name: SkillNames.Reaper, - desc: "Each level of this skill increases your effective combat stats for Bladeburner actions by 2%", - baseCost: 2, costInc: 2.1, - effStr: 2, effDef: 2, effDex: 2, effAgi: 2, - }); - Skills[SkillNames.EvasiveSystem] = new Skill({ - name:SkillNames.EvasiveSystem, - desc:"Each level of this skill increases your effective " + - "dexterity and agility for Bladeburner actions by 4%", - baseCost: 2, costInc: 2.1, - effDex: 4, effAgi: 4, - }); - Skills[SkillNames.Datamancer] = new Skill({ - name:SkillNames.Datamancer, - desc:"Each level of this skill increases your effectiveness in " + - "synthoid population analysis and investigation by 5%. " + - "This affects all actions that can potentially increase " + - "the accuracy of your synthoid population/community estimates.", - baseCost:3, costInc:1, - successChanceEstimate:5, - }); - Skills[SkillNames.CybersEdge] = new Skill({ - name:SkillNames.CybersEdge, - desc:"Each level of this skill increases your max stamina by 2%", - baseCost:1, costInc:3, - stamina:2, - }); - Skills[SkillNames.HandsOfMidas] = new Skill({ - name: SkillNames.HandsOfMidas, - desc: "Each level of this skill increases the amount of money you receive from Contracts by 10%", - baseCost: 2, costInc: 2.5, - money: 10, - }); - Skills[SkillNames.Hyperdrive] = new Skill({ - name: SkillNames.Hyperdrive, - desc: "Each level of this skill increases the experience earned from Contracts, Operations, and BlackOps by 10%", - baseCost: 1, costInc: 2.5, - expGain: 10, - }); -})() \ No newline at end of file +(function () { + Skills[SkillNames.BladesIntuition] = new Skill({ + name: SkillNames.BladesIntuition, + desc: + "Each level of this skill increases your success chance " + + "for all Contracts, Operations, and BlackOps by 3%", + baseCost: 3, + costInc: 2.1, + successChanceAll: 3, + }); + Skills[SkillNames.Cloak] = new Skill({ + name: SkillNames.Cloak, + desc: + "Each level of this skill increases your " + + "success chance in stealth-related Contracts, Operations, and BlackOps by 5.5%", + baseCost: 2, + costInc: 1.1, + successChanceStealth: 5.5, + }); + Skills[SkillNames.ShortCircuit] = new Skill({ + name: SkillNames.ShortCircuit, + desc: + "Each level of this skill increases your success chance " + + "in Contracts, Operations, and BlackOps that involve retirement by 5.5%", + baseCost: 2, + costInc: 2.1, + successChanceKill: 5.5, + }); + Skills[SkillNames.DigitalObserver] = new Skill({ + name: SkillNames.DigitalObserver, + desc: + "Each level of this skill increases your success chance in " + + "all Operations and BlackOps by 4%", + baseCost: 2, + costInc: 2.1, + successChanceOperation: 4, + }); + Skills[SkillNames.Tracer] = new Skill({ + name: SkillNames.Tracer, + desc: + "Each level of this skill increases your success chance in " + + "all Contracts by 4%", + baseCost: 2, + costInc: 2.1, + successChanceContract: 4, + }); + Skills[SkillNames.Overclock] = new Skill({ + name: SkillNames.Overclock, + desc: + "Each level of this skill decreases the time it takes " + + "to attempt a Contract, Operation, and BlackOp by 1% (Max Level: 90)", + baseCost: 3, + costInc: 1.4, + maxLvl: 90, + actionTime: 1, + }); + Skills[SkillNames.Reaper] = new Skill({ + name: SkillNames.Reaper, + desc: "Each level of this skill increases your effective combat stats for Bladeburner actions by 2%", + baseCost: 2, + costInc: 2.1, + effStr: 2, + effDef: 2, + effDex: 2, + effAgi: 2, + }); + Skills[SkillNames.EvasiveSystem] = new Skill({ + name: SkillNames.EvasiveSystem, + desc: + "Each level of this skill increases your effective " + + "dexterity and agility for Bladeburner actions by 4%", + baseCost: 2, + costInc: 2.1, + effDex: 4, + effAgi: 4, + }); + Skills[SkillNames.Datamancer] = new Skill({ + name: SkillNames.Datamancer, + desc: + "Each level of this skill increases your effectiveness in " + + "synthoid population analysis and investigation by 5%. " + + "This affects all actions that can potentially increase " + + "the accuracy of your synthoid population/community estimates.", + baseCost: 3, + costInc: 1, + successChanceEstimate: 5, + }); + Skills[SkillNames.CybersEdge] = new Skill({ + name: SkillNames.CybersEdge, + desc: "Each level of this skill increases your max stamina by 2%", + baseCost: 1, + costInc: 3, + stamina: 2, + }); + Skills[SkillNames.HandsOfMidas] = new Skill({ + name: SkillNames.HandsOfMidas, + desc: "Each level of this skill increases the amount of money you receive from Contracts by 10%", + baseCost: 2, + costInc: 2.5, + money: 10, + }); + Skills[SkillNames.Hyperdrive] = new Skill({ + name: SkillNames.Hyperdrive, + desc: "Each level of this skill increases the experience earned from Contracts, Operations, and BlackOps by 10%", + baseCost: 1, + costInc: 2.5, + expGain: 10, + }); +})(); diff --git a/src/Bladeburner/data/ActionTypes.ts b/src/Bladeburner/data/ActionTypes.ts index a1252b1e4..e5bcc789d 100644 --- a/src/Bladeburner/data/ActionTypes.ts +++ b/src/Bladeburner/data/ActionTypes.ts @@ -1,27 +1,27 @@ // Action Identifier enum export const ActionTypes: { - [key: string]: number; - "Idle": number; - "Contract": number; - "Operation": number; - "BlackOp": number; - "BlackOperation": number; - "Training": number; - "Recruitment": number; - "FieldAnalysis": number; - "Field Analysis": number; - "Diplomacy": number; - "Hyperbolic Regeneration Chamber": number; + [key: string]: number; + Idle: number; + Contract: number; + Operation: number; + BlackOp: number; + BlackOperation: number; + Training: number; + Recruitment: number; + FieldAnalysis: number; + "Field Analysis": number; + Diplomacy: number; + "Hyperbolic Regeneration Chamber": number; } = { - "Idle": 1, - "Contract": 2, - "Operation": 3, - "BlackOp": 4, - "BlackOperation": 4, - "Training": 5, - "Recruitment": 6, - "FieldAnalysis": 7, - "Field Analysis": 7, - "Diplomacy": 8, - "Hyperbolic Regeneration Chamber": 9, -}; \ No newline at end of file + Idle: 1, + Contract: 2, + Operation: 3, + BlackOp: 4, + BlackOperation: 4, + Training: 5, + Recruitment: 6, + FieldAnalysis: 7, + "Field Analysis": 7, + Diplomacy: 8, + "Hyperbolic Regeneration Chamber": 9, +}; diff --git a/src/Bladeburner/data/Constants.ts b/src/Bladeburner/data/Constants.ts index 33b3d3d69..4672f803c 100644 --- a/src/Bladeburner/data/Constants.ts +++ b/src/Bladeburner/data/Constants.ts @@ -1,79 +1,86 @@ export const BladeburnerConstants: { - CityNames: string[]; - CyclesPerSecond: number; - StaminaGainPerSecond: number; - BaseStaminaLoss: number; - MaxStaminaToGainFactor: number; - DifficultyToTimeFactor: number; - DiffMultExponentialFactor: number; - DiffMultLinearFactor: number; - EffAgiLinearFactor: number; - EffDexLinearFactor: number; - EffAgiExponentialFactor: number; - EffDexExponentialFactor: number; - BaseRecruitmentTimeNeeded: number; - PopulationThreshold: number; - PopulationExponent: number; - ChaosThreshold: number; - BaseStatGain: number; - BaseIntGain: number; - ActionCountGrowthPeriod: number; - RankToFactionRepFactor: number; - RankNeededForFaction: number; - ContractSuccessesPerLevel: number; - OperationSuccessesPerLevel: number; - RanksPerSkillPoint: number; - ContractBaseMoneyGain: number; - HrcHpGain: number; - HrcStaminaGain: number; + CityNames: string[]; + CyclesPerSecond: number; + StaminaGainPerSecond: number; + BaseStaminaLoss: number; + MaxStaminaToGainFactor: number; + DifficultyToTimeFactor: number; + DiffMultExponentialFactor: number; + DiffMultLinearFactor: number; + EffAgiLinearFactor: number; + EffDexLinearFactor: number; + EffAgiExponentialFactor: number; + EffDexExponentialFactor: number; + BaseRecruitmentTimeNeeded: number; + PopulationThreshold: number; + PopulationExponent: number; + ChaosThreshold: number; + BaseStatGain: number; + BaseIntGain: number; + ActionCountGrowthPeriod: number; + RankToFactionRepFactor: number; + RankNeededForFaction: number; + ContractSuccessesPerLevel: number; + OperationSuccessesPerLevel: number; + RanksPerSkillPoint: number; + ContractBaseMoneyGain: number; + HrcHpGain: number; + HrcStaminaGain: number; } = { - CityNames: ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"], - CyclesPerSecond: 5, // Game cycle is 200 ms + CityNames: [ + "Aevum", + "Chongqing", + "Sector-12", + "New Tokyo", + "Ishima", + "Volhaven", + ], + CyclesPerSecond: 5, // Game cycle is 200 ms - StaminaGainPerSecond: 0.0085, - BaseStaminaLoss: 0.285, // Base stamina loss per action. Increased based on difficulty - MaxStaminaToGainFactor: 70000, // Max Stamina is divided by this to get bonus stamina gain + StaminaGainPerSecond: 0.0085, + BaseStaminaLoss: 0.285, // Base stamina loss per action. Increased based on difficulty + MaxStaminaToGainFactor: 70000, // Max Stamina is divided by this to get bonus stamina gain - DifficultyToTimeFactor: 10, // Action Difficulty divided by this to get base action time + DifficultyToTimeFactor: 10, // Action Difficulty divided by this to get base action time - /** - * The difficulty multiplier affects stamina loss and hp loss of an action. Also affects - * experience gain. Its formula is: - * difficulty ^ exponentialFactor + difficulty / linearFactor - */ - DiffMultExponentialFactor: 0.28, - DiffMultLinearFactor: 650, + /** + * The difficulty multiplier affects stamina loss and hp loss of an action. Also affects + * experience gain. Its formula is: + * difficulty ^ exponentialFactor + difficulty / linearFactor + */ + DiffMultExponentialFactor: 0.28, + DiffMultLinearFactor: 650, - /** - * These factors are used to calculate action time. - * They affect how much action time is reduced based on your agility and dexterity - */ - EffAgiLinearFactor: 10e3, - EffDexLinearFactor: 10e3, - EffAgiExponentialFactor: 0.04, - EffDexExponentialFactor: 0.035, + /** + * These factors are used to calculate action time. + * They affect how much action time is reduced based on your agility and dexterity + */ + EffAgiLinearFactor: 10e3, + EffDexLinearFactor: 10e3, + EffAgiExponentialFactor: 0.04, + EffDexExponentialFactor: 0.035, - BaseRecruitmentTimeNeeded: 300, // Base time needed (s) to complete a Recruitment action + BaseRecruitmentTimeNeeded: 300, // Base time needed (s) to complete a Recruitment action - PopulationThreshold: 1e9, // Population which determines baseline success rate - PopulationExponent: 0.7, // Exponent that influences how different populations affect success rate - ChaosThreshold: 50, // City chaos level after which it starts making tasks harder + PopulationThreshold: 1e9, // Population which determines baseline success rate + PopulationExponent: 0.7, // Exponent that influences how different populations affect success rate + ChaosThreshold: 50, // City chaos level after which it starts making tasks harder - BaseStatGain: 1, // Base stat gain per second - BaseIntGain: 0.003, // Base intelligence stat gain + BaseStatGain: 1, // Base stat gain per second + BaseIntGain: 0.003, // Base intelligence stat gain - ActionCountGrowthPeriod: 480, // Time (s) it takes for action count to grow by its specified value + ActionCountGrowthPeriod: 480, // Time (s) it takes for action count to grow by its specified value - RankToFactionRepFactor: 2, // Delta Faction Rep = this * Delta Rank - RankNeededForFaction: 25, + RankToFactionRepFactor: 2, // Delta Faction Rep = this * Delta Rank + RankNeededForFaction: 25, - ContractSuccessesPerLevel: 3, // How many successes you need to level up a contract - OperationSuccessesPerLevel: 2.5, // How many successes you need to level up an op + ContractSuccessesPerLevel: 3, // How many successes you need to level up a contract + OperationSuccessesPerLevel: 2.5, // How many successes you need to level up an op - RanksPerSkillPoint: 3, // How many ranks needed to get 1 Skill Point + RanksPerSkillPoint: 3, // How many ranks needed to get 1 Skill Point - ContractBaseMoneyGain: 250e3, // Base Money Gained per contract + ContractBaseMoneyGain: 250e3, // Base Money Gained per contract - HrcHpGain: 2, // HP Gained from Hyperbolic Regeneration chamber - HrcStaminaGain: 1, // Percentage Stamina gained from Hyperbolic Regeneration Chamber -} \ No newline at end of file + HrcHpGain: 2, // HP Gained from Hyperbolic Regeneration chamber + HrcStaminaGain: 1, // Percentage Stamina gained from Hyperbolic Regeneration Chamber +}; diff --git a/src/Bladeburner/data/Help.ts b/src/Bladeburner/data/Help.ts index b2d085681..19485df37 100644 --- a/src/Bladeburner/data/Help.ts +++ b/src/Bladeburner/data/Help.ts @@ -10,118 +10,105 @@ export const ConsoleHelpText: { start: string[]; stop: string[]; } = { - helpList: [ - "Use 'help [command]' to get more information about a particular Bladeburner console command.", - "", - " automate [var] [val] [hi/low] Configure simple automation for Bladeburner tasks", - " clear/cls Clear the console", - " help [cmd] Display this help text, or help text for a specific command", - " log [en/dis] [type] Enable or disable logging for events and actions", - " skill [action] [name] Level or display info about your Bladeburner skills", - " start [type] [name] Start a Bladeburner action/task" , - " stop Stops your current Bladeburner action/task", - ], - automate: [ - "automate [var] [val] [hi/low]", - "", - "A simple way to automate your Bladeburner actions. This console command can be used " + + helpList: [ + "Use 'help [command]' to get more information about a particular Bladeburner console command.", + "", + " automate [var] [val] [hi/low] Configure simple automation for Bladeburner tasks", + " clear/cls Clear the console", + " help [cmd] Display this help text, or help text for a specific command", + " log [en/dis] [type] Enable or disable logging for events and actions", + " skill [action] [name] Level or display info about your Bladeburner skills", + " start [type] [name] Start a Bladeburner action/task", + " stop Stops your current Bladeburner action/task", + ], + automate: [ + "automate [var] [val] [hi/low]", + "", + "A simple way to automate your Bladeburner actions. This console command can be used " + "to automatically start an action when your stamina rises above a certain threshold, and " + "automatically switch to another action when your stamina drops below another threshold.", - " automate status - Check the current status of your automation and get a brief description of what it'll do", - " automate en - Enable the automation feature", - " automate dis - Disable the automation feature", - "", - "There are four properties that must be set for this automation to work properly. Here is how to set them:", - "", - " automate stamina 100 high", - " automate contract Tracking high", - " automate stamina 50 low", - " automate general 'Field Analysis' low", - "", - "Using the four console commands above will set the automation to perform Tracking contracts " + + " automate status - Check the current status of your automation and get a brief description of what it'll do", + " automate en - Enable the automation feature", + " automate dis - Disable the automation feature", + "", + "There are four properties that must be set for this automation to work properly. Here is how to set them:", + "", + " automate stamina 100 high", + " automate contract Tracking high", + " automate stamina 50 low", + " automate general 'Field Analysis' low", + "", + "Using the four console commands above will set the automation to perform Tracking contracts " + "if your stamina is 100 or higher, and then switch to Field Analysis if your stamina drops below " + "50. Note that when setting the action, the name of the action is CASE-SENSITIVE. It must " + "exactly match whatever the name is in the UI.", - ], - clear: [ - "clear", - "", - "Clears the console", - ], - cls: [ - "cls", - "", - "Clears the console", - ], - help: [ - "help [command]", - "", - "Running 'help' with no arguments displays the general help text, which lists all console commands " + + ], + clear: ["clear", "", "Clears the console"], + cls: ["cls", "", "Clears the console"], + help: [ + "help [command]", + "", + "Running 'help' with no arguments displays the general help text, which lists all console commands " + "and a brief description of what they do. A command can be specified to get more specific help text " + "about that particular command. For example:", - "", - " help automate", - "", - "will display specific information about using the automate console command", - ], - log: [ - "log [en/dis] [type]", - "", - "Enable or disable logging. By default, the results of completing actions such as contracts/operations are logged " + + "", + " help automate", + "", + "will display specific information about using the automate console command", + ], + log: [ + "log [en/dis] [type]", + "", + "Enable or disable logging. By default, the results of completing actions such as contracts/operations are logged " + "in the console. There are also random events that are logged in the console as well. The five categories of " + "things that get logged are:", - "", - "[general, contracts, ops, blackops, events]", - "", - "The logging for these categories can be enabled or disabled like so:", - "", - " log dis contracts - Disables logging that occurs when contracts are completed", - " log en contracts - Enables logging that occurs when contracts are completed", - " log dis events - Disables logging for Bladeburner random events", - "", - "Logging can be universally enabled/disabled using the 'all' keyword:", - "", - " log dis all", - " log en all", - ], - skill: [ - "skill [action] [name]", - "", - "Level or display information about your skills.", - "", - "To display information about all of your skills and your multipliers, use:", - "", - " skill list", - "", - "To display information about a specific skill, specify the name of the skill afterwards. " + + "", + "[general, contracts, ops, blackops, events]", + "", + "The logging for these categories can be enabled or disabled like so:", + "", + " log dis contracts - Disables logging that occurs when contracts are completed", + " log en contracts - Enables logging that occurs when contracts are completed", + " log dis events - Disables logging for Bladeburner random events", + "", + "Logging can be universally enabled/disabled using the 'all' keyword:", + "", + " log dis all", + " log en all", + ], + skill: [ + "skill [action] [name]", + "", + "Level or display information about your skills.", + "", + "To display information about all of your skills and your multipliers, use:", + "", + " skill list", + "", + "To display information about a specific skill, specify the name of the skill afterwards. " + "Note that the name of the skill is case-sensitive. Enter it exactly as seen in the UI. If " + "the name of the skill has whitespace, enclose the name of the skill in double quotation marks:", - "", - " skill list Reaper
    " + - " skill list 'Digital Observer'", - "", - "This console command can also be used to level up skills:", - "", - " skill level [skill name]", - ], - start: [ - "start [type] [name]", - "", - "Start an action. An action is specified by its type and its name. The " + + "", + " skill list Reaper
    " + " skill list 'Digital Observer'", + "", + "This console command can also be used to level up skills:", + "", + " skill level [skill name]", + ], + start: [ + "start [type] [name]", + "", + "Start an action. An action is specified by its type and its name. The " + "name is case-sensitive. It must appear exactly as it does in the UI. If " + "the name of the action has whitespace, enclose it in double quotation marks. " + "Valid action types include:", - "", - "[general, contract, op, blackop]", - "", - "Examples:", - "", - " start contract Tracking", - " start op 'Undercover Operation'", - ], - stop:[ - "stop", - "", - "Stop your current action and go idle.", - ], -} \ No newline at end of file + "", + "[general, contract, op, blackop]", + "", + "Examples:", + "", + " start contract Tracking", + " start op 'Undercover Operation'", + ], + stop: ["stop", "", "Stop your current action and go idle."], +}; diff --git a/src/Bladeburner/data/Icons.tsx b/src/Bladeburner/data/Icons.tsx index 296c1d249..b17211ca4 100644 --- a/src/Bladeburner/data/Icons.tsx +++ b/src/Bladeburner/data/Icons.tsx @@ -1,14 +1,30 @@ import * as React from "react"; -export const stealthIcon = +export const stealthIcon = ( + - - + + - -export const killIcon = + +); +export const killIcon = ( + - \ No newline at end of file + +); diff --git a/src/Bladeburner/data/SkillNames.ts b/src/Bladeburner/data/SkillNames.ts index 1a71bd63c..866ecac1f 100644 --- a/src/Bladeburner/data/SkillNames.ts +++ b/src/Bladeburner/data/SkillNames.ts @@ -1,31 +1,31 @@ export const SkillNames: { - BladesIntuition: string; - Cloak: string; - Marksman: string; - WeaponProficiency: string; - ShortCircuit: string; - DigitalObserver: string; - Tracer: string; - Overclock: string; - Reaper: string; - EvasiveSystem: string; - Datamancer: string; - CybersEdge: string; - HandsOfMidas: string; - Hyperdrive: string; + BladesIntuition: string; + Cloak: string; + Marksman: string; + WeaponProficiency: string; + ShortCircuit: string; + DigitalObserver: string; + Tracer: string; + Overclock: string; + Reaper: string; + EvasiveSystem: string; + Datamancer: string; + CybersEdge: string; + HandsOfMidas: string; + Hyperdrive: string; } = { - BladesIntuition: "Blade's Intuition", - Cloak: "Cloak", - Marksman: "Marksman", - WeaponProficiency: "Weapon Proficiency", - ShortCircuit: "Short-Circuit", - DigitalObserver: "Digital Observer", - Tracer: "Tracer", - Overclock: "Overclock", - Reaper: "Reaper", - EvasiveSystem: "Evasive System", - Datamancer: "Datamancer", - CybersEdge: "Cyber's Edge", - HandsOfMidas: "Hands of Midas", - Hyperdrive: "Hyperdrive", -} \ No newline at end of file + BladesIntuition: "Blade's Intuition", + Cloak: "Cloak", + Marksman: "Marksman", + WeaponProficiency: "Weapon Proficiency", + ShortCircuit: "Short-Circuit", + DigitalObserver: "Digital Observer", + Tracer: "Tracer", + Overclock: "Overclock", + Reaper: "Reaper", + EvasiveSystem: "Evasive System", + Datamancer: "Datamancer", + CybersEdge: "Cyber's Edge", + HandsOfMidas: "Hands of Midas", + Hyperdrive: "Hyperdrive", +}; diff --git a/src/Bladeburner/ui/AllPages.tsx b/src/Bladeburner/ui/AllPages.tsx index 0385e961a..0dafb959e 100644 --- a/src/Bladeburner/ui/AllPages.tsx +++ b/src/Bladeburner/ui/AllPages.tsx @@ -9,41 +9,65 @@ import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; + bladeburner: IBladeburner; + player: IPlayer; } export function AllPages(props: IProps): React.ReactElement { - const [page, setPage] = useState('General'); - const setRerender = useState(false)[1]; + const [page, setPage] = useState("General"); + const setRerender = useState(false)[1]; - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); + useEffect(() => { + const id = setInterval(() => setRerender((old) => !old), 1000); + return () => clearInterval(id); + }, []); - function Header(props: {name: string}): React.ReactElement { - return (setPage(props.name)} - className={page !== props.name ? - "bladeburner-nav-button" : - "bladeburner-nav-button-inactive"}> - {props.name} - ); - } - return (<> -
    -
    -
    -
    -
    -
    - {page === 'General' && } - {page === 'Contracts' && } - {page === 'Operations' && } - {page === 'BlackOps' && } - {page === 'Skills' && } -
    - {stealthIcon} = This action requires stealth, {killIcon} = This action involves retirement - ); -} \ No newline at end of file + function Header(props: { name: string }): React.ReactElement { + return ( + setPage(props.name)} + className={ + page !== props.name + ? "bladeburner-nav-button" + : "bladeburner-nav-button-inactive" + } + > + {props.name} + + ); + } + return ( + <> +
    +
    +
    +
    +
    +
    + {page === "General" && ( + + )} + {page === "Contracts" && ( + + )} + {page === "Operations" && ( + + )} + {page === "BlackOps" && ( + + )} + {page === "Skills" && } +
    + + {stealthIcon} = This action requires stealth, {killIcon} = This action + involves retirement + + + ); +} diff --git a/src/Bladeburner/ui/BlackOpElem.tsx b/src/Bladeburner/ui/BlackOpElem.tsx index dd5de5784..7d5fba339 100644 --- a/src/Bladeburner/ui/BlackOpElem.tsx +++ b/src/Bladeburner/ui/BlackOpElem.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { - formatNumber, - convertTimeMsToTimeElapsedString, + formatNumber, + convertTimeMsToTimeElapsedString, } from "../../../utils/StringHelperFunctions"; import { ActionTypes } from "../data/ActionTypes"; import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; @@ -14,76 +14,108 @@ import { SuccessChance } from "./SuccessChance"; import { CopyableText } from "../../ui/React/CopyableText"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; - action: any; + bladeburner: IBladeburner; + player: IPlayer; + action: any; } export function BlackOpElem(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - const isCompleted = (props.bladeburner.blackops[props.action.name] != null); - if(isCompleted) { - return ( -

    {props.action.name} (COMPLETED)

    ); - } + const setRerender = useState(false)[1]; + const isCompleted = props.bladeburner.blackops[props.action.name] != null; + if (isCompleted) { + return ( +

    {props.action.name} (COMPLETED)

    + ); + } - const isActive = props.bladeburner.action.type === ActionTypes["BlackOperation"] && props.action.name === props.bladeburner.action.name; - const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner); - const actionTime = props.action.getActionTime(props.bladeburner); - const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank; - const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete); + const isActive = + props.bladeburner.action.type === ActionTypes["BlackOperation"] && + props.action.name === props.bladeburner.action.name; + const estimatedSuccessChance = props.action.getEstSuccessChance( + props.bladeburner, + ); + const actionTime = props.action.getActionTime(props.bladeburner); + const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank; + const computedActionTimeCurrent = Math.min( + props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, + props.bladeburner.actionTimeToComplete, + ); - function onStart(): void { - props.bladeburner.action.type = ActionTypes.BlackOperation; - props.bladeburner.action.name = props.action.name; - props.bladeburner.startAction(props.player, props.bladeburner.action); - setRerender(old => !old); - } + function onStart(): void { + props.bladeburner.action.type = ActionTypes.BlackOperation; + props.bladeburner.action.name = props.action.name; + props.bladeburner.startAction(props.player, props.bladeburner.action); + setRerender((old) => !old); + } - function onTeam(): void { - const popupId = "bladeburner-operation-set-team-size-popup"; - createPopup(popupId, TeamSizePopup, { - bladeburner: props.bladeburner, - action: props.action, - popupId: popupId, - }); - } + function onTeam(): void { + const popupId = "bladeburner-operation-set-team-size-popup"; + createPopup(popupId, TeamSizePopup, { + bladeburner: props.bladeburner, + action: props.action, + popupId: popupId, + }); + } - return (<> -

    - {isActive ? - <> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) : - - } -

    - {isActive ? -

    {createProgressBarText({progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}

    : - <> - Start - - Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)}) - - } -
    -
    -

    -
    -
    -

    - Required Rank: {formatNumber(props.action.reqdRank, 0)} + return ( + <> +

    + {isActive ? ( + <> + (IN PROGRESS -{" "} + {formatNumber(computedActionTimeCurrent, 0)} /{" "} + {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) + + ) : ( + + )} +

    + {isActive ? ( +

    + {createProgressBarText({ + progress: + computedActionTimeCurrent / + props.bladeburner.actionTimeToComplete, + })}

    + ) : ( + <> + + Start + + + Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)}) + + + )} +
    +
    +

    +
    +
    +

    + Required Rank: {formatNumber(props.action.reqdRank, 0)} +

    +
    +
    +        Estimated Success Chance:{" "}
    +        {" "}
    +        {props.action.isStealth ? stealthIcon : <>}
    +        {props.action.isKill ? killIcon : <>}
             
    -
    -            Estimated Success Chance:  {props.action.isStealth?stealthIcon:<>}{props.action.isKill?killIcon:<>}
    -            
    - Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)} -
    - ); -} \ No newline at end of file + Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)} +
    + + ); +} diff --git a/src/Bladeburner/ui/BlackOpList.tsx b/src/Bladeburner/ui/BlackOpList.tsx index 345d73200..0bf21ac89 100644 --- a/src/Bladeburner/ui/BlackOpList.tsx +++ b/src/Bladeburner/ui/BlackOpList.tsx @@ -6,31 +6,43 @@ import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; + bladeburner: IBladeburner; + player: IPlayer; } export function BlackOpList(props: IProps): React.ReactElement { - let blackops: BlackOperation[] = []; - for (const blackopName in BlackOperations) { - if (BlackOperations.hasOwnProperty(blackopName)) { - blackops.push(BlackOperations[blackopName]); - } + let blackops: BlackOperation[] = []; + for (const blackopName in BlackOperations) { + if (BlackOperations.hasOwnProperty(blackopName)) { + blackops.push(BlackOperations[blackopName]); } - blackops.sort(function(a, b) { - return (a.reqdRank - b.reqdRank); - }); + } + blackops.sort(function (a, b) { + return a.reqdRank - b.reqdRank; + }); - blackops = blackops.filter((blackop: BlackOperation, i: number) => !(props.bladeburner.blackops[blackops[i].name] == null && - i !== 0 && - props.bladeburner.blackops[blackops[i-1].name] == null)); + blackops = blackops.filter( + (blackop: BlackOperation, i: number) => + !( + props.bladeburner.blackops[blackops[i].name] == null && + i !== 0 && + props.bladeburner.blackops[blackops[i - 1].name] == null + ), + ); - blackops = blackops.reverse(); + blackops = blackops.reverse(); - return (<> - {blackops.map((blackop: BlackOperation) =>
  • - -
  • , - )} - ); -} \ No newline at end of file + return ( + <> + {blackops.map((blackop: BlackOperation) => ( +
  • + +
  • + ))} + + ); +} diff --git a/src/Bladeburner/ui/BlackOpPage.tsx b/src/Bladeburner/ui/BlackOpPage.tsx index 6895b05d1..f841d7ebd 100644 --- a/src/Bladeburner/ui/BlackOpPage.tsx +++ b/src/Bladeburner/ui/BlackOpPage.tsx @@ -5,25 +5,29 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; import { CopyableText } from "../../ui/React/CopyableText"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; + bladeburner: IBladeburner; + player: IPlayer; } export function BlackOpPage(props: IProps): React.ReactElement { - return (<> -

    - Black Operations (Black Ops) are special, one-time covert operations. - Each Black Op must be unlocked successively by completing - the one before it. -
    -
    - Your ultimate goal to climb through the ranks of Bladeburners is to complete - all of the Black Ops. -
    -
    - Like normal operations, you may use a team for Black Ops. Failing - a black op will incur heavy HP and rank losses. -

    - - ); -} \ No newline at end of file + return ( + <> +

    + Black Operations (Black Ops) are special, one-time covert operations. + Each Black Op must be unlocked successively by completing the one before + it. +
    +
    + + Your ultimate goal to climb through the ranks of Bladeburners is to + complete all of the Black Ops. + +
    +
    + Like normal operations, you may use a team for Black Ops. Failing a + black op will incur heavy HP and rank losses. +

    + + + ); +} diff --git a/src/Bladeburner/ui/Console.tsx b/src/Bladeburner/ui/Console.tsx index 988d0d6a2..027feac3c 100644 --- a/src/Bladeburner/ui/Console.tsx +++ b/src/Bladeburner/ui/Console.tsx @@ -4,112 +4,141 @@ import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface ILineProps { - content: any; + content: any; } function Line(props: ILineProps): React.ReactElement { - return ( - {props.content} - ) + return ( + + + {props.content} + + + ); } interface IProps { - bladeburner: IBladeburner; - player: IPlayer; + bladeburner: IBladeburner; + player: IPlayer; } export function Console(props: IProps): React.ReactElement { - const lastRef = useRef(null); - const setRerender = useState(false)[1]; + const lastRef = useRef(null); + const setRerender = useState(false)[1]; - const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length); + const [consoleHistoryIndex, setConsoleHistoryIndex] = useState( + props.bladeburner.consoleHistory.length, + ); - // TODO: Figure out how to actually make the scrolling work correctly. - function scrollToBottom(): void { - if(!lastRef.current) return; - lastRef.current.scrollTop = lastRef.current.scrollHeight; + // TODO: Figure out how to actually make the scrolling work correctly. + function scrollToBottom(): void { + if (!lastRef.current) return; + lastRef.current.scrollTop = lastRef.current.scrollHeight; + } + + function rerender(): void { + setRerender((old) => !old); + } + + useEffect(() => { + const id = setInterval(rerender, 1000); + const id2 = setInterval(scrollToBottom, 100); + return () => { + clearInterval(id); + clearInterval(id2); + }; + }, []); + + function handleKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) { + event.preventDefault(); + const command = event.currentTarget.value; + event.currentTarget.value = ""; + if (command.length > 0) { + props.bladeburner.postToConsole("> " + command); + props.bladeburner.executeConsoleCommands(props.player, command); + setConsoleHistoryIndex(props.bladeburner.consoleHistory.length); + rerender(); + } } - function rerender(): void { - setRerender(old => !old); + const consoleHistory = props.bladeburner.consoleHistory; + + if (event.keyCode === 38) { + // up + let i = consoleHistoryIndex; + const len = consoleHistory.length; + if (len === 0) { + return; + } + if (i < 0 || i > len) { + setConsoleHistoryIndex(len); + } + + if (i !== 0) { + i = i - 1; + } + setConsoleHistoryIndex(i); + const prevCommand = consoleHistory[i]; + event.currentTarget.value = prevCommand; } - useEffect(() => { - const id = setInterval(rerender, 1000); - const id2 = setInterval(scrollToBottom, 100); - return () => { - clearInterval(id); - clearInterval(id2); - }; - }, []); + if (event.keyCode === 40) { + const i = consoleHistoryIndex; + const len = consoleHistory.length; - function handleKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) { - event.preventDefault(); - const command = event.currentTarget.value; - event.currentTarget.value = ""; - if (command.length > 0) { - props.bladeburner.postToConsole("> " + command); - props.bladeburner.executeConsoleCommands(props.player, command); - setConsoleHistoryIndex(props.bladeburner.consoleHistory.length); - rerender(); - } - } + if (len == 0) { + return; + } + if (i < 0 || i > len) { + setConsoleHistoryIndex(len); + } - const consoleHistory = props.bladeburner.consoleHistory; - - if (event.keyCode === 38) { // up - let i = consoleHistoryIndex; - const len = consoleHistory.length; - if (len === 0) {return;} - if (i < 0 || i > len) { - setConsoleHistoryIndex(len); - } - - if (i !== 0) { - i = i-1; - } - setConsoleHistoryIndex(i); - const prevCommand = consoleHistory[i]; - event.currentTarget.value = prevCommand; - } - - if (event.keyCode === 40) { - const i = consoleHistoryIndex; - const len = consoleHistory.length; - - if (len == 0) {return;} - if (i < 0 || i > len) { - setConsoleHistoryIndex(len); - } - - // Latest command, put nothing - if (i == len || i == len-1) { - setConsoleHistoryIndex(len); - event.currentTarget.value = ""; - } else { - setConsoleHistoryIndex(consoleHistoryIndex+1); - const prevCommand = consoleHistory[consoleHistoryIndex+1]; - event.currentTarget.value = prevCommand; - } - } + // Latest command, put nothing + if (i == len || i == len - 1) { + setConsoleHistoryIndex(len); + event.currentTarget.value = ""; + } else { + setConsoleHistoryIndex(consoleHistoryIndex + 1); + const prevCommand = consoleHistory[consoleHistoryIndex + 1]; + event.currentTarget.value = prevCommand; + } } + } - return (
    - - - {/* + return ( +
    +
    + + {/* TODO: optimize this. using `i` as a key here isn't great because it'll re-render everything everytime the console reaches max length. */} - {props.bladeburner.consoleLogs.map((log: any, i: number) => )} - - - - -
    -
    {"> "}
    -
    -
    ); -} \ No newline at end of file + {props.bladeburner.consoleLogs.map((log: any, i: number) => ( + + ))} + + +
    {"> "}
    + + + + + + + ); +} diff --git a/src/Bladeburner/ui/ContractElem.tsx b/src/Bladeburner/ui/ContractElem.tsx index cda601160..303524cee 100644 --- a/src/Bladeburner/ui/ContractElem.tsx +++ b/src/Bladeburner/ui/ContractElem.tsx @@ -2,8 +2,8 @@ import React, { useState } from "react"; import { ActionTypes } from "../data/ActionTypes"; import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; import { - formatNumber, - convertTimeMsToTimeElapsedString, + formatNumber, + convertTimeMsToTimeElapsedString, } from "../../../utils/StringHelperFunctions"; import { stealthIcon, killIcon } from "../data/Icons"; import { BladeburnerConstants } from "../data/Constants"; @@ -13,107 +13,161 @@ import { SuccessChance } from "./SuccessChance"; import { CopyableText } from "../../ui/React/CopyableText"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; - action: any; + bladeburner: IBladeburner; + player: IPlayer; + action: any; } export function ContractElem(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - const isActive = props.bladeburner.action.type === ActionTypes["Contract"] && props.action.name === props.bladeburner.action.name; - const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner); - const successChance = props.action.getSuccessChance(props.bladeburner); - const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete); - const maxLevel = (props.action.level >= props.action.maxLevel); - const actionTime = props.action.getActionTime(props.bladeburner); - const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`; + const setRerender = useState(false)[1]; + const isActive = + props.bladeburner.action.type === ActionTypes["Contract"] && + props.action.name === props.bladeburner.action.name; + const estimatedSuccessChance = props.action.getEstSuccessChance( + props.bladeburner, + ); + const successChance = props.action.getSuccessChance(props.bladeburner); + const computedActionTimeCurrent = Math.min( + props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, + props.bladeburner.actionTimeToComplete, + ); + const maxLevel = props.action.level >= props.action.maxLevel; + const actionTime = props.action.getActionTime(props.bladeburner); + const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`; - function onStart(): void { - props.bladeburner.action.type = ActionTypes.Contract; - props.bladeburner.action.name = props.action.name; - props.bladeburner.startAction(props.player, props.bladeburner.action); - setRerender(old => !old); - } + function onStart(): void { + props.bladeburner.action.type = ActionTypes.Contract; + props.bladeburner.action.name = props.action.name; + props.bladeburner.startAction(props.player, props.bladeburner.action); + setRerender((old) => !old); + } - function increaseLevel(): void { - ++props.action.level; - if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action); - setRerender(old => !old); - } + function increaseLevel(): void { + ++props.action.level; + if (isActive) + props.bladeburner.startAction(props.player, props.bladeburner.action); + setRerender((old) => !old); + } - function decreaseLevel(): void { - --props.action.level; - if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action); - setRerender(old => !old); - } + function decreaseLevel(): void { + --props.action.level; + if (isActive) + props.bladeburner.startAction(props.player, props.bladeburner.action); + setRerender((old) => !old); + } - function onAutolevel(event: React.ChangeEvent): void { - props.action.autoLevel = event.target.checked; - setRerender(old => !old); - } + function onAutolevel(event: React.ChangeEvent): void { + props.action.autoLevel = event.target.checked; + setRerender((old) => !old); + } - return (<> -

    - {isActive ? - <> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) : - - } -

    - {isActive ? -

    {createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}

    : + return ( + <> +

    + {isActive ? ( + <> + (IN PROGRESS -{" "} + {formatNumber(computedActionTimeCurrent, 0)} /{" "} + {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) + + ) : ( + + )} +

    + {isActive ? ( +

    + {createProgressBarText({ + progress: + computedActionTimeCurrent / + props.bladeburner.actionTimeToComplete, + })} +

    + ) : ( <> - - Start - - } + + Start + + + )} +
    +
    +
    +        
    +          {props.action.getSuccessesNeededForNextLevel(
    +            BladeburnerConstants.ContractSuccessesPerLevel,
    +          )}{" "}
    +          successes needed for next level
    +        
    +        Level: {props.action.level} / {props.action.maxLevel}
    +      
    + + {isActive && ( + + WARNING: changing the level will restart the Operation + + )} + ↑ + + + {isActive && ( + + WARNING: changing the level will restart the Operation + + )} + ↓ + +
    +
    +
    +        
             

    -
    -            
    -                {props.action.getSuccessesNeededForNextLevel(BladeburnerConstants.ContractSuccessesPerLevel)} successes needed for next level
    -            
    -            Level: {props.action.level} / {props.action.maxLevel}
    -        
    - - {isActive && (WARNING: changing the level will restart the Operation)} - ↑ - - - {isActive && (WARNING: changing the level will restart the Operation)} - ↓ - + Estimated success chance:{" "} + {" "} + {props.action.isStealth ? stealthIcon : <>} + {props.action.isKill ? killIcon : <>}
    + Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
    -
    -
    -

    -Estimated success chance: {props.action.isStealth?stealthIcon:<>}{props.action.isKill?killIcon:<>}
    -Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}
    -Contracts remaining: {Math.floor(props.action.count)}
    -Successes: {props.action.successes}
    -Failures: {props.action.failures} -
    + Contracts remaining: {Math.floor(props.action.count)}
    - - - ); + Successes: {props.action.successes} +
    + Failures: {props.action.failures} +
    +
    + + + + ); } diff --git a/src/Bladeburner/ui/ContractList.tsx b/src/Bladeburner/ui/ContractList.tsx index d95bd19c0..b4fa7881d 100644 --- a/src/Bladeburner/ui/ContractList.tsx +++ b/src/Bladeburner/ui/ContractList.tsx @@ -4,17 +4,24 @@ import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; + bladeburner: IBladeburner; + player: IPlayer; } export function ContractList(props: IProps): React.ReactElement { - const names = Object.keys(props.bladeburner.contracts); - const contracts = props.bladeburner.contracts; - return (<> - {names.map((name: string) =>
  • - -
  • , - )} - ); -} \ No newline at end of file + const names = Object.keys(props.bladeburner.contracts); + const contracts = props.bladeburner.contracts; + return ( + <> + {names.map((name: string) => ( +
  • + +
  • + ))} + + ); +} diff --git a/src/Bladeburner/ui/ContractPage.tsx b/src/Bladeburner/ui/ContractPage.tsx index 15848d8cf..f9cb9bdf9 100644 --- a/src/Bladeburner/ui/ContractPage.tsx +++ b/src/Bladeburner/ui/ContractPage.tsx @@ -4,20 +4,24 @@ import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; + bladeburner: IBladeburner; + player: IPlayer; } export function ContractPage(props: IProps): React.ReactElement { - return (<> -

    - Complete contracts in order to increase your Bladeburner rank and earn money. - Failing a contract will cause you to lose HP, which can lead to hospitalization. -
    -
    - You can unlock higher-level contracts by successfully completing them. - Higher-level contracts are more difficult, but grant more rank, experience, and money. -

    - - ); -} \ No newline at end of file + return ( + <> +

    + Complete contracts in order to increase your Bladeburner rank and earn + money. Failing a contract will cause you to lose HP, which can lead to + hospitalization. +
    +
    + You can unlock higher-level contracts by successfully completing them. + Higher-level contracts are more difficult, but grant more rank, + experience, and money. +

    + + + ); +} diff --git a/src/Bladeburner/ui/GeneralActionElem.tsx b/src/Bladeburner/ui/GeneralActionElem.tsx index b08c74478..9ae5b275d 100644 --- a/src/Bladeburner/ui/GeneralActionElem.tsx +++ b/src/Bladeburner/ui/GeneralActionElem.tsx @@ -2,68 +2,106 @@ import React, { useState } from "react"; import { ActionTypes } from "../data/ActionTypes"; import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; import { - formatNumber, - convertTimeMsToTimeElapsedString, + formatNumber, + convertTimeMsToTimeElapsedString, } from "../../../utils/StringHelperFunctions"; import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { CopyableText } from "../../ui/React/CopyableText"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; - action: any; + bladeburner: IBladeburner; + player: IPlayer; + action: any; } export function GeneralActionElem(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - const isActive = props.action.name === props.bladeburner.action.name; - const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete); - const actionTime = (function(): number{ - switch(props.action.name) { - case "Training": - case "Field Analysis": - return 30; - case "Diplomacy": - case "Hyperbolic Regeneration Chamber": - return 60; - case "Recruitment": - return props.bladeburner.getRecruitmentTime(props.player); - } - return -1; // dead code - })(); - const successChance = props.action.name === "Recruitment" ? Math.max(0, Math.min(props.bladeburner.getRecruitmentSuccessChance(props.player), 1)) : -1; - - function onStart(): void { - props.bladeburner.action.type = ActionTypes[(props.action.name as string)]; - props.bladeburner.action.name = props.action.name; - props.bladeburner.startAction(props.player, props.bladeburner.action); - setRerender(old => !old); + const setRerender = useState(false)[1]; + const isActive = props.action.name === props.bladeburner.action.name; + const computedActionTimeCurrent = Math.min( + props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, + props.bladeburner.actionTimeToComplete, + ); + const actionTime = (function (): number { + switch (props.action.name) { + case "Training": + case "Field Analysis": + return 30; + case "Diplomacy": + case "Hyperbolic Regeneration Chamber": + return 60; + case "Recruitment": + return props.bladeburner.getRecruitmentTime(props.player); } + return -1; // dead code + })(); + const successChance = + props.action.name === "Recruitment" + ? Math.max( + 0, + Math.min( + props.bladeburner.getRecruitmentSuccessChance(props.player), + 1, + ), + ) + : -1; - return (<> -

    - {isActive ? - <> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) : - - } -

    - {isActive ? -

    {createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}

    : + function onStart(): void { + props.bladeburner.action.type = ActionTypes[props.action.name as string]; + props.bladeburner.action.name = props.action.name; + props.bladeburner.startAction(props.player, props.bladeburner.action); + setRerender((old) => !old); + } + + return ( + <> +

    + {isActive ? ( + <> + (IN PROGRESS -{" "} + {formatNumber(computedActionTimeCurrent, 0)} /{" "} + {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) + + ) : ( + + )} +

    + {isActive ? ( +

    + {createProgressBarText({ + progress: + computedActionTimeCurrent / + props.bladeburner.actionTimeToComplete, + })} +

    + ) : ( <> - - Start - - } -
    -
    -


    -
    -            Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}
    -            {successChance !== -1 && <>
    Estimated success chance: {formatNumber(successChance*100, 1)}%} -
    - ); -} \ No newline at end of file + + Start + + + )} +
    +
    +
    
    +      
    +
    +
    +        Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
    +        {successChance !== -1 && (
    +          <>
    +            
    + Estimated success chance: {formatNumber(successChance * 100, 1)}% + + )} +
    + + ); +} diff --git a/src/Bladeburner/ui/GeneralActionList.tsx b/src/Bladeburner/ui/GeneralActionList.tsx index b5d2b5070..5085448b6 100644 --- a/src/Bladeburner/ui/GeneralActionList.tsx +++ b/src/Bladeburner/ui/GeneralActionList.tsx @@ -6,21 +6,28 @@ import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; + bladeburner: IBladeburner; + player: IPlayer; } export function GeneralActionList(props: IProps): React.ReactElement { - const actions: Action[] = []; - for (const name in GeneralActions) { - if (GeneralActions.hasOwnProperty(name)) { - actions.push(GeneralActions[name]); - } + const actions: Action[] = []; + for (const name in GeneralActions) { + if (GeneralActions.hasOwnProperty(name)) { + actions.push(GeneralActions[name]); } - return (<> - {actions.map((action: Action) =>
  • - -
  • , - )} - ); -} \ No newline at end of file + } + return ( + <> + {actions.map((action: Action) => ( +
  • + +
  • + ))} + + ); +} diff --git a/src/Bladeburner/ui/GeneralActionPage.tsx b/src/Bladeburner/ui/GeneralActionPage.tsx index b2647a483..f93b664f5 100644 --- a/src/Bladeburner/ui/GeneralActionPage.tsx +++ b/src/Bladeburner/ui/GeneralActionPage.tsx @@ -4,16 +4,21 @@ import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; + bladeburner: IBladeburner; + player: IPlayer; } export function GeneralActionPage(props: IProps): React.ReactElement { - return (<> -

    - These are generic actions that will assist you in your Bladeburner - duties. They will not affect your Bladeburner rank in any way. -

    - - ); -} \ No newline at end of file + return ( + <> +

    + These are generic actions that will assist you in your Bladeburner + duties. They will not affect your Bladeburner rank in any way. +

    + + + ); +} diff --git a/src/Bladeburner/ui/OperationElem.tsx b/src/Bladeburner/ui/OperationElem.tsx index f388cfc8a..692b91a52 100644 --- a/src/Bladeburner/ui/OperationElem.tsx +++ b/src/Bladeburner/ui/OperationElem.tsx @@ -2,8 +2,8 @@ import React, { useState } from "react"; import { ActionTypes } from "../data/ActionTypes"; import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; import { - formatNumber, - convertTimeMsToTimeElapsedString, + formatNumber, + convertTimeMsToTimeElapsedString, } from "../../../utils/StringHelperFunctions"; import { stealthIcon, killIcon } from "../data/Icons"; import { BladeburnerConstants } from "../data/Constants"; @@ -15,121 +15,176 @@ import { SuccessChance } from "./SuccessChance"; import { CopyableText } from "../../ui/React/CopyableText"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; - action: any; + bladeburner: IBladeburner; + player: IPlayer; + action: any; } export function OperationElem(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - const isActive = props.bladeburner.action.type === ActionTypes["Operation"] && props.action.name === props.bladeburner.action.name; - const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner); - const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow,props.bladeburner.actionTimeToComplete); - const maxLevel = (props.action.level >= props.action.maxLevel); - const actionTime = props.action.getActionTime(props.bladeburner); - const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`; + const setRerender = useState(false)[1]; + const isActive = + props.bladeburner.action.type === ActionTypes["Operation"] && + props.action.name === props.bladeburner.action.name; + const estimatedSuccessChance = props.action.getEstSuccessChance( + props.bladeburner, + ); + const computedActionTimeCurrent = Math.min( + props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, + props.bladeburner.actionTimeToComplete, + ); + const maxLevel = props.action.level >= props.action.maxLevel; + const actionTime = props.action.getActionTime(props.bladeburner); + const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`; - function onStart(): void { - props.bladeburner.action.type = ActionTypes.Operation; - props.bladeburner.action.name = props.action.name; - props.bladeburner.startAction(props.player, props.bladeburner.action); - setRerender(old => !old); - } + function onStart(): void { + props.bladeburner.action.type = ActionTypes.Operation; + props.bladeburner.action.name = props.action.name; + props.bladeburner.startAction(props.player, props.bladeburner.action); + setRerender((old) => !old); + } - function onTeam(): void { - const popupId = "bladeburner-operation-set-team-size-popup"; - createPopup(popupId, TeamSizePopup, { - bladeburner: props.bladeburner, - action: props.action, - popupId: popupId, - }); - } + function onTeam(): void { + const popupId = "bladeburner-operation-set-team-size-popup"; + createPopup(popupId, TeamSizePopup, { + bladeburner: props.bladeburner, + action: props.action, + popupId: popupId, + }); + } - function increaseLevel(): void { - ++props.action.level; - if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action); - setRerender(old => !old); - } + function increaseLevel(): void { + ++props.action.level; + if (isActive) + props.bladeburner.startAction(props.player, props.bladeburner.action); + setRerender((old) => !old); + } - function decreaseLevel(): void { - --props.action.level; - if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action); - setRerender(old => !old); - } + function decreaseLevel(): void { + --props.action.level; + if (isActive) + props.bladeburner.startAction(props.player, props.bladeburner.action); + setRerender((old) => !old); + } - function onAutolevel(event: React.ChangeEvent): void { - props.action.autoLevel = event.target.checked; - setRerender(old => !old); - } + function onAutolevel(event: React.ChangeEvent): void { + props.action.autoLevel = event.target.checked; + setRerender((old) => !old); + } - return (<> -

    - {isActive ? - <> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) : - - } -

    - {isActive ? -

    {createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}

    : + return ( + <> +

    + {isActive ? ( + <> + (IN PROGRESS -{" "} + {formatNumber(computedActionTimeCurrent, 0)} /{" "} + {formatNumber(props.bladeburner.actionTimeToComplete, 0)}) + + ) : ( + + )} +

    + {isActive ? ( +

    + {createProgressBarText({ + progress: + computedActionTimeCurrent / + props.bladeburner.actionTimeToComplete, + })} +

    + ) : ( <> - - Start - - - Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)}) - - } + + Start + + + Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)}) + + + )} +
    +
    +
    +        
    +          {props.action.getSuccessesNeededForNextLevel(
    +            BladeburnerConstants.OperationSuccessesPerLevel,
    +          )}{" "}
    +          successes needed for next level
    +        
    +        Level: {props.action.level} / {props.action.maxLevel}
    +      
    + + {isActive && ( + + WARNING: changing the level will restart the Operation + + )} + ↑ + + + {isActive && ( + + WARNING: changing the level will restart the Operation + + )} + ↓ + +
    +
    +
    +        
             

    -
    -            
    -                {props.action.getSuccessesNeededForNextLevel(BladeburnerConstants.OperationSuccessesPerLevel)} successes needed for next level
    -            
    -            Level: {props.action.level} / {props.action.maxLevel}
    -        
    - - {isActive && (WARNING: changing the level will restart the Operation)} - ↑ - - - {isActive && (WARNING: changing the level will restart the Operation)} - ↓ - + Estimated success chance:{" "} + {" "} + {props.action.isStealth ? stealthIcon : <>} + {props.action.isKill ? killIcon : <>}
    + Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
    -
    -
    -

    -Estimated success chance: {props.action.isStealth?stealthIcon:<>}{props.action.isKill?killIcon:<>}
    -Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}
    -Operations remaining: {Math.floor(props.action.count)}
    -Successes: {props.action.successes}
    -Failures: {props.action.failures} -
    + Operations remaining: {Math.floor(props.action.count)}
    - - - ); + Successes: {props.action.successes} +
    + Failures: {props.action.failures} +
    +
    + + + + ); } diff --git a/src/Bladeburner/ui/OperationList.tsx b/src/Bladeburner/ui/OperationList.tsx index 31f24fe5d..58147a077 100644 --- a/src/Bladeburner/ui/OperationList.tsx +++ b/src/Bladeburner/ui/OperationList.tsx @@ -4,17 +4,24 @@ import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; + bladeburner: IBladeburner; + player: IPlayer; } export function OperationList(props: IProps): React.ReactElement { - const names = Object.keys(props.bladeburner.operations); - const operations = props.bladeburner.operations; - return (<> - {names.map((name: string) =>
  • - -
  • , - )} - ); -} \ No newline at end of file + const names = Object.keys(props.bladeburner.operations); + const operations = props.bladeburner.operations; + return ( + <> + {names.map((name: string) => ( +
  • + +
  • + ))} + + ); +} diff --git a/src/Bladeburner/ui/OperationPage.tsx b/src/Bladeburner/ui/OperationPage.tsx index f2560f6c0..9d62ae37d 100644 --- a/src/Bladeburner/ui/OperationPage.tsx +++ b/src/Bladeburner/ui/OperationPage.tsx @@ -4,31 +4,33 @@ import { IBladeburner } from "../IBladeburner"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - bladeburner: IBladeburner; - player: IPlayer; + bladeburner: IBladeburner; + player: IPlayer; } export function OperationPage(props: IProps): React.ReactElement { - return (<> -

    - Carry out operations for the Bladeburner division. - Failing an operation will reduce your Bladeburner rank. It will also - cause you to lose HP, which can lead to hospitalization. In general, - operations are harder and more punishing than contracts, - but are also more rewarding. -
    -
    - Operations can affect the chaos level and Synthoid population of your - current city. The exact effects vary between different Operations. -
    -
    - For operations, you can use a team. You must first recruit team members. - Having a larger team will improves your chances of success. -
    -
    - You can unlock higher-level operations by successfully completing them. - Higher-level operations are more difficult, but grant more rank and experience. -

    - - ); -} \ No newline at end of file + return ( + <> +

    + Carry out operations for the Bladeburner division. Failing an operation + will reduce your Bladeburner rank. It will also cause you to lose HP, + which can lead to hospitalization. In general, operations are harder and + more punishing than contracts, but are also more rewarding. +
    +
    + Operations can affect the chaos level and Synthoid population of your + current city. The exact effects vary between different Operations. +
    +
    + For operations, you can use a team. You must first recruit team members. + Having a larger team will improves your chances of success. +
    +
    + You can unlock higher-level operations by successfully completing them. + Higher-level operations are more difficult, but grant more rank and + experience. +

    + + + ); +} diff --git a/src/Bladeburner/ui/Root.tsx b/src/Bladeburner/ui/Root.tsx index 7bb47d55e..85f816990 100644 --- a/src/Bladeburner/ui/Root.tsx +++ b/src/Bladeburner/ui/Root.tsx @@ -8,21 +8,43 @@ import { IEngine } from "../../IEngine"; import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: IBladeburner; - engine: IEngine; - player: IPlayer; + bladeburner: IBladeburner; + engine: IEngine; + player: IPlayer; } export function Root(props: IProps): React.ReactElement { - return (
    -
    -
    - -
    - + return ( +
    +
    +
    +
    -
    - -
    -
    ); -} \ No newline at end of file + +
    +
    + +
    +
    + ); +} diff --git a/src/Bladeburner/ui/SkillElem.tsx b/src/Bladeburner/ui/SkillElem.tsx index 90e8cf643..85f563881 100644 --- a/src/Bladeburner/ui/SkillElem.tsx +++ b/src/Bladeburner/ui/SkillElem.tsx @@ -4,45 +4,62 @@ import { formatNumber } from "../../../utils/StringHelperFunctions"; import { IBladeburner } from "../IBladeburner"; interface IProps { - skill: any; - bladeburner: IBladeburner; - onUpgrade: () => void; + skill: any; + bladeburner: IBladeburner; + onUpgrade: () => void; } export function SkillElem(props: IProps): React.ReactElement { - const skillName = props.skill.name; - let currentLevel = 0; - if (props.bladeburner.skills[skillName] && !isNaN(props.bladeburner.skills[skillName])) { - currentLevel = props.bladeburner.skills[skillName]; - } - const pointCost = props.skill.calculateCost(currentLevel); + const skillName = props.skill.name; + let currentLevel = 0; + if ( + props.bladeburner.skills[skillName] && + !isNaN(props.bladeburner.skills[skillName]) + ) { + currentLevel = props.bladeburner.skills[skillName]; + } + const pointCost = props.skill.calculateCost(currentLevel); - const canLevel = props.bladeburner.skillPoints >= pointCost; - const maxLvl = props.skill.maxLvl ? currentLevel >= props.skill.maxLvl : false; + const canLevel = props.bladeburner.skillPoints >= pointCost; + const maxLvl = props.skill.maxLvl + ? currentLevel >= props.skill.maxLvl + : false; - function onClick(): void { - if (props.bladeburner.skillPoints < pointCost) return; - props.bladeburner.skillPoints -= pointCost; - props.bladeburner.upgradeSkill(props.skill); - props.onUpgrade(); - } + function onClick(): void { + if (props.bladeburner.skillPoints < pointCost) return; + props.bladeburner.skillPoints -= pointCost; + props.bladeburner.upgradeSkill(props.skill); + props.onUpgrade(); + } - return (<> -

    - -

    - - Level - -
    -
    -

    Level: {currentLevel}

    - {maxLvl ? -

    MAX LEVEL

    : -

    Skill Points required: {formatNumber(pointCost, 0)}

    } -

    - ); -} \ No newline at end of file + return ( + <> +

    + +

    + + Level + +
    +
    +

    Level: {currentLevel}

    + {maxLvl ? ( +

    MAX LEVEL

    + ) : ( +

    + Skill Points required: {formatNumber(pointCost, 0)} +

    + )} +

    + + ); +} diff --git a/src/Bladeburner/ui/SkillList.tsx b/src/Bladeburner/ui/SkillList.tsx index 71c67692d..6e2a21f6b 100644 --- a/src/Bladeburner/ui/SkillList.tsx +++ b/src/Bladeburner/ui/SkillList.tsx @@ -4,15 +4,22 @@ import { Skills } from "../Skills"; import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: IBladeburner; - onUpgrade: () => void; + bladeburner: IBladeburner; + onUpgrade: () => void; } export function SkillList(props: IProps): React.ReactElement { - return (<> - {Object.keys(Skills).map((skill: string) =>

  • - -
  • , - )} - ); -} \ No newline at end of file + return ( + <> + {Object.keys(Skills).map((skill: string) => ( +
  • + +
  • + ))} + + ); +} diff --git a/src/Bladeburner/ui/SkillPage.tsx b/src/Bladeburner/ui/SkillPage.tsx index 7bd43ca51..259e1b3ff 100644 --- a/src/Bladeburner/ui/SkillPage.tsx +++ b/src/Bladeburner/ui/SkillPage.tsx @@ -5,51 +5,110 @@ import { formatNumber } from "../../../utils/StringHelperFunctions"; import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: IBladeburner; + bladeburner: IBladeburner; } - export function SkillPage(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - const mults = props.bladeburner.skillMultipliers; + const setRerender = useState(false)[1]; + const mults = props.bladeburner.skillMultipliers; - function valid(mult: any): boolean { - return mult && mult !== 1 - } + function valid(mult: any): boolean { + return mult && mult !== 1; + } - return (<> -

    - Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)} -

    -

    - You will gain one skill point every {BladeburnerConstants.RanksPerSkillPoint} ranks. -
    -
    - Note that when upgrading a skill, the benefit for that skill is additive. - However, the effects of different skills with each other is multiplicative. -
    -

    + return ( + <> +

    + + Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)} + +

    +

    + You will gain one skill point every{" "} + {BladeburnerConstants.RanksPerSkillPoint} ranks.
    - {valid(mults["successChanceAll"]) &&

    Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)}

    } - {valid(mults["successChanceStealth"]) &&

    Stealth Success Chance: x{formatNumber(mults["successChanceStealth"], 3)}

    } - {valid(mults["successChanceKill"]) &&

    Retirement Success Chance: x{formatNumber(mults["successChanceKill"], 3)}

    } - {valid(mults["successChanceContract"]) &&

    Contract Success Chance: x{formatNumber(mults["successChanceContract"], 3)}

    } - {valid(mults["successChanceOperation"]) &&

    Operation Success Chance: x{formatNumber(mults["successChanceOperation"], 3)}

    } - {valid(mults["successChanceEstimate"]) &&

    Synthoid Data Estimate: x{formatNumber(mults["successChanceEstimate"], 3)}

    } - {valid(mults["actionTime"]) &&

    Action Time: x{formatNumber(mults["actionTime"], 3)}

    } - {valid(mults["effHack"]) &&

    Hacking Skill: x{formatNumber(mults["effHack"], 3)}

    } - {valid(mults["effStr"]) &&

    Strength: x{formatNumber(mults["effStr"], 3)}

    } - {valid(mults["effDef"]) &&

    Defense: x{formatNumber(mults["effDef"], 3)}

    } - {valid(mults["effDex"]) &&

    Dexterity: x{formatNumber(mults["effDex"], 3)}

    } - {valid(mults["effAgi"]) &&

    Agility: x{formatNumber(mults["effAgi"], 3)}

    } - {valid(mults["effCha"]) &&

    Charisma: x{formatNumber(mults["effCha"], 3)}

    } - {valid(mults["effInt"]) &&

    Intelligence: x{formatNumber(mults["effInt"], 3)}

    } - {valid(mults["stamina"]) &&

    Stamina: x{formatNumber(mults["stamina"], 3)}

    } - {valid(mults["money"]) &&

    Contract Money: x{formatNumber(mults["money"], 3)}

    } - {valid(mults["expGain"]) &&

    Exp Gain: x{formatNumber(mults["expGain"], 3)}

    }
    - setRerender(old => !old)} /> - ); + Note that when upgrading a skill, the benefit for that skill is + additive. However, the effects of different skills with each other is + multiplicative. +
    +

    +
    + {valid(mults["successChanceAll"]) && ( +

    + Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)} +

    + )} + {valid(mults["successChanceStealth"]) && ( +

    + Stealth Success Chance: x + {formatNumber(mults["successChanceStealth"], 3)} +

    + )} + {valid(mults["successChanceKill"]) && ( +

    + Retirement Success Chance: x + {formatNumber(mults["successChanceKill"], 3)} +

    + )} + {valid(mults["successChanceContract"]) && ( +

    + Contract Success Chance: x + {formatNumber(mults["successChanceContract"], 3)} +

    + )} + {valid(mults["successChanceOperation"]) && ( +

    + Operation Success Chance: x + {formatNumber(mults["successChanceOperation"], 3)} +

    + )} + {valid(mults["successChanceEstimate"]) && ( +

    + Synthoid Data Estimate: x + {formatNumber(mults["successChanceEstimate"], 3)} +

    + )} + {valid(mults["actionTime"]) && ( +

    Action Time: x{formatNumber(mults["actionTime"], 3)}

    + )} + {valid(mults["effHack"]) && ( +

    Hacking Skill: x{formatNumber(mults["effHack"], 3)}

    + )} + {valid(mults["effStr"]) && ( +

    Strength: x{formatNumber(mults["effStr"], 3)}

    + )} + {valid(mults["effDef"]) && ( +

    Defense: x{formatNumber(mults["effDef"], 3)}

    + )} + {valid(mults["effDex"]) && ( +

    Dexterity: x{formatNumber(mults["effDex"], 3)}

    + )} + {valid(mults["effAgi"]) && ( +

    Agility: x{formatNumber(mults["effAgi"], 3)}

    + )} + {valid(mults["effCha"]) && ( +

    Charisma: x{formatNumber(mults["effCha"], 3)}

    + )} + {valid(mults["effInt"]) && ( +

    Intelligence: x{formatNumber(mults["effInt"], 3)}

    + )} + {valid(mults["stamina"]) && ( +

    Stamina: x{formatNumber(mults["stamina"], 3)}

    + )} + {valid(mults["money"]) && ( +

    Contract Money: x{formatNumber(mults["money"], 3)}

    + )} + {valid(mults["expGain"]) && ( +

    Exp Gain: x{formatNumber(mults["expGain"], 3)}

    + )} +
    + setRerender((old) => !old)} + /> + + ); } /* @@ -67,4 +126,4 @@ for (var i = 0; i < multKeys.length; ++i) { } } } -*/ \ No newline at end of file +*/ diff --git a/src/Bladeburner/ui/Stats.tsx b/src/Bladeburner/ui/Stats.tsx index 103f6240b..bbda4238a 100644 --- a/src/Bladeburner/ui/Stats.tsx +++ b/src/Bladeburner/ui/Stats.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import { - formatNumber, - convertTimeMsToTimeElapsedString, + formatNumber, + convertTimeMsToTimeElapsedString, } from "../../../utils/StringHelperFunctions"; import { BladeburnerConstants } from "../data/Constants"; import { IPlayer } from "../../PersonObjects/IPlayer"; @@ -13,127 +13,214 @@ import { dialogBoxCreate } from "../../../utils/DialogBox"; import { createPopup } from "../../ui/React/createPopup"; import { Factions } from "../../Faction/Factions"; import { - joinFaction, - displayFactionContent, + joinFaction, + displayFactionContent, } from "../../Faction/FactionHelpers"; -import { IBladeburner } from "../IBladeburner" +import { IBladeburner } from "../IBladeburner"; import { TravelPopup } from "./TravelPopup"; interface IProps { - bladeburner: IBladeburner; - engine: IEngine; - player: IPlayer; + bladeburner: IBladeburner; + engine: IEngine; + player: IPlayer; } export function Stats(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; + const setRerender = useState(false)[1]; - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); + useEffect(() => { + const id = setInterval(() => setRerender((old) => !old), 1000); + return () => clearInterval(id); + }, []); - function openStaminaHelp(): void { - dialogBoxCreate("Performing actions will use up your stamina.

    " + - "Your max stamina is determined primarily by your agility stat.

    " + - "Your stamina gain rate is determined by both your agility and your " + - "max stamina. Higher max stamina leads to a higher gain rate.

    " + - "Once your " + - "stamina falls below 50% of its max value, it begins to negatively " + - "affect the success rate of your contracts/operations. This penalty " + - "is shown in the overview panel. If the penalty is 15%, then this means " + - "your success rate would be multipled by 85% (100 - 15).

    " + - "Your max stamina and stamina gain rate can also be increased by " + - "training, or through skills and Augmentation upgrades."); + function openStaminaHelp(): void { + dialogBoxCreate( + "Performing actions will use up your stamina.

    " + + "Your max stamina is determined primarily by your agility stat.

    " + + "Your stamina gain rate is determined by both your agility and your " + + "max stamina. Higher max stamina leads to a higher gain rate.

    " + + "Once your " + + "stamina falls below 50% of its max value, it begins to negatively " + + "affect the success rate of your contracts/operations. This penalty " + + "is shown in the overview panel. If the penalty is 15%, then this means " + + "your success rate would be multipled by 85% (100 - 15).

    " + + "Your max stamina and stamina gain rate can also be increased by " + + "training, or through skills and Augmentation upgrades.", + ); + } + + function openPopulationHelp(): void { + dialogBoxCreate( + "The success rate of your contracts/operations depends on " + + "the population of Synthoids in your current city. " + + "The success rate that is shown to you is only an estimate, " + + "and it is based on your Synthoid population estimate.

    " + + "Therefore, it is important that this Synthoid population estimate " + + "is accurate so that you have a better idea of your " + + "success rate for contracts/operations. Certain " + + "actions will increase the accuracy of your population " + + "estimate.

    " + + "The Synthoid populations of cities can change due to your " + + "actions or random events. If random events occur, they will " + + "be logged in the Bladeburner Console.", + ); + } + + function openTravel(): void { + const popupId = "bladeburner-travel-popup"; + createPopup(popupId, TravelPopup, { + bladeburner: props.bladeburner, + popupId: popupId, + }); + } + + function openFaction(): void { + const faction = Factions["Bladeburners"]; + if (faction.isMember) { + props.engine.loadFactionContent(); + displayFactionContent("Bladeburners"); + } else { + if (props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) { + joinFaction(faction); + dialogBoxCreate( + "Congratulations! You were accepted into the Bladeburners faction", + ); + } else { + dialogBoxCreate( + "You need a rank of 25 to join the Bladeburners Faction!", + ); + } } + } - function openPopulationHelp(): void { - dialogBoxCreate("The success rate of your contracts/operations depends on " + - "the population of Synthoids in your current city. " + - "The success rate that is shown to you is only an estimate, " + - "and it is based on your Synthoid population estimate.

    " + - "Therefore, it is important that this Synthoid population estimate " + - "is accurate so that you have a better idea of your " + - "success rate for contracts/operations. Certain " + - "actions will increase the accuracy of your population " + - "estimate.

    " + - "The Synthoid populations of cities can change due to your " + - "actions or random events. If random events occur, they will " + - "be logged in the Bladeburner Console."); - } - - function openTravel(): void { - const popupId = "bladeburner-travel-popup"; - createPopup(popupId, TravelPopup, { - bladeburner: props.bladeburner, - popupId: popupId, - }); - } - - function openFaction(): void { - const faction = Factions["Bladeburners"]; - if (faction.isMember) { - props.engine.loadFactionContent(); - displayFactionContent("Bladeburners"); - } else { - if (props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) { - joinFaction(faction); - dialogBoxCreate("Congratulations! You were accepted into the Bladeburners faction"); - } else { - dialogBoxCreate("You need a rank of 25 to join the Bladeburners Faction!") - } - } - } - - return (<> -

    - Rank: {formatNumber(props.bladeburner.rank, 2)} - Your rank within the Bladeburner division. -


    -

    Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)}

    -
    ?

    -

    Stamina Penalty: {formatNumber((1-props.bladeburner.calculateStaminaPenalty())*100, 1)}%


    -

    Team Size: {formatNumber(props.bladeburner.teamSize, 0)}

    -

    Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}


    -

    Num Times Hospitalized: {props.bladeburner.numHosp}

    -

    Money Lost From Hospitalizations:


    -

    Current City: {props.bladeburner.city}

    -

    - Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)} - This is your Bladeburner division's estimate of how many Synthoids exist in your current city. -

    -
    ?

    -

    - Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)} - This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city. -


    -

    - City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)} - The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a chaos level can make contracts and operations harder. -


    + return ( + <> +

    + Rank: {formatNumber(props.bladeburner.rank, 2)} + + Your rank within the Bladeburner division. + +

    +
    +

    + Stamina: {formatNumber(props.bladeburner.stamina, 3)} /{" "} + {formatNumber(props.bladeburner.maxStamina, 3)} +

    +
    + ? +
    +
    +

    + Stamina Penalty:{" "} + {formatNumber( + (1 - props.bladeburner.calculateStaminaPenalty()) * 100, + 1, + )} + % +

    +
    +

    Team Size: {formatNumber(props.bladeburner.teamSize, 0)}

    +

    Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}

    +
    +

    Num Times Hospitalized: {props.bladeburner.numHosp}

    +

    + Money Lost From Hospitalizations:{" "} + +

    +
    +

    Current City: {props.bladeburner.city}

    +

    + Est. Synthoid Population:{" "} + {numeralWrapper.formatPopulation( + props.bladeburner.getCurrentCity().popEst, + )} + + This is your Bladeburner division's estimate of how many Synthoids + exist in your current city. + +

    +
    + ? +
    +
    +

    + Est. Synthoid Communities:{" "} + {formatNumber(props.bladeburner.getCurrentCity().comms, 0)} + + This is your Bladeburner divison's estimate of how many Synthoid + communities exist in your current city. + +

    +
    +

    + City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)} + + The city's chaos level due to tensions and conflicts between humans + and Synthoids. Having too high of a chaos level can make contracts and + operations harder. + +

    +
    +
    +

    + Bonus time:{" "} + {convertTimeMsToTimeElapsedString( + (props.bladeburner.storedCycles / + BladeburnerConstants.CyclesPerSecond) * + 1000, + )}
    -

    - Bonus time: {convertTimeMsToTimeElapsedString(props.bladeburner.storedCycles/BladeburnerConstants.CyclesPerSecond*1000)}
    - - You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by browser). - Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed. - -

    -

    Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}


    - {StatsTable([ - ["Aug. Success Chance mult: ", formatNumber(props.player.bladeburner_success_chance_mult*100, 1) + "%"], - ["Aug. Max Stamina mult: ", formatNumber(props.player.bladeburner_max_stamina_mult*100, 1) + "%"], - ["Aug. Stamina Gain mult: ", formatNumber(props.player.bladeburner_stamina_gain_mult*100, 1) + "%"], - ["Aug. Field Analysis mult: ", formatNumber(props.player.bladeburner_analysis_mult*100, 1) + "%"], - ])} -
    - Travel - - Apply to the Bladeburner Faction, or go to the faction page if you are already a member - Faction - -
    -
    - ); + + You gain bonus time while offline or when the game is inactive (e.g. + when the tab is throttled by browser). Bonus time makes the + Bladeburner mechanic progress faster, up to 5x the normal speed. + +

    +

    Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}

    +
    + {StatsTable([ + [ + "Aug. Success Chance mult: ", + formatNumber(props.player.bladeburner_success_chance_mult * 100, 1) + + "%", + ], + [ + "Aug. Max Stamina mult: ", + formatNumber(props.player.bladeburner_max_stamina_mult * 100, 1) + + "%", + ], + [ + "Aug. Stamina Gain mult: ", + formatNumber(props.player.bladeburner_stamina_gain_mult * 100, 1) + + "%", + ], + [ + "Aug. Field Analysis mult: ", + formatNumber(props.player.bladeburner_analysis_mult * 100, 1) + "%", + ], + ])} +
    + + Travel + + + + Apply to the Bladeburner Faction, or go to the faction page if you are + already a member + + Faction + +
    +
    + + ); } diff --git a/src/Bladeburner/ui/SuccessChance.tsx b/src/Bladeburner/ui/SuccessChance.tsx index fbeaf33c7..510c729f3 100644 --- a/src/Bladeburner/ui/SuccessChance.tsx +++ b/src/Bladeburner/ui/SuccessChance.tsx @@ -2,13 +2,18 @@ import React from "react"; import { formatNumber } from "../../../utils/StringHelperFunctions"; interface IProps { - chance: number[]; + chance: number[]; } export function SuccessChance(props: IProps): React.ReactElement { - if(props.chance[0] === props.chance[1]) { - return (<>{formatNumber(props.chance[0]*100, 1)}%); - } + if (props.chance[0] === props.chance[1]) { + return <>{formatNumber(props.chance[0] * 100, 1)}%; + } - return (<>{formatNumber(props.chance[0]*100, 1)}% ~ {formatNumber(props.chance[1]*100, 1)}%); + return ( + <> + {formatNumber(props.chance[0] * 100, 1)}% ~{" "} + {formatNumber(props.chance[1] * 100, 1)}% + + ); } diff --git a/src/Bladeburner/ui/TeamSizePopup.tsx b/src/Bladeburner/ui/TeamSizePopup.tsx index 3169407bd..d03e4753f 100644 --- a/src/Bladeburner/ui/TeamSizePopup.tsx +++ b/src/Bladeburner/ui/TeamSizePopup.tsx @@ -5,33 +5,45 @@ import { Action } from "../Action"; import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: IBladeburner; - action: Action; - popupId: string; + bladeburner: IBladeburner; + action: Action; + popupId: string; } export function TeamSizePopup(props: IProps): React.ReactElement { - const [teamSize, setTeamSize] = useState(); - - function confirmTeamSize(): void { - if(teamSize === undefined) return; - const num = Math.round(teamSize); - if (isNaN(num) || num < 0) { - dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric, positive)") - } else { - props.action.teamCount = num; - } - removePopup(props.popupId); - } + const [teamSize, setTeamSize] = useState(); - return (<> -

    - Enter the amount of team members you would like to take on this - Op. If you do not have the specified number of team members, - then as many as possible will be used. Note that team members may - be lost during operations. -

    - setTeamSize(parseFloat(event.target.value))} /> - Confirm - ); -} \ No newline at end of file + function confirmTeamSize(): void { + if (teamSize === undefined) return; + const num = Math.round(teamSize); + if (isNaN(num) || num < 0) { + dialogBoxCreate( + "Invalid value entered for number of Team Members (must be numeric, positive)", + ); + } else { + props.action.teamCount = num; + } + removePopup(props.popupId); + } + + return ( + <> +

    + Enter the amount of team members you would like to take on this Op. If + you do not have the specified number of team members, then as many as + possible will be used. Note that team members may be lost during + operations. +

    + setTeamSize(parseFloat(event.target.value))} + /> + + Confirm + + + ); +} diff --git a/src/Bladeburner/ui/TravelPopup.tsx b/src/Bladeburner/ui/TravelPopup.tsx index 62a9dc341..9828e15b1 100644 --- a/src/Bladeburner/ui/TravelPopup.tsx +++ b/src/Bladeburner/ui/TravelPopup.tsx @@ -4,29 +4,36 @@ import { BladeburnerConstants } from "../data/Constants"; import { IBladeburner } from "../IBladeburner"; interface IProps { - bladeburner: IBladeburner; - popupId: string; + bladeburner: IBladeburner; + popupId: string; } export function TravelPopup(props: IProps): React.ReactElement { - function travel(city: string): void { - props.bladeburner.city = city; - removePopup(props.popupId); - } + function travel(city: string): void { + props.bladeburner.city = city; + removePopup(props.popupId); + } - return (<> -

    - Travel to a different city for your Bladeburner - activities. This does not cost any money. The city you are - in for your Bladeburner duties does not affect - your location in the game otherwise. -

    - {BladeburnerConstants.CityNames.map(city => { - // Reusing this css class...it adds a border and makes it - // so that background color changes when you hover - return
    travel(city)}> - {city} -
    })} - ); -} \ No newline at end of file + return ( + <> +

    + Travel to a different city for your Bladeburner activities. This does + not cost any money. The city you are in for your Bladeburner duties does + not affect your location in the game otherwise. +

    + {BladeburnerConstants.CityNames.map((city) => { + // Reusing this css class...it adds a border and makes it + // so that background color changes when you hover + return ( +
    travel(city)} + > + {city} +
    + ); + })} + + ); +} diff --git a/src/Casino/Blackjack.tsx b/src/Casino/Blackjack.tsx index 8a41b1f1c..c9792769c 100644 --- a/src/Casino/Blackjack.tsx +++ b/src/Casino/Blackjack.tsx @@ -14,408 +14,428 @@ import { MuiPaper } from "../ui/React/MuiPaper"; const MAX_BET = 100e6; enum Result { - Pending = "", - PlayerWon = "You won!", - PlayerWonByBlackjack = "You Won! Blackjack!", - DealerWon = "You lost!", - Tie = "Push! (Tie)", + Pending = "", + PlayerWon = "You won!", + PlayerWonByBlackjack = "You Won! Blackjack!", + DealerWon = "You lost!", + Tie = "Push! (Tie)", } type Props = { - p: IPlayer; -} + p: IPlayer; +}; -type State ={ - playerHand: Hand; - dealerHand: Hand; - bet: number; - betInput: string; - gameInProgress: boolean; - result: Result; - gains: number; // Track gains only for this session - wagerInvalid: boolean; - wagerInvalidHelperText: string; -} +type State = { + playerHand: Hand; + dealerHand: Hand; + bet: number; + betInput: string; + gameInProgress: boolean; + result: Result; + gains: number; // Track gains only for this session + wagerInvalid: boolean; + wagerInvalidHelperText: string; +}; export class Blackjack extends Game { + deck: Deck; - deck: Deck; + constructor(props: Props) { + super(props); - constructor(props: Props) { - super(props); + this.deck = new Deck(5); // 5-deck multideck - this.deck = new Deck(5); // 5-deck multideck + const initialBet = 1e6; - const initialBet = 1e6; + this.state = { + playerHand: new Hand([]), + dealerHand: new Hand([]), + bet: initialBet, + betInput: String(initialBet), + gameInProgress: false, + result: Result.Pending, + gains: 0, + wagerInvalid: false, + wagerInvalidHelperText: "", + }; + } - this.state = { - playerHand: new Hand([]), - dealerHand: new Hand([]), - bet: initialBet, - betInput: String(initialBet), - gameInProgress: false, - result: Result.Pending, - gains: 0, - wagerInvalid: false, - wagerInvalidHelperText: "", - } + canStartGame = (): boolean => { + const { p } = this.props; + const { bet } = this.state; + + return p.canAfford(bet); + }; + + startGame = (): void => { + if (!this.canStartGame()) { + return; } - canStartGame = (): boolean => { - const { p } = this.props; - const { bet } = this.state; + // Take money from player right away so that player's dont just "leave" to avoid the loss (I mean they could + // always reload without saving but w.e) + this.props.p.loseMoney(this.state.bet); - return p.canAfford(bet); + const playerHand = new Hand([ + this.deck.safeDrawCard(), + this.deck.safeDrawCard(), + ]); + const dealerHand = new Hand([ + this.deck.safeDrawCard(), + this.deck.safeDrawCard(), + ]); + + this.setState({ + playerHand, + dealerHand, + gameInProgress: true, + result: Result.Pending, + }); + + // If the player is dealt a blackjack and the dealer is not, then the player + // immediately wins + if (this.getTrueHandValue(playerHand) === 21) { + if (this.getTrueHandValue(dealerHand) === 21) { + this.finishGame(Result.Tie); + } else { + this.finishGame(Result.PlayerWonByBlackjack); + } + } else if (this.getTrueHandValue(dealerHand) === 21) { + // Check if dealer won by blackjack. We know at this point that the player does not also have blackjack. + this.finishGame(Result.DealerWon); + } + }; + + // Returns an array of numbers representing all possible values of the given Hand. The reason it needs to be + // an array is because an Ace can count as both 1 and 11. + getHandValue = (hand: Hand): number[] => { + let result: number[] = [0]; + + for (let i = 0; i < hand.cards.length; ++i) { + const value = hand.cards[i].value; + if (value >= 10) { + result = result.map((x) => x + 10); + } else if (value === 1) { + result = result.flatMap((x) => [x + 1, x + 11]); + } else { + result = result.map((x) => x + value); + } } - startGame = (): void => { - if (!this.canStartGame()) { return; } + return result; + }; - // Take money from player right away so that player's dont just "leave" to avoid the loss (I mean they could - // always reload without saving but w.e) - this.props.p.loseMoney(this.state.bet); + // Returns the single hand value used for determine things like victory and whether or not + // the dealer has to hit. Essentially this uses the biggest value that's 21 or under. If no such value exists, + // then it means the hand is busted and we can just return whatever + getTrueHandValue = (hand: Hand): number => { + const handValues = this.getHandValue(hand); + const valuesUnder21 = handValues.filter((x) => x <= 21); - const playerHand = new Hand([ this.deck.safeDrawCard(), this.deck.safeDrawCard() ]); - const dealerHand = new Hand([ this.deck.safeDrawCard(), this.deck.safeDrawCard() ]); + if (valuesUnder21.length > 0) { + valuesUnder21.sort((a, b) => a - b); + return valuesUnder21[valuesUnder21.length - 1]; + } else { + // Just return the first value. It doesnt really matter anyways since hand is buted + return handValues[0]; + } + }; - this.setState({ - playerHand, - dealerHand, - gameInProgress: true, - result: Result.Pending, - }); + // Returns all hand values that are 21 or under. If no values are 21 or under, then the first value is returned. + getHandDisplayValues = (hand: Hand): number[] => { + const handValues = this.getHandValue(hand); + if (this.isHandBusted(hand)) { + // Hand is busted so just return the 1st value, doesn't really matter + return [...new Set([handValues[0]])]; + } else { + return [...new Set(handValues.filter((x) => x <= 21))]; + } + }; - // If the player is dealt a blackjack and the dealer is not, then the player - // immediately wins - if (this.getTrueHandValue(playerHand) === 21) { - if (this.getTrueHandValue(dealerHand) === 21) { - this.finishGame(Result.Tie); - } else { - this.finishGame(Result.PlayerWonByBlackjack); + isHandBusted = (hand: Hand): boolean => { + return this.getTrueHandValue(hand) > 21; + }; + + playerHit = (event: React.MouseEvent): void => { + if (!event.isTrusted) { + return; + } + + const newHand = this.state.playerHand.addCards(this.deck.safeDrawCard()); + + this.setState({ + playerHand: newHand, + }); + + // Check if player busted, and finish the game if so + if (this.isHandBusted(newHand)) { + this.finishGame(Result.DealerWon); + } + }; + + playerStay = (event: React.MouseEvent): void => { + if (!event.isTrusted) { + return; + } + + // Determine if Dealer needs to hit. A dealer must hit if they have 16 or lower. + // If the dealer has a Soft 17 (Ace + 6), then they stay. + let newDealerHand = this.state.dealerHand; + while (true) { + // The dealer's "true" hand value is the 2nd one if its 21 or less (the 2nd value is always guaranteed + // to be equal or larger). Otherwise its the 1st. + const dealerHandValue = this.getTrueHandValue(newDealerHand); + + if (dealerHandValue <= 16) { + newDealerHand = newDealerHand.addCards(this.deck.safeDrawCard()); + } else { + break; + } + } + + this.setState({ + dealerHand: newDealerHand, + }); + + // If dealer has busted, then player wins + if (this.isHandBusted(newDealerHand)) { + this.finishGame(Result.PlayerWon); + } else { + const dealerHandValue = this.getTrueHandValue(newDealerHand); + const playerHandValue = this.getTrueHandValue(this.state.playerHand); + + // We expect nobody to have busted. If someone busted, there is an error + // in our game logic + if (dealerHandValue > 21 || playerHandValue > 21) { + throw new Error("Someone busted when not expected to"); + } + + if (playerHandValue > dealerHandValue) { + this.finishGame(Result.PlayerWon); + } else if (playerHandValue < dealerHandValue) { + this.finishGame(Result.DealerWon); + } else { + this.finishGame(Result.Tie); + } + } + }; + + finishGame = (result: Result): void => { + let gains = 0; + if (this.isPlayerWinResult(result)) { + gains = this.state.bet; + + // We 2x the gains because we took away money at the start, so we need to give the original bet back. + this.win(this.props.p, 2 * gains); + } else if (result === Result.DealerWon) { + gains = -1 * this.state.bet; + // Dont need to take money here since we already did it at the start + } else if (result === Result.Tie) { + this.win(this.props.p, this.state.bet); // Get the original bet back + } + + this.setState({ + gameInProgress: false, + result, + gains: this.state.gains + gains, + }); + }; + + isPlayerWinResult = (result: Result): boolean => { + return ( + result === Result.PlayerWon || result === Result.PlayerWonByBlackjack + ); + }; + + wagerOnChange = (event: React.ChangeEvent): void => { + const { p } = this.props; + const betInput = event.target.value; + const wager = Math.round(parseFloat(betInput)); + if (isNaN(wager)) { + this.setState({ + bet: 0, + betInput, + wagerInvalid: true, + wagerInvalidHelperText: "Not a valid number", + }); + } else if (wager <= 0) { + this.setState({ + bet: 0, + betInput, + wagerInvalid: true, + wagerInvalidHelperText: "Must bet a postive amount", + }); + } else if (wager > MAX_BET) { + this.setState({ + bet: 0, + betInput, + wagerInvalid: true, + wagerInvalidHelperText: "Exceeds max bet", + }); + } else if (!p.canAfford(wager)) { + this.setState({ + bet: 0, + betInput, + wagerInvalid: true, + wagerInvalidHelperText: "Not enough money", + }); + } else { + // Valid wager + this.setState({ + bet: wager, + betInput, + wagerInvalid: false, + wagerInvalidHelperText: "", + result: Result.Pending, // Reset previous game status to clear the win/lose text UI + }); + } + }; + + // Start game button + startOnClick = (event: React.MouseEvent): void => { + // Protect against scripting...although maybe this would be fun to automate + if (!event.isTrusted) { + return; + } + + if (!this.state.wagerInvalid) { + this.startGame(); + } + }; + + render(): React.ReactNode { + const { + betInput, + playerHand, + dealerHand, + gameInProgress, + result, + wagerInvalid, + wagerInvalidHelperText, + gains, + } = this.state; + + // Get the player totals to display. + const playerHandValues = this.getHandDisplayValues(playerHand); + const dealerHandValues = this.getHandDisplayValues(dealerHand); + + return ( +
    + {/* Wager input */} +
    + + {"Wager (Max: "} + + {")"} + } - } else if (this.getTrueHandValue(dealerHand) === 21) { - // Check if dealer won by blackjack. We know at this point that the player does not also have blackjack. - this.finishGame(Result.DealerWon); - } - } + disabled={gameInProgress} + onChange={this.wagerOnChange} + error={wagerInvalid} + helperText={wagerInvalid ? wagerInvalidHelperText : ""} + type="number" + variant="filled" + style={{ + width: "200px", + }} + InputProps={{ + startAdornment: ( + $ + ), + }} + /> - // Returns an array of numbers representing all possible values of the given Hand. The reason it needs to be - // an array is because an Ace can count as both 1 and 11. - getHandValue = (hand: Hand): number[] => { - let result: number[] = [ 0 ]; +

    + {"Total earnings this session: "} + +

    +
    - for (let i = 0 ; i < hand.cards.length; ++i) { - const value = hand.cards[i].value; - if (value >= 10) { - result = result.map((x) => x + 10); - } else if (value === 1) { - result = result.flatMap((x) => [ x + 1, x + 11 ]); - } else { - result = result.map((x) => x + value); - } - } + {/* Buttons */} + {!gameInProgress ? ( +
    + + Start + +
    + ) : ( +
    + + Hit + + + Stay + +
    + )} - return result; - } + {/* Main game part. Displays both if the game is in progress OR if there's a result so you can see + * the cards that led to that result. */} + {(gameInProgress || result !== Result.Pending) && ( +
    + +
    Player
    + {playerHand.cards.map((card, i) => ( + + ))} - // Returns the single hand value used for determine things like victory and whether or not - // the dealer has to hit. Essentially this uses the biggest value that's 21 or under. If no such value exists, - // then it means the hand is busted and we can just return whatever - getTrueHandValue = (hand: Hand): number => { - const handValues = this.getHandValue(hand); - const valuesUnder21 = handValues.filter((x) => (x <= 21)); - - if (valuesUnder21.length > 0) { - valuesUnder21.sort((a, b) => a - b); - return valuesUnder21[valuesUnder21.length - 1]; - } else { - // Just return the first value. It doesnt really matter anyways since hand is buted - return handValues[0]; - } - } +
    Value(s): 
    + {playerHandValues.map((value, i) => ( +
    {value}
    + ))} +
    - // Returns all hand values that are 21 or under. If no values are 21 or under, then the first value is returned. - getHandDisplayValues = (hand: Hand): number[] => { - const handValues = this.getHandValue(hand); - if (this.isHandBusted(hand)) { - // Hand is busted so just return the 1st value, doesn't really matter - return [ ...new Set([ handValues[0] ]) ]; - } else { - return [ ...new Set(handValues.filter((x) => x <= 21)) ]; - } - } +
    - isHandBusted = (hand: Hand): boolean => { - return this.getTrueHandValue(hand) > 21; - } + +
    Dealer
    + {dealerHand.cards.map((card, i) => ( + // Hide every card except the first while game is in progress +
    +
    + )} - const newHand = this.state.playerHand.addCards(this.deck.safeDrawCard()); - - this.setState({ - playerHand: newHand, - }); - - // Check if player busted, and finish the game if so - if (this.isHandBusted(newHand)) { - this.finishGame(Result.DealerWon); - } - } - - playerStay = (event: React.MouseEvent): void => { - if (!event.isTrusted) { return; } - - // Determine if Dealer needs to hit. A dealer must hit if they have 16 or lower. - // If the dealer has a Soft 17 (Ace + 6), then they stay. - let newDealerHand = this.state.dealerHand; - while (true) { - // The dealer's "true" hand value is the 2nd one if its 21 or less (the 2nd value is always guaranteed - // to be equal or larger). Otherwise its the 1st. - const dealerHandValue = this.getTrueHandValue(newDealerHand); - - if (dealerHandValue <= 16) { - newDealerHand = newDealerHand.addCards(this.deck.safeDrawCard()); - } else { - break; - } - } - - this.setState({ - dealerHand: newDealerHand, - }) - - // If dealer has busted, then player wins - if (this.isHandBusted(newDealerHand)) { - this.finishGame(Result.PlayerWon); - } else { - const dealerHandValue = this.getTrueHandValue(newDealerHand); - const playerHandValue = this.getTrueHandValue(this.state.playerHand); - - // We expect nobody to have busted. If someone busted, there is an error - // in our game logic - if (dealerHandValue > 21 || playerHandValue > 21) { - throw new Error("Someone busted when not expected to"); - } - - if (playerHandValue > dealerHandValue) { - this.finishGame(Result.PlayerWon); - } else if (playerHandValue < dealerHandValue) { - this.finishGame(Result.DealerWon); - } else { - this.finishGame(Result.Tie); - } - - } - } - - finishGame = (result: Result): void => { - let gains = 0; - if (this.isPlayerWinResult(result)) { - gains = this.state.bet; - - // We 2x the gains because we took away money at the start, so we need to give the original bet back. - this.win(this.props.p, 2 * gains); - } else if (result === Result.DealerWon) { - gains = -1 * this.state.bet; - // Dont need to take money here since we already did it at the start - } else if (result === Result.Tie) { - this.win(this.props.p, this.state.bet); // Get the original bet back - } - - this.setState({ - gameInProgress: false, - result, - gains: this.state.gains + gains, - }); - } - - isPlayerWinResult = (result: Result): boolean => { - return (result === Result.PlayerWon || result === Result.PlayerWonByBlackjack); - } - - wagerOnChange = (event: React.ChangeEvent): void => { - const { p } = this.props; - const betInput = event.target.value; - const wager = Math.round(parseFloat(betInput)); - if (isNaN(wager)) { - this.setState({ - bet: 0, - betInput, - wagerInvalid: true, - wagerInvalidHelperText: "Not a valid number", - }); - } else if (wager <= 0) { - this.setState({ - bet: 0, - betInput, - wagerInvalid: true, - wagerInvalidHelperText: "Must bet a postive amount", - }); - } else if (wager > MAX_BET) { - this.setState({ - bet: 0, - betInput, - wagerInvalid: true, - wagerInvalidHelperText: "Exceeds max bet", - }); - } else if (!p.canAfford(wager)) { - this.setState({ - bet: 0, - betInput, - wagerInvalid: true, - wagerInvalidHelperText: "Not enough money", - }); - } else { - // Valid wager - this.setState({ - bet: wager, - betInput, - wagerInvalid: false, - wagerInvalidHelperText: "", - result: Result.Pending, // Reset previous game status to clear the win/lose text UI - }); - } - } - - // Start game button - startOnClick = (event: React.MouseEvent): void => { - // Protect against scripting...although maybe this would be fun to automate - if (!event.isTrusted) { return; } - - if (!this.state.wagerInvalid) { - this.startGame(); - } - } - - render(): React.ReactNode { - const { - betInput, - playerHand, - dealerHand, - gameInProgress, - result, - wagerInvalid, - wagerInvalidHelperText, - gains, - } = this.state; - - // Get the player totals to display. - const playerHandValues = this.getHandDisplayValues(playerHand); - const dealerHandValues = this.getHandDisplayValues(dealerHand); - - return ( -
    - {/* Wager input */} -
    - - {"Wager (Max: "} - - {")"} - - } - disabled={gameInProgress} - onChange={this.wagerOnChange} - error={wagerInvalid} - helperText={wagerInvalid ? wagerInvalidHelperText : ""} - type="number" - variant="filled" - style={{ - width: "200px", - }} - InputProps={{ - startAdornment: $, - }} /> - -

    - {"Total earnings this session: "} - -

    -
    - - - {/* Buttons */} - {!gameInProgress ? ( -
    - - - Start - -
    - ) : ( -
    - - Hit - - - Stay - -
    - )} - - {/* Main game part. Displays both if the game is in progress OR if there's a result so you can see - * the cards that led to that result. */} - {(gameInProgress || result !== Result.Pending) && ( -
    - -
    Player
    - {playerHand.cards.map((card, i) => ( - - ))} - -
    Value(s): 
    - {playerHandValues.map((value, i) => ( -
    {value}
    - ))} -
    - -
    - - -
    Dealer
    - {dealerHand.cards.map((card, i) => ( - // Hide every card except the first while game is in progress -
    -
    - )} - - {/* Results from previous round */} - {result !== Result.Pending && ( -

    - {result} - {this.isPlayerWinResult(result) && ( - <> - {" You gained "} - - - )} - {result === Result.DealerWon && ( - <> - {" You lost "} - - - )} -

    - )} -
    - ) - } + {/* Results from previous round */} + {result !== Result.Pending && ( +

    + {result} + {this.isPlayerWinResult(result) && ( + <> + {" You gained "} + + + )} + {result === Result.DealerWon && ( + <> + {" You lost "} + + + )} +

    + )} +
    + ); + } } diff --git a/src/Casino/CardDeck/Card.ts b/src/Casino/CardDeck/Card.ts index 18af17b43..575e1bf30 100644 --- a/src/Casino/CardDeck/Card.ts +++ b/src/Casino/CardDeck/Card.ts @@ -1,42 +1,40 @@ // Enum values are lowercased to match css classes export enum Suit { - Clubs = "clubs", - Diamonds = "diamonds", - Hearts = "hearts", - Spades = "spades", + Clubs = "clubs", + Diamonds = "diamonds", + Hearts = "hearts", + Spades = "spades", } export class Card { - - constructor(readonly value: number, readonly suit: Suit) { - if (value < 1 || value > 13) { - throw new Error(`Card instantiated with improper value: ${value}`); - } + constructor(readonly value: number, readonly suit: Suit) { + if (value < 1 || value > 13) { + throw new Error(`Card instantiated with improper value: ${value}`); } + } - formatValue(): string { - switch (this.value) { - case 1: - return "A"; - case 11: - return "J"; - case 12: - return "Q"; - case 13: - return "K"; - default: - return `${this.value}`; - } + formatValue(): string { + switch (this.value) { + case 1: + return "A"; + case 11: + return "J"; + case 12: + return "Q"; + case 13: + return "K"; + default: + return `${this.value}`; } + } - isRedSuit(): boolean { - return this.suit === Suit.Hearts || this.suit === Suit.Diamonds; - } + isRedSuit(): boolean { + return this.suit === Suit.Hearts || this.suit === Suit.Diamonds; + } - getStringRepresentation(): string { - const value = this.formatValue(); + getStringRepresentation(): string { + const value = this.formatValue(); - return `${value} of ${this.suit}`; - } - -} \ No newline at end of file + return `${value} of ${this.suit}`; + } +} diff --git a/src/Casino/CardDeck/Deck.ts b/src/Casino/CardDeck/Deck.ts index 1accfe441..97a616ddb 100644 --- a/src/Casino/CardDeck/Deck.ts +++ b/src/Casino/CardDeck/Deck.ts @@ -2,57 +2,55 @@ import { Card, Suit } from "./Card"; import { shuffle } from "lodash"; export class Deck { + private cards: Card[] = []; - private cards: Card[] = []; + // Support multiple decks + constructor(private numDecks = 1) { + this.reset(); + } - // Support multiple decks - constructor(private numDecks = 1) { - this.reset(); + shuffle(): void { + this.cards = shuffle(this.cards); // Just use lodash + } + + drawCard(): Card { + if (this.cards.length == 0) { + throw new Error("Tried to draw card from empty deck"); } - shuffle(): void { - this.cards = shuffle(this.cards); // Just use lodash + return this.cards.shift() as Card; // Guaranteed to return a Card since we throw an Error if array is empty + } + + // Draws a card, resetting the deck beforehands if the Deck is empty + safeDrawCard(): Card { + if (this.cards.length === 0) { + this.reset(); } - drawCard(): Card { - if (this.cards.length == 0) { - throw new Error("Tried to draw card from empty deck"); - } - - return this.cards.shift() as Card; // Guaranteed to return a Card since we throw an Error if array is empty + return this.drawCard(); + } + + // Reset the deck back to the original 52 cards and shuffle it + reset(): void { + this.cards = []; + + for (let i = 1; i <= 13; ++i) { + for (let j = 0; j < this.numDecks; ++j) { + this.cards.push(new Card(i, Suit.Clubs)); + this.cards.push(new Card(i, Suit.Diamonds)); + this.cards.push(new Card(i, Suit.Hearts)); + this.cards.push(new Card(i, Suit.Spades)); + } } - // Draws a card, resetting the deck beforehands if the Deck is empty - safeDrawCard(): Card { - if (this.cards.length === 0) { - this.reset(); - } + this.shuffle(); + } - return this.drawCard(); - } + size(): number { + return this.cards.length; + } - // Reset the deck back to the original 52 cards and shuffle it - reset(): void { - this.cards = []; - - for (let i = 1; i <= 13; ++i) { - for (let j = 0; j < this.numDecks; ++j) { - this.cards.push(new Card(i, Suit.Clubs)); - this.cards.push(new Card(i, Suit.Diamonds)); - this.cards.push(new Card(i, Suit.Hearts)); - this.cards.push(new Card(i, Suit.Spades)); - } - } - - this.shuffle(); - } - - size(): number { - return this.cards.length; - } - - isEmpty(): boolean { - return this.cards.length === 0; - } - -} \ No newline at end of file + isEmpty(): boolean { + return this.cards.length === 0; + } +} diff --git a/src/Casino/CardDeck/Hand.ts b/src/Casino/CardDeck/Hand.ts index 768582d5c..09f1afb96 100644 --- a/src/Casino/CardDeck/Hand.ts +++ b/src/Casino/CardDeck/Hand.ts @@ -1,25 +1,23 @@ /** * Represents a Hand of cards. - * - * This class is IMMUTABLE + * + * This class is IMMUTABLE */ import { Card } from "./Card"; export class Hand { + constructor(readonly cards: readonly Card[]) {} - constructor(readonly cards: readonly Card[]) {} + addCards(...cards: Card[]): Hand { + return new Hand([...this.cards, ...cards]); + } - addCards(...cards: Card[]): Hand { - return new Hand([ ...this.cards, ...cards ]); + removeByIndex(i: number): Hand { + if (i >= this.cards.length) { + throw new Error(`Tried to remove invalid card from Hand by index: ${i}`); } - removeByIndex(i: number): Hand { - if (i >= this.cards.length) { - throw new Error(`Tried to remove invalid card from Hand by index: ${i}`); - } - - return new Hand([ ...this.cards.slice().splice(i, 1) ]) - } - -} \ No newline at end of file + return new Hand([...this.cards.slice().splice(i, 1)]); + } +} diff --git a/src/Casino/CardDeck/ReactCard.tsx b/src/Casino/CardDeck/ReactCard.tsx index cf3d30c51..eb5437cb9 100644 --- a/src/Casino/CardDeck/ReactCard.tsx +++ b/src/Casino/CardDeck/ReactCard.tsx @@ -2,39 +2,34 @@ import React, { FC } from "react"; import { Card, Suit } from "./Card"; type Props = { - card: Card; - hidden?: boolean; -} + card: Card; + hidden?: boolean; +}; export const ReactCard: FC = ({ card, hidden }) => { - let suit : React.ReactNode; - switch (card.suit) { - case Suit.Clubs: - suit = ; - break; - case Suit.Diamonds: - suit = ; - break; - case Suit.Hearts: - suit = ; - break; - case Suit.Spades: - suit = ; - break; - default: - throw new Error(`MissingCaseException: ${card.suit}`); - - } - return ( -
    - <> -
    - {hidden ? " - " : card.formatValue()} -
    -
    - {hidden ? " - " : suit} -
    - -
    - ) -} \ No newline at end of file + let suit: React.ReactNode; + switch (card.suit) { + case Suit.Clubs: + suit = ; + break; + case Suit.Diamonds: + suit = ; + break; + case Suit.Hearts: + suit = ; + break; + case Suit.Spades: + suit = ; + break; + default: + throw new Error(`MissingCaseException: ${card.suit}`); + } + return ( +
    + <> +
    {hidden ? " - " : card.formatValue()}
    +
    {hidden ? " - " : suit}
    + +
    + ); +}; diff --git a/src/Casino/CoinFlip.tsx b/src/Casino/CoinFlip.tsx index e67935073..f9e10973f 100644 --- a/src/Casino/CoinFlip.tsx +++ b/src/Casino/CoinFlip.tsx @@ -5,94 +5,110 @@ */ import * as React from "react"; -import { IPlayer } from "../PersonObjects/IPlayer"; +import { IPlayer } from "../PersonObjects/IPlayer"; import { StdButton } from "../ui/React/StdButton"; -import { BadRNG } from "./RNG"; -import { Game } from "./Game"; -import { trusted } from "./utils"; +import { BadRNG } from "./RNG"; +import { Game } from "./Game"; +import { trusted } from "./utils"; type IProps = { - p: IPlayer; -} + p: IPlayer; +}; type IState = { - investment: number; - result: any; - status: string; - playLock: boolean; -} + investment: number; + result: any; + status: string; + playLock: boolean; +}; const minPlay = 0; const maxPlay = 10e3; export class CoinFlip extends Game { + constructor(props: IProps) { + super(props); - constructor(props: IProps) { - super(props); + this.state = { + investment: 1000, + result: , + status: "", + playLock: false, + }; - this.state = { - investment: 1000, - result: , - status: '', - playLock: false, - }; + this.play = this.play.bind(this); + this.updateInvestment = this.updateInvestment.bind(this); + } - this.play = this.play.bind(this); - this.updateInvestment = this.updateInvestment.bind(this); + updateInvestment(e: React.FormEvent): void { + let investment: number = parseInt(e.currentTarget.value); + if (isNaN(investment)) { + investment = minPlay; } - - updateInvestment(e: React.FormEvent): void { - let investment: number = parseInt(e.currentTarget.value); - if (isNaN(investment)) { - investment = minPlay; - } - if (investment > maxPlay) { - investment = maxPlay; - } - if (investment < minPlay) { - investment = minPlay; - } - this.setState({investment: investment}); + if (investment > maxPlay) { + investment = maxPlay; } - - play(guess: string): void { - if(this.reachedLimit(this.props.p)) return; - const v = BadRNG.random(); - let letter: string; - if (v < 0.5) { - letter = 'H'; - } else { - letter = 'T'; - } - const correct: boolean = guess===letter; - this.setState({ - result: {letter}, - status: correct ? " win!" : "lose!", - playLock: true, - }); - setTimeout(()=>this.setState({playLock: false}), 250); - if (correct) { - this.win(this.props.p, this.state.investment); - } else { - this.win(this.props.p, -this.state.investment); - } - if(this.reachedLimit(this.props.p)) return; + if (investment < minPlay) { + investment = minPlay; } + this.setState({ investment: investment }); + } + play(guess: string): void { + if (this.reachedLimit(this.props.p)) return; + const v = BadRNG.random(); + let letter: string; + if (v < 0.5) { + letter = "H"; + } else { + letter = "T"; + } + const correct: boolean = guess === letter; + this.setState({ + result: {letter}, + status: correct ? " win!" : "lose!", + playLock: true, + }); + setTimeout(() => this.setState({ playLock: false }), 250); + if (correct) { + this.win(this.props.p, this.state.investment); + } else { + this.win(this.props.p, -this.state.investment); + } + if (this.reachedLimit(this.props.p)) return; + } - render(): React.ReactNode { - return <> -
    -+———————+
    -| | | |
    -| | {this.state.result} | |
    -| | | |
    -+———————+
    -
    - Play for:
    - this.play('H'))} text={"Head!"} disabled={this.state.playLock} /> - this.play('T'))} text={"Tail!"} disabled={this.state.playLock} /> + render(): React.ReactNode { + return ( + <> +
    +          +———————+
    +          
    + | | | |
    | | {this.state.result} | |
    + | | | |
    + +———————+ +
    +
    + Play for: + +
    + this.play("H"))} + text={"Head!"} + disabled={this.state.playLock} + /> + this.play("T"))} + text={"Tail!"} + disabled={this.state.playLock} + />

    {this.state.status}

    - - } -} \ No newline at end of file + + ); + } +} diff --git a/src/Casino/Game.tsx b/src/Casino/Game.tsx index d5c784e06..ece86b17c 100644 --- a/src/Casino/Game.tsx +++ b/src/Casino/Game.tsx @@ -1,20 +1,22 @@ import * as React from "react"; -import { IPlayer } from "../PersonObjects/IPlayer"; +import { IPlayer } from "../PersonObjects/IPlayer"; import { dialogBoxCreate } from "../../utils/DialogBox"; const gainLimit = 10e9; -export class Game extends React.Component { - win(p: IPlayer, n: number): void{ - p.gainMoney(n); - p.recordMoneySource(n, "casino"); - } +export class Game extends React.Component { + win(p: IPlayer, n: number): void { + p.gainMoney(n); + p.recordMoneySource(n, "casino"); + } - reachedLimit(p: IPlayer): boolean { - const reached = p.getCasinoWinnings() > gainLimit; - if(reached) { - dialogBoxCreate(<>Alright cheater get out of here. You're not allowed here anymore.); - } - return reached; - } -} \ No newline at end of file + reachedLimit(p: IPlayer): boolean { + const reached = p.getCasinoWinnings() > gainLimit; + if (reached) { + dialogBoxCreate( + <>Alright cheater get out of here. You're not allowed here anymore., + ); + } + return reached; + } +} diff --git a/src/Casino/RNG.ts b/src/Casino/RNG.ts index 00550020f..09e040875 100644 --- a/src/Casino/RNG.ts +++ b/src/Casino/RNG.ts @@ -1,6 +1,5 @@ - export interface RNG { - random(): number; + random(): number; } /* @@ -8,28 +7,28 @@ export interface RNG { * period of 1024. */ class RNG0 implements RNG { - x: number; - m = 1024; - a = 341; - c = 1; + x: number; + m = 1024; + a = 341; + c = 1; - constructor() { - this.x = 0; - this.reset(); - } + constructor() { + this.x = 0; + this.reset(); + } - step(): void { - this.x = (this.a*this.x+this.c) % this.m; - } + step(): void { + this.x = (this.a * this.x + this.c) % this.m; + } - random(): number { - this.step(); - return this.x/this.m; - } + random(): number { + this.step(); + return this.x / this.m; + } - reset(): void { - this.x = (new Date()).getTime() % this.m; - } + reset(): void { + this.x = new Date().getTime() % this.m; + } } export const BadRNG: RNG0 = new RNG0(); @@ -39,26 +38,26 @@ export const BadRNG: RNG0 = new RNG0(); * The period is 6e12. */ export class WHRNG implements RNG { - s1 = 0; - s2 = 0; - s3 = 0; + s1 = 0; + s2 = 0; + s3 = 0; - constructor(totalPlaytime: number) { - // This one is seeded by the players total play time. - const v: number = (totalPlaytime/1000)%30000; - this.s1 = v; - this.s2 = v; - this.s3 = v; - } + constructor(totalPlaytime: number) { + // This one is seeded by the players total play time. + const v: number = (totalPlaytime / 1000) % 30000; + this.s1 = v; + this.s2 = v; + this.s3 = v; + } - step(): void { - this.s1 = (171 * this.s1) % 30269; - this.s2 = (172 * this.s2) % 30307; - this.s3 = (170 * this.s3) % 30323; - } + step(): void { + this.s1 = (171 * this.s1) % 30269; + this.s2 = (172 * this.s2) % 30307; + this.s3 = (170 * this.s3) % 30323; + } - random(): number { - this.step(); - return (this.s1/30269.0 + this.s2/30307.0 + this.s3/30323.0)%1.0; - } + random(): number { + this.step(); + return (this.s1 / 30269.0 + this.s2 / 30307.0 + this.s3 / 30323.0) % 1.0; + } } diff --git a/src/Casino/Roulette.tsx b/src/Casino/Roulette.tsx index f48132013..0e1ec751d 100644 --- a/src/Casino/Roulette.tsx +++ b/src/Casino/Roulette.tsx @@ -1,291 +1,586 @@ import * as React from "react"; -import { IPlayer } from "../PersonObjects/IPlayer"; +import { IPlayer } from "../PersonObjects/IPlayer"; import { StdButton } from "../ui/React/StdButton"; -import { Money } from "../ui/React/Money"; -import { Game } from "./Game"; -import { WHRNG } from "./RNG"; -import { trusted } from "./utils"; +import { Money } from "../ui/React/Money"; +import { Game } from "./Game"; +import { WHRNG } from "./RNG"; +import { trusted } from "./utils"; type IProps = { - p: IPlayer; -} + p: IPlayer; +}; type IState = { - investment: number; - canPlay: boolean; - status: string | JSX.Element; - n: number; - lock: boolean; - strategy: Strategy; -} + investment: number; + canPlay: boolean; + status: string | JSX.Element; + n: number; + lock: boolean; + strategy: Strategy; +}; const minPlay = 0; const maxPlay = 1e7; function isRed(n: number): boolean { - return [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, - 21, 23, 25, 27, 30, 32, 34, 36].includes(n); + return [ + 1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36, + ].includes(n); } type Strategy = { - match: (n: number) => boolean; - payout: number; -} + match: (n: number) => boolean; + payout: number; +}; -const redNumbers: number[] = [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, - 21, 23, 25, 27, 30, 32, 34, 36]; +const redNumbers: number[] = [ + 1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36, +]; const strategies: { - Red: Strategy; - Black: Strategy; - Odd: Strategy; - Even: Strategy; - High: Strategy; - Low: Strategy; - Third1: Strategy; - Third2: Strategy; - Third3: Strategy; + Red: Strategy; + Black: Strategy; + Odd: Strategy; + Even: Strategy; + High: Strategy; + Low: Strategy; + Third1: Strategy; + Third2: Strategy; + Third3: Strategy; } = { - Red: { - match: (n: number): boolean => { - if (n === 0) return false; - return redNumbers.includes(n); - }, - payout: 1, + Red: { + match: (n: number): boolean => { + if (n === 0) return false; + return redNumbers.includes(n); }, - Black: { - match: (n: number): boolean => { - return !redNumbers.includes(n); - }, - payout: 1, + payout: 1, + }, + Black: { + match: (n: number): boolean => { + return !redNumbers.includes(n); }, - Odd: { - match: (n: number): boolean => { - if (n === 0) return false; - return n%2 === 1; - }, - payout: 1, + payout: 1, + }, + Odd: { + match: (n: number): boolean => { + if (n === 0) return false; + return n % 2 === 1; }, - Even: { - match: (n: number): boolean => { - if (n === 0) return false; - return n%2 === 0; - }, - payout: 1, + payout: 1, + }, + Even: { + match: (n: number): boolean => { + if (n === 0) return false; + return n % 2 === 0; }, - High: { - match: (n: number): boolean => { - if (n === 0) return false; - return n>18 - }, - payout: 1, + payout: 1, + }, + High: { + match: (n: number): boolean => { + if (n === 0) return false; + return n > 18; }, - Low: { - match: (n: number): boolean => { - if (n === 0) return false; - return n<19; - }, - payout: 1, + payout: 1, + }, + Low: { + match: (n: number): boolean => { + if (n === 0) return false; + return n < 19; }, - Third1: { - match: (n: number): boolean => { - if (n === 0) return false; - return n <= 12; - }, - payout: 2, + payout: 1, + }, + Third1: { + match: (n: number): boolean => { + if (n === 0) return false; + return n <= 12; }, - Third2: { - match: (n: number): boolean => { - if (n === 0) return false; - return n >= 13 && n <= 24; - }, - payout: 2, + payout: 2, + }, + Third2: { + match: (n: number): boolean => { + if (n === 0) return false; + return n >= 13 && n <= 24; }, - Third3: { - match: (n: number): boolean => { - if (n === 0) return false; - return n >= 25; - }, - payout: 2, + payout: 2, + }, + Third3: { + match: (n: number): boolean => { + if (n === 0) return false; + return n >= 25; }, -} + payout: 2, + }, +}; function Single(s: number): Strategy { - return { - match: (n: number): boolean => { - return s === n; - }, - payout: 36, - } + return { + match: (n: number): boolean => { + return s === n; + }, + payout: 36, + }; } export class Roulette extends Game { - interval = -1; - rng: WHRNG; + interval = -1; + rng: WHRNG; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.rng = new WHRNG((new Date()).getTime()); - this.state = { - investment: 1000, - canPlay: true, - status: 'waiting', - n: 0, - lock: true, - strategy: { - payout: 0, - match: (): boolean => { return false }, - }, + this.rng = new WHRNG(new Date().getTime()); + this.state = { + investment: 1000, + canPlay: true, + status: "waiting", + n: 0, + lock: true, + strategy: { + payout: 0, + match: (): boolean => { + return false; + }, + }, + }; + + this.step = this.step.bind(this); + this.currentNumber = this.currentNumber.bind(this); + this.updateInvestment = this.updateInvestment.bind(this); + } + + componentDidMount(): void { + this.interval = window.setInterval(this.step, 50); + } + + step(): void { + if (!this.state.lock) { + this.setState({ n: Math.floor(Math.random() * 37) }); + } + } + + componentWillUnmount(): void { + clearInterval(this.interval); + } + + updateInvestment(e: React.FormEvent): void { + let investment: number = parseInt(e.currentTarget.value); + if (isNaN(investment)) { + investment = minPlay; + } + if (investment > maxPlay) { + investment = maxPlay; + } + if (investment < minPlay) { + investment = minPlay; + } + this.setState({ investment: investment }); + } + + currentNumber(): string { + if (this.state.n === 0) return "0"; + const color = isRed(this.state.n) ? "R" : "B"; + return `${this.state.n}${color}`; + } + + play(s: Strategy): void { + if (this.reachedLimit(this.props.p)) return; + this.setState({ + canPlay: false, + lock: false, + status: "playing", + strategy: s, + }); + setTimeout(() => { + let n = Math.floor(this.rng.random() * 37); + let status = <>; + let gain = 0; + let playerWin = this.state.strategy.match(n); + // oh yeah, the house straight up cheats. Try finding the seed now! + if (playerWin && Math.random() > 0.9) { + playerWin = false; + while (this.state.strategy.match(n)) { + n++; } + } + if (playerWin) { + gain = this.state.investment * this.state.strategy.payout; + status = ( + <> + won + + ); + } else { + gain = -this.state.investment; + status = ( + <> + lost + + ); + } + this.win(this.props.p, gain); + this.setState({ + canPlay: true, + lock: true, + status: status, + n: n, + }); + this.reachedLimit(this.props.p); + }, 1600); + } - this.step = this.step.bind(this); - this.currentNumber = this.currentNumber.bind(this); - this.updateInvestment = this.updateInvestment.bind(this); - } - - - componentDidMount(): void { - this.interval = window.setInterval(this.step, 50); - } - - step(): void { - if (!this.state.lock) { - this.setState({n: Math.floor(Math.random()*37)}); - } - } - - componentWillUnmount(): void { - clearInterval(this.interval); - } - - updateInvestment(e: React.FormEvent): void { - let investment: number = parseInt(e.currentTarget.value); - if (isNaN(investment)) { - investment = minPlay; - } - if (investment > maxPlay) { - investment = maxPlay; - } - if (investment < minPlay) { - investment = minPlay; - } - this.setState({investment: investment}); - } - - currentNumber(): string { - if (this.state.n === 0) return '0'; - const color = isRed(this.state.n) ? 'R' : 'B'; - return `${this.state.n}${color}`; - } - - play(s: Strategy): void { - if(this.reachedLimit(this.props.p)) return; - this.setState({ - canPlay: false, - lock: false, - status: 'playing', - strategy: s, - }) - setTimeout(() => { - let n = Math.floor(this.rng.random()*37); - let status = <>; - let gain = 0; - let playerWin = this.state.strategy.match(n) - // oh yeah, the house straight up cheats. Try finding the seed now! - if(playerWin && Math.random() > 0.9) { - playerWin = false; - while(this.state.strategy.match(n)) { - n++; - } - } - if(playerWin) { - gain = this.state.investment*this.state.strategy.payout; - status = <>won ; - } else { - gain = -this.state.investment; - status = <>lost ; - } - this.win(this.props.p, gain); - this.setState({ - canPlay: true, - lock: true, - status: status, - n: n, - }); - this.reachedLimit(this.props.p); - }, 1600); - } - - - render(): React.ReactNode { - return <> + render(): React.ReactNode { + return ( + <>

    {this.currentNumber()}

    - +

    {this.state.status}

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    this.play(Single(3)))} />this.play(Single(6)))} />this.play(Single(9)))} />this.play(Single(12)))} />this.play(Single(15)))} />this.play(Single(18)))} />this.play(Single(21)))} />this.play(Single(24)))} />this.play(Single(27)))} />this.play(Single(30)))} />this.play(Single(33)))} />this.play(Single(36)))} />
    this.play(Single(2)))} />this.play(Single(5)))} />this.play(Single(8)))} />this.play(Single(11)))} />this.play(Single(14)))} />this.play(Single(17)))} />this.play(Single(20)))} />this.play(Single(23)))} />this.play(Single(26)))} />this.play(Single(29)))} />this.play(Single(32)))} />this.play(Single(35)))} />
    this.play(Single(1)))} />this.play(Single(4)))} />this.play(Single(7)))} />this.play(Single(10)))} />this.play(Single(13)))} />this.play(Single(16)))} />this.play(Single(19)))} />this.play(Single(22)))} />this.play(Single(25)))} />this.play(Single(28)))} />this.play(Single(31)))} />this.play(Single(34)))} />
    this.play(strategies.Third1))} />this.play(strategies.Third2))} />this.play(strategies.Third3))} />
    this.play(strategies.Red))} />this.play(strategies.Black))} />this.play(strategies.Odd))} />this.play(strategies.Even))} />this.play(strategies.High))} />this.play(strategies.Low))} />
    this.play(Single(0)))} />
    + this.play(Single(3)))} + /> + + this.play(Single(6)))} + /> + + this.play(Single(9)))} + /> + + this.play(Single(12)))} + /> + + this.play(Single(15)))} + /> + + this.play(Single(18)))} + /> + + this.play(Single(21)))} + /> + + this.play(Single(24)))} + /> + + this.play(Single(27)))} + /> + + this.play(Single(30)))} + /> + + this.play(Single(33)))} + /> + + this.play(Single(36)))} + /> +
    + this.play(Single(2)))} + /> + + this.play(Single(5)))} + /> + + this.play(Single(8)))} + /> + + this.play(Single(11)))} + /> + + this.play(Single(14)))} + /> + + this.play(Single(17)))} + /> + + this.play(Single(20)))} + /> + + this.play(Single(23)))} + /> + + this.play(Single(26)))} + /> + + this.play(Single(29)))} + /> + + this.play(Single(32)))} + /> + + this.play(Single(35)))} + /> +
    + this.play(Single(1)))} + /> + + this.play(Single(4)))} + /> + + this.play(Single(7)))} + /> + + this.play(Single(10)))} + /> + + this.play(Single(13)))} + /> + + this.play(Single(16)))} + /> + + this.play(Single(19)))} + /> + + this.play(Single(22)))} + /> + + this.play(Single(25)))} + /> + + this.play(Single(28)))} + /> + + this.play(Single(31)))} + /> + + this.play(Single(34)))} + /> +
    + this.play(strategies.Third1))} + /> + + this.play(strategies.Third2))} + /> + + this.play(strategies.Third3))} + /> +
    + this.play(strategies.Red))} + /> + + this.play(strategies.Black))} + /> + + this.play(strategies.Odd))} + /> + + this.play(strategies.Even))} + /> + + this.play(strategies.High))} + /> + + this.play(strategies.Low))} + /> +
    + this.play(Single(0)))} + /> +
    - - } -} \ No newline at end of file + + ); + } +} diff --git a/src/Casino/SlotMachine.tsx b/src/Casino/SlotMachine.tsx index 720ea06f8..7554246c9 100644 --- a/src/Casino/SlotMachine.tsx +++ b/src/Casino/SlotMachine.tsx @@ -1,239 +1,374 @@ import * as React from "react"; -import { IPlayer } from "../PersonObjects/IPlayer"; +import { IPlayer } from "../PersonObjects/IPlayer"; import { StdButton } from "../ui/React/StdButton"; -import { Money } from "../ui/React/Money"; -import { WHRNG } from "./RNG"; -import { Game } from "./Game"; -import { trusted } from "./utils"; +import { Money } from "../ui/React/Money"; +import { WHRNG } from "./RNG"; +import { Game } from "./Game"; +import { trusted } from "./utils"; type IProps = { - p: IPlayer; -} + p: IPlayer; +}; type IState = { - index: number[]; - locks: number[]; - investment: number; - canPlay: boolean; - status: string | JSX.Element; -} + index: number[]; + locks: number[]; + investment: number; + canPlay: boolean; + status: string | JSX.Element; +}; // statically shuffled array of symbols. -const symbols = ["D", "C", "$", "?", "♥", "A", "C", "B", "C", "E", "B", "E", "C", - "*", "D", "♥", "B", "A", "A", "A", "C", "A", "D", "B", "E", "?", "D", "*", - "@", "♥", "B", "E", "?"]; +const symbols = [ + "D", + "C", + "$", + "?", + "♥", + "A", + "C", + "B", + "C", + "E", + "B", + "E", + "C", + "*", + "D", + "♥", + "B", + "A", + "A", + "A", + "C", + "A", + "D", + "B", + "E", + "?", + "D", + "*", + "@", + "♥", + "B", + "E", + "?", +]; function getPayout(s: string, n: number): number { - switch (s) { - case "$": - return [20, 200, 1000][n]; - case "@": - return [8, 80, 400][n]; - case "♥": - case "?": - return [6, 20, 150][n]; - case "D": - case "E": - return [1, 8, 30][n]; - default: - return [1, 5, 20][n]; - } + switch (s) { + case "$": + return [20, 200, 1000][n]; + case "@": + return [8, 80, 400][n]; + case "♥": + case "?": + return [6, 20, 150][n]; + case "D": + case "E": + return [1, 8, 30][n]; + default: + return [1, 5, 20][n]; + } } const payLines = [ - // lines - [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]], - [[1, 0], [1, 1], [1, 2], [1, 3], [1, 4]], - [[2, 0], [2, 1], [2, 2], [2, 3], [2, 4]], + // lines + [ + [0, 0], + [0, 1], + [0, 2], + [0, 3], + [0, 4], + ], + [ + [1, 0], + [1, 1], + [1, 2], + [1, 3], + [1, 4], + ], + [ + [2, 0], + [2, 1], + [2, 2], + [2, 3], + [2, 4], + ], - // Vs - [[2, 0], [1, 1], [0, 2], [1, 3], [2, 4]], - [[0, 0], [1, 1], [2, 2], [1, 3], [0, 4]], + // Vs + [ + [2, 0], + [1, 1], + [0, 2], + [1, 3], + [2, 4], + ], + [ + [0, 0], + [1, 1], + [2, 2], + [1, 3], + [0, 4], + ], - // rest - [[0, 0], [1, 1], [1, 2], [1, 3], [0, 4]], - [[2, 0], [1, 1], [1, 2], [1, 3], [2, 4]], - [[1, 0], [0, 1], [0, 2], [0, 3], [1, 4]], - [[1, 0], [2, 1], [2, 2], [2, 3], [1, 4]], + // rest + [ + [0, 0], + [1, 1], + [1, 2], + [1, 3], + [0, 4], + ], + [ + [2, 0], + [1, 1], + [1, 2], + [1, 3], + [2, 4], + ], + [ + [1, 0], + [0, 1], + [0, 2], + [0, 3], + [1, 4], + ], + [ + [1, 0], + [2, 1], + [2, 2], + [2, 3], + [1, 4], + ], ]; const minPlay = 0; const maxPlay = 1e6; export class SlotMachine extends Game { - rng: WHRNG; - interval = -1; + rng: WHRNG; + interval = -1; - constructor(props: IProps) { - super(props); - this.rng = new WHRNG(this.props.p.totalPlaytime); + constructor(props: IProps) { + super(props); + this.rng = new WHRNG(this.props.p.totalPlaytime); - this.state = { - index: [0, 0, 0, 0, 0], - investment: 1000, - locks: [0, 0, 0, 0, 0], - canPlay: true, - status: 'waiting', - }; + this.state = { + index: [0, 0, 0, 0, 0], + investment: 1000, + locks: [0, 0, 0, 0, 0], + canPlay: true, + status: "waiting", + }; - this.play = this.play.bind(this); - this.lock = this.lock.bind(this); - this.unlock = this.unlock.bind(this); - this.step = this.step.bind(this); - this.checkWinnings = this.checkWinnings.bind(this); - this.getTable = this.getTable.bind(this); - this.updateInvestment = this.updateInvestment.bind(this); + this.play = this.play.bind(this); + this.lock = this.lock.bind(this); + this.unlock = this.unlock.bind(this); + this.step = this.step.bind(this); + this.checkWinnings = this.checkWinnings.bind(this); + this.getTable = this.getTable.bind(this); + this.updateInvestment = this.updateInvestment.bind(this); + } + + componentDidMount(): void { + this.interval = window.setInterval(this.step, 50); + } + + step(): void { + let stoppedOne = false; + const index = this.state.index.slice(); + for (const i in index) { + if (index[i] === this.state.locks[i] && !stoppedOne) continue; + index[i] = (index[i] + 1) % symbols.length; + stoppedOne = true; } - componentDidMount(): void { - this.interval = window.setInterval(this.step, 50); + this.setState({ index: index }); + + if (stoppedOne && index.every((e, i) => e === this.state.locks[i])) { + this.checkWinnings(); + } + } + + componentWillUnmount(): void { + clearInterval(this.interval); + } + + getTable(): string[][] { + return [ + [ + symbols[(this.state.index[0] + symbols.length - 1) % symbols.length], + symbols[(this.state.index[1] + symbols.length - 1) % symbols.length], + symbols[(this.state.index[2] + symbols.length - 1) % symbols.length], + symbols[(this.state.index[3] + symbols.length - 1) % symbols.length], + symbols[(this.state.index[4] + symbols.length - 1) % symbols.length], + ], + [ + symbols[this.state.index[0]], + symbols[this.state.index[1]], + symbols[this.state.index[2]], + symbols[this.state.index[3]], + symbols[this.state.index[4]], + ], + [ + symbols[(this.state.index[0] + 1) % symbols.length], + symbols[(this.state.index[1] + 1) % symbols.length], + symbols[(this.state.index[2] + 1) % symbols.length], + symbols[(this.state.index[3] + 1) % symbols.length], + symbols[(this.state.index[4] + 1) % symbols.length], + ], + ]; + } + + play(): void { + if (this.reachedLimit(this.props.p)) return; + this.setState({ status: "playing" }); + this.win(this.props.p, -this.state.investment); + if (!this.state.canPlay) return; + this.unlock(); + setTimeout(this.lock, this.rng.random() * 2000 + 1000); + } + + lock(): void { + this.setState({ + locks: [ + Math.floor(this.rng.random() * symbols.length), + Math.floor(this.rng.random() * symbols.length), + Math.floor(this.rng.random() * symbols.length), + Math.floor(this.rng.random() * symbols.length), + Math.floor(this.rng.random() * symbols.length), + ], + }); + } + + checkWinnings(): void { + const t = this.getTable(); + const getPaylineData = function (payline: number[][]): string[] { + const data = []; + for (const point of payline) { + data.push(t[point[0]][point[1]]); + } + return data; + }; + + const countSequence = function (data: string[]): number { + let count = 1; + for (let i = 1; i < data.length; i++) { + if (data[i] !== data[i - 1]) break; + count++; + } + + return count; + }; + + let gains = -this.state.investment; + for (const payline of payLines) { + const data = getPaylineData(payline); + const count = countSequence(data); + if (count < 3) continue; + const payout = getPayout(data[0], count - 3); + gains += this.state.investment * payout; + this.win(this.props.p, this.state.investment * payout); } - step(): void { - let stoppedOne = false; - const index = this.state.index.slice(); - for(const i in index) { - if (index[i] === this.state.locks[i] && !stoppedOne) continue; - index[i] = (index[i] + 1) % symbols.length; - stoppedOne = true; - } - - this.setState({index: index}); - - if(stoppedOne && index.every((e, i) => e === this.state.locks[i])) { - this.checkWinnings(); - } - } - - componentWillUnmount(): void { - clearInterval(this.interval); - } - - getTable(): string[][] { - return [ - [symbols[(this.state.index[0]+symbols.length-1)%symbols.length], symbols[(this.state.index[1]+symbols.length-1)%symbols.length], symbols[(this.state.index[2]+symbols.length-1)%symbols.length], symbols[(this.state.index[3]+symbols.length-1)%symbols.length], symbols[(this.state.index[4]+symbols.length-1)%symbols.length]], - [symbols[this.state.index[0]], symbols[this.state.index[1]], symbols[this.state.index[2]], symbols[this.state.index[3]], symbols[this.state.index[4]]], - [symbols[(this.state.index[0]+1)%symbols.length], symbols[(this.state.index[1]+1)%symbols.length], symbols[(this.state.index[2]+1)%symbols.length], symbols[(this.state.index[3]+1)%symbols.length], symbols[(this.state.index[4]+1)%symbols.length]], - ]; - } - - play(): void { - if(this.reachedLimit(this.props.p)) return; - this.setState({status: 'playing'}); - this.win(this.props.p, -this.state.investment); - if(!this.state.canPlay) return; - this.unlock(); - setTimeout(this.lock, this.rng.random()*2000+1000); - } - - lock(): void { - this.setState({ - locks: [ - Math.floor(this.rng.random()*symbols.length), - Math.floor(this.rng.random()*symbols.length), - Math.floor(this.rng.random()*symbols.length), - Math.floor(this.rng.random()*symbols.length), - Math.floor(this.rng.random()*symbols.length), - ], - }) - } - - checkWinnings(): void { - const t = this.getTable(); - const getPaylineData = function(payline: number[][]): string[] { - const data = []; - for(const point of payline) { - data.push(t[point[0]][point[1]]); - } - return data; - } - - const countSequence = function(data: string[]): number { - let count = 1; - for(let i = 1; i < data.length; i++) { - if (data[i]!==data[i-1]) break; - count++; - } - - return count; - } - - let gains = -this.state.investment; - for (const payline of payLines) { - const data = getPaylineData(payline); - const count = countSequence(data); - if (count < 3) continue; - const payout = getPayout(data[0], count-3); - gains += this.state.investment*payout; - this.win(this.props.p, this.state.investment*payout); - } - - this.setState({ - status: <>{gains>0?"gained":"lost"} , - canPlay: true, - }) - if(this.reachedLimit(this.props.p)) return; - } - - unlock(): void { - this.setState({ - locks: [-1, -1, -1, -1, -1], - canPlay: false, - }) - } - - updateInvestment(e: React.FormEvent): void { - let investment: number = parseInt(e.currentTarget.value); - if (isNaN(investment)) { - investment = minPlay; - } - if (investment > maxPlay) { - investment = maxPlay; - } - if (investment < minPlay) { - investment = minPlay; - } - this.setState({investment: investment}); - } - - render(): React.ReactNode { - const t = this.getTable(); - return <> -
    -+———————————————————————+
    -| | {t[0][0]} | {t[0][1]} | {t[0][2]} | {t[0][3]} | {t[0][4]} | |
    -| | | | | | | |
    -| | {symbols[this.state.index[0]]} | {symbols[this.state.index[1]]} | {symbols[this.state.index[2]]} | {symbols[this.state.index[3]]} | {symbols[this.state.index[4]]} | |
    -| | | | | | | |
    -| | {symbols[(this.state.index[0]+1)%symbols.length]} | {symbols[(this.state.index[1]+1)%symbols.length]} | {symbols[(this.state.index[2]+1)%symbols.length]} | {symbols[(this.state.index[3]+1)%symbols.length]} | {symbols[(this.state.index[4]+1)%symbols.length]} | |
    -+———————————————————————+
    -
    - - -

    {this.state.status}

    -

    Pay lines

    -
    ------   ·····   ····· 
    -····· ----- ·····
    -····· ····· -----
    -
    -
    - -
    -··^··   \···/   \···/
    -·/·\· ·\·/· ·---·
    -/···\ ··v·· ·····
    -
    -
    - -
    -·····   ·---·   ·····
    -·---· /···\ \···/
    -/···\ ····· ·---·
    -
    + this.setState({ + status: ( + <> + {gains > 0 ? "gained" : "lost"} + ), + canPlay: true, + }); + if (this.reachedLimit(this.props.p)) return; + } + + unlock(): void { + this.setState({ + locks: [-1, -1, -1, -1, -1], + canPlay: false, + }); + } + + updateInvestment(e: React.FormEvent): void { + let investment: number = parseInt(e.currentTarget.value); + if (isNaN(investment)) { + investment = minPlay; } + if (investment > maxPlay) { + investment = maxPlay; + } + if (investment < minPlay) { + investment = minPlay; + } + this.setState({ investment: investment }); + } + + render(): React.ReactNode { + const t = this.getTable(); + return ( + <> +
    +          +———————————————————————+
    +          
    | | {t[0][0]} | {t[0][1]} | {t[0][2]} | {t[0][3]} | {t[0][4]} | + |
    + | | | | | | | |
    | | {symbols[this.state.index[0]]} |{" "} + {symbols[this.state.index[1]]} | {symbols[this.state.index[2]]} |{" "} + {symbols[this.state.index[3]]} | {symbols[this.state.index[4]]} | | +
    + | | | | | | | |
    | |{" "} + {symbols[(this.state.index[0] + 1) % symbols.length]} |{" "} + {symbols[(this.state.index[1] + 1) % symbols.length]} |{" "} + {symbols[(this.state.index[2] + 1) % symbols.length]} |{" "} + {symbols[(this.state.index[3] + 1) % symbols.length]} |{" "} + {symbols[(this.state.index[4] + 1) % symbols.length]} | |
    + +———————————————————————+ +
    +
    + + +

    {this.state.status}

    +

    Pay lines

    +
    +          ----- ····· ····· 
    + ····· ----- ·····
    + ····· ····· -----
    +
    +
    + +
    +          ··^·· \···/ \···/
    +          
    + ·/·\· ·\·/· ·---· +
    + /···\ ··v·· ····· +
    +
    +
    + +
    +          ····· ·---· ·····
    +          
    + ·---· /···\ \···/ +
    + /···\ ····· ·---· +
    +
    + + ); + } } -// https://felgo.com/doc/how-to-make-a-slot-game-tutorial/ \ No newline at end of file +// https://felgo.com/doc/how-to-make-a-slot-game-tutorial/ diff --git a/src/Casino/utils.ts b/src/Casino/utils.ts index 11a51c9b1..5e907bb83 100644 --- a/src/Casino/utils.ts +++ b/src/Casino/utils.ts @@ -1,8 +1,10 @@ import * as React from "react"; -export function trusted(f: () => void): (event: React.MouseEvent) => any { - return function(event: React.MouseEvent): any { - if(!event.isTrusted) return; - f(); - }; -} \ No newline at end of file +export function trusted( + f: () => void, +): (event: React.MouseEvent) => any { + return function (event: React.MouseEvent): any { + if (!event.isTrusted) return; + f(); + }; +} diff --git a/src/CinematicText.js b/src/CinematicText.js index 7cdb67ff4..e59e3b75f 100644 --- a/src/CinematicText.js +++ b/src/CinematicText.js @@ -1,4 +1,3 @@ - import { Engine } from "./engine"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; @@ -12,94 +11,116 @@ export let cinematicTextFlag = false; /** * Print a message using a hacking-style "typing" effect. * Note that this clears the UI so that the text from this is the only thing visible. - * + * * @param lines {string[]} Array of strings to print, where each element is a separate line */ export function writeCinematicText(lines) { - cinematicTextFlag = true; + cinematicTextFlag = true; - if (lines.constructor !== Array) { - throw new Error("Invalid non-array argument passed into writeCinematicText()"); + if (lines.constructor !== Array) { + throw new Error( + "Invalid non-array argument passed into writeCinematicText()", + ); + } + + // Reuse the 'Red Pill' content + Engine.loadCinematicTextContent(); + const container = document.getElementById("cinematic-text-container"); + container.style.width = "75%"; + if (container == null) { + throw new Error( + "Could not find cinematic-text-container for writeCinematicText()", + ); + } + removeChildrenFromElement(container); + + for (let i = 0; i < lines.length; ++i) { + if (!isString(lines[i])) { + throw new Error( + "Invalid non-string element in 'lines' argument. writeCinematicText() failed", + ); } + } - // Reuse the 'Red Pill' content - Engine.loadCinematicTextContent(); - const container = document.getElementById("cinematic-text-container"); - container.style.width = "75%"; - if (container == null) {throw new Error("Could not find cinematic-text-container for writeCinematicText()");} - removeChildrenFromElement(container); - - for (let i = 0; i < lines.length; ++i) { - if (!isString(lines[i])) { - throw new Error("Invalid non-string element in 'lines' argument. writeCinematicText() failed"); - } - } - - return writeCinematicTextRecurse(lines).then(function() { - return cinematicTextEnd(); //Puts the continue button - }).catch(function(e) { - exceptionAlert(e); + return writeCinematicTextRecurse(lines) + .then(function () { + return cinematicTextEnd(); //Puts the continue button + }) + .catch(function (e) { + exceptionAlert(e); }); } -function writeCinematicTextRecurse(lines, lineNumber=0) { - if (lineNumber >= lines.length) {return Promise.resolve(true);} - return writeCinematicTextLine(lines[lineNumber]).then(function() { - return writeCinematicTextRecurse(lines, lineNumber+1); - }); +function writeCinematicTextRecurse(lines, lineNumber = 0) { + if (lineNumber >= lines.length) { + return Promise.resolve(true); + } + return writeCinematicTextLine(lines[lineNumber]).then(function () { + return writeCinematicTextRecurse(lines, lineNumber + 1); + }); } function writeCinematicTextLine(line) { - return new Promise(function(resolve, reject) { - const container = document.getElementById("cinematic-text-container"); - const pElem = document.createElement("p"); - container.appendChild(pElem); + return new Promise(function (resolve, reject) { + const container = document.getElementById("cinematic-text-container"); + const pElem = document.createElement("p"); + container.appendChild(pElem); - const promise = writeCinematicTextLetter(pElem, line, 0); - promise.then(function(res) { - resolve(res); - }, function(e) { - reject(e); - }); - }); + const promise = writeCinematicTextLetter(pElem, line, 0); + promise.then( + function (res) { + resolve(res); + }, + function (e) { + reject(e); + }, + ); + }); } -function writeCinematicTextLetter(pElem, line, i=0) { - return new Promise(function(resolve, reject) { - setTimeoutRef(function() { - const textToShow = line.substring(0, i); +function writeCinematicTextLetter(pElem, line, i = 0) { + return new Promise(function (resolve, reject) { + setTimeoutRef(function () { + const textToShow = line.substring(0, i); - if (i >= line.length) { - pElem.innerHTML = textToShow; - return resolve(true); - } + if (i >= line.length) { + pElem.innerHTML = textToShow; + return resolve(true); + } - pElem.innerHTML = textToShow + ""; - const promise = writeCinematicTextLetter(pElem, line, i+1); - promise.then(function(res) { - resolve(res); - }, function(e) { - reject(e); - }); - }, 15); - }); + pElem.innerHTML = + textToShow + ""; + const promise = writeCinematicTextLetter(pElem, line, i + 1); + promise.then( + function (res) { + resolve(res); + }, + function (e) { + reject(e); + }, + ); + }, 15); + }); } function cinematicTextEnd() { - var container = document.getElementById("cinematic-text-container"); - var mainMenu = document.getElementById("mainmenu-container"); - container.appendChild(createElement("br")); + var container = document.getElementById("cinematic-text-container"); + var mainMenu = document.getElementById("mainmenu-container"); + container.appendChild(createElement("br")); - return new Promise (function(resolve) { - container.appendChild(createElement("a", { - class:"a-link-button", innerText:"Continue...", - clickListener:()=>{ - removeChildrenFromElement(container); - Engine.loadTerminalContent(); - mainMenu.style.visibility = "visible"; - cinematicTextFlag = false; - resolve(); - }, - })); - }); + return new Promise(function (resolve) { + container.appendChild( + createElement("a", { + class: "a-link-button", + innerText: "Continue...", + clickListener: () => { + removeChildrenFromElement(container); + Engine.loadTerminalContent(); + mainMenu.style.visibility = "visible"; + cinematicTextFlag = false; + resolve(); + }, + }), + ); + }); } diff --git a/src/CodingContractGenerator.ts b/src/CodingContractGenerator.ts index 49f89c4ee..6048de731 100644 --- a/src/CodingContractGenerator.ts +++ b/src/CodingContractGenerator.ts @@ -1,8 +1,8 @@ import { - CodingContract, - CodingContractRewardType, - CodingContractTypes, - ICodingContractReward, + CodingContract, + CodingContractRewardType, + CodingContractTypes, + ICodingContractReward, } from "./CodingContracts"; import { Factions } from "./Faction/Factions"; import { Player } from "./Player"; @@ -14,185 +14,215 @@ import { HacknetServer } from "./Hacknet/HacknetServer"; import { getRandomInt } from "../utils/helpers/getRandomInt"; - export function generateRandomContract(): void { - // First select a random problem type - const problemType = getRandomProblemType(); + // First select a random problem type + const problemType = getRandomProblemType(); - // Then select a random reward type. 'Money' will always be the last reward type - const reward = getRandomReward(); + // Then select a random reward type. 'Money' will always be the last reward type + const reward = getRandomReward(); - // Choose random server - const randServer = getRandomServer(); + // Choose random server + const randServer = getRandomServer(); - const contractFn = getRandomFilename(randServer, reward); - const contract = new CodingContract(contractFn, problemType, reward); + const contractFn = getRandomFilename(randServer, reward); + const contract = new CodingContract(contractFn, problemType, reward); - randServer.addContract(contract); + randServer.addContract(contract); } export function generateRandomContractOnHome(): void { - // First select a random problem type - const problemType = getRandomProblemType(); + // First select a random problem type + const problemType = getRandomProblemType(); - // Then select a random reward type. 'Money' will always be the last reward type - const reward = getRandomReward(); + // Then select a random reward type. 'Money' will always be the last reward type + const reward = getRandomReward(); - // Choose random server - const serv = Player.getHomeComputer(); + // Choose random server + const serv = Player.getHomeComputer(); - const contractFn = getRandomFilename(serv, reward); - const contract = new CodingContract(contractFn, problemType, reward); + const contractFn = getRandomFilename(serv, reward); + const contract = new CodingContract(contractFn, problemType, reward); - serv.addContract(contract); + serv.addContract(contract); } export interface IGenerateContractParams { - problemType?: string; - server?: string; - fn?: string; + problemType?: string; + server?: string; + fn?: string; } export function generateContract(params: IGenerateContractParams): void { - // Problem Type - let problemType; - const problemTypes = Object.keys(CodingContractTypes); - if (params.problemType != null && problemTypes.includes(params.problemType)) { - problemType = params.problemType; - } else { - problemType = getRandomProblemType(); + // Problem Type + let problemType; + const problemTypes = Object.keys(CodingContractTypes); + if (params.problemType != null && problemTypes.includes(params.problemType)) { + problemType = params.problemType; + } else { + problemType = getRandomProblemType(); + } + + // Reward Type - This is always random for now + const reward = getRandomReward(); + + // Server + let server; + if (params.server != null) { + server = GetServerByHostname(params.server); + if (server == null) { + server = AllServers[params.server]; } - - // Reward Type - This is always random for now - const reward = getRandomReward(); - - // Server - let server; - if (params.server != null) { - server = GetServerByHostname(params.server); - if (server == null) { - server = AllServers[params.server]; - } - if (server == null) { - server = getRandomServer(); - } - } else { - server = getRandomServer(); + if (server == null) { + server = getRandomServer(); } + } else { + server = getRandomServer(); + } - // Filename - let fn; - if (params.fn != null) { - fn = params.fn; - } else { - fn = getRandomFilename(server, reward); - } + // Filename + let fn; + if (params.fn != null) { + fn = params.fn; + } else { + fn = getRandomFilename(server, reward); + } - const contract = new CodingContract(fn, problemType, reward); - server.addContract(contract); + const contract = new CodingContract(fn, problemType, reward); + server.addContract(contract); } // Ensures that a contract's reward type is valid -function sanitizeRewardType(rewardType: CodingContractRewardType): CodingContractRewardType { - let type = rewardType; // Create copy +function sanitizeRewardType( + rewardType: CodingContractRewardType, +): CodingContractRewardType { + let type = rewardType; // Create copy - const factionsThatAllowHacking = Player.factions.filter((fac) => { - try { - return Factions[fac].getInfo().offerHackingWork; - } catch (e) { - console.error(`Error when trying to filter Hacking Factions for Coding Contract Generation: ${e}`); - return false; - } - }); - if (type === CodingContractRewardType.FactionReputation && factionsThatAllowHacking.length === 0) { - type = CodingContractRewardType.CompanyReputation; - } - if (type === CodingContractRewardType.FactionReputationAll && factionsThatAllowHacking.length === 0) { - type = CodingContractRewardType.CompanyReputation; - } - if (type === CodingContractRewardType.CompanyReputation && Object.keys(Player.jobs).length === 0) { - type = CodingContractRewardType.Money; + const factionsThatAllowHacking = Player.factions.filter((fac) => { + try { + return Factions[fac].getInfo().offerHackingWork; + } catch (e) { + console.error( + `Error when trying to filter Hacking Factions for Coding Contract Generation: ${e}`, + ); + return false; } + }); + if ( + type === CodingContractRewardType.FactionReputation && + factionsThatAllowHacking.length === 0 + ) { + type = CodingContractRewardType.CompanyReputation; + } + if ( + type === CodingContractRewardType.FactionReputationAll && + factionsThatAllowHacking.length === 0 + ) { + type = CodingContractRewardType.CompanyReputation; + } + if ( + type === CodingContractRewardType.CompanyReputation && + Object.keys(Player.jobs).length === 0 + ) { + type = CodingContractRewardType.Money; + } - return type; + return type; } function getRandomProblemType(): string { - const problemTypes = Object.keys(CodingContractTypes); - const randIndex = getRandomInt(0, problemTypes.length - 1); + const problemTypes = Object.keys(CodingContractTypes); + const randIndex = getRandomInt(0, problemTypes.length - 1); - return problemTypes[randIndex]; + return problemTypes[randIndex]; } function getRandomReward(): ICodingContractReward { - const reward: ICodingContractReward = { - name: "", - type: getRandomInt(0, CodingContractRewardType.Money), - }; - reward.type = sanitizeRewardType(reward.type); + const reward: ICodingContractReward = { + name: "", + type: getRandomInt(0, CodingContractRewardType.Money), + }; + reward.type = sanitizeRewardType(reward.type); - // Add additional information based on the reward type - const factionsThatAllowHacking = Player.factions.filter((fac) => { - try { - return Factions[fac].getInfo().offerHackingWork; - } catch (e) { - console.error(`Error when trying to filter Hacking Factions for Coding Contract Generation: ${e}`); - return false; - } - }); - - switch (reward.type) { - case CodingContractRewardType.FactionReputation: { - // Get a random faction that player is a part of. That - // faction must allow hacking contracts - const numFactions = factionsThatAllowHacking.length; - const randFaction = factionsThatAllowHacking[getRandomInt(0, numFactions - 1)]; - reward.name = randFaction; - break; - } - case CodingContractRewardType.CompanyReputation: { - const allJobs = Object.keys(Player.jobs); - if (allJobs.length > 0) { - reward.name = allJobs[getRandomInt(0, allJobs.length - 1)]; - } else { - reward.type = CodingContractRewardType.Money; - } - break; - } - default: - break; + // Add additional information based on the reward type + const factionsThatAllowHacking = Player.factions.filter((fac) => { + try { + return Factions[fac].getInfo().offerHackingWork; + } catch (e) { + console.error( + `Error when trying to filter Hacking Factions for Coding Contract Generation: ${e}`, + ); + return false; } + }); - return reward; + switch (reward.type) { + case CodingContractRewardType.FactionReputation: { + // Get a random faction that player is a part of. That + // faction must allow hacking contracts + const numFactions = factionsThatAllowHacking.length; + const randFaction = + factionsThatAllowHacking[getRandomInt(0, numFactions - 1)]; + reward.name = randFaction; + break; + } + case CodingContractRewardType.CompanyReputation: { + const allJobs = Object.keys(Player.jobs); + if (allJobs.length > 0) { + reward.name = allJobs[getRandomInt(0, allJobs.length - 1)]; + } else { + reward.type = CodingContractRewardType.Money; + } + break; + } + default: + break; + } + + return reward; } function getRandomServer(): Server | HacknetServer { - const servers = Object.keys(AllServers); - let randIndex = getRandomInt(0, servers.length - 1); - let randServer = AllServers[servers[randIndex]]; + const servers = Object.keys(AllServers); + let randIndex = getRandomInt(0, servers.length - 1); + let randServer = AllServers[servers[randIndex]]; - // An infinite loop shouldn't ever happen, but to be safe we'll use - // a for loop with a limited number of tries - for (let i = 0; i < 200; ++i) { - if (randServer instanceof Server && !randServer.purchasedByPlayer && randServer.hostname !== SpecialServerNames.WorldDaemon) { - break; - } - randIndex = getRandomInt(0, servers.length - 1); - randServer = AllServers[servers[randIndex]]; + // An infinite loop shouldn't ever happen, but to be safe we'll use + // a for loop with a limited number of tries + for (let i = 0; i < 200; ++i) { + if ( + randServer instanceof Server && + !randServer.purchasedByPlayer && + randServer.hostname !== SpecialServerNames.WorldDaemon + ) { + break; } + randIndex = getRandomInt(0, servers.length - 1); + randServer = AllServers[servers[randIndex]]; + } - return randServer; + return randServer; } -function getRandomFilename(server: Server | HacknetServer, reward: ICodingContractReward): string { - let contractFn = `contract-${getRandomInt(0, 1e6)}`; +function getRandomFilename( + server: Server | HacknetServer, + reward: ICodingContractReward, +): string { + let contractFn = `contract-${getRandomInt(0, 1e6)}`; - for (let i = 0; i < 1000; ++i) { - if (server.contracts.filter((c: CodingContract) => {return c.fn === contractFn}).length <= 0) { break; } - contractFn = `contract-${getRandomInt(0, 1e6)}`; + for (let i = 0; i < 1000; ++i) { + if ( + server.contracts.filter((c: CodingContract) => { + return c.fn === contractFn; + }).length <= 0 + ) { + break; } + contractFn = `contract-${getRandomInt(0, 1e6)}`; + } - if (reward.name) { contractFn += `-${reward.name.replace(/\s/g, "")}`; } + if (reward.name) { + contractFn += `-${reward.name.replace(/\s/g, "")}`; + } - return contractFn; + return contractFn; } diff --git a/src/CodingContracts.ts b/src/CodingContracts.ts index 9b332d8a2..9d47723cb 100644 --- a/src/CodingContracts.ts +++ b/src/CodingContracts.ts @@ -1,68 +1,69 @@ import { - codingContractTypesMetadata, - DescriptionFunc, - GeneratorFunc, - SolverFunc, + codingContractTypesMetadata, + DescriptionFunc, + GeneratorFunc, + SolverFunc, } from "./data/codingcontracttypes"; import { IMap } from "./types"; import { - Generic_fromJSON, - Generic_toJSON, - Reviver, + Generic_fromJSON, + Generic_toJSON, + Reviver, } from "../utils/JSONReviver"; import { createPopup, removePopup } from "./ui/React/createPopup"; import { CodingContractPopup } from "./ui/React/CodingContractPopup"; - /* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */ /* Represents different types of problems that a Coding Contract can have */ export class CodingContractType { - /** - * Function that generates a description of the problem - */ - desc: DescriptionFunc; + /** + * Function that generates a description of the problem + */ + desc: DescriptionFunc; - /** - * Number that generally represents the problem's difficulty. Bigger numbers = harder - */ - difficulty: number; + /** + * Number that generally represents the problem's difficulty. Bigger numbers = harder + */ + difficulty: number; - /** - * A function that randomly generates a valid 'data' for the problem - */ - generate: GeneratorFunc; + /** + * A function that randomly generates a valid 'data' for the problem + */ + generate: GeneratorFunc; - /** - * Name of the type of problem - */ - name: string; + /** + * Name of the type of problem + */ + name: string; - /** - * The maximum number of tries the player gets on this kind of problem before it self-destructs - */ - numTries: number; + /** + * The maximum number of tries the player gets on this kind of problem before it self-destructs + */ + numTries: number; - /** - * Stores a function that checks if the provided answer is correct - */ - solver: SolverFunc; + /** + * Stores a function that checks if the provided answer is correct + */ + solver: SolverFunc; - constructor(name: string, - desc: DescriptionFunc, - gen: GeneratorFunc, - solver: SolverFunc, - diff: number, - numTries: number) { - this.name = name; - this.desc = desc; - this.generate = gen; - this.solver = solver; - this.difficulty = diff; - this.numTries = numTries; - } + constructor( + name: string, + desc: DescriptionFunc, + gen: GeneratorFunc, + solver: SolverFunc, + diff: number, + numTries: number, + ) { + this.name = name; + this.desc = desc; + this.generate = gen; + this.solver = solver; + this.difficulty = diff; + this.numTries = numTries; + } } /* Contract Types */ @@ -70,36 +71,43 @@ export class CodingContractType { export const CodingContractTypes: IMap = {}; for (const md of codingContractTypesMetadata) { - // tslint:disable-next-line - CodingContractTypes[md.name] = new CodingContractType(md.name, md.desc, md.gen, md.solver, md.difficulty, md.numTries); + // tslint:disable-next-line + CodingContractTypes[md.name] = new CodingContractType( + md.name, + md.desc, + md.gen, + md.solver, + md.difficulty, + md.numTries, + ); } /** * Enum representing the different types of rewards a Coding Contract can give */ export enum CodingContractRewardType { - FactionReputation, - FactionReputationAll, - CompanyReputation, - Money, // This must always be the last reward type + FactionReputation, + FactionReputationAll, + CompanyReputation, + Money, // This must always be the last reward type } /** * Enum representing the result when trying to solve the Contract */ export enum CodingContractResult { - Success, - Failure, - Cancelled, + Success, + Failure, + Cancelled, } /** * A class that represents the type of reward a contract gives */ export interface ICodingContractReward { - /* Name of Company/Faction name for reward, if applicable */ - name?: string; - type: CodingContractRewardType; + /* Name of Company/Faction name for reward, if applicable */ + name?: string; + type: CodingContractRewardType; } /** @@ -107,104 +115,107 @@ export interface ICodingContractReward { * The player receives a reward if the problem is solved correctly */ export class CodingContract { + /* Relevant data for the contract's problem */ + data: any; - /* Relevant data for the contract's problem */ - data: any; + /* Contract's filename */ + fn: string; - /* Contract's filename */ - fn: string; - - /* Describes the reward given if this Contract is solved. The reward is actually + /* Describes the reward given if this Contract is solved. The reward is actually processed outside of this file */ - reward: ICodingContractReward | null; + reward: ICodingContractReward | null; - /* Number of times the Contract has been attempted */ - tries = 0; + /* Number of times the Contract has been attempted */ + tries = 0; - /* String representing the contract's type. Must match type in ContractTypes */ - type: string; + /* String representing the contract's type. Must match type in ContractTypes */ + type: string; - constructor(fn = "", - type = "Find Largest Prime Factor", - reward: ICodingContractReward | null = null) { - this.fn = fn; - if (!this.fn.endsWith(".cct")) { - this.fn += ".cct"; - } - - // tslint:disable-next-line - if (CodingContractTypes[type] == null) { - throw new Error(`Error: invalid contract type: ${type} please contact developer`); - } - - this.type = type; - this.data = CodingContractTypes[type].generate(); - this.reward = reward; + constructor( + fn = "", + type = "Find Largest Prime Factor", + reward: ICodingContractReward | null = null, + ) { + this.fn = fn; + if (!this.fn.endsWith(".cct")) { + this.fn += ".cct"; } - getData(): any { - return this.data; + // tslint:disable-next-line + if (CodingContractTypes[type] == null) { + throw new Error( + `Error: invalid contract type: ${type} please contact developer`, + ); } - getDescription(): string { - return CodingContractTypes[this.type].desc(this.data); - } + this.type = type; + this.data = CodingContractTypes[type].generate(); + this.reward = reward; + } - getDifficulty(): number { - return CodingContractTypes[this.type].difficulty; - } + getData(): any { + return this.data; + } - getMaxNumTries(): number { - return CodingContractTypes[this.type].numTries; - } + getDescription(): string { + return CodingContractTypes[this.type].desc(this.data); + } - getType(): string { - return CodingContractTypes[this.type].name; - } + getDifficulty(): number { + return CodingContractTypes[this.type].difficulty; + } - isSolution(solution: string): boolean { - return CodingContractTypes[this.type].solver(this.data, solution); - } + getMaxNumTries(): number { + return CodingContractTypes[this.type].numTries; + } - /** - * Creates a popup to prompt the player to solve the problem - */ - async prompt(): Promise { - const popupId = `coding-contract-prompt-popup-${this.fn}`; - return new Promise((resolve) => { - createPopup(popupId, CodingContractPopup, { - c: this, - popupId: popupId, - onClose: () => { - resolve(CodingContractResult.Cancelled); - removePopup(popupId); - }, - onAttempt: (val: string) => { - if (this.isSolution(val)) { - resolve(CodingContractResult.Success); - } else { - resolve(CodingContractResult.Failure); - } - removePopup(popupId); - }, - }); - }); - } + getType(): string { + return CodingContractTypes[this.type].name; + } - /** - * Serialize the current file to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("CodingContract", this); - } + isSolution(solution: string): boolean { + return CodingContractTypes[this.type].solver(this.data, solution); + } - /** - * Initiatizes a CodingContract from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): CodingContract { - return Generic_fromJSON(CodingContract, value.data); - } + /** + * Creates a popup to prompt the player to solve the problem + */ + async prompt(): Promise { + const popupId = `coding-contract-prompt-popup-${this.fn}`; + return new Promise((resolve) => { + createPopup(popupId, CodingContractPopup, { + c: this, + popupId: popupId, + onClose: () => { + resolve(CodingContractResult.Cancelled); + removePopup(popupId); + }, + onAttempt: (val: string) => { + if (this.isSolution(val)) { + resolve(CodingContractResult.Success); + } else { + resolve(CodingContractResult.Failure); + } + removePopup(popupId); + }, + }); + }); + } + + /** + * Serialize the current file to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("CodingContract", this); + } + + /** + * Initiatizes a CodingContract from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): CodingContract { + return Generic_fromJSON(CodingContract, value.data); + } } Reviver.constructors.CodingContract = CodingContract; diff --git a/src/Company/Companies.ts b/src/Company/Companies.ts index 6c775b42c..09abf8373 100644 --- a/src/Company/Companies.ts +++ b/src/Company/Companies.ts @@ -1,50 +1,52 @@ // Constructs all CompanyPosition objects using the metadata in data/companypositions.ts -import { companiesMetadata } from "./data/CompaniesMetadata"; -import { Company, IConstructorParams } from "./Company"; -import { IMap } from "../types"; -import { Reviver } from "../../utils/JSONReviver"; +import { companiesMetadata } from "./data/CompaniesMetadata"; +import { Company, IConstructorParams } from "./Company"; +import { IMap } from "../types"; +import { Reviver } from "../../utils/JSONReviver"; export let Companies: IMap = {}; function addCompany(params: IConstructorParams): void { - if (Companies[params.name] != null) { - console.warn(`Duplicate Company Position being defined: ${params.name}`); - } - Companies[params.name] = new Company(params); + if (Companies[params.name] != null) { + console.warn(`Duplicate Company Position being defined: ${params.name}`); + } + Companies[params.name] = new Company(params); } // Used to initialize new Company objects for the Companies map // Called when creating new game or after a prestige/reset export function initCompanies(): void { - // Save Old Company data for 'favor' - const oldCompanies = Companies; + // Save Old Company data for 'favor' + const oldCompanies = Companies; - // Re-construct all Companies - Companies = {}; - companiesMetadata.forEach((e) => { - addCompany(e); - }); + // Re-construct all Companies + Companies = {}; + companiesMetadata.forEach((e) => { + addCompany(e); + }); - // Reset data - for (const companyName in Companies) { - const company = Companies[companyName]; - const oldCompany = oldCompanies[companyName]; - if (!(oldCompany instanceof Company)) { - // New game, so no OldCompanies data - company.favor = 0; - } else { - company.favor = oldCompanies[companyName].favor; - if (isNaN(company.favor)) { company.favor = 0; } - } + // Reset data + for (const companyName in Companies) { + const company = Companies[companyName]; + const oldCompany = oldCompanies[companyName]; + if (!(oldCompany instanceof Company)) { + // New game, so no OldCompanies data + company.favor = 0; + } else { + company.favor = oldCompanies[companyName].favor; + if (isNaN(company.favor)) { + company.favor = 0; + } } + } } // Used to load Companies map from a save export function loadCompanies(saveString: string): void { - Companies = JSON.parse(saveString, Reviver); + Companies = JSON.parse(saveString, Reviver); } // Utility function to check if a string is valid company name export function companyExists(name: string): boolean { - return Companies.hasOwnProperty(name); + return Companies.hasOwnProperty(name); } diff --git a/src/Company/Company.ts b/src/Company/Company.ts index 6bf877a94..2e6483572 100644 --- a/src/Company/Company.ts +++ b/src/Company/Company.ts @@ -4,174 +4,192 @@ import * as posNames from "./data/companypositionnames"; import { CONSTANTS } from "../Constants"; import { IMap } from "../types"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; export interface IConstructorParams { - name: string; - info: string; - companyPositions: IMap; - expMultiplier: number; - salaryMultiplier: number; - jobStatReqOffset: number; + name: string; + info: string; + companyPositions: IMap; + expMultiplier: number; + salaryMultiplier: number; + jobStatReqOffset: number; } const DefaultConstructorParams: IConstructorParams = { - name: "", - info: "", - companyPositions: {}, - expMultiplier: 1, - salaryMultiplier: 1, - jobStatReqOffset: 0, -} + name: "", + info: "", + companyPositions: {}, + expMultiplier: 1, + salaryMultiplier: 1, + jobStatReqOffset: 0, +}; export class Company { + /** + * Company name + */ + name: string; - /** - * Company name - */ - name: string; + /** + * Description and general information about company + */ + info: string; - /** - * Description and general information about company - */ - info: string; + /** + * Object that holds all available positions in this Company. + * Position names are held in keys. + * The values for the keys don't matter, but we'll make them booleans + * + * Must match names of Company Positions, defined in data/companypositionnames.ts + */ + companyPositions: IMap; - /** - * Object that holds all available positions in this Company. - * Position names are held in keys. - * The values for the keys don't matter, but we'll make them booleans - * - * Must match names of Company Positions, defined in data/companypositionnames.ts - */ - companyPositions: IMap; + /** + * Company-specific multiplier for earnings + */ + expMultiplier: number; + salaryMultiplier: number; - /** - * Company-specific multiplier for earnings - */ - expMultiplier: number; - salaryMultiplier: number; + /** + * The additional levels of stats you need to quality for a job + * in this company. + * + * For example, the base stat requirement for an intern position is 1. + * But if a company has a offset of 200, then you would need stat(s) of 201 + */ + jobStatReqOffset: number; - /** - * The additional levels of stats you need to quality for a job - * in this company. - * - * For example, the base stat requirement for an intern position is 1. - * But if a company has a offset of 200, then you would need stat(s) of 201 - */ - jobStatReqOffset: number; + /** + * Properties to track the player's progress in this company + */ + isPlayerEmployed: boolean; + playerReputation: number; + favor: number; + rolloverRep: number; - /** - * Properties to track the player's progress in this company - */ - isPlayerEmployed: boolean; - playerReputation: number; - favor: number; - rolloverRep: number; + constructor(p: IConstructorParams = DefaultConstructorParams) { + this.name = p.name; + this.info = p.info; + this.companyPositions = p.companyPositions; + this.expMultiplier = p.expMultiplier; + this.salaryMultiplier = p.salaryMultiplier; + this.jobStatReqOffset = p.jobStatReqOffset; - constructor(p: IConstructorParams = DefaultConstructorParams) { - this.name = p.name; - this.info = p.info; - this.companyPositions = p.companyPositions; - this.expMultiplier = p.expMultiplier; - this.salaryMultiplier = p.salaryMultiplier; - this.jobStatReqOffset = p.jobStatReqOffset; + this.isPlayerEmployed = false; + this.playerReputation = 1; + this.favor = 0; + this.rolloverRep = 0; + } - this.isPlayerEmployed = false; - this.playerReputation = 1; - this.favor = 0; - this.rolloverRep = 0; + hasPosition(pos: CompanyPosition | string): boolean { + if (pos instanceof CompanyPosition) { + return this.companyPositions[pos.name] != null; + } else { + return this.companyPositions[pos] != null; + } + } + + hasAgentPositions(): boolean { + return this.companyPositions[posNames.AgentCompanyPositions[0]] != null; + } + + hasBusinessConsultantPositions(): boolean { + return ( + this.companyPositions[posNames.BusinessConsultantCompanyPositions[0]] != + null + ); + } + + hasBusinessPositions(): boolean { + return this.companyPositions[posNames.BusinessCompanyPositions[0]] != null; + } + + hasEmployeePositions(): boolean { + return this.companyPositions[posNames.MiscCompanyPositions[1]] != null; + } + + hasITPositions(): boolean { + return this.companyPositions[posNames.ITCompanyPositions[0]] != null; + } + + hasSecurityPositions(): boolean { + return this.companyPositions[posNames.SecurityCompanyPositions[2]] != null; + } + + hasSoftwareConsultantPositions(): boolean { + return ( + this.companyPositions[posNames.SoftwareConsultantCompanyPositions[0]] != + null + ); + } + + hasSoftwarePositions(): boolean { + return this.companyPositions[posNames.SoftwareCompanyPositions[0]] != null; + } + + hasWaiterPositions(): boolean { + return this.companyPositions[posNames.MiscCompanyPositions[0]] != null; + } + + gainFavor(): void { + if (this.favor == null) { + this.favor = 0; + } + if (this.rolloverRep == null) { + this.rolloverRep = 0; + } + const res = this.getFavorGain(); + if (res.length != 2) { + console.error("Invalid result from getFavorGain() function"); + return; } - hasPosition(pos: CompanyPosition | string): boolean { - if (pos instanceof CompanyPosition) { - return (this.companyPositions[pos.name] != null); - } else { - return (this.companyPositions[pos] != null); - } + this.favor += res[0]; + this.rolloverRep = res[1]; + } + + getFavorGain(): number[] { + if (this.favor == null) { + this.favor = 0; } - - hasAgentPositions(): boolean { - return (this.companyPositions[posNames.AgentCompanyPositions[0]] != null); + if (this.rolloverRep == null) { + this.rolloverRep = 0; } - - hasBusinessConsultantPositions(): boolean { - return (this.companyPositions[posNames.BusinessConsultantCompanyPositions[0]] != null); + let favorGain = 0, + rep = this.playerReputation + this.rolloverRep; + let reqdRep = + CONSTANTS.CompanyReputationToFavorBase * + Math.pow(CONSTANTS.CompanyReputationToFavorMult, this.favor); + while (rep > 0) { + if (rep >= reqdRep) { + ++favorGain; + rep -= reqdRep; + } else { + break; + } + reqdRep *= CONSTANTS.FactionReputationToFavorMult; } + return [favorGain, rep]; + } - hasBusinessPositions(): boolean { - return (this.companyPositions[posNames.BusinessCompanyPositions[0]] != null); - } + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Company", this); + } - hasEmployeePositions(): boolean { - return (this.companyPositions[posNames.MiscCompanyPositions[1]] != null); - } - - hasITPositions(): boolean { - return (this.companyPositions[posNames.ITCompanyPositions[0]] != null); - } - - hasSecurityPositions(): boolean { - return (this.companyPositions[posNames.SecurityCompanyPositions[2]] != null); - } - - hasSoftwareConsultantPositions(): boolean { - return (this.companyPositions[posNames.SoftwareConsultantCompanyPositions[0]] != null); - } - - hasSoftwarePositions(): boolean { - return (this.companyPositions[posNames.SoftwareCompanyPositions[0]] != null); - } - - hasWaiterPositions(): boolean { - return (this.companyPositions[posNames.MiscCompanyPositions[0]] != null); - } - - - gainFavor(): void { - if (this.favor == null) { this.favor = 0; } - if (this.rolloverRep == null) { this.rolloverRep = 0; } - const res = this.getFavorGain(); - if (res.length != 2) { - console.error("Invalid result from getFavorGain() function"); - return; - } - - this.favor += res[0]; - this.rolloverRep = res[1]; - } - - getFavorGain(): number[] { - if (this.favor == null) { this.favor = 0; } - if (this.rolloverRep == null) { this.rolloverRep = 0; } - let favorGain = 0, rep = this.playerReputation + this.rolloverRep; - let reqdRep = CONSTANTS.CompanyReputationToFavorBase * - Math.pow(CONSTANTS.CompanyReputationToFavorMult, this.favor); - while(rep > 0) { - if (rep >= reqdRep) { - ++favorGain; - rep -= reqdRep; - } else { - break; - } - reqdRep *= CONSTANTS.FactionReputationToFavorMult; - } - return [favorGain, rep]; - } - - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("Company", this); - } - - /** - * Initiatizes a Company from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Company { - return Generic_fromJSON(Company, value.data); - } + /** + * Initiatizes a Company from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Company { + return Generic_fromJSON(Company, value.data); + } } Reviver.constructors.Company = Company; diff --git a/src/Company/CompanyPosition.ts b/src/Company/CompanyPosition.ts index 1786bd4c6..0c17fe744 100644 --- a/src/Company/CompanyPosition.ts +++ b/src/Company/CompanyPosition.ts @@ -4,179 +4,211 @@ import * as names from "./data/companypositionnames"; /* tslint:disable:completed-docs */ export interface IConstructorParams { - name: string; - nextPosition: string | null; - baseSalary: number; - repMultiplier: number; + name: string; + nextPosition: string | null; + baseSalary: number; + repMultiplier: number; - reqdHacking?: number; - reqdStrength?: number; - reqdDefense?: number; - reqdDexterity?: number; - reqdAgility?: number; - reqdCharisma?: number; - reqdReputation?: number; + reqdHacking?: number; + reqdStrength?: number; + reqdDefense?: number; + reqdDexterity?: number; + reqdAgility?: number; + reqdCharisma?: number; + reqdReputation?: number; - hackingEffectiveness?: number; - strengthEffectiveness?: number; - defenseEffectiveness?: number; - dexterityEffectiveness?: number; - agilityEffectiveness?: number; - charismaEffectiveness?: number; + hackingEffectiveness?: number; + strengthEffectiveness?: number; + defenseEffectiveness?: number; + dexterityEffectiveness?: number; + agilityEffectiveness?: number; + charismaEffectiveness?: number; - hackingExpGain?: number; - strengthExpGain?: number; - defenseExpGain?: number; - dexterityExpGain?: number; - agilityExpGain?: number; - charismaExpGain?: number; + hackingExpGain?: number; + strengthExpGain?: number; + defenseExpGain?: number; + dexterityExpGain?: number; + agilityExpGain?: number; + charismaExpGain?: number; } export class CompanyPosition { - /** - * Position title - */ - name: string; + /** + * Position title + */ + name: string; - /** - * Title of next position to be promoted to - */ - nextPosition: string | null; + /** + * Title of next position to be promoted to + */ + nextPosition: string | null; - /** - * Base salary for this position ($ per 200ms game cycle) - * Will be multiplier by a company-specific multiplier for final salary - */ - baseSalary: number; + /** + * Base salary for this position ($ per 200ms game cycle) + * Will be multiplier by a company-specific multiplier for final salary + */ + baseSalary: number; - /** - * Reputation multiplier - */ - repMultiplier: number; + /** + * Reputation multiplier + */ + repMultiplier: number; - /** - * Required stats to earn this position - */ - requiredAgility: number; - requiredCharisma: number; - requiredDefense: number; - requiredDexterity: number; - requiredHacking: number; - requiredStrength: number; + /** + * Required stats to earn this position + */ + requiredAgility: number; + requiredCharisma: number; + requiredDefense: number; + requiredDexterity: number; + requiredHacking: number; + requiredStrength: number; - /** - * Required company reputation to earn this position - */ - requiredReputation: number; + /** + * Required company reputation to earn this position + */ + requiredReputation: number; - /** - * Effectiveness of each stat time for job performance - */ - hackingEffectiveness: number; - strengthEffectiveness: number; - defenseEffectiveness: number; - dexterityEffectiveness: number; - agilityEffectiveness: number; - charismaEffectiveness: number; + /** + * Effectiveness of each stat time for job performance + */ + hackingEffectiveness: number; + strengthEffectiveness: number; + defenseEffectiveness: number; + dexterityEffectiveness: number; + agilityEffectiveness: number; + charismaEffectiveness: number; - /** - * Experience gain for performing job (per 200ms game cycle) - */ - hackingExpGain: number; - strengthExpGain: number; - defenseExpGain: number; - dexterityExpGain: number; - agilityExpGain: number; - charismaExpGain: number; + /** + * Experience gain for performing job (per 200ms game cycle) + */ + hackingExpGain: number; + strengthExpGain: number; + defenseExpGain: number; + dexterityExpGain: number; + agilityExpGain: number; + charismaExpGain: number; - constructor(p: IConstructorParams) { - this.name = p.name; - this.nextPosition = p.nextPosition; - this.baseSalary = p.baseSalary; - this.repMultiplier = p.repMultiplier; + constructor(p: IConstructorParams) { + this.name = p.name; + this.nextPosition = p.nextPosition; + this.baseSalary = p.baseSalary; + this.repMultiplier = p.repMultiplier; - this.requiredHacking = (p.reqdHacking != null) ? p.reqdHacking : 0; - this.requiredStrength = (p.reqdStrength != null) ? p.reqdStrength : 0; - this.requiredDefense = (p.reqdDefense != null) ? p.reqdDefense : 0; - this.requiredDexterity = (p.reqdDexterity != null) ? p.reqdDexterity : 0; - this.requiredAgility = (p.reqdAgility != null) ? p.reqdAgility : 0; - this.requiredCharisma = (p.reqdCharisma != null) ? p.reqdCharisma : 0; - this.requiredReputation = (p.reqdReputation != null) ? p.reqdReputation : 0; + this.requiredHacking = p.reqdHacking != null ? p.reqdHacking : 0; + this.requiredStrength = p.reqdStrength != null ? p.reqdStrength : 0; + this.requiredDefense = p.reqdDefense != null ? p.reqdDefense : 0; + this.requiredDexterity = p.reqdDexterity != null ? p.reqdDexterity : 0; + this.requiredAgility = p.reqdAgility != null ? p.reqdAgility : 0; + this.requiredCharisma = p.reqdCharisma != null ? p.reqdCharisma : 0; + this.requiredReputation = p.reqdReputation != null ? p.reqdReputation : 0; - this.hackingEffectiveness = (p.hackingEffectiveness != null) ? p.hackingEffectiveness : 0; - this.strengthEffectiveness = (p.strengthEffectiveness != null) ? p.strengthEffectiveness : 0; - this.defenseEffectiveness = (p.defenseEffectiveness != null) ? p.defenseEffectiveness : 0; - this.dexterityEffectiveness = (p.dexterityEffectiveness != null) ? p.dexterityEffectiveness : 0; - this.agilityEffectiveness = (p.agilityEffectiveness != null) ? p.agilityEffectiveness : 0; - this.charismaEffectiveness = (p.charismaEffectiveness != null) ? p.charismaEffectiveness : 0; + this.hackingEffectiveness = + p.hackingEffectiveness != null ? p.hackingEffectiveness : 0; + this.strengthEffectiveness = + p.strengthEffectiveness != null ? p.strengthEffectiveness : 0; + this.defenseEffectiveness = + p.defenseEffectiveness != null ? p.defenseEffectiveness : 0; + this.dexterityEffectiveness = + p.dexterityEffectiveness != null ? p.dexterityEffectiveness : 0; + this.agilityEffectiveness = + p.agilityEffectiveness != null ? p.agilityEffectiveness : 0; + this.charismaEffectiveness = + p.charismaEffectiveness != null ? p.charismaEffectiveness : 0; - if (Math.round(this.hackingEffectiveness + this.strengthEffectiveness + this.defenseEffectiveness + - this.dexterityEffectiveness + this.agilityEffectiveness + this.charismaEffectiveness) !== 100) { - console.error(`CompanyPosition ${this.name} parameters do not sum to 100`); - } - - this.hackingExpGain = (p.hackingExpGain != null) ? p.hackingExpGain : 0; - this.strengthExpGain = (p.strengthExpGain != null) ? p.strengthExpGain : 0; - this.defenseExpGain = (p.defenseExpGain != null) ? p.defenseExpGain : 0; - this.dexterityExpGain = (p.dexterityExpGain != null) ? p.dexterityExpGain : 0; - this.agilityExpGain = (p.agilityExpGain != null) ? p.agilityExpGain : 0; - this.charismaExpGain = (p.charismaExpGain != null) ? p.charismaExpGain : 0; + if ( + Math.round( + this.hackingEffectiveness + + this.strengthEffectiveness + + this.defenseEffectiveness + + this.dexterityEffectiveness + + this.agilityEffectiveness + + this.charismaEffectiveness, + ) !== 100 + ) { + console.error( + `CompanyPosition ${this.name} parameters do not sum to 100`, + ); } - calculateJobPerformance(hack: number, str: number, def: number, dex: number, agi: number, cha: number): number { - const hackRatio: number = this.hackingEffectiveness * hack / CONSTANTS.MaxSkillLevel; - const strRatio: number = this.strengthEffectiveness * str / CONSTANTS.MaxSkillLevel; - const defRatio: number = this.defenseEffectiveness * def / CONSTANTS.MaxSkillLevel; - const dexRatio: number = this.dexterityEffectiveness * dex / CONSTANTS.MaxSkillLevel; - const agiRatio: number = this.agilityEffectiveness * agi / CONSTANTS.MaxSkillLevel; - const chaRatio: number = this.charismaEffectiveness * cha / CONSTANTS.MaxSkillLevel; + this.hackingExpGain = p.hackingExpGain != null ? p.hackingExpGain : 0; + this.strengthExpGain = p.strengthExpGain != null ? p.strengthExpGain : 0; + this.defenseExpGain = p.defenseExpGain != null ? p.defenseExpGain : 0; + this.dexterityExpGain = p.dexterityExpGain != null ? p.dexterityExpGain : 0; + this.agilityExpGain = p.agilityExpGain != null ? p.agilityExpGain : 0; + this.charismaExpGain = p.charismaExpGain != null ? p.charismaExpGain : 0; + } - let reputationGain: number = this.repMultiplier * (hackRatio + strRatio + defRatio + dexRatio + agiRatio + chaRatio) / 100; - if (isNaN(reputationGain)) { - console.error("Company reputation gain calculated to be NaN"); - reputationGain = 0; - } + calculateJobPerformance( + hack: number, + str: number, + def: number, + dex: number, + agi: number, + cha: number, + ): number { + const hackRatio: number = + (this.hackingEffectiveness * hack) / CONSTANTS.MaxSkillLevel; + const strRatio: number = + (this.strengthEffectiveness * str) / CONSTANTS.MaxSkillLevel; + const defRatio: number = + (this.defenseEffectiveness * def) / CONSTANTS.MaxSkillLevel; + const dexRatio: number = + (this.dexterityEffectiveness * dex) / CONSTANTS.MaxSkillLevel; + const agiRatio: number = + (this.agilityEffectiveness * agi) / CONSTANTS.MaxSkillLevel; + const chaRatio: number = + (this.charismaEffectiveness * cha) / CONSTANTS.MaxSkillLevel; - return reputationGain; + let reputationGain: number = + (this.repMultiplier * + (hackRatio + strRatio + defRatio + dexRatio + agiRatio + chaRatio)) / + 100; + if (isNaN(reputationGain)) { + console.error("Company reputation gain calculated to be NaN"); + reputationGain = 0; } - isSoftwareJob(): boolean { - return names.SoftwareCompanyPositions.includes(this.name); - } + return reputationGain; + } - isITJob(): boolean { - return names.ITCompanyPositions.includes(this.name); - } + isSoftwareJob(): boolean { + return names.SoftwareCompanyPositions.includes(this.name); + } - isSecurityEngineerJob(): boolean { - return names.SecurityEngineerCompanyPositions.includes(this.name); - } + isITJob(): boolean { + return names.ITCompanyPositions.includes(this.name); + } - isNetworkEngineerJob(): boolean { - return names.NetworkEngineerCompanyPositions.includes(this.name); - } + isSecurityEngineerJob(): boolean { + return names.SecurityEngineerCompanyPositions.includes(this.name); + } - isBusinessJob(): boolean { - return names.BusinessCompanyPositions.includes(this.name); - } + isNetworkEngineerJob(): boolean { + return names.NetworkEngineerCompanyPositions.includes(this.name); + } - isSecurityJob(): boolean { - return names.SecurityCompanyPositions.includes(this.name); - } + isBusinessJob(): boolean { + return names.BusinessCompanyPositions.includes(this.name); + } - isAgentJob(): boolean { - return names.AgentCompanyPositions.includes(this.name); - } + isSecurityJob(): boolean { + return names.SecurityCompanyPositions.includes(this.name); + } - isSoftwareConsultantJob(): boolean { - return names.SoftwareConsultantCompanyPositions.includes(this.name); - } + isAgentJob(): boolean { + return names.AgentCompanyPositions.includes(this.name); + } - isBusinessConsultantJob(): boolean { - return names.BusinessConsultantCompanyPositions.includes(this.name); - } + isSoftwareConsultantJob(): boolean { + return names.SoftwareConsultantCompanyPositions.includes(this.name); + } - isPartTimeJob(): boolean { - return names.PartTimeCompanyPositions.includes(this.name); - } + isBusinessConsultantJob(): boolean { + return names.BusinessConsultantCompanyPositions.includes(this.name); + } + + isPartTimeJob(): boolean { + return names.PartTimeCompanyPositions.includes(this.name); + } } diff --git a/src/Company/CompanyPositions.ts b/src/Company/CompanyPositions.ts index 097abe2ba..3075808a4 100644 --- a/src/Company/CompanyPositions.ts +++ b/src/Company/CompanyPositions.ts @@ -6,12 +6,12 @@ import { IMap } from "../types"; export const CompanyPositions: IMap = {}; function addCompanyPosition(params: IConstructorParams): void { - if (CompanyPositions[params.name] != null) { - console.warn(`Duplicate Company Position being defined: ${params.name}`); - } - CompanyPositions[params.name] = new CompanyPosition(params); + if (CompanyPositions[params.name] != null) { + console.warn(`Duplicate Company Position being defined: ${params.name}`); + } + CompanyPositions[params.name] = new CompanyPosition(params); } companyPositionMetadata.forEach((e) => { - addCompanyPosition(e); + addCompanyPosition(e); }); diff --git a/src/Company/GetJobRequirementText.ts b/src/Company/GetJobRequirementText.ts index c9da1b541..89e750e42 100644 --- a/src/Company/GetJobRequirementText.ts +++ b/src/Company/GetJobRequirementText.ts @@ -5,36 +5,60 @@ import { CompanyPosition } from "./CompanyPosition"; * Returns a string with the given CompanyPosition's stat requirements */ -export function getJobRequirementText(company: Company, pos: CompanyPosition, tooltiptext = false): string { - let reqText = ""; - const offset: number = company.jobStatReqOffset; - const reqHacking: number = pos.requiredHacking > 0 ? pos.requiredHacking+offset : 0; - const reqStrength: number = pos.requiredStrength > 0 ? pos.requiredStrength+offset : 0; - const reqDefense: number = pos.requiredDefense > 0 ? pos.requiredDefense+offset : 0; - const reqDexterity: number = pos.requiredDexterity > 0 ? pos.requiredDexterity+offset : 0; - const reqAgility: number = pos.requiredDexterity > 0 ? pos.requiredDexterity+offset : 0; - const reqCharisma: number = pos.requiredCharisma > 0 ? pos.requiredCharisma+offset : 0; - const reqRep: number = pos.requiredReputation; - if (tooltiptext) { - reqText = "Requires:
    "; - reqText += (reqHacking.toString() + " hacking
    "); - reqText += (reqStrength.toString() + " strength
    "); - reqText += (reqDefense.toString() + " defense
    "); - reqText += (reqDexterity.toString() + " dexterity
    "); - reqText += (reqAgility.toString() + " agility
    "); - reqText += (reqCharisma.toString() + " charisma
    "); - reqText += (reqRep.toString() + " reputation"); - } else { - reqText = "(Requires "; - if (reqHacking > 0) {reqText += (reqHacking + " hacking, ");} - if (reqStrength > 0) {reqText += (reqStrength + " strength, ");} - if (reqDefense > 0) {reqText += (reqDefense + " defense, ");} - if (reqDexterity > 0) {reqText += (reqDexterity + " dexterity, ");} - if (reqAgility > 0) {reqText += (reqAgility + " agility, ");} - if (reqCharisma > 0) {reqText += (reqCharisma + " charisma, ");} - if (reqRep > 1) {reqText += (reqRep + " reputation, ");} - reqText = reqText.substring(0, reqText.length - 2); - reqText += ")"; +export function getJobRequirementText( + company: Company, + pos: CompanyPosition, + tooltiptext = false, +): string { + let reqText = ""; + const offset: number = company.jobStatReqOffset; + const reqHacking: number = + pos.requiredHacking > 0 ? pos.requiredHacking + offset : 0; + const reqStrength: number = + pos.requiredStrength > 0 ? pos.requiredStrength + offset : 0; + const reqDefense: number = + pos.requiredDefense > 0 ? pos.requiredDefense + offset : 0; + const reqDexterity: number = + pos.requiredDexterity > 0 ? pos.requiredDexterity + offset : 0; + const reqAgility: number = + pos.requiredDexterity > 0 ? pos.requiredDexterity + offset : 0; + const reqCharisma: number = + pos.requiredCharisma > 0 ? pos.requiredCharisma + offset : 0; + const reqRep: number = pos.requiredReputation; + if (tooltiptext) { + reqText = "Requires:
    "; + reqText += reqHacking.toString() + " hacking
    "; + reqText += reqStrength.toString() + " strength
    "; + reqText += reqDefense.toString() + " defense
    "; + reqText += reqDexterity.toString() + " dexterity
    "; + reqText += reqAgility.toString() + " agility
    "; + reqText += reqCharisma.toString() + " charisma
    "; + reqText += reqRep.toString() + " reputation"; + } else { + reqText = "(Requires "; + if (reqHacking > 0) { + reqText += reqHacking + " hacking, "; } - return reqText; + if (reqStrength > 0) { + reqText += reqStrength + " strength, "; + } + if (reqDefense > 0) { + reqText += reqDefense + " defense, "; + } + if (reqDexterity > 0) { + reqText += reqDexterity + " dexterity, "; + } + if (reqAgility > 0) { + reqText += reqAgility + " agility, "; + } + if (reqCharisma > 0) { + reqText += reqCharisma + " charisma, "; + } + if (reqRep > 1) { + reqText += reqRep + " reputation, "; + } + reqText = reqText.substring(0, reqText.length - 2); + reqText += ")"; + } + return reqText; } diff --git a/src/Company/GetNextCompanyPosition.ts b/src/Company/GetNextCompanyPosition.ts index b6ca6118c..485820cb4 100644 --- a/src/Company/GetNextCompanyPosition.ts +++ b/src/Company/GetNextCompanyPosition.ts @@ -3,11 +3,17 @@ import { CompanyPosition } from "./CompanyPosition"; import { CompanyPositions } from "./CompanyPositions"; -export function getNextCompanyPositionHelper(currPos: CompanyPosition | null): CompanyPosition | null { - if (currPos == null) { return null; } +export function getNextCompanyPositionHelper( + currPos: CompanyPosition | null, +): CompanyPosition | null { + if (currPos == null) { + return null; + } - const nextPosName: string | null = currPos.nextPosition; - if (nextPosName == null) { return null; } + const nextPosName: string | null = currPos.nextPosition; + if (nextPosName == null) { + return null; + } - return CompanyPositions[nextPosName]; + return CompanyPositions[nextPosName]; } diff --git a/src/Company/data/CompaniesMetadata.ts b/src/Company/data/CompaniesMetadata.ts index a4d833fec..02ed2b0a5 100644 --- a/src/Company/data/CompaniesMetadata.ts +++ b/src/Company/data/CompaniesMetadata.ts @@ -1,8 +1,8 @@ -import * as posNames from "./companypositionnames"; -import { IConstructorParams } from "../Company"; +import * as posNames from "./companypositionnames"; +import { IConstructorParams } from "../Company"; -import { IMap } from "../../types"; -import { LocationName } from "../../Locations/data/LocationNames"; +import { IMap } from "../../types"; +import { LocationName } from "../../Locations/data/LocationNames"; // Create Objects containing Company Positions by category // Will help in metadata construction later @@ -27,57 +27,57 @@ const OperationsManagerOnly: IMap = {}; const CEOOnly: IMap = {}; posNames.SoftwareCompanyPositions.forEach((e) => { - AllSoftwarePositions[e] = true; - AllTechnologyPositions[e] = true; + AllSoftwarePositions[e] = true; + AllTechnologyPositions[e] = true; }); posNames.ITCompanyPositions.forEach((e) => { - AllITPositions[e] = true; - AllTechnologyPositions[e] = true; + AllITPositions[e] = true; + AllTechnologyPositions[e] = true; }); posNames.NetworkEngineerCompanyPositions.forEach((e) => { - AllNetworkEngineerPositions[e] = true; - AllTechnologyPositions[e] = true; + AllNetworkEngineerPositions[e] = true; + AllTechnologyPositions[e] = true; }); AllTechnologyPositions[posNames.SecurityEngineerCompanyPositions[0]] = true; SecurityEngineerPositions[posNames.SecurityEngineerCompanyPositions[0]] = true; posNames.BusinessCompanyPositions.forEach((e) => { - AllBusinessPositions[e] = true; + AllBusinessPositions[e] = true; }); posNames.SecurityCompanyPositions.forEach((e) => { - AllSecurityPositions[e] = true; + AllSecurityPositions[e] = true; }); posNames.AgentCompanyPositions.forEach((e) => { - AllAgentPositions[e] = true; + AllAgentPositions[e] = true; }); posNames.SoftwareConsultantCompanyPositions.forEach((e) => { - AllSoftwareConsultantPositions[e] = true; + AllSoftwareConsultantPositions[e] = true; }); posNames.BusinessConsultantCompanyPositions.forEach((e) => { - AllBusinessConsultantPositions[e] = true; + AllBusinessConsultantPositions[e] = true; }); for (let i = 0; i < posNames.SoftwareCompanyPositions.length; ++i) { - const e = posNames.SoftwareCompanyPositions[i]; - if (i <= 5) { - SoftwarePositionsUpToHeadOfEngineering[e] = true; - } - if (i <= 3) { - SoftwarePositionsUpToLeadDeveloper[e] = true; - } + const e = posNames.SoftwareCompanyPositions[i]; + if (i <= 5) { + SoftwarePositionsUpToHeadOfEngineering[e] = true; + } + if (i <= 3) { + SoftwarePositionsUpToLeadDeveloper[e] = true; + } } for (let i = 0; i < posNames.BusinessCompanyPositions.length; ++i) { - const e = posNames.BusinessCompanyPositions[i]; - if (i <= 3) { - BusinessPositionsUpToOperationsManager[e] = true; - } + const e = posNames.BusinessCompanyPositions[i]; + if (i <= 3) { + BusinessPositionsUpToOperationsManager[e] = true; + } } WaiterOnly[posNames.MiscCompanyPositions[0]] = true; @@ -89,462 +89,482 @@ CEOOnly[posNames.BusinessCompanyPositions[5]] = true; // Metadata export const companiesMetadata: IConstructorParams[] = [ - { - name: LocationName.AevumECorp, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSecurityPositions, - ), - expMultiplier: 3, - salaryMultiplier: 3, - jobStatReqOffset: 249, - }, - { - name: LocationName.Sector12MegaCorp, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSecurityPositions, - ), - expMultiplier: 3, - salaryMultiplier: 3, - jobStatReqOffset: 249, - }, - { - name: LocationName.AevumBachmanAndAssociates, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSecurityPositions, - ), - expMultiplier: 2.6, - salaryMultiplier: 2.6, - jobStatReqOffset: 224, - }, - { - name: LocationName.Sector12BladeIndustries, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSecurityPositions, - ), - expMultiplier: 2.75, - salaryMultiplier: 2.75, - jobStatReqOffset: 224, - }, - { - name: LocationName.VolhavenNWO, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSecurityPositions, - ), - expMultiplier: 2.75, - salaryMultiplier: 2.75, - jobStatReqOffset: 249, - }, - { - name: LocationName.AevumClarkeIncorporated, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSecurityPositions, - ), - expMultiplier: 2.25, - salaryMultiplier: 2.25, - jobStatReqOffset: 224, - }, - { - name: LocationName.VolhavenOmniTekIncorporated, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSecurityPositions, - ), - expMultiplier: 2.25, - salaryMultiplier: 2.25, - jobStatReqOffset: 224, - }, - { - name: LocationName.Sector12FourSigma, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSecurityPositions, - ), - expMultiplier: 2.5, - salaryMultiplier: 2.5, - jobStatReqOffset: 224, - }, - { - name: LocationName.ChongqingKuaiGongInternational, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSecurityPositions, - ), - expMultiplier: 2.2, - salaryMultiplier: 2.2, - jobStatReqOffset: 224, - }, - { - name: LocationName.AevumFulcrumTechnologies, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - ), - expMultiplier: 2, - salaryMultiplier: 2, - jobStatReqOffset: 224, - }, - { - name: LocationName.IshimaStormTechnologies, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllSoftwareConsultantPositions, - AllBusinessPositions, - ), - expMultiplier: 1.8, - salaryMultiplier: 1.8, - jobStatReqOffset: 199, - }, - { - name: LocationName.NewTokyoDefComm, - info: "", - companyPositions: Object.assign({}, - CEOOnly, - AllTechnologyPositions, - AllSoftwareConsultantPositions, - ), - expMultiplier: 1.75, - salaryMultiplier: 1.75, - jobStatReqOffset: 199, - }, - { - name: LocationName.VolhavenHeliosLabs, - info: "", - companyPositions: Object.assign({}, - CEOOnly, - AllTechnologyPositions, - AllSoftwareConsultantPositions, - ), - expMultiplier: 1.8, - salaryMultiplier: 1.8, - jobStatReqOffset: 199, - }, - { - name: LocationName.NewTokyoVitaLife, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSoftwareConsultantPositions, - ), - expMultiplier: 1.8, - salaryMultiplier: 1.8, - jobStatReqOffset: 199, - }, - { - name: LocationName.Sector12IcarusMicrosystems, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSoftwareConsultantPositions, - ), - expMultiplier: 1.9, - salaryMultiplier: 1.9, - jobStatReqOffset: 199, - }, - { - name: LocationName.Sector12UniversalEnergy, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSoftwareConsultantPositions, - ), - expMultiplier: 2, - salaryMultiplier: 2, - jobStatReqOffset: 199, - }, - { - name: LocationName.AevumGalacticCybersystems, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSoftwareConsultantPositions, - ), - expMultiplier: 1.9, - salaryMultiplier: 1.9, - jobStatReqOffset: 199, - }, - { - name: LocationName.AevumAeroCorp, - info: "", - companyPositions: Object.assign({}, - CEOOnly, - OperationsManagerOnly, - AllTechnologyPositions, - AllSecurityPositions, - ), - expMultiplier: 1.7, - salaryMultiplier: 1.7, - jobStatReqOffset: 199, - }, - { - name: LocationName.VolhavenOmniaCybersystems, - info: "", - companyPositions: Object.assign({}, - CEOOnly, - OperationsManagerOnly, - AllTechnologyPositions, - AllSecurityPositions, - ), - expMultiplier: 1.7, - salaryMultiplier: 1.7, - jobStatReqOffset: 199, - }, - { - name: LocationName.ChongqingSolarisSpaceSystems, - info: "", - companyPositions: Object.assign({}, - CEOOnly, - OperationsManagerOnly, - AllTechnologyPositions, - AllSecurityPositions, - ), - expMultiplier: 1.7, - salaryMultiplier: 1.7, - jobStatReqOffset: 199, - }, - { - name: LocationName.Sector12DeltaOne, - info: "", - companyPositions: Object.assign({}, - CEOOnly, - OperationsManagerOnly, - AllTechnologyPositions, - AllSecurityPositions, - ), - expMultiplier: 1.6, - salaryMultiplier: 1.6, - jobStatReqOffset: 199, - }, - { - name: LocationName.NewTokyoGlobalPharmaceuticals, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSoftwareConsultantPositions, - AllSecurityPositions, - ), - expMultiplier: 1.8, - salaryMultiplier: 1.8, - jobStatReqOffset: 224, - }, - { - name: LocationName.IshimaNovaMedical, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllBusinessPositions, - AllSoftwareConsultantPositions, - AllSecurityPositions, - ), - expMultiplier: 1.75, - salaryMultiplier: 1.75, - jobStatReqOffset: 199, - }, - { - name: LocationName.Sector12CIA, - info: "", - companyPositions: Object.assign({}, - SoftwarePositionsUpToHeadOfEngineering, - AllNetworkEngineerPositions, - SecurityEngineerPositions, - AllITPositions, - AllSecurityPositions, - AllAgentPositions, - ), - expMultiplier: 2, - salaryMultiplier: 2, - jobStatReqOffset: 149, - }, - { - name: LocationName.Sector12NSA, - info: "", - companyPositions: Object.assign({}, - SoftwarePositionsUpToHeadOfEngineering, - AllNetworkEngineerPositions, - SecurityEngineerPositions, - AllITPositions, - AllSecurityPositions, - AllAgentPositions, - ), - expMultiplier: 2, - salaryMultiplier: 2, - jobStatReqOffset: 149, - }, - { - name: LocationName.AevumWatchdogSecurity, - info: "", - companyPositions: Object.assign({}, - SoftwarePositionsUpToHeadOfEngineering, - AllNetworkEngineerPositions, - AllITPositions, - AllSecurityPositions, - AllAgentPositions, - AllSoftwareConsultantPositions, - ), - expMultiplier: 1.5, - salaryMultiplier: 1.5, - jobStatReqOffset: 124, - }, - { - name: LocationName.VolhavenLexoCorp, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllSoftwareConsultantPositions, - AllBusinessPositions, - AllSecurityPositions, - ), - expMultiplier: 1.4, - salaryMultiplier: 1.4, - jobStatReqOffset: 99, - }, - { - name: LocationName.AevumRhoConstruction, - info: "", - companyPositions: Object.assign({}, - SoftwarePositionsUpToLeadDeveloper, - BusinessPositionsUpToOperationsManager, - ), - expMultiplier: 1.3, - salaryMultiplier: 1.3, - jobStatReqOffset: 49, - }, - { - name: LocationName.Sector12AlphaEnterprises, - info: "", - companyPositions: Object.assign({}, - SoftwarePositionsUpToLeadDeveloper, - BusinessPositionsUpToOperationsManager, - AllSoftwareConsultantPositions, - ), - expMultiplier: 1.5, - salaryMultiplier: 1.5, - jobStatReqOffset: 99, - }, - { - name: LocationName.AevumPolice, - info: "", - companyPositions: Object.assign({}, - AllSecurityPositions, - SoftwarePositionsUpToLeadDeveloper, - ), - expMultiplier: 1.3, - salaryMultiplier: 1.3, - jobStatReqOffset: 99, - }, - { - name: LocationName.VolhavenSysCoreSecurities, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - ), - expMultiplier: 1.3, - salaryMultiplier: 1.3, - jobStatReqOffset: 124, - }, - { - name: LocationName.VolhavenCompuTek, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - ), - expMultiplier: 1.2, - salaryMultiplier: 1.2, - jobStatReqOffset: 74, - }, - { - name: LocationName.AevumNetLinkTechnologies, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - ), - expMultiplier: 1.2, - salaryMultiplier: 1.2, - jobStatReqOffset: 99, - }, - { - name: LocationName.Sector12CarmichaelSecurity, - info: "", - companyPositions: Object.assign({}, - AllTechnologyPositions, - AllSoftwareConsultantPositions, - AllAgentPositions, - AllSecurityPositions, - ), - expMultiplier: 1.2, - salaryMultiplier: 1.2, - jobStatReqOffset: 74, - }, - { - name: LocationName.Sector12FoodNStuff, - info: "", - companyPositions: Object.assign({}, - EmployeeOnly, PartTimeEmployeeOnly, - ), - expMultiplier: 1, - salaryMultiplier: 1, - jobStatReqOffset: 0, - }, - { - name: LocationName.Sector12JoesGuns, - info: "", - companyPositions: Object.assign({}, - EmployeeOnly, PartTimeEmployeeOnly, - ), - expMultiplier: 1, - salaryMultiplier: 1, - jobStatReqOffset: 0, - }, - { - name: LocationName.IshimaOmegaSoftware, - info: "", - companyPositions: Object.assign({}, - AllSoftwarePositions, - AllSoftwareConsultantPositions, - AllITPositions, - ), - expMultiplier: 1.1, - salaryMultiplier: 1.1, - jobStatReqOffset: 49, - }, - { - name: LocationName.NewTokyoNoodleBar, - info: "", - companyPositions: Object.assign({}, - WaiterOnly, PartTimeWaiterOnly, - ), - expMultiplier: 1, - salaryMultiplier: 1, - jobStatReqOffset: 0, - }, -] + { + name: LocationName.AevumECorp, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSecurityPositions, + ), + expMultiplier: 3, + salaryMultiplier: 3, + jobStatReqOffset: 249, + }, + { + name: LocationName.Sector12MegaCorp, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSecurityPositions, + ), + expMultiplier: 3, + salaryMultiplier: 3, + jobStatReqOffset: 249, + }, + { + name: LocationName.AevumBachmanAndAssociates, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSecurityPositions, + ), + expMultiplier: 2.6, + salaryMultiplier: 2.6, + jobStatReqOffset: 224, + }, + { + name: LocationName.Sector12BladeIndustries, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSecurityPositions, + ), + expMultiplier: 2.75, + salaryMultiplier: 2.75, + jobStatReqOffset: 224, + }, + { + name: LocationName.VolhavenNWO, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSecurityPositions, + ), + expMultiplier: 2.75, + salaryMultiplier: 2.75, + jobStatReqOffset: 249, + }, + { + name: LocationName.AevumClarkeIncorporated, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSecurityPositions, + ), + expMultiplier: 2.25, + salaryMultiplier: 2.25, + jobStatReqOffset: 224, + }, + { + name: LocationName.VolhavenOmniTekIncorporated, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSecurityPositions, + ), + expMultiplier: 2.25, + salaryMultiplier: 2.25, + jobStatReqOffset: 224, + }, + { + name: LocationName.Sector12FourSigma, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSecurityPositions, + ), + expMultiplier: 2.5, + salaryMultiplier: 2.5, + jobStatReqOffset: 224, + }, + { + name: LocationName.ChongqingKuaiGongInternational, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSecurityPositions, + ), + expMultiplier: 2.2, + salaryMultiplier: 2.2, + jobStatReqOffset: 224, + }, + { + name: LocationName.AevumFulcrumTechnologies, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + ), + expMultiplier: 2, + salaryMultiplier: 2, + jobStatReqOffset: 224, + }, + { + name: LocationName.IshimaStormTechnologies, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllSoftwareConsultantPositions, + AllBusinessPositions, + ), + expMultiplier: 1.8, + salaryMultiplier: 1.8, + jobStatReqOffset: 199, + }, + { + name: LocationName.NewTokyoDefComm, + info: "", + companyPositions: Object.assign( + {}, + CEOOnly, + AllTechnologyPositions, + AllSoftwareConsultantPositions, + ), + expMultiplier: 1.75, + salaryMultiplier: 1.75, + jobStatReqOffset: 199, + }, + { + name: LocationName.VolhavenHeliosLabs, + info: "", + companyPositions: Object.assign( + {}, + CEOOnly, + AllTechnologyPositions, + AllSoftwareConsultantPositions, + ), + expMultiplier: 1.8, + salaryMultiplier: 1.8, + jobStatReqOffset: 199, + }, + { + name: LocationName.NewTokyoVitaLife, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSoftwareConsultantPositions, + ), + expMultiplier: 1.8, + salaryMultiplier: 1.8, + jobStatReqOffset: 199, + }, + { + name: LocationName.Sector12IcarusMicrosystems, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSoftwareConsultantPositions, + ), + expMultiplier: 1.9, + salaryMultiplier: 1.9, + jobStatReqOffset: 199, + }, + { + name: LocationName.Sector12UniversalEnergy, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSoftwareConsultantPositions, + ), + expMultiplier: 2, + salaryMultiplier: 2, + jobStatReqOffset: 199, + }, + { + name: LocationName.AevumGalacticCybersystems, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSoftwareConsultantPositions, + ), + expMultiplier: 1.9, + salaryMultiplier: 1.9, + jobStatReqOffset: 199, + }, + { + name: LocationName.AevumAeroCorp, + info: "", + companyPositions: Object.assign( + {}, + CEOOnly, + OperationsManagerOnly, + AllTechnologyPositions, + AllSecurityPositions, + ), + expMultiplier: 1.7, + salaryMultiplier: 1.7, + jobStatReqOffset: 199, + }, + { + name: LocationName.VolhavenOmniaCybersystems, + info: "", + companyPositions: Object.assign( + {}, + CEOOnly, + OperationsManagerOnly, + AllTechnologyPositions, + AllSecurityPositions, + ), + expMultiplier: 1.7, + salaryMultiplier: 1.7, + jobStatReqOffset: 199, + }, + { + name: LocationName.ChongqingSolarisSpaceSystems, + info: "", + companyPositions: Object.assign( + {}, + CEOOnly, + OperationsManagerOnly, + AllTechnologyPositions, + AllSecurityPositions, + ), + expMultiplier: 1.7, + salaryMultiplier: 1.7, + jobStatReqOffset: 199, + }, + { + name: LocationName.Sector12DeltaOne, + info: "", + companyPositions: Object.assign( + {}, + CEOOnly, + OperationsManagerOnly, + AllTechnologyPositions, + AllSecurityPositions, + ), + expMultiplier: 1.6, + salaryMultiplier: 1.6, + jobStatReqOffset: 199, + }, + { + name: LocationName.NewTokyoGlobalPharmaceuticals, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSoftwareConsultantPositions, + AllSecurityPositions, + ), + expMultiplier: 1.8, + salaryMultiplier: 1.8, + jobStatReqOffset: 224, + }, + { + name: LocationName.IshimaNovaMedical, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllBusinessPositions, + AllSoftwareConsultantPositions, + AllSecurityPositions, + ), + expMultiplier: 1.75, + salaryMultiplier: 1.75, + jobStatReqOffset: 199, + }, + { + name: LocationName.Sector12CIA, + info: "", + companyPositions: Object.assign( + {}, + SoftwarePositionsUpToHeadOfEngineering, + AllNetworkEngineerPositions, + SecurityEngineerPositions, + AllITPositions, + AllSecurityPositions, + AllAgentPositions, + ), + expMultiplier: 2, + salaryMultiplier: 2, + jobStatReqOffset: 149, + }, + { + name: LocationName.Sector12NSA, + info: "", + companyPositions: Object.assign( + {}, + SoftwarePositionsUpToHeadOfEngineering, + AllNetworkEngineerPositions, + SecurityEngineerPositions, + AllITPositions, + AllSecurityPositions, + AllAgentPositions, + ), + expMultiplier: 2, + salaryMultiplier: 2, + jobStatReqOffset: 149, + }, + { + name: LocationName.AevumWatchdogSecurity, + info: "", + companyPositions: Object.assign( + {}, + SoftwarePositionsUpToHeadOfEngineering, + AllNetworkEngineerPositions, + AllITPositions, + AllSecurityPositions, + AllAgentPositions, + AllSoftwareConsultantPositions, + ), + expMultiplier: 1.5, + salaryMultiplier: 1.5, + jobStatReqOffset: 124, + }, + { + name: LocationName.VolhavenLexoCorp, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllSoftwareConsultantPositions, + AllBusinessPositions, + AllSecurityPositions, + ), + expMultiplier: 1.4, + salaryMultiplier: 1.4, + jobStatReqOffset: 99, + }, + { + name: LocationName.AevumRhoConstruction, + info: "", + companyPositions: Object.assign( + {}, + SoftwarePositionsUpToLeadDeveloper, + BusinessPositionsUpToOperationsManager, + ), + expMultiplier: 1.3, + salaryMultiplier: 1.3, + jobStatReqOffset: 49, + }, + { + name: LocationName.Sector12AlphaEnterprises, + info: "", + companyPositions: Object.assign( + {}, + SoftwarePositionsUpToLeadDeveloper, + BusinessPositionsUpToOperationsManager, + AllSoftwareConsultantPositions, + ), + expMultiplier: 1.5, + salaryMultiplier: 1.5, + jobStatReqOffset: 99, + }, + { + name: LocationName.AevumPolice, + info: "", + companyPositions: Object.assign( + {}, + AllSecurityPositions, + SoftwarePositionsUpToLeadDeveloper, + ), + expMultiplier: 1.3, + salaryMultiplier: 1.3, + jobStatReqOffset: 99, + }, + { + name: LocationName.VolhavenSysCoreSecurities, + info: "", + companyPositions: Object.assign({}, AllTechnologyPositions), + expMultiplier: 1.3, + salaryMultiplier: 1.3, + jobStatReqOffset: 124, + }, + { + name: LocationName.VolhavenCompuTek, + info: "", + companyPositions: Object.assign({}, AllTechnologyPositions), + expMultiplier: 1.2, + salaryMultiplier: 1.2, + jobStatReqOffset: 74, + }, + { + name: LocationName.AevumNetLinkTechnologies, + info: "", + companyPositions: Object.assign({}, AllTechnologyPositions), + expMultiplier: 1.2, + salaryMultiplier: 1.2, + jobStatReqOffset: 99, + }, + { + name: LocationName.Sector12CarmichaelSecurity, + info: "", + companyPositions: Object.assign( + {}, + AllTechnologyPositions, + AllSoftwareConsultantPositions, + AllAgentPositions, + AllSecurityPositions, + ), + expMultiplier: 1.2, + salaryMultiplier: 1.2, + jobStatReqOffset: 74, + }, + { + name: LocationName.Sector12FoodNStuff, + info: "", + companyPositions: Object.assign({}, EmployeeOnly, PartTimeEmployeeOnly), + expMultiplier: 1, + salaryMultiplier: 1, + jobStatReqOffset: 0, + }, + { + name: LocationName.Sector12JoesGuns, + info: "", + companyPositions: Object.assign({}, EmployeeOnly, PartTimeEmployeeOnly), + expMultiplier: 1, + salaryMultiplier: 1, + jobStatReqOffset: 0, + }, + { + name: LocationName.IshimaOmegaSoftware, + info: "", + companyPositions: Object.assign( + {}, + AllSoftwarePositions, + AllSoftwareConsultantPositions, + AllITPositions, + ), + expMultiplier: 1.1, + salaryMultiplier: 1.1, + jobStatReqOffset: 49, + }, + { + name: LocationName.NewTokyoNoodleBar, + info: "", + companyPositions: Object.assign({}, WaiterOnly, PartTimeWaiterOnly), + expMultiplier: 1, + salaryMultiplier: 1, + jobStatReqOffset: 0, + }, +]; diff --git a/src/Company/data/CompanyPositionsMetadata.ts b/src/Company/data/CompanyPositionsMetadata.ts index 7276f7978..fa4b6412e 100644 --- a/src/Company/data/CompanyPositionsMetadata.ts +++ b/src/Company/data/CompanyPositionsMetadata.ts @@ -3,600 +3,600 @@ import { IConstructorParams } from "../CompanyPosition"; import * as posNames from "./companypositionnames"; export const companyPositionMetadata: IConstructorParams[] = [ - { - name: posNames.SoftwareCompanyPositions[0], // Software Enginering Intern - nextPosition: posNames.SoftwareCompanyPositions[1], // Junior Software Engineer - baseSalary: 33, - charismaEffectiveness: 15, - charismaExpGain: 0.02, - hackingEffectiveness: 85, - hackingExpGain: 0.05, - reqdHacking: 1, - repMultiplier: 0.9, - }, - { - name: posNames.SoftwareCompanyPositions[1], // Junior Software Engineer - nextPosition: posNames.SoftwareCompanyPositions[2], // Senior Software Engineer - baseSalary: 80, - charismaEffectiveness: 15, - charismaExpGain: 0.05, - hackingEffectiveness: 85, - hackingExpGain: 0.1, - reqdHacking: 51, - reqdReputation: 8e3, - repMultiplier: 1.1, - }, - { - name: posNames.SoftwareCompanyPositions[2], // Senior Software Engineer - nextPosition: posNames.SoftwareCompanyPositions[3], // Lead Software Developer - baseSalary: 165, - charismaEffectiveness: 20, - charismaExpGain: 0.08, - hackingEffectiveness: 80, - hackingExpGain: 0.4, - reqdCharisma: 51, - reqdHacking: 251, - reqdReputation: 40e3, - repMultiplier: 1.3, - }, - { - name: posNames.SoftwareCompanyPositions[3], // Lead Software Developer - nextPosition: posNames.SoftwareCompanyPositions[4], // Head of Software - baseSalary: 500, - charismaEffectiveness: 25, - charismaExpGain: 0.1, - hackingEffectiveness: 75, - hackingExpGain: 0.8, - reqdCharisma: 151, - reqdHacking: 401, - reqdReputation: 200e3, - repMultiplier: 1.5, - }, - { - name: posNames.SoftwareCompanyPositions[4], // Head of Software - nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering - baseSalary: 800, - charismaEffectiveness: 25, - charismaExpGain: 0.5, - hackingEffectiveness: 75, - hackingExpGain: 1, - reqdCharisma: 251, - reqdHacking: 501, - reqdReputation: 400e3, - repMultiplier: 1.6, - }, - { - name: posNames.SoftwareCompanyPositions[5], // Head of Engineering - nextPosition: posNames.SoftwareCompanyPositions[6], // Vice President of Technology - baseSalary: 1650, - charismaEffectiveness: 25, - charismaExpGain: 0.5, - hackingEffectiveness: 75, - hackingExpGain: 1.1, - reqdCharisma: 251, - reqdHacking: 501, - reqdReputation: 800e3, - repMultiplier: 1.6, - }, - { - name: posNames.SoftwareCompanyPositions[6], // Vice President of Technology - nextPosition: posNames.SoftwareCompanyPositions[7], // Chief Technology Officer - baseSalary: 2310, - charismaEffectiveness: 30, - charismaExpGain: 0.6, - hackingEffectiveness: 70, - hackingExpGain: 1.2, - reqdCharisma: 401, - reqdHacking: 601, - reqdReputation: 1.6e6, - repMultiplier: 1.75, - }, - { - name: posNames.SoftwareCompanyPositions[7], // Chief Technology Officer - nextPosition: null, - baseSalary: 2640, - charismaEffectiveness: 35, - charismaExpGain: 1, - hackingEffectiveness: 65, - hackingExpGain: 1.5, - reqdCharisma: 501, - reqdHacking: 751, - reqdReputation: 3.2e6, - repMultiplier: 2, - }, - { - name: posNames.ITCompanyPositions[0], // IT Intern - nextPosition: posNames.ITCompanyPositions[1], // IT Analyst - baseSalary: 26, - charismaEffectiveness: 10, - charismaExpGain: 0.01, - hackingEffectiveness: 90, - hackingExpGain: 0.04, - reqdHacking: 1, - repMultiplier: 0.9, - }, - { - name: posNames.ITCompanyPositions[1], // IT Analyst - nextPosition: posNames.ITCompanyPositions[2], // IT Manager - baseSalary: 66, - charismaEffectiveness: 15, - charismaExpGain: 0.02, - hackingEffectiveness: 85, - hackingExpGain: 0.08, - reqdHacking: 26, - reqdReputation: 7e3, - repMultiplier: 1.1, - }, - { - name: posNames.ITCompanyPositions[2], // IT Manager - nextPosition: posNames.ITCompanyPositions[3], // Systems Administrator - baseSalary: 132, - charismaEffectiveness: 20, - charismaExpGain: 0.1, - hackingEffectiveness: 80, - hackingExpGain: 0.3, - reqdCharisma: 51, - reqdHacking: 151, - reqdReputation: 35e3, - repMultiplier: 1.3, - }, - { - name: posNames.ITCompanyPositions[3], // Systems Administrator - nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering - baseSalary: 410, - charismaEffectiveness: 20, - charismaExpGain: 0.2, - hackingEffectiveness: 80, - hackingExpGain: 0.5, - reqdCharisma: 76, - reqdHacking: 251, - reqdReputation: 175e3, - repMultiplier: 1.4, - }, - { - name: posNames.SecurityEngineerCompanyPositions[0], // Security Engineer - nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering - baseSalary: 121, - charismaEffectiveness: 15, - charismaExpGain: 0.05, - hackingEffectiveness: 85, - hackingExpGain: 0.4, - reqdCharisma: 26, - reqdHacking: 151, - reqdReputation: 35e3, - repMultiplier: 1.2, - }, - { - name: posNames.NetworkEngineerCompanyPositions[0], // Network Engineer - nextPosition: posNames.NetworkEngineerCompanyPositions[1], // Network Adminsitrator - baseSalary: 121, - charismaEffectiveness: 15, - charismaExpGain: 0.05, - hackingEffectiveness: 85, - hackingExpGain: 0.4, - reqdCharisma: 26, - reqdHacking: 151, - reqdReputation: 35e3, - repMultiplier: 1.2, - }, - { - name: posNames.NetworkEngineerCompanyPositions[1], // Network Administrator - nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering - baseSalary: 410, - charismaEffectiveness: 20, - charismaExpGain: 0.1, - hackingEffectiveness: 80, - hackingExpGain: 0.5, - reqdCharisma: 76, - reqdHacking: 251, - reqdReputation: 175e3, - repMultiplier: 1.3, - }, - { - name: posNames.BusinessCompanyPositions[0], // Business Intern - nextPosition: posNames.BusinessCompanyPositions[1], // Business Analyst - baseSalary: 46, - charismaEffectiveness: 90, - charismaExpGain: 0.08, - hackingEffectiveness: 10, - hackingExpGain: 0.01, - reqdCharisma: 1, - reqdHacking: 1, - repMultiplier: 0.9, - }, - { - name: posNames.BusinessCompanyPositions[1], // Business Analyst - nextPosition: posNames.BusinessCompanyPositions[2], // Business Manager - baseSalary: 100, - charismaEffectiveness: 85, - charismaExpGain: 0.15, - hackingEffectiveness: 15, - hackingExpGain: 0.02, - reqdCharisma: 51, - reqdHacking: 6, - reqdReputation: 8e3, - repMultiplier: 1.1, - }, - { - name: posNames.BusinessCompanyPositions[2], // Business Manager - nextPosition: posNames.BusinessCompanyPositions[3], // Operations Manager - baseSalary: 200, - charismaEffectiveness: 85, - charismaExpGain: 0.3, - hackingEffectiveness: 15, - hackingExpGain: 0.02, - reqdCharisma: 101, - reqdHacking: 51, - reqdReputation: 40e3, - repMultiplier: 1.3, - }, - { - name: posNames.BusinessCompanyPositions[3], // Operations Manager - nextPosition: posNames.BusinessCompanyPositions[4], // Chief Financial Officer - baseSalary: 660, - charismaEffectiveness: 85, - charismaExpGain: 0.4, - hackingEffectiveness: 15, - hackingExpGain: 0.02, - reqdCharisma: 226, - reqdHacking: 51, - reqdReputation: 200e3, - repMultiplier: 1.5, - }, - { - name: posNames.BusinessCompanyPositions[4], // Chief Financial Officer - nextPosition: posNames.BusinessCompanyPositions[5], // Chief Executive Officer - baseSalary: 1950, - charismaEffectiveness: 90, - charismaExpGain: 1, - hackingEffectiveness: 10, - hackingExpGain: 0.05, - reqdCharisma: 501, - reqdHacking: 76, - reqdReputation: 800e3, - repMultiplier: 1.6, - }, - { - name: posNames.BusinessCompanyPositions[5], // Chief Executive Officer - nextPosition: null, - baseSalary: 3900, - charismaEffectiveness: 90, - charismaExpGain: 1.5, - hackingEffectiveness: 10, - hackingExpGain: 0.05, - reqdCharisma: 751, - reqdHacking: 101, - reqdReputation: 3.2e6, - repMultiplier: 1.75, - }, - { - name: posNames.SecurityCompanyPositions[0], // Police Officer - nextPosition: posNames.SecurityCompanyPositions[1], // Police Chief - baseSalary: 82, - hackingEffectiveness: 5, - strengthEffectiveness: 20, - defenseEffectiveness: 20, - dexterityEffectiveness: 20, - agilityEffectiveness: 20, - charismaEffectiveness: 15, - hackingExpGain: 0.02, - strengthExpGain: 0.08, - defenseExpGain: 0.08, - dexterityExpGain: 0.08, - agilityExpGain: 0.08, - charismaExpGain: 0.04, - reqdHacking: 11, - reqdStrength: 101, - reqdDefense: 101, - reqdDexterity: 101, - reqdAgility: 101, - reqdCharisma: 51, - reqdReputation: 8e3, - repMultiplier: 1, - }, - { - name: posNames.SecurityCompanyPositions[1], // Police Chief - nextPosition: null, - baseSalary: 460, - hackingEffectiveness: 5, - strengthEffectiveness: 20, - defenseEffectiveness: 20, - dexterityEffectiveness: 20, - agilityEffectiveness: 20, - charismaEffectiveness: 15, - hackingExpGain: 0.02, - strengthExpGain: 0.1, - defenseExpGain: 0.1, - dexterityExpGain: 0.1, - agilityExpGain: 0.1, - charismaExpGain: 0.1, - reqdHacking: 101, - reqdStrength: 301, - reqdDefense: 301, - reqdDexterity: 301, - reqdAgility: 301, - reqdCharisma: 151, - reqdReputation: 36e3, - repMultiplier: 1.25, - }, - { - name: posNames.SecurityCompanyPositions[2], // Security Guard - nextPosition: posNames.SecurityCompanyPositions[3], // Security Officer - baseSalary: 50, - hackingEffectiveness: 5, - strengthEffectiveness: 20, - defenseEffectiveness: 20, - dexterityEffectiveness: 20, - agilityEffectiveness: 20, - charismaEffectiveness: 15, - hackingExpGain: 0.01, - strengthExpGain: 0.04, - defenseExpGain: 0.04, - dexterityExpGain: 0.04, - agilityExpGain: 0.04, - charismaExpGain: 0.02, - reqdStrength: 51, - reqdDefense: 51, - reqdDexterity: 51, - reqdAgility: 51, - reqdCharisma: 1, - repMultiplier: 1, - }, - { - name: posNames.SecurityCompanyPositions[3], // Security Officer - nextPosition: posNames.SecurityCompanyPositions[4], // Security Supervisor - baseSalary: 195, - hackingEffectiveness: 10, - strengthEffectiveness: 20, - defenseEffectiveness: 20, - dexterityEffectiveness: 20, - agilityEffectiveness: 20, - charismaEffectiveness: 10, - hackingExpGain: 0.02, - strengthExpGain: 0.1, - defenseExpGain: 0.1, - dexterityExpGain: 0.1, - agilityExpGain: 0.1, - charismaExpGain: 0.05, - reqdHacking: 26, - reqdStrength: 151, - reqdDefense: 151, - reqdDexterity: 151, - reqdAgility: 151, - reqdCharisma: 51, - reqdReputation: 8e3, - repMultiplier: 1.1, - }, - { - name: posNames.SecurityCompanyPositions[4], // Security Supervisor - nextPosition: posNames.SecurityCompanyPositions[5], // Head of Security - baseSalary: 660, - hackingEffectiveness: 10, - strengthEffectiveness: 15, - defenseEffectiveness: 15, - dexterityEffectiveness: 15, - agilityEffectiveness: 15, - charismaEffectiveness: 30, - hackingExpGain: 0.02, - strengthExpGain: 0.12, - defenseExpGain: 0.12, - dexterityExpGain: 0.12, - agilityExpGain: 0.12, - charismaExpGain: 0.1, - reqdHacking: 26, - reqdStrength: 251, - reqdDefense: 251, - reqdDexterity: 251, - reqdAgility: 251, - reqdCharisma: 101, - reqdReputation: 36e3, - repMultiplier: 1.25, - }, - { - name: posNames.SecurityCompanyPositions[5], // Head of Security - nextPosition: null, - baseSalary: 1320, - hackingEffectiveness: 10, - strengthEffectiveness: 15, - defenseEffectiveness: 15, - dexterityEffectiveness: 15, - agilityEffectiveness: 15, - charismaEffectiveness: 30, - hackingExpGain: 0.05, - strengthExpGain: 0.15, - defenseExpGain: 0.15, - dexterityExpGain: 0.15, - agilityExpGain: 0.15, - charismaExpGain: 0.15, - reqdHacking: 51, - reqdStrength: 501, - reqdDefense: 501, - reqdDexterity: 501, - reqdAgility: 501, - reqdCharisma: 151, - reqdReputation: 144e3, - repMultiplier: 1.4, - }, - { - name: posNames.AgentCompanyPositions[0], // Field Agent - nextPosition: posNames.AgentCompanyPositions[1], // Secret Agent - baseSalary: 330, - hackingEffectiveness: 10, - strengthEffectiveness: 15, - defenseEffectiveness: 15, - dexterityEffectiveness: 20, - agilityEffectiveness: 20, - charismaEffectiveness: 20, - hackingExpGain: 0.04, - strengthExpGain: 0.08, - defenseExpGain: 0.08, - dexterityExpGain: 0.08, - agilityExpGain: 0.08, - charismaExpGain: 0.05, - reqdHacking: 101, - reqdStrength: 101, - reqdDefense: 101, - reqdDexterity: 101, - reqdAgility: 101, - reqdCharisma: 101, - reqdReputation: 8e3, - repMultiplier: 1, - }, - { - name: posNames.AgentCompanyPositions[1], // Secret Agent - nextPosition: posNames.AgentCompanyPositions[2], // Special Operative - baseSalary: 990, - hackingEffectiveness: 15, - strengthEffectiveness: 15, - defenseEffectiveness: 15, - dexterityEffectiveness: 20, - agilityEffectiveness: 20, - charismaEffectiveness: 15, - hackingExpGain: 0.1, - strengthExpGain: 0.15, - defenseExpGain: 0.15, - dexterityExpGain: 0.15, - agilityExpGain: 0.15, - charismaExpGain: 0.1, - reqdHacking: 201, - reqdStrength: 251, - reqdDefense: 251, - reqdDexterity: 251, - reqdAgility: 251, - reqdCharisma: 201, - reqdReputation: 32e3, - repMultiplier: 1.25, - }, - { - name: posNames.AgentCompanyPositions[2], // Special Operative - nextPosition: null, - baseSalary: 2000, - hackingEffectiveness: 15, - strengthEffectiveness: 15, - defenseEffectiveness: 15, - dexterityEffectiveness: 20, - agilityEffectiveness: 20, - charismaEffectiveness: 15, - hackingExpGain: 0.15, - strengthExpGain: 0.2, - defenseExpGain: 0.2, - dexterityExpGain: 0.2, - agilityExpGain: 0.2, - charismaExpGain: 0.15, - reqdHacking: 251, - reqdStrength: 501, - reqdDefense: 501, - reqdDexterity: 501, - reqdAgility: 501, - reqdCharisma: 251, - reqdReputation: 162e3, - repMultiplier: 1.5, - }, - { - name: posNames.MiscCompanyPositions[0], // Waiter - nextPosition: null, - baseSalary: 22, - strengthEffectiveness: 10, - dexterityEffectiveness: 10, - agilityEffectiveness: 10, - charismaEffectiveness: 70, - strengthExpGain: 0.02, - defenseExpGain: 0.02, - dexterityExpGain: 0.02, - agilityExpGain: 0.02, - charismaExpGain: 0.05, - repMultiplier: 1, - }, - { - name: posNames.MiscCompanyPositions[1], // Employee - nextPosition: null, - baseSalary: 22, - strengthEffectiveness: 10, - dexterityEffectiveness: 10, - agilityEffectiveness: 10, - charismaEffectiveness: 70, - strengthExpGain: 0.02, - defenseExpGain: 0.02, - dexterityExpGain: 0.02, - agilityExpGain: 0.02, - charismaExpGain: 0.04, - repMultiplier: 1, - }, - { - name: posNames.SoftwareConsultantCompanyPositions[0], // Software Consultant - nextPosition: posNames.SoftwareConsultantCompanyPositions[1], // Senior Software Consultant - baseSalary: 66, - hackingEffectiveness: 80, - charismaEffectiveness: 20, - hackingExpGain: 0.08, - charismaExpGain: 0.03, - reqdHacking: 51, - repMultiplier: 1, - }, - { - name: posNames.SoftwareConsultantCompanyPositions[1], // Senior Software Consultant - nextPosition: null, - baseSalary: 132, - hackingEffectiveness: 75, - charismaEffectiveness: 25, - hackingExpGain: 0.25, - charismaExpGain: 0.06, - reqdHacking: 251, - reqdCharisma: 51, - repMultiplier: 1.2, - }, - { - name: posNames.BusinessConsultantCompanyPositions[0], // Business Consultant - nextPosition: posNames.BusinessConsultantCompanyPositions[1], // Senior Business Consultant - baseSalary: 66, - hackingEffectiveness: 20, - charismaEffectiveness: 80, - hackingExpGain: 0.015, - charismaExpGain: 0.15, - reqdHacking: 6, - reqdCharisma: 51, - repMultiplier: 1, - }, - { - name: posNames.BusinessConsultantCompanyPositions[1], // Senior Business Consultant - nextPosition: null, - baseSalary: 525, - hackingEffectiveness: 15, - charismaEffectiveness: 85, - hackingExpGain: 0.015, - charismaExpGain: 0.3, - reqdHacking: 51, - reqdCharisma: 226, - repMultiplier: 1.2, - }, - { - name: posNames.PartTimeCompanyPositions[0], // Part-time waiter - nextPosition: null, - baseSalary: 20, - strengthEffectiveness: 10, - dexterityEffectiveness: 10, - agilityEffectiveness: 10, - charismaEffectiveness: 70, - strengthExpGain: 0.0075, - defenseExpGain: 0.0075, - dexterityExpGain: 0.0075, - agilityExpGain: 0.0075, - charismaExpGain: 0.04, - repMultiplier: 1, - }, - { - name: posNames.PartTimeCompanyPositions[1], // Part-time employee - nextPosition: null, - baseSalary: 20, - strengthEffectiveness: 10, - dexterityEffectiveness: 10, - agilityEffectiveness: 10, - charismaEffectiveness: 70, - strengthExpGain: 0.0075, - defenseExpGain: 0.0075, - dexterityExpGain: 0.0075, - agilityExpGain: 0.0075, - charismaExpGain: 0.03, - repMultiplier: 1, - }, -] + { + name: posNames.SoftwareCompanyPositions[0], // Software Enginering Intern + nextPosition: posNames.SoftwareCompanyPositions[1], // Junior Software Engineer + baseSalary: 33, + charismaEffectiveness: 15, + charismaExpGain: 0.02, + hackingEffectiveness: 85, + hackingExpGain: 0.05, + reqdHacking: 1, + repMultiplier: 0.9, + }, + { + name: posNames.SoftwareCompanyPositions[1], // Junior Software Engineer + nextPosition: posNames.SoftwareCompanyPositions[2], // Senior Software Engineer + baseSalary: 80, + charismaEffectiveness: 15, + charismaExpGain: 0.05, + hackingEffectiveness: 85, + hackingExpGain: 0.1, + reqdHacking: 51, + reqdReputation: 8e3, + repMultiplier: 1.1, + }, + { + name: posNames.SoftwareCompanyPositions[2], // Senior Software Engineer + nextPosition: posNames.SoftwareCompanyPositions[3], // Lead Software Developer + baseSalary: 165, + charismaEffectiveness: 20, + charismaExpGain: 0.08, + hackingEffectiveness: 80, + hackingExpGain: 0.4, + reqdCharisma: 51, + reqdHacking: 251, + reqdReputation: 40e3, + repMultiplier: 1.3, + }, + { + name: posNames.SoftwareCompanyPositions[3], // Lead Software Developer + nextPosition: posNames.SoftwareCompanyPositions[4], // Head of Software + baseSalary: 500, + charismaEffectiveness: 25, + charismaExpGain: 0.1, + hackingEffectiveness: 75, + hackingExpGain: 0.8, + reqdCharisma: 151, + reqdHacking: 401, + reqdReputation: 200e3, + repMultiplier: 1.5, + }, + { + name: posNames.SoftwareCompanyPositions[4], // Head of Software + nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering + baseSalary: 800, + charismaEffectiveness: 25, + charismaExpGain: 0.5, + hackingEffectiveness: 75, + hackingExpGain: 1, + reqdCharisma: 251, + reqdHacking: 501, + reqdReputation: 400e3, + repMultiplier: 1.6, + }, + { + name: posNames.SoftwareCompanyPositions[5], // Head of Engineering + nextPosition: posNames.SoftwareCompanyPositions[6], // Vice President of Technology + baseSalary: 1650, + charismaEffectiveness: 25, + charismaExpGain: 0.5, + hackingEffectiveness: 75, + hackingExpGain: 1.1, + reqdCharisma: 251, + reqdHacking: 501, + reqdReputation: 800e3, + repMultiplier: 1.6, + }, + { + name: posNames.SoftwareCompanyPositions[6], // Vice President of Technology + nextPosition: posNames.SoftwareCompanyPositions[7], // Chief Technology Officer + baseSalary: 2310, + charismaEffectiveness: 30, + charismaExpGain: 0.6, + hackingEffectiveness: 70, + hackingExpGain: 1.2, + reqdCharisma: 401, + reqdHacking: 601, + reqdReputation: 1.6e6, + repMultiplier: 1.75, + }, + { + name: posNames.SoftwareCompanyPositions[7], // Chief Technology Officer + nextPosition: null, + baseSalary: 2640, + charismaEffectiveness: 35, + charismaExpGain: 1, + hackingEffectiveness: 65, + hackingExpGain: 1.5, + reqdCharisma: 501, + reqdHacking: 751, + reqdReputation: 3.2e6, + repMultiplier: 2, + }, + { + name: posNames.ITCompanyPositions[0], // IT Intern + nextPosition: posNames.ITCompanyPositions[1], // IT Analyst + baseSalary: 26, + charismaEffectiveness: 10, + charismaExpGain: 0.01, + hackingEffectiveness: 90, + hackingExpGain: 0.04, + reqdHacking: 1, + repMultiplier: 0.9, + }, + { + name: posNames.ITCompanyPositions[1], // IT Analyst + nextPosition: posNames.ITCompanyPositions[2], // IT Manager + baseSalary: 66, + charismaEffectiveness: 15, + charismaExpGain: 0.02, + hackingEffectiveness: 85, + hackingExpGain: 0.08, + reqdHacking: 26, + reqdReputation: 7e3, + repMultiplier: 1.1, + }, + { + name: posNames.ITCompanyPositions[2], // IT Manager + nextPosition: posNames.ITCompanyPositions[3], // Systems Administrator + baseSalary: 132, + charismaEffectiveness: 20, + charismaExpGain: 0.1, + hackingEffectiveness: 80, + hackingExpGain: 0.3, + reqdCharisma: 51, + reqdHacking: 151, + reqdReputation: 35e3, + repMultiplier: 1.3, + }, + { + name: posNames.ITCompanyPositions[3], // Systems Administrator + nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering + baseSalary: 410, + charismaEffectiveness: 20, + charismaExpGain: 0.2, + hackingEffectiveness: 80, + hackingExpGain: 0.5, + reqdCharisma: 76, + reqdHacking: 251, + reqdReputation: 175e3, + repMultiplier: 1.4, + }, + { + name: posNames.SecurityEngineerCompanyPositions[0], // Security Engineer + nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering + baseSalary: 121, + charismaEffectiveness: 15, + charismaExpGain: 0.05, + hackingEffectiveness: 85, + hackingExpGain: 0.4, + reqdCharisma: 26, + reqdHacking: 151, + reqdReputation: 35e3, + repMultiplier: 1.2, + }, + { + name: posNames.NetworkEngineerCompanyPositions[0], // Network Engineer + nextPosition: posNames.NetworkEngineerCompanyPositions[1], // Network Adminsitrator + baseSalary: 121, + charismaEffectiveness: 15, + charismaExpGain: 0.05, + hackingEffectiveness: 85, + hackingExpGain: 0.4, + reqdCharisma: 26, + reqdHacking: 151, + reqdReputation: 35e3, + repMultiplier: 1.2, + }, + { + name: posNames.NetworkEngineerCompanyPositions[1], // Network Administrator + nextPosition: posNames.SoftwareCompanyPositions[5], // Head of Engineering + baseSalary: 410, + charismaEffectiveness: 20, + charismaExpGain: 0.1, + hackingEffectiveness: 80, + hackingExpGain: 0.5, + reqdCharisma: 76, + reqdHacking: 251, + reqdReputation: 175e3, + repMultiplier: 1.3, + }, + { + name: posNames.BusinessCompanyPositions[0], // Business Intern + nextPosition: posNames.BusinessCompanyPositions[1], // Business Analyst + baseSalary: 46, + charismaEffectiveness: 90, + charismaExpGain: 0.08, + hackingEffectiveness: 10, + hackingExpGain: 0.01, + reqdCharisma: 1, + reqdHacking: 1, + repMultiplier: 0.9, + }, + { + name: posNames.BusinessCompanyPositions[1], // Business Analyst + nextPosition: posNames.BusinessCompanyPositions[2], // Business Manager + baseSalary: 100, + charismaEffectiveness: 85, + charismaExpGain: 0.15, + hackingEffectiveness: 15, + hackingExpGain: 0.02, + reqdCharisma: 51, + reqdHacking: 6, + reqdReputation: 8e3, + repMultiplier: 1.1, + }, + { + name: posNames.BusinessCompanyPositions[2], // Business Manager + nextPosition: posNames.BusinessCompanyPositions[3], // Operations Manager + baseSalary: 200, + charismaEffectiveness: 85, + charismaExpGain: 0.3, + hackingEffectiveness: 15, + hackingExpGain: 0.02, + reqdCharisma: 101, + reqdHacking: 51, + reqdReputation: 40e3, + repMultiplier: 1.3, + }, + { + name: posNames.BusinessCompanyPositions[3], // Operations Manager + nextPosition: posNames.BusinessCompanyPositions[4], // Chief Financial Officer + baseSalary: 660, + charismaEffectiveness: 85, + charismaExpGain: 0.4, + hackingEffectiveness: 15, + hackingExpGain: 0.02, + reqdCharisma: 226, + reqdHacking: 51, + reqdReputation: 200e3, + repMultiplier: 1.5, + }, + { + name: posNames.BusinessCompanyPositions[4], // Chief Financial Officer + nextPosition: posNames.BusinessCompanyPositions[5], // Chief Executive Officer + baseSalary: 1950, + charismaEffectiveness: 90, + charismaExpGain: 1, + hackingEffectiveness: 10, + hackingExpGain: 0.05, + reqdCharisma: 501, + reqdHacking: 76, + reqdReputation: 800e3, + repMultiplier: 1.6, + }, + { + name: posNames.BusinessCompanyPositions[5], // Chief Executive Officer + nextPosition: null, + baseSalary: 3900, + charismaEffectiveness: 90, + charismaExpGain: 1.5, + hackingEffectiveness: 10, + hackingExpGain: 0.05, + reqdCharisma: 751, + reqdHacking: 101, + reqdReputation: 3.2e6, + repMultiplier: 1.75, + }, + { + name: posNames.SecurityCompanyPositions[0], // Police Officer + nextPosition: posNames.SecurityCompanyPositions[1], // Police Chief + baseSalary: 82, + hackingEffectiveness: 5, + strengthEffectiveness: 20, + defenseEffectiveness: 20, + dexterityEffectiveness: 20, + agilityEffectiveness: 20, + charismaEffectiveness: 15, + hackingExpGain: 0.02, + strengthExpGain: 0.08, + defenseExpGain: 0.08, + dexterityExpGain: 0.08, + agilityExpGain: 0.08, + charismaExpGain: 0.04, + reqdHacking: 11, + reqdStrength: 101, + reqdDefense: 101, + reqdDexterity: 101, + reqdAgility: 101, + reqdCharisma: 51, + reqdReputation: 8e3, + repMultiplier: 1, + }, + { + name: posNames.SecurityCompanyPositions[1], // Police Chief + nextPosition: null, + baseSalary: 460, + hackingEffectiveness: 5, + strengthEffectiveness: 20, + defenseEffectiveness: 20, + dexterityEffectiveness: 20, + agilityEffectiveness: 20, + charismaEffectiveness: 15, + hackingExpGain: 0.02, + strengthExpGain: 0.1, + defenseExpGain: 0.1, + dexterityExpGain: 0.1, + agilityExpGain: 0.1, + charismaExpGain: 0.1, + reqdHacking: 101, + reqdStrength: 301, + reqdDefense: 301, + reqdDexterity: 301, + reqdAgility: 301, + reqdCharisma: 151, + reqdReputation: 36e3, + repMultiplier: 1.25, + }, + { + name: posNames.SecurityCompanyPositions[2], // Security Guard + nextPosition: posNames.SecurityCompanyPositions[3], // Security Officer + baseSalary: 50, + hackingEffectiveness: 5, + strengthEffectiveness: 20, + defenseEffectiveness: 20, + dexterityEffectiveness: 20, + agilityEffectiveness: 20, + charismaEffectiveness: 15, + hackingExpGain: 0.01, + strengthExpGain: 0.04, + defenseExpGain: 0.04, + dexterityExpGain: 0.04, + agilityExpGain: 0.04, + charismaExpGain: 0.02, + reqdStrength: 51, + reqdDefense: 51, + reqdDexterity: 51, + reqdAgility: 51, + reqdCharisma: 1, + repMultiplier: 1, + }, + { + name: posNames.SecurityCompanyPositions[3], // Security Officer + nextPosition: posNames.SecurityCompanyPositions[4], // Security Supervisor + baseSalary: 195, + hackingEffectiveness: 10, + strengthEffectiveness: 20, + defenseEffectiveness: 20, + dexterityEffectiveness: 20, + agilityEffectiveness: 20, + charismaEffectiveness: 10, + hackingExpGain: 0.02, + strengthExpGain: 0.1, + defenseExpGain: 0.1, + dexterityExpGain: 0.1, + agilityExpGain: 0.1, + charismaExpGain: 0.05, + reqdHacking: 26, + reqdStrength: 151, + reqdDefense: 151, + reqdDexterity: 151, + reqdAgility: 151, + reqdCharisma: 51, + reqdReputation: 8e3, + repMultiplier: 1.1, + }, + { + name: posNames.SecurityCompanyPositions[4], // Security Supervisor + nextPosition: posNames.SecurityCompanyPositions[5], // Head of Security + baseSalary: 660, + hackingEffectiveness: 10, + strengthEffectiveness: 15, + defenseEffectiveness: 15, + dexterityEffectiveness: 15, + agilityEffectiveness: 15, + charismaEffectiveness: 30, + hackingExpGain: 0.02, + strengthExpGain: 0.12, + defenseExpGain: 0.12, + dexterityExpGain: 0.12, + agilityExpGain: 0.12, + charismaExpGain: 0.1, + reqdHacking: 26, + reqdStrength: 251, + reqdDefense: 251, + reqdDexterity: 251, + reqdAgility: 251, + reqdCharisma: 101, + reqdReputation: 36e3, + repMultiplier: 1.25, + }, + { + name: posNames.SecurityCompanyPositions[5], // Head of Security + nextPosition: null, + baseSalary: 1320, + hackingEffectiveness: 10, + strengthEffectiveness: 15, + defenseEffectiveness: 15, + dexterityEffectiveness: 15, + agilityEffectiveness: 15, + charismaEffectiveness: 30, + hackingExpGain: 0.05, + strengthExpGain: 0.15, + defenseExpGain: 0.15, + dexterityExpGain: 0.15, + agilityExpGain: 0.15, + charismaExpGain: 0.15, + reqdHacking: 51, + reqdStrength: 501, + reqdDefense: 501, + reqdDexterity: 501, + reqdAgility: 501, + reqdCharisma: 151, + reqdReputation: 144e3, + repMultiplier: 1.4, + }, + { + name: posNames.AgentCompanyPositions[0], // Field Agent + nextPosition: posNames.AgentCompanyPositions[1], // Secret Agent + baseSalary: 330, + hackingEffectiveness: 10, + strengthEffectiveness: 15, + defenseEffectiveness: 15, + dexterityEffectiveness: 20, + agilityEffectiveness: 20, + charismaEffectiveness: 20, + hackingExpGain: 0.04, + strengthExpGain: 0.08, + defenseExpGain: 0.08, + dexterityExpGain: 0.08, + agilityExpGain: 0.08, + charismaExpGain: 0.05, + reqdHacking: 101, + reqdStrength: 101, + reqdDefense: 101, + reqdDexterity: 101, + reqdAgility: 101, + reqdCharisma: 101, + reqdReputation: 8e3, + repMultiplier: 1, + }, + { + name: posNames.AgentCompanyPositions[1], // Secret Agent + nextPosition: posNames.AgentCompanyPositions[2], // Special Operative + baseSalary: 990, + hackingEffectiveness: 15, + strengthEffectiveness: 15, + defenseEffectiveness: 15, + dexterityEffectiveness: 20, + agilityEffectiveness: 20, + charismaEffectiveness: 15, + hackingExpGain: 0.1, + strengthExpGain: 0.15, + defenseExpGain: 0.15, + dexterityExpGain: 0.15, + agilityExpGain: 0.15, + charismaExpGain: 0.1, + reqdHacking: 201, + reqdStrength: 251, + reqdDefense: 251, + reqdDexterity: 251, + reqdAgility: 251, + reqdCharisma: 201, + reqdReputation: 32e3, + repMultiplier: 1.25, + }, + { + name: posNames.AgentCompanyPositions[2], // Special Operative + nextPosition: null, + baseSalary: 2000, + hackingEffectiveness: 15, + strengthEffectiveness: 15, + defenseEffectiveness: 15, + dexterityEffectiveness: 20, + agilityEffectiveness: 20, + charismaEffectiveness: 15, + hackingExpGain: 0.15, + strengthExpGain: 0.2, + defenseExpGain: 0.2, + dexterityExpGain: 0.2, + agilityExpGain: 0.2, + charismaExpGain: 0.15, + reqdHacking: 251, + reqdStrength: 501, + reqdDefense: 501, + reqdDexterity: 501, + reqdAgility: 501, + reqdCharisma: 251, + reqdReputation: 162e3, + repMultiplier: 1.5, + }, + { + name: posNames.MiscCompanyPositions[0], // Waiter + nextPosition: null, + baseSalary: 22, + strengthEffectiveness: 10, + dexterityEffectiveness: 10, + agilityEffectiveness: 10, + charismaEffectiveness: 70, + strengthExpGain: 0.02, + defenseExpGain: 0.02, + dexterityExpGain: 0.02, + agilityExpGain: 0.02, + charismaExpGain: 0.05, + repMultiplier: 1, + }, + { + name: posNames.MiscCompanyPositions[1], // Employee + nextPosition: null, + baseSalary: 22, + strengthEffectiveness: 10, + dexterityEffectiveness: 10, + agilityEffectiveness: 10, + charismaEffectiveness: 70, + strengthExpGain: 0.02, + defenseExpGain: 0.02, + dexterityExpGain: 0.02, + agilityExpGain: 0.02, + charismaExpGain: 0.04, + repMultiplier: 1, + }, + { + name: posNames.SoftwareConsultantCompanyPositions[0], // Software Consultant + nextPosition: posNames.SoftwareConsultantCompanyPositions[1], // Senior Software Consultant + baseSalary: 66, + hackingEffectiveness: 80, + charismaEffectiveness: 20, + hackingExpGain: 0.08, + charismaExpGain: 0.03, + reqdHacking: 51, + repMultiplier: 1, + }, + { + name: posNames.SoftwareConsultantCompanyPositions[1], // Senior Software Consultant + nextPosition: null, + baseSalary: 132, + hackingEffectiveness: 75, + charismaEffectiveness: 25, + hackingExpGain: 0.25, + charismaExpGain: 0.06, + reqdHacking: 251, + reqdCharisma: 51, + repMultiplier: 1.2, + }, + { + name: posNames.BusinessConsultantCompanyPositions[0], // Business Consultant + nextPosition: posNames.BusinessConsultantCompanyPositions[1], // Senior Business Consultant + baseSalary: 66, + hackingEffectiveness: 20, + charismaEffectiveness: 80, + hackingExpGain: 0.015, + charismaExpGain: 0.15, + reqdHacking: 6, + reqdCharisma: 51, + repMultiplier: 1, + }, + { + name: posNames.BusinessConsultantCompanyPositions[1], // Senior Business Consultant + nextPosition: null, + baseSalary: 525, + hackingEffectiveness: 15, + charismaEffectiveness: 85, + hackingExpGain: 0.015, + charismaExpGain: 0.3, + reqdHacking: 51, + reqdCharisma: 226, + repMultiplier: 1.2, + }, + { + name: posNames.PartTimeCompanyPositions[0], // Part-time waiter + nextPosition: null, + baseSalary: 20, + strengthEffectiveness: 10, + dexterityEffectiveness: 10, + agilityEffectiveness: 10, + charismaEffectiveness: 70, + strengthExpGain: 0.0075, + defenseExpGain: 0.0075, + dexterityExpGain: 0.0075, + agilityExpGain: 0.0075, + charismaExpGain: 0.04, + repMultiplier: 1, + }, + { + name: posNames.PartTimeCompanyPositions[1], // Part-time employee + nextPosition: null, + baseSalary: 20, + strengthEffectiveness: 10, + dexterityEffectiveness: 10, + agilityEffectiveness: 10, + charismaEffectiveness: 70, + strengthExpGain: 0.0075, + defenseExpGain: 0.0075, + dexterityExpGain: 0.0075, + agilityExpGain: 0.0075, + charismaExpGain: 0.03, + repMultiplier: 1, + }, +]; diff --git a/src/Company/data/companypositionnames.ts b/src/Company/data/companypositionnames.ts index c1d4a46b3..6a5b1fe19 100644 --- a/src/Company/data/companypositionnames.ts +++ b/src/Company/data/companypositionnames.ts @@ -1,72 +1,67 @@ // Defs for job titles, stored in arrays and categorized by job "type" export const SoftwareCompanyPositions: string[] = [ - "Software Engineering Intern", - "Junior Software Engineer", - "Senior Software Engineer", - "Lead Software Developer", - "Head of Software", - "Head of Engineering", - "Vice President of Technology", - "Chief Technology Officer", + "Software Engineering Intern", + "Junior Software Engineer", + "Senior Software Engineer", + "Lead Software Developer", + "Head of Software", + "Head of Engineering", + "Vice President of Technology", + "Chief Technology Officer", ]; export const ITCompanyPositions: string[] = [ - "IT Intern", - "IT Analyst", - "IT Manager", - "Systems Administrator", + "IT Intern", + "IT Analyst", + "IT Manager", + "Systems Administrator", ]; -export const SecurityEngineerCompanyPositions: string[] = [ - "Security Engineer", -]; +export const SecurityEngineerCompanyPositions: string[] = ["Security Engineer"]; export const NetworkEngineerCompanyPositions: string[] = [ - "Network Engineer", - "Network Administrator", + "Network Engineer", + "Network Administrator", ]; export const BusinessCompanyPositions: string[] = [ - "Business Intern", - "Business Analyst", - "Business Manager", - "Operations Manager", - "Chief Financial Officer", - "Chief Executive Officer", + "Business Intern", + "Business Analyst", + "Business Manager", + "Operations Manager", + "Chief Financial Officer", + "Chief Executive Officer", ]; export const SecurityCompanyPositions: string[] = [ - "Police Officer", - "Police Chief", - "Security Guard", - "Security Officer", - "Security Supervisor", - "Head of Security", + "Police Officer", + "Police Chief", + "Security Guard", + "Security Officer", + "Security Supervisor", + "Head of Security", ]; export const AgentCompanyPositions: string[] = [ - "Field Agent", - "Secret Agent", - "Special Operative", + "Field Agent", + "Secret Agent", + "Special Operative", ]; -export const MiscCompanyPositions: string[] = [ - "Waiter", - "Employee", -]; +export const MiscCompanyPositions: string[] = ["Waiter", "Employee"]; export const SoftwareConsultantCompanyPositions: string[] = [ - "Software Consultant", - "Senior Software Consultant", + "Software Consultant", + "Senior Software Consultant", ]; export const BusinessConsultantCompanyPositions: string[] = [ - "Business Consultant", - "Senior Business Consultant", + "Business Consultant", + "Senior Business Consultant", ]; export const PartTimeCompanyPositions: string[] = [ - "Part-time Waiter", - "Part-time Employee", + "Part-time Waiter", + "Part-time Employee", ]; diff --git a/src/Constants.ts b/src/Constants.ts index 2bfd69a97..08f6bf0e8 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -6,345 +6,346 @@ import { IMap } from "./types"; export const CONSTANTS: { - Version: string; - _idleSpeed: number; - MaxSkillLevel: number; - MilliPerCycle: number; - CorpFactionRepRequirement: number; - BaseCostFor1GBOfRamHome: number; - BaseCostFor1GBOfRamServer: number; - TravelCost: number; - BaseFavorToDonate: number; - DonateMoneyToRepDivisor: number; - FactionReputationToFavorBase: number; - FactionReputationToFavorMult: number; - CompanyReputationToFavorBase: number; - CompanyReputationToFavorMult: number; - NeuroFluxGovernorLevelMult: number; - NumNetscriptPorts: number; - HomeComputerMaxRam: number; - ServerBaseGrowthRate: number; - ServerMaxGrowthRate: number; - ServerFortifyAmount: number; - ServerWeakenAmount: number; - PurchasedServerLimit: number; - PurchasedServerMaxRam: number; - MultipleAugMultiplier: number; - TorRouterCost: number; - InfiltrationBribeBaseAmount: number; - InfiltrationMoneyValue: number; - InfiltrationRepValue: number; - InfiltrationExpPow: number; - WSEAccountCost: number; - TIXAPICost: number; - MarketData4SCost: number; - MarketDataTixApi4SCost: number; - StockMarketCommission: number; - HospitalCostPerHp: number; - IntelligenceCrimeWeight: number; - IntelligenceInfiltrationWeight: number; - IntelligenceCrimeBaseExpGain: number; - IntelligenceProgramBaseExpGain: number; - IntelligenceTerminalHackBaseExpGain: number; - IntelligenceSingFnBaseExpGain: number; - IntelligenceClassBaseExpGain: number; - IntelligenceHackingMissionBaseExpGain: number; - HackingMissionRepToDiffConversion: number; - HackingMissionRepToRewardConversion: number; - HackingMissionSpamTimeIncrease: number; - HackingMissionTransferAttackIncrease: number; - HackingMissionMiscDefenseIncrease: number; - HackingMissionDifficultyToHacking: number; - HackingMissionHowToPlay: string; - MillisecondsPer20Hours: number; - GameCyclesPer20Hours: number; - MillisecondsPer10Hours: number; - GameCyclesPer10Hours: number; - MillisecondsPer8Hours: number; - GameCyclesPer8Hours: number; - MillisecondsPer4Hours: number; - GameCyclesPer4Hours: number; - MillisecondsPer2Hours: number; - GameCyclesPer2Hours: number; - MillisecondsPerHour: number; - GameCyclesPerHour: number; - MillisecondsPerHalfHour: number; - GameCyclesPerHalfHour: number; - MillisecondsPerQuarterHour: number; - GameCyclesPerQuarterHour: number; - MillisecondsPerFiveMinutes: number; - GameCyclesPerFiveMinutes: number; - FactionWorkHacking: string; - FactionWorkField: string; - FactionWorkSecurity: string; - WorkTypeCompany: string; - WorkTypeCompanyPartTime: string; - WorkTypeFaction: string; - WorkTypeCreateProgram: string; - WorkTypeStudyClass: string; - WorkTypeCrime: string; - ClassStudyComputerScience: string; - ClassDataStructures: string; - ClassNetworks: string; - ClassAlgorithms: string; - ClassManagement: string; - ClassLeadership: string; - ClassGymStrength: string; - ClassGymDefense: string; - ClassGymDexterity: string; - ClassGymAgility: string; - ClassDataStructuresBaseCost: number; - ClassNetworksBaseCost: number; - ClassAlgorithmsBaseCost: number; - ClassManagementBaseCost: number; - ClassLeadershipBaseCost: number; - ClassGymBaseCost: number; - ClassStudyComputerScienceBaseExp: number; - ClassDataStructuresBaseExp: number; - ClassNetworksBaseExp: number; - ClassAlgorithmsBaseExp: number; - ClassManagementBaseExp: number; - ClassLeadershipBaseExp: number; - CrimeShoplift: string; - CrimeRobStore: string; - CrimeMug: string; - CrimeLarceny: string; - CrimeDrugs: string; - CrimeBondForgery: string; - CrimeTraffickArms: string; - CrimeHomicide: string; - CrimeGrandTheftAuto: string; - CrimeKidnap: string; - CrimeAssassination: string; - CrimeHeist: string; - CodingContractBaseFactionRepGain: number; - CodingContractBaseCompanyRepGain: number; - CodingContractBaseMoneyGain: number; - TotalNumBitNodes: number; - LatestUpdate: string; + Version: string; + _idleSpeed: number; + MaxSkillLevel: number; + MilliPerCycle: number; + CorpFactionRepRequirement: number; + BaseCostFor1GBOfRamHome: number; + BaseCostFor1GBOfRamServer: number; + TravelCost: number; + BaseFavorToDonate: number; + DonateMoneyToRepDivisor: number; + FactionReputationToFavorBase: number; + FactionReputationToFavorMult: number; + CompanyReputationToFavorBase: number; + CompanyReputationToFavorMult: number; + NeuroFluxGovernorLevelMult: number; + NumNetscriptPorts: number; + HomeComputerMaxRam: number; + ServerBaseGrowthRate: number; + ServerMaxGrowthRate: number; + ServerFortifyAmount: number; + ServerWeakenAmount: number; + PurchasedServerLimit: number; + PurchasedServerMaxRam: number; + MultipleAugMultiplier: number; + TorRouterCost: number; + InfiltrationBribeBaseAmount: number; + InfiltrationMoneyValue: number; + InfiltrationRepValue: number; + InfiltrationExpPow: number; + WSEAccountCost: number; + TIXAPICost: number; + MarketData4SCost: number; + MarketDataTixApi4SCost: number; + StockMarketCommission: number; + HospitalCostPerHp: number; + IntelligenceCrimeWeight: number; + IntelligenceInfiltrationWeight: number; + IntelligenceCrimeBaseExpGain: number; + IntelligenceProgramBaseExpGain: number; + IntelligenceTerminalHackBaseExpGain: number; + IntelligenceSingFnBaseExpGain: number; + IntelligenceClassBaseExpGain: number; + IntelligenceHackingMissionBaseExpGain: number; + HackingMissionRepToDiffConversion: number; + HackingMissionRepToRewardConversion: number; + HackingMissionSpamTimeIncrease: number; + HackingMissionTransferAttackIncrease: number; + HackingMissionMiscDefenseIncrease: number; + HackingMissionDifficultyToHacking: number; + HackingMissionHowToPlay: string; + MillisecondsPer20Hours: number; + GameCyclesPer20Hours: number; + MillisecondsPer10Hours: number; + GameCyclesPer10Hours: number; + MillisecondsPer8Hours: number; + GameCyclesPer8Hours: number; + MillisecondsPer4Hours: number; + GameCyclesPer4Hours: number; + MillisecondsPer2Hours: number; + GameCyclesPer2Hours: number; + MillisecondsPerHour: number; + GameCyclesPerHour: number; + MillisecondsPerHalfHour: number; + GameCyclesPerHalfHour: number; + MillisecondsPerQuarterHour: number; + GameCyclesPerQuarterHour: number; + MillisecondsPerFiveMinutes: number; + GameCyclesPerFiveMinutes: number; + FactionWorkHacking: string; + FactionWorkField: string; + FactionWorkSecurity: string; + WorkTypeCompany: string; + WorkTypeCompanyPartTime: string; + WorkTypeFaction: string; + WorkTypeCreateProgram: string; + WorkTypeStudyClass: string; + WorkTypeCrime: string; + ClassStudyComputerScience: string; + ClassDataStructures: string; + ClassNetworks: string; + ClassAlgorithms: string; + ClassManagement: string; + ClassLeadership: string; + ClassGymStrength: string; + ClassGymDefense: string; + ClassGymDexterity: string; + ClassGymAgility: string; + ClassDataStructuresBaseCost: number; + ClassNetworksBaseCost: number; + ClassAlgorithmsBaseCost: number; + ClassManagementBaseCost: number; + ClassLeadershipBaseCost: number; + ClassGymBaseCost: number; + ClassStudyComputerScienceBaseExp: number; + ClassDataStructuresBaseExp: number; + ClassNetworksBaseExp: number; + ClassAlgorithmsBaseExp: number; + ClassManagementBaseExp: number; + ClassLeadershipBaseExp: number; + CrimeShoplift: string; + CrimeRobStore: string; + CrimeMug: string; + CrimeLarceny: string; + CrimeDrugs: string; + CrimeBondForgery: string; + CrimeTraffickArms: string; + CrimeHomicide: string; + CrimeGrandTheftAuto: string; + CrimeKidnap: string; + CrimeAssassination: string; + CrimeHeist: string; + CodingContractBaseFactionRepGain: number; + CodingContractBaseCompanyRepGain: number; + CodingContractBaseMoneyGain: number; + TotalNumBitNodes: number; + LatestUpdate: string; } = { - Version: "0.52.9", + Version: "0.52.9", - // Speed (in ms) at which the main loop is updated - _idleSpeed: 200, + // Speed (in ms) at which the main loop is updated + _idleSpeed: 200, - /** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience - * and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then - * the player will have this level assuming no multipliers. Multipliers can cause skills to go above this. - */ - MaxSkillLevel: 975, + /** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience + * and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then + * the player will have this level assuming no multipliers. Multipliers can cause skills to go above this. + */ + MaxSkillLevel: 975, - // Milliseconds per game cycle - MilliPerCycle: 200, + // Milliseconds per game cycle + MilliPerCycle: 200, - // How much reputation is needed to join a megacorporation's faction - CorpFactionRepRequirement: 200e3, + // How much reputation is needed to join a megacorporation's faction + CorpFactionRepRequirement: 200e3, - // Base RAM costs - BaseCostFor1GBOfRamHome: 32000, - BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM + // Base RAM costs + BaseCostFor1GBOfRamHome: 32000, + BaseCostFor1GBOfRamServer: 55000, //1 GB of RAM - // Cost to travel to another city - TravelCost: 200e3, + // Cost to travel to another city + TravelCost: 200e3, - // Faction and Company favor-related things - BaseFavorToDonate: 150, - DonateMoneyToRepDivisor: 1e6, - FactionReputationToFavorBase: 500, - FactionReputationToFavorMult: 1.02, - CompanyReputationToFavorBase: 500, - CompanyReputationToFavorMult: 1.02, + // Faction and Company favor-related things + BaseFavorToDonate: 150, + DonateMoneyToRepDivisor: 1e6, + FactionReputationToFavorBase: 500, + FactionReputationToFavorMult: 1.02, + CompanyReputationToFavorBase: 500, + CompanyReputationToFavorMult: 1.02, - // NeuroFlux Governor Augmentation cost multiplier - NeuroFluxGovernorLevelMult: 1.14, + // NeuroFlux Governor Augmentation cost multiplier + NeuroFluxGovernorLevelMult: 1.14, - NumNetscriptPorts: 20, + NumNetscriptPorts: 20, - // Server-related constants - HomeComputerMaxRam: 1073741824, // 2 ^ 30 - ServerBaseGrowthRate: 1.03, // Unadjusted Growth rate - ServerMaxGrowthRate: 1.0035, // Maximum possible growth rate (max rate accounting for server security) - ServerFortifyAmount: 0.002, // Amount by which server's security increases when its hacked/grown - ServerWeakenAmount: 0.05, // Amount by which server's security decreases when weakened + // Server-related constants + HomeComputerMaxRam: 1073741824, // 2 ^ 30 + ServerBaseGrowthRate: 1.03, // Unadjusted Growth rate + ServerMaxGrowthRate: 1.0035, // Maximum possible growth rate (max rate accounting for server security) + ServerFortifyAmount: 0.002, // Amount by which server's security increases when its hacked/grown + ServerWeakenAmount: 0.05, // Amount by which server's security decreases when weakened - PurchasedServerLimit: 25, - PurchasedServerMaxRam: 1048576, // 2^20 + PurchasedServerLimit: 25, + PurchasedServerMaxRam: 1048576, // 2^20 - // Augmentation Constants - MultipleAugMultiplier: 1.9, + // Augmentation Constants + MultipleAugMultiplier: 1.9, - // TOR Router - TorRouterCost: 200e3, + // TOR Router + TorRouterCost: 200e3, - // Infiltration - InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level - InfiltrationMoneyValue: 5e3, //Convert "secret" value to money - InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation - InfiltrationExpPow: 0.8, + // Infiltration + InfiltrationBribeBaseAmount: 100e3, //Amount per clearance level + InfiltrationMoneyValue: 5e3, //Convert "secret" value to money + InfiltrationRepValue: 1.4, //Convert "secret" value to faction reputation + InfiltrationExpPow: 0.8, - // Stock market - WSEAccountCost: 200e6, - TIXAPICost: 5e9, - MarketData4SCost: 1e9, - MarketDataTixApi4SCost: 25e9, - StockMarketCommission: 100e3, + // Stock market + WSEAccountCost: 200e6, + TIXAPICost: 5e9, + MarketData4SCost: 1e9, + MarketDataTixApi4SCost: 25e9, + StockMarketCommission: 100e3, - // Hospital/Health - HospitalCostPerHp: 100e3, + // Hospital/Health + HospitalCostPerHp: 100e3, - // Intelligence-related constants - IntelligenceCrimeWeight: 0.025, // Weight for how much int affects crime success rates - IntelligenceInfiltrationWeight: 0.1, // Weight for how much int affects infiltration success rates - IntelligenceCrimeBaseExpGain: 0.05, - IntelligenceProgramBaseExpGain: 2.5, // Program required hack level divided by this to determine int exp gain - IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain - IntelligenceSingFnBaseExpGain: 1.5, - IntelligenceClassBaseExpGain: 0.01, - IntelligenceHackingMissionBaseExpGain: 3, // Hacking Mission difficulty multiplied by this to get exp gain + // Intelligence-related constants + IntelligenceCrimeWeight: 0.025, // Weight for how much int affects crime success rates + IntelligenceInfiltrationWeight: 0.1, // Weight for how much int affects infiltration success rates + IntelligenceCrimeBaseExpGain: 0.05, + IntelligenceProgramBaseExpGain: 2.5, // Program required hack level divided by this to determine int exp gain + IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain + IntelligenceSingFnBaseExpGain: 1.5, + IntelligenceClassBaseExpGain: 0.01, + IntelligenceHackingMissionBaseExpGain: 3, // Hacking Mission difficulty multiplied by this to get exp gain - // Hacking Missions - // TODO Move this into Hacking Mission implementation - HackingMissionRepToDiffConversion: 10000, // Faction rep is divided by this to get mission difficulty - HackingMissionRepToRewardConversion: 7, // Faction rep divided byt his to get mission rep reward - HackingMissionSpamTimeIncrease: 25000, // How much time limit increase is gained when conquering a Spam Node (ms) - HackingMissionTransferAttackIncrease: 1.05, // Multiplier by which the attack for all Core Nodes is increased when conquering a Transfer Node - HackingMissionMiscDefenseIncrease: 1.05, // The amount by which every misc node's defense is multiplied when one is conquered - HackingMissionDifficultyToHacking: 135, // Difficulty is multiplied by this to determine enemy's "hacking" level (to determine effects of scan/attack, etc) - HackingMissionHowToPlay: "Hacking missions are a minigame that, if won, will reward you with faction reputation.

    " + - "In this game you control a set of Nodes and use them to try and defeat an enemy. Your Nodes " + - "are colored blue, while the enemy's are red. There are also other nodes on the map colored gray " + - "that initially belong to neither you nor the enemy. The goal of the game is " + - "to capture all of the enemy's Database nodes within the time limit. " + - "If you fail to do this, you will lose.

    " + - "Each Node has three stats: Attack, Defense, and HP. There are five different actions that " + - "a Node can take:

    " + - "Attack - Targets an enemy Node and lowers its HP. The effectiveness is determined by the owner's Attack, the Player's " + - "hacking level, and the enemy's defense.

    " + - "Scan - Targets an enemy Node and lowers its Defense. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the " + - "enemy's defense.

    " + - "Weaken - Targets an enemy Node and lowers its Attack. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's " + - "defense.

    " + - "Fortify - Raises the Node's Defense. The effectiveness is determined by your hacking level.

    " + - "Overflow - Raises the Node's Attack but lowers its Defense. The effectiveness is determined by your hacking level.

    " + - "Note that when determining the effectiveness of the above actions, the TOTAL Attack or Defense of the team is used, not just the " + - "Attack/Defense of the individual Node that is performing the action.

    " + - "To capture a Node, you must lower its HP down to 0.

    " + - "There are six different types of Nodes:

    " + - "CPU Core - These are your main Nodes that are used to perform actions. Capable of performing every action

    " + - "Firewall - Nodes with high defense. These Nodes can 'Fortify'

    " + - "Database - A special type of Node. The player's objective is to conquer all of the enemy's Database Nodes within " + - "the time limit. These Nodes cannot perform any actions

    " + - "Spam - Conquering one of these Nodes will slow the enemy's trace, giving the player additional time to complete " + - "the mission. These Nodes cannot perform any actions

    " + - "Transfer - Conquering one of these nodes will increase the Attack of all of your CPU Cores by a small fixed percentage. " + - "These Nodes are capable of performing every action except the 'Attack' action

    " + - "Shield - Nodes with high defense. These Nodes can 'Fortify'

    " + - "To assign an action to a Node, you must first select one of your Nodes. This can be done by simply clicking on it. Double-clicking " + - "a node will select all of your Nodes of the same type (e.g. select all CPU Core Nodes or all Transfer Nodes). Note that only Nodes " + - "that can perform actions (CPU Core, Transfer, Shield, Firewall) can be selected. Selected Nodes will be denoted with a white highlight. After selecting a Node or multiple Nodes, " + - "select its action using the Action Buttons near the top of the screen. Every action also has a corresponding keyboard " + - "shortcut.

    " + - "For certain actions such as attacking, scanning, and weakening, the Node performing the action must have a target. To target " + - "another node, simply click-and-drag from the 'source' Node to a target. A Node can only have one target, and you can target " + - "any Node that is adjacent to one of your Nodes (immediately above, below, or to the side. NOT diagonal). Furthermore, only CPU Cores and Transfer Nodes " + - "can target, since they are the only ones that can perform the related actions. To remove a target, you can simply click on the line that represents " + - "the connection between one of your Nodes and its target. Alternatively, you can select the 'source' Node and click the 'Drop Connection' button, " + - "or press 'd'.

    " + - "Other Notes:

    " + - "-Whenever a miscellenaous Node (not owned by the player or enemy) is conquered, the defense of all remaining miscellaneous Nodes that " + - "are not actively being targeted will increase by a fixed percentage.

    " + - "-Whenever a Node is conquered, its stats are significantly reduced

    " + - "-Miscellaneous Nodes slowly raise their defense over time

    " + - "-Nodes slowly regenerate health over time.", + // Hacking Missions + // TODO Move this into Hacking Mission implementation + HackingMissionRepToDiffConversion: 10000, // Faction rep is divided by this to get mission difficulty + HackingMissionRepToRewardConversion: 7, // Faction rep divided byt his to get mission rep reward + HackingMissionSpamTimeIncrease: 25000, // How much time limit increase is gained when conquering a Spam Node (ms) + HackingMissionTransferAttackIncrease: 1.05, // Multiplier by which the attack for all Core Nodes is increased when conquering a Transfer Node + HackingMissionMiscDefenseIncrease: 1.05, // The amount by which every misc node's defense is multiplied when one is conquered + HackingMissionDifficultyToHacking: 135, // Difficulty is multiplied by this to determine enemy's "hacking" level (to determine effects of scan/attack, etc) + HackingMissionHowToPlay: + "Hacking missions are a minigame that, if won, will reward you with faction reputation.

    " + + "In this game you control a set of Nodes and use them to try and defeat an enemy. Your Nodes " + + "are colored blue, while the enemy's are red. There are also other nodes on the map colored gray " + + "that initially belong to neither you nor the enemy. The goal of the game is " + + "to capture all of the enemy's Database nodes within the time limit. " + + "If you fail to do this, you will lose.

    " + + "Each Node has three stats: Attack, Defense, and HP. There are five different actions that " + + "a Node can take:

    " + + "Attack - Targets an enemy Node and lowers its HP. The effectiveness is determined by the owner's Attack, the Player's " + + "hacking level, and the enemy's defense.

    " + + "Scan - Targets an enemy Node and lowers its Defense. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the " + + "enemy's defense.

    " + + "Weaken - Targets an enemy Node and lowers its Attack. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's " + + "defense.

    " + + "Fortify - Raises the Node's Defense. The effectiveness is determined by your hacking level.

    " + + "Overflow - Raises the Node's Attack but lowers its Defense. The effectiveness is determined by your hacking level.

    " + + "Note that when determining the effectiveness of the above actions, the TOTAL Attack or Defense of the team is used, not just the " + + "Attack/Defense of the individual Node that is performing the action.

    " + + "To capture a Node, you must lower its HP down to 0.

    " + + "There are six different types of Nodes:

    " + + "CPU Core - These are your main Nodes that are used to perform actions. Capable of performing every action

    " + + "Firewall - Nodes with high defense. These Nodes can 'Fortify'

    " + + "Database - A special type of Node. The player's objective is to conquer all of the enemy's Database Nodes within " + + "the time limit. These Nodes cannot perform any actions

    " + + "Spam - Conquering one of these Nodes will slow the enemy's trace, giving the player additional time to complete " + + "the mission. These Nodes cannot perform any actions

    " + + "Transfer - Conquering one of these nodes will increase the Attack of all of your CPU Cores by a small fixed percentage. " + + "These Nodes are capable of performing every action except the 'Attack' action

    " + + "Shield - Nodes with high defense. These Nodes can 'Fortify'

    " + + "To assign an action to a Node, you must first select one of your Nodes. This can be done by simply clicking on it. Double-clicking " + + "a node will select all of your Nodes of the same type (e.g. select all CPU Core Nodes or all Transfer Nodes). Note that only Nodes " + + "that can perform actions (CPU Core, Transfer, Shield, Firewall) can be selected. Selected Nodes will be denoted with a white highlight. After selecting a Node or multiple Nodes, " + + "select its action using the Action Buttons near the top of the screen. Every action also has a corresponding keyboard " + + "shortcut.

    " + + "For certain actions such as attacking, scanning, and weakening, the Node performing the action must have a target. To target " + + "another node, simply click-and-drag from the 'source' Node to a target. A Node can only have one target, and you can target " + + "any Node that is adjacent to one of your Nodes (immediately above, below, or to the side. NOT diagonal). Furthermore, only CPU Cores and Transfer Nodes " + + "can target, since they are the only ones that can perform the related actions. To remove a target, you can simply click on the line that represents " + + "the connection between one of your Nodes and its target. Alternatively, you can select the 'source' Node and click the 'Drop Connection' button, " + + "or press 'd'.

    " + + "Other Notes:

    " + + "-Whenever a miscellenaous Node (not owned by the player or enemy) is conquered, the defense of all remaining miscellaneous Nodes that " + + "are not actively being targeted will increase by a fixed percentage.

    " + + "-Whenever a Node is conquered, its stats are significantly reduced

    " + + "-Miscellaneous Nodes slowly raise their defense over time

    " + + "-Nodes slowly regenerate health over time.", - // Time-related constants - MillisecondsPer20Hours: 72000000, - GameCyclesPer20Hours: 72000000 / 200, + // Time-related constants + MillisecondsPer20Hours: 72000000, + GameCyclesPer20Hours: 72000000 / 200, - MillisecondsPer10Hours: 36000000, - GameCyclesPer10Hours: 36000000 / 200, + MillisecondsPer10Hours: 36000000, + GameCyclesPer10Hours: 36000000 / 200, - MillisecondsPer8Hours: 28800000, - GameCyclesPer8Hours: 28800000 / 200, + MillisecondsPer8Hours: 28800000, + GameCyclesPer8Hours: 28800000 / 200, - MillisecondsPer4Hours: 14400000, - GameCyclesPer4Hours: 14400000 / 200, + MillisecondsPer4Hours: 14400000, + GameCyclesPer4Hours: 14400000 / 200, - MillisecondsPer2Hours: 7200000, - GameCyclesPer2Hours: 7200000 / 200, + MillisecondsPer2Hours: 7200000, + GameCyclesPer2Hours: 7200000 / 200, - MillisecondsPerHour: 3600000, - GameCyclesPerHour: 3600000 / 200, + MillisecondsPerHour: 3600000, + GameCyclesPerHour: 3600000 / 200, - MillisecondsPerHalfHour: 1800000, - GameCyclesPerHalfHour: 1800000 / 200, + MillisecondsPerHalfHour: 1800000, + GameCyclesPerHalfHour: 1800000 / 200, - MillisecondsPerQuarterHour: 900000, - GameCyclesPerQuarterHour: 900000 / 200, + MillisecondsPerQuarterHour: 900000, + GameCyclesPerQuarterHour: 900000 / 200, - MillisecondsPerFiveMinutes: 300000, - GameCyclesPerFiveMinutes: 300000 / 200, + MillisecondsPerFiveMinutes: 300000, + GameCyclesPerFiveMinutes: 300000 / 200, - // Player Work & Action - FactionWorkHacking: "Faction Hacking Work", - FactionWorkField: "Faction Field Work", - FactionWorkSecurity: "Faction Security Work", + // Player Work & Action + FactionWorkHacking: "Faction Hacking Work", + FactionWorkField: "Faction Field Work", + FactionWorkSecurity: "Faction Security Work", - WorkTypeCompany: "Working for Company", - WorkTypeCompanyPartTime: "Working for Company part-time", - WorkTypeFaction: "Working for Faction", - WorkTypeCreateProgram: "Working on Create a Program", - WorkTypeStudyClass: "Studying or Taking a class at university", - WorkTypeCrime: "Committing a crime", + WorkTypeCompany: "Working for Company", + WorkTypeCompanyPartTime: "Working for Company part-time", + WorkTypeFaction: "Working for Faction", + WorkTypeCreateProgram: "Working on Create a Program", + WorkTypeStudyClass: "Studying or Taking a class at university", + WorkTypeCrime: "Committing a crime", - ClassStudyComputerScience: "studying Computer Science", - ClassDataStructures: "taking a Data Structures course", - ClassNetworks: "taking a Networks course", - ClassAlgorithms: "taking an Algorithms course", - ClassManagement: "taking a Management course", - ClassLeadership: "taking a Leadership course", - ClassGymStrength: "training your strength at a gym", - ClassGymDefense: "training your defense at a gym", - ClassGymDexterity: "training your dexterity at a gym", - ClassGymAgility: "training your agility at a gym", + ClassStudyComputerScience: "studying Computer Science", + ClassDataStructures: "taking a Data Structures course", + ClassNetworks: "taking a Networks course", + ClassAlgorithms: "taking an Algorithms course", + ClassManagement: "taking a Management course", + ClassLeadership: "taking a Leadership course", + ClassGymStrength: "training your strength at a gym", + ClassGymDefense: "training your defense at a gym", + ClassGymDexterity: "training your dexterity at a gym", + ClassGymAgility: "training your agility at a gym", - ClassDataStructuresBaseCost: 40, - ClassNetworksBaseCost: 80, - ClassAlgorithmsBaseCost: 320, - ClassManagementBaseCost: 160, - ClassLeadershipBaseCost: 320, - ClassGymBaseCost: 120, + ClassDataStructuresBaseCost: 40, + ClassNetworksBaseCost: 80, + ClassAlgorithmsBaseCost: 320, + ClassManagementBaseCost: 160, + ClassLeadershipBaseCost: 320, + ClassGymBaseCost: 120, - ClassStudyComputerScienceBaseExp: 0.5, - ClassDataStructuresBaseExp: 1, - ClassNetworksBaseExp: 2, - ClassAlgorithmsBaseExp: 4, - ClassManagementBaseExp: 2, - ClassLeadershipBaseExp: 4, + ClassStudyComputerScienceBaseExp: 0.5, + ClassDataStructuresBaseExp: 1, + ClassNetworksBaseExp: 2, + ClassAlgorithmsBaseExp: 4, + ClassManagementBaseExp: 2, + ClassLeadershipBaseExp: 4, - CrimeShoplift: "shoplift", - CrimeRobStore: "rob a store", - CrimeMug: "mug someone", - CrimeLarceny: "commit larceny", - CrimeDrugs: "deal drugs", - CrimeBondForgery: "forge corporate bonds", - CrimeTraffickArms: "traffick illegal arms", - CrimeHomicide: "commit homicide", - CrimeGrandTheftAuto: "commit grand theft auto", - CrimeKidnap: "kidnap someone for ransom", - CrimeAssassination: "assassinate a high-profile target", - CrimeHeist: "pull off the ultimate heist", + CrimeShoplift: "shoplift", + CrimeRobStore: "rob a store", + CrimeMug: "mug someone", + CrimeLarceny: "commit larceny", + CrimeDrugs: "deal drugs", + CrimeBondForgery: "forge corporate bonds", + CrimeTraffickArms: "traffick illegal arms", + CrimeHomicide: "commit homicide", + CrimeGrandTheftAuto: "commit grand theft auto", + CrimeKidnap: "kidnap someone for ransom", + CrimeAssassination: "assassinate a high-profile target", + CrimeHeist: "pull off the ultimate heist", - // Coding Contract - // TODO: Move this into Coding contract implementation? - CodingContractBaseFactionRepGain: 2500, - CodingContractBaseCompanyRepGain: 4000, - CodingContractBaseMoneyGain: 75e6, + // Coding Contract + // TODO: Move this into Coding contract implementation? + CodingContractBaseFactionRepGain: 2500, + CodingContractBaseCompanyRepGain: 4000, + CodingContractBaseMoneyGain: 75e6, - // BitNode/Source-File related stuff - TotalNumBitNodes: 24, + // BitNode/Source-File related stuff + TotalNumBitNodes: 24, - LatestUpdate: ` + LatestUpdate: ` v0.52.9 - 2021-08-27 Less lag! (hydroflame & community) ------------------------------------------- @@ -377,8 +378,8 @@ export const CONSTANTS: { * nerf noodle bar `, -/* + /* */ -} \ No newline at end of file +}; diff --git a/src/Corporation/Actions.ts b/src/Corporation/Actions.ts index d3bfbc80f..f6a34bd4e 100644 --- a/src/Corporation/Actions.ts +++ b/src/Corporation/Actions.ts @@ -11,218 +11,268 @@ import { CorporationUnlockUpgrade } from "./data/CorporationUnlockUpgrades"; import { CorporationUpgrade } from "./data/CorporationUpgrades"; import { Cities } from "../Locations/Cities"; -export function NewIndustry(corporation: ICorporation, industry: string, name: string): void { - for (let i = 0; i < corporation.divisions.length; ++i) { - if (corporation.divisions[i].name === name) { - throw new Error("This division name is already in use!"); - return; - } +export function NewIndustry( + corporation: ICorporation, + industry: string, + name: string, +): void { + for (let i = 0; i < corporation.divisions.length; ++i) { + if (corporation.divisions[i].name === name) { + throw new Error("This division name is already in use!"); + return; } + } - const cost = IndustryStartingCosts[industry]; - if(cost === undefined) { - throw new Error("Invalid industry: ${industry}"); - } - if (corporation.funds.lt(cost)) { - throw new Error("Not enough money to create a new division in this industry"); - } else if (name === "") { - throw new Error("New division must have a name!"); - } else { - corporation.funds = corporation.funds.minus(cost); - corporation.divisions.push(new Industry({ - corp: corporation, - name: name, - type: industry, - })); - } + const cost = IndustryStartingCosts[industry]; + if (cost === undefined) { + throw new Error("Invalid industry: ${industry}"); + } + if (corporation.funds.lt(cost)) { + throw new Error( + "Not enough money to create a new division in this industry", + ); + } else if (name === "") { + throw new Error("New division must have a name!"); + } else { + corporation.funds = corporation.funds.minus(cost); + corporation.divisions.push( + new Industry({ + corp: corporation, + name: name, + type: industry, + }), + ); + } } -export function NewCity(corporation: ICorporation, division: IIndustry, city: string): void { - if (corporation.funds.lt(CorporationConstants.OfficeInitialCost)) { - throw new Error("You don't have enough company funds to open a new office!"); - } else { - corporation.funds = corporation.funds.minus(CorporationConstants.OfficeInitialCost); - division.offices[city] = new OfficeSpace({ - loc: city, - size: CorporationConstants.OfficeInitialSize, - }); - } +export function NewCity( + corporation: ICorporation, + division: IIndustry, + city: string, +): void { + if (corporation.funds.lt(CorporationConstants.OfficeInitialCost)) { + throw new Error( + "You don't have enough company funds to open a new office!", + ); + } else { + corporation.funds = corporation.funds.minus( + CorporationConstants.OfficeInitialCost, + ); + division.offices[city] = new OfficeSpace({ + loc: city, + size: CorporationConstants.OfficeInitialSize, + }); + } } -export function UnlockUpgrade(corporation: ICorporation, upgrade: CorporationUnlockUpgrade): void { - if (corporation.funds.lt(upgrade[1])) { - throw new Error("Insufficient funds"); - } - corporation.unlock(upgrade); +export function UnlockUpgrade( + corporation: ICorporation, + upgrade: CorporationUnlockUpgrade, +): void { + if (corporation.funds.lt(upgrade[1])) { + throw new Error("Insufficient funds"); + } + corporation.unlock(upgrade); } -export function LevelUpgrade(corporation: ICorporation, upgrade: CorporationUpgrade): void { - const baseCost = upgrade[1]; - const priceMult = upgrade[2]; - const level = corporation.upgrades[upgrade[0]]; - const cost = baseCost * Math.pow(priceMult, level); - if (corporation.funds.lt(cost)) { - throw new Error("Insufficient funds"); - } else { - corporation.upgrade(upgrade); - } +export function LevelUpgrade( + corporation: ICorporation, + upgrade: CorporationUpgrade, +): void { + const baseCost = upgrade[1]; + const priceMult = upgrade[2]; + const level = corporation.upgrades[upgrade[0]]; + const cost = baseCost * Math.pow(priceMult, level); + if (corporation.funds.lt(cost)) { + throw new Error("Insufficient funds"); + } else { + corporation.upgrade(upgrade); + } } -export function IssueDividends(corporation: ICorporation, percent: number): void { - if (isNaN(percent) || percent < 0 || percent > CorporationConstants.DividendMaxPercentage) { - throw new Error(`Invalid value. Must be an integer between 0 and ${CorporationConstants.DividendMaxPercentage}`); - } +export function IssueDividends( + corporation: ICorporation, + percent: number, +): void { + if ( + isNaN(percent) || + percent < 0 || + percent > CorporationConstants.DividendMaxPercentage + ) { + throw new Error( + `Invalid value. Must be an integer between 0 and ${CorporationConstants.DividendMaxPercentage}`, + ); + } - corporation.dividendPercentage = percent*100; + corporation.dividendPercentage = percent * 100; } export function SellMaterial(mat: Material, amt: string, price: string): void { - if(price === '') price = '0'; - if(amt === '') amt = '0'; - let cost = price.replace(/\s+/g, ''); - cost = cost.replace(/[^-()\d/*+.MP]/g, ''); //Sanitize cost - let temp = cost.replace(/MP/g, mat.bCost+''); + if (price === "") price = "0"; + if (amt === "") amt = "0"; + let cost = price.replace(/\s+/g, ""); + cost = cost.replace(/[^-()\d/*+.MP]/g, ""); //Sanitize cost + let temp = cost.replace(/MP/g, mat.bCost + ""); + try { + temp = eval(temp); + } catch (e) { + throw new Error("Invalid value or expression for sell price field: " + e); + } + + if (temp == null || isNaN(parseFloat(temp))) { + throw new Error("Invalid value or expression for sell price field"); + } + + if (cost.includes("MP")) { + mat.sCost = cost; //Dynamically evaluated + } else { + mat.sCost = temp; + } + + //Parse quantity + if (amt.includes("MAX") || amt.includes("PROD")) { + let q = amt.replace(/\s+/g, ""); + q = q.replace(/[^-()\d/*+.MAXPROD]/g, ""); + let tempQty = q.replace(/MAX/g, "1"); + tempQty = tempQty.replace(/PROD/g, "1"); try { - temp = eval(temp); - } catch(e) { - throw new Error("Invalid value or expression for sell price field: " + e); + tempQty = eval(tempQty); + } catch (e) { + throw new Error("Invalid value or expression for sell price field: " + e); + } + + if (tempQty == null || isNaN(parseFloat(tempQty))) { + throw new Error("Invalid value or expression for sell price field"); + } + + mat.sllman[0] = true; + mat.sllman[1] = q; //Use sanitized input + } else if (isNaN(parseFloat(amt))) { + throw new Error( + "Invalid value for sell quantity field! Must be numeric or 'MAX'", + ); + } else { + let q = parseFloat(amt); + if (isNaN(q)) { + q = 0; + } + if (q === 0) { + mat.sllman[0] = false; + mat.sllman[1] = 0; + } else { + mat.sllman[0] = true; + mat.sllman[1] = q; + } + } +} + +export function SellProduct( + product: Product, + city: string, + amt: string, + price: string, + all: boolean, +): void { + //Parse price + if (price.includes("MP")) { + //Dynamically evaluated quantity. First test to make sure its valid + //Sanitize input, then replace dynamic variables with arbitrary numbers + price = price.replace(/\s+/g, ""); + price = price.replace(/[^-()\d/*+.MP]/g, ""); + let temp = price.replace(/MP/g, "1"); + try { + temp = eval(temp); + } catch (e) { + throw new Error( + "Invalid value or expression for sell quantity field: " + e, + ); + } + if (temp == null || isNaN(parseFloat(temp))) { + throw new Error("Invalid value or expression for sell quantity field."); + } + product.sCost = price; //Use sanitized price + } else { + const cost = parseFloat(price); + if (isNaN(cost)) { + throw new Error("Invalid value for sell price field"); + } + product.sCost = cost; + } + + // Array of all cities. Used later + const cities = Object.keys(Cities); + + // Parse quantity + if (amt.includes("MAX") || amt.includes("PROD")) { + //Dynamically evaluated quantity. First test to make sure its valid + let qty = amt.replace(/\s+/g, ""); + qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ""); + let temp = qty.replace(/MAX/g, "1"); + temp = temp.replace(/PROD/g, "1"); + try { + temp = eval(temp); + } catch (e) { + throw new Error("Invalid value or expression for sell price field: " + e); } if (temp == null || isNaN(parseFloat(temp))) { - throw new Error("Invalid value or expression for sell price field"); + throw new Error("Invalid value or expression for sell price field"); } - - if (cost.includes("MP")) { - mat.sCost = cost; //Dynamically evaluated + if (all) { + for (let i = 0; i < cities.length; ++i) { + const tempCity = cities[i]; + product.sllman[tempCity][0] = true; + product.sllman[tempCity][1] = qty; //Use sanitized input + } } else { - mat.sCost = temp; + product.sllman[city][0] = true; + product.sllman[city][1] = qty; //Use sanitized input } - - //Parse quantity - if (amt.includes("MAX") || amt.includes("PROD")) { - let q = amt.replace(/\s+/g, ''); - q = q.replace(/[^-()\d/*+.MAXPROD]/g, ''); - let tempQty = q.replace(/MAX/g, '1'); - tempQty = tempQty.replace(/PROD/g, '1'); - try { - tempQty = eval(tempQty); - } catch(e) { - throw new Error("Invalid value or expression for sell price field: " + e); + } else if (isNaN(parseFloat(amt))) { + throw new Error("Invalid value for sell quantity field! Must be numeric"); + } else { + let qty = parseFloat(amt); + if (isNaN(qty)) { + qty = 0; + } + if (qty === 0) { + if (all) { + for (let i = 0; i < cities.length; ++i) { + const tempCity = cities[i]; + product.sllman[tempCity][0] = false; + product.sllman[tempCity][1] = ""; } - - if (tempQty == null || isNaN(parseFloat(tempQty))) { - throw new Error("Invalid value or expression for sell price field"); - } - - mat.sllman[0] = true; - mat.sllman[1] = q; //Use sanitized input - } else if (isNaN(parseFloat(amt))) { - throw new Error("Invalid value for sell quantity field! Must be numeric or 'MAX'"); + } else { + product.sllman[city][0] = false; + product.sllman[city][1] = ""; + } } else { - let q = parseFloat(amt); - if (isNaN(q)) {q = 0;} - if (q === 0) { - mat.sllman[0] = false; - mat.sllman[1] = 0; - } else { - mat.sllman[0] = true; - mat.sllman[1] = q; + if (all) { + for (let i = 0; i < cities.length; ++i) { + const tempCity = cities[i]; + product.sllman[tempCity][0] = true; + product.sllman[tempCity][1] = qty; } + } else { + product.sllman[city][0] = true; + product.sllman[city][1] = qty; + } } + } } -export function SellProduct(product: Product, city: string, amt: string, price: string, all: boolean): void { - //Parse price - if (price.includes("MP")) { - //Dynamically evaluated quantity. First test to make sure its valid - //Sanitize input, then replace dynamic variables with arbitrary numbers - price = price.replace(/\s+/g, ''); - price = price.replace(/[^-()\d/*+.MP]/g, ''); - let temp = price.replace(/MP/g, '1'); - try { - temp = eval(temp); - } catch(e) { - throw new Error("Invalid value or expression for sell quantity field: " + e); - } - if (temp == null || isNaN(parseFloat(temp))) { - throw new Error("Invalid value or expression for sell quantity field."); - } - product.sCost = price; //Use sanitized price - } else { - const cost = parseFloat(price); - if (isNaN(cost)) { - throw new Error("Invalid value for sell price field"); - } - product.sCost = cost; - } - - // Array of all cities. Used later - const cities = Object.keys(Cities); - - // Parse quantity - if (amt.includes("MAX") || amt.includes("PROD")) { - //Dynamically evaluated quantity. First test to make sure its valid - let qty = amt.replace(/\s+/g, ''); - qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); - let temp = qty.replace(/MAX/g, '1'); - temp = temp.replace(/PROD/g, '1'); - try { - temp = eval(temp); - } catch(e) { - throw new Error("Invalid value or expression for sell price field: " + e); - } - - if (temp == null || isNaN(parseFloat(temp))) { - throw new Error("Invalid value or expression for sell price field"); - } - if (all) { - for (let i = 0; i < cities.length; ++i) { - const tempCity = cities[i]; - product.sllman[tempCity][0] = true; - product.sllman[tempCity][1] = qty; //Use sanitized input - } - } else { - product.sllman[city][0] = true; - product.sllman[city][1] = qty; //Use sanitized input - } - } else if (isNaN(parseFloat(amt))) { - throw new Error("Invalid value for sell quantity field! Must be numeric"); - } else { - let qty = parseFloat(amt); - if (isNaN(qty)) {qty = 0;} - if (qty === 0) { - if (all) { - for (let i = 0; i < cities.length; ++i) { - const tempCity = cities[i]; - product.sllman[tempCity][0] = false; - product.sllman[tempCity][1] = ''; - } - } else { - product.sllman[city][0] = false; - product.sllman[city][1] = ''; - } - } else { - if (all) { - for (let i = 0; i < cities.length; ++i) { - const tempCity = cities[i]; - product.sllman[tempCity][0] = true; - product.sllman[tempCity][1] = qty; - } - } else { - product.sllman[city][0] = true; - product.sllman[city][1] = qty; - } - } - } -} - -export function SetSmartSupply(warehouse: Warehouse, smartSupply: boolean): void { - warehouse.smartSupplyEnabled = smartSupply; +export function SetSmartSupply( + warehouse: Warehouse, + smartSupply: boolean, +): void { + warehouse.smartSupplyEnabled = smartSupply; } export function BuyMaterial(material: Material, amt: number): void { - if (isNaN(amt)) { - throw new Error(`Invalid amount '${amt}' to buy material '${material.name}'`); - } - material.buy = amt; -} \ No newline at end of file + if (isNaN(amt)) { + throw new Error( + `Invalid amount '${amt}' to buy material '${material.name}'`, + ); + } + material.buy = amt; +} diff --git a/src/Corporation/Corporation.tsx b/src/Corporation/Corporation.tsx index 139d9cdf5..6408f4221 100644 --- a/src/Corporation/Corporation.tsx +++ b/src/Corporation/Corporation.tsx @@ -1,434 +1,533 @@ -import { CorporationState } from "./CorporationState"; +import { CorporationState } from "./CorporationState"; import { - CorporationUnlockUpgrade, - CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades"; -import { CorporationUpgrade, CorporationUpgrades } from "./data/CorporationUpgrades"; -import { Warehouse } from "./Warehouse"; -import { CorporationConstants } from "./data/Constants"; -import { Industry } from "./Industry"; + CorporationUnlockUpgrade, + CorporationUnlockUpgrades, +} from "./data/CorporationUnlockUpgrades"; +import { + CorporationUpgrade, + CorporationUpgrades, +} from "./data/CorporationUpgrades"; +import { Warehouse } from "./Warehouse"; +import { CorporationConstants } from "./data/Constants"; +import { Industry } from "./Industry"; -import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; -import { showLiterature } from "../Literature/LiteratureHelpers"; -import { LiteratureNames } from "../Literature/data/LiteratureNames"; -import { IPlayer } from "../PersonObjects/IPlayer"; +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import { showLiterature } from "../Literature/LiteratureHelpers"; +import { LiteratureNames } from "../Literature/data/LiteratureNames"; +import { IPlayer } from "../PersonObjects/IPlayer"; -import { Page, routing } from "../ui/navigationTracking"; +import { Page, routing } from "../ui/navigationTracking"; -import { dialogBoxCreate } from "../../utils/DialogBox"; -import { Reviver, - Generic_toJSON, - Generic_fromJSON } from "../../utils/JSONReviver"; -import { createElement } from "../../utils/uiHelpers/createElement"; -import { isString } from "../../utils/helpers/isString"; -import { removeElementById } from "../../utils/uiHelpers/removeElementById"; +import { dialogBoxCreate } from "../../utils/DialogBox"; +import { + Reviver, + Generic_toJSON, + Generic_fromJSON, +} from "../../utils/JSONReviver"; +import { createElement } from "../../utils/uiHelpers/createElement"; +import { isString } from "../../utils/helpers/isString"; +import { removeElementById } from "../../utils/uiHelpers/removeElementById"; // UI Related Imports -import React from "react"; -import ReactDOM from "react-dom"; -import { CorporationRoot } from "./ui/Root"; -import { CorporationRouting } from "./ui/Routing"; +import React from "react"; +import ReactDOM from "react-dom"; +import { CorporationRoot } from "./ui/Root"; +import { CorporationRouting } from "./ui/Routing"; -import Decimal from "decimal.js"; +import Decimal from "decimal.js"; interface IParams { - name?: string; + name?: string; } let corpRouting: CorporationRouting; let companyManagementDiv: HTMLDivElement | null = null; export class Corporation { - name = "The Corporation"; + name = "The Corporation"; - //A division/business sector is represented by the object: - divisions: Industry[] = []; + //A division/business sector is represented by the object: + divisions: Industry[] = []; - //Financial stats - funds = new Decimal(150e9); - revenue = new Decimal(0); - expenses = new Decimal(0); - fundingRound = 0; - public = false; //Publicly traded - totalShares = CorporationConstants.INITIALSHARES; // Total existing shares - numShares = CorporationConstants.INITIALSHARES; // Total shares owned by player - shareSalesUntilPriceUpdate = CorporationConstants.SHARESPERPRICEUPDATE; - shareSaleCooldown = 0; // Game cycles until player can sell shares again - issueNewSharesCooldown = 0; // Game cycles until player can issue shares again - dividendPercentage = 0; - dividendTaxPercentage = 50; - issuedShares = 0; - sharePrice = 0; - storedCycles = 0; + //Financial stats + funds = new Decimal(150e9); + revenue = new Decimal(0); + expenses = new Decimal(0); + fundingRound = 0; + public = false; //Publicly traded + totalShares = CorporationConstants.INITIALSHARES; // Total existing shares + numShares = CorporationConstants.INITIALSHARES; // Total shares owned by player + shareSalesUntilPriceUpdate = CorporationConstants.SHARESPERPRICEUPDATE; + shareSaleCooldown = 0; // Game cycles until player can sell shares again + issueNewSharesCooldown = 0; // Game cycles until player can issue shares again + dividendPercentage = 0; + dividendTaxPercentage = 50; + issuedShares = 0; + sharePrice = 0; + storedCycles = 0; - unlockUpgrades: number[]; - upgrades: number[]; - upgradeMultipliers: number[]; + unlockUpgrades: number[]; + upgrades: number[]; + upgradeMultipliers: number[]; - state = new CorporationState(); + state = new CorporationState(); - constructor(params: IParams = {}) { - this.name = params.name ? params.name : "The Corporation"; - const numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length; - const numUpgrades = Object.keys(CorporationUpgrades).length; - this.unlockUpgrades = Array(numUnlockUpgrades).fill(0); - this.upgrades = Array(numUpgrades).fill(0); - this.upgradeMultipliers = Array(numUpgrades).fill(1); + constructor(params: IParams = {}) { + this.name = params.name ? params.name : "The Corporation"; + const numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length; + const numUpgrades = Object.keys(CorporationUpgrades).length; + this.unlockUpgrades = Array(numUnlockUpgrades).fill(0); + this.upgrades = Array(numUpgrades).fill(0); + this.upgradeMultipliers = Array(numUpgrades).fill(1); + } + + addFunds(amt: number): void { + if (!isFinite(amt)) { + console.error( + "Trying to add invalid amount of funds. Report to a developper.", + ); + return; } + this.funds = this.funds.plus(amt); + } - addFunds(amt: number): void { - if(!isFinite(amt)) { - console.error('Trying to add invalid amount of funds. Report to a developper.'); + getState(): string { + return this.state.getState(); + } + + storeCycles(numCycles = 1): void { + this.storedCycles += numCycles; + } + + process(player: IPlayer): void { + if (this.storedCycles >= CorporationConstants.CyclesPerIndustryStateCycle) { + const state = this.getState(); + const marketCycles = 1; + const gameCycles = + marketCycles * CorporationConstants.CyclesPerIndustryStateCycle; + this.storedCycles -= gameCycles; + + this.divisions.forEach((ind) => { + ind.process(marketCycles, state, this); + }); + + // Process cooldowns + if (this.shareSaleCooldown > 0) { + this.shareSaleCooldown -= gameCycles; + } + if (this.issueNewSharesCooldown > 0) { + this.issueNewSharesCooldown -= gameCycles; + } + + //At the start of a new cycle, calculate profits from previous cycle + if (state === "START") { + this.revenue = new Decimal(0); + this.expenses = new Decimal(0); + this.divisions.forEach((ind) => { + if ( + ind.lastCycleRevenue === -Infinity || + ind.lastCycleRevenue === Infinity + ) { return; + } + if ( + ind.lastCycleExpenses === -Infinity || + ind.lastCycleExpenses === Infinity + ) { + return; + } + this.revenue = this.revenue.plus(ind.lastCycleRevenue); + this.expenses = this.expenses.plus(ind.lastCycleExpenses); + }); + const profit = this.revenue.minus(this.expenses); + const cycleProfit = profit.times( + marketCycles * CorporationConstants.SecsPerMarketCycle, + ); + if ( + isNaN(this.funds) || + this.funds === Infinity || + this.funds === -Infinity + ) { + dialogBoxCreate( + "There was an error calculating your Corporations funds and they got reset to 0. " + + "This is a bug. Please report to game developer.

    " + + "(Your funds have been set to $150b for the inconvenience)", + ); + this.funds = new Decimal(150e9); } - this.funds = this.funds.plus(amt); - } - getState(): string { - return this.state.getState(); - } - - storeCycles(numCycles=1): void { - this.storedCycles += numCycles; - } - - process(player: IPlayer): void { - if (this.storedCycles >= CorporationConstants.CyclesPerIndustryStateCycle) { - const state = this.getState(); - const marketCycles = 1; - const gameCycles = (marketCycles * CorporationConstants.CyclesPerIndustryStateCycle); - this.storedCycles -= gameCycles; - - this.divisions.forEach((ind) => { - ind.process(marketCycles, state, this); - }); - - // Process cooldowns - if (this.shareSaleCooldown > 0) { - this.shareSaleCooldown -= gameCycles; - } - if (this.issueNewSharesCooldown > 0) { - this.issueNewSharesCooldown -= gameCycles; - } - - //At the start of a new cycle, calculate profits from previous cycle - if (state === "START") { - this.revenue = new Decimal(0); - this.expenses = new Decimal(0); - this.divisions.forEach((ind) => { - if (ind.lastCycleRevenue === -Infinity || ind.lastCycleRevenue === Infinity) { return; } - if (ind.lastCycleExpenses === -Infinity || ind.lastCycleExpenses === Infinity) { return; } - this.revenue = this.revenue.plus(ind.lastCycleRevenue); - this.expenses = this.expenses.plus(ind.lastCycleExpenses); - }); - const profit = this.revenue.minus(this.expenses); - const cycleProfit = profit.times(marketCycles * CorporationConstants.SecsPerMarketCycle); - if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) { - dialogBoxCreate("There was an error calculating your Corporations funds and they got reset to 0. " + - "This is a bug. Please report to game developer.

    " + - "(Your funds have been set to $150b for the inconvenience)"); - this.funds = new Decimal(150e9); - } - - // Process dividends - if (this.dividendPercentage > 0 && cycleProfit > 0) { - // Validate input again, just to be safe - if (isNaN(this.dividendPercentage) || this.dividendPercentage < 0 || this.dividendPercentage > CorporationConstants.DividendMaxPercentage*100) { - console.error(`Invalid Corporation dividend percentage: ${this.dividendPercentage}`); - } else { - const totalDividends = (this.dividendPercentage / 100) * cycleProfit; - const retainedEarnings = cycleProfit - totalDividends; - const dividendsPerShare = totalDividends / this.totalShares; - const profit = this.numShares * dividendsPerShare * (1 - (this.dividendTaxPercentage / 100)); - player.gainMoney(profit); - player.recordMoneySource(profit, "corporation"); - this.addFunds(retainedEarnings); - } - } else { - this.addFunds(cycleProfit); - } - - this.updateSharePrice(); - } - - this.state.nextState(); - - if (routing.isOn(Page.Corporation)) this.rerender(player); - } - } - - determineValuation(): number { - let val, profit = (this.revenue.minus(this.expenses)).toNumber(); - if (this.public) { - // Account for dividends - if (this.dividendPercentage > 0) { - profit *= ((100 - this.dividendPercentage) / 100); - } - - val = this.funds.toNumber() + (profit * 85e3); - val *= (Math.pow(1.1, this.divisions.length)); - val = Math.max(val, 0); + // Process dividends + if (this.dividendPercentage > 0 && cycleProfit > 0) { + // Validate input again, just to be safe + if ( + isNaN(this.dividendPercentage) || + this.dividendPercentage < 0 || + this.dividendPercentage > + CorporationConstants.DividendMaxPercentage * 100 + ) { + console.error( + `Invalid Corporation dividend percentage: ${this.dividendPercentage}`, + ); + } else { + const totalDividends = + (this.dividendPercentage / 100) * cycleProfit; + const retainedEarnings = cycleProfit - totalDividends; + const dividendsPerShare = totalDividends / this.totalShares; + const profit = + this.numShares * + dividendsPerShare * + (1 - this.dividendTaxPercentage / 100); + player.gainMoney(profit); + player.recordMoneySource(profit, "corporation"); + this.addFunds(retainedEarnings); + } } else { - val = 10e9 + Math.max(this.funds.toNumber(), 0) / 3; //Base valuation - if (profit > 0) { - val += (profit * 315e3); - val *= (Math.pow(1.1, this.divisions.length)); - } else { - val = 10e9 * Math.pow(1.1, this.divisions.length); - } - val -= (val % 1e6); //Round down to nearest millionth - } - return val * BitNodeMultipliers.CorporationValuation; - } - - getTargetSharePrice(): number { - // Note: totalShares - numShares is not the same as issuedShares because - // issuedShares does not account for private investors - return this.determineValuation() / (2 * (this.totalShares - this.numShares) + 1); - } - - updateSharePrice(): void { - const targetPrice = this.getTargetSharePrice(); - if (this.sharePrice <= targetPrice) { - this.sharePrice *= (1 + (Math.random() * 0.01)); - } else { - this.sharePrice *= (1 - (Math.random() * 0.01)); - } - if (this.sharePrice <= 0.01) {this.sharePrice = 0.01;} - } - - immediatelyUpdateSharePrice(): void { - this.sharePrice = this.getTargetSharePrice(); - } - - // Calculates how much money will be made and what the resulting stock price - // will be when the player sells his/her shares - // @return - [Player profit, final stock price, end shareSalesUntilPriceUpdate property] - calculateShareSale(numShares: number): [number, number, number] { - let sharesTracker = numShares; - let sharesUntilUpdate = this.shareSalesUntilPriceUpdate; - let sharePrice = this.sharePrice; - let sharesSold = 0; - let profit = 0; - - const maxIterations = Math.ceil(numShares / CorporationConstants.SHARESPERPRICEUPDATE); - if (isNaN(maxIterations) || maxIterations > 10e6) { - console.error(`Something went wrong or unexpected when calculating share sale. Maxiterations calculated to be ${maxIterations}`); - return [0, 0, 0]; + this.addFunds(cycleProfit); } - for (let i = 0; i < maxIterations; ++i) { - if (sharesTracker < sharesUntilUpdate) { - profit += (sharePrice * sharesTracker); - sharesUntilUpdate -= sharesTracker; - break; - } else { - profit += (sharePrice * sharesUntilUpdate); - sharesUntilUpdate = CorporationConstants.SHARESPERPRICEUPDATE; - sharesTracker -= sharesUntilUpdate; - sharesSold += sharesUntilUpdate; + this.updateSharePrice(); + } - // Calculate what new share price would be - sharePrice = this.determineValuation() / (2 * (this.totalShares + sharesSold - this.numShares)); - } + this.state.nextState(); + + if (routing.isOn(Page.Corporation)) this.rerender(player); + } + } + + determineValuation(): number { + let val, + profit = this.revenue.minus(this.expenses).toNumber(); + if (this.public) { + // Account for dividends + if (this.dividendPercentage > 0) { + profit *= (100 - this.dividendPercentage) / 100; + } + + val = this.funds.toNumber() + profit * 85e3; + val *= Math.pow(1.1, this.divisions.length); + val = Math.max(val, 0); + } else { + val = 10e9 + Math.max(this.funds.toNumber(), 0) / 3; //Base valuation + if (profit > 0) { + val += profit * 315e3; + val *= Math.pow(1.1, this.divisions.length); + } else { + val = 10e9 * Math.pow(1.1, this.divisions.length); + } + val -= val % 1e6; //Round down to nearest millionth + } + return val * BitNodeMultipliers.CorporationValuation; + } + + getTargetSharePrice(): number { + // Note: totalShares - numShares is not the same as issuedShares because + // issuedShares does not account for private investors + return ( + this.determineValuation() / (2 * (this.totalShares - this.numShares) + 1) + ); + } + + updateSharePrice(): void { + const targetPrice = this.getTargetSharePrice(); + if (this.sharePrice <= targetPrice) { + this.sharePrice *= 1 + Math.random() * 0.01; + } else { + this.sharePrice *= 1 - Math.random() * 0.01; + } + if (this.sharePrice <= 0.01) { + this.sharePrice = 0.01; + } + } + + immediatelyUpdateSharePrice(): void { + this.sharePrice = this.getTargetSharePrice(); + } + + // Calculates how much money will be made and what the resulting stock price + // will be when the player sells his/her shares + // @return - [Player profit, final stock price, end shareSalesUntilPriceUpdate property] + calculateShareSale(numShares: number): [number, number, number] { + let sharesTracker = numShares; + let sharesUntilUpdate = this.shareSalesUntilPriceUpdate; + let sharePrice = this.sharePrice; + let sharesSold = 0; + let profit = 0; + + const maxIterations = Math.ceil( + numShares / CorporationConstants.SHARESPERPRICEUPDATE, + ); + if (isNaN(maxIterations) || maxIterations > 10e6) { + console.error( + `Something went wrong or unexpected when calculating share sale. Maxiterations calculated to be ${maxIterations}`, + ); + return [0, 0, 0]; + } + + for (let i = 0; i < maxIterations; ++i) { + if (sharesTracker < sharesUntilUpdate) { + profit += sharePrice * sharesTracker; + sharesUntilUpdate -= sharesTracker; + break; + } else { + profit += sharePrice * sharesUntilUpdate; + sharesUntilUpdate = CorporationConstants.SHARESPERPRICEUPDATE; + sharesTracker -= sharesUntilUpdate; + sharesSold += sharesUntilUpdate; + + // Calculate what new share price would be + sharePrice = + this.determineValuation() / + (2 * (this.totalShares + sharesSold - this.numShares)); + } + } + + return [profit, sharePrice, sharesUntilUpdate]; + } + + convertCooldownToString(cd: number): string { + // The cooldown value is based on game cycles. Convert to a simple string + const seconds = cd / 5; + + const SecondsPerMinute = 60; + const SecondsPerHour = 3600; + + if (seconds > SecondsPerHour) { + return `${Math.floor(seconds / SecondsPerHour)} hour(s)`; + } else if (seconds > SecondsPerMinute) { + return `${Math.floor(seconds / SecondsPerMinute)} minute(s)`; + } else { + return `${Math.floor(seconds)} second(s)`; + } + } + + //One time upgrades that unlock new features + unlock(upgrade: CorporationUnlockUpgrade): void { + const upgN = upgrade[0], + price = upgrade[1]; + while (this.unlockUpgrades.length <= upgN) { + this.unlockUpgrades.push(0); + } + if (this.funds.lt(price)) { + dialogBoxCreate("You don't have enough funds to unlock this!"); + return; + } + this.unlockUpgrades[upgN] = 1; + this.funds = this.funds.minus(price); + + // Apply effects for one-time upgrades + if (upgN === 5) { + this.dividendTaxPercentage -= 5; + } else if (upgN === 6) { + this.dividendTaxPercentage -= 10; + } + } + + //Levelable upgrades + upgrade(upgrade: CorporationUpgrade): void { + const upgN = upgrade[0], + basePrice = upgrade[1], + priceMult = upgrade[2], + upgradeAmt = upgrade[3]; //Amount by which the upgrade multiplier gets increased (additive) + while (this.upgrades.length <= upgN) { + this.upgrades.push(0); + } + while (this.upgradeMultipliers.length <= upgN) { + this.upgradeMultipliers.push(1); + } + const totalCost = basePrice * Math.pow(priceMult, this.upgrades[upgN]); + if (this.funds.lt(totalCost)) { + dialogBoxCreate("You don't have enough funds to purchase this!"); + return; + } + ++this.upgrades[upgN]; + this.funds = this.funds.minus(totalCost); + + //Increase upgrade multiplier + this.upgradeMultipliers[upgN] = 1 + this.upgrades[upgN] * upgradeAmt; + + //If storage size is being updated, update values in Warehouse objects + if (upgN === 1) { + for (let i = 0; i < this.divisions.length; ++i) { + const industry = this.divisions[i]; + for (const city in industry.warehouses) { + const warehouse = industry.warehouses[city]; + if (warehouse === 0) continue; + if ( + industry.warehouses.hasOwnProperty(city) && + warehouse instanceof Warehouse + ) { + warehouse.updateSize(this, industry); + } } + } + } + } - return [profit, sharePrice, sharesUntilUpdate]; + getProductionMultiplier(): number { + const mult = this.upgradeMultipliers[0]; + if (isNaN(mult) || mult < 1) { + return 1; + } else { + return mult; + } + } + + getStorageMultiplier(): number { + const mult = this.upgradeMultipliers[1]; + if (isNaN(mult) || mult < 1) { + return 1; + } else { + return mult; + } + } + + getDreamSenseGain(): number { + const gain = this.upgradeMultipliers[2] - 1; + return gain <= 0 ? 0 : gain; + } + + getAdvertisingMultiplier(): number { + const mult = this.upgradeMultipliers[3]; + if (isNaN(mult) || mult < 1) { + return 1; + } else { + return mult; + } + } + + getEmployeeCreMultiplier(): number { + const mult = this.upgradeMultipliers[4]; + if (isNaN(mult) || mult < 1) { + return 1; + } else { + return mult; + } + } + + getEmployeeChaMultiplier(): number { + const mult = this.upgradeMultipliers[5]; + if (isNaN(mult) || mult < 1) { + return 1; + } else { + return mult; + } + } + + getEmployeeIntMultiplier(): number { + const mult = this.upgradeMultipliers[6]; + if (isNaN(mult) || mult < 1) { + return 1; + } else { + return mult; + } + } + + getEmployeeEffMultiplier(): number { + const mult = this.upgradeMultipliers[7]; + if (isNaN(mult) || mult < 1) { + return 1; + } else { + return mult; + } + } + + getSalesMultiplier(): number { + const mult = this.upgradeMultipliers[8]; + if (isNaN(mult) || mult < 1) { + return 1; + } else { + return mult; + } + } + + getScientificResearchMultiplier(): number { + const mult = this.upgradeMultipliers[9]; + if (isNaN(mult) || mult < 1) { + return 1; + } else { + return mult; + } + } + + // Adds the Corporation Handbook (Starter Guide) to the player's home computer. + // This is a lit file that gives introductory info to the player + // This occurs when the player clicks the "Getting Started Guide" button on the overview panel + getStarterGuide(player: IPlayer): void { + // Check if player already has Corporation Handbook + const homeComp = player.getHomeComputer(); + let hasHandbook = false; + const handbookFn = LiteratureNames.CorporationManagementHandbook; + for (let i = 0; i < homeComp.messages.length; ++i) { + if ( + isString(homeComp.messages[i]) && + homeComp.messages[i] === handbookFn + ) { + hasHandbook = true; + break; + } } - convertCooldownToString(cd: number): string { - // The cooldown value is based on game cycles. Convert to a simple string - const seconds = cd / 5; + if (!hasHandbook) { + homeComp.messages.push(handbookFn); + } + showLiterature(handbookFn); + return; + } - const SecondsPerMinute = 60; - const SecondsPerHour = 3600; + createUI(player: IPlayer): void { + companyManagementDiv = createElement("div", { + id: "cmpy-mgmt-container", + position: "fixed", + class: "generic-menupage-container", + }) as HTMLDivElement; + const game = document.getElementById("entire-game-container"); + if (game) game.appendChild(companyManagementDiv); - if (seconds > SecondsPerHour) { - return `${Math.floor(seconds / SecondsPerHour)} hour(s)`; - } else if (seconds > SecondsPerMinute) { - return `${Math.floor(seconds / SecondsPerMinute)} minute(s)`; - } else { - return `${Math.floor(seconds)} second(s)`; - } + corpRouting = new CorporationRouting(this); + + this.rerender(player); + } + + rerender(player: IPlayer): void { + if (companyManagementDiv == null || corpRouting == null) { + console.warn( + `Corporation.rerender() called when companyManagementDiv, corpRouting, or eventHandler is null`, + ); + return; + } + if (!routing.isOn(Page.Corporation)) return; + + ReactDOM.render( + , + companyManagementDiv, + ); + } + + clearUI(): void { + if (companyManagementDiv instanceof HTMLElement) { + ReactDOM.unmountComponentAtNode(companyManagementDiv); + removeElementById(companyManagementDiv.id); } - //One time upgrades that unlock new features - unlock(upgrade: CorporationUnlockUpgrade): void { - const upgN = upgrade[0], price = upgrade[1]; - while (this.unlockUpgrades.length <= upgN) { - this.unlockUpgrades.push(0); - } - if (this.funds.lt(price)) { - dialogBoxCreate("You don't have enough funds to unlock this!"); - return; - } - this.unlockUpgrades[upgN] = 1; - this.funds = this.funds.minus(price); + companyManagementDiv = null; + const character = document.getElementById("character-overview-wrapper"); + if (character) character.style.visibility = "visible"; + } - // Apply effects for one-time upgrades - if (upgN === 5) { - this.dividendTaxPercentage -= 5; - } else if (upgN === 6) { - this.dividendTaxPercentage -= 10; - } - } + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Corporation", this); + } - //Levelable upgrades - upgrade(upgrade: CorporationUpgrade): void { - const upgN = upgrade[0], basePrice = upgrade[1], priceMult = upgrade[2], - upgradeAmt = upgrade[3]; //Amount by which the upgrade multiplier gets increased (additive) - while (this.upgrades.length <= upgN) {this.upgrades.push(0);} - while (this.upgradeMultipliers.length <= upgN) {this.upgradeMultipliers.push(1);} - const totalCost = basePrice * Math.pow(priceMult, this.upgrades[upgN]); - if (this.funds.lt(totalCost)) { - dialogBoxCreate("You don't have enough funds to purchase this!"); - return; - } - ++this.upgrades[upgN]; - this.funds = this.funds.minus(totalCost); - - //Increase upgrade multiplier - this.upgradeMultipliers[upgN] = 1 + (this.upgrades[upgN] * upgradeAmt); - - //If storage size is being updated, update values in Warehouse objects - if (upgN === 1) { - for (let i = 0; i < this.divisions.length; ++i) { - const industry = this.divisions[i]; - for (const city in industry.warehouses) { - const warehouse = industry.warehouses[city] - if(warehouse === 0) continue - if (industry.warehouses.hasOwnProperty(city) && warehouse instanceof Warehouse) { - warehouse.updateSize(this, industry); - } - } - } - } - } - - getProductionMultiplier(): number { - const mult = this.upgradeMultipliers[0]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} - } - - getStorageMultiplier(): number { - const mult = this.upgradeMultipliers[1]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} - } - - getDreamSenseGain(): number { - const gain = this.upgradeMultipliers[2] - 1; - return gain <= 0 ? 0 : gain; - } - - getAdvertisingMultiplier(): number { - const mult = this.upgradeMultipliers[3]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} - } - - getEmployeeCreMultiplier(): number { - const mult = this.upgradeMultipliers[4]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} - } - - getEmployeeChaMultiplier(): number { - const mult = this.upgradeMultipliers[5]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} - } - - getEmployeeIntMultiplier(): number { - const mult = this.upgradeMultipliers[6]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} - } - - getEmployeeEffMultiplier(): number { - const mult = this.upgradeMultipliers[7]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} - } - - getSalesMultiplier(): number { - const mult = this.upgradeMultipliers[8]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} - } - - getScientificResearchMultiplier(): number { - const mult = this.upgradeMultipliers[9]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} - } - - // Adds the Corporation Handbook (Starter Guide) to the player's home computer. - // This is a lit file that gives introductory info to the player - // This occurs when the player clicks the "Getting Started Guide" button on the overview panel - getStarterGuide(player: IPlayer): void { - // Check if player already has Corporation Handbook - const homeComp = player.getHomeComputer(); - let hasHandbook = false; - const handbookFn = LiteratureNames.CorporationManagementHandbook; - for (let i = 0; i < homeComp.messages.length; ++i) { - if (isString(homeComp.messages[i]) && homeComp.messages[i] === handbookFn) { - hasHandbook = true; - break; - } - } - - if (!hasHandbook) { homeComp.messages.push(handbookFn); } - showLiterature(handbookFn); - return; - } - - createUI(player: IPlayer): void { - companyManagementDiv = createElement("div", { - id:"cmpy-mgmt-container", - position:"fixed", - class:"generic-menupage-container", - }) as HTMLDivElement; - const game = document.getElementById("entire-game-container"); - if(game) - game.appendChild(companyManagementDiv); - - corpRouting = new CorporationRouting(this); - - this.rerender(player); - } - - rerender(player: IPlayer): void { - if (companyManagementDiv == null || corpRouting == null) { - console.warn(`Corporation.rerender() called when companyManagementDiv, corpRouting, or eventHandler is null`); - return; - } - if (!routing.isOn(Page.Corporation)) return; - - ReactDOM.render(, companyManagementDiv); - } - - clearUI(): void { - if (companyManagementDiv instanceof HTMLElement) { - ReactDOM.unmountComponentAtNode(companyManagementDiv); - removeElementById(companyManagementDiv.id); - } - - companyManagementDiv = null; - const character = document.getElementById("character-overview-wrapper"); - if(character) - character.style.visibility = "visible"; - } - - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("Corporation", this); - } - - /** - * Initiatizes a Corporation object from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Corporation { - return Generic_fromJSON(Corporation, value.data); - } + /** + * Initiatizes a Corporation object from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Corporation { + return Generic_fromJSON(Corporation, value.data); + } } Reviver.constructors.Corporation = Corporation; diff --git a/src/Corporation/CorporationState.ts b/src/Corporation/CorporationState.ts index 1a7b2f806..9c87e81ae 100644 --- a/src/Corporation/CorporationState.ts +++ b/src/Corporation/CorporationState.ts @@ -1,45 +1,52 @@ -import { Generic_fromJSON, - Generic_toJSON, - Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; // Array of all valid states -export const AllCorporationStates: string[] = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"]; +export const AllCorporationStates: string[] = [ + "START", + "PURCHASE", + "PRODUCTION", + "SALE", + "EXPORT", +]; export class CorporationState { + // Number representing what state the Corporation is in. The number + // is an index for the array that holds all Corporation States + state = 0; - // Number representing what state the Corporation is in. The number - // is an index for the array that holds all Corporation States - state = 0; + // Get the name of the current state + // NOTE: This does NOT return the number stored in the 'state' property, + // which is just an index for the array of all possible Corporation States. + getState(): string { + return AllCorporationStates[this.state]; + } - // Get the name of the current state - // NOTE: This does NOT return the number stored in the 'state' property, - // which is just an index for the array of all possible Corporation States. - getState(): string { - return AllCorporationStates[this.state]; + // Transition to the next state + nextState(): void { + if (this.state < 0 || this.state >= AllCorporationStates.length) { + this.state = 0; } - // Transition to the next state - nextState(): void { - if (this.state < 0 || this.state >= AllCorporationStates.length) { - this.state = 0; - } - - ++this.state; - if (this.state >= AllCorporationStates.length) { - this.state = 0; - } + ++this.state; + if (this.state >= AllCorporationStates.length) { + this.state = 0; } + } - // Serialize the current object to a JSON save state. - toJSON(): any { - return Generic_toJSON("CorporationState", this); - } + // Serialize the current object to a JSON save state. + toJSON(): any { + return Generic_toJSON("CorporationState", this); + } - // Initiatizes a CorporationState object from a JSON save state. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): CorporationState { - return Generic_fromJSON(CorporationState, value.data); - } + // Initiatizes a CorporationState object from a JSON save state. + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): CorporationState { + return Generic_fromJSON(CorporationState, value.data); + } } Reviver.constructors.CorporationState = CorporationState; diff --git a/src/Corporation/Employee.ts b/src/Corporation/Employee.ts index 2750a6c0c..2bea886e3 100644 --- a/src/Corporation/Employee.ts +++ b/src/Corporation/Employee.ts @@ -1,6 +1,10 @@ import { CorporationConstants } from "./data/Constants"; import { getRandomInt } from "../../utils/helpers/getRandomInt"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; import { createElement } from "../../utils/uiHelpers/createElement"; import { EmployeePositions } from "./EmployeePositions"; import { ICorporation } from "./ICorporation"; @@ -10,188 +14,244 @@ import { OfficeSpace } from "./OfficeSpace"; import { IIndustry } from "./IIndustry"; interface IParams { - name?: string; - morale?: number; - happiness?: number; - energy?: number; - intelligence?: number; - charisma?: number; - experience?: number; - creativity?: number; - efficiency?: number; - salary?: number; - loc?: string; + name?: string; + morale?: number; + happiness?: number; + energy?: number; + intelligence?: number; + charisma?: number; + experience?: number; + creativity?: number; + efficiency?: number; + salary?: number; + loc?: string; } export class Employee { - name: string; - mor: number; - hap: number; - ene: number; - int: number; - cha: number; - exp: number; - cre: number; - eff: number; - sal: number; - pro = 0; - cyclesUntilRaise = CorporationConstants.CyclesPerEmployeeRaise; - loc: string; - pos: string; + name: string; + mor: number; + hap: number; + ene: number; + int: number; + cha: number; + exp: number; + cre: number; + eff: number; + sal: number; + pro = 0; + cyclesUntilRaise = CorporationConstants.CyclesPerEmployeeRaise; + loc: string; + pos: string; - constructor(params: IParams = {}) { - this.name = params.name ? params.name : "Bobby"; + constructor(params: IParams = {}) { + this.name = params.name ? params.name : "Bobby"; - //Morale, happiness, and energy are 0-100 - this.mor = params.morale ? params.morale : getRandomInt(50, 100); - this.hap = params.happiness ? params.happiness : getRandomInt(50, 100); - this.ene = params.energy ? params.energy : getRandomInt(50, 100); + //Morale, happiness, and energy are 0-100 + this.mor = params.morale ? params.morale : getRandomInt(50, 100); + this.hap = params.happiness ? params.happiness : getRandomInt(50, 100); + this.ene = params.energy ? params.energy : getRandomInt(50, 100); - this.int = params.intelligence ? params.intelligence : getRandomInt(10, 50); - this.cha = params.charisma ? params.charisma : getRandomInt(10, 50); - this.exp = params.experience ? params.experience : getRandomInt(10, 50); - this.cre = params.creativity ? params.creativity : getRandomInt(10, 50); - this.eff = params.efficiency ? params.efficiency : getRandomInt(10, 50); - this.sal = params.salary ? params.salary : getRandomInt(0.1, 5); + this.int = params.intelligence ? params.intelligence : getRandomInt(10, 50); + this.cha = params.charisma ? params.charisma : getRandomInt(10, 50); + this.exp = params.experience ? params.experience : getRandomInt(10, 50); + this.cre = params.creativity ? params.creativity : getRandomInt(10, 50); + this.eff = params.efficiency ? params.efficiency : getRandomInt(10, 50); + this.sal = params.salary ? params.salary : getRandomInt(0.1, 5); - this.loc = params.loc ? params.loc : ""; - this.pos = EmployeePositions.Unassigned; + this.loc = params.loc ? params.loc : ""; + this.pos = EmployeePositions.Unassigned; + } + + //Returns the amount the employee needs to be paid + process(marketCycles = 1, office: OfficeSpace): number { + const gain = 0.003 * marketCycles, + det = gain * Math.random(); + this.exp += gain; + + // Employee salaries slowly go up over time + this.cyclesUntilRaise -= marketCycles; + if (this.cyclesUntilRaise <= 0) { + this.sal += CorporationConstants.EmployeeRaiseAmount; + this.cyclesUntilRaise += CorporationConstants.CyclesPerEmployeeRaise; } - //Returns the amount the employee needs to be paid - process(marketCycles = 1, office: OfficeSpace): number { - const gain = 0.003 * marketCycles, - det = gain * Math.random(); - this.exp += gain; - - // Employee salaries slowly go up over time - this.cyclesUntilRaise -= marketCycles; - if (this.cyclesUntilRaise <= 0) { - this.sal += CorporationConstants.EmployeeRaiseAmount; - this.cyclesUntilRaise += CorporationConstants.CyclesPerEmployeeRaise; - } - - //Training - const trainingEff = gain * Math.random(); - if (this.pos === EmployeePositions.Training) { - //To increase creativity and intelligence special upgrades are needed - this.cha += trainingEff; - this.exp += trainingEff; - this.eff += trainingEff; - } - - this.ene -= det; - this.hap -= det; - - if (this.ene < office.minEne) {this.ene = office.minEne;} - if (this.hap < office.minHap) {this.hap = office.minHap;} - const salary = this.sal * marketCycles * CorporationConstants.SecsPerMarketCycle; - return salary; + //Training + const trainingEff = gain * Math.random(); + if (this.pos === EmployeePositions.Training) { + //To increase creativity and intelligence special upgrades are needed + this.cha += trainingEff; + this.exp += trainingEff; + this.eff += trainingEff; } - calculateProductivity(corporation: ICorporation, industry: IIndustry): number { - const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), - effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), - effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), - effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier(); - const prodBase = this.mor * this.hap * this.ene * 1e-6; - let prodMult = 0; - switch(this.pos) { - //Calculate productivity based on position. This is multipled by prodBase - //to get final value - case EmployeePositions.Operations: - prodMult = (0.6 * effInt) + (0.1 * effCha) + (this.exp) + - (0.5 * effCre) + (effEff); - break; - case EmployeePositions.Engineer: - prodMult = (effInt) + (0.1 * effCha) + (1.5 * this.exp) + - (effEff); - break; - case EmployeePositions.Business: - prodMult = (0.4 * effInt) + (effCha) + (0.5 * this.exp); - break; - case EmployeePositions.Management: - prodMult = (2 * effCha) + (this.exp) + (0.2 * effCre) + - (0.7 * effEff); - break; - case EmployeePositions.RandD: - prodMult = (1.5 * effInt) + (0.8 * this.exp) + (effCre) + - (0.5 * effEff); - break; - case EmployeePositions.Unassigned: - case EmployeePositions.Training: - prodMult = 0; - break; - default: - console.error(`Invalid employee position: ${this.pos}`); - break; - } - return prodBase * prodMult; + this.ene -= det; + this.hap -= det; + + if (this.ene < office.minEne) { + this.ene = office.minEne; + } + if (this.hap < office.minHap) { + this.hap = office.minHap; + } + const salary = + this.sal * marketCycles * CorporationConstants.SecsPerMarketCycle; + return salary; + } + + calculateProductivity( + corporation: ICorporation, + industry: IIndustry, + ): number { + const effCre = + this.cre * + corporation.getEmployeeCreMultiplier() * + industry.getEmployeeCreMultiplier(), + effCha = + this.cha * + corporation.getEmployeeChaMultiplier() * + industry.getEmployeeChaMultiplier(), + effInt = + this.int * + corporation.getEmployeeIntMultiplier() * + industry.getEmployeeIntMultiplier(), + effEff = + this.eff * + corporation.getEmployeeEffMultiplier() * + industry.getEmployeeEffMultiplier(); + const prodBase = this.mor * this.hap * this.ene * 1e-6; + let prodMult = 0; + switch (this.pos) { + //Calculate productivity based on position. This is multipled by prodBase + //to get final value + case EmployeePositions.Operations: + prodMult = + 0.6 * effInt + 0.1 * effCha + this.exp + 0.5 * effCre + effEff; + break; + case EmployeePositions.Engineer: + prodMult = effInt + 0.1 * effCha + 1.5 * this.exp + effEff; + break; + case EmployeePositions.Business: + prodMult = 0.4 * effInt + effCha + 0.5 * this.exp; + break; + case EmployeePositions.Management: + prodMult = 2 * effCha + this.exp + 0.2 * effCre + 0.7 * effEff; + break; + case EmployeePositions.RandD: + prodMult = 1.5 * effInt + 0.8 * this.exp + effCre + 0.5 * effEff; + break; + case EmployeePositions.Unassigned: + case EmployeePositions.Training: + prodMult = 0; + break; + default: + console.error(`Invalid employee position: ${this.pos}`); + break; + } + return prodBase * prodMult; + } + + //Process benefits from having an office party thrown + throwParty(money: number): number { + const mult = 1 + money / 10e6; + this.mor *= mult; + this.mor = Math.min(100, this.mor); + this.hap *= mult; + this.hap = Math.min(100, this.hap); + return mult; + } + + //'panel' is the DOM element on which to create the UI + createUI( + panel: HTMLElement, + corporation: ICorporation, + industry: IIndustry, + ): void { + const effCre = + this.cre * + corporation.getEmployeeCreMultiplier() * + industry.getEmployeeCreMultiplier(), + effCha = + this.cha * + corporation.getEmployeeChaMultiplier() * + industry.getEmployeeChaMultiplier(), + effInt = + this.int * + corporation.getEmployeeIntMultiplier() * + industry.getEmployeeIntMultiplier(), + effEff = + this.eff * + corporation.getEmployeeEffMultiplier() * + industry.getEmployeeEffMultiplier(); + panel.style.color = "white"; + panel.appendChild( + createElement("p", { + id: "cmpy-mgmt-employee-" + this.name + "-panel-text", + innerHTML: + "Morale: " + + formatNumber(this.mor, 3) + + "
    " + + "Happiness: " + + formatNumber(this.hap, 3) + + "
    " + + "Energy: " + + formatNumber(this.ene, 3) + + "
    " + + "Intelligence: " + + formatNumber(effInt, 3) + + "
    " + + "Charisma: " + + formatNumber(effCha, 3) + + "
    " + + "Experience: " + + formatNumber(this.exp, 3) + + "
    " + + "Creativity: " + + formatNumber(effCre, 3) + + "
    " + + "Efficiency: " + + formatNumber(effEff, 3) + + "
    " + + "Salary: " + + numeralWrapper.format(this.sal, "$0.000a") + + "/ s
    ", + }), + ); + + //Selector for employee position + const selector = createElement("select", {}) as HTMLSelectElement; + for (const key in EmployeePositions) { + if (EmployeePositions.hasOwnProperty(key)) { + selector.add( + createElement("option", { + text: EmployeePositions[key], + value: EmployeePositions[key], + }) as HTMLOptionElement, + ); + } } - //Process benefits from having an office party thrown - throwParty(money: number): number { - const mult = 1 + (money / 10e6); - this.mor *= mult; - this.mor = Math.min(100, this.mor); - this.hap *= mult; - this.hap = Math.min(100, this.hap); - return mult; + selector.addEventListener("change", () => { + this.pos = selector.options[selector.selectedIndex].value; + }); + + //Set initial value of selector + for (let i = 0; i < selector.length; ++i) { + if (selector.options[i].value === this.pos) { + selector.selectedIndex = i; + break; + } } + panel.appendChild(selector); + } - //'panel' is the DOM element on which to create the UI - createUI(panel: HTMLElement, corporation: ICorporation, industry: IIndustry): void { - const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), - effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), - effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), - effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier(); - panel.style.color = "white"; - panel.appendChild(createElement("p", { - id:"cmpy-mgmt-employee-" + this.name + "-panel-text", - innerHTML:"Morale: " + formatNumber(this.mor, 3) + "
    " + - "Happiness: " + formatNumber(this.hap, 3) + "
    " + - "Energy: " + formatNumber(this.ene, 3) + "
    " + - "Intelligence: " + formatNumber(effInt, 3) + "
    " + - "Charisma: " + formatNumber(effCha, 3) + "
    " + - "Experience: " + formatNumber(this.exp, 3) + "
    " + - "Creativity: " + formatNumber(effCre, 3) + "
    " + - "Efficiency: " + formatNumber(effEff, 3) + "
    " + - "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s
    ", - })); + toJSON(): any { + return Generic_toJSON("Employee", this); + } - //Selector for employee position - const selector = createElement("select", {}) as HTMLSelectElement; - for (const key in EmployeePositions) { - if (EmployeePositions.hasOwnProperty(key)) { - selector.add(createElement("option", { - text: EmployeePositions[key], - value: EmployeePositions[key], - }) as HTMLOptionElement); - } - } - - selector.addEventListener("change", () => { - this.pos = selector.options[selector.selectedIndex].value; - }); - - //Set initial value of selector - for (let i = 0; i < selector.length; ++i) { - if (selector.options[i].value === this.pos) { - selector.selectedIndex = i; - break; - } - } - panel.appendChild(selector); - } - - toJSON(): any { - return Generic_toJSON("Employee", this); - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Employee { - return Generic_fromJSON(Employee, value.data); - } + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Employee { + return Generic_fromJSON(Employee, value.data); + } } -Reviver.constructors.Employee = Employee; \ No newline at end of file +Reviver.constructors.Employee = Employee; diff --git a/src/Corporation/EmployeePositions.ts b/src/Corporation/EmployeePositions.ts index b7bd3420f..ed7d524cd 100644 --- a/src/Corporation/EmployeePositions.ts +++ b/src/Corporation/EmployeePositions.ts @@ -1,11 +1,11 @@ -import { IMap } from "../types"; +import { IMap } from "../types"; export const EmployeePositions: IMap = { - Operations: "Operations", - Engineer: "Engineer", - Business: "Business", - Management: "Management", - RandD: "Research & Development", - Training:"Training", - Unassigned:"Unassigned", -} + Operations: "Operations", + Engineer: "Engineer", + Business: "Business", + Management: "Management", + RandD: "Research & Development", + Training: "Training", + Unassigned: "Unassigned", +}; diff --git a/src/Corporation/Export.ts b/src/Corporation/Export.ts index ff4ae9ec6..53eee2066 100644 --- a/src/Corporation/Export.ts +++ b/src/Corporation/Export.ts @@ -1,5 +1,5 @@ export interface Export { - ind: string; - city: string; - amt: string; -} \ No newline at end of file + ind: string; + city: string; + amt: string; +} diff --git a/src/Corporation/ICorporation.ts b/src/Corporation/ICorporation.ts index 775774458..706f59b21 100644 --- a/src/Corporation/ICorporation.ts +++ b/src/Corporation/ICorporation.ts @@ -5,57 +5,57 @@ import { CorporationUpgrade } from "./data/CorporationUpgrades"; import { CorporationState } from "./CorporationState"; export interface ICorporation { - name: string; + name: string; - divisions: Industry[]; + divisions: Industry[]; - funds: any; - revenue: any; - expenses: any; - fundingRound: number; - public: boolean; - totalShares: number; - numShares: number; - shareSalesUntilPriceUpdate: number; - shareSaleCooldown: number; - issueNewSharesCooldown: number; - dividendPercentage: number; - dividendTaxPercentage: number; - issuedShares: number; - sharePrice: number; - storedCycles: number; + funds: any; + revenue: any; + expenses: any; + fundingRound: number; + public: boolean; + totalShares: number; + numShares: number; + shareSalesUntilPriceUpdate: number; + shareSaleCooldown: number; + issueNewSharesCooldown: number; + dividendPercentage: number; + dividendTaxPercentage: number; + issuedShares: number; + sharePrice: number; + storedCycles: number; - unlockUpgrades: number[]; - upgrades: number[]; - upgradeMultipliers: number[]; + unlockUpgrades: number[]; + upgrades: number[]; + upgradeMultipliers: number[]; - state: CorporationState; + state: CorporationState; - addFunds(amt: number): void; - getState(): string; - storeCycles(numCycles: number): void; - process(player: IPlayer): void; - determineValuation(): number; - getTargetSharePrice(): number; - updateSharePrice(): void; - immediatelyUpdateSharePrice(): void; - calculateShareSale(numShares: number): [number, number, number]; - convertCooldownToString(cd: number): string; - unlock(upgrade: CorporationUnlockUpgrade): void; - upgrade(upgrade: CorporationUpgrade): void; - getProductionMultiplier(): number; - getStorageMultiplier(): number; - getDreamSenseGain(): number; - getAdvertisingMultiplier(): number; - getEmployeeCreMultiplier(): number; - getEmployeeChaMultiplier(): number; - getEmployeeIntMultiplier(): number; - getEmployeeEffMultiplier(): number; - getSalesMultiplier(): number; - getScientificResearchMultiplier(): number; - getStarterGuide(player: IPlayer): void; - createUI(player: IPlayer): void; - rerender(player: IPlayer): void; - clearUI(): void; - toJSON(): any; -} \ No newline at end of file + addFunds(amt: number): void; + getState(): string; + storeCycles(numCycles: number): void; + process(player: IPlayer): void; + determineValuation(): number; + getTargetSharePrice(): number; + updateSharePrice(): void; + immediatelyUpdateSharePrice(): void; + calculateShareSale(numShares: number): [number, number, number]; + convertCooldownToString(cd: number): string; + unlock(upgrade: CorporationUnlockUpgrade): void; + upgrade(upgrade: CorporationUpgrade): void; + getProductionMultiplier(): number; + getStorageMultiplier(): number; + getDreamSenseGain(): number; + getAdvertisingMultiplier(): number; + getEmployeeCreMultiplier(): number; + getEmployeeChaMultiplier(): number; + getEmployeeIntMultiplier(): number; + getEmployeeEffMultiplier(): number; + getSalesMultiplier(): number; + getScientificResearchMultiplier(): number; + getStarterGuide(player: IPlayer): void; + createUI(player: IPlayer): void; + rerender(player: IPlayer): void; + clearUI(): void; + toJSON(): any; +} diff --git a/src/Corporation/IIndustry.ts b/src/Corporation/IIndustry.ts index 298d92fd5..7529db5cf 100644 --- a/src/Corporation/IIndustry.ts +++ b/src/Corporation/IIndustry.ts @@ -6,72 +6,88 @@ import { Product } from "./Product"; import { IndustryUpgrade } from "./IndustryUpgrades"; export interface IIndustry { - name: string; - type: string; - sciResearch: Material; - researched: {[key: string]: boolean | undefined}; - reqMats: {[key: string]: number | undefined}; + name: string; + type: string; + sciResearch: Material; + researched: { [key: string]: boolean | undefined }; + reqMats: { [key: string]: number | undefined }; - prodMats: string[]; + prodMats: string[]; - products: {[key: string]: Product | undefined}; - makesProducts: boolean; + products: { [key: string]: Product | undefined }; + makesProducts: boolean; - awareness: number; - popularity: number; - startingCost: number; + awareness: number; + popularity: number; + startingCost: number; - reFac: number; - sciFac: number; - hwFac: number; - robFac: number; - aiFac: number; - advFac: number; + reFac: number; + sciFac: number; + hwFac: number; + robFac: number; + aiFac: number; + advFac: number; - prodMult: number; + prodMult: number; - // Decimal - lastCycleRevenue: any; - lastCycleExpenses: any; - thisCycleRevenue: any; - thisCycleExpenses: any; + // Decimal + lastCycleRevenue: any; + lastCycleExpenses: any; + thisCycleRevenue: any; + thisCycleExpenses: any; - upgrades: number[]; + upgrades: number[]; - state: string; - newInd: boolean; - warehouses: {[key: string]: Warehouse | 0}; - offices: {[key: string]: OfficeSpace | 0}; + state: string; + newInd: boolean; + warehouses: { [key: string]: Warehouse | 0 }; + offices: { [key: string]: OfficeSpace | 0 }; - init(): void; - getProductDescriptionText(): string; - getMaximumNumberProducts(): number; - hasMaximumNumberProducts(): boolean; - calculateProductionFactors(): void; - updateWarehouseSizeUsed(warehouse: Warehouse): void; - process(marketCycles: number, state: string, corporation: ICorporation): void; - processMaterialMarket(): void; - processProductMarket(marketCycles: number): void; - processMaterials(marketCycles: number, corporation: ICorporation): [number, number]; - processProducts(marketCycles: number, corporation: ICorporation): [number, number]; - processProduct(marketCycles: number, product: Product, corporation: ICorporation): number; - discontinueProduct(product: Product): void; - upgrade(upgrade: IndustryUpgrade, refs: {corporation: ICorporation; office: OfficeSpace}): void; - getOfficeProductivity(office: OfficeSpace, params?: {forProduct?: boolean}): number; - getBusinessFactor(office: OfficeSpace): number; - getAdvertisingFactors(): [number, number, number, number]; - getMarketFactor(mat: {dmd: number; cmp: number}): number; - hasResearch(name: string): boolean; - updateResearchTree(): void; - getAdvertisingMultiplier(): number; - getEmployeeChaMultiplier(): number; - getEmployeeCreMultiplier(): number; - getEmployeeEffMultiplier(): number; - getEmployeeIntMultiplier(): number; - getProductionMultiplier(): number; - getProductProductionMultiplier(): number; - getSalesMultiplier(): number; - getScientificResearchMultiplier(): number; - getStorageMultiplier(): number; - toJSON(): any; -} \ No newline at end of file + init(): void; + getProductDescriptionText(): string; + getMaximumNumberProducts(): number; + hasMaximumNumberProducts(): boolean; + calculateProductionFactors(): void; + updateWarehouseSizeUsed(warehouse: Warehouse): void; + process(marketCycles: number, state: string, corporation: ICorporation): void; + processMaterialMarket(): void; + processProductMarket(marketCycles: number): void; + processMaterials( + marketCycles: number, + corporation: ICorporation, + ): [number, number]; + processProducts( + marketCycles: number, + corporation: ICorporation, + ): [number, number]; + processProduct( + marketCycles: number, + product: Product, + corporation: ICorporation, + ): number; + discontinueProduct(product: Product): void; + upgrade( + upgrade: IndustryUpgrade, + refs: { corporation: ICorporation; office: OfficeSpace }, + ): void; + getOfficeProductivity( + office: OfficeSpace, + params?: { forProduct?: boolean }, + ): number; + getBusinessFactor(office: OfficeSpace): number; + getAdvertisingFactors(): [number, number, number, number]; + getMarketFactor(mat: { dmd: number; cmp: number }): number; + hasResearch(name: string): boolean; + updateResearchTree(): void; + getAdvertisingMultiplier(): number; + getEmployeeChaMultiplier(): number; + getEmployeeCreMultiplier(): number; + getEmployeeEffMultiplier(): number; + getEmployeeIntMultiplier(): number; + getProductionMultiplier(): number; + getProductProductionMultiplier(): number; + getSalesMultiplier(): number; + getScientificResearchMultiplier(): number; + getStorageMultiplier(): number; + toJSON(): any; +} diff --git a/src/Corporation/Industry.ts b/src/Corporation/Industry.ts index 49c68203d..b9e5550f5 100644 --- a/src/Corporation/Industry.ts +++ b/src/Corporation/Industry.ts @@ -1,13 +1,15 @@ import { - Reviver, - Generic_toJSON, - Generic_fromJSON, + Reviver, + Generic_toJSON, + Generic_fromJSON, } from "../../utils/JSONReviver"; import { CityName } from "../Locations/data/CityNames"; -import Decimal from 'decimal.js'; -import { Industries, - IndustryStartingCosts, - IndustryResearchTrees } from "./IndustryData"; +import Decimal from "decimal.js"; +import { + Industries, + IndustryStartingCosts, + IndustryResearchTrees, +} from "./IndustryData"; import { CorporationConstants } from "./data/Constants"; import { EmployeePositions } from "./EmployeePositions"; import { Material } from "./Material"; @@ -21,1333 +23,1505 @@ import { MaterialSizes } from "./MaterialSizes"; import { Warehouse } from "./Warehouse"; import { ICorporation } from "./ICorporation"; import { IIndustry } from "./IIndustry"; -import { - IndustryUpgrade, - IndustryUpgrades } from "./IndustryUpgrades"; +import { IndustryUpgrade, IndustryUpgrades } from "./IndustryUpgrades"; import { formatNumber } from "../../utils/StringHelperFunctions"; interface IParams { - name?: string; - corp?: ICorporation; - type?: string; + name?: string; + corp?: ICorporation; + type?: string; } export class Industry implements IIndustry { - name = ""; - type = Industries.Agriculture; - sciResearch = new Material({name: "Scientific Research"}); - researched: {[key: string]: boolean | undefined} = {}; - reqMats: {[key: string]: number | undefined} = {}; + name = ""; + type = Industries.Agriculture; + sciResearch = new Material({ name: "Scientific Research" }); + researched: { [key: string]: boolean | undefined } = {}; + reqMats: { [key: string]: number | undefined } = {}; - //An array of the name of materials being produced - prodMats: string[] = []; + //An array of the name of materials being produced + prodMats: string[] = []; - products: {[key: string]: Product | undefined} = {}; - makesProducts = false; + products: { [key: string]: Product | undefined } = {}; + makesProducts = false; - awareness = 0; - popularity = 0; //Should always be less than awareness - startingCost = 0; + awareness = 0; + popularity = 0; //Should always be less than awareness + startingCost = 0; - /* The following are factors for how much production/other things are increased by + /* The following are factors for how much production/other things are increased by different factors. The production increase always has diminishing returns, and they are all reprsented by exponentials of < 1 (e.g x ^ 0.5, x ^ 0.8) The number for these represent the exponential. A lower number means more diminishing returns */ - reFac = 0; //Real estate Factor - sciFac = 0; //Scientific Research Factor, affects quality - hwFac = 0; //Hardware factor - robFac = 0; //Robotics Factor - aiFac = 0; //AI Cores factor; - advFac = 0; //Advertising factor, affects sales + reFac = 0; //Real estate Factor + sciFac = 0; //Scientific Research Factor, affects quality + hwFac = 0; //Hardware factor + robFac = 0; //Robotics Factor + aiFac = 0; //AI Cores factor; + advFac = 0; //Advertising factor, affects sales - prodMult = 0; //Production multiplier + prodMult = 0; //Production multiplier + + //Financials + lastCycleRevenue: any; + lastCycleExpenses: any; + thisCycleRevenue: any; + thisCycleExpenses: any; + + //Upgrades + upgrades: number[] = Array(Object.keys(IndustryUpgrades).length).fill(0); + + state = "START"; + newInd = true; + + //Maps locations to warehouses. 0 if no warehouse at that location + warehouses: { [key: string]: Warehouse | 0 }; + + //Maps locations to offices. 0 if no office at that location + offices: { [key: string]: OfficeSpace | 0 } = { + [CityName.Aevum]: 0, + [CityName.Chongqing]: 0, + [CityName.Sector12]: new OfficeSpace({ + loc: CityName.Sector12, + size: CorporationConstants.OfficeInitialSize, + }), + [CityName.NewTokyo]: 0, + [CityName.Ishima]: 0, + [CityName.Volhaven]: 0, + }; + + constructor(params: IParams = {}) { + this.name = params.name ? params.name : ""; + this.type = params.type ? params.type : Industries.Agriculture; //Financials - lastCycleRevenue: any; - lastCycleExpenses: any; - thisCycleRevenue: any; - thisCycleExpenses: any; + this.lastCycleRevenue = new Decimal(0); + this.lastCycleExpenses = new Decimal(0); + this.thisCycleRevenue = new Decimal(0); + this.thisCycleExpenses = new Decimal(0); - //Upgrades - upgrades: number[] = Array(Object.keys(IndustryUpgrades).length).fill(0); - - state = "START"; - newInd = true; - - //Maps locations to warehouses. 0 if no warehouse at that location - warehouses: {[key: string]: Warehouse | 0}; - - //Maps locations to offices. 0 if no office at that location - offices: {[key: string]: OfficeSpace | 0} = { - [CityName.Aevum]: 0, - [CityName.Chongqing]: 0, - [CityName.Sector12]: new OfficeSpace({ - loc: CityName.Sector12, - size: CorporationConstants.OfficeInitialSize, - }), - [CityName.NewTokyo]: 0, - [CityName.Ishima]: 0, - [CityName.Volhaven]: 0, + this.warehouses = { + [CityName.Aevum]: 0, + [CityName.Chongqing]: 0, + [CityName.Sector12]: new Warehouse({ + corp: params.corp, + industry: this, + loc: CityName.Sector12, + size: CorporationConstants.WarehouseInitialSize, + }), + [CityName.NewTokyo]: 0, + [CityName.Ishima]: 0, + [CityName.Volhaven]: 0, }; - constructor(params: IParams = {}) { - this.name = params.name ? params.name : ''; - this.type = params.type ? params.type : Industries.Agriculture; + this.init(); + } - //Financials - this.lastCycleRevenue = new Decimal(0); - this.lastCycleExpenses = new Decimal(0); - this.thisCycleRevenue = new Decimal(0); - this.thisCycleExpenses = new Decimal(0); - - this.warehouses = { - [CityName.Aevum]: 0, - [CityName.Chongqing]: 0, - [CityName.Sector12]: new Warehouse({ - corp: params.corp, - industry: this, - loc: CityName.Sector12, - size: CorporationConstants.WarehouseInitialSize, - }), - [CityName.NewTokyo]: 0, - [CityName.Ishima]: 0, - [CityName.Volhaven]: 0, + init(): void { + //Set the unique properties of an industry (how much its affected by real estate/scientific research, etc.) + const startingCost = IndustryStartingCosts[this.type]; + if (startingCost === undefined) + throw new Error(`Invalid industry: "${this.type}"`); + this.startingCost = startingCost; + switch (this.type) { + case Industries.Energy: + this.reFac = 0.65; + this.sciFac = 0.7; + this.robFac = 0.05; + this.aiFac = 0.3; + this.advFac = 0.08; + this.reqMats = { + Hardware: 0.1, + Metal: 0.2, }; + this.prodMats = ["Energy"]; + break; + case Industries.Utilities: + case "Utilities": + this.reFac = 0.5; + this.sciFac = 0.6; + this.robFac = 0.4; + this.aiFac = 0.4; + this.advFac = 0.08; + this.reqMats = { + Hardware: 0.1, + Metal: 0.1, + }; + this.prodMats = ["Water"]; + break; + case Industries.Agriculture: + this.reFac = 0.72; + this.sciFac = 0.5; + this.hwFac = 0.2; + this.robFac = 0.3; + this.aiFac = 0.3; + this.advFac = 0.04; + this.reqMats = { + Water: 0.5, + Energy: 0.5, + }; + this.prodMats = ["Plants", "Food"]; + break; + case Industries.Fishing: + this.reFac = 0.15; + this.sciFac = 0.35; + this.hwFac = 0.35; + this.robFac = 0.5; + this.aiFac = 0.2; + this.advFac = 0.08; + this.reqMats = { + Energy: 0.5, + }; + this.prodMats = ["Food"]; + break; + case Industries.Mining: + this.reFac = 0.3; + this.sciFac = 0.26; + this.hwFac = 0.4; + this.robFac = 0.45; + this.aiFac = 0.45; + this.advFac = 0.06; + this.reqMats = { + Energy: 0.8, + }; + this.prodMats = ["Metal"]; + break; + case Industries.Food: + //reFac is unique for this bc it diminishes greatly per city. Handle this separately in code? + this.sciFac = 0.12; + this.hwFac = 0.15; + this.robFac = 0.3; + this.aiFac = 0.25; + this.advFac = 0.25; + this.reFac = 0.05; + this.reqMats = { + Food: 0.5, + Water: 0.5, + Energy: 0.2, + }; + this.makesProducts = true; + break; + case Industries.Tobacco: + this.reFac = 0.15; + this.sciFac = 0.75; + this.hwFac = 0.15; + this.robFac = 0.2; + this.aiFac = 0.15; + this.advFac = 0.2; + this.reqMats = { + Plants: 1, + Water: 0.2, + }; + this.makesProducts = true; + break; + case Industries.Chemical: + this.reFac = 0.25; + this.sciFac = 0.75; + this.hwFac = 0.2; + this.robFac = 0.25; + this.aiFac = 0.2; + this.advFac = 0.07; + this.reqMats = { + Plants: 1, + Energy: 0.5, + Water: 0.5, + }; + this.prodMats = ["Chemicals"]; + break; + case Industries.Pharmaceutical: + this.reFac = 0.05; + this.sciFac = 0.8; + this.hwFac = 0.15; + this.robFac = 0.25; + this.aiFac = 0.2; + this.advFac = 0.16; + this.reqMats = { + Chemicals: 2, + Energy: 1, + Water: 0.5, + }; + this.prodMats = ["Drugs"]; + this.makesProducts = true; + break; + case Industries.Computer: + case "Computer": + this.reFac = 0.2; + this.sciFac = 0.62; + this.robFac = 0.36; + this.aiFac = 0.19; + this.advFac = 0.17; + this.reqMats = { + Metal: 2, + Energy: 1, + }; + this.prodMats = ["Hardware"]; + this.makesProducts = true; + break; + case Industries.Robotics: + this.reFac = 0.32; + this.sciFac = 0.65; + this.aiFac = 0.36; + this.advFac = 0.18; + this.hwFac = 0.19; + this.reqMats = { + Hardware: 5, + Energy: 3, + }; + this.prodMats = ["Robots"]; + this.makesProducts = true; + break; + case Industries.Software: + this.sciFac = 0.62; + this.advFac = 0.16; + this.hwFac = 0.25; + this.reFac = 0.15; + this.aiFac = 0.18; + this.robFac = 0.05; + this.reqMats = { + Hardware: 0.5, + Energy: 0.5, + }; + this.prodMats = ["AICores"]; + this.makesProducts = true; + break; + case Industries.Healthcare: + this.reFac = 0.1; + this.sciFac = 0.75; + this.advFac = 0.11; + this.hwFac = 0.1; + this.robFac = 0.1; + this.aiFac = 0.1; + this.reqMats = { + Robots: 10, + AICores: 5, + Energy: 5, + Water: 5, + }; + this.makesProducts = true; + break; + case Industries.RealEstate: + this.robFac = 0.6; + this.aiFac = 0.6; + this.advFac = 0.25; + this.sciFac = 0.05; + this.hwFac = 0.05; + this.reqMats = { + Metal: 5, + Energy: 5, + Water: 2, + Hardware: 4, + }; + this.prodMats = ["RealEstate"]; + this.makesProducts = true; + break; + default: + console.error( + `Invalid Industry Type passed into Industry.init(): ${this.type}`, + ); + return; + } + } - this.init(); + getProductDescriptionText(): string { + if (!this.makesProducts) return ""; + switch (this.type) { + case Industries.Food: + return "create and manage restaurants"; + case Industries.Tobacco: + return "create tobacco and tobacco-related products"; + case Industries.Pharmaceutical: + return "develop new pharmaceutical drugs"; + case Industries.Computer: + case "Computer": + return "create new computer hardware and networking infrastructures"; + case Industries.Robotics: + return "build specialized robots and robot-related products"; + case Industries.Software: + return "develop computer software"; + case Industries.Healthcare: + return "build and manage hospitals"; + case Industries.RealEstate: + return "develop and manage real estate properties"; + default: + console.error( + "Invalid industry type in Industry.getProductDescriptionText", + ); + return ""; + } + } + + getMaximumNumberProducts(): number { + if (!this.makesProducts) return 0; + + // Calculate additional number of allowed Products from Research/Upgrades + let additional = 0; + if (this.hasResearch("uPgrade: Capacity.I")) ++additional; + if (this.hasResearch("uPgrade: Capacity.II")) ++additional; + + return CorporationConstants.BaseMaxProducts + additional; + } + + hasMaximumNumberProducts(): boolean { + return Object.keys(this.products).length >= this.getMaximumNumberProducts(); + } + + //Calculates the values that factor into the production and properties of + //materials/products (such as quality, etc.) + calculateProductionFactors(): void { + let multSum = 0; + for (let i = 0; i < CorporationConstants.Cities.length; ++i) { + const city = CorporationConstants.Cities[i]; + const warehouse = this.warehouses[city]; + if (!(warehouse instanceof Warehouse)) { + continue; + } + + const materials = warehouse.materials; + + const cityMult = + Math.pow(0.002 * materials.RealEstate.qty + 1, this.reFac) * + Math.pow(0.002 * materials.Hardware.qty + 1, this.hwFac) * + Math.pow(0.002 * materials.Robots.qty + 1, this.robFac) * + Math.pow(0.002 * materials.AICores.qty + 1, this.aiFac); + multSum += Math.pow(cityMult, 0.73); } - init(): void { - //Set the unique properties of an industry (how much its affected by real estate/scientific research, etc.) - const startingCost = IndustryStartingCosts[this.type]; - if(startingCost === undefined) - throw new Error(`Invalid industry: "${this.type}"`); - this.startingCost = startingCost; - switch (this.type) { - case Industries.Energy: - this.reFac = 0.65; - this.sciFac = 0.7; - this.robFac = 0.05; - this.aiFac = 0.3; - this.advFac = 0.08; - this.reqMats = { - "Hardware": 0.1, - "Metal": 0.2, - }; - this.prodMats = ["Energy"]; - break; - case Industries.Utilities: - case "Utilities": - this.reFac = 0.5; - this.sciFac = 0.6; - this.robFac = 0.4; - this.aiFac = 0.4; - this.advFac = 0.08; - this.reqMats = { - "Hardware": 0.1, - "Metal": 0.1, - } - this.prodMats = ["Water"]; - break; - case Industries.Agriculture: - this.reFac = 0.72; - this.sciFac = 0.5; - this.hwFac = 0.2; - this.robFac = 0.3; - this.aiFac = 0.3; - this.advFac = 0.04; - this.reqMats = { - "Water": 0.5, - "Energy": 0.5, - } - this.prodMats = ["Plants", "Food"]; - break; - case Industries.Fishing: - this.reFac = 0.15; - this.sciFac = 0.35; - this.hwFac = 0.35; - this.robFac = 0.5; - this.aiFac = 0.2; - this.advFac = 0.08; - this.reqMats = { - "Energy": 0.5, - } - this.prodMats = ["Food"]; - break; - case Industries.Mining: - this.reFac = 0.3; - this.sciFac = 0.26; - this.hwFac = 0.4; - this.robFac = 0.45; - this.aiFac = 0.45; - this.advFac = 0.06; - this.reqMats = { - "Energy": 0.8, - } - this.prodMats = ["Metal"]; - break; - case Industries.Food: - //reFac is unique for this bc it diminishes greatly per city. Handle this separately in code? - this.sciFac = 0.12; - this.hwFac = 0.15; - this.robFac = 0.3; - this.aiFac = 0.25; - this.advFac = 0.25; - this.reFac = 0.05; - this.reqMats = { - "Food": 0.5, - "Water": 0.5, - "Energy": 0.2, - } - this.makesProducts = true; - break; - case Industries.Tobacco: - this.reFac = 0.15; - this.sciFac = 0.75; - this.hwFac = 0.15; - this.robFac = 0.2; - this.aiFac = 0.15; - this.advFac = 0.2; - this.reqMats = { - "Plants": 1, - "Water": 0.2, - } - this.makesProducts = true; - break; - case Industries.Chemical: - this.reFac = 0.25; - this.sciFac = 0.75; - this.hwFac = 0.2; - this.robFac = 0.25; - this.aiFac = 0.2; - this.advFac = 0.07; - this.reqMats = { - "Plants": 1, - "Energy": 0.5, - "Water": 0.5, - } - this.prodMats = ["Chemicals"]; - break; - case Industries.Pharmaceutical: - this.reFac = 0.05; - this.sciFac = 0.8; - this.hwFac = 0.15; - this.robFac = 0.25; - this.aiFac = 0.2; - this.advFac = 0.16; - this.reqMats = { - "Chemicals": 2, - "Energy": 1, - "Water": 0.5, - } - this.prodMats = ["Drugs"]; - this.makesProducts = true; - break; - case Industries.Computer: - case "Computer": - this.reFac = 0.2; - this.sciFac = 0.62; - this.robFac = 0.36; - this.aiFac = 0.19; - this.advFac = 0.17; - this.reqMats = { - "Metal": 2, - "Energy": 1, - } - this.prodMats = ["Hardware"]; - this.makesProducts = true; - break; - case Industries.Robotics: - this.reFac = 0.32; - this.sciFac = 0.65; - this.aiFac = 0.36; - this.advFac = 0.18; - this.hwFac = 0.19; - this.reqMats = { - "Hardware": 5, - "Energy": 3, - } - this.prodMats = ["Robots"]; - this.makesProducts = true; - break; - case Industries.Software: - this.sciFac = 0.62; - this.advFac = 0.16; - this.hwFac = 0.25; - this.reFac = 0.15; - this.aiFac = 0.18; - this.robFac = 0.05; - this.reqMats = { - "Hardware": 0.5, - "Energy": 0.5, - } - this.prodMats = ["AICores"]; - this.makesProducts = true; - break; - case Industries.Healthcare: - this.reFac = 0.1; - this.sciFac = 0.75; - this.advFac = 0.11; - this.hwFac = 0.1; - this.robFac = 0.1; - this.aiFac = 0.1; - this.reqMats = { - "Robots": 10, - "AICores": 5, - "Energy": 5, - "Water": 5, - } - this.makesProducts = true; - break; - case Industries.RealEstate: - this.robFac = 0.6; - this.aiFac = 0.6; - this.advFac = 0.25; - this.sciFac = 0.05; - this.hwFac = 0.05; - this.reqMats = { - "Metal": 5, - "Energy": 5, - "Water": 2, - "Hardware": 4, - } - this.prodMats = ["RealEstate"]; - this.makesProducts = true; - break; - default: - console.error(`Invalid Industry Type passed into Industry.init(): ${this.type}`); - return; + multSum < 1 ? (this.prodMult = 1) : (this.prodMult = multSum); + } + + updateWarehouseSizeUsed(warehouse: Warehouse): void { + warehouse.updateMaterialSizeUsed(); + + for (const prodName in this.products) { + if (this.products.hasOwnProperty(prodName)) { + const prod = this.products[prodName]; + if (prod === undefined) continue; + warehouse.sizeUsed += prod.data[warehouse.loc][0] * prod.siz; + if (prod.data[warehouse.loc][0] > 0) { + warehouse.breakdown += + prodName + + ": " + + formatNumber(prod.data[warehouse.loc][0] * prod.siz, 0) + + "
    "; } + } + } + } + + process(marketCycles = 1, state: string, corporation: ICorporation): void { + this.state = state; + + //At the start of a cycle, store and reset revenue/expenses + //Then calculate salaries and processs the markets + if (state === "START") { + if (isNaN(this.thisCycleRevenue) || isNaN(this.thisCycleExpenses)) { + console.error("NaN in Corporation's computed revenue/expenses"); + dialogBoxCreate( + "Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer", + ); + this.thisCycleRevenue = new Decimal(0); + this.thisCycleExpenses = new Decimal(0); + } + this.lastCycleRevenue = this.thisCycleRevenue.dividedBy( + marketCycles * CorporationConstants.SecsPerMarketCycle, + ); + this.lastCycleExpenses = this.thisCycleExpenses.dividedBy( + marketCycles * CorporationConstants.SecsPerMarketCycle, + ); + this.thisCycleRevenue = new Decimal(0); + this.thisCycleExpenses = new Decimal(0); + + // Once you start making revenue, the player should no longer be + // considered new, and therefore no longer needs the 'tutorial' UI elements + if (this.lastCycleRevenue.gt(0)) { + this.newInd = false; + } + + // Process offices (and the employees in them) + let employeeSalary = 0; + for (const officeLoc in this.offices) { + const office = this.offices[officeLoc]; + if (office === 0) continue; + if (office instanceof OfficeSpace) { + employeeSalary += office.process(marketCycles, corporation, this); + } + } + this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary); + + // Process change in demand/competition of materials/products + this.processMaterialMarket(); + this.processProductMarket(marketCycles); + + // Process loss of popularity + this.popularity -= marketCycles * 0.0001; + this.popularity = Math.max(0, this.popularity); + + // Process Dreamsense gains + const popularityGain = corporation.getDreamSenseGain(), + awarenessGain = popularityGain * 4; + if (popularityGain > 0) { + this.popularity += popularityGain * marketCycles; + this.awareness += awarenessGain * marketCycles; + } + + return; } - getProductDescriptionText(): string { - if (!this.makesProducts) return ''; - switch (this.type) { - case Industries.Food: - return "create and manage restaurants"; - case Industries.Tobacco: - return "create tobacco and tobacco-related products"; - case Industries.Pharmaceutical: - return "develop new pharmaceutical drugs"; - case Industries.Computer: - case "Computer": - return "create new computer hardware and networking infrastructures"; - case Industries.Robotics: - return "build specialized robots and robot-related products"; - case Industries.Software: - return "develop computer software"; - case Industries.Healthcare: - return "build and manage hospitals"; - case Industries.RealEstate: - return "develop and manage real estate properties"; - default: - console.error("Invalid industry type in Industry.getProductDescriptionText"); - return ""; - } + // Process production, purchase, and import/export of materials + let res = this.processMaterials(marketCycles, corporation); + if (Array.isArray(res)) { + this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); + this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); } - getMaximumNumberProducts(): number { - if (!this.makesProducts) return 0; + // Process creation, production & sale of products + res = this.processProducts(marketCycles, corporation); + if (Array.isArray(res)) { + this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); + this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); + } + } - // Calculate additional number of allowed Products from Research/Upgrades - let additional = 0; - if (this.hasResearch("uPgrade: Capacity.I")) ++additional; - if (this.hasResearch("uPgrade: Capacity.II")) ++additional; + // Process change in demand and competition for this industry's materials + processMaterialMarket(): void { + //References to prodMats and reqMats + const reqMats = this.reqMats, + prodMats = this.prodMats; - return CorporationConstants.BaseMaxProducts + additional; + //Only 'process the market' for materials that this industry deals with + for (let i = 0; i < CorporationConstants.Cities.length; ++i) { + //If this industry has a warehouse in this city, process the market + //for every material this industry requires or produces + if ( + this.warehouses[CorporationConstants.Cities[i]] instanceof Warehouse + ) { + const wh = this.warehouses[CorporationConstants.Cities[i]]; + if (wh === 0) continue; + for (const name in reqMats) { + if (reqMats.hasOwnProperty(name)) { + wh.materials[name].processMarket(); + } + } + + //Produced materials are stored in an array + for (let foo = 0; foo < prodMats.length; ++foo) { + wh.materials[prodMats[foo]].processMarket(); + } + + //Process these twice because these boost production + wh.materials["Hardware"].processMarket(); + wh.materials["Robots"].processMarket(); + wh.materials["AICores"].processMarket(); + wh.materials["RealEstate"].processMarket(); + } + } + } + + // Process change in demand and competition for this industry's products + processProductMarket(marketCycles = 1): void { + // Demand gradually decreases, and competition gradually increases + for (const name in this.products) { + if (this.products.hasOwnProperty(name)) { + const product = this.products[name]; + if (product === undefined) continue; + let change = getRandomInt(0, 3) * 0.0004; + if (change === 0) continue; + + if ( + this.type === Industries.Pharmaceutical || + this.type === Industries.Software || + this.type === Industries.Robotics + ) { + change *= 3; + } + change *= marketCycles; + product.dmd -= change; + product.cmp += change; + product.cmp = Math.min(product.cmp, 99.99); + product.dmd = Math.max(product.dmd, 0.001); + } + } + } + + //Process production, purchase, and import/export of materials + processMaterials( + marketCycles = 1, + corporation: ICorporation, + ): [number, number] { + let revenue = 0, + expenses = 0; + this.calculateProductionFactors(); + + //At the start of the export state, set the imports of everything to 0 + if (this.state === "EXPORT") { + for (let i = 0; i < CorporationConstants.Cities.length; ++i) { + const city = CorporationConstants.Cities[i]; + if (!(this.warehouses[city] instanceof Warehouse)) { + continue; + } + const warehouse = this.warehouses[city]; + if (warehouse === 0) continue; + for (const matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + const mat = warehouse.materials[matName]; + mat.imp = 0; + } + } + } } - hasMaximumNumberProducts(): boolean { - return (Object.keys(this.products).length >= this.getMaximumNumberProducts()); - } + for (let i = 0; i < CorporationConstants.Cities.length; ++i) { + const city = CorporationConstants.Cities[i]; + const office = this.offices[city]; + if (office === 0) continue; - //Calculates the values that factor into the production and properties of - //materials/products (such as quality, etc.) - calculateProductionFactors(): void { - let multSum = 0; - for (let i = 0; i < CorporationConstants.Cities.length; ++i) { - const city = CorporationConstants.Cities[i]; - const warehouse = this.warehouses[city]; - if (!(warehouse instanceof Warehouse)) { - continue; - } + if (this.warehouses[city] instanceof Warehouse) { + const warehouse = this.warehouses[city]; + if (warehouse === 0) continue; - const materials = warehouse.materials; + switch (this.state) { + case "PURCHASE": + /* Process purchase of materials */ + for (const matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + (function (matName, ind) { + const mat = warehouse.materials[matName]; + let buyAmt, maxAmt; + if ( + warehouse.smartSupplyEnabled && + Object.keys(ind.reqMats).includes(matName) + ) { + //Smart supply tracker is stored as per second rate + const reqMat = ind.reqMats[matName]; + if (reqMat === undefined) + throw new Error(`reqMat "${matName}" is undefined`); + mat.buy = reqMat * warehouse.smartSupplyStore; + buyAmt = + mat.buy * + CorporationConstants.SecsPerMarketCycle * + marketCycles; + } else { + buyAmt = + mat.buy * + CorporationConstants.SecsPerMarketCycle * + marketCycles; + } - const cityMult = Math.pow(0.002 * materials.RealEstate.qty+1, this.reFac) * - Math.pow(0.002 * materials.Hardware.qty+1, this.hwFac) * - Math.pow(0.002 * materials.Robots.qty+1, this.robFac) * - Math.pow(0.002 * materials.AICores.qty+1, this.aiFac); - multSum += Math.pow(cityMult, 0.73); - } - - multSum < 1 ? this.prodMult = 1 : this.prodMult = multSum; - } - - updateWarehouseSizeUsed(warehouse: Warehouse): void { - warehouse.updateMaterialSizeUsed(); - - for (const prodName in this.products) { - if (this.products.hasOwnProperty(prodName)) { - const prod = this.products[prodName]; - if(prod === undefined) continue; - warehouse.sizeUsed += (prod.data[warehouse.loc][0] * prod.siz); - if (prod.data[warehouse.loc][0] > 0) { - warehouse.breakdown += (prodName + ": " + formatNumber(prod.data[warehouse.loc][0] * prod.siz, 0) + "
    "); - } - } - } - } - - process(marketCycles=1, state: string, corporation: ICorporation): void { - this.state = state; - - //At the start of a cycle, store and reset revenue/expenses - //Then calculate salaries and processs the markets - if (state === "START") { - if (isNaN(this.thisCycleRevenue) || isNaN(this.thisCycleExpenses)) { - console.error("NaN in Corporation's computed revenue/expenses"); - dialogBoxCreate("Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer"); - this.thisCycleRevenue = new Decimal(0); - this.thisCycleExpenses = new Decimal(0); - } - this.lastCycleRevenue = this.thisCycleRevenue.dividedBy(marketCycles * CorporationConstants.SecsPerMarketCycle); - this.lastCycleExpenses = this.thisCycleExpenses.dividedBy(marketCycles * CorporationConstants.SecsPerMarketCycle); - this.thisCycleRevenue = new Decimal(0); - this.thisCycleExpenses = new Decimal(0); - - // Once you start making revenue, the player should no longer be - // considered new, and therefore no longer needs the 'tutorial' UI elements - if (this.lastCycleRevenue.gt(0)) {this.newInd = false;} - - // Process offices (and the employees in them) - let employeeSalary = 0; - for (const officeLoc in this.offices) { - const office = this.offices[officeLoc]; - if(office === 0) continue; - if (office instanceof OfficeSpace) { - employeeSalary += office.process(marketCycles, corporation, this); - } - } - this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary); - - // Process change in demand/competition of materials/products - this.processMaterialMarket(); - this.processProductMarket(marketCycles); - - // Process loss of popularity - this.popularity -= (marketCycles * .0001); - this.popularity = Math.max(0, this.popularity); - - // Process Dreamsense gains - const popularityGain = corporation.getDreamSenseGain(), awarenessGain = popularityGain * 4; - if (popularityGain > 0) { - this.popularity += (popularityGain * marketCycles); - this.awareness += (awarenessGain * marketCycles); - } - - return; - } - - // Process production, purchase, and import/export of materials - let res = this.processMaterials(marketCycles, corporation); - if (Array.isArray(res)) { - this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); - this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); - } - - // Process creation, production & sale of products - res = this.processProducts(marketCycles, corporation); - if (Array.isArray(res)) { - this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); - this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); - } - } - - // Process change in demand and competition for this industry's materials - processMaterialMarket(): void { - //References to prodMats and reqMats - const reqMats = this.reqMats, prodMats = this.prodMats; - - //Only 'process the market' for materials that this industry deals with - for (let i = 0; i < CorporationConstants.Cities.length; ++i) { - //If this industry has a warehouse in this city, process the market - //for every material this industry requires or produces - if (this.warehouses[CorporationConstants.Cities[i]] instanceof Warehouse) { - const wh = this.warehouses[CorporationConstants.Cities[i]]; - if(wh === 0) continue; - for (const name in reqMats) { - if (reqMats.hasOwnProperty(name)) { - wh.materials[name].processMarket(); - } - } - - //Produced materials are stored in an array - for (let foo = 0; foo < prodMats.length; ++foo) { - wh.materials[prodMats[foo]].processMarket(); - } - - //Process these twice because these boost production - wh.materials["Hardware"].processMarket(); - wh.materials["Robots"].processMarket(); - wh.materials["AICores"].processMarket(); - wh.materials["RealEstate"].processMarket(); - } - } - } - - // Process change in demand and competition for this industry's products - processProductMarket(marketCycles=1): void { - // Demand gradually decreases, and competition gradually increases - for (const name in this.products) { - if (this.products.hasOwnProperty(name)) { - const product = this.products[name]; - if(product === undefined) continue; - let change = getRandomInt(0, 3) * 0.0004; - if (change === 0) continue; - - if (this.type === Industries.Pharmaceutical || this.type === Industries.Software || - this.type === Industries.Robotics) { - change *= 3; - } - change *= marketCycles; - product.dmd -= change; - product.cmp += change; - product.cmp = Math.min(product.cmp, 99.99); - product.dmd = Math.max(product.dmd, 0.001); - } - } - } - - //Process production, purchase, and import/export of materials - processMaterials(marketCycles=1, corporation: ICorporation): [number, number] { - let revenue = 0, expenses = 0; - this.calculateProductionFactors(); - - //At the start of the export state, set the imports of everything to 0 - if (this.state === "EXPORT") { - for (let i = 0; i < CorporationConstants.Cities.length; ++i) { - const city = CorporationConstants.Cities[i]; - if (!(this.warehouses[city] instanceof Warehouse)) { - continue; - } - const warehouse = this.warehouses[city]; - if(warehouse === 0) continue; - for (const matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - const mat = warehouse.materials[matName]; - mat.imp = 0; - } - } - } - } - - for (let i = 0; i < CorporationConstants.Cities.length; ++i) { - const city = CorporationConstants.Cities[i]; - const office = this.offices[city]; - if(office === 0) continue; - - if (this.warehouses[city] instanceof Warehouse) { - const warehouse = this.warehouses[city]; - if(warehouse === 0) continue; - - switch(this.state) { - - case "PURCHASE": - /* Process purchase of materials */ - for (const matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - (function(matName, ind) { - const mat = warehouse.materials[matName]; - let buyAmt, maxAmt; - if (warehouse.smartSupplyEnabled && Object.keys(ind.reqMats).includes(matName)) { - //Smart supply tracker is stored as per second rate - const reqMat = ind.reqMats[matName]; - if(reqMat === undefined) - throw new Error(`reqMat "${matName}" is undefined`); - mat.buy = reqMat * warehouse.smartSupplyStore; - buyAmt = mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles; - } else { - buyAmt = (mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles); - } - - if (matName == "RealEstate") { - maxAmt = buyAmt; - } else { - maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]); - } - buyAmt = Math.min(buyAmt, maxAmt); - if (buyAmt > 0) { - mat.qty += buyAmt; - expenses += (buyAmt * mat.bCost); - } - })(matName, this); - this.updateWarehouseSizeUsed(warehouse); - } - } //End process purchase of materials - break; - - case "PRODUCTION": - warehouse.smartSupplyStore = 0; //Reset smart supply amount - - /* Process production of materials */ - if (this.prodMats.length > 0) { - const mat = warehouse.materials[this.prodMats[0]]; - //Calculate the maximum production of this material based - //on the office's productivity - const maxProd = this.getOfficeProductivity(office) - * this.prodMult // Multiplier from materials - * corporation.getProductionMultiplier() - * this.getProductionMultiplier(); // Multiplier from Research - let prod; - - if (mat.prdman[0]) { - //Production is manually limited - prod = Math.min(maxProd, mat.prdman[1]); - } else { - prod = maxProd; - } - prod *= (CorporationConstants.SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle - - // Calculate net change in warehouse storage making the produced materials will cost - let totalMatSize = 0; - for (let tmp = 0; tmp < this.prodMats.length; ++tmp) { - totalMatSize += (MaterialSizes[this.prodMats[tmp]]); - } - for (const reqMatName in this.reqMats) { - const normQty = this.reqMats[reqMatName]; - if(normQty === undefined) continue; - totalMatSize -= (MaterialSizes[reqMatName] * normQty); - } - // If not enough space in warehouse, limit the amount of produced materials - if (totalMatSize > 0) { - const maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize); - prod = Math.min(maxAmt, prod); - } - - if (prod < 0) {prod = 0;} - - // Keep track of production for smart supply (/s) - warehouse.smartSupplyStore += (prod / (CorporationConstants.SecsPerMarketCycle * marketCycles)); - - // Make sure we have enough resource to make our materials - let producableFrac = 1; - for (const reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - const reqMat = this.reqMats[reqMatName]; - if(reqMat === undefined) continue; - const req = reqMat * prod; - if (warehouse.materials[reqMatName].qty < req) { - producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); - } - } - } - if (producableFrac <= 0) {producableFrac = 0; prod = 0;} - - // Make our materials if they are producable - if (producableFrac > 0 && prod > 0) { - for (const reqMatName in this.reqMats) { - const reqMat = this.reqMats[reqMatName]; - if(reqMat === undefined) continue; - const reqMatQtyNeeded = (reqMat * prod * producableFrac); - warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; - warehouse.materials[reqMatName].prd = 0; - warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (CorporationConstants.SecsPerMarketCycle * marketCycles); - } - for (let j = 0; j < this.prodMats.length; ++j) { - warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac); - warehouse.materials[this.prodMats[j]].qlt = - (office.employeeProd[EmployeePositions.Engineer] / 90 + - Math.pow(this.sciResearch.qty, this.sciFac) + - Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3); - } - } else { - for (const reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - warehouse.materials[reqMatName].prd = 0; - } - } - } - - //Per second - const fooProd = prod * producableFrac / (CorporationConstants.SecsPerMarketCycle * marketCycles); - for (let fooI = 0; fooI < this.prodMats.length; ++fooI) { - warehouse.materials[this.prodMats[fooI]].prd = fooProd; - } - } else { - //If this doesn't produce any materials, then it only creates - //Products. Creating products will consume materials. The - //Production of all consumed materials must be set to 0 - for (const reqMatName in this.reqMats) { - warehouse.materials[reqMatName].prd = 0; - } - } - break; - - case "SALE": - /* Process sale of materials */ - for (const matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - const mat = warehouse.materials[matName]; - if (mat.sCost < 0 || mat.sllman[0] === false) { - mat.sll = 0; - continue; - } - - // Sale multipliers - const businessFactor = this.getBusinessFactor(office); //Business employee productivity - const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity - const marketFactor = this.getMarketFactor(mat); //Competition + demand - - // Determine the cost that the material will be sold at - const markupLimit = mat.getMarkupLimit(); - let sCost; - if (mat.marketTa2) { - const prod = mat.prd; - - // Reverse engineer the 'maxSell' formula - // 1. Set 'maxSell' = prod - // 2. Substitute formula for 'markup' - // 3. Solve for 'sCost' - const numerator = markupLimit; - const sqrtNumerator = prod; - const sqrtDenominator = ((mat.qlt + .001) - * marketFactor - * businessFactor - * corporation.getSalesMultiplier() - * advertisingFactor - * this.getSalesMultiplier()); - const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); - let optimalPrice; - if (sqrtDenominator === 0 || denominator === 0) { - if (sqrtNumerator === 0) { - optimalPrice = 0; // No production - } else { - optimalPrice = mat.bCost + markupLimit; - console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`); - } - } else { - optimalPrice = (numerator / denominator) + mat.bCost; - } - - // We'll store this "Optimal Price" in a property so that we don't have - // to re-calculate it for the UI - mat.marketTa2Price = optimalPrice; - - sCost = optimalPrice; - } else if (mat.marketTa1) { - sCost = mat.bCost + markupLimit; - } else if (isString(mat.sCost)) { - sCost = (mat.sCost as string).replace(/MP/g, mat.bCost+''); - sCost = eval(sCost); - } else { - sCost = mat.sCost; - } - - // Calculate how much of the material sells (per second) - let markup = 1; - if (sCost > mat.bCost) { - //Penalty if difference between sCost and bCost is greater than markup limit - if ((sCost - mat.bCost) > markupLimit) { - markup = Math.pow(markupLimit / (sCost - mat.bCost), 2); - } - } else if (sCost < mat.bCost) { - if (sCost <= 0) { - markup = 1e12; //Sell everything, essentially discard - } else { - //Lower prices than market increases sales - markup = mat.bCost / sCost; - } - } - - const maxSell = (mat.qlt + .001) - * marketFactor - * markup - * businessFactor - * corporation.getSalesMultiplier() - * advertisingFactor - * this.getSalesMultiplier(); - let sellAmt; - if (isString(mat.sllman[1])) { - //Dynamically evaluated - let tmp = (mat.sllman[1] as string).replace(/MAX/g, maxSell+''); - tmp = tmp.replace(/PROD/g, mat.prd+''); - try { - sellAmt = eval(tmp); - } catch(e) { - dialogBoxCreate("Error evaluating your sell amount for material " + mat.name + - " in " + this.name + "'s " + city + " office. The sell amount " + - "is being set to zero"); - sellAmt = 0; - } - sellAmt = Math.min(maxSell, sellAmt); - } else if (mat.sllman[1] === -1) { - //Backwards compatibility, -1 = MAX - sellAmt = maxSell; - } else { - //Player's input value is just a number - sellAmt = Math.min(maxSell, mat.sllman[1] as number); - } - - sellAmt = (sellAmt * CorporationConstants.SecsPerMarketCycle * marketCycles); - sellAmt = Math.min(mat.qty, sellAmt); - if (sellAmt < 0) { - console.warn(`sellAmt calculated to be negative for ${matName} in ${city}`); - mat.sll = 0; - continue; - } - if (sellAmt && sCost >= 0) { - mat.qty -= sellAmt; - revenue += (sellAmt * sCost); - mat.sll = sellAmt / (CorporationConstants.SecsPerMarketCycle * marketCycles); - } else { - mat.sll = 0; - } - } - } //End processing of sale of materials - break; - - case "EXPORT": - for (const matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - const mat = warehouse.materials[matName]; - mat.totalExp = 0; //Reset export - for (let expI = 0; expI < mat.exp.length; ++expI) { - const exp = mat.exp[expI]; - const amtStr = exp.amt.replace(/MAX/g, (mat.qty / (CorporationConstants.SecsPerMarketCycle * marketCycles))+''); - let amt = 0; - try { - amt = eval(amtStr); - } catch(e) { - dialogBoxCreate("Calculating export for " + mat.name + " in " + - this.name + "'s " + city + " division failed with " + - "error: " + e); - continue; - } - if (isNaN(amt)) { - dialogBoxCreate("Error calculating export amount for " + mat.name + " in " + - this.name + "'s " + city + " division."); - continue; - } - amt = amt * CorporationConstants.SecsPerMarketCycle * marketCycles; - - if (mat.qty < amt) { - amt = mat.qty; - } - if (amt === 0) { - break; //None left - } - for (let foo = 0; foo < corporation.divisions.length; ++foo) { - if (corporation.divisions[foo].name === exp.ind) { - const expIndustry = corporation.divisions[foo]; - const expWarehouse = expIndustry.warehouses[exp.city]; - if (!(expWarehouse instanceof Warehouse)) { - console.error(`Invalid export! ${expIndustry.name} ${exp.city}`); - break; - } - - // Make sure theres enough space in warehouse - if (expWarehouse.sizeUsed >= expWarehouse.size) { - // Warehouse at capacity. Exporting doesnt - // affect revenue so just return 0's - return [0, 0]; - } else { - const maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]); - amt = Math.min(maxAmt, amt); - } - expWarehouse.materials[matName].imp += (amt / (CorporationConstants.SecsPerMarketCycle * marketCycles)); - expWarehouse.materials[matName].qty += amt; - expWarehouse.materials[matName].qlt = mat.qlt; - mat.qty -= amt; - mat.totalExp += amt; - expIndustry.updateWarehouseSizeUsed(expWarehouse); - break; - } - } - } - //totalExp should be per second - mat.totalExp /= (CorporationConstants.SecsPerMarketCycle * marketCycles); - } - } - - break; - - case "START": - break; - default: - console.error(`Invalid state: ${this.state}`); - break; - } //End switch(this.state) + if (matName == "RealEstate") { + maxAmt = buyAmt; + } else { + maxAmt = Math.floor( + (warehouse.size - warehouse.sizeUsed) / + MaterialSizes[matName], + ); + } + buyAmt = Math.min(buyAmt, maxAmt); + if (buyAmt > 0) { + mat.qty += buyAmt; + expenses += buyAmt * mat.bCost; + } + })(matName, this); this.updateWarehouseSizeUsed(warehouse); + } + } //End process purchase of materials + break; - } // End warehouse + case "PRODUCTION": + warehouse.smartSupplyStore = 0; //Reset smart supply amount - //Produce Scientific Research based on R&D employees - //Scientific Research can be produced without a warehouse - if (office instanceof OfficeSpace) { - this.sciResearch.qty += (.004 - * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5) - * corporation.getScientificResearchMultiplier() - * this.getScientificResearchMultiplier()); + /* Process production of materials */ + if (this.prodMats.length > 0) { + const mat = warehouse.materials[this.prodMats[0]]; + //Calculate the maximum production of this material based + //on the office's productivity + const maxProd = + this.getOfficeProductivity(office) * + this.prodMult * // Multiplier from materials + corporation.getProductionMultiplier() * + this.getProductionMultiplier(); // Multiplier from Research + let prod; + + if (mat.prdman[0]) { + //Production is manually limited + prod = Math.min(maxProd, mat.prdman[1]); + } else { + prod = maxProd; + } + prod *= CorporationConstants.SecsPerMarketCycle * marketCycles; //Convert production from per second to per market cycle + + // Calculate net change in warehouse storage making the produced materials will cost + let totalMatSize = 0; + for (let tmp = 0; tmp < this.prodMats.length; ++tmp) { + totalMatSize += MaterialSizes[this.prodMats[tmp]]; + } + for (const reqMatName in this.reqMats) { + const normQty = this.reqMats[reqMatName]; + if (normQty === undefined) continue; + totalMatSize -= MaterialSizes[reqMatName] * normQty; + } + // If not enough space in warehouse, limit the amount of produced materials + if (totalMatSize > 0) { + const maxAmt = Math.floor( + (warehouse.size - warehouse.sizeUsed) / totalMatSize, + ); + prod = Math.min(maxAmt, prod); + } + + if (prod < 0) { + prod = 0; + } + + // Keep track of production for smart supply (/s) + warehouse.smartSupplyStore += + prod / (CorporationConstants.SecsPerMarketCycle * marketCycles); + + // Make sure we have enough resource to make our materials + let producableFrac = 1; + for (const reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + const reqMat = this.reqMats[reqMatName]; + if (reqMat === undefined) continue; + const req = reqMat * prod; + if (warehouse.materials[reqMatName].qty < req) { + producableFrac = Math.min( + producableFrac, + warehouse.materials[reqMatName].qty / req, + ); + } + } + } + if (producableFrac <= 0) { + producableFrac = 0; + prod = 0; + } + + // Make our materials if they are producable + if (producableFrac > 0 && prod > 0) { + for (const reqMatName in this.reqMats) { + const reqMat = this.reqMats[reqMatName]; + if (reqMat === undefined) continue; + const reqMatQtyNeeded = reqMat * prod * producableFrac; + warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; + warehouse.materials[reqMatName].prd = 0; + warehouse.materials[reqMatName].prd -= + reqMatQtyNeeded / + (CorporationConstants.SecsPerMarketCycle * marketCycles); + } + for (let j = 0; j < this.prodMats.length; ++j) { + warehouse.materials[this.prodMats[j]].qty += + prod * producableFrac; + warehouse.materials[this.prodMats[j]].qlt = + office.employeeProd[EmployeePositions.Engineer] / 90 + + Math.pow(this.sciResearch.qty, this.sciFac) + + Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / + 10e3; + } + } else { + for (const reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + warehouse.materials[reqMatName].prd = 0; + } + } + } + + //Per second + const fooProd = + (prod * producableFrac) / + (CorporationConstants.SecsPerMarketCycle * marketCycles); + for (let fooI = 0; fooI < this.prodMats.length; ++fooI) { + warehouse.materials[this.prodMats[fooI]].prd = fooProd; + } + } else { + //If this doesn't produce any materials, then it only creates + //Products. Creating products will consume materials. The + //Production of all consumed materials must be set to 0 + for (const reqMatName in this.reqMats) { + warehouse.materials[reqMatName].prd = 0; + } } - } - return [revenue, expenses]; - } + break; - //Process production & sale of this industry's FINISHED products (including all of their stats) - processProducts(marketCycles=1, corporation: ICorporation): [number, number] { - let revenue = 0; - const expenses = 0; - - //Create products - if (this.state === "PRODUCTION") { - for (const prodName in this.products) { - const prod = this.products[prodName]; - if(prod === undefined) continue; - if (!prod.fin) { - const city = prod.createCity; - const office = this.offices[city]; - if(office === 0) continue; - - // Designing/Creating a Product is based mostly off Engineers - const engrProd = office.employeeProd[EmployeePositions.Engineer]; - const mgmtProd = office.employeeProd[EmployeePositions.Management]; - const opProd = office.employeeProd[EmployeePositions.Operations]; - const total = engrProd + mgmtProd + opProd; - if (total <= 0) { break; } - - // Management is a multiplier for the production from Engineers - const mgmtFactor = 1 + (mgmtProd / (1.2 * total)); - - const progress = (Math.pow(engrProd, 0.34) + Math.pow(opProd, 0.2)) * mgmtFactor; - - prod.createProduct(marketCycles, progress); - if (prod.prog >= 100) { - prod.finishProduct(office.employeeProd, this); - } - break; + case "SALE": + /* Process sale of materials */ + for (const matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + const mat = warehouse.materials[matName]; + if (mat.sCost < 0 || mat.sllman[0] === false) { + mat.sll = 0; + continue; } - } - } - - //Produce Products - for (const prodName in this.products) { - if (this.products.hasOwnProperty(prodName)) { - const prod = this.products[prodName]; - if (prod instanceof Product && prod.fin) { - revenue += this.processProduct(marketCycles, prod, corporation); - } - } - } - return [revenue, expenses]; - } - - //Processes FINISHED products - processProduct(marketCycles=1, product: Product, corporation: ICorporation): number { - let totalProfit = 0; - for (let i = 0; i < CorporationConstants.Cities.length; ++i) { - const city = CorporationConstants.Cities[i]; - const office = this.offices[city]; - if(office === 0) continue; - const warehouse = this.warehouses[city]; - if (warehouse instanceof Warehouse) { - switch(this.state) { - - case "PRODUCTION": { - //Calculate the maximum production of this material based - //on the office's productivity - const maxProd = this.getOfficeProductivity(office, {forProduct:true}) - * corporation.getProductionMultiplier() - * this.prodMult // Multiplier from materials - * this.getProductionMultiplier() // Multiplier from research - * this.getProductProductionMultiplier(); // Multiplier from research - let prod; - - //Account for whether production is manually limited - if (product.prdman[city][0]) { - prod = Math.min(maxProd, product.prdman[city][1]); - } else { - prod = maxProd; - } - prod *= (CorporationConstants.SecsPerMarketCycle * marketCycles); - - //Calculate net change in warehouse storage making the Products will cost - let netStorageSize = product.siz; - for (const reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - const normQty = product.reqMats[reqMatName]; - netStorageSize -= (MaterialSizes[reqMatName] * normQty); - } - } - - //If there's not enough space in warehouse, limit the amount of Product - if (netStorageSize > 0) { - const maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / netStorageSize); - prod = Math.min(maxAmt, prod); - } - - warehouse.smartSupplyStore += (prod / (CorporationConstants.SecsPerMarketCycle * marketCycles)); - - //Make sure we have enough resources to make our Products - let producableFrac = 1; - for (const reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - const req = product.reqMats[reqMatName] * prod; - if (warehouse.materials[reqMatName].qty < req) { - producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); - } - } - } - - //Make our Products if they are producable - if (producableFrac > 0 && prod > 0) { - for (const reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - const reqMatQtyNeeded = (product.reqMats[reqMatName] * prod * producableFrac); - warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; - warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (CorporationConstants.SecsPerMarketCycle * marketCycles); - } - } - //Quantity - product.data[city][0] += (prod * producableFrac); - } - - //Keep track of production Per second - product.data[city][1] = prod * producableFrac / (CorporationConstants.SecsPerMarketCycle * marketCycles); - break; - } - case "SALE": { - //Process sale of Products - product.pCost = 0; //Estimated production cost - for (const reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - product.pCost += (product.reqMats[reqMatName] * warehouse.materials[reqMatName].bCost); - } - } - - // Since its a product, its production cost is increased for labor - product.pCost *= CorporationConstants.ProductProductionCostRatio; // Sale multipliers - const businessFactor = this.getBusinessFactor(office); //Business employee productivity - const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity - const marketFactor = this.getMarketFactor(product); //Competition + demand + const businessFactor = this.getBusinessFactor(office); //Business employee productivity + const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity + const marketFactor = this.getMarketFactor(mat); //Competition + demand - // Calculate Sale Cost (sCost), which could be dynamically evaluated - const markupLimit = product.rat / product.mku; + // Determine the cost that the material will be sold at + const markupLimit = mat.getMarkupLimit(); let sCost; - if (product.marketTa2) { - const prod = product.data[city][1]; + if (mat.marketTa2) { + const prod = mat.prd; - // Reverse engineer the 'maxSell' formula - // 1. Set 'maxSell' = prod - // 2. Substitute formula for 'markup' - // 3. Solve for 'sCost'roduct.pCost = sCost - const numerator = markupLimit; - const sqrtNumerator = prod; - const sqrtDenominator = (0.5 - * Math.pow(product.rat, 0.65) - * marketFactor - * corporation.getSalesMultiplier() - * businessFactor - * advertisingFactor - * this.getSalesMultiplier()); - const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); - let optimalPrice; - if (sqrtDenominator === 0 || denominator === 0) { - if (sqrtNumerator === 0) { - optimalPrice = 0; // No production - } else { - optimalPrice = product.pCost + markupLimit; - console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`); - } + // Reverse engineer the 'maxSell' formula + // 1. Set 'maxSell' = prod + // 2. Substitute formula for 'markup' + // 3. Solve for 'sCost' + const numerator = markupLimit; + const sqrtNumerator = prod; + const sqrtDenominator = + (mat.qlt + 0.001) * + marketFactor * + businessFactor * + corporation.getSalesMultiplier() * + advertisingFactor * + this.getSalesMultiplier(); + const denominator = Math.sqrt( + sqrtNumerator / sqrtDenominator, + ); + let optimalPrice; + if (sqrtDenominator === 0 || denominator === 0) { + if (sqrtNumerator === 0) { + optimalPrice = 0; // No production } else { - optimalPrice = (numerator / denominator) + product.pCost; + optimalPrice = mat.bCost + markupLimit; + console.warn( + `In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`, + ); } + } else { + optimalPrice = numerator / denominator + mat.bCost; + } - // Store this "optimal Price" in a property so we don't have to re-calculate for UI - product.marketTa2Price[city] = optimalPrice; - sCost = optimalPrice; - } else if (product.marketTa1) { - sCost = product.pCost + markupLimit; - } else if (isString(product.sCost)) { - const sCostString = product.sCost as string; - if(product.mku === 0) { - console.error(`mku is zero, reverting to 1 to avoid Infinity`); - product.mku = 1; - } - sCost = sCostString.replace(/MP/g, (product.pCost + product.rat / product.mku)+''); - sCost = eval(sCost); + // We'll store this "Optimal Price" in a property so that we don't have + // to re-calculate it for the UI + mat.marketTa2Price = optimalPrice; + sCost = optimalPrice; + } else if (mat.marketTa1) { + sCost = mat.bCost + markupLimit; + } else if (isString(mat.sCost)) { + sCost = (mat.sCost as string).replace(/MP/g, mat.bCost + ""); + sCost = eval(sCost); } else { - sCost = product.sCost; + sCost = mat.sCost; } + // Calculate how much of the material sells (per second) let markup = 1; - if (sCost > product.pCost) { - if ((sCost - product.pCost) > markupLimit) { - markup = markupLimit / (sCost - product.pCost); - } + if (sCost > mat.bCost) { + //Penalty if difference between sCost and bCost is greater than markup limit + if (sCost - mat.bCost > markupLimit) { + markup = Math.pow(markupLimit / (sCost - mat.bCost), 2); + } + } else if (sCost < mat.bCost) { + if (sCost <= 0) { + markup = 1e12; //Sell everything, essentially discard + } else { + //Lower prices than market increases sales + markup = mat.bCost / sCost; + } } - const maxSell = 0.5 - * Math.pow(product.rat, 0.65) - * marketFactor - * corporation.getSalesMultiplier() - * Math.pow(markup, 2) - * businessFactor - * advertisingFactor - * this.getSalesMultiplier(); + const maxSell = + (mat.qlt + 0.001) * + marketFactor * + markup * + businessFactor * + corporation.getSalesMultiplier() * + advertisingFactor * + this.getSalesMultiplier(); let sellAmt; - if (product.sllman[city][0] && isString(product.sllman[city][1])) { - //Sell amount is dynamically evaluated - let tmp = product.sllman[city][1].replace(/MAX/g, maxSell); - tmp = tmp.replace(/PROD/g, product.data[city][1]); - try { - tmp = eval(tmp); - } catch(e) { - dialogBoxCreate("Error evaluating your sell price expression for " + product.name + - " in " + this.name + "'s " + city + " office. Sell price is being set to MAX"); - tmp = maxSell; - } - sellAmt = Math.min(maxSell, tmp); - } else if (product.sllman[city][0] && product.sllman[city][1] > 0) { - //Sell amount is manually limited - sellAmt = Math.min(maxSell, product.sllman[city][1]); - } else if (product.sllman[city][0] === false){ + if (isString(mat.sllman[1])) { + //Dynamically evaluated + let tmp = (mat.sllman[1] as string).replace( + /MAX/g, + maxSell + "", + ); + tmp = tmp.replace(/PROD/g, mat.prd + ""); + try { + sellAmt = eval(tmp); + } catch (e) { + dialogBoxCreate( + "Error evaluating your sell amount for material " + + mat.name + + " in " + + this.name + + "'s " + + city + + " office. The sell amount " + + "is being set to zero", + ); sellAmt = 0; + } + sellAmt = Math.min(maxSell, sellAmt); + } else if (mat.sllman[1] === -1) { + //Backwards compatibility, -1 = MAX + sellAmt = maxSell; } else { - sellAmt = maxSell; + //Player's input value is just a number + sellAmt = Math.min(maxSell, mat.sllman[1] as number); } - if (sellAmt < 0) { sellAmt = 0; } - sellAmt = sellAmt * CorporationConstants.SecsPerMarketCycle * marketCycles; - sellAmt = Math.min(product.data[city][0], sellAmt); //data[0] is qty - if (sellAmt && sCost) { - product.data[city][0] -= sellAmt; //data[0] is qty - totalProfit += (sellAmt * sCost); - product.data[city][2] = sellAmt / (CorporationConstants.SecsPerMarketCycle * marketCycles); //data[2] is sell property + + sellAmt = + sellAmt * + CorporationConstants.SecsPerMarketCycle * + marketCycles; + sellAmt = Math.min(mat.qty, sellAmt); + if (sellAmt < 0) { + console.warn( + `sellAmt calculated to be negative for ${matName} in ${city}`, + ); + mat.sll = 0; + continue; + } + if (sellAmt && sCost >= 0) { + mat.qty -= sellAmt; + revenue += sellAmt * sCost; + mat.sll = + sellAmt / + (CorporationConstants.SecsPerMarketCycle * marketCycles); } else { - product.data[city][2] = 0; //data[2] is sell property + mat.sll = 0; } - break; + } + } //End processing of sale of materials + break; + + case "EXPORT": + for (const matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + const mat = warehouse.materials[matName]; + mat.totalExp = 0; //Reset export + for (let expI = 0; expI < mat.exp.length; ++expI) { + const exp = mat.exp[expI]; + const amtStr = exp.amt.replace( + /MAX/g, + mat.qty / + (CorporationConstants.SecsPerMarketCycle * marketCycles) + + "", + ); + let amt = 0; + try { + amt = eval(amtStr); + } catch (e) { + dialogBoxCreate( + "Calculating export for " + + mat.name + + " in " + + this.name + + "'s " + + city + + " division failed with " + + "error: " + + e, + ); + continue; + } + if (isNaN(amt)) { + dialogBoxCreate( + "Error calculating export amount for " + + mat.name + + " in " + + this.name + + "'s " + + city + + " division.", + ); + continue; + } + amt = + amt * + CorporationConstants.SecsPerMarketCycle * + marketCycles; + + if (mat.qty < amt) { + amt = mat.qty; + } + if (amt === 0) { + break; //None left + } + for (let foo = 0; foo < corporation.divisions.length; ++foo) { + if (corporation.divisions[foo].name === exp.ind) { + const expIndustry = corporation.divisions[foo]; + const expWarehouse = expIndustry.warehouses[exp.city]; + if (!(expWarehouse instanceof Warehouse)) { + console.error( + `Invalid export! ${expIndustry.name} ${exp.city}`, + ); + break; + } + + // Make sure theres enough space in warehouse + if (expWarehouse.sizeUsed >= expWarehouse.size) { + // Warehouse at capacity. Exporting doesnt + // affect revenue so just return 0's + return [0, 0]; + } else { + const maxAmt = Math.floor( + (expWarehouse.size - expWarehouse.sizeUsed) / + MaterialSizes[matName], + ); + amt = Math.min(maxAmt, amt); + } + expWarehouse.materials[matName].imp += + amt / + (CorporationConstants.SecsPerMarketCycle * + marketCycles); + expWarehouse.materials[matName].qty += amt; + expWarehouse.materials[matName].qlt = mat.qlt; + mat.qty -= amt; + mat.totalExp += amt; + expIndustry.updateWarehouseSizeUsed(expWarehouse); + break; + } + } } - case "START": - case "PURCHASE": - case "EXPORT": - break; - default: - console.error(`Invalid State: ${this.state}`); - break; - } //End switch(this.state) + //totalExp should be per second + mat.totalExp /= + CorporationConstants.SecsPerMarketCycle * marketCycles; + } } + + break; + + case "START": + break; + default: + console.error(`Invalid state: ${this.state}`); + break; + } //End switch(this.state) + this.updateWarehouseSizeUsed(warehouse); + } // End warehouse + + //Produce Scientific Research based on R&D employees + //Scientific Research can be produced without a warehouse + if (office instanceof OfficeSpace) { + this.sciResearch.qty += + 0.004 * + Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5) * + corporation.getScientificResearchMultiplier() * + this.getScientificResearchMultiplier(); + } + } + return [revenue, expenses]; + } + + //Process production & sale of this industry's FINISHED products (including all of their stats) + processProducts( + marketCycles = 1, + corporation: ICorporation, + ): [number, number] { + let revenue = 0; + const expenses = 0; + + //Create products + if (this.state === "PRODUCTION") { + for (const prodName in this.products) { + const prod = this.products[prodName]; + if (prod === undefined) continue; + if (!prod.fin) { + const city = prod.createCity; + const office = this.offices[city]; + if (office === 0) continue; + + // Designing/Creating a Product is based mostly off Engineers + const engrProd = office.employeeProd[EmployeePositions.Engineer]; + const mgmtProd = office.employeeProd[EmployeePositions.Management]; + const opProd = office.employeeProd[EmployeePositions.Operations]; + const total = engrProd + mgmtProd + opProd; + if (total <= 0) { + break; + } + + // Management is a multiplier for the production from Engineers + const mgmtFactor = 1 + mgmtProd / (1.2 * total); + + const progress = + (Math.pow(engrProd, 0.34) + Math.pow(opProd, 0.2)) * mgmtFactor; + + prod.createProduct(marketCycles, progress); + if (prod.prog >= 100) { + prod.finishProduct(office.employeeProd, this); + } + break; } - return totalProfit; + } } - discontinueProduct(product: Product): void { - for (const productName in this.products) { - if (this.products.hasOwnProperty(productName)) { - if (product === this.products[productName]) { - delete this.products[productName]; + //Produce Products + for (const prodName in this.products) { + if (this.products.hasOwnProperty(prodName)) { + const prod = this.products[prodName]; + if (prod instanceof Product && prod.fin) { + revenue += this.processProduct(marketCycles, prod, corporation); + } + } + } + return [revenue, expenses]; + } + + //Processes FINISHED products + processProduct( + marketCycles = 1, + product: Product, + corporation: ICorporation, + ): number { + let totalProfit = 0; + for (let i = 0; i < CorporationConstants.Cities.length; ++i) { + const city = CorporationConstants.Cities[i]; + const office = this.offices[city]; + if (office === 0) continue; + const warehouse = this.warehouses[city]; + if (warehouse instanceof Warehouse) { + switch (this.state) { + case "PRODUCTION": { + //Calculate the maximum production of this material based + //on the office's productivity + const maxProd = + this.getOfficeProductivity(office, { forProduct: true }) * + corporation.getProductionMultiplier() * + this.prodMult * // Multiplier from materials + this.getProductionMultiplier() * // Multiplier from research + this.getProductProductionMultiplier(); // Multiplier from research + let prod; + + //Account for whether production is manually limited + if (product.prdman[city][0]) { + prod = Math.min(maxProd, product.prdman[city][1]); + } else { + prod = maxProd; + } + prod *= CorporationConstants.SecsPerMarketCycle * marketCycles; + + //Calculate net change in warehouse storage making the Products will cost + let netStorageSize = product.siz; + for (const reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + const normQty = product.reqMats[reqMatName]; + netStorageSize -= MaterialSizes[reqMatName] * normQty; + } + } + + //If there's not enough space in warehouse, limit the amount of Product + if (netStorageSize > 0) { + const maxAmt = Math.floor( + (warehouse.size - warehouse.sizeUsed) / netStorageSize, + ); + prod = Math.min(maxAmt, prod); + } + + warehouse.smartSupplyStore += + prod / (CorporationConstants.SecsPerMarketCycle * marketCycles); + + //Make sure we have enough resources to make our Products + let producableFrac = 1; + for (const reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + const req = product.reqMats[reqMatName] * prod; + if (warehouse.materials[reqMatName].qty < req) { + producableFrac = Math.min( + producableFrac, + warehouse.materials[reqMatName].qty / req, + ); } + } } - } - } - upgrade(upgrade: IndustryUpgrade, refs: {corporation: ICorporation; office: OfficeSpace}): void { - const corporation = refs.corporation; - const office = refs.office; - const upgN = upgrade[0]; - while (this.upgrades.length <= upgN) {this.upgrades.push(0);} - ++this.upgrades[upgN]; - - switch (upgN) { - case 0: { //Coffee, 5% energy per employee - for (let i = 0; i < office.employees.length; ++i) { - office.employees[i].ene = Math.min(office.employees[i].ene * 1.05, office.maxEne); + //Make our Products if they are producable + if (producableFrac > 0 && prod > 0) { + for (const reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + const reqMatQtyNeeded = + product.reqMats[reqMatName] * prod * producableFrac; + warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; + warehouse.materials[reqMatName].prd -= + reqMatQtyNeeded / + (CorporationConstants.SecsPerMarketCycle * marketCycles); } - break; + } + //Quantity + product.data[city][0] += prod * producableFrac; } - case 1: { //AdVert.Inc, - const advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier(); - this.awareness += (3 * advMult); - this.popularity += (1 * advMult); - this.awareness *= (1.01 * advMult); - this.popularity *= ((1 + getRandomInt(1, 3) / 100) * advMult); - break; + + //Keep track of production Per second + product.data[city][1] = + (prod * producableFrac) / + (CorporationConstants.SecsPerMarketCycle * marketCycles); + break; + } + case "SALE": { + //Process sale of Products + product.pCost = 0; //Estimated production cost + for (const reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + product.pCost += + product.reqMats[reqMatName] * + warehouse.materials[reqMatName].bCost; + } } - default: { - console.error(`Un-implemented function index: ${upgN}`); - break; + + // Since its a product, its production cost is increased for labor + product.pCost *= CorporationConstants.ProductProductionCostRatio; + + // Sale multipliers + const businessFactor = this.getBusinessFactor(office); //Business employee productivity + const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity + const marketFactor = this.getMarketFactor(product); //Competition + demand + + // Calculate Sale Cost (sCost), which could be dynamically evaluated + const markupLimit = product.rat / product.mku; + let sCost; + if (product.marketTa2) { + const prod = product.data[city][1]; + + // Reverse engineer the 'maxSell' formula + // 1. Set 'maxSell' = prod + // 2. Substitute formula for 'markup' + // 3. Solve for 'sCost'roduct.pCost = sCost + const numerator = markupLimit; + const sqrtNumerator = prod; + const sqrtDenominator = + 0.5 * + Math.pow(product.rat, 0.65) * + marketFactor * + corporation.getSalesMultiplier() * + businessFactor * + advertisingFactor * + this.getSalesMultiplier(); + const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); + let optimalPrice; + if (sqrtDenominator === 0 || denominator === 0) { + if (sqrtNumerator === 0) { + optimalPrice = 0; // No production + } else { + optimalPrice = product.pCost + markupLimit; + console.warn( + `In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`, + ); + } + } else { + optimalPrice = numerator / denominator + product.pCost; + } + + // Store this "optimal Price" in a property so we don't have to re-calculate for UI + product.marketTa2Price[city] = optimalPrice; + sCost = optimalPrice; + } else if (product.marketTa1) { + sCost = product.pCost + markupLimit; + } else if (isString(product.sCost)) { + const sCostString = product.sCost as string; + if (product.mku === 0) { + console.error(`mku is zero, reverting to 1 to avoid Infinity`); + product.mku = 1; + } + sCost = sCostString.replace( + /MP/g, + product.pCost + product.rat / product.mku + "", + ); + sCost = eval(sCost); + } else { + sCost = product.sCost; } + + let markup = 1; + if (sCost > product.pCost) { + if (sCost - product.pCost > markupLimit) { + markup = markupLimit / (sCost - product.pCost); + } + } + + const maxSell = + 0.5 * + Math.pow(product.rat, 0.65) * + marketFactor * + corporation.getSalesMultiplier() * + Math.pow(markup, 2) * + businessFactor * + advertisingFactor * + this.getSalesMultiplier(); + let sellAmt; + if (product.sllman[city][0] && isString(product.sllman[city][1])) { + //Sell amount is dynamically evaluated + let tmp = product.sllman[city][1].replace(/MAX/g, maxSell); + tmp = tmp.replace(/PROD/g, product.data[city][1]); + try { + tmp = eval(tmp); + } catch (e) { + dialogBoxCreate( + "Error evaluating your sell price expression for " + + product.name + + " in " + + this.name + + "'s " + + city + + " office. Sell price is being set to MAX", + ); + tmp = maxSell; + } + sellAmt = Math.min(maxSell, tmp); + } else if (product.sllman[city][0] && product.sllman[city][1] > 0) { + //Sell amount is manually limited + sellAmt = Math.min(maxSell, product.sllman[city][1]); + } else if (product.sllman[city][0] === false) { + sellAmt = 0; + } else { + sellAmt = maxSell; + } + if (sellAmt < 0) { + sellAmt = 0; + } + sellAmt = + sellAmt * CorporationConstants.SecsPerMarketCycle * marketCycles; + sellAmt = Math.min(product.data[city][0], sellAmt); //data[0] is qty + if (sellAmt && sCost) { + product.data[city][0] -= sellAmt; //data[0] is qty + totalProfit += sellAmt * sCost; + product.data[city][2] = + sellAmt / + (CorporationConstants.SecsPerMarketCycle * marketCycles); //data[2] is sell property + } else { + product.data[city][2] = 0; //data[2] is sell property + } + break; + } + case "START": + case "PURCHASE": + case "EXPORT": + break; + default: + console.error(`Invalid State: ${this.state}`); + break; + } //End switch(this.state) + } + } + return totalProfit; + } + + discontinueProduct(product: Product): void { + for (const productName in this.products) { + if (this.products.hasOwnProperty(productName)) { + if (product === this.products[productName]) { + delete this.products[productName]; } + } } + } - // Returns how much of a material can be produced based of office productivity (employee stats) - getOfficeProductivity(office: OfficeSpace, params: {forProduct?: boolean} = {}): number { - const opProd = office.employeeProd[EmployeePositions.Operations]; - const engrProd = office.employeeProd[EmployeePositions.Engineer]; - const mgmtProd = office.employeeProd[EmployeePositions.Management] - const total = opProd + engrProd + mgmtProd; + upgrade( + upgrade: IndustryUpgrade, + refs: { corporation: ICorporation; office: OfficeSpace }, + ): void { + const corporation = refs.corporation; + const office = refs.office; + const upgN = upgrade[0]; + while (this.upgrades.length <= upgN) { + this.upgrades.push(0); + } + ++this.upgrades[upgN]; - if (total <= 0) return 0; - - // Management is a multiplier for the production from Operations and Engineers - const mgmtFactor = 1 + (mgmtProd / (1.2 * total)); - - // For production, Operations is slightly more important than engineering - // Both Engineering and Operations have diminishing returns - const prod = (Math.pow(opProd, 0.4) + Math.pow(engrProd, 0.3)) * mgmtFactor; - - // Generic multiplier for the production. Used for game-balancing purposes - const balancingMult = 0.05; - - if (params && params.forProduct) { - // Products are harder to create and therefore have less production - return 0.5 * balancingMult * prod; - } else { - return balancingMult * prod; + switch (upgN) { + case 0: { + //Coffee, 5% energy per employee + for (let i = 0; i < office.employees.length; ++i) { + office.employees[i].ene = Math.min( + office.employees[i].ene * 1.05, + office.maxEne, + ); } + break; + } + case 1: { + //AdVert.Inc, + const advMult = + corporation.getAdvertisingMultiplier() * + this.getAdvertisingMultiplier(); + this.awareness += 3 * advMult; + this.popularity += 1 * advMult; + this.awareness *= 1.01 * advMult; + this.popularity *= (1 + getRandomInt(1, 3) / 100) * advMult; + break; + } + default: { + console.error(`Un-implemented function index: ${upgN}`); + break; + } } + } - // Returns a multiplier based on the office' 'Business' employees that affects sales - getBusinessFactor(office: OfficeSpace): number { - const businessProd = 1 + office.employeeProd[EmployeePositions.Business]; + // Returns how much of a material can be produced based of office productivity (employee stats) + getOfficeProductivity( + office: OfficeSpace, + params: { forProduct?: boolean } = {}, + ): number { + const opProd = office.employeeProd[EmployeePositions.Operations]; + const engrProd = office.employeeProd[EmployeePositions.Engineer]; + const mgmtProd = office.employeeProd[EmployeePositions.Management]; + const total = opProd + engrProd + mgmtProd; - return calculateEffectWithFactors(businessProd, 0.26, 10e3); + if (total <= 0) return 0; + + // Management is a multiplier for the production from Operations and Engineers + const mgmtFactor = 1 + mgmtProd / (1.2 * total); + + // For production, Operations is slightly more important than engineering + // Both Engineering and Operations have diminishing returns + const prod = (Math.pow(opProd, 0.4) + Math.pow(engrProd, 0.3)) * mgmtFactor; + + // Generic multiplier for the production. Used for game-balancing purposes + const balancingMult = 0.05; + + if (params && params.forProduct) { + // Products are harder to create and therefore have less production + return 0.5 * balancingMult * prod; + } else { + return balancingMult * prod; } + } - //Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This - //multiplier affects sales. The result is: - // [Total sales mult, total awareness mult, total pop mult, awareness/pop ratio mult] - getAdvertisingFactors(): [number, number, number, number] { - const awarenessFac = Math.pow(this.awareness + 1, this.advFac); - const popularityFac = Math.pow(this.popularity + 1, this.advFac); - const ratioFac = (this.awareness === 0 ? 0.01 : Math.max((this.popularity + .001) / this.awareness, 0.01)); - const totalFac = Math.pow(awarenessFac * popularityFac * ratioFac, 0.85); - return [totalFac, awarenessFac, popularityFac, ratioFac]; + // Returns a multiplier based on the office' 'Business' employees that affects sales + getBusinessFactor(office: OfficeSpace): number { + const businessProd = 1 + office.employeeProd[EmployeePositions.Business]; + + return calculateEffectWithFactors(businessProd, 0.26, 10e3); + } + + //Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This + //multiplier affects sales. The result is: + // [Total sales mult, total awareness mult, total pop mult, awareness/pop ratio mult] + getAdvertisingFactors(): [number, number, number, number] { + const awarenessFac = Math.pow(this.awareness + 1, this.advFac); + const popularityFac = Math.pow(this.popularity + 1, this.advFac); + const ratioFac = + this.awareness === 0 + ? 0.01 + : Math.max((this.popularity + 0.001) / this.awareness, 0.01); + const totalFac = Math.pow(awarenessFac * popularityFac * ratioFac, 0.85); + return [totalFac, awarenessFac, popularityFac, ratioFac]; + } + + //Returns a multiplier based on a materials demand and competition that affects sales + getMarketFactor(mat: { dmd: number; cmp: number }): number { + return Math.max(0.1, (mat.dmd * (100 - mat.cmp)) / 100); + } + + // Returns a boolean indicating whether this Industry has the specified Research + hasResearch(name: string): boolean { + return this.researched[name] === true; + } + + updateResearchTree(): void { + const researchTree = IndustryResearchTrees[this.type]; + if (researchTree === undefined) + throw new Error(`Invalid industry "${this.type}"`); + + // Since ResearchTree data isnt saved, we'll update the Research Tree data + // based on the stored 'researched' property in the Industry object + if ( + Object.keys(researchTree.researched).length !== + Object.keys(this.researched).length + ) { + for (const research in this.researched) { + researchTree.research(research); + } } + } - //Returns a multiplier based on a materials demand and competition that affects sales - getMarketFactor(mat: {dmd: number; cmp: number}): number { - return Math.max(0.1, mat.dmd * (100 - mat.cmp) / 100); - } + // Get multipliers from Research + getAdvertisingMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + if (researchTree === undefined) + throw new Error(`Invalid industry: "${this.type}"`); + this.updateResearchTree(); + return researchTree.getAdvertisingMultiplier(); + } - // Returns a boolean indicating whether this Industry has the specified Research - hasResearch(name: string): boolean { - return (this.researched[name] === true); - } + getEmployeeChaMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + if (researchTree === undefined) + throw new Error(`Invalid industry: "${this.type}"`); + this.updateResearchTree(); + return researchTree.getEmployeeChaMultiplier(); + } - updateResearchTree(): void { - const researchTree = IndustryResearchTrees[this.type]; - if(researchTree === undefined) - throw new Error(`Invalid industry "${this.type}"`); + getEmployeeCreMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + if (researchTree === undefined) + throw new Error(`Invalid industry: "${this.type}"`); + this.updateResearchTree(); + return researchTree.getEmployeeCreMultiplier(); + } - // Since ResearchTree data isnt saved, we'll update the Research Tree data - // based on the stored 'researched' property in the Industry object - if (Object.keys(researchTree.researched).length !== Object.keys(this.researched).length) { - for (const research in this.researched) { - researchTree.research(research); - } - } - } + getEmployeeEffMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + if (researchTree === undefined) + throw new Error(`Invalid industry: "${this.type}"`); + this.updateResearchTree(); + return researchTree.getEmployeeEffMultiplier(); + } - // Get multipliers from Research - getAdvertisingMultiplier(): number { - const researchTree = IndustryResearchTrees[this.type]; - if(researchTree === undefined) - throw new Error(`Invalid industry: "${this.type}"`); - this.updateResearchTree(); - return researchTree.getAdvertisingMultiplier(); - } + getEmployeeIntMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + if (researchTree === undefined) + throw new Error(`Invalid industry: "${this.type}"`); + this.updateResearchTree(); + return researchTree.getEmployeeIntMultiplier(); + } - getEmployeeChaMultiplier(): number { - const researchTree = IndustryResearchTrees[this.type]; - if(researchTree === undefined) - throw new Error(`Invalid industry: "${this.type}"`); - this.updateResearchTree(); - return researchTree.getEmployeeChaMultiplier(); - } + getProductionMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + if (researchTree === undefined) + throw new Error(`Invalid industry: "${this.type}"`); + this.updateResearchTree(); + return researchTree.getProductionMultiplier(); + } - getEmployeeCreMultiplier(): number { - const researchTree = IndustryResearchTrees[this.type]; - if(researchTree === undefined) - throw new Error(`Invalid industry: "${this.type}"`); - this.updateResearchTree(); - return researchTree.getEmployeeCreMultiplier(); - } + getProductProductionMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + if (researchTree === undefined) + throw new Error(`Invalid industry: "${this.type}"`); + this.updateResearchTree(); + return researchTree.getProductProductionMultiplier(); + } - getEmployeeEffMultiplier(): number { - const researchTree = IndustryResearchTrees[this.type]; - if(researchTree === undefined) - throw new Error(`Invalid industry: "${this.type}"`); - this.updateResearchTree(); - return researchTree.getEmployeeEffMultiplier(); - } + getSalesMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + if (researchTree === undefined) + throw new Error(`Invalid industry: "${this.type}"`); + this.updateResearchTree(); + return researchTree.getSalesMultiplier(); + } - getEmployeeIntMultiplier(): number { - const researchTree = IndustryResearchTrees[this.type]; - if(researchTree === undefined) - throw new Error(`Invalid industry: "${this.type}"`); - this.updateResearchTree(); - return researchTree.getEmployeeIntMultiplier(); - } + getScientificResearchMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + if (researchTree === undefined) + throw new Error(`Invalid industry: "${this.type}"`); + this.updateResearchTree(); + return researchTree.getScientificResearchMultiplier(); + } - getProductionMultiplier(): number { - const researchTree = IndustryResearchTrees[this.type]; - if(researchTree === undefined) - throw new Error(`Invalid industry: "${this.type}"`); - this.updateResearchTree(); - return researchTree.getProductionMultiplier(); - } + getStorageMultiplier(): number { + const researchTree = IndustryResearchTrees[this.type]; + if (researchTree === undefined) + throw new Error(`Invalid industry: "${this.type}"`); + this.updateResearchTree(); + return researchTree.getStorageMultiplier(); + } - getProductProductionMultiplier(): number { - const researchTree = IndustryResearchTrees[this.type]; - if(researchTree === undefined) - throw new Error(`Invalid industry: "${this.type}"`); - this.updateResearchTree(); - return researchTree.getProductProductionMultiplier(); - } + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Industry", this); + } - getSalesMultiplier(): number { - const researchTree = IndustryResearchTrees[this.type]; - if(researchTree === undefined) - throw new Error(`Invalid industry: "${this.type}"`); - this.updateResearchTree(); - return researchTree.getSalesMultiplier(); - } - - getScientificResearchMultiplier(): number { - const researchTree = IndustryResearchTrees[this.type]; - if(researchTree === undefined) - throw new Error(`Invalid industry: "${this.type}"`); - this.updateResearchTree(); - return researchTree.getScientificResearchMultiplier(); - } - - getStorageMultiplier(): number { - const researchTree = IndustryResearchTrees[this.type]; - if(researchTree === undefined) - throw new Error(`Invalid industry: "${this.type}"`); - this.updateResearchTree(); - return researchTree.getStorageMultiplier(); - } - - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("Industry", this); - } - - /** - * Initiatizes a Industry object from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Industry { - return Generic_fromJSON(Industry, value.data); - } + /** + * Initiatizes a Industry object from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Industry { + return Generic_fromJSON(Industry, value.data); + } } Reviver.constructors.Industry = Industry; diff --git a/src/Corporation/IndustryData.tsx b/src/Corporation/IndustryData.tsx index 04459d3ef..623e84b9e 100644 --- a/src/Corporation/IndustryData.tsx +++ b/src/Corporation/IndustryData.tsx @@ -1,141 +1,243 @@ -import React from 'react'; +import React from "react"; import { ResearchTree } from "./ResearchTree"; -import { getBaseResearchTreeCopy, - getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree"; +import { + getBaseResearchTreeCopy, + getProductIndustryResearchTreeCopy, +} from "./data/BaseResearchTree"; import { Money } from "../ui/React/Money"; interface IIndustryMap { - [key: string]: T | undefined; - Energy: T; - Utilities: T; - Agriculture: T; - Fishing: T; - Mining: T; - Food: T; - Tobacco: T; - Chemical: T; - Pharmaceutical: T; - Computer: T; - Robotics: T; - Software: T; - Healthcare: T; - RealEstate: T; + [key: string]: T | undefined; + Energy: T; + Utilities: T; + Agriculture: T; + Fishing: T; + Mining: T; + Food: T; + Tobacco: T; + Chemical: T; + Pharmaceutical: T; + Computer: T; + Robotics: T; + Software: T; + Healthcare: T; + RealEstate: T; } // Map of official names for each Industry export const Industries: IIndustryMap = { - Energy: "Energy", - Utilities: "Water Utilities", - Agriculture: "Agriculture", - Fishing: "Fishing", - Mining: "Mining", - Food: "Food", - Tobacco: "Tobacco", - Chemical: "Chemical", - Pharmaceutical: "Pharmaceutical", - Computer: "Computer Hardware", - Robotics: "Robotics", - Software: "Software", - Healthcare: "Healthcare", - RealEstate: "RealEstate", -} + Energy: "Energy", + Utilities: "Water Utilities", + Agriculture: "Agriculture", + Fishing: "Fishing", + Mining: "Mining", + Food: "Food", + Tobacco: "Tobacco", + Chemical: "Chemical", + Pharmaceutical: "Pharmaceutical", + Computer: "Computer Hardware", + Robotics: "Robotics", + Software: "Software", + Healthcare: "Healthcare", + RealEstate: "RealEstate", +}; // Map of how much money it takes to start each industry export const IndustryStartingCosts: IIndustryMap = { - Energy: 225e9, - Utilities: 150e9, - Agriculture: 40e9, - Fishing: 80e9, - Mining: 300e9, - Food: 10e9, - Tobacco: 20e9, - Chemical: 70e9, - Pharmaceutical: 200e9, - Computer: 500e9, - Robotics: 1e12, - Software: 25e9, - Healthcare: 750e9, - RealEstate: 600e9, -} + Energy: 225e9, + Utilities: 150e9, + Agriculture: 40e9, + Fishing: 80e9, + Mining: 300e9, + Food: 10e9, + Tobacco: 20e9, + Chemical: 70e9, + Pharmaceutical: 200e9, + Computer: 500e9, + Robotics: 1e12, + Software: 25e9, + Healthcare: 750e9, + RealEstate: 600e9, +}; // Map of description for each industry export const IndustryDescriptions: IIndustryMap = { - Energy: (<>Engage in the production and distribution of energy.

    - Starting cost:
    - Recommended starting Industry: NO), - Utilities: (<>Distribute water and provide wastewater services.

    - Starting cost:
    - Recommended starting Industry: NO), - Agriculture: (<>Cultivate crops and breed livestock to produce food.

    - Starting cost:
    - Recommended starting Industry: YES), - Fishing: (<>Produce food through the breeding and processing of fish and fish products.

    - Starting cost:
    - Recommended starting Industry: NO), - Mining: (<>Extract and process metals from the earth.

    - Starting cost:
    - Recommended starting Industry: NO), - Food: (<>Create your own restaurants all around the world.

    - Starting cost:
    - Recommended starting Industry: YES), - Tobacco: (<>Create and distribute tobacco and tobacco-related products.

    - Starting cost:
    - Recommended starting Industry: YES), - Chemical: (<>Produce industrial chemicals.

    - Starting cost:
    - Recommended starting Industry: NO), - Pharmaceutical: (<>Discover, develop, and create new pharmaceutical drugs.

    - Starting cost:
    - Recommended starting Industry: NO), - Computer: (<>Develop and manufacture new computer hardware and networking infrastructures.

    - Starting cost:
    - Recommended starting Industry: NO), - Robotics: (<>Develop and create robots.

    - Starting cost:
    - Recommended starting Industry: NO), - Software: (<>Develop computer software and create AI Cores.

    - Starting cost:
    - Recommended starting Industry: YES), - Healthcare: (<>Create and manage hospitals.

    - Starting cost:
    - Recommended starting Industry: NO), - RealEstate: (<>Develop and manage real estate properties.

    - Starting cost:
    - Recommended starting Industry: NO), -} + Energy: ( + <> + Engage in the production and distribution of energy. +
    +
    + Starting cost: +
    + Recommended starting Industry: NO + + ), + Utilities: ( + <> + Distribute water and provide wastewater services. +
    +
    + Starting cost: +
    + Recommended starting Industry: NO + + ), + Agriculture: ( + <> + Cultivate crops and breed livestock to produce food. +
    +
    + Starting cost: +
    + Recommended starting Industry: YES + + ), + Fishing: ( + <> + Produce food through the breeding and processing of fish and fish + products. +
    +
    + Starting cost: +
    + Recommended starting Industry: NO + + ), + Mining: ( + <> + Extract and process metals from the earth. +
    +
    + Starting cost: +
    + Recommended starting Industry: NO + + ), + Food: ( + <> + Create your own restaurants all around the world. +
    +
    + Starting cost: +
    + Recommended starting Industry: YES + + ), + Tobacco: ( + <> + Create and distribute tobacco and tobacco-related products. +
    +
    + Starting cost: +
    + Recommended starting Industry: YES + + ), + Chemical: ( + <> + Produce industrial chemicals. +
    +
    + Starting cost: +
    + Recommended starting Industry: NO + + ), + Pharmaceutical: ( + <> + Discover, develop, and create new pharmaceutical drugs. +
    +
    + Starting cost: +
    + Recommended starting Industry: NO + + ), + Computer: ( + <> + Develop and manufacture new computer hardware and networking + infrastructures. +
    +
    + Starting cost: +
    + Recommended starting Industry: NO + + ), + Robotics: ( + <> + Develop and create robots. +
    +
    + Starting cost: +
    + Recommended starting Industry: NO + + ), + Software: ( + <> + Develop computer software and create AI Cores. +
    +
    + Starting cost: +
    + Recommended starting Industry: YES + + ), + Healthcare: ( + <> + Create and manage hospitals. +
    +
    + Starting cost: +
    + Recommended starting Industry: NO + + ), + RealEstate: ( + <> + Develop and manage real estate properties. +
    +
    + Starting cost: +
    + Recommended starting Industry: NO + + ), +}; // Map of available Research for each Industry. This data is held in a // ResearchTree object export const IndustryResearchTrees: IIndustryMap = { - Energy: getBaseResearchTreeCopy(), - Utilities: getBaseResearchTreeCopy(), - Agriculture: getBaseResearchTreeCopy(), - Fishing: getBaseResearchTreeCopy(), - Mining: getBaseResearchTreeCopy(), - Food: getProductIndustryResearchTreeCopy(), - Tobacco: getProductIndustryResearchTreeCopy(), - Chemical: getBaseResearchTreeCopy(), - Pharmaceutical: getProductIndustryResearchTreeCopy(), - Computer: getProductIndustryResearchTreeCopy(), - Robotics: getProductIndustryResearchTreeCopy(), - Software: getProductIndustryResearchTreeCopy(), - Healthcare: getProductIndustryResearchTreeCopy(), - RealEstate: getProductIndustryResearchTreeCopy(), -} + Energy: getBaseResearchTreeCopy(), + Utilities: getBaseResearchTreeCopy(), + Agriculture: getBaseResearchTreeCopy(), + Fishing: getBaseResearchTreeCopy(), + Mining: getBaseResearchTreeCopy(), + Food: getProductIndustryResearchTreeCopy(), + Tobacco: getProductIndustryResearchTreeCopy(), + Chemical: getBaseResearchTreeCopy(), + Pharmaceutical: getProductIndustryResearchTreeCopy(), + Computer: getProductIndustryResearchTreeCopy(), + Robotics: getProductIndustryResearchTreeCopy(), + Software: getProductIndustryResearchTreeCopy(), + Healthcare: getProductIndustryResearchTreeCopy(), + RealEstate: getProductIndustryResearchTreeCopy(), +}; export function resetIndustryResearchTrees(): void { - IndustryResearchTrees.Energy = getBaseResearchTreeCopy(); - IndustryResearchTrees.Utilities = getBaseResearchTreeCopy(); - IndustryResearchTrees.Agriculture = getBaseResearchTreeCopy(); - IndustryResearchTrees.Fishing = getBaseResearchTreeCopy(); - IndustryResearchTrees.Mining = getBaseResearchTreeCopy(); - IndustryResearchTrees.Food = getBaseResearchTreeCopy(); - IndustryResearchTrees.Tobacco = getBaseResearchTreeCopy(); - IndustryResearchTrees.Chemical = getBaseResearchTreeCopy(); - IndustryResearchTrees.Pharmaceutical = getBaseResearchTreeCopy(); - IndustryResearchTrees.Computer = getBaseResearchTreeCopy(); - IndustryResearchTrees.Robotics = getBaseResearchTreeCopy(); - IndustryResearchTrees.Software = getBaseResearchTreeCopy(); - IndustryResearchTrees.Healthcare = getBaseResearchTreeCopy(); - IndustryResearchTrees.RealEstate = getBaseResearchTreeCopy(); + IndustryResearchTrees.Energy = getBaseResearchTreeCopy(); + IndustryResearchTrees.Utilities = getBaseResearchTreeCopy(); + IndustryResearchTrees.Agriculture = getBaseResearchTreeCopy(); + IndustryResearchTrees.Fishing = getBaseResearchTreeCopy(); + IndustryResearchTrees.Mining = getBaseResearchTreeCopy(); + IndustryResearchTrees.Food = getBaseResearchTreeCopy(); + IndustryResearchTrees.Tobacco = getBaseResearchTreeCopy(); + IndustryResearchTrees.Chemical = getBaseResearchTreeCopy(); + IndustryResearchTrees.Pharmaceutical = getBaseResearchTreeCopy(); + IndustryResearchTrees.Computer = getBaseResearchTreeCopy(); + IndustryResearchTrees.Robotics = getBaseResearchTreeCopy(); + IndustryResearchTrees.Software = getBaseResearchTreeCopy(); + IndustryResearchTrees.Healthcare = getBaseResearchTreeCopy(); + IndustryResearchTrees.RealEstate = getBaseResearchTreeCopy(); } diff --git a/src/Corporation/IndustryUpgrades.ts b/src/Corporation/IndustryUpgrades.ts index 234ca88e5..277c15313 100644 --- a/src/Corporation/IndustryUpgrades.ts +++ b/src/Corporation/IndustryUpgrades.ts @@ -6,12 +6,24 @@ export type IndustryUpgrade = [number, number, number, number, string, string]; // The data structure is an array with the following format: // [index in array, base price, price mult, benefit mult (if applicable), name, desc] export const IndustryUpgrades: IMap = { - "0": [0, 500e3, 1, 1.05, - "Coffee", "Provide your employees with coffee, increasing their energy by 5%."], - "1": [1, 1e9, 1.06, 1.03, - "AdVert.Inc", "Hire AdVert.Inc to advertise your company. Each level of " + - "this upgrade grants your company a static increase of 3 and 1 to its awareness and " + - "popularity, respectively. It will then increase your company's awareness by 1%, and its popularity " + - "by a random percentage between 1% and 3%. These effects are increased by other upgrades " + - "that increase the power of your advertising."], -} + "0": [ + 0, + 500e3, + 1, + 1.05, + "Coffee", + "Provide your employees with coffee, increasing their energy by 5%.", + ], + "1": [ + 1, + 1e9, + 1.06, + 1.03, + "AdVert.Inc", + "Hire AdVert.Inc to advertise your company. Each level of " + + "this upgrade grants your company a static increase of 3 and 1 to its awareness and " + + "popularity, respectively. It will then increase your company's awareness by 1%, and its popularity " + + "by a random percentage between 1% and 3%. These effects are increased by other upgrades " + + "that increase the power of your advertising.", + ], +}; diff --git a/src/Corporation/Material.ts b/src/Corporation/Material.ts index 9c5d1ca19..69881b9dd 100644 --- a/src/Corporation/Material.ts +++ b/src/Corporation/Material.ts @@ -1,200 +1,244 @@ -import { Generic_fromJSON, - Generic_toJSON, - Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; import { Export } from "./Export"; interface IConstructorParams { - name?: string; + name?: string; } export class Material { + // Name of material + name = "InitName"; - // Name of material - name = "InitName"; + // Amount of material owned + qty = 0; - // Amount of material owned - qty = 0; + // Material's "quality". Unbounded + qlt = 0; - // Material's "quality". Unbounded - qlt = 0; + // How much demand the Material has in the market, and the range of possible + // values for this "demand" + dmd = 0; + dmdR: number[] = [0, 0]; - // How much demand the Material has in the market, and the range of possible - // values for this "demand" - dmd = 0; - dmdR: number[] = [0, 0]; + // How much competition there is for this Material in the market, and the range + // of possible values for this "competition" + cmp = 0; + cmpR: number[] = [0, 0]; - // How much competition there is for this Material in the market, and the range - // of possible values for this "competition" - cmp = 0; - cmpR: number[] = [0, 0]; + // Maximum volatility of this Materials stats + mv = 0; - // Maximum volatility of this Materials stats - mv = 0; + // Markup. Determines how high of a price you can charge on the material + // compared to the market price without suffering loss in # of sales + // Quality is divided by this to determine markup limits + // e,g, If mku is 10 and quality is 100 then you can markup prices by 100/10 = 10 + mku = 0; - // Markup. Determines how high of a price you can charge on the material - // compared to the market price without suffering loss in # of sales - // Quality is divided by this to determine markup limits - // e,g, If mku is 10 and quality is 100 then you can markup prices by 100/10 = 10 - mku = 0; + // How much of this material is being bought, sold, imported and produced every second + buy = 0; + sll = 0; + prd = 0; + imp = 0; - // How much of this material is being bought, sold, imported and produced every second - buy = 0; - sll = 0; - prd = 0; - imp = 0; + // Exports of this material to another warehouse/industry + exp: Export[] = []; - // Exports of this material to another warehouse/industry - exp: Export[] = []; + // Total amount of this material exported in the last cycle + totalExp = 0; - // Total amount of this material exported in the last cycle - totalExp = 0; + // Cost / sec to buy this material. AKA Market Price + bCost = 0; - // Cost / sec to buy this material. AKA Market Price - bCost = 0; + // Cost / sec to sell this material + sCost: string | number = 0; - // Cost / sec to sell this material - sCost: string | number = 0; + // Flags to keep track of whether production and/or sale of this material is limited + // [Whether production/sale is limited, limit amount] + prdman: [boolean, number] = [false, 0]; // Production + sllman: [boolean, string | number] = [false, 0]; // Sale - // Flags to keep track of whether production and/or sale of this material is limited - // [Whether production/sale is limited, limit amount] - prdman: [boolean, number] = [false, 0]; // Production - sllman: [boolean, string | number] = [false, 0]; // Sale + // Flags that signal whether automatic sale pricing through Market TA is enabled + marketTa1 = false; + marketTa2 = false; + marketTa2Price = 0; - // Flags that signal whether automatic sale pricing through Market TA is enabled - marketTa1 = false; - marketTa2 = false; - marketTa2Price = 0; + constructor(params: IConstructorParams = {}) { + if (params.name) { + this.name = params.name; + } + this.init(); + } - constructor(params: IConstructorParams = {}) { - if (params.name) { this.name = params.name; } - this.init(); + getMarkupLimit(): number { + return this.qlt / this.mku; + } + + init(): void { + switch (this.name) { + case "Water": + this.dmd = 75; + this.dmdR = [65, 85]; + this.cmp = 50; + this.cmpR = [40, 60]; + this.bCost = 1500; + this.mv = 0.2; + this.mku = 6; + break; + case "Energy": + this.dmd = 90; + this.dmdR = [80, 99]; + this.cmp = 80; + this.cmpR = [65, 95]; + this.bCost = 2000; + this.mv = 0.2; + this.mku = 6; + break; + case "Food": + this.dmd = 80; + this.dmdR = [70, 90]; + this.cmp = 60; + this.cmpR = [35, 85]; + this.bCost = 5000; + this.mv = 1; + this.mku = 3; + break; + case "Plants": + this.dmd = 70; + this.dmdR = [20, 90]; + this.cmp = 50; + this.cmpR = [30, 70]; + this.bCost = 3000; + this.mv = 0.6; + this.mku = 3.75; + break; + case "Metal": + this.dmd = 80; + this.dmdR = [75, 85]; + this.cmp = 70; + this.cmpR = [60, 80]; + this.bCost = 2650; + this.mv = 1; + this.mku = 6; + break; + case "Hardware": + this.dmd = 85; + this.dmdR = [80, 90]; + this.cmp = 80; + this.cmpR = [65, 95]; + this.bCost = 8e3; + this.mv = 0.5; //Less mv bc its processed twice + this.mku = 1; + break; + case "Chemicals": + this.dmd = 55; + this.dmdR = [40, 70]; + this.cmp = 60; + this.cmpR = [40, 80]; + this.bCost = 9e3; + this.mv = 1.2; + this.mku = 2; + break; + case "Real Estate": + this.dmd = 50; + this.dmdR = [5, 99]; + this.cmp = 50; + this.cmpR = [25, 75]; + this.bCost = 80e3; + this.mv = 1.5; //Less mv bc its processed twice + this.mku = 1.5; + break; + case "Drugs": + this.dmd = 60; + this.dmdR = [45, 75]; + this.cmp = 70; + this.cmpR = [40, 99]; + this.bCost = 40e3; + this.mv = 1.6; + this.mku = 1; + break; + case "Robots": + this.dmd = 90; + this.dmdR = [80, 9]; + this.cmp = 90; + this.cmpR = [80, 9]; + this.bCost = 75e3; + this.mv = 0.5; //Less mv bc its processed twice + this.mku = 1; + break; + case "AI Cores": + this.dmd = 90; + this.dmdR = [80, 99]; + this.cmp = 90; + this.cmpR = [80, 9]; + this.bCost = 15e3; + this.mv = 0.8; //Less mv bc its processed twice + this.mku = 0.5; + break; + case "Scientific Research": + case "InitName": + break; + default: + console.error(`Invalid material type in init(): ${this.name}`); + break; + } + } + + // Process change in demand, competition, and buy cost of this material + processMarket(): void { + // The price will change in accordance with demand and competition. + // e.g. If demand goes up, then so does price. If competition goes up, price goes down + const priceVolatility: number = (Math.random() * this.mv) / 300; + const priceChange: number = 1 + priceVolatility; + + //This 1st random check determines whether competition increases or decreases + const compVolatility: number = (Math.random() * this.mv) / 100; + const compChange: number = 1 + compVolatility; + if (Math.random() < 0.5) { + this.cmp *= compChange; + if (this.cmp > this.cmpR[1]) { + this.cmp = this.cmpR[1]; + } + this.bCost *= 1 / priceChange; // Competition increases, so price goes down + } else { + this.cmp *= 1 / compChange; + if (this.cmp < this.cmpR[0]) { + this.cmp = this.cmpR[0]; + } + this.bCost *= priceChange; // Competition decreases, so price goes up } - getMarkupLimit(): number { - return this.qlt / this.mku; + // This 2nd random check determines whether demand increases or decreases + const dmdVolatility: number = (Math.random() * this.mv) / 100; + const dmdChange: number = 1 + dmdVolatility; + if (Math.random() < 0.5) { + this.dmd *= dmdChange; + if (this.dmd > this.dmdR[1]) { + this.dmd = this.dmdR[1]; + } + this.bCost *= priceChange; // Demand increases, so price goes up + } else { + this.dmd *= 1 / dmdChange; + if (this.dmd < this.dmdR[0]) { + this.dmd = this.dmdR[0]; + } + this.bCost *= 1 / priceChange; } + } - init(): void { - switch(this.name) { - case "Water": - this.dmd = 75; this.dmdR = [65, 85]; - this.cmp = 50; this.cmpR = [40, 60]; - this.bCost = 1500; this.mv = 0.2; - this.mku = 6; - break; - case "Energy": - this.dmd = 90; this.dmdR = [80, 99]; - this.cmp = 80; this.cmpR = [65, 95]; - this.bCost = 2000; this.mv = 0.2; - this.mku = 6; - break; - case "Food": - this.dmd = 80; this.dmdR = [70, 90]; - this.cmp = 60; this.cmpR = [35, 85]; - this.bCost = 5000; this.mv = 1; - this.mku = 3; - break; - case "Plants": - this.dmd = 70; this.dmdR = [20, 90]; - this.cmp = 50; this.cmpR = [30, 70]; - this.bCost = 3000; this.mv = 0.6; - this.mku = 3.75; - break; - case "Metal": - this.dmd = 80; this.dmdR = [75, 85]; - this.cmp = 70; this.cmpR = [60, 80]; - this.bCost = 2650; this.mv = 1; - this.mku = 6; - break; - case "Hardware": - this.dmd = 85; this.dmdR = [80, 90]; - this.cmp = 80; this.cmpR = [65, 95]; - this.bCost = 8e3; this.mv = 0.5; //Less mv bc its processed twice - this.mku = 1; - break; - case "Chemicals": - this.dmd = 55; this.dmdR = [40, 70]; - this.cmp = 60; this.cmpR = [40, 80]; - this.bCost = 9e3; this.mv = 1.2; - this.mku = 2; - break; - case "Real Estate": - this.dmd = 50; this.dmdR = [5, 99]; - this.cmp = 50; this.cmpR = [25, 75]; - this.bCost = 80e3; this.mv = 1.5; //Less mv bc its processed twice - this.mku = 1.5; - break; - case "Drugs": - this.dmd = 60; this.dmdR = [45, 75]; - this.cmp = 70; this.cmpR = [40, 99]; - this.bCost = 40e3; this.mv = 1.6; - this.mku = 1; - break; - case "Robots": - this.dmd = 90; this.dmdR = [80, 9]; - this.cmp = 90; this.cmpR = [80, 9]; - this.bCost = 75e3; this.mv = 0.5; //Less mv bc its processed twice - this.mku = 1; - break; - case "AI Cores": - this.dmd = 90; this.dmdR = [80, 99]; - this.cmp = 90; this.cmpR = [80, 9]; - this.bCost = 15e3; this.mv = 0.8; //Less mv bc its processed twice - this.mku = 0.5; - break; - case "Scientific Research": - case "InitName": - break; - default: - console.error(`Invalid material type in init(): ${this.name}`); - break; - } - } + // Serialize the current object to a JSON save state. + toJSON(): any { + return Generic_toJSON("Material", this); + } - // Process change in demand, competition, and buy cost of this material - processMarket(): void { - // The price will change in accordance with demand and competition. - // e.g. If demand goes up, then so does price. If competition goes up, price goes down - const priceVolatility: number = (Math.random() * this.mv) / 300; - const priceChange: number = 1 + priceVolatility; - - //This 1st random check determines whether competition increases or decreases - const compVolatility: number = (Math.random() * this.mv) / 100; - const compChange: number = 1 + compVolatility; - if (Math.random() < 0.5) { - this.cmp *= compChange; - if (this.cmp > this.cmpR[1]) {this.cmp = this.cmpR[1]} - this.bCost *= (1 / priceChange); // Competition increases, so price goes down - } else { - this.cmp *= (1 / compChange); - if (this.cmp < this.cmpR[0]) {this.cmp = this.cmpR[0];} - this.bCost *= priceChange; // Competition decreases, so price goes up - } - - // This 2nd random check determines whether demand increases or decreases - const dmdVolatility: number = (Math.random() * this.mv) / 100; - const dmdChange: number = 1 + dmdVolatility; - if (Math.random() < 0.5) { - this.dmd *= dmdChange; - if (this.dmd > this.dmdR[1]) {this.dmd = this.dmdR[1];} - this.bCost *= priceChange; // Demand increases, so price goes up - } else { - this.dmd *= (1 / dmdChange); - if (this.dmd < this.dmdR[0]) {this.dmd = this.dmdR[0];} - this.bCost *= (1 / priceChange); - } - } - - // Serialize the current object to a JSON save state. - toJSON(): any { - return Generic_toJSON("Material", this); - } - - // Initiatizes a Material object from a JSON save state. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Material { - return Generic_fromJSON(Material, value.data); - } + // Initiatizes a Material object from a JSON save state. + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Material { + return Generic_fromJSON(Material, value.data); + } } Reviver.constructors.Material = Material; diff --git a/src/Corporation/MaterialSizes.ts b/src/Corporation/MaterialSizes.ts index 2ec694f25..1c47340f4 100644 --- a/src/Corporation/MaterialSizes.ts +++ b/src/Corporation/MaterialSizes.ts @@ -2,17 +2,17 @@ import { IMap } from "../types"; // Map of material (by name) to their sizes (how much space it takes in warehouse) export const MaterialSizes: IMap = { - Water: 0.05, - Energy: 0.01, - Food: 0.03, - Plants: 0.05, - Metal: 0.1, - Hardware: 0.06, - Chemicals: 0.05, - Drugs: 0.02, - Robots: 0.5, - AICores: 0.1, - RealEstate: 0, - "Real Estate": 0, - "AI Cores": 0, -} + Water: 0.05, + Energy: 0.01, + Food: 0.03, + Plants: 0.05, + Metal: 0.1, + Hardware: 0.06, + Chemicals: 0.05, + Drugs: 0.02, + Robots: 0.5, + AICores: 0.1, + RealEstate: 0, + "Real Estate": 0, + "AI Cores": 0, +}; diff --git a/src/Corporation/OfficeSpace.ts b/src/Corporation/OfficeSpace.ts index 057813df2..2529c043c 100644 --- a/src/Corporation/OfficeSpace.ts +++ b/src/Corporation/OfficeSpace.ts @@ -2,197 +2,210 @@ import { EmployeePositions } from "./EmployeePositions"; import { CorporationConstants } from "./data/Constants"; import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { generateRandomString } from "../../utils/StringHelperFunctions"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; import { Employee } from "./Employee"; import { IIndustry } from "./IIndustry"; -import { ICorporation } from './ICorporation'; +import { ICorporation } from "./ICorporation"; interface IParams { - loc?: string; - cost?: number; - size?: number; - comfort?: number; - beauty?: number; + loc?: string; + cost?: number; + size?: number; + comfort?: number; + beauty?: number; } export class OfficeSpace { - loc: string; - cost: number; - size: number; - comf: number; - beau: number; - tier = "Basic"; - minEne = 0; - maxEne = 100; - minHap = 0; - maxHap = 100; - maxMor = 100; - employees: Employee[] = []; - employeeProd: {[key: string]: number} = { - [EmployeePositions.Operations]: 0, - [EmployeePositions.Engineer]: 0, - [EmployeePositions.Business]: 0, - [EmployeePositions.Management]: 0, - [EmployeePositions.RandD]: 0, - total: 0, - }; + loc: string; + cost: number; + size: number; + comf: number; + beau: number; + tier = "Basic"; + minEne = 0; + maxEne = 100; + minHap = 0; + maxHap = 100; + maxMor = 100; + employees: Employee[] = []; + employeeProd: { [key: string]: number } = { + [EmployeePositions.Operations]: 0, + [EmployeePositions.Engineer]: 0, + [EmployeePositions.Business]: 0, + [EmployeePositions.Management]: 0, + [EmployeePositions.RandD]: 0, + total: 0, + }; - constructor(params: IParams = {}) { - this.loc = params.loc ? params.loc : ""; - this.cost = params.cost ? params.cost : 1; - this.size = params.size ? params.size : 1; - this.comf = params.comfort ? params.comfort : 1; - this.beau = params.beauty ? params.beauty : 1; + constructor(params: IParams = {}) { + this.loc = params.loc ? params.loc : ""; + this.cost = params.cost ? params.cost : 1; + this.size = params.size ? params.size : 1; + this.comf = params.comfort ? params.comfort : 1; + this.beau = params.beauty ? params.beauty : 1; + } + + atCapacity(): boolean { + return this.employees.length >= this.size; + } + + process( + marketCycles = 1, + corporation: ICorporation, + industry: IIndustry, + ): number { + // HRBuddy AutoRecruitment and training + if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) { + const emp = this.hireRandomEmployee(); + if (industry.hasResearch("HRBuddy-Training") && emp !== undefined) { + emp.pos = EmployeePositions.Training; + } } - - atCapacity(): boolean { - return (this.employees.length) >= this.size; + // Process Office properties + this.maxEne = 100; + this.maxHap = 100; + this.maxMor = 100; + if (industry.hasResearch("Go-Juice")) { + this.maxEne += 10; + } + if (industry.hasResearch("JoyWire")) { + this.maxHap += 10; + } + if (industry.hasResearch("Sti.mu")) { + this.maxMor += 10; } - process(marketCycles = 1, corporation: ICorporation, industry: IIndustry): number { - // HRBuddy AutoRecruitment and training - if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) { - const emp = this.hireRandomEmployee(); - if (industry.hasResearch("HRBuddy-Training") && emp !== undefined) { - emp.pos = EmployeePositions.Training; - } - } - - // Process Office properties - this.maxEne = 100; - this.maxHap = 100; - this.maxMor = 100; - if (industry.hasResearch("Go-Juice")) { - this.maxEne += 10; - } - if (industry.hasResearch("JoyWire")) { - this.maxHap += 10; - } - if (industry.hasResearch("Sti.mu")) { - this.maxMor += 10; - } - - // Calculate changes in Morale/Happiness/Energy for Employees - let perfMult=1; //Multiplier for employee morale/happiness/energy based on company performance - if (corporation.funds < 0 && industry.lastCycleRevenue < 0) { - perfMult = Math.pow(0.99, marketCycles); - } else if (corporation.funds > 0 && industry.lastCycleRevenue > 0) { - perfMult = Math.pow(1.01, marketCycles); - } - - const hasAutobrew = industry.hasResearch("AutoBrew"); - const hasAutoparty = industry.hasResearch("AutoPartyManager"); - - let salaryPaid = 0; - for (let i = 0; i < this.employees.length; ++i) { - const emp = this.employees[i]; - if (hasAutoparty) { - emp.mor = this.maxMor; - emp.hap = this.maxHap; - } else { - emp.mor *= perfMult; - emp.hap *= perfMult; - emp.mor = Math.min(emp.mor, this.maxMor); - emp.hap = Math.min(emp.hap, this.maxHap); - } - - if (hasAutobrew) { - emp.ene = this.maxEne; - } else { - emp.ene *= perfMult; - emp.ene = Math.min(emp.ene, this.maxEne); - } - - const salary = emp.process(marketCycles, this); - salaryPaid += salary; - } - - this.calculateEmployeeProductivity(corporation, industry); - return salaryPaid; + // Calculate changes in Morale/Happiness/Energy for Employees + let perfMult = 1; //Multiplier for employee morale/happiness/energy based on company performance + if (corporation.funds < 0 && industry.lastCycleRevenue < 0) { + perfMult = Math.pow(0.99, marketCycles); + } else if (corporation.funds > 0 && industry.lastCycleRevenue > 0) { + perfMult = Math.pow(1.01, marketCycles); } - calculateEmployeeProductivity(corporation: ICorporation, industry: IIndustry): void { - //Reset - for (const name in this.employeeProd) { - this.employeeProd[name] = 0; - } + const hasAutobrew = industry.hasResearch("AutoBrew"); + const hasAutoparty = industry.hasResearch("AutoPartyManager"); - let total = 0; - for (let i = 0; i < this.employees.length; ++i) { - const employee = this.employees[i]; - const prod = employee.calculateProductivity(corporation, industry); - this.employeeProd[employee.pos] += prod; - total += prod; - } - this.employeeProd.total = total; + let salaryPaid = 0; + for (let i = 0; i < this.employees.length; ++i) { + const emp = this.employees[i]; + if (hasAutoparty) { + emp.mor = this.maxMor; + emp.hap = this.maxHap; + } else { + emp.mor *= perfMult; + emp.hap *= perfMult; + emp.mor = Math.min(emp.mor, this.maxMor); + emp.hap = Math.min(emp.hap, this.maxHap); + } + + if (hasAutobrew) { + emp.ene = this.maxEne; + } else { + emp.ene *= perfMult; + emp.ene = Math.min(emp.ene, this.maxEne); + } + + const salary = emp.process(marketCycles, this); + salaryPaid += salary; } - hireRandomEmployee(): Employee | undefined { - if (this.atCapacity()) return; - if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) return; + this.calculateEmployeeProductivity(corporation, industry); + return salaryPaid; + } - //Generate three random employees (meh, decent, amazing) - const mult = getRandomInt(76, 100)/100; - const int = getRandomInt(50, 100), - cha = getRandomInt(50, 100), - exp = getRandomInt(50, 100), - cre = getRandomInt(50, 100), - eff = getRandomInt(50, 100), - sal = CorporationConstants.EmployeeSalaryMultiplier * (int + cha + exp + cre + eff); - - const emp = new Employee({ - intelligence: int * mult, - charisma: cha * mult, - experience: exp * mult, - creativity: cre * mult, - efficiency: eff * mult, - salary: sal * mult, - }); - - const name = generateRandomString(7); - - for (let i = 0; i < this.employees.length; ++i) { - if (this.employees[i].name === name) { - return this.hireRandomEmployee(); - } - } - emp.name = name; - this.employees.push(emp); - - return emp; + calculateEmployeeProductivity( + corporation: ICorporation, + industry: IIndustry, + ): void { + //Reset + for (const name in this.employeeProd) { + this.employeeProd[name] = 0; } - //Finds the first unassigned employee and assigns its to the specified job - assignEmployeeToJob(job: string): boolean { - for (let i = 0; i < this.employees.length; ++i) { - if (this.employees[i].pos === EmployeePositions.Unassigned) { - this.employees[i].pos = job; - return true; - } - } - return false; + let total = 0; + for (let i = 0; i < this.employees.length; ++i) { + const employee = this.employees[i]; + const prod = employee.calculateProductivity(corporation, industry); + this.employeeProd[employee.pos] += prod; + total += prod; } + this.employeeProd.total = total; + } - //Finds the first employee with the given job and unassigns it - unassignEmployeeFromJob(job: string): boolean { - for (let i = 0; i < this.employees.length; ++i) { - if (this.employees[i].pos === job) { - this.employees[i].pos = EmployeePositions.Unassigned; - return true; - } - } - return false; - } + hireRandomEmployee(): Employee | undefined { + if (this.atCapacity()) return; + if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) + return; - toJSON(): any { - return Generic_toJSON("OfficeSpace", this); - } + //Generate three random employees (meh, decent, amazing) + const mult = getRandomInt(76, 100) / 100; + const int = getRandomInt(50, 100), + cha = getRandomInt(50, 100), + exp = getRandomInt(50, 100), + cre = getRandomInt(50, 100), + eff = getRandomInt(50, 100), + sal = + CorporationConstants.EmployeeSalaryMultiplier * + (int + cha + exp + cre + eff); - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): OfficeSpace { - return Generic_fromJSON(OfficeSpace, value.data); + const emp = new Employee({ + intelligence: int * mult, + charisma: cha * mult, + experience: exp * mult, + creativity: cre * mult, + efficiency: eff * mult, + salary: sal * mult, + }); + + const name = generateRandomString(7); + + for (let i = 0; i < this.employees.length; ++i) { + if (this.employees[i].name === name) { + return this.hireRandomEmployee(); + } } + emp.name = name; + this.employees.push(emp); + + return emp; + } + + //Finds the first unassigned employee and assigns its to the specified job + assignEmployeeToJob(job: string): boolean { + for (let i = 0; i < this.employees.length; ++i) { + if (this.employees[i].pos === EmployeePositions.Unassigned) { + this.employees[i].pos = job; + return true; + } + } + return false; + } + + //Finds the first employee with the given job and unassigns it + unassignEmployeeFromJob(job: string): boolean { + for (let i = 0; i < this.employees.length; ++i) { + if (this.employees[i].pos === job) { + this.employees[i].pos = EmployeePositions.Unassigned; + return true; + } + } + return false; + } + + toJSON(): any { + return Generic_toJSON("OfficeSpace", this); + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): OfficeSpace { + return Generic_fromJSON(OfficeSpace, value.data); + } } -Reviver.constructors.OfficeSpace = OfficeSpace; \ No newline at end of file +Reviver.constructors.OfficeSpace = OfficeSpace; diff --git a/src/Corporation/Product.ts b/src/Corporation/Product.ts index 3ac15ff6d..1385960b7 100644 --- a/src/Corporation/Product.ts +++ b/src/Corporation/Product.ts @@ -1,236 +1,273 @@ -import { EmployeePositions } from "./EmployeePositions"; -import { MaterialSizes } from "./MaterialSizes"; -import { IIndustry } from "./IIndustry"; -import { ProductRatingWeights, - IProductRatingWeight } from "./ProductRatingWeights"; +import { EmployeePositions } from "./EmployeePositions"; +import { MaterialSizes } from "./MaterialSizes"; +import { IIndustry } from "./IIndustry"; +import { + ProductRatingWeights, + IProductRatingWeight, +} from "./ProductRatingWeights"; -import { createCityMap } from "../Locations/createCityMap"; -import { IMap } from "../types"; +import { createCityMap } from "../Locations/createCityMap"; +import { IMap } from "../types"; -import { Generic_fromJSON, - Generic_toJSON, - Reviver } from "../../utils/JSONReviver"; -import { getRandomInt } from "../../utils/helpers/getRandomInt"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; +import { getRandomInt } from "../../utils/helpers/getRandomInt"; interface IConstructorParams { - name?: string; - demand?: number; - competition?: number; - markup?: number; - createCity?: string; - designCost?: number; - advCost?: number; - quality?: number; - performance?: number; - durability?: number; - reliability?: number; - aesthetics?: number; - features?: number; - loc?: string; - size?: number; - req?: IMap; + name?: string; + demand?: number; + competition?: number; + markup?: number; + createCity?: string; + designCost?: number; + advCost?: number; + quality?: number; + performance?: number; + durability?: number; + reliability?: number; + aesthetics?: number; + features?: number; + loc?: string; + size?: number; + req?: IMap; } export class Product { + // Product name + name = ""; - // Product name - name = ""; + // The demand for this Product in the market. Gradually decreases + dmd = 0; - // The demand for this Product in the market. Gradually decreases - dmd = 0; + // How much competition there is in the market for this Product + cmp = 0; - // How much competition there is in the market for this Product - cmp = 0; + // Markup. Affects how high of a price you can charge for this Product + // without suffering a loss in the # of sales + mku = 0; - // Markup. Affects how high of a price you can charge for this Product - // without suffering a loss in the # of sales - mku = 0; + // Production cost - estimation of how much money it costs to make this Product + pCost = 0; - // Production cost - estimation of how much money it costs to make this Product - pCost = 0; + // Sell cost + sCost: string | number = 0; - // Sell cost - sCost: string | number = 0; + // Variables for handling the creation process of this Product + fin = false; // Whether this Product has finished being created + prog = 0; // Creation progress - A number betwee 0-100 representing percentage + createCity = ""; // City in which the product is/was being created + designCost = 0; // How much money was invested into designing this Product + advCost = 0; // How much money was invested into advertising this Product - // Variables for handling the creation process of this Product - fin = false; // Whether this Product has finished being created - prog = 0; // Creation progress - A number betwee 0-100 representing percentage - createCity = ""; // City in which the product is/was being created - designCost = 0; // How much money was invested into designing this Product - advCost = 0; // How much money was invested into advertising this Product + // Aggregate score for this Product's 'rating' + // This is based on the stats/properties below. The weighting of the + // stats/properties below differs between different industries + rat = 0; - // Aggregate score for this Product's 'rating' - // This is based on the stats/properties below. The weighting of the - // stats/properties below differs between different industries - rat = 0; + // Stats/properties of this Product + qlt = 0; + per = 0; + dur = 0; + rel = 0; + aes = 0; + fea = 0; - // Stats/properties of this Product - qlt = 0; - per = 0; - dur = 0; - rel = 0; - aes = 0; - fea = 0; + // Data refers to the production, sale, and quantity of the products + // These values are specific to a city + // For each city, the data is [qty, prod, sell] + data: IMap = createCityMap([0, 0, 0]); - // Data refers to the production, sale, and quantity of the products - // These values are specific to a city - // For each city, the data is [qty, prod, sell] - data: IMap = createCityMap([0, 0, 0]); + // Location of this Product + // Only applies for location-based products like restaurants/hospitals + loc = ""; - // Location of this Product - // Only applies for location-based products like restaurants/hospitals - loc = ""; + // How much space 1 unit of the Product takes (in the warehouse) + // Not applicable for all Products + siz = 0; - // How much space 1 unit of the Product takes (in the warehouse) - // Not applicable for all Products - siz = 0; + // Material requirements. An object that maps the name of a material to how much it requires + // to make 1 unit of the product. + reqMats: IMap = {}; - // Material requirements. An object that maps the name of a material to how much it requires - // to make 1 unit of the product. - reqMats: IMap = {}; + // Data to keep track of whether production/sale of this Product is + // manually limited. These values are specific to a city + // [Whether production/sale is limited, limit amount] + prdman: IMap = createCityMap([false, 0]); + sllman: IMap = createCityMap([false, 0]); - // Data to keep track of whether production/sale of this Product is - // manually limited. These values are specific to a city - // [Whether production/sale is limited, limit amount] - prdman: IMap = createCityMap([false, 0]); - sllman: IMap = createCityMap([false, 0]); + // Flags that signal whether automatic sale pricing through Market TA is enabled + marketTa1 = false; + marketTa2 = false; + marketTa2Price: IMap = createCityMap(0); - // Flags that signal whether automatic sale pricing through Market TA is enabled - marketTa1 = false; - marketTa2 = false; - marketTa2Price: IMap = createCityMap(0); + constructor(params: IConstructorParams = {}) { + this.name = params.name ? params.name : ""; + this.dmd = params.demand ? params.demand : 0; + this.cmp = params.competition ? params.competition : 0; + this.mku = params.markup ? params.markup : 0; + this.createCity = params.createCity ? params.createCity : ""; + this.designCost = params.designCost ? params.designCost : 0; + this.advCost = params.advCost ? params.advCost : 0; + this.qlt = params.quality ? params.quality : 0; + this.per = params.performance ? params.performance : 0; + this.dur = params.durability ? params.durability : 0; + this.rel = params.reliability ? params.reliability : 0; + this.aes = params.aesthetics ? params.aesthetics : 0; + this.fea = params.features ? params.features : 0; + this.loc = params.loc ? params.loc : ""; + this.siz = params.size ? params.size : 0; + this.reqMats = params.req ? params.req : {}; + } - constructor(params: IConstructorParams={}) { - this.name = params.name ? params.name : ""; - this.dmd = params.demand ? params.demand : 0; - this.cmp = params.competition ? params.competition : 0; - this.mku = params.markup ? params.markup : 0; - this.createCity = params.createCity ? params.createCity : ""; - this.designCost = params.designCost ? params.designCost : 0; - this.advCost = params.advCost ? params.advCost : 0; - this.qlt = params.quality ? params.quality : 0; - this.per = params.performance ? params.performance : 0; - this.dur = params.durability ? params.durability : 0; - this.rel = params.reliability ? params.reliability : 0; - this.aes = params.aesthetics ? params.aesthetics : 0; - this.fea = params.features ? params.features : 0; - this.loc = params.loc ? params.loc : ""; - this.siz = params.size ? params.size : 0; - this.reqMats = params.req ? params.req : {}; + // empWorkMult is a multiplier that increases progress rate based on + // productivity of employees + createProduct(marketCycles = 1, empWorkMult = 1): void { + if (this.fin) { + return; + } + this.prog += marketCycles * 0.01 * empWorkMult; + } + + // @param industry - Industry object. Reference to industry that makes this Product + finishProduct( + employeeProd: { [key: string]: number }, + industry: IIndustry, + ): void { + this.fin = true; + + //Calculate properties + const progrMult = this.prog / 100; + + const engrRatio = + employeeProd[EmployeePositions.Engineer] / employeeProd["total"]; + const mgmtRatio = + employeeProd[EmployeePositions.Management] / employeeProd["total"]; + const rndRatio = + employeeProd[EmployeePositions.RandD] / employeeProd["total"]; + const opsRatio = + employeeProd[EmployeePositions.Operations] / employeeProd["total"]; + const busRatio = + employeeProd[EmployeePositions.Business] / employeeProd["total"]; + const designMult = 1 + Math.pow(this.designCost, 0.1) / 100; + const balanceMult = + 1.2 * engrRatio + + 0.9 * mgmtRatio + + 1.3 * rndRatio + + 1.5 * opsRatio + + busRatio; + const sciMult = + 1 + Math.pow(industry.sciResearch.qty, industry.sciFac) / 800; + const totalMult = progrMult * balanceMult * designMult * sciMult; + + this.qlt = + totalMult * + (0.1 * employeeProd[EmployeePositions.Engineer] + + 0.05 * employeeProd[EmployeePositions.Management] + + 0.05 * employeeProd[EmployeePositions.RandD] + + 0.02 * employeeProd[EmployeePositions.Operations] + + 0.02 * employeeProd[EmployeePositions.Business]); + this.per = + totalMult * + (0.15 * employeeProd[EmployeePositions.Engineer] + + 0.02 * employeeProd[EmployeePositions.Management] + + 0.02 * employeeProd[EmployeePositions.RandD] + + 0.02 * employeeProd[EmployeePositions.Operations] + + 0.02 * employeeProd[EmployeePositions.Business]); + this.dur = + totalMult * + (0.05 * employeeProd[EmployeePositions.Engineer] + + 0.02 * employeeProd[EmployeePositions.Management] + + 0.08 * employeeProd[EmployeePositions.RandD] + + 0.05 * employeeProd[EmployeePositions.Operations] + + 0.05 * employeeProd[EmployeePositions.Business]); + this.rel = + totalMult * + (0.02 * employeeProd[EmployeePositions.Engineer] + + 0.08 * employeeProd[EmployeePositions.Management] + + 0.02 * employeeProd[EmployeePositions.RandD] + + 0.05 * employeeProd[EmployeePositions.Operations] + + 0.08 * employeeProd[EmployeePositions.Business]); + this.aes = + totalMult * + (0.0 * employeeProd[EmployeePositions.Engineer] + + 0.08 * employeeProd[EmployeePositions.Management] + + 0.05 * employeeProd[EmployeePositions.RandD] + + 0.02 * employeeProd[EmployeePositions.Operations] + + 0.1 * employeeProd[EmployeePositions.Business]); + this.fea = + totalMult * + (0.08 * employeeProd[EmployeePositions.Engineer] + + 0.05 * employeeProd[EmployeePositions.Management] + + 0.02 * employeeProd[EmployeePositions.RandD] + + 0.05 * employeeProd[EmployeePositions.Operations] + + 0.05 * employeeProd[EmployeePositions.Business]); + this.calculateRating(industry); + const advMult = 1 + Math.pow(this.advCost, 0.1) / 100; + this.mku = + 100 / + (advMult * Math.pow(this.qlt + 0.001, 0.65) * (busRatio + mgmtRatio)); + + // I actually don't understand well enough to know if this is right. + // I'm adding this to prevent a crash. + if (this.mku === 0) this.mku = 1; + + this.dmd = + industry.awareness === 0 + ? 20 + : Math.min( + 100, + advMult * (100 * (industry.popularity / industry.awareness)), + ); + this.cmp = getRandomInt(0, 70); + + //Calculate the product's required materials + //For now, just set it to be the same as the requirements to make materials + for (const matName in industry.reqMats) { + if (industry.reqMats.hasOwnProperty(matName)) { + const reqMat = industry.reqMats[matName]; + if (reqMat === undefined) continue; + this.reqMats[matName] = reqMat; + } } - // empWorkMult is a multiplier that increases progress rate based on - // productivity of employees - createProduct(marketCycles=1, empWorkMult=1): void { - if (this.fin) { return; } - this.prog += (marketCycles * .01 * empWorkMult); + //Calculate the product's size + //For now, just set it to be the same size as the requirements to make materials + this.siz = 0; + for (const matName in industry.reqMats) { + const reqMat = industry.reqMats[matName]; + if (reqMat === undefined) continue; + this.siz += MaterialSizes[matName] * reqMat; } + } - // @param industry - Industry object. Reference to industry that makes this Product - finishProduct(employeeProd: {[key: string]: number}, industry: IIndustry): void { - this.fin = true; - - //Calculate properties - const progrMult = this.prog / 100; - - const engrRatio = employeeProd[EmployeePositions.Engineer] / employeeProd["total"]; - const mgmtRatio = employeeProd[EmployeePositions.Management] / employeeProd["total"]; - const rndRatio = employeeProd[EmployeePositions.RandD] / employeeProd["total"]; - const opsRatio = employeeProd[EmployeePositions.Operations] / employeeProd["total"]; - const busRatio = employeeProd[EmployeePositions.Business] / employeeProd["total"]; - const designMult = 1 + (Math.pow(this.designCost, 0.1) / 100); - const balanceMult = (1.2 * engrRatio) + (0.9 * mgmtRatio) + (1.3 * rndRatio) + - (1.5 * opsRatio) + (busRatio); - const sciMult = 1 + (Math.pow(industry.sciResearch.qty, industry.sciFac) / 800); - const totalMult = progrMult * balanceMult * designMult * sciMult; - - this.qlt = totalMult * ((0.10 * employeeProd[EmployeePositions.Engineer]) + - (0.05 * employeeProd[EmployeePositions.Management]) + - (0.05 * employeeProd[EmployeePositions.RandD]) + - (0.02 * employeeProd[EmployeePositions.Operations]) + - (0.02 * employeeProd[EmployeePositions.Business])); - this.per = totalMult * ((0.15 * employeeProd[EmployeePositions.Engineer]) + - (0.02 * employeeProd[EmployeePositions.Management]) + - (0.02 * employeeProd[EmployeePositions.RandD]) + - (0.02 * employeeProd[EmployeePositions.Operations]) + - (0.02 * employeeProd[EmployeePositions.Business])); - this.dur = totalMult * ((0.05 * employeeProd[EmployeePositions.Engineer]) + - (0.02 * employeeProd[EmployeePositions.Management]) + - (0.08 * employeeProd[EmployeePositions.RandD]) + - (0.05 * employeeProd[EmployeePositions.Operations]) + - (0.05 * employeeProd[EmployeePositions.Business])); - this.rel = totalMult * ((0.02 * employeeProd[EmployeePositions.Engineer]) + - (0.08 * employeeProd[EmployeePositions.Management]) + - (0.02 * employeeProd[EmployeePositions.RandD]) + - (0.05 * employeeProd[EmployeePositions.Operations]) + - (0.08 * employeeProd[EmployeePositions.Business])); - this.aes = totalMult * ((0.00 * employeeProd[EmployeePositions.Engineer]) + - (0.08 * employeeProd[EmployeePositions.Management]) + - (0.05 * employeeProd[EmployeePositions.RandD]) + - (0.02 * employeeProd[EmployeePositions.Operations]) + - (0.10 * employeeProd[EmployeePositions.Business])); - this.fea = totalMult * ((0.08 * employeeProd[EmployeePositions.Engineer]) + - (0.05 * employeeProd[EmployeePositions.Management]) + - (0.02 * employeeProd[EmployeePositions.RandD]) + - (0.05 * employeeProd[EmployeePositions.Operations]) + - (0.05 * employeeProd[EmployeePositions.Business])); - this.calculateRating(industry); - const advMult = 1 + (Math.pow(this.advCost, 0.1) / 100); - this.mku = 100 / (advMult * Math.pow((this.qlt + 0.001), 0.65) * (busRatio + mgmtRatio)); - - // I actually don't understand well enough to know if this is right. - // I'm adding this to prevent a crash. - if(this.mku === 0) this.mku = 1; - - this.dmd = industry.awareness === 0 ? 20 : Math.min(100, advMult * (100 * (industry.popularity / industry.awareness))); - this.cmp = getRandomInt(0, 70); - - //Calculate the product's required materials - //For now, just set it to be the same as the requirements to make materials - for (const matName in industry.reqMats) { - if (industry.reqMats.hasOwnProperty(matName)) { - const reqMat = industry.reqMats[matName]; - if(reqMat === undefined) continue; - this.reqMats[matName] = reqMat; - } - } - - //Calculate the product's size - //For now, just set it to be the same size as the requirements to make materials - this.siz = 0; - for (const matName in industry.reqMats) { - const reqMat = industry.reqMats[matName]; - if(reqMat === undefined) continue; - this.siz += MaterialSizes[matName] * reqMat; - } + calculateRating(industry: IIndustry): void { + const weights: IProductRatingWeight = ProductRatingWeights[industry.type]; + if (weights == null) { + console.error(`Could not find product rating weights for: ${industry}`); + return; } + this.rat = 0; + this.rat += weights.Quality ? this.qlt * weights.Quality : 0; + this.rat += weights.Performance ? this.per * weights.Performance : 0; + this.rat += weights.Durability ? this.dur * weights.Durability : 0; + this.rat += weights.Reliability ? this.rel * weights.Reliability : 0; + this.rat += weights.Aesthetics ? this.aes * weights.Aesthetics : 0; + this.rat += weights.Features ? this.fea * weights.Features : 0; + } + // Serialize the current object to a JSON save state. + toJSON(): any { + return Generic_toJSON("Product", this); + } - calculateRating(industry: IIndustry): void { - const weights: IProductRatingWeight = ProductRatingWeights[industry.type]; - if (weights == null) { - console.error(`Could not find product rating weights for: ${industry}`); - return; - } - this.rat = 0; - this.rat += weights.Quality ? this.qlt * weights.Quality : 0; - this.rat += weights.Performance ? this.per * weights.Performance : 0; - this.rat += weights.Durability ? this.dur * weights.Durability : 0; - this.rat += weights.Reliability ? this.rel * weights.Reliability : 0; - this.rat += weights.Aesthetics ? this.aes * weights.Aesthetics : 0; - this.rat += weights.Features ? this.fea * weights.Features : 0; - } - - // Serialize the current object to a JSON save state. - toJSON(): any { - return Generic_toJSON("Product", this); - } - - // Initiatizes a Product object from a JSON save state. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Product { - return Generic_fromJSON(Product, value.data); - } + // Initiatizes a Product object from a JSON save state. + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Product { + return Generic_fromJSON(Product, value.data); + } } Reviver.constructors.Product = Product; diff --git a/src/Corporation/ProductRatingWeights.ts b/src/Corporation/ProductRatingWeights.ts index 8459fa6bf..667a59f0b 100644 --- a/src/Corporation/ProductRatingWeights.ts +++ b/src/Corporation/ProductRatingWeights.ts @@ -2,76 +2,77 @@ import { Industries } from "./IndustryData"; import { IMap } from "../types"; export interface IProductRatingWeight { - Aesthetics?: number; - Durability?: number; - Features?: number; - Quality?: number; - Performance?: number; - Reliability?: number; + Aesthetics?: number; + Durability?: number; + Features?: number; + Quality?: number; + Performance?: number; + Reliability?: number; } export const ProductRatingWeights: IMap = { - [Industries.Food]: { - Quality: 0.7, - Durability: 0.1, - Aesthetics: 0.2, - }, - [Industries.Tobacco]: { - Quality: 0.4, - Durability: 0.2, - Reliability: 0.2, - Aesthetics: 0.2, - }, - [Industries.Pharmaceutical]: { - Quality: 0.2, - Performance: 0.2, - Durability: 0.1, - Reliability: 0.3, - Features: 0.2, - }, - [Industries.Computer]: { - Quality: 0.15, - Performance: 0.25, - Durability: 0.25, - Reliability: 0.2, - Aesthetics: 0.05, - Features: 0.1, - }, - "Computer" : { //Repeat - Quality: 0.15, - Performance: 0.25, - Durability: 0.25, - Reliability: 0.2, - Aesthetics: 0.05, - Features: 0.1, - }, - [Industries.Robotics]: { - Quality: 0.1, - Performance: 0.2, - Durability: 0.2, - Reliability: 0.2, - Aesthetics: 0.1, - Features: 0.2, - }, - [Industries.Software]: { - Quality: 0.2, - Performance: 0.2, - Reliability: 0.2, - Durability: 0.2, - Features: 0.2, - }, - [Industries.Healthcare]: { - Quality: 0.4, - Performance: 0.1, - Durability: 0.1, - Reliability: 0.3, - Features: 0.1, - }, - [Industries.RealEstate]: { - Quality: 0.2, - Durability: 0.25, - Reliability: 0.1, - Aesthetics: 0.35, - Features: 0.1, - }, -} + [Industries.Food]: { + Quality: 0.7, + Durability: 0.1, + Aesthetics: 0.2, + }, + [Industries.Tobacco]: { + Quality: 0.4, + Durability: 0.2, + Reliability: 0.2, + Aesthetics: 0.2, + }, + [Industries.Pharmaceutical]: { + Quality: 0.2, + Performance: 0.2, + Durability: 0.1, + Reliability: 0.3, + Features: 0.2, + }, + [Industries.Computer]: { + Quality: 0.15, + Performance: 0.25, + Durability: 0.25, + Reliability: 0.2, + Aesthetics: 0.05, + Features: 0.1, + }, + Computer: { + //Repeat + Quality: 0.15, + Performance: 0.25, + Durability: 0.25, + Reliability: 0.2, + Aesthetics: 0.05, + Features: 0.1, + }, + [Industries.Robotics]: { + Quality: 0.1, + Performance: 0.2, + Durability: 0.2, + Reliability: 0.2, + Aesthetics: 0.1, + Features: 0.2, + }, + [Industries.Software]: { + Quality: 0.2, + Performance: 0.2, + Reliability: 0.2, + Durability: 0.2, + Features: 0.2, + }, + [Industries.Healthcare]: { + Quality: 0.4, + Performance: 0.1, + Durability: 0.1, + Reliability: 0.3, + Features: 0.1, + }, + [Industries.RealEstate]: { + Quality: 0.2, + Durability: 0.25, + Reliability: 0.1, + Aesthetics: 0.35, + Features: 0.1, + }, +}; diff --git a/src/Corporation/Research.ts b/src/Corporation/Research.ts index 6c370e8ab..85432166c 100644 --- a/src/Corporation/Research.ts +++ b/src/Corporation/Research.ts @@ -1,54 +1,74 @@ export interface IConstructorParams { - name: string; - cost: number; - desc: string; - advertisingMult?: number; - employeeChaMult?: number; - employeeCreMult?: number; - employeeEffMult?: number; - employeeIntMult?: number; - productionMult?: number; - productProductionMult?: number; - salesMult?: number; - sciResearchMult?: number; - storageMult?: number; + name: string; + cost: number; + desc: string; + advertisingMult?: number; + employeeChaMult?: number; + employeeCreMult?: number; + employeeEffMult?: number; + employeeIntMult?: number; + productionMult?: number; + productProductionMult?: number; + salesMult?: number; + sciResearchMult?: number; + storageMult?: number; } export class Research { - // Name of research. This will be used to identify researches in the Research Tree - name = ""; + // Name of research. This will be used to identify researches in the Research Tree + name = ""; - // How much scientific research it costs to unlock this - cost = 0; + // How much scientific research it costs to unlock this + cost = 0; - // Description of what the Research does - desc = ""; + // Description of what the Research does + desc = ""; - // All possible generic upgrades for the company, in the form of multipliers - advertisingMult = 1; - employeeChaMult = 1; - employeeCreMult = 1; - employeeEffMult = 1; - employeeIntMult = 1; - productionMult = 1; - productProductionMult = 1; - salesMult = 1; - sciResearchMult = 1; - storageMult = 1; + // All possible generic upgrades for the company, in the form of multipliers + advertisingMult = 1; + employeeChaMult = 1; + employeeCreMult = 1; + employeeEffMult = 1; + employeeIntMult = 1; + productionMult = 1; + productProductionMult = 1; + salesMult = 1; + sciResearchMult = 1; + storageMult = 1; - constructor(p: IConstructorParams={name: "", cost: 0, desc: ""}) { - this.name = p.name; - this.cost = p.cost; - this.desc = p.desc; - if (p.advertisingMult) { this.advertisingMult = p.advertisingMult; } - if (p.employeeChaMult) { this.employeeChaMult = p.employeeChaMult; } - if (p.employeeCreMult) { this.employeeCreMult = p.employeeCreMult; } - if (p.employeeEffMult) { this.employeeEffMult = p.employeeEffMult; } - if (p.employeeIntMult) { this.employeeIntMult = p.employeeIntMult; } - if (p.productionMult) { this.productionMult = p.productionMult; } - if (p.productProductionMult) { this.productProductionMult = p.productProductionMult; } - if (p.salesMult) { this.salesMult = p.salesMult; } - if (p.sciResearchMult) { this.sciResearchMult = p.sciResearchMult; } - if (p.storageMult) { this.storageMult = p.storageMult; } + constructor(p: IConstructorParams = { name: "", cost: 0, desc: "" }) { + this.name = p.name; + this.cost = p.cost; + this.desc = p.desc; + if (p.advertisingMult) { + this.advertisingMult = p.advertisingMult; } + if (p.employeeChaMult) { + this.employeeChaMult = p.employeeChaMult; + } + if (p.employeeCreMult) { + this.employeeCreMult = p.employeeCreMult; + } + if (p.employeeEffMult) { + this.employeeEffMult = p.employeeEffMult; + } + if (p.employeeIntMult) { + this.employeeIntMult = p.employeeIntMult; + } + if (p.productionMult) { + this.productionMult = p.productionMult; + } + if (p.productProductionMult) { + this.productProductionMult = p.productProductionMult; + } + if (p.salesMult) { + this.salesMult = p.salesMult; + } + if (p.sciResearchMult) { + this.sciResearchMult = p.sciResearchMult; + } + if (p.storageMult) { + this.storageMult = p.storageMult; + } + } } diff --git a/src/Corporation/ResearchMap.ts b/src/Corporation/ResearchMap.ts index 0159c3283..7b2ab3928 100644 --- a/src/Corporation/ResearchMap.ts +++ b/src/Corporation/ResearchMap.ts @@ -1,19 +1,18 @@ // The Research Map is an object that holds all Corporation Research objects // as values. They are identified by their names -import { Research, - IConstructorParams } from "./Research"; +import { Research, IConstructorParams } from "./Research"; import { researchMetadata } from "./data/ResearchMetadata"; import { IMap } from "../types"; export const ResearchMap: IMap = {}; function addResearch(p: IConstructorParams): void { - if (ResearchMap[p.name] != null) { - console.warn(`Duplicate Research being defined: ${p.name}`); - } - ResearchMap[p.name] = new Research(p); + if (ResearchMap[p.name] != null) { + console.warn(`Duplicate Research being defined: ${p.name}`); + } + ResearchMap[p.name] = new Research(p); } for (const metadata of researchMetadata) { - addResearch(metadata); + addResearch(metadata); } diff --git a/src/Corporation/ResearchTree.ts b/src/Corporation/ResearchTree.ts index c55a62e43..9cdaa8fc1 100644 --- a/src/Corporation/ResearchTree.ts +++ b/src/Corporation/ResearchTree.ts @@ -11,267 +11,294 @@ import { IMap } from "../types"; import { numeralWrapper } from "../ui/numeralFormat"; interface IConstructorParams { - children?: Node[]; - cost: number; - text: string; - parent?: Node | null; + children?: Node[]; + cost: number; + text: string; + parent?: Node | null; } export class Node { + // All child Nodes in the tree + // The Research held in this Node is a prerequisite for all Research in + // child Nodes + children: Node[] = []; - // All child Nodes in the tree - // The Research held in this Node is a prerequisite for all Research in - // child Nodes - children: Node[] = []; + // How much Scientific Research is needed for this + // Necessary to show it on the UI + cost = 0; - // How much Scientific Research is needed for this - // Necessary to show it on the UI - cost = 0; + // Whether or not this Research has been unlocked + researched = false; - // Whether or not this Research has been unlocked - researched = false; + // Parent node in the tree + // The parent node defines the prerequisite Research (there can only be one) + // Set as null for no prerequisites + parent: Node | null = null; - // Parent node in the tree - // The parent node defines the prerequisite Research (there can only be one) - // Set as null for no prerequisites - parent: Node | null = null; + // Name of the Research held in this Node + text = ""; - // Name of the Research held in this Node - text = ""; - - constructor(p: IConstructorParams = {cost: 0, text: ""}) { - if (ResearchMap[p.text] == null) { - throw new Error(`Invalid Research name used when constructing ResearchTree Node: ${p.text}`); - } - - this.text = p.text; - this.cost = p.cost; - - if (p.children && p.children.length > 0) { - this.children = p.children; - } - - if (p.parent != null) { - this.parent = p.parent; - } + constructor(p: IConstructorParams = { cost: 0, text: "" }) { + if (ResearchMap[p.text] == null) { + throw new Error( + `Invalid Research name used when constructing ResearchTree Node: ${p.text}`, + ); } - addChild(n: Node): void { - this.children.push(n); - n.parent = this; + this.text = p.text; + this.cost = p.cost; + + if (p.children && p.children.length > 0) { + this.children = p.children; } - // Return an object that describes a TreantJS-compatible markup/config for this Node - // See: http://fperucic.github.io/treant-js/ - createTreantMarkup(): any { - const childrenArray = []; - for (let i = 0; i < this.children.length; ++i) { - childrenArray.push(this.children[i].createTreantMarkup()); - } + if (p.parent != null) { + this.parent = p.parent; + } + } - // Determine what css class this Node should have in the diagram - let htmlClass = ""; - if (this.researched) { - htmlClass = "researched"; - } else if (this.parent && this.parent.researched === false) { - htmlClass = "locked"; - } else { - htmlClass = "unlocked"; - } + addChild(n: Node): void { + this.children.push(n); + n.parent = this; + } - const research: Research | null = ResearchMap[this.text]; - const sanitizedName: string = this.text.replace(/\s/g, ''); - return { - children: childrenArray, - HTMLclass: htmlClass, - innerHTML: `
    ` + - `${this.text}
    ${numeralWrapper.format(this.cost, "0,0")} Scientific Research` + - `` + - `${research.desc}` + - `` + - `
    ` , - text: { name: this.text }, - } + // Return an object that describes a TreantJS-compatible markup/config for this Node + // See: http://fperucic.github.io/treant-js/ + createTreantMarkup(): any { + const childrenArray = []; + for (let i = 0; i < this.children.length; ++i) { + childrenArray.push(this.children[i].createTreantMarkup()); } - // Recursive function for finding a Node with the specified text - findNode(text: string): Node | null { - // Is this the Node? - if (this.text === text) { return this; } - - // Recursively search chilren - let res = null; - for (let i = 0; i < this.children.length; ++i) { - res = this.children[i].findNode(text); - if (res != null) { return res; } - } - - return null; + // Determine what css class this Node should have in the diagram + let htmlClass = ""; + if (this.researched) { + htmlClass = "researched"; + } else if (this.parent && this.parent.researched === false) { + htmlClass = "locked"; + } else { + htmlClass = "unlocked"; } - setParent(n: Node): void { - this.parent = n; + const research: Research | null = ResearchMap[this.text]; + const sanitizedName: string = this.text.replace(/\s/g, ""); + return { + children: childrenArray, + HTMLclass: htmlClass, + innerHTML: + `
    ` + + `${this.text}
    ${numeralWrapper.format( + this.cost, + "0,0", + )} Scientific Research` + + `` + + `${research.desc}` + + `` + + `
    `, + text: { name: this.text }, + }; + } + + // Recursive function for finding a Node with the specified text + findNode(text: string): Node | null { + // Is this the Node? + if (this.text === text) { + return this; } + + // Recursively search chilren + let res = null; + for (let i = 0; i < this.children.length; ++i) { + res = this.children[i].findNode(text); + if (res != null) { + return res; + } + } + + return null; + } + + setParent(n: Node): void { + this.parent = n; + } } - // A ResearchTree defines all available Research in an Industry // The root node in a Research Tree must always be the "Hi-Tech R&D Laboratory" export class ResearchTree { - // Object containing names of all acquired Research by name - researched: IMap = {}; + // Object containing names of all acquired Research by name + researched: IMap = {}; - // Root Node - root: Node | null = null; + // Root Node + root: Node | null = null; - // Return an object that contains a Tree markup for TreantJS (using the JSON approach) - // See: http://fperucic.github.io/treant-js/ - createTreantMarkup(): any { - if (this.root == null) { return {}; } - - const treeMarkup = this.root.createTreantMarkup(); - - return { - chart: { - container: "", - }, - nodeStructure: treeMarkup, - }; + // Return an object that contains a Tree markup for TreantJS (using the JSON approach) + // See: http://fperucic.github.io/treant-js/ + createTreantMarkup(): any { + if (this.root == null) { + return {}; } - // Gets an array with the 'text' values of ALL Nodes in the Research Tree - getAllNodes(): string[] { - const res: string[] = []; - const queue: Node[] = []; + const treeMarkup = this.root.createTreantMarkup(); - if (this.root == null) { return res; } + return { + chart: { + container: "", + }, + nodeStructure: treeMarkup, + }; + } - queue.push(this.root); - while (queue.length !== 0) { - const node: Node | undefined = queue.shift(); - if (node == null) { continue; } + // Gets an array with the 'text' values of ALL Nodes in the Research Tree + getAllNodes(): string[] { + const res: string[] = []; + const queue: Node[] = []; - res.push(node.text); - for (let i = 0; i < node.children.length; ++i) { - queue.push(node.children[i]); - } - } - - return res; + if (this.root == null) { + return res; } - // Get total multipliers from this Research Tree - getAdvertisingMultiplier(): number { - return this.getMultiplierHelper("advertisingMult"); + queue.push(this.root); + while (queue.length !== 0) { + const node: Node | undefined = queue.shift(); + if (node == null) { + continue; + } + + res.push(node.text); + for (let i = 0; i < node.children.length; ++i) { + queue.push(node.children[i]); + } } - getEmployeeChaMultiplier(): number { - return this.getMultiplierHelper("employeeChaMult"); + return res; + } + + // Get total multipliers from this Research Tree + getAdvertisingMultiplier(): number { + return this.getMultiplierHelper("advertisingMult"); + } + + getEmployeeChaMultiplier(): number { + return this.getMultiplierHelper("employeeChaMult"); + } + + getEmployeeCreMultiplier(): number { + return this.getMultiplierHelper("employeeCreMult"); + } + + getEmployeeEffMultiplier(): number { + return this.getMultiplierHelper("employeeEffMult"); + } + + getEmployeeIntMultiplier(): number { + return this.getMultiplierHelper("employeeIntMult"); + } + + getProductionMultiplier(): number { + return this.getMultiplierHelper("productionMult"); + } + + getProductProductionMultiplier(): number { + return this.getMultiplierHelper("productProductionMult"); + } + + getSalesMultiplier(): number { + return this.getMultiplierHelper("salesMult"); + } + + getScientificResearchMultiplier(): number { + return this.getMultiplierHelper("sciResearchMult"); + } + + getStorageMultiplier(): number { + return this.getMultiplierHelper("storageMult"); + } + + // Helper function for all the multiplier getter fns + getMultiplierHelper(propName: string): number { + let res = 1; + if (this.root == null) { + return res; } - getEmployeeCreMultiplier(): number { - return this.getMultiplierHelper("employeeCreMult"); + const queue: Node[] = []; + queue.push(this.root); + while (queue.length !== 0) { + const node: Node | undefined = queue.shift(); + + // If the Node has not been researched, there's no need to + // process it or its children + if (node == null || !node.researched) { + continue; + } + + const research: Research | null = ResearchMap[node.text]; + + // Safety checks + if (research == null) { + console.warn(`Invalid Research name in node: ${node.text}`); + continue; + } + + const mult: any = (research)[propName]; + if (mult == null) { + console.warn( + `Invalid propName specified in ResearchTree.getMultiplierHelper: ${propName}`, + ); + continue; + } + + res *= mult; + for (let i = 0; i < node.children.length; ++i) { + queue.push(node.children[i]); + } } - getEmployeeEffMultiplier(): number { - return this.getMultiplierHelper("employeeEffMult"); + return res; + } + + // Search for a Node with the given name ('text' property on the Node) + // Returns 'null' if it cannot be found + findNode(name: string): Node | null { + if (this.root == null) { + return null; + } + return this.root.findNode(name); + } + + // Marks a Node as researched + research(name: string): void { + if (this.root == null) { + return; } - getEmployeeIntMultiplier(): number { - return this.getMultiplierHelper("employeeIntMult"); + const queue: Node[] = []; + queue.push(this.root); + while (queue.length !== 0) { + const node: Node | undefined = queue.shift(); + if (node == null) { + continue; + } + + if (node.text === name) { + node.researched = true; + this.researched[name] = true; + return; + } + + for (let i = 0; i < node.children.length; ++i) { + queue.push(node.children[i]); + } } - getProductionMultiplier(): number { - return this.getMultiplierHelper("productionMult"); - } + console.warn( + `ResearchTree.research() did not find the specified Research node for: ${name}`, + ); + } - getProductProductionMultiplier(): number { - return this.getMultiplierHelper("productProductionMult"); - } - - getSalesMultiplier(): number { - return this.getMultiplierHelper("salesMult"); - } - - getScientificResearchMultiplier(): number { - return this.getMultiplierHelper("sciResearchMult"); - } - - getStorageMultiplier(): number { - return this.getMultiplierHelper("storageMult"); - } - - // Helper function for all the multiplier getter fns - getMultiplierHelper(propName: string): number { - let res = 1; - if (this.root == null) { return res; } - - const queue: Node[] = []; - queue.push(this.root); - while (queue.length !== 0) { - const node: Node | undefined = queue.shift(); - - // If the Node has not been researched, there's no need to - // process it or its children - if (node == null || !node.researched) { continue; } - - const research: Research | null = ResearchMap[node.text]; - - // Safety checks - if (research == null) { - console.warn(`Invalid Research name in node: ${node.text}`); - continue; - } - - const mult: any = (research)[propName]; - if (mult == null) { - console.warn(`Invalid propName specified in ResearchTree.getMultiplierHelper: ${propName}`); - continue; - } - - res *= mult; - for (let i = 0; i < node.children.length; ++i) { - queue.push(node.children[i]); - } - } - - return res; - } - - - // Search for a Node with the given name ('text' property on the Node) - // Returns 'null' if it cannot be found - findNode(name: string): Node | null { - if (this.root == null) { return null; } - return this.root.findNode(name); - } - - // Marks a Node as researched - research(name: string): void { - if (this.root == null) { return; } - - const queue: Node[] = []; - queue.push(this.root); - while (queue.length !== 0) { - const node: Node | undefined = queue.shift(); - if (node == null) { continue; } - - if (node.text === name) { - node.researched = true; - this.researched[name] = true; - return; - } - - for (let i = 0; i < node.children.length; ++i) { - queue.push(node.children[i]); - } - } - - console.warn(`ResearchTree.research() did not find the specified Research node for: ${name}`); - } - - // Set the tree's Root Node - setRoot(root: Node): void { - this.root = root; - } + // Set the tree's Root Node + setRoot(root: Node): void { + this.root = root; + } } diff --git a/src/Corporation/Warehouse.ts b/src/Corporation/Warehouse.ts index 100573c05..6abbf92e4 100644 --- a/src/Corporation/Warehouse.ts +++ b/src/Corporation/Warehouse.ts @@ -1,115 +1,124 @@ -import { Material } from "./Material"; -import { ICorporation } from "./ICorporation"; -import { IIndustry } from "./IIndustry"; -import { MaterialSizes } from "./MaterialSizes"; -import { IMap } from "../types"; -import { numeralWrapper } from "../ui/numeralFormat"; -import { Generic_fromJSON, - Generic_toJSON, - Reviver } from "../../utils/JSONReviver"; -import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; +import { Material } from "./Material"; +import { ICorporation } from "./ICorporation"; +import { IIndustry } from "./IIndustry"; +import { MaterialSizes } from "./MaterialSizes"; +import { IMap } from "../types"; +import { numeralWrapper } from "../ui/numeralFormat"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; +import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; interface IConstructorParams { - corp?: ICorporation; - industry?: IIndustry; - loc?: string; - size?: number; + corp?: ICorporation; + industry?: IIndustry; + loc?: string; + size?: number; } export class Warehouse { + // Text that describes how the space in this Warehouse is being used + // Used to create a tooltip in the UI + breakdown = ""; - // Text that describes how the space in this Warehouse is being used - // Used to create a tooltip in the UI - breakdown = ""; + // Warehouse's level, which affects its maximum size + level = 1; - // Warehouse's level, which affects its maximum size - level = 1; + // City that this Warehouse is in + loc: string; - // City that this Warehouse is in - loc: string; + // Map of Materials held by this Warehouse + materials: IMap; - // Map of Materials held by this Warehouse - materials: IMap; + // Maximum amount warehouse can hold + size: number; - // Maximum amount warehouse can hold - size: number; + // Amount of space currently used by warehouse + sizeUsed = 0; - // Amount of space currently used by warehouse - sizeUsed = 0; + // Whether Smart Supply is enabled for this Industry (the Industry that this Warehouse is for) + smartSupplyEnabled = false; - // Whether Smart Supply is enabled for this Industry (the Industry that this Warehouse is for) - smartSupplyEnabled = false; + // Flag that indicates whether Smart Supply accounts for imports when calculating + // the amount fo purchase + smartSupplyConsiderExports = false; - // Flag that indicates whether Smart Supply accounts for imports when calculating - // the amount fo purchase - smartSupplyConsiderExports = false; + // Stores the amount of product to be produced. Used for Smart Supply unlock. + // The production tracked by smart supply is always based on the previous cycle, + // so it will always trail the "true" production by 1 cycle + smartSupplyStore = 0; - // Stores the amount of product to be produced. Used for Smart Supply unlock. - // The production tracked by smart supply is always based on the previous cycle, - // so it will always trail the "true" production by 1 cycle - smartSupplyStore = 0; + constructor(params: IConstructorParams = {}) { + this.loc = params.loc ? params.loc : ""; + this.size = params.size ? params.size : 0; - constructor(params: IConstructorParams = {}) { - this.loc = params.loc ? params.loc : ""; - this.size = params.size ? params.size : 0; + this.materials = { + Water: new Material({ name: "Water" }), + Energy: new Material({ name: "Energy" }), + Food: new Material({ name: "Food" }), + Plants: new Material({ name: "Plants" }), + Metal: new Material({ name: "Metal" }), + Hardware: new Material({ name: "Hardware" }), + Chemicals: new Material({ name: "Chemicals" }), + Drugs: new Material({ name: "Drugs" }), + Robots: new Material({ name: "Robots" }), + AICores: new Material({ name: "AI Cores" }), + RealEstate: new Material({ name: "Real Estate" }), + }; - this.materials = { - Water: new Material({name: "Water"}), - Energy: new Material({name: "Energy"}), - Food: new Material({name: "Food"}), - Plants: new Material({name: "Plants"}), - Metal: new Material({name: "Metal"}), - Hardware: new Material({name: "Hardware"}), - Chemicals: new Material({name: "Chemicals"}), - Drugs: new Material({name: "Drugs"}), - Robots: new Material({name: "Robots"}), - AICores: new Material({name: "AI Cores"}), - RealEstate: new Material({name: "Real Estate"}), - } - - if (params.corp && params.industry) { - this.updateSize(params.corp, params.industry); - } + if (params.corp && params.industry) { + this.updateSize(params.corp, params.industry); } + } - // Re-calculate how much space is being used by this Warehouse - updateMaterialSizeUsed(): void { - this.sizeUsed = 0; - this.breakdown = ""; - for (const matName in this.materials) { - const mat = this.materials[matName]; - if (MaterialSizes.hasOwnProperty(matName)) { - this.sizeUsed += (mat.qty * MaterialSizes[matName]); - if (mat.qty > 0) { - this.breakdown += (matName + ": " + numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0.0") + "
    "); - } - } - } - if (this.sizeUsed > this.size) { - console.warn("Warehouse size used greater than capacity, something went wrong"); + // Re-calculate how much space is being used by this Warehouse + updateMaterialSizeUsed(): void { + this.sizeUsed = 0; + this.breakdown = ""; + for (const matName in this.materials) { + const mat = this.materials[matName]; + if (MaterialSizes.hasOwnProperty(matName)) { + this.sizeUsed += mat.qty * MaterialSizes[matName]; + if (mat.qty > 0) { + this.breakdown += + matName + + ": " + + numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0.0") + + "
    "; } + } } + if (this.sizeUsed > this.size) { + console.warn( + "Warehouse size used greater than capacity, something went wrong", + ); + } + } - updateSize(corporation: ICorporation, industry: IIndustry): void { - try { - this.size = (this.level * 100) - * corporation.getStorageMultiplier() - * industry.getStorageMultiplier(); - } catch(e) { - exceptionAlert(e); - } + updateSize(corporation: ICorporation, industry: IIndustry): void { + try { + this.size = + this.level * + 100 * + corporation.getStorageMultiplier() * + industry.getStorageMultiplier(); + } catch (e) { + exceptionAlert(e); } + } - // Serialize the current object to a JSON save state. - toJSON(): any { - return Generic_toJSON("Warehouse", this); - } + // Serialize the current object to a JSON save state. + toJSON(): any { + return Generic_toJSON("Warehouse", this); + } - // Initiatizes a Warehouse object from a JSON save state. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Warehouse { - return Generic_fromJSON(Warehouse, value.data); - } + // Initiatizes a Warehouse object from a JSON save state. + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Warehouse { + return Generic_fromJSON(Warehouse, value.data); + } } Reviver.constructors.Warehouse = Warehouse; diff --git a/src/Corporation/data/BaseResearchTree.ts b/src/Corporation/data/BaseResearchTree.ts index b13ebd28b..8eee5d6dc 100644 --- a/src/Corporation/data/BaseResearchTree.ts +++ b/src/Corporation/data/BaseResearchTree.ts @@ -2,89 +2,88 @@ // i.e. all Industries have these types of Research available to unlock import { Research } from "../Research"; import { ResearchMap } from "../ResearchMap"; -import { ResearchTree, - Node } from "../ResearchTree"; +import { ResearchTree, Node } from "../ResearchTree"; function makeNode(name: string): Node { - const research: Research | null = ResearchMap[name]; - if (research == null) { - throw new Error(`Invalid research name: ${name}`); - } + const research: Research | null = ResearchMap[name]; + if (research == null) { + throw new Error(`Invalid research name: ${name}`); + } - return new Node({ text: research.name, cost: research.cost }); + return new Node({ text: research.name, cost: research.cost }); } // Creates the Nodes for the BaseResearchTree. // Return the Root Node function createBaseResearchTreeNodes(): Node { - const rootNode: Node = makeNode("Hi-Tech R&D Laboratory"); - const autoBrew: Node = makeNode("AutoBrew"); - const autoParty: Node = makeNode("AutoPartyManager"); - const autoDrugs: Node = makeNode("Automatic Drug Administration"); - const bulkPurchasing: Node = makeNode("Bulk Purchasing"); - const cph4: Node = makeNode("CPH4 Injections"); - const drones: Node = makeNode("Drones"); - const dronesAssembly: Node = makeNode("Drones - Assembly"); - const dronesTransport: Node = makeNode("Drones - Transport"); - const goJuice: Node = makeNode("Go-Juice"); - const hrRecruitment: Node = makeNode("HRBuddy-Recruitment"); - const hrTraining: Node = makeNode("HRBuddy-Training"); - const joywire: Node = makeNode("JoyWire"); - const marketta1: Node = makeNode("Market-TA.I"); - const marketta2: Node = makeNode("Market-TA.II"); - const overclock: Node = makeNode("Overclock"); - const scAssemblers: Node = makeNode("Self-Correcting Assemblers"); - const stimu: Node = makeNode("Sti.mu"); + const rootNode: Node = makeNode("Hi-Tech R&D Laboratory"); + const autoBrew: Node = makeNode("AutoBrew"); + const autoParty: Node = makeNode("AutoPartyManager"); + const autoDrugs: Node = makeNode("Automatic Drug Administration"); + const bulkPurchasing: Node = makeNode("Bulk Purchasing"); + const cph4: Node = makeNode("CPH4 Injections"); + const drones: Node = makeNode("Drones"); + const dronesAssembly: Node = makeNode("Drones - Assembly"); + const dronesTransport: Node = makeNode("Drones - Transport"); + const goJuice: Node = makeNode("Go-Juice"); + const hrRecruitment: Node = makeNode("HRBuddy-Recruitment"); + const hrTraining: Node = makeNode("HRBuddy-Training"); + const joywire: Node = makeNode("JoyWire"); + const marketta1: Node = makeNode("Market-TA.I"); + const marketta2: Node = makeNode("Market-TA.II"); + const overclock: Node = makeNode("Overclock"); + const scAssemblers: Node = makeNode("Self-Correcting Assemblers"); + const stimu: Node = makeNode("Sti.mu"); - autoDrugs.addChild(goJuice); - autoDrugs.addChild(cph4); + autoDrugs.addChild(goJuice); + autoDrugs.addChild(cph4); - drones.addChild(dronesAssembly); - drones.addChild(dronesTransport); + drones.addChild(dronesAssembly); + drones.addChild(dronesTransport); - hrRecruitment.addChild(hrTraining); + hrRecruitment.addChild(hrTraining); - marketta1.addChild(marketta2); + marketta1.addChild(marketta2); - overclock.addChild(stimu); + overclock.addChild(stimu); - rootNode.addChild(autoBrew); - rootNode.addChild(autoParty); - rootNode.addChild(autoDrugs); - rootNode.addChild(bulkPurchasing); - rootNode.addChild(drones); - rootNode.addChild(hrRecruitment); - rootNode.addChild(joywire); - rootNode.addChild(marketta1); - rootNode.addChild(overclock); - rootNode.addChild(scAssemblers); + rootNode.addChild(autoBrew); + rootNode.addChild(autoParty); + rootNode.addChild(autoDrugs); + rootNode.addChild(bulkPurchasing); + rootNode.addChild(drones); + rootNode.addChild(hrRecruitment); + rootNode.addChild(joywire); + rootNode.addChild(marketta1); + rootNode.addChild(overclock); + rootNode.addChild(scAssemblers); - return rootNode; + return rootNode; } export function getBaseResearchTreeCopy(): ResearchTree { - const baseResearchTree: ResearchTree = new ResearchTree(); - baseResearchTree.setRoot(createBaseResearchTreeNodes()); + const baseResearchTree: ResearchTree = new ResearchTree(); + baseResearchTree.setRoot(createBaseResearchTreeNodes()); - return baseResearchTree; + return baseResearchTree; } // Base Research Tree for Industry's that make products export function getProductIndustryResearchTreeCopy(): ResearchTree { - const researchTree: ResearchTree = new ResearchTree(); - const root = createBaseResearchTreeNodes(); + const researchTree: ResearchTree = new ResearchTree(); + const root = createBaseResearchTreeNodes(); - const upgradeFulcrum = makeNode("uPgrade: Fulcrum"); - const upgradeCapacity1 = makeNode("uPgrade: Capacity.I"); - const upgradeCapacity2 = makeNode("uPgrade: Capacity.II"); - const upgradeDashboard = makeNode("uPgrade: Dashboard"); + const upgradeFulcrum = makeNode("uPgrade: Fulcrum"); + const upgradeCapacity1 = makeNode("uPgrade: Capacity.I"); + const upgradeCapacity2 = makeNode("uPgrade: Capacity.II"); + const upgradeDashboard = makeNode("uPgrade: Dashboard"); - upgradeCapacity1.addChild(upgradeCapacity2); - upgradeFulcrum.addChild(upgradeCapacity1); - upgradeFulcrum.addChild(upgradeDashboard); - root.addChild(upgradeFulcrum); + upgradeCapacity1.addChild(upgradeCapacity2); + upgradeFulcrum.addChild(upgradeCapacity1); + upgradeFulcrum.addChild(upgradeDashboard); + root.addChild(upgradeFulcrum); - researchTree.setRoot(root); + researchTree.setRoot(root); - return researchTree; + return researchTree; } diff --git a/src/Corporation/data/Constants.ts b/src/Corporation/data/Constants.ts index 3b0692a9e..2c60b5c68 100644 --- a/src/Corporation/data/Constants.ts +++ b/src/Corporation/data/Constants.ts @@ -1,60 +1,74 @@ const CyclesPerMarketCycle = 50; -const AllCorporationStates = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"]; +const AllCorporationStates = [ + "START", + "PURCHASE", + "PRODUCTION", + "SALE", + "EXPORT", +]; export const CorporationConstants: { - INITIALSHARES: number; - SHARESPERPRICEUPDATE: number; - IssueNewSharesCooldown: number; - SellSharesCooldown: number; - CyclesPerMarketCycle: number; - CyclesPerIndustryStateCycle: number; - SecsPerMarketCycle: number; - Cities: string[]; - WarehouseInitialCost: number; - WarehouseInitialSize: number; - WarehouseUpgradeBaseCost: number; - OfficeInitialCost: number; - OfficeInitialSize: number; - OfficeUpgradeBaseCost: number; - BribeThreshold: number; - BribeToRepRatio: number; - ProductProductionCostRatio: number; - DividendMaxPercentage: number; - EmployeeSalaryMultiplier: number; - CyclesPerEmployeeRaise: number; - EmployeeRaiseAmount: number; - BaseMaxProducts: number; - AllCorporationStates: string[]; + INITIALSHARES: number; + SHARESPERPRICEUPDATE: number; + IssueNewSharesCooldown: number; + SellSharesCooldown: number; + CyclesPerMarketCycle: number; + CyclesPerIndustryStateCycle: number; + SecsPerMarketCycle: number; + Cities: string[]; + WarehouseInitialCost: number; + WarehouseInitialSize: number; + WarehouseUpgradeBaseCost: number; + OfficeInitialCost: number; + OfficeInitialSize: number; + OfficeUpgradeBaseCost: number; + BribeThreshold: number; + BribeToRepRatio: number; + ProductProductionCostRatio: number; + DividendMaxPercentage: number; + EmployeeSalaryMultiplier: number; + CyclesPerEmployeeRaise: number; + EmployeeRaiseAmount: number; + BaseMaxProducts: number; + AllCorporationStates: string[]; } = { - INITIALSHARES: 1e9, //Total number of shares you have at your company - SHARESPERPRICEUPDATE: 1e6, //When selling large number of shares, price is dynamically updated for every batch of this amount - IssueNewSharesCooldown: 216e3, // 12 Hour in terms of game cycles - SellSharesCooldown: 18e3, // 1 Hour in terms of game cycles + INITIALSHARES: 1e9, //Total number of shares you have at your company + SHARESPERPRICEUPDATE: 1e6, //When selling large number of shares, price is dynamically updated for every batch of this amount + IssueNewSharesCooldown: 216e3, // 12 Hour in terms of game cycles + SellSharesCooldown: 18e3, // 1 Hour in terms of game cycles - CyclesPerMarketCycle: CyclesPerMarketCycle, - CyclesPerIndustryStateCycle: CyclesPerMarketCycle / AllCorporationStates.length, - SecsPerMarketCycle: CyclesPerMarketCycle / 5, + CyclesPerMarketCycle: CyclesPerMarketCycle, + CyclesPerIndustryStateCycle: + CyclesPerMarketCycle / AllCorporationStates.length, + SecsPerMarketCycle: CyclesPerMarketCycle / 5, - Cities: ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"], + Cities: [ + "Aevum", + "Chongqing", + "Sector-12", + "New Tokyo", + "Ishima", + "Volhaven", + ], - WarehouseInitialCost: 5e9, //Initial purchase cost of warehouse - WarehouseInitialSize: 100, - WarehouseUpgradeBaseCost: 1e9, + WarehouseInitialCost: 5e9, //Initial purchase cost of warehouse + WarehouseInitialSize: 100, + WarehouseUpgradeBaseCost: 1e9, - OfficeInitialCost: 4e9, - OfficeInitialSize: 3, - OfficeUpgradeBaseCost: 1e9, + OfficeInitialCost: 4e9, + OfficeInitialSize: 3, + OfficeUpgradeBaseCost: 1e9, - BribeThreshold: 100e12, //Money needed to be able to bribe for faction rep - BribeToRepRatio: 1e9, //Bribe Value divided by this = rep gain + BribeThreshold: 100e12, //Money needed to be able to bribe for faction rep + BribeToRepRatio: 1e9, //Bribe Value divided by this = rep gain - ProductProductionCostRatio: 5, //Ratio of material cost of a product to its production cost + ProductProductionCostRatio: 5, //Ratio of material cost of a product to its production cost - DividendMaxPercentage: .5, + DividendMaxPercentage: 0.5, - EmployeeSalaryMultiplier: 3, // Employee stats multiplied by this to determine initial salary - CyclesPerEmployeeRaise: 400, // All employees get a raise every X market cycles - EmployeeRaiseAmount: 50, // Employee salary increases by this (additive) + EmployeeSalaryMultiplier: 3, // Employee stats multiplied by this to determine initial salary + CyclesPerEmployeeRaise: 400, // All employees get a raise every X market cycles + EmployeeRaiseAmount: 50, // Employee salary increases by this (additive) - BaseMaxProducts: 3, // Initial value for maximum number of products allowed - AllCorporationStates: AllCorporationStates, -}; \ No newline at end of file + BaseMaxProducts: 3, // Initial value for maximum number of products allowed + AllCorporationStates: AllCorporationStates, +}; diff --git a/src/Corporation/data/CorporationUnlockUpgrades.ts b/src/Corporation/data/CorporationUnlockUpgrades.ts index aae13fe5a..57355a5f1 100644 --- a/src/Corporation/data/CorporationUnlockUpgrades.ts +++ b/src/Corporation/data/CorporationUnlockUpgrades.ts @@ -7,33 +7,62 @@ export type CorporationUnlockUpgrade = [number, number, string, string]; // The data structure is an array with the following format: // [index in Corporation feature upgrades array, price, name, description] export const CorporationUnlockUpgrades: IMap = { - //Lets you export goods - "0": [0, 20e9, "Export", - "Develop infrastructure to export your materials to your other facilities. " + - "This allows you to move materials around between different divisions and cities."], + //Lets you export goods + "0": [ + 0, + 20e9, + "Export", + "Develop infrastructure to export your materials to your other facilities. " + + "This allows you to move materials around between different divisions and cities.", + ], - //Lets you buy exactly however many required materials you need for production - "1": [1, 25e9, "Smart Supply", "Use advanced AI to anticipate your supply needs. " + - "This allows you to purchase exactly however many materials you need for production."], + //Lets you buy exactly however many required materials you need for production + "1": [ + 1, + 25e9, + "Smart Supply", + "Use advanced AI to anticipate your supply needs. " + + "This allows you to purchase exactly however many materials you need for production.", + ], - //Displays each material/product's demand - "2": [2, 5e9, "Market Research - Demand", - "Mine and analyze market data to determine the demand of all resources. " + - "The demand attribute, which affects sales, will be displayed for every material and product."], + //Displays each material/product's demand + "2": [ + 2, + 5e9, + "Market Research - Demand", + "Mine and analyze market data to determine the demand of all resources. " + + "The demand attribute, which affects sales, will be displayed for every material and product.", + ], - //Display's each material/product's competition - "3": [3, 5e9, "Market Data - Competition", - "Mine and analyze market data to determine how much competition there is on the market " + - "for all resources. The competition attribute, which affects sales, will be displayed for " + - "every material and product."], - "4": [4, 10e9, "VeChain", - "Use AI and blockchain technology to identify where you can improve your supply chain systems. " + - "This upgrade will allow you to view a wide array of useful statistics about your " + - "Corporation."], - "5": [5, 500e9, "Shady Accounting", - "Utilize unscrupulous accounting practices and pay off government officials to save money " + - "on taxes. This reduces the dividend tax rate by 5%."], - "6": [6, 2e12, "Government Partnership", - "Help national governments further their agendas in exchange for lowered taxes. " + - "This reduces the dividend tax rate by 10%"], -} + //Display's each material/product's competition + "3": [ + 3, + 5e9, + "Market Data - Competition", + "Mine and analyze market data to determine how much competition there is on the market " + + "for all resources. The competition attribute, which affects sales, will be displayed for " + + "every material and product.", + ], + "4": [ + 4, + 10e9, + "VeChain", + "Use AI and blockchain technology to identify where you can improve your supply chain systems. " + + "This upgrade will allow you to view a wide array of useful statistics about your " + + "Corporation.", + ], + "5": [ + 5, + 500e9, + "Shady Accounting", + "Utilize unscrupulous accounting practices and pay off government officials to save money " + + "on taxes. This reduces the dividend tax rate by 5%.", + ], + "6": [ + 6, + 2e12, + "Government Partnership", + "Help national governments further their agendas in exchange for lowered taxes. " + + "This reduces the dividend tax rate by 10%", + ], +}; diff --git a/src/Corporation/data/CorporationUpgrades.ts b/src/Corporation/data/CorporationUpgrades.ts index 57c97331c..1b1f6ab68 100644 --- a/src/Corporation/data/CorporationUpgrades.ts +++ b/src/Corporation/data/CorporationUpgrades.ts @@ -1,67 +1,134 @@ import { IMap } from "../../types"; -export type CorporationUpgrade = [number, number, number, number, string, string]; +export type CorporationUpgrade = [ + number, + number, + number, + number, + string, + string, +]; // Corporation Upgrades // Upgrades for entire corporation, levelable upgrades // The data structure is an array with the following format // [index in Corporation upgrades array, base price, price mult, benefit mult (additive), name, desc] export const CorporationUpgrades: IMap = { - //Smart factories, increases production - "0": [0, 2e9, 1.06, 0.03, - "Smart Factories", "Advanced AI automatically optimizes the operation and productivity " + - "of factories. Each level of this upgrade increases your global production by 3% (additive)."], + //Smart factories, increases production + "0": [ + 0, + 2e9, + 1.06, + 0.03, + "Smart Factories", + "Advanced AI automatically optimizes the operation and productivity " + + "of factories. Each level of this upgrade increases your global production by 3% (additive).", + ], - //Smart warehouses, increases storage size - "1": [1, 2e9, 1.06, .1, - "Smart Storage", "Advanced AI automatically optimizes your warehouse storage methods. " + - "Each level of this upgrade increases your global warehouse storage size by 10% (additive)."], + //Smart warehouses, increases storage size + "1": [ + 1, + 2e9, + 1.06, + 0.1, + "Smart Storage", + "Advanced AI automatically optimizes your warehouse storage methods. " + + "Each level of this upgrade increases your global warehouse storage size by 10% (additive).", + ], - //Advertise through dreams, passive popularity/ awareness gain - "2": [2, 4e9, 1.1, .001, - "DreamSense", "Use DreamSense LCC Technologies to advertise your corporation " + - "to consumers through their dreams. Each level of this upgrade provides a passive " + - "increase in awareness of all of your companies (divisions) by 0.004 / market cycle," + - "and in popularity by 0.001 / market cycle. A market cycle is approximately " + - "15 seconds."], + //Advertise through dreams, passive popularity/ awareness gain + "2": [ + 2, + 4e9, + 1.1, + 0.001, + "DreamSense", + "Use DreamSense LCC Technologies to advertise your corporation " + + "to consumers through their dreams. Each level of this upgrade provides a passive " + + "increase in awareness of all of your companies (divisions) by 0.004 / market cycle," + + "and in popularity by 0.001 / market cycle. A market cycle is approximately " + + "15 seconds.", + ], - //Makes advertising more effective - "3": [3, 4e9, 1.12, 0.005, - "Wilson Analytics", "Purchase data and analysis from Wilson, a marketing research " + - "firm. Each level of this upgrades increases the effectiveness of your " + - "advertising by 0.5% (additive)."], + //Makes advertising more effective + "3": [ + 3, + 4e9, + 1.12, + 0.005, + "Wilson Analytics", + "Purchase data and analysis from Wilson, a marketing research " + + "firm. Each level of this upgrades increases the effectiveness of your " + + "advertising by 0.5% (additive).", + ], - //Augmentation for employees, increases cre - "4": [4, 1e9, 1.06, 0.1, - "Nuoptimal Nootropic Injector Implants", "Purchase the Nuoptimal Nootropic " + - "Injector augmentation for your employees. Each level of this upgrade " + - "globally increases the creativity of your employees by 10% (additive)."], + //Augmentation for employees, increases cre + "4": [ + 4, + 1e9, + 1.06, + 0.1, + "Nuoptimal Nootropic Injector Implants", + "Purchase the Nuoptimal Nootropic " + + "Injector augmentation for your employees. Each level of this upgrade " + + "globally increases the creativity of your employees by 10% (additive).", + ], - //Augmentation for employees, increases cha - "5": [5, 1e9, 1.06, 0.1, - "Speech Processor Implants", "Purchase the Speech Processor augmentation for your employees. " + - "Each level of this upgrade globally increases the charisma of your employees by 10% (additive)."], + //Augmentation for employees, increases cha + "5": [ + 5, + 1e9, + 1.06, + 0.1, + "Speech Processor Implants", + "Purchase the Speech Processor augmentation for your employees. " + + "Each level of this upgrade globally increases the charisma of your employees by 10% (additive).", + ], - //Augmentation for employees, increases int - "6": [6, 1e9, 1.06, 0.1, - "Neural Accelerators", "Purchase the Neural Accelerator augmentation for your employees. " + - "Each level of this upgrade globally increases the intelligence of your employees " + - "by 10% (additive)."], + //Augmentation for employees, increases int + "6": [ + 6, + 1e9, + 1.06, + 0.1, + "Neural Accelerators", + "Purchase the Neural Accelerator augmentation for your employees. " + + "Each level of this upgrade globally increases the intelligence of your employees " + + "by 10% (additive).", + ], - //Augmentation for employees, increases eff - "7": [7, 1e9, 1.06, 0.1, - "FocusWires", "Purchase the FocusWire augmentation for your employees. Each level " + - "of this upgrade globally increases the efficiency of your employees by 10% (additive)."], + //Augmentation for employees, increases eff + "7": [ + 7, + 1e9, + 1.06, + 0.1, + "FocusWires", + "Purchase the FocusWire augmentation for your employees. Each level " + + "of this upgrade globally increases the efficiency of your employees by 10% (additive).", + ], - //Improves sales of materials/products - "8": [8, 1e9, 1.07, 0.01, - "ABC SalesBots", "Always Be Closing. Purchase these robotic salesmen to increase the amount of " + - "materials and products you sell. Each level of this upgrade globally increases your sales " + - "by 1% (additive)."], + //Improves sales of materials/products + "8": [ + 8, + 1e9, + 1.07, + 0.01, + "ABC SalesBots", + "Always Be Closing. Purchase these robotic salesmen to increase the amount of " + + "materials and products you sell. Each level of this upgrade globally increases your sales " + + "by 1% (additive).", + ], - //Improves scientific research rate - "9": [9, 5e9, 1.07, 0.05, - "Project Insight", "Purchase 'Project Insight', a R&D service provided by the secretive " + - "Fulcrum Technologies. Each level of this upgrade globally increases the amount of " + - "Scientific Research you produce by 5% (additive)."], -} + //Improves scientific research rate + "9": [ + 9, + 5e9, + 1.07, + 0.05, + "Project Insight", + "Purchase 'Project Insight', a R&D service provided by the secretive " + + "Fulcrum Technologies. Each level of this upgrade globally increases the amount of " + + "Scientific Research you produce by 5% (additive).", + ], +}; diff --git a/src/Corporation/data/ResearchMetadata.ts b/src/Corporation/data/ResearchMetadata.ts index 921989bb8..cd08e2ea6 100644 --- a/src/Corporation/data/ResearchMetadata.ts +++ b/src/Corporation/data/ResearchMetadata.ts @@ -1,179 +1,201 @@ import { IConstructorParams } from "../Research"; export const researchMetadata: IConstructorParams[] = [ - { - name: "AutoBrew", - cost: 12e3, - desc: "Automatically keep your employees fully caffeinated with " + - "coffee injections. This research will keep the energy of all " + - "employees at its maximum possible value, for no cost. " + - "This will also disable the Coffee upgrade.", - }, - { - name: "AutoPartyManager", - cost: 15e3, - desc: "Automatically analyzes your employees' happiness and morale " + - "and boosts them whenever it detects a decrease. This research will " + - "keep the morale and happiness of all employees at their maximum possible " + - "values, for no cost. " + - "This will also disable the 'Throw Party' feature.", - }, - { - name: "Automatic Drug Administration", - cost: 10e3, - desc: "Research how to automatically administer performance-enhacing drugs to all of " + - "your employees. This unlocks Drug-related Research.", - }, - { - name: "Bulk Purchasing", - cost: 5e3, - desc: "Research the art of buying materials in bulk. This allows you to purchase " + - "any amount of a material instantly.", - }, - { - name: "CPH4 Injections", - cost: 25e3, - desc: "Develop an advanced and harmless synthetic drug that is administered to " + - "employees to increase all of their stats, except experience, by 10%.", - employeeCreMult: 1.1, - employeeChaMult: 1.1, - employeeEffMult: 1.1, - employeeIntMult: 1.1, - }, - { - name: "Drones", - cost: 5e3, - desc: "Acquire the knowledge needed to create advanced drones. This research does nothing " + - "by itself, but unlocks other Drone-related research.", - }, - { - name: "Drones - Assembly", - cost: 25e3, - desc: "Manufacture and use Assembly Drones to improve the efficiency of " + - "your production lines. This increases all production by 20%.", - productionMult: 1.2, - }, - { - name: "Drones - Transport", - cost: 30e3, - desc: "Manufacture and use intelligent Transport Drones to optimize " + - "your warehouses. This increases the storage space of all warehouses " + - "by 50%.", - storageMult: 1.5, - }, - { - name: "Go-Juice", - cost: 25e3, - desc: "Provide employees with Go-Juice, a coffee-derivative that further enhances " + - "the brain's dopamine production. This increases the maximum energy of all " + - "employees by 10.", - }, - { - name: "Hi-Tech R&D Laboratory", - cost: 5e3, - desc: "Construct a cutting edge facility dedicated to advanced research and " + - "and development. This allows you to spend Scientific Research " + - "on powerful upgrades. It also globally increases Scientific Research " + - "production by 10%.", - sciResearchMult: 1.1, - }, - { - name: "HRBuddy-Recruitment", - cost: 15e3, - desc: "Use automated software to handle the hiring of employees. With this " + - "research, each office will automatically hire one employee per " + - "market cycle if there is available space.", - - }, - { - name: "HRBuddy-Training", - cost: 20e3, - desc: "Use automated software to handle the training of employees. With this " + - "research, each employee hired with HRBuddy-Recruitment will automatically " + - "be assigned to 'Training', rather than being unassigned.", - }, - { - name: "JoyWire", - cost: 20e3, - desc: "A brain implant which is installed in employees, increasing their " + - "maximum happiness by 10.", - }, - { - name: "Market-TA.I", - cost: 20e3, - desc: "Develop advanced AI software that uses technical analysis to " + - "help you understand and exploit the market. This research " + - "allows you to know what price to sell your Materials/Products " + - "at in order to avoid losing sales due to having too high of a mark-up. " + - "It also lets you automatically use that sale price.", - }, - { - name: "Market-TA.II", - cost: 50e3, - desc: "Develop double-advanced AI software that uses technical analysis to " + - "help you understand and exploit the market. This research " + - "allows you to know how many sales of a Material/Product you lose or gain " + - "from having too high or too low or a sale price. It also lets you automatically " + - "set the sale price of your Materials/Products at the optimal price such that " + - "the amount sold matches the amount produced.", - }, - { - name: "Overclock", - cost: 15e3, - desc: "Equip employees with a headset that uses transcranial direct current " + - "stimulation (tDCS) to increase the speed of their neurotransmitters. " + - "This research increases the intelligence and efficiency of all " + - "employees by 25%.", - employeeEffMult: 1.25, - employeeIntMult: 1.25, - }, - { - name: "Self-Correcting Assemblers", - cost: 25e3, - desc: "Create assemblers that can be used for universal production. " + - "These assemblers use deep learning to improve their efficiency " + - "at their tasks. This research increases all production by 10%", - productionMult: 1.1, - }, - { - name: "Sti.mu", - cost: 30e3, - desc: "Upgrade the tDCS headset to stimulate regions of the brain that " + - "control confidence and enthusiasm. This research increases the max " + - "morale of all employees by 10.", - }, - { - name: "sudo.Assist", - cost: 15e3, - desc: "Develop a virtual assistant AI to handle and manage administrative " + - "issues for your corporation.", - }, - { - name: "uPgrade: Capacity.I", - cost: 20e3, - desc: "Expand the industry's capacity for designing and manufacturing its " + - "various products. This increases the industry's maximum number of products " + - "by 1 (from 3 to 4).", - }, - { - name: "uPgrade: Capacity.II", - cost: 30e3, - desc: "Expand the industry's capacity for designing and manufacturing its " + - "various products. This increases the industry's maximum number of products " + - "by 1 (from 4 to 5).", - }, - { - name: "uPgrade: Dashboard", - cost: 5e3, - desc: "Improve the software used to manage the industry's production line " + - "for its various products. This allows you to manage the production and " + - "sale of a product before it's finished being designed.", - }, - { - name: "uPgrade: Fulcrum", - cost: 10e3, - desc: "Streamline the manufacturing of this industry's various products. " + - "This research increases the production of your products by 5%", - productProductionMult: 1.05, - }, + { + name: "AutoBrew", + cost: 12e3, + desc: + "Automatically keep your employees fully caffeinated with " + + "coffee injections. This research will keep the energy of all " + + "employees at its maximum possible value, for no cost. " + + "This will also disable the Coffee upgrade.", + }, + { + name: "AutoPartyManager", + cost: 15e3, + desc: + "Automatically analyzes your employees' happiness and morale " + + "and boosts them whenever it detects a decrease. This research will " + + "keep the morale and happiness of all employees at their maximum possible " + + "values, for no cost. " + + "This will also disable the 'Throw Party' feature.", + }, + { + name: "Automatic Drug Administration", + cost: 10e3, + desc: + "Research how to automatically administer performance-enhacing drugs to all of " + + "your employees. This unlocks Drug-related Research.", + }, + { + name: "Bulk Purchasing", + cost: 5e3, + desc: + "Research the art of buying materials in bulk. This allows you to purchase " + + "any amount of a material instantly.", + }, + { + name: "CPH4 Injections", + cost: 25e3, + desc: + "Develop an advanced and harmless synthetic drug that is administered to " + + "employees to increase all of their stats, except experience, by 10%.", + employeeCreMult: 1.1, + employeeChaMult: 1.1, + employeeEffMult: 1.1, + employeeIntMult: 1.1, + }, + { + name: "Drones", + cost: 5e3, + desc: + "Acquire the knowledge needed to create advanced drones. This research does nothing " + + "by itself, but unlocks other Drone-related research.", + }, + { + name: "Drones - Assembly", + cost: 25e3, + desc: + "Manufacture and use Assembly Drones to improve the efficiency of " + + "your production lines. This increases all production by 20%.", + productionMult: 1.2, + }, + { + name: "Drones - Transport", + cost: 30e3, + desc: + "Manufacture and use intelligent Transport Drones to optimize " + + "your warehouses. This increases the storage space of all warehouses " + + "by 50%.", + storageMult: 1.5, + }, + { + name: "Go-Juice", + cost: 25e3, + desc: + "Provide employees with Go-Juice, a coffee-derivative that further enhances " + + "the brain's dopamine production. This increases the maximum energy of all " + + "employees by 10.", + }, + { + name: "Hi-Tech R&D Laboratory", + cost: 5e3, + desc: + "Construct a cutting edge facility dedicated to advanced research and " + + "and development. This allows you to spend Scientific Research " + + "on powerful upgrades. It also globally increases Scientific Research " + + "production by 10%.", + sciResearchMult: 1.1, + }, + { + name: "HRBuddy-Recruitment", + cost: 15e3, + desc: + "Use automated software to handle the hiring of employees. With this " + + "research, each office will automatically hire one employee per " + + "market cycle if there is available space.", + }, + { + name: "HRBuddy-Training", + cost: 20e3, + desc: + "Use automated software to handle the training of employees. With this " + + "research, each employee hired with HRBuddy-Recruitment will automatically " + + "be assigned to 'Training', rather than being unassigned.", + }, + { + name: "JoyWire", + cost: 20e3, + desc: + "A brain implant which is installed in employees, increasing their " + + "maximum happiness by 10.", + }, + { + name: "Market-TA.I", + cost: 20e3, + desc: + "Develop advanced AI software that uses technical analysis to " + + "help you understand and exploit the market. This research " + + "allows you to know what price to sell your Materials/Products " + + "at in order to avoid losing sales due to having too high of a mark-up. " + + "It also lets you automatically use that sale price.", + }, + { + name: "Market-TA.II", + cost: 50e3, + desc: + "Develop double-advanced AI software that uses technical analysis to " + + "help you understand and exploit the market. This research " + + "allows you to know how many sales of a Material/Product you lose or gain " + + "from having too high or too low or a sale price. It also lets you automatically " + + "set the sale price of your Materials/Products at the optimal price such that " + + "the amount sold matches the amount produced.", + }, + { + name: "Overclock", + cost: 15e3, + desc: + "Equip employees with a headset that uses transcranial direct current " + + "stimulation (tDCS) to increase the speed of their neurotransmitters. " + + "This research increases the intelligence and efficiency of all " + + "employees by 25%.", + employeeEffMult: 1.25, + employeeIntMult: 1.25, + }, + { + name: "Self-Correcting Assemblers", + cost: 25e3, + desc: + "Create assemblers that can be used for universal production. " + + "These assemblers use deep learning to improve their efficiency " + + "at their tasks. This research increases all production by 10%", + productionMult: 1.1, + }, + { + name: "Sti.mu", + cost: 30e3, + desc: + "Upgrade the tDCS headset to stimulate regions of the brain that " + + "control confidence and enthusiasm. This research increases the max " + + "morale of all employees by 10.", + }, + { + name: "sudo.Assist", + cost: 15e3, + desc: + "Develop a virtual assistant AI to handle and manage administrative " + + "issues for your corporation.", + }, + { + name: "uPgrade: Capacity.I", + cost: 20e3, + desc: + "Expand the industry's capacity for designing and manufacturing its " + + "various products. This increases the industry's maximum number of products " + + "by 1 (from 3 to 4).", + }, + { + name: "uPgrade: Capacity.II", + cost: 30e3, + desc: + "Expand the industry's capacity for designing and manufacturing its " + + "various products. This increases the industry's maximum number of products " + + "by 1 (from 4 to 5).", + }, + { + name: "uPgrade: Dashboard", + cost: 5e3, + desc: + "Improve the software used to manage the industry's production line " + + "for its various products. This allows you to manage the production and " + + "sale of a product before it's finished being designed.", + }, + { + name: "uPgrade: Fulcrum", + cost: 10e3, + desc: + "Streamline the manufacturing of this industry's various products. " + + "This research increases the production of your products by 5%", + productProductionMult: 1.05, + }, ]; diff --git a/src/Corporation/ui/BribeFactionPopup.tsx b/src/Corporation/ui/BribeFactionPopup.tsx index 3ded57c1c..bae40c0d7 100644 --- a/src/Corporation/ui/BribeFactionPopup.tsx +++ b/src/Corporation/ui/BribeFactionPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { Factions } from "../../Faction/Factions"; import { CorporationConstants } from "../data/Constants"; @@ -8,81 +8,122 @@ import { dialogBoxCreate } from "../../../utils/DialogBox"; import { ICorporation } from "../ICorporation"; interface IProps { - popupId: string; - corp: ICorporation; - player: IPlayer; + popupId: string; + corp: ICorporation; + player: IPlayer; } export function BribeFactionPopup(props: IProps): React.ReactElement { - const [money, setMoney] = useState(0); - const [stock, setStock] = useState(0); - const [selectedFaction, setSelectedFaction] = useState(props.player.factions.length > 0 ? props.player.factions[0] : ""); + const [money, setMoney] = useState(0); + const [stock, setStock] = useState(0); + const [selectedFaction, setSelectedFaction] = useState( + props.player.factions.length > 0 ? props.player.factions[0] : "", + ); - function onMoneyChange(event: React.ChangeEvent): void { - setMoney(parseFloat(event.target.value)); + function onMoneyChange(event: React.ChangeEvent): void { + setMoney(parseFloat(event.target.value)); + } + + function onStockChange(event: React.ChangeEvent): void { + setStock(parseFloat(event.target.value)); + } + + function changeFaction(event: React.ChangeEvent): void { + setSelectedFaction(event.target.value); + } + + function repGain(money: number, stock: number): number { + return ( + (money + stock * props.corp.sharePrice) / + CorporationConstants.BribeToRepRatio + ); + } + + function getRepText(money: number, stock: number): string { + if (money === 0 && stock === 0) return ""; + if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) { + return "ERROR: Invalid value(s) entered"; + } else if (props.corp.funds.lt(money)) { + return "ERROR: You do not have this much money to bribe with"; + } else if (stock > props.corp.numShares) { + return "ERROR: You do not have this many shares to bribe with"; + } else { + return ( + "You will gain " + + numeralWrapper.formatReputation(repGain(money, stock)) + + " reputation with " + + selectedFaction + + " with this bribe" + ); } + } - function onStockChange(event: React.ChangeEvent): void { - setStock(parseFloat(event.target.value)); + function bribe(money: number, stock: number): void { + const fac = Factions[selectedFaction]; + if (fac == null) { + dialogBoxCreate("ERROR: You must select a faction to bribe"); } - - function changeFaction(event: React.ChangeEvent): void { - setSelectedFaction(event.target.value); + if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) { + } else if (props.corp.funds.lt(money)) { + } else if (stock > props.corp.numShares) { + } else { + const rep = repGain(money, stock); + dialogBoxCreate( + "You gained " + + numeralWrapper.formatReputation(rep) + + " reputation with " + + fac.name + + " by bribing them.", + ); + fac.playerReputation += rep; + props.corp.funds = props.corp.funds.minus(money); + props.corp.numShares -= stock; + removePopup(props.popupId); } + } - function repGain(money: number, stock: number): number { - return (money + (stock * props.corp.sharePrice)) / CorporationConstants.BribeToRepRatio; - } - - function getRepText(money: number, stock: number): string { - if(money === 0 && stock === 0) return ""; - if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) { - return "ERROR: Invalid value(s) entered"; - } else if (props.corp.funds.lt(money)) { - return "ERROR: You do not have this much money to bribe with"; - } else if (stock > props.corp.numShares) { - return "ERROR: You do not have this many shares to bribe with"; - } else { - return "You will gain " + numeralWrapper.formatReputation(repGain(money, stock)) + - " reputation with " + - selectedFaction + - " with this bribe"; - } - } - - function bribe(money: number, stock: number): void { - const fac = Factions[selectedFaction]; - if (fac == null) { - dialogBoxCreate("ERROR: You must select a faction to bribe"); - } - if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) { - } else if (props.corp.funds.lt(money)) { - } else if (stock > props.corp.numShares) { - } else { - const rep = repGain(money, stock); - dialogBoxCreate("You gained " + numeralWrapper.formatReputation(rep) + - " reputation with " + fac.name + " by bribing them."); - fac.playerReputation += rep; - props.corp.funds = props.corp.funds.minus(money); - props.corp.numShares -= stock; - removePopup(props.popupId); - } - } - - return (<> -

    You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation.

    - -

    {getRepText(money ? money : 0, stock ? stock : 0)}

    - - - - ); + return ( + <> +

    + You can use Corporation funds or stock shares to bribe Faction Leaders + in exchange for faction reputation. +

    + +

    {getRepText(money ? money : 0, stock ? stock : 0)}

    + + + + + ); } diff --git a/src/Corporation/ui/BuybackSharesPopup.tsx b/src/Corporation/ui/BuybackSharesPopup.tsx index eda57a330..2d0c371f8 100644 --- a/src/Corporation/ui/BuybackSharesPopup.tsx +++ b/src/Corporation/ui/BuybackSharesPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { removePopup } from "../../ui/React/createPopup"; import { numeralWrapper } from "../../ui/numeralFormat"; @@ -6,82 +6,122 @@ import { dialogBoxCreate } from "../../../utils/DialogBox"; import { ICorporation } from "../ICorporation"; interface IProps { - player: IPlayer; - popupId: string; - corp: ICorporation; + player: IPlayer; + popupId: string; + corp: ICorporation; } // Create a popup that lets the player buyback shares // This is created when the player clicks the "Buyback Shares" button in the overview panel export function BuybackSharesPopup(props: IProps): React.ReactElement { - const [shares, setShares] = useState(null); + const [shares, setShares] = useState(null); - function changeShares(event: React.ChangeEvent): void { - if(event.target.value === "") setShares(null); - else setShares(Math.round(parseFloat(event.target.value))); - } + function changeShares(event: React.ChangeEvent): void { + if (event.target.value === "") setShares(null); + else setShares(Math.round(parseFloat(event.target.value))); + } - const currentStockPrice = props.corp.sharePrice; - const buybackPrice = currentStockPrice * 1.1; + const currentStockPrice = props.corp.sharePrice; + const buybackPrice = currentStockPrice * 1.1; - function buy(): void { - if(shares === null) return; - const tempStockPrice = props.corp.sharePrice; - const buybackPrice = tempStockPrice * 1.1; - if (isNaN(shares) || shares <= 0) { - dialogBoxCreate("ERROR: Invalid value for number of shares"); - } else if (shares > props.corp.issuedShares) { - dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back"); - } else if (shares * buybackPrice > props.player.money) { - dialogBoxCreate("ERROR: You do not have enough money to purchase this many shares (you need " + - numeralWrapper.format(shares * buybackPrice, "$0.000a") + ")"); + function buy(): void { + if (shares === null) return; + const tempStockPrice = props.corp.sharePrice; + const buybackPrice = tempStockPrice * 1.1; + if (isNaN(shares) || shares <= 0) { + dialogBoxCreate("ERROR: Invalid value for number of shares"); + } else if (shares > props.corp.issuedShares) { + dialogBoxCreate( + "ERROR: There are not this many oustanding shares to buy back", + ); + } else if (shares * buybackPrice > props.player.money) { + dialogBoxCreate( + "ERROR: You do not have enough money to purchase this many shares (you need " + + numeralWrapper.format(shares * buybackPrice, "$0.000a") + + ")", + ); + } else { + props.corp.numShares += shares; + if (isNaN(props.corp.issuedShares)) { + console.warn( + "Corporation issuedShares is NaN: " + props.corp.issuedShares, + ); + console.warn("Converting to number now"); + const res = props.corp.issuedShares; + if (isNaN(res)) { + props.corp.issuedShares = 0; } else { - props.corp.numShares += shares; - if (isNaN(props.corp.issuedShares)) { - console.warn("Corporation issuedShares is NaN: " + props.corp.issuedShares); - console.warn("Converting to number now"); - const res = props.corp.issuedShares; - if (isNaN(res)) { - props.corp.issuedShares = 0; - } else { - props.corp.issuedShares = res; - } - } - props.corp.issuedShares -= shares; - props.player.loseMoney(shares * buybackPrice); - removePopup(props.popupId); - props.corp.rerender(props.player); + props.corp.issuedShares = res; } + } + props.corp.issuedShares -= shares; + props.player.loseMoney(shares * buybackPrice); + removePopup(props.popupId); + props.corp.rerender(props.player); } + } - function CostIndicator(): React.ReactElement { - if(shares === null) return (<>); - if (isNaN(shares) || shares <= 0) { - return (<>ERROR: Invalid value entered for number of shares to buyback); - } else if (shares > props.corp.issuedShares) { - return (<>There are not this many shares available to buy back. - There are only {numeralWrapper.formatBigNumber(props.corp.issuedShares)} outstanding shares.); - } else { - return (<>Purchase {shares} shares for a total of {numeralWrapper.formatMoney(shares * buybackPrice)}); - } + function CostIndicator(): React.ReactElement { + if (shares === null) return <>; + if (isNaN(shares) || shares <= 0) { + return <>ERROR: Invalid value entered for number of shares to buyback; + } else if (shares > props.corp.issuedShares) { + return ( + <> + There are not this many shares available to buy back. There are only{" "} + {numeralWrapper.formatBigNumber(props.corp.issuedShares)} outstanding + shares. + + ); + } else { + return ( + <> + Purchase {shares} shares for a total of{" "} + {numeralWrapper.formatMoney(shares * buybackPrice)} + + ); } + } - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) buy(); - } + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) buy(); + } - return (<> -

    -Enter the number of outstanding shares you would like to buy back. -These shares must be bought at a 10% premium. However, -repurchasing shares from the market tends to lead to an increase in stock price.

    -To purchase these shares, you must use your own money (NOT your Corporation's funds).

    -The current buyback price of your company's stock is {numeralWrapper.formatMoney(buybackPrice)}. -Your company currently has {numeralWrapper.formatBigNumber(props.corp.issuedShares)} outstanding stock shares. -

    - + return ( + <> +

    + Enter the number of outstanding shares you would like to buy back. These + shares must be bought at a 10% premium. However, repurchasing shares + from the market tends to lead to an increase in stock price.
    - - - ); -} \ No newline at end of file +
    + To purchase these shares, you must use your own money (NOT your + Corporation's funds). +
    +
    + The current buyback price of your company's stock is{" "} + {numeralWrapper.formatMoney(buybackPrice)}. Your company currently has{" "} + {numeralWrapper.formatBigNumber(props.corp.issuedShares)} outstanding + stock shares. +

    + +
    + + + + ); +} diff --git a/src/Corporation/ui/CityTab.tsx b/src/Corporation/ui/CityTab.tsx index 8e6759417..4fb9ecac0 100644 --- a/src/Corporation/ui/CityTab.tsx +++ b/src/Corporation/ui/CityTab.tsx @@ -1,20 +1,20 @@ import React from "react"; interface IProps { - onClick: () => void; - name: string; - current: boolean; + onClick: () => void; + name: string; + current: boolean; } export function CityTab(props: IProps): React.ReactElement { - let className = "cmpy-mgmt-city-tab"; - if (props.current) { - className += " current"; - } + let className = "cmpy-mgmt-city-tab"; + if (props.current) { + className += " current"; + } - return ( - - ) -} \ No newline at end of file + return ( + + ); +} diff --git a/src/Corporation/ui/CityTabs.tsx b/src/Corporation/ui/CityTabs.tsx index 17cc606f1..94f77f607 100644 --- a/src/Corporation/ui/CityTabs.tsx +++ b/src/Corporation/ui/CityTabs.tsx @@ -8,37 +8,43 @@ import { ICorporation } from "../ICorporation"; import { CorporationRouting } from "./Routing"; interface IProps { - routing: CorporationRouting; - onClicks: {[key: string]: () => void}; - city: string; // currentCity - cityStateSetter: (city: string) => void; - corp: ICorporation; + routing: CorporationRouting; + onClicks: { [key: string]: () => void }; + city: string; // currentCity + cityStateSetter: (city: string) => void; + corp: ICorporation; } export function CityTabs(props: IProps): React.ReactElement { - const division = props.routing.currentDivision; + const division = props.routing.currentDivision; - function openExpandNewCityModal(): void { - if(division === null) return; - const popupId = "cmpy-mgmt-expand-city-popup"; - createPopup(popupId, ExpandNewCityPopup, { - popupId: popupId, - corp: props.corp, - division: division, - cityStateSetter: props.cityStateSetter, - }); - } + function openExpandNewCityModal(): void { + if (division === null) return; + const popupId = "cmpy-mgmt-expand-city-popup"; + createPopup(popupId, ExpandNewCityPopup, { + popupId: popupId, + corp: props.corp, + division: division, + cityStateSetter: props.cityStateSetter, + }); + } - return <> - { - Object.keys(props.onClicks).map((cityName: string) => , - ) - } + return ( + <> + {Object.keys(props.onClicks).map((cityName: string) => ( - ; -} \ No newline at end of file + ))} + + + ); +} diff --git a/src/Corporation/ui/DiscontinueProductPopup.tsx b/src/Corporation/ui/DiscontinueProductPopup.tsx index 41c803f01..aada07a1e 100644 --- a/src/Corporation/ui/DiscontinueProductPopup.tsx +++ b/src/Corporation/ui/DiscontinueProductPopup.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; import { removePopup } from "../../ui/React/createPopup"; import { ICorporation } from "../ICorporation"; import { Product } from "../Product"; @@ -6,27 +6,31 @@ import { IIndustry } from "../IIndustry"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - product: Product; - industry: IIndustry; - corp: ICorporation; - popupId: string; - player: IPlayer; + product: Product; + industry: IIndustry; + corp: ICorporation; + popupId: string; + player: IPlayer; } // Create a popup that lets the player discontinue a product export function DiscontinueProductPopup(props: IProps): React.ReactElement { - function discontinue(): void { - props.industry.discontinueProduct(props.product); - removePopup(props.popupId); - props.corp.rerender(props.player); - } + function discontinue(): void { + props.industry.discontinueProduct(props.product); + removePopup(props.popupId); + props.corp.rerender(props.player); + } - return (<> -

    -Are you sure you want to do this? Discontinuing a product -removes it completely and permanently. You will no longer -produce this product and all of its existing stock will be -removed and left unsold

    - - ); -} \ No newline at end of file + return ( + <> +

    + Are you sure you want to do this? Discontinuing a product removes it + completely and permanently. You will no longer produce this product and + all of its existing stock will be removed and left unsold +

    + + + ); +} diff --git a/src/Corporation/ui/ExpandNewCityPopup.tsx b/src/Corporation/ui/ExpandNewCityPopup.tsx index f40807fcc..13a574870 100644 --- a/src/Corporation/ui/ExpandNewCityPopup.tsx +++ b/src/Corporation/ui/ExpandNewCityPopup.tsx @@ -9,42 +9,55 @@ import { ICorporation } from "../ICorporation"; import { NewCity } from "../Actions"; interface IProps { - popupId: string; - corp: ICorporation; - division: IIndustry; - cityStateSetter: (city: string) => void; + popupId: string; + corp: ICorporation; + division: IIndustry; + cityStateSetter: (city: string) => void; } export function ExpandNewCityPopup(props: IProps): React.ReactElement { - const dropdown = useRef(null); + const dropdown = useRef(null); - function expand(): void { - if(dropdown.current === null) return; - try { - NewCity(props.corp, props.division, dropdown.current.value); - } catch(err) { - dialogBoxCreate(err+''); - return; - } - - dialogBoxCreate(`Opened a new office in ${dropdown.current.value}!`); - - props.cityStateSetter(dropdown.current.value); - removePopup(props.popupId); + function expand(): void { + if (dropdown.current === null) return; + try { + NewCity(props.corp, props.division, dropdown.current.value); + } catch (err) { + dialogBoxCreate(err + ""); + return; } - return (<> -

    - Would you like to expand into a new city by opening an office? - This would cost {numeralWrapper.format(CorporationConstants.OfficeInitialCost, '$0.000a')} -

    - - - ); -} \ No newline at end of file + + dialogBoxCreate(`Opened a new office in ${dropdown.current.value}!`); + + props.cityStateSetter(dropdown.current.value); + removePopup(props.popupId); + } + return ( + <> +

    + Would you like to expand into a new city by opening an office? This + would cost{" "} + {numeralWrapper.format( + CorporationConstants.OfficeInitialCost, + "$0.000a", + )} +

    + + + + ); +} diff --git a/src/Corporation/ui/ExportPopup.tsx b/src/Corporation/ui/ExportPopup.tsx index 7ff9f5a89..117fe2698 100644 --- a/src/Corporation/ui/ExportPopup.tsx +++ b/src/Corporation/ui/ExportPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { ICorporation } from "../ICorporation"; @@ -7,110 +7,146 @@ import { Export } from "../Export"; import { IIndustry } from "../IIndustry"; interface IProps { - mat: Material; - corp: ICorporation; - popupId: string; + mat: Material; + corp: ICorporation; + popupId: string; } // Create a popup that lets the player manage exports export function ExportPopup(props: IProps): React.ReactElement { - if(props.corp.divisions.length === 0) - throw new Error('Export popup created with no divisions.'); - if(Object.keys(props.corp.divisions[0].warehouses).length === 0) - throw new Error('Export popup created in a division with no warehouses.'); - const [industry, setIndustry] = useState(props.corp.divisions[0].name); - const [city, setCity] = useState(Object.keys(props.corp.divisions[0].warehouses)[0]); - const [amt, setAmt] = useState(''); - const setRerender = useState(false)[1]; + if (props.corp.divisions.length === 0) + throw new Error("Export popup created with no divisions."); + if (Object.keys(props.corp.divisions[0].warehouses).length === 0) + throw new Error("Export popup created in a division with no warehouses."); + const [industry, setIndustry] = useState( + props.corp.divisions[0].name, + ); + const [city, setCity] = useState( + Object.keys(props.corp.divisions[0].warehouses)[0], + ); + const [amt, setAmt] = useState(""); + const setRerender = useState(false)[1]; - function rerender(): void { - setRerender(old => !old); + function rerender(): void { + setRerender((old) => !old); + } + + function onCityChange(event: React.ChangeEvent): void { + setCity(event.target.value); + } + + function onIndustryChange(event: React.ChangeEvent): void { + setIndustry(event.target.value); + } + + function onAmtChange(event: React.ChangeEvent): void { + setAmt(event.target.value); + } + + function exportMaterial(): void { + const industryName = industry; + const cityName = city; + console.log(`${industryName}, ${cityName}, ${amt}`); + + // Sanitize amt + let sanitizedAmt = amt.replace(/\s+/g, ""); + sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, ""); + let temp = sanitizedAmt.replace(/MAX/g, "1"); + try { + temp = eval(temp); + } catch (e) { + dialogBoxCreate("Invalid expression entered for export amount: " + e); + return; } - function onCityChange(event: React.ChangeEvent): void { - setCity(event.target.value); + const n = parseFloat(temp); + + if (n == null || isNaN(n) || n < 0) { + dialogBoxCreate("Invalid amount entered for export"); + return; } + const exportObj = { ind: industryName, city: cityName, amt: sanitizedAmt }; + console.log(exportObj); + props.mat.exp.push(exportObj); + removePopup(props.popupId); + } - function onIndustryChange(event: React.ChangeEvent): void { - setIndustry(event.target.value); + function removeExport(exp: Export): void { + for (let i = 0; i < props.mat.exp.length; ++i) { + if ( + props.mat.exp[i].ind !== exp.ind || + props.mat.exp[i].city !== exp.city || + props.mat.exp[i].amt !== exp.amt + ) + continue; + props.mat.exp.splice(i, 1); + break; } + rerender(); + } - function onAmtChange(event: React.ChangeEvent): void { - setAmt(event.target.value); - } + const currentDivision = props.corp.divisions.find( + (division: IIndustry) => division.name === industry, + ); - function exportMaterial(): void { - const industryName = industry; - const cityName = city; - console.log(`${industryName}, ${cityName}, ${amt}`) - - // Sanitize amt - let sanitizedAmt = amt.replace(/\s+/g, ''); - sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, ''); - let temp = sanitizedAmt.replace(/MAX/g, '1'); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid expression entered for export amount: " + e); - return; - } - - const n = parseFloat(temp); - - if (n == null || isNaN(n) || n < 0) { - dialogBoxCreate("Invalid amount entered for export"); - return; - } - const exportObj = {ind:industryName, city:cityName, amt:sanitizedAmt}; - console.log(exportObj); - props.mat.exp.push(exportObj); - removePopup(props.popupId); - } - - function removeExport(exp: Export): void { - for (let i = 0; i < props.mat.exp.length; ++i) { - if(props.mat.exp[i].ind !== exp.ind || - props.mat.exp[i].city !== exp.city || - props.mat.exp[i].amt !== exp.amt) continue; - props.mat.exp.splice(i, 1); - break - } - rerender(); - } - - const currentDivision = props.corp.divisions.find((division: IIndustry) => division.name === industry); - - return (<> -

    -Select the industry and city to export this material to, as well as -how much of this material to export per second. You can set the export -amount to 'MAX' to export all of the materials in this warehouse. -

    - - - - -

    -Below is a list of all current exports of this material from this warehouse. -Clicking on one of the exports below will REMOVE that export. -

    - { - props.mat.exp.map((exp: Export, index: number) =>
    removeExport(exp)}> - Industry: {exp.ind}
    - City: {exp.city}
    - Amount/s: {exp.amt} -
    ) - } - ); + return ( + <> +

    + Select the industry and city to export this material to, as well as how + much of this material to export per second. You can set the export + amount to 'MAX' to export all of the materials in this warehouse. +

    + + + + +

    + Below is a list of all current exports of this material from this + warehouse. Clicking on one of the exports below will REMOVE that export. +

    + {props.mat.exp.map((exp: Export, index: number) => ( +
    removeExport(exp)} + > + Industry: {exp.ind} +
    + City: {exp.city} +
    + Amount/s: {exp.amt} +
    + ))} + + ); } diff --git a/src/Corporation/ui/FindInvestorsPopup.tsx b/src/Corporation/ui/FindInvestorsPopup.tsx index 3c52331ba..715d928a9 100644 --- a/src/Corporation/ui/FindInvestorsPopup.tsx +++ b/src/Corporation/ui/FindInvestorsPopup.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; import { removePopup } from "../../ui/React/createPopup"; import { numeralWrapper } from "../../ui/numeralFormat"; import { CorporationConstants } from "../data/Constants"; @@ -6,54 +6,66 @@ import { ICorporation } from "../ICorporation"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - corp: ICorporation; - popupId: string; - player: IPlayer; + corp: ICorporation; + popupId: string; + player: IPlayer; } // Create a popup that lets the player manage exports export function FindInvestorsPopup(props: IProps): React.ReactElement { - const val = props.corp.determineValuation() - let percShares = 0; - let roundMultiplier = 4; - switch (props.corp.fundingRound) { - case 0: //Seed - percShares = 0.10; - roundMultiplier = 4; - break; - case 1: //Series A - percShares = 0.35; - roundMultiplier = 3; - break; - case 2: //Series B - percShares = 0.25; - roundMultiplier = 3; - break; - case 3: //Series C - percShares = 0.20; - roundMultiplier = 2.5; - break; - default: - return (<>); - } - const funding = val * percShares * roundMultiplier; - const investShares = Math.floor(CorporationConstants.INITIALSHARES * percShares); - - function findInvestors(): void { - props.corp.fundingRound++; - props.corp.addFunds(funding); - props.corp.numShares -= investShares; - props.corp.rerender(props.player); - removePopup(props.popupId); - } - return (<> -

    - An investment firm has offered you {numeralWrapper.formatMoney(funding)} in - funding in exchange for a {numeralWrapper.format(percShares*100, "0.000a")}% - stake in the company ({numeralWrapper.format(investShares, '0.000a')} shares).

    - Do you accept or reject this offer?

    - Hint: Investment firms will offer more money if your corporation is turning a profit -

    - - ); + const val = props.corp.determineValuation(); + let percShares = 0; + let roundMultiplier = 4; + switch (props.corp.fundingRound) { + case 0: //Seed + percShares = 0.1; + roundMultiplier = 4; + break; + case 1: //Series A + percShares = 0.35; + roundMultiplier = 3; + break; + case 2: //Series B + percShares = 0.25; + roundMultiplier = 3; + break; + case 3: //Series C + percShares = 0.2; + roundMultiplier = 2.5; + break; + default: + return <>; + } + const funding = val * percShares * roundMultiplier; + const investShares = Math.floor( + CorporationConstants.INITIALSHARES * percShares, + ); + + function findInvestors(): void { + props.corp.fundingRound++; + props.corp.addFunds(funding); + props.corp.numShares -= investShares; + props.corp.rerender(props.player); + removePopup(props.popupId); + } + return ( + <> +

    + An investment firm has offered you {numeralWrapper.formatMoney(funding)}{" "} + in funding in exchange for a{" "} + {numeralWrapper.format(percShares * 100, "0.000a")}% stake in the + company ({numeralWrapper.format(investShares, "0.000a")} shares). +
    +
    + Do you accept or reject this offer? +
    +
    + Hint: Investment firms will offer more money if your corporation is + turning a profit +

    + + + ); } diff --git a/src/Corporation/ui/GoPublicPopup.tsx b/src/Corporation/ui/GoPublicPopup.tsx index 305c502d4..706e3064a 100644 --- a/src/Corporation/ui/GoPublicPopup.tsx +++ b/src/Corporation/ui/GoPublicPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { numeralWrapper } from "../../ui/numeralFormat"; @@ -6,58 +6,78 @@ import { ICorporation } from "../ICorporation"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - corp: ICorporation; - popupId: string; - player: IPlayer; + corp: ICorporation; + popupId: string; + player: IPlayer; } // Create a popup that lets the player manage exports export function GoPublicPopup(props: IProps): React.ReactElement { - const [shares, setShares] = useState(''); - const initialSharePrice = props.corp.determineValuation() / (props.corp.totalShares); + const [shares, setShares] = useState(""); + const initialSharePrice = + props.corp.determineValuation() / props.corp.totalShares; - function goPublic(): void { - const numShares = parseFloat(shares); - const initialSharePrice = props.corp.determineValuation() / (props.corp.totalShares); - if (isNaN(numShares)) { - dialogBoxCreate("Invalid value for number of issued shares"); - return; - } - if (numShares > props.corp.numShares) { - dialogBoxCreate("Error: You don't have that many shares to issue!"); - return; - } - props.corp.public = true; - props.corp.sharePrice = initialSharePrice; - props.corp.issuedShares = numShares; - props.corp.numShares -= numShares; - props.corp.addFunds(numShares * initialSharePrice); - props.corp.rerender(props.player); - dialogBoxCreate(`You took your ${props.corp.name} public and earned ` + - `${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`); - removePopup(props.popupId); + function goPublic(): void { + const numShares = parseFloat(shares); + const initialSharePrice = + props.corp.determineValuation() / props.corp.totalShares; + if (isNaN(numShares)) { + dialogBoxCreate("Invalid value for number of issued shares"); + return; } - - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) goPublic(); + if (numShares > props.corp.numShares) { + dialogBoxCreate("Error: You don't have that many shares to issue!"); + return; } + props.corp.public = true; + props.corp.sharePrice = initialSharePrice; + props.corp.issuedShares = numShares; + props.corp.numShares -= numShares; + props.corp.addFunds(numShares * initialSharePrice); + props.corp.rerender(props.player); + dialogBoxCreate( + `You took your ${props.corp.name} public and earned ` + + `${numeralWrapper.formatMoney( + numShares * initialSharePrice, + )} in your IPO`, + ); + removePopup(props.popupId); + } - function onChange(event: React.ChangeEvent): void { - setShares(event.target.value); - } + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) goPublic(); + } - return (<> -

    - Enter the number of shares you would like to issue - for your IPO. These shares will be publicly sold - and you will no longer own them. Your Corporation will - receive {numeralWrapper.formatMoney(initialSharePrice)} per share - (the IPO money will be deposited directly into your Corporation's funds). -

    - You have a total of {numeralWrapper.format(props.corp.numShares, "0.000a")} of - shares that you can issue. -

    - - - ); -} \ No newline at end of file + function onChange(event: React.ChangeEvent): void { + setShares(event.target.value); + } + + return ( + <> +

    + Enter the number of shares you would like to issue for your IPO. These + shares will be publicly sold and you will no longer own them. Your + Corporation will receive {numeralWrapper.formatMoney(initialSharePrice)}{" "} + per share (the IPO money will be deposited directly into your + Corporation's funds). +
    +
    + You have a total of{" "} + {numeralWrapper.format(props.corp.numShares, "0.000a")} of shares that + you can issue. +

    + + + + ); +} diff --git a/src/Corporation/ui/HeaderTab.tsx b/src/Corporation/ui/HeaderTab.tsx index d31f34271..ec7e1e40c 100644 --- a/src/Corporation/ui/HeaderTab.tsx +++ b/src/Corporation/ui/HeaderTab.tsx @@ -1,20 +1,20 @@ -import React from 'react'; +import React from "react"; interface IProps { - current: boolean; - text: string; - onClick: () => void; + current: boolean; + text: string; + onClick: () => void; } export function HeaderTab(props: IProps): React.ReactElement { - let className = "cmpy-mgmt-header-tab"; - if (props.current) { - className += " current"; - } + let className = "cmpy-mgmt-header-tab"; + if (props.current) { + className += " current"; + } - return ( - - ) -} \ No newline at end of file + return ( + + ); +} diff --git a/src/Corporation/ui/HeaderTabs.tsx b/src/Corporation/ui/HeaderTabs.tsx index 67b368977..371ec2aa5 100644 --- a/src/Corporation/ui/HeaderTabs.tsx +++ b/src/Corporation/ui/HeaderTabs.tsx @@ -11,51 +11,50 @@ import { CorporationRouting } from "./Routing"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - corp: ICorporation; - routing: CorporationRouting; - player: IPlayer; + corp: ICorporation; + routing: CorporationRouting; + player: IPlayer; } export function HeaderTabs(props: IProps): React.ReactElement { - function overviewOnClick(): void { - props.routing.routeToOverviewPage(); - props.corp.rerender(props.player); - } + function overviewOnClick(): void { + props.routing.routeToOverviewPage(); + props.corp.rerender(props.player); + } - function openNewIndustryPopup(): void { - const popupId = "cmpy-mgmt-expand-industry-popup"; - createPopup(popupId, NewIndustryPopup, { - corp: props.corp, - routing: props.routing, - popupId: popupId, - }); - } - - return ( -
    - - { - props.corp.divisions.map((division: IIndustry) => { - props.routing.routeTo(division.name); - props.corp.rerender(props.player); - }} - text={division.name} - />) - } - -
    - ) + function openNewIndustryPopup(): void { + const popupId = "cmpy-mgmt-expand-industry-popup"; + createPopup(popupId, NewIndustryPopup, { + corp: props.corp, + routing: props.routing, + popupId: popupId, + }); + } + return ( +
    + + {props.corp.divisions.map((division: IIndustry) => ( + { + props.routing.routeTo(division.name); + props.corp.rerender(props.player); + }} + text={division.name} + /> + ))} + +
    + ); } diff --git a/src/Corporation/ui/HireEmployeePopup.tsx b/src/Corporation/ui/HireEmployeePopup.tsx index e4571d220..e45a4cb6e 100644 --- a/src/Corporation/ui/HireEmployeePopup.tsx +++ b/src/Corporation/ui/HireEmployeePopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { createPopup, removePopup } from "../../ui/React/createPopup"; import { numeralWrapper } from "../../ui/numeralFormat"; import { CorporationConstants } from "../data/Constants"; @@ -11,127 +11,167 @@ import { Employee } from "../Employee"; import { dialogBoxCreate } from "../../../utils/DialogBox"; interface INameEmployeeProps { - office: OfficeSpace; - corp: ICorporation; - popupId: string; - employee: Employee; - player: IPlayer; + office: OfficeSpace; + corp: ICorporation; + popupId: string; + employee: Employee; + player: IPlayer; } function NameEmployeePopup(props: INameEmployeeProps): React.ReactElement { - const [name, setName] = useState(''); - function nameEmployee(): void { - for (let i = 0; i < props.office.employees.length; ++i) { - if (props.office.employees[i].name === name) { - dialogBoxCreate("You already have an employee with this nickname!"); - return; - } - } - props.employee.name = name; - props.office.employees.push(props.employee); - props.corp.rerender(props.player); - removePopup(props.popupId); + const [name, setName] = useState(""); + function nameEmployee(): void { + for (let i = 0; i < props.office.employees.length; ++i) { + if (props.office.employees[i].name === name) { + dialogBoxCreate("You already have an employee with this nickname!"); + return; + } } + props.employee.name = name; + props.office.employees.push(props.employee); + props.corp.rerender(props.player); + removePopup(props.popupId); + } - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) nameEmployee(); - } + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) nameEmployee(); + } - function onChange(event: React.ChangeEvent): void { - setName(event.target.value); - } + function onChange(event: React.ChangeEvent): void { + setName(event.target.value); + } - return (<> -

    Give your employee a nickname!

    - - - ); + return ( + <> +

    Give your employee a nickname!

    + + + + ); } interface IHireEmployeeProps { - employee: Employee; - office: OfficeSpace; - popupId: string; - player: IPlayer; - corp: ICorporation; + employee: Employee; + office: OfficeSpace; + popupId: string; + player: IPlayer; + corp: ICorporation; } function HireEmployeeButton(props: IHireEmployeeProps): React.ReactElement { - function hire(): void { - const popupId = "cmpy-mgmt-name-employee-popup"; - createPopup(popupId, NameEmployeePopup, { - office: props.office, - corp: props.corp, - popupId: popupId, - player: props.player, - employee: props.employee, - }); - removePopup(props.popupId); - } + function hire(): void { + const popupId = "cmpy-mgmt-name-employee-popup"; + createPopup(popupId, NameEmployeePopup, { + office: props.office, + corp: props.corp, + popupId: popupId, + player: props.player, + employee: props.employee, + }); + removePopup(props.popupId); + } - return (
    - Intelligence: {formatNumber(props.employee.int, 1)}
    - Charisma: {formatNumber(props.employee.cha, 1)}
    - Experience: {formatNumber(props.employee.exp, 1)}
    - Creativity: {formatNumber(props.employee.cre, 1)}
    - Efficiency: {formatNumber(props.employee.eff, 1)}
    - Salary: {numeralWrapper.formatMoney(props.employee.sal)} \ s
    -
    ); + return ( +
    + Intelligence: {formatNumber(props.employee.int, 1)} +
    + Charisma: {formatNumber(props.employee.cha, 1)} +
    + Experience: {formatNumber(props.employee.exp, 1)} +
    + Creativity: {formatNumber(props.employee.cre, 1)} +
    + Efficiency: {formatNumber(props.employee.eff, 1)} +
    + Salary: {numeralWrapper.formatMoney(props.employee.sal)} \ s
    +
    + ); } interface IProps { - office: OfficeSpace; - corp: ICorporation; - popupId: string; - player: IPlayer; + office: OfficeSpace; + corp: ICorporation; + popupId: string; + player: IPlayer; } // Create a popup that lets the player manage exports export function HireEmployeePopup(props: IProps): React.ReactElement { - if (props.office.atCapacity()) return (<>); + if (props.office.atCapacity()) return <>; - //Generate three random employees (meh, decent, amazing) - const mult1 = getRandomInt(25, 50)/100; - const mult2 = getRandomInt(51, 75)/100; - const mult3 = getRandomInt(76, 100)/100; - const int = getRandomInt(50, 100); - const cha = getRandomInt(50, 100); - const exp = getRandomInt(50, 100); - const cre = getRandomInt(50, 100); - const eff = getRandomInt(50, 100); - const sal = CorporationConstants.EmployeeSalaryMultiplier * (int + cha + exp + cre + eff); + //Generate three random employees (meh, decent, amazing) + const mult1 = getRandomInt(25, 50) / 100; + const mult2 = getRandomInt(51, 75) / 100; + const mult3 = getRandomInt(76, 100) / 100; + const int = getRandomInt(50, 100); + const cha = getRandomInt(50, 100); + const exp = getRandomInt(50, 100); + const cre = getRandomInt(50, 100); + const eff = getRandomInt(50, 100); + const sal = + CorporationConstants.EmployeeSalaryMultiplier * + (int + cha + exp + cre + eff); - const emp1 = new Employee({ - intelligence: int * mult1, - charisma: cha * mult1, - experience: exp * mult1, - creativity: cre * mult1, - efficiency: eff * mult1, - salary: sal * mult1, - }); + const emp1 = new Employee({ + intelligence: int * mult1, + charisma: cha * mult1, + experience: exp * mult1, + creativity: cre * mult1, + efficiency: eff * mult1, + salary: sal * mult1, + }); - const emp2 = new Employee({ - intelligence: int * mult2, - charisma: cha * mult2, - experience: exp * mult2, - creativity: cre * mult2, - efficiency: eff * mult2, - salary: sal * mult2, - }); + const emp2 = new Employee({ + intelligence: int * mult2, + charisma: cha * mult2, + experience: exp * mult2, + creativity: cre * mult2, + efficiency: eff * mult2, + salary: sal * mult2, + }); - const emp3 = new Employee({ - intelligence: int * mult3, - charisma: cha * mult3, - experience: exp * mult3, - creativity: cre * mult3, - efficiency: eff * mult3, - salary: sal * mult3, - }); + const emp3 = new Employee({ + intelligence: int * mult3, + charisma: cha * mult3, + experience: exp * mult3, + creativity: cre * mult3, + efficiency: eff * mult3, + salary: sal * mult3, + }); - return (<> -

    Select one of the following candidates for hire:

    - - - - ); + return ( + <> +

    Select one of the following candidates for hire:

    + + + + + ); } diff --git a/src/Corporation/ui/Industry.tsx b/src/Corporation/ui/Industry.tsx index c96596212..b7ed6d224 100644 --- a/src/Corporation/ui/Industry.tsx +++ b/src/Corporation/ui/Industry.tsx @@ -10,34 +10,37 @@ import { CorporationRouting } from "./Routing"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - routing: CorporationRouting; - corp: ICorporation; - currentCity: string; - player: IPlayer; + routing: CorporationRouting; + corp: ICorporation; + currentCity: string; + player: IPlayer; } export function Industry(props: IProps): React.ReactElement { - return ( -
    -
    - - -
    -
    - -
    -
    - ) + return ( +
    +
    + + +
    +
    + +
    +
    + ); } diff --git a/src/Corporation/ui/IndustryOffice.tsx b/src/Corporation/ui/IndustryOffice.tsx index fa09e3a12..22a9f99dc 100644 --- a/src/Corporation/ui/IndustryOffice.tsx +++ b/src/Corporation/ui/IndustryOffice.tsx @@ -2,659 +2,807 @@ // (bottom-left panel in the Industry UI) import React, { useState } from "react"; -import { OfficeSpace } from "../OfficeSpace"; -import { Employee } from "../Employee"; -import { EmployeePositions } from "../EmployeePositions"; +import { OfficeSpace } from "../OfficeSpace"; +import { Employee } from "../Employee"; +import { EmployeePositions } from "../EmployeePositions"; -import { numeralWrapper } from "../../ui/numeralFormat"; +import { numeralWrapper } from "../../ui/numeralFormat"; -import { getSelectText } from "../../../utils/uiHelpers/getSelectData"; -import { createPopup } from "../../ui/React/createPopup"; -import { UpgradeOfficeSizePopup } from "./UpgradeOfficeSizePopup"; -import { HireEmployeePopup } from "./HireEmployeePopup"; -import { ThrowPartyPopup } from "./ThrowPartyPopup"; -import { ICorporation } from "../ICorporation"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { CorporationRouting } from "./Routing"; +import { getSelectText } from "../../../utils/uiHelpers/getSelectData"; +import { createPopup } from "../../ui/React/createPopup"; +import { UpgradeOfficeSizePopup } from "./UpgradeOfficeSizePopup"; +import { HireEmployeePopup } from "./HireEmployeePopup"; +import { ThrowPartyPopup } from "./ThrowPartyPopup"; +import { ICorporation } from "../ICorporation"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { CorporationRouting } from "./Routing"; interface IProps { - routing: CorporationRouting; - corp: ICorporation; - currentCity: string; - player: IPlayer; + routing: CorporationRouting; + corp: ICorporation; + currentCity: string; + player: IPlayer; } export function IndustryOffice(props: IProps): React.ReactElement { - const [employeeManualAssignMode, setEmployeeManualAssignMode] = useState(false); - const [city, setCity] = useState(""); - const [divisionName, setDivisionName] = useState(""); - const [employee, setEmployee] = useState(null); - const [numEmployees, setNumEmployees] = useState(0); - const [numOperations, setNumOperations] = useState(0); - const [numEngineers, setNumEngineers] = useState(0); - const [numBusiness, setNumBusiness] = useState(0); - const [numManagement, setNumManagement] = useState(0); - const [numResearch, setNumResearch] = useState(0); - const [numUnassigned, setNumUnassigned] = useState(0); - const [numTraining, setNumTraining] = useState(0); + const [employeeManualAssignMode, setEmployeeManualAssignMode] = + useState(false); + const [city, setCity] = useState(""); + const [divisionName, setDivisionName] = useState(""); + const [employee, setEmployee] = useState(null); + const [numEmployees, setNumEmployees] = useState(0); + const [numOperations, setNumOperations] = useState(0); + const [numEngineers, setNumEngineers] = useState(0); + const [numBusiness, setNumBusiness] = useState(0); + const [numManagement, setNumManagement] = useState(0); + const [numResearch, setNumResearch] = useState(0); + const [numUnassigned, setNumUnassigned] = useState(0); + const [numTraining, setNumTraining] = useState(0); - function resetEmployeeCount(): void { - setNumEmployees(0); - setNumOperations(0); - setNumEngineers(0); - setNumBusiness(0); - setNumManagement(0); - setNumResearch(0); - setNumUnassigned(0); - setNumTraining(0); + function resetEmployeeCount(): void { + setNumEmployees(0); + setNumOperations(0); + setNumEngineers(0); + setNumBusiness(0); + setNumManagement(0); + setNumResearch(0); + setNumUnassigned(0); + setNumTraining(0); + } + + function updateEmployeeCount(): void { + const division = props.routing.currentDivision; + if (division == null) { + throw new Error( + `Routing does not hold reference to the current Industry`, + ); + } + const office = division.offices[props.currentCity]; + if (!(office instanceof OfficeSpace)) { + throw new Error( + `Current City (${props.currentCity}) for UI does not have an OfficeSpace object`, + ); } - function updateEmployeeCount(): void { - const division = props.routing.currentDivision; - if (division == null) { - throw new Error(`Routing does not hold reference to the current Industry`); - } - const office = division.offices[props.currentCity]; - if (!(office instanceof OfficeSpace)) { - throw new Error(`Current City (${props.currentCity}) for UI does not have an OfficeSpace object`); - } - - // If we're in a new city, we have to reset the state - if (division.name !== divisionName || props.currentCity !== city) { - resetEmployeeCount(); - setDivisionName(division.name); - setCity(props.currentCity); - } - - // Calculate how many NEW employees we need to account for - const currentNumEmployees = office.employees.length; - - let newOperations = numOperations; - let newEngineers = numEngineers; - let newBusiness = numBusiness; - let newManagement = numManagement; - let newResearch = numResearch; - let newUnassigned = numUnassigned; - let newTraining = numTraining; - - // Record the number of employees in each position, for NEW employees only - for (let i = numEmployees; i < office.employees.length; ++i) { - switch (office.employees[i].pos) { - case EmployeePositions.Operations: - newOperations++; - break; - case EmployeePositions.Engineer: - newEngineers++; - break; - case EmployeePositions.Business: - newBusiness++; - break; - case EmployeePositions.Management: - newManagement++; - break; - case EmployeePositions.RandD: - newResearch++; - break; - case EmployeePositions.Unassigned: - newUnassigned++; - break; - case EmployeePositions.Training: - newTraining++; - break; - default: - console.error("Unrecognized employee position: " + office.employees[i].pos); - break; - } - } - if(newOperations !== numOperations) setNumOperations(newOperations); - if(newEngineers !== numEngineers) setNumEngineers(newEngineers); - if(newBusiness !== numBusiness) setNumBusiness(newBusiness); - if(newManagement !== numManagement) setNumManagement(newManagement); - if(newResearch !== numResearch) setNumResearch(newResearch); - if(newUnassigned !== numUnassigned) setNumUnassigned(newUnassigned); - if(newTraining !== numTraining) setNumTraining(newTraining); - - if(currentNumEmployees !== numEmployees) setNumEmployees(currentNumEmployees); + // If we're in a new city, we have to reset the state + if (division.name !== divisionName || props.currentCity !== city) { + resetEmployeeCount(); + setDivisionName(division.name); + setCity(props.currentCity); } + // Calculate how many NEW employees we need to account for + const currentNumEmployees = office.employees.length; + + let newOperations = numOperations; + let newEngineers = numEngineers; + let newBusiness = numBusiness; + let newManagement = numManagement; + let newResearch = numResearch; + let newUnassigned = numUnassigned; + let newTraining = numTraining; + + // Record the number of employees in each position, for NEW employees only + for (let i = numEmployees; i < office.employees.length; ++i) { + switch (office.employees[i].pos) { + case EmployeePositions.Operations: + newOperations++; + break; + case EmployeePositions.Engineer: + newEngineers++; + break; + case EmployeePositions.Business: + newBusiness++; + break; + case EmployeePositions.Management: + newManagement++; + break; + case EmployeePositions.RandD: + newResearch++; + break; + case EmployeePositions.Unassigned: + newUnassigned++; + break; + case EmployeePositions.Training: + newTraining++; + break; + default: + console.error( + "Unrecognized employee position: " + office.employees[i].pos, + ); + break; + } + } + if (newOperations !== numOperations) setNumOperations(newOperations); + if (newEngineers !== numEngineers) setNumEngineers(newEngineers); + if (newBusiness !== numBusiness) setNumBusiness(newBusiness); + if (newManagement !== numManagement) setNumManagement(newManagement); + if (newResearch !== numResearch) setNumResearch(newResearch); + if (newUnassigned !== numUnassigned) setNumUnassigned(newUnassigned); + if (newTraining !== numTraining) setNumTraining(newTraining); + + if (currentNumEmployees !== numEmployees) + setNumEmployees(currentNumEmployees); + } + + updateEmployeeCount(); + + // Renders the "Employee Management" section of the Office UI + function renderEmployeeManagement(): React.ReactElement { updateEmployeeCount(); - // Renders the "Employee Management" section of the Office UI - function renderEmployeeManagement(): React.ReactElement { - updateEmployeeCount(); - - if (employeeManualAssignMode) { - return renderManualEmployeeManagement(); - } else { - return renderAutomaticEmployeeManagement(); - } - } - - function renderAutomaticEmployeeManagement(): React.ReactElement { - const division = props.routing.currentDivision; // Validated in constructor - if(division === null) return(<>); - const office = division.offices[props.currentCity]; // Validated in constructor - if(office === 0) return (<>); - const vechain = (props.corp.unlockUpgrades[4] === 1); // Has Vechain upgrade - - function switchModeOnClick(): void { - setEmployeeManualAssignMode(true); - props.corp.rerender(props.player); - } - - // Calculate average morale, happiness, and energy. Also salary - // TODO is this efficient? - let totalMorale = 0, totalHappiness = 0, totalEnergy = 0, totalSalary = 0; - for (let i = 0; i < office.employees.length; ++i) { - totalMorale += office.employees[i].mor; - totalHappiness += office.employees[i].hap; - totalEnergy += office.employees[i].ene; - totalSalary += office.employees[i].sal; - } - - let avgMorale = 0, avgHappiness = 0, avgEnergy = 0; - if (office.employees.length > 0) { - avgMorale = totalMorale / office.employees.length; - avgHappiness = totalHappiness / office.employees.length; - avgEnergy = totalEnergy / office.employees.length; - } - - // Helper functions for (re-)assigning employees to different positions - function assignEmployee(to: string): void { - if(office === 0) return; - if(division === null) return; - if (numUnassigned <= 0) { - console.warn("Cannot assign employee. No unassigned employees available"); - return; - } - - switch (to) { - case EmployeePositions.Operations: - setNumOperations(n => n+1); - break; - case EmployeePositions.Engineer: - setNumEngineers(n => n+1); - break; - case EmployeePositions.Business: - setNumBusiness(n => n+1); - break; - case EmployeePositions.Management: - setNumManagement(n => n+1); - break; - case EmployeePositions.RandD: - setNumResearch(n => n+1); - break; - case EmployeePositions.Unassigned: - setNumUnassigned(n => n+1); - break; - case EmployeePositions.Training: - setNumTraining(n => n+1); - break; - default: - console.error("Unrecognized employee position: " + to); - break; - } - setNumUnassigned(n => n-1); - - office.assignEmployeeToJob(to); - office.calculateEmployeeProductivity(props.corp, division); - props.corp.rerender(props.player); - } - - function unassignEmployee(from: string): void { - if(office === 0) return; - if(division === null) return; - function logWarning(pos: string): void { - console.warn(`Cannot unassign from ${pos} because there is nobody assigned to that position`); - } - - switch (from) { - case EmployeePositions.Operations: - if (numOperations <= 0) { return logWarning(EmployeePositions.Operations); } - setNumOperations(n => n-1); - break; - case EmployeePositions.Engineer: - if (numEngineers <= 0) { return logWarning(EmployeePositions.Operations); } - setNumEngineers(n => n-1); - break; - case EmployeePositions.Business: - if (numBusiness <= 0) { return logWarning(EmployeePositions.Operations); } - setNumBusiness(n => n-1); - break; - case EmployeePositions.Management: - if (numManagement <= 0) { return logWarning(EmployeePositions.Operations); } - setNumManagement(n => n-1); - break; - case EmployeePositions.RandD: - if (numResearch <= 0) { return logWarning(EmployeePositions.Operations); } - setNumResearch(n => n-1); - break; - case EmployeePositions.Unassigned: - console.warn(`Tried to unassign from the Unassigned position`); - break; - case EmployeePositions.Training: - if (numTraining <= 0) { return logWarning(EmployeePositions.Operations); } - setNumTraining(n => n-1); - break; - default: - console.error("Unrecognized employee position: " + from); - break; - } - setNumUnassigned(n => n+1); - - office.unassignEmployeeFromJob(from); - office.calculateEmployeeProductivity(props.corp, division); - props.corp.rerender(props.player); - } - - const positionHeaderStyle = { - fontSize: "15px", - margin: "5px 0px 5px 0px", - width: "50%", - } - const assignButtonClass = numUnassigned > 0 ? "std-button" : "a-link-button-inactive"; - - function operationAssignButtonOnClick(): void { - assignEmployee(EmployeePositions.Operations); - props.corp.rerender(props.player); - } - function operationUnassignButtonOnClick(): void { - unassignEmployee(EmployeePositions.Operations); - props.corp.rerender(props.player); - } - const operationUnassignButtonClass = numOperations > 0 ? "std-button" : "a-link-button-inactive"; - - function engineerAssignButtonOnClick(): void { - assignEmployee(EmployeePositions.Engineer); - props.corp.rerender(props.player); - } - function engineerUnassignButtonOnClick(): void { - unassignEmployee(EmployeePositions.Engineer); - props.corp.rerender(props.player); - } - const engineerUnassignButtonClass = numEngineers > 0 ? "std-button" : "a-link-button-inactive"; - - function businessAssignButtonOnClick(): void { - assignEmployee(EmployeePositions.Business); - props.corp.rerender(props.player); - } - function businessUnassignButtonOnClick(): void { - unassignEmployee(EmployeePositions.Business); - props.corp.rerender(props.player); - } - const businessUnassignButtonClass = numBusiness > 0 ? "std-button" : "a-link-button-inactive"; - - function managementAssignButtonOnClick(): void { - assignEmployee(EmployeePositions.Management); - props.corp.rerender(props.player); - } - function managementUnassignButtonOnClick(): void { - unassignEmployee(EmployeePositions.Management); - props.corp.rerender(props.player); - } - const managementUnassignButtonClass = numManagement > 0 ? "std-button" : "a-link-button-inactive"; - - function rndAssignButtonOnClick(): void { - assignEmployee(EmployeePositions.RandD); - props.corp.rerender(props.player); - } - function rndUnassignButtonOnClick(): void { - unassignEmployee(EmployeePositions.RandD); - props.corp.rerender(props.player); - } - const rndUnassignButtonClass = numResearch > 0 ? "std-button" : "a-link-button-inactive"; - - function trainingAssignButtonOnClick(): void { - assignEmployee(EmployeePositions.Training); - props.corp.rerender(props.player); - } - function trainingUnassignButtonOnClick(): void { - unassignEmployee(EmployeePositions.Training); - props.corp.rerender(props.player); - } - const trainingUnassignButtonClass = numTraining > 0 ? "std-button" : "a-link-button-inactive"; - - return ( -
    - - -

    Unassigned Employees: {numUnassigned}

    -
    - -

    Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}

    -

    Avg Employee Happiness: {numeralWrapper.format(avgHappiness, "0.000")}

    -

    Avg Employee Energy: {numeralWrapper.format(avgEnergy, "0.000")}

    -

    Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}

    - { - vechain && -

    - Material Production: {numeralWrapper.format(division.getOfficeProductivity(office), "0.000")} - - The base amount of material this office can produce. Does not include - production multipliers from upgrades and materials. This value is based off - the productivity of your Operations, Engineering, and Management employees - -

    - } - { - vechain &&
    - } - { - vechain && -

    - Product Production: {numeralWrapper.format(division.getOfficeProductivity(office, {forProduct:true}), "0.000")} - - The base amount of any given Product this office can produce. Does not include - production multipliers from upgrades and materials. This value is based off - the productivity of your Operations, Engineering, and Management employees - -

    - } - { - vechain &&
    - } - { - vechain && -

    - Business Multiplier: x{numeralWrapper.format(division.getBusinessFactor(office), "0.000")} - - The effect this office's 'Business' employees has on boosting sales - -

    - } - { - vechain &&
    - } - -

    - {EmployeePositions.Operations} ({numOperations}) - - Manages supply chain operations. Improves the amount of Materials and Products you produce. - -

    - - -
    - -

    - {EmployeePositions.Engineer} ({numEngineers}) - - Develops and maintains products and production systems. Increases the quality of - everything you produce. Also increases the amount you produce (not as much - as Operations, however) - -

    - - -
    - -

    - {EmployeePositions.Business} ({numBusiness}) - - Handles sales and finances. Improves the amount of Materials and Products you can sell. - -

    - - -
    - -

    - {EmployeePositions.Management} ({numManagement}) - - Leads and oversees employees and office operations. Improves the effectiveness of - Engineer and Operations employees - -

    - - -
    - -

    - {EmployeePositions.RandD} ({numResearch}) - - Research new innovative ways to improve the company. Generates Scientific Research - -

    - - -
    - -

    - {EmployeePositions.Training} ({numTraining}) - - Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations. - -

    - - -
    - ) - } - - function renderManualEmployeeManagement(): React.ReactElement { - const corp = props.corp; - const division = props.routing.currentDivision; // Validated in constructor - if(division === null) return (<>); - const office = division.offices[props.currentCity]; // Validated in constructor - if(office === 0) return (<>); - - function switchModeOnClick(): void { - setEmployeeManualAssignMode(false); - props.corp.rerender(props.player); - } - - const employeeInfoDivStyle = { - color: "white", - margin: "4px", - padding: "4px", - } - - // Employee Selector - const employees = []; - for (let i = 0; i < office.employees.length; ++i) { - employees.push() - } - - function employeeSelectorOnChange(e: React.ChangeEvent): void { - if(office === 0) return; - const name = getSelectText(e.target); - for (let i = 0; i < office.employees.length; ++i) { - if (name === office.employees[i].name) { - setEmployee(office.employees[i]); - break; - } - } - - corp.rerender(props.player); - } - - // Employee Positions Selector - const emp = employee; - let employeePositionSelectorInitialValue = ""; - const employeePositions = []; - const positionNames = Object.values(EmployeePositions); - for (let i = 0; i < positionNames.length; ++i) { - employeePositions.push(); - if (emp != null && emp.pos === positionNames[i]) { - employeePositionSelectorInitialValue = positionNames[i]; - } - } - - function employeePositionSelectorOnChange(e: React.ChangeEvent): void { - if(employee === null) return; - const pos = getSelectText(e.target); - employee.pos = pos; - resetEmployeeCount(); - corp.rerender(props.player); - } - - // Numeraljs formatter - const nf = "0.000"; - - // Employee stats (after applying multipliers) - const effCre = emp ? emp.cre * corp.getEmployeeCreMultiplier() * division.getEmployeeCreMultiplier() : 0; - const effCha = emp ? emp.cha * corp.getEmployeeChaMultiplier() * division.getEmployeeChaMultiplier() : 0; - const effInt = emp ? emp.int * corp.getEmployeeIntMultiplier() * division.getEmployeeIntMultiplier() : 0; - const effEff = emp ? emp.eff * corp.getEmployeeEffMultiplier() * division.getEmployeeEffMultiplier() : 0; - - return ( -
    - - -
    - - { - employee != null && -

    - Morale: {numeralWrapper.format(employee.mor, nf)} -
    - Happiness: {numeralWrapper.format(employee.hap, nf)} -
    - Energy: {numeralWrapper.format(employee.ene, nf)} -
    - Intelligence: {numeralWrapper.format(effInt, nf)} -
    - Charisma: {numeralWrapper.format(effCha, nf)} -
    - Experience: {numeralWrapper.format(employee.exp, nf)} -
    - Creativity: {numeralWrapper.format(effCre, nf)} -
    - Efficiency: {numeralWrapper.format(effEff, nf)} -
    - Salary: {numeralWrapper.formatMoney(employee.sal)} -

    - } - { - employee != null && - - } -
    -
    - ) + if (employeeManualAssignMode) { + return renderManualEmployeeManagement(); + } else { + return renderAutomaticEmployeeManagement(); } + } + function renderAutomaticEmployeeManagement(): React.ReactElement { const division = props.routing.currentDivision; // Validated in constructor - if(division === null) return (<>); + if (division === null) return <>; const office = division.offices[props.currentCity]; // Validated in constructor - if(office === 0) return (<>); - const buttonStyle = { - fontSize: "13px", + if (office === 0) return <>; + const vechain = props.corp.unlockUpgrades[4] === 1; // Has Vechain upgrade + + function switchModeOnClick(): void { + setEmployeeManualAssignMode(true); + props.corp.rerender(props.player); } - // Hire Employee button - let hireEmployeeButtonClass = "tooltip"; - if (office.atCapacity()) { - hireEmployeeButtonClass += " a-link-button-inactive"; - } else { - hireEmployeeButtonClass += " std-button"; - if (office.employees.length === 0) { - hireEmployeeButtonClass += " flashing-button"; - } + // Calculate average morale, happiness, and energy. Also salary + // TODO is this efficient? + let totalMorale = 0, + totalHappiness = 0, + totalEnergy = 0, + totalSalary = 0; + for (let i = 0; i < office.employees.length; ++i) { + totalMorale += office.employees[i].mor; + totalHappiness += office.employees[i].hap; + totalEnergy += office.employees[i].ene; + totalSalary += office.employees[i].sal; } - function openHireEmployeePopup(): void { - if(office === 0) return; - const popupId = "cmpy-mgmt-hire-employee-popup"; - createPopup(popupId, HireEmployeePopup, { - office: office, - corp: props.corp, - popupId: popupId, - player: props.player, - }); + let avgMorale = 0, + avgHappiness = 0, + avgEnergy = 0; + if (office.employees.length > 0) { + avgMorale = totalMorale / office.employees.length; + avgHappiness = totalHappiness / office.employees.length; + avgEnergy = totalEnergy / office.employees.length; } - // Autohire employee button - let autohireEmployeeButtonClass = "tooltip"; - if (office.atCapacity()) { - autohireEmployeeButtonClass += " a-link-button-inactive"; - } else { - autohireEmployeeButtonClass += " std-button"; - } - function autohireEmployeeButtonOnClick(): void { - if(office === 0) return; - if (office.atCapacity()) return; - office.hireRandomEmployee(); - props.corp.rerender(props.player); + // Helper functions for (re-)assigning employees to different positions + function assignEmployee(to: string): void { + if (office === 0) return; + if (division === null) return; + if (numUnassigned <= 0) { + console.warn( + "Cannot assign employee. No unassigned employees available", + ); + return; + } + + switch (to) { + case EmployeePositions.Operations: + setNumOperations((n) => n + 1); + break; + case EmployeePositions.Engineer: + setNumEngineers((n) => n + 1); + break; + case EmployeePositions.Business: + setNumBusiness((n) => n + 1); + break; + case EmployeePositions.Management: + setNumManagement((n) => n + 1); + break; + case EmployeePositions.RandD: + setNumResearch((n) => n + 1); + break; + case EmployeePositions.Unassigned: + setNumUnassigned((n) => n + 1); + break; + case EmployeePositions.Training: + setNumTraining((n) => n + 1); + break; + default: + console.error("Unrecognized employee position: " + to); + break; + } + setNumUnassigned((n) => n - 1); + + office.assignEmployeeToJob(to); + office.calculateEmployeeProductivity(props.corp, division); + props.corp.rerender(props.player); } - function openUpgradeOfficeSizePopup(): void { - if(office === 0) return; - const popupId = "cmpy-mgmt-upgrade-office-size-popup"; - createPopup(popupId, UpgradeOfficeSizePopup, { - office: office, - corp: props.corp, - popupId: popupId, - player: props.player, - }); + function unassignEmployee(from: string): void { + if (office === 0) return; + if (division === null) return; + function logWarning(pos: string): void { + console.warn( + `Cannot unassign from ${pos} because there is nobody assigned to that position`, + ); + } + + switch (from) { + case EmployeePositions.Operations: + if (numOperations <= 0) { + return logWarning(EmployeePositions.Operations); + } + setNumOperations((n) => n - 1); + break; + case EmployeePositions.Engineer: + if (numEngineers <= 0) { + return logWarning(EmployeePositions.Operations); + } + setNumEngineers((n) => n - 1); + break; + case EmployeePositions.Business: + if (numBusiness <= 0) { + return logWarning(EmployeePositions.Operations); + } + setNumBusiness((n) => n - 1); + break; + case EmployeePositions.Management: + if (numManagement <= 0) { + return logWarning(EmployeePositions.Operations); + } + setNumManagement((n) => n - 1); + break; + case EmployeePositions.RandD: + if (numResearch <= 0) { + return logWarning(EmployeePositions.Operations); + } + setNumResearch((n) => n - 1); + break; + case EmployeePositions.Unassigned: + console.warn(`Tried to unassign from the Unassigned position`); + break; + case EmployeePositions.Training: + if (numTraining <= 0) { + return logWarning(EmployeePositions.Operations); + } + setNumTraining((n) => n - 1); + break; + default: + console.error("Unrecognized employee position: " + from); + break; + } + setNumUnassigned((n) => n + 1); + + office.unassignEmployeeFromJob(from); + office.calculateEmployeeProductivity(props.corp, division); + props.corp.rerender(props.player); } - function openThrowPartyPopup(): void { - if(office === 0) return; - const popupId = "cmpy-mgmt-throw-office-party-popup"; - createPopup(popupId, ThrowPartyPopup, { - office: office, - corp: props.corp, - popupId: popupId, - }); + const positionHeaderStyle = { + fontSize: "15px", + margin: "5px 0px 5px 0px", + width: "50%", + }; + const assignButtonClass = + numUnassigned > 0 ? "std-button" : "a-link-button-inactive"; + + function operationAssignButtonOnClick(): void { + assignEmployee(EmployeePositions.Operations); + props.corp.rerender(props.player); } + function operationUnassignButtonOnClick(): void { + unassignEmployee(EmployeePositions.Operations); + props.corp.rerender(props.player); + } + const operationUnassignButtonClass = + numOperations > 0 ? "std-button" : "a-link-button-inactive"; + + function engineerAssignButtonOnClick(): void { + assignEmployee(EmployeePositions.Engineer); + props.corp.rerender(props.player); + } + function engineerUnassignButtonOnClick(): void { + unassignEmployee(EmployeePositions.Engineer); + props.corp.rerender(props.player); + } + const engineerUnassignButtonClass = + numEngineers > 0 ? "std-button" : "a-link-button-inactive"; + + function businessAssignButtonOnClick(): void { + assignEmployee(EmployeePositions.Business); + props.corp.rerender(props.player); + } + function businessUnassignButtonOnClick(): void { + unassignEmployee(EmployeePositions.Business); + props.corp.rerender(props.player); + } + const businessUnassignButtonClass = + numBusiness > 0 ? "std-button" : "a-link-button-inactive"; + + function managementAssignButtonOnClick(): void { + assignEmployee(EmployeePositions.Management); + props.corp.rerender(props.player); + } + function managementUnassignButtonOnClick(): void { + unassignEmployee(EmployeePositions.Management); + props.corp.rerender(props.player); + } + const managementUnassignButtonClass = + numManagement > 0 ? "std-button" : "a-link-button-inactive"; + + function rndAssignButtonOnClick(): void { + assignEmployee(EmployeePositions.RandD); + props.corp.rerender(props.player); + } + function rndUnassignButtonOnClick(): void { + unassignEmployee(EmployeePositions.RandD); + props.corp.rerender(props.player); + } + const rndUnassignButtonClass = + numResearch > 0 ? "std-button" : "a-link-button-inactive"; + + function trainingAssignButtonOnClick(): void { + assignEmployee(EmployeePositions.Training); + props.corp.rerender(props.player); + } + function trainingUnassignButtonOnClick(): void { + unassignEmployee(EmployeePositions.Training); + props.corp.rerender(props.player); + } + const trainingUnassignButtonClass = + numTraining > 0 ? "std-button" : "a-link-button-inactive"; return ( -
    -

    Office Space

    -

    Size: {office.employees.length} / {office.size} employees

    - - -
    - - { - !division.hasResearch("AutoPartyManager") && - - } -
    +
    + - {renderEmployeeManagement()} +

    + Unassigned Employees: {numUnassigned} +

    +
    + +

    Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}

    +

    + Avg Employee Happiness: {numeralWrapper.format(avgHappiness, "0.000")} +

    +

    Avg Employee Energy: {numeralWrapper.format(avgEnergy, "0.000")}

    +

    Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}

    + {vechain && ( +

    + Material Production:{" "} + {numeralWrapper.format( + division.getOfficeProductivity(office), + "0.000", + )} + + The base amount of material this office can produce. Does not + include production multipliers from upgrades and materials. This + value is based off the productivity of your Operations, + Engineering, and Management employees + +

    + )} + {vechain &&
    } + {vechain && ( +

    + Product Production:{" "} + {numeralWrapper.format( + division.getOfficeProductivity(office, { forProduct: true }), + "0.000", + )} + + The base amount of any given Product this office can produce. Does + not include production multipliers from upgrades and materials. + This value is based off the productivity of your Operations, + Engineering, and Management employees + +

    + )} + {vechain &&
    } + {vechain && ( +

    + Business Multiplier: x + {numeralWrapper.format(division.getBusinessFactor(office), "0.000")} + + The effect this office's 'Business' employees has on boosting + sales + +

    + )} + {vechain &&
    } + +

    + {EmployeePositions.Operations} ({numOperations}) + + Manages supply chain operations. Improves the amount of Materials + and Products you produce. + +

    + + +
    + +

    + {EmployeePositions.Engineer} ({numEngineers}) + + Develops and maintains products and production systems. Increases + the quality of everything you produce. Also increases the amount you + produce (not as much as Operations, however) + +

    + + +
    + +

    + {EmployeePositions.Business} ({numBusiness}) + + Handles sales and finances. Improves the amount of Materials and + Products you can sell. + +

    + + +
    + +

    + {EmployeePositions.Management} ({numManagement}) + + Leads and oversees employees and office operations. Improves the + effectiveness of Engineer and Operations employees + +

    + + +
    + +

    + {EmployeePositions.RandD} ({numResearch}) + + Research new innovative ways to improve the company. Generates + Scientific Research + +

    + + +
    + +

    + {EmployeePositions.Training} ({numTraining}) + + Set employee to training, which will increase some of their stats. + Employees in training do not affect any company operations. + +

    + + +
    + ); + } + + function renderManualEmployeeManagement(): React.ReactElement { + const corp = props.corp; + const division = props.routing.currentDivision; // Validated in constructor + if (division === null) return <>; + const office = division.offices[props.currentCity]; // Validated in constructor + if (office === 0) return <>; + + function switchModeOnClick(): void { + setEmployeeManualAssignMode(false); + props.corp.rerender(props.player); + } + + const employeeInfoDivStyle = { + color: "white", + margin: "4px", + padding: "4px", + }; + + // Employee Selector + const employees = []; + for (let i = 0; i < office.employees.length; ++i) { + employees.push( + , + ); + } + + function employeeSelectorOnChange( + e: React.ChangeEvent, + ): void { + if (office === 0) return; + const name = getSelectText(e.target); + for (let i = 0; i < office.employees.length; ++i) { + if (name === office.employees[i].name) { + setEmployee(office.employees[i]); + break; + } + } + + corp.rerender(props.player); + } + + // Employee Positions Selector + const emp = employee; + let employeePositionSelectorInitialValue = ""; + const employeePositions = []; + const positionNames = Object.values(EmployeePositions); + for (let i = 0; i < positionNames.length; ++i) { + employeePositions.push( + , + ); + if (emp != null && emp.pos === positionNames[i]) { + employeePositionSelectorInitialValue = positionNames[i]; + } + } + + function employeePositionSelectorOnChange( + e: React.ChangeEvent, + ): void { + if (employee === null) return; + const pos = getSelectText(e.target); + employee.pos = pos; + resetEmployeeCount(); + corp.rerender(props.player); + } + + // Numeraljs formatter + const nf = "0.000"; + + // Employee stats (after applying multipliers) + const effCre = emp + ? emp.cre * + corp.getEmployeeCreMultiplier() * + division.getEmployeeCreMultiplier() + : 0; + const effCha = emp + ? emp.cha * + corp.getEmployeeChaMultiplier() * + division.getEmployeeChaMultiplier() + : 0; + const effInt = emp + ? emp.int * + corp.getEmployeeIntMultiplier() * + division.getEmployeeIntMultiplier() + : 0; + const effEff = emp + ? emp.eff * + corp.getEmployeeEffMultiplier() * + division.getEmployeeEffMultiplier() + : 0; + + return ( +
    + + +
    + + {employee != null && ( +

    + Morale: {numeralWrapper.format(employee.mor, nf)} +
    + Happiness: {numeralWrapper.format(employee.hap, nf)} +
    + Energy: {numeralWrapper.format(employee.ene, nf)} +
    + Intelligence: {numeralWrapper.format(effInt, nf)} +
    + Charisma: {numeralWrapper.format(effCha, nf)} +
    + Experience: {numeralWrapper.format(employee.exp, nf)} +
    + Creativity: {numeralWrapper.format(effCre, nf)} +
    + Efficiency: {numeralWrapper.format(effEff, nf)} +
    + Salary: {numeralWrapper.formatMoney(employee.sal)} +

    + )} + {employee != null && ( + + )}
    - ) +
    + ); + } + + const division = props.routing.currentDivision; // Validated in constructor + if (division === null) return <>; + const office = division.offices[props.currentCity]; // Validated in constructor + if (office === 0) return <>; + const buttonStyle = { + fontSize: "13px", + }; + + // Hire Employee button + let hireEmployeeButtonClass = "tooltip"; + if (office.atCapacity()) { + hireEmployeeButtonClass += " a-link-button-inactive"; + } else { + hireEmployeeButtonClass += " std-button"; + if (office.employees.length === 0) { + hireEmployeeButtonClass += " flashing-button"; + } + } + + function openHireEmployeePopup(): void { + if (office === 0) return; + const popupId = "cmpy-mgmt-hire-employee-popup"; + createPopup(popupId, HireEmployeePopup, { + office: office, + corp: props.corp, + popupId: popupId, + player: props.player, + }); + } + + // Autohire employee button + let autohireEmployeeButtonClass = "tooltip"; + if (office.atCapacity()) { + autohireEmployeeButtonClass += " a-link-button-inactive"; + } else { + autohireEmployeeButtonClass += " std-button"; + } + function autohireEmployeeButtonOnClick(): void { + if (office === 0) return; + if (office.atCapacity()) return; + office.hireRandomEmployee(); + props.corp.rerender(props.player); + } + + function openUpgradeOfficeSizePopup(): void { + if (office === 0) return; + const popupId = "cmpy-mgmt-upgrade-office-size-popup"; + createPopup(popupId, UpgradeOfficeSizePopup, { + office: office, + corp: props.corp, + popupId: popupId, + player: props.player, + }); + } + + function openThrowPartyPopup(): void { + if (office === 0) return; + const popupId = "cmpy-mgmt-throw-office-party-popup"; + createPopup(popupId, ThrowPartyPopup, { + office: office, + corp: props.corp, + popupId: popupId, + }); + } + + return ( +
    +

    Office Space

    +

    + Size: {office.employees.length} / {office.size} employees +

    + + +
    + + {!division.hasResearch("AutoPartyManager") && ( + + )} +
    + + {renderEmployeeManagement()} +
    + ); } diff --git a/src/Corporation/ui/IndustryOverview.tsx b/src/Corporation/ui/IndustryOverview.tsx index f288f5dd5..254bfc548 100644 --- a/src/Corporation/ui/IndustryOverview.tsx +++ b/src/Corporation/ui/IndustryOverview.tsx @@ -2,309 +2,356 @@ // (top-left panel in the Industry UI) import React from "react"; -import { OfficeSpace } from "../OfficeSpace"; -import { Industries } from "../IndustryData"; -import { IndustryUpgrades } from "../IndustryUpgrades"; -import { numeralWrapper } from "../../ui/numeralFormat"; -import { dialogBoxCreate } from "../../../utils/DialogBox"; -import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; -import { MakeProductPopup } from "./MakeProductPopup"; -import { ResearchPopup } from "./ResearchPopup"; -import { createPopup } from "../../ui/React/createPopup"; -import { Money } from "../../ui/React/Money"; -import { ICorporation } from "../ICorporation"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { CorporationRouting } from "./Routing"; +import { OfficeSpace } from "../OfficeSpace"; +import { Industries } from "../IndustryData"; +import { IndustryUpgrades } from "../IndustryUpgrades"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; +import { MakeProductPopup } from "./MakeProductPopup"; +import { ResearchPopup } from "./ResearchPopup"; +import { createPopup } from "../../ui/React/createPopup"; +import { Money } from "../../ui/React/Money"; +import { ICorporation } from "../ICorporation"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { CorporationRouting } from "./Routing"; interface IProps { - routing: CorporationRouting; - corp: ICorporation; - currentCity: string; - player: IPlayer; + routing: CorporationRouting; + corp: ICorporation; + currentCity: string; + player: IPlayer; } export function IndustryOverview(props: IProps): React.ReactElement { - function renderMakeProductButton(): React.ReactElement { - const division = props.routing.currentDivision; // Validated inside render() - if(division === null) return (<>); - let createProductButtonText = ""; - let createProductPopupText = ""; - switch(division.type) { - case Industries.Food: - createProductButtonText = "Build Restaurant"; - createProductPopupText = "Build and manage a new restaurant!" - break; - case Industries.Tobacco: - createProductButtonText = "Create Product"; - createProductPopupText = "Create a new tobacco product!"; - break; - case Industries.Pharmaceutical: - createProductButtonText = "Create Drug"; - createProductPopupText = "Design and develop a new pharmaceutical drug!"; - break; - case Industries.Computer: - case "Computer": - createProductButtonText = "Create Product"; - createProductPopupText = "Design and manufacture a new computer hardware product!"; - break; - case Industries.Robotics: - createProductButtonText = "Design Robot"; - createProductPopupText = "Design and create a new robot or robotic system!"; - break; - case Industries.Software: - createProductButtonText = "Develop Software"; - createProductPopupText = "Develop a new piece of software!"; - break; - case Industries.Healthcare: - createProductButtonText = "Build Hospital"; - createProductPopupText = "Build and manage a new hospital!"; - break; - case Industries.RealEstate: - createProductButtonText = "Develop Property"; - createProductPopupText = "Develop a new piece of real estate property!"; - break; - default: - createProductButtonText = "Create Product"; - createProductPopupText = "Create a new product!"; - return (<>); - } - createProductPopupText += "

    To begin developing a product, " + - "first choose the city in which it will be designed. The stats of your employees " + - "in the selected city affect the properties of the finished product, such as its " + - "quality, performance, and durability.

    " + - "You can also choose to invest money in the design and marketing of " + - "the product. Investing money in its design will result in a superior product. " + - "Investing money in marketing the product will help the product's sales."; - - const hasMaxProducts = division.hasMaximumNumberProducts(); - - const className = hasMaxProducts ? "a-link-button-inactive tooltip" : "std-button"; - const buttonStyle = { - margin: "6px", - display: "inline-block", - } - - function openMakeProductPopup(): void { - if(division === null) return; - const popupId = "cmpy-mgmt-create-product-popup"; - createPopup(popupId, MakeProductPopup, { - popupText: createProductPopupText, - division: division, - corp: props.corp, - popupId: popupId, - }); - } - - return ( - - ) + function renderMakeProductButton(): React.ReactElement { + const division = props.routing.currentDivision; // Validated inside render() + if (division === null) return <>; + let createProductButtonText = ""; + let createProductPopupText = ""; + switch (division.type) { + case Industries.Food: + createProductButtonText = "Build Restaurant"; + createProductPopupText = "Build and manage a new restaurant!"; + break; + case Industries.Tobacco: + createProductButtonText = "Create Product"; + createProductPopupText = "Create a new tobacco product!"; + break; + case Industries.Pharmaceutical: + createProductButtonText = "Create Drug"; + createProductPopupText = + "Design and develop a new pharmaceutical drug!"; + break; + case Industries.Computer: + case "Computer": + createProductButtonText = "Create Product"; + createProductPopupText = + "Design and manufacture a new computer hardware product!"; + break; + case Industries.Robotics: + createProductButtonText = "Design Robot"; + createProductPopupText = + "Design and create a new robot or robotic system!"; + break; + case Industries.Software: + createProductButtonText = "Develop Software"; + createProductPopupText = "Develop a new piece of software!"; + break; + case Industries.Healthcare: + createProductButtonText = "Build Hospital"; + createProductPopupText = "Build and manage a new hospital!"; + break; + case Industries.RealEstate: + createProductButtonText = "Develop Property"; + createProductPopupText = "Develop a new piece of real estate property!"; + break; + default: + createProductButtonText = "Create Product"; + createProductPopupText = "Create a new product!"; + return <>; } + createProductPopupText += + "

    To begin developing a product, " + + "first choose the city in which it will be designed. The stats of your employees " + + "in the selected city affect the properties of the finished product, such as its " + + "quality, performance, and durability.

    " + + "You can also choose to invest money in the design and marketing of " + + "the product. Investing money in its design will result in a superior product. " + + "Investing money in marketing the product will help the product's sales."; - function renderText(): React.ReactElement { - const corp = props.corp; - const division = props.routing.currentDivision; // Validated inside render() - if(division === null) return (<>); - const vechain = (corp.unlockUpgrades[4] === 1); - const profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber(); + const hasMaxProducts = division.hasMaximumNumberProducts(); - let advertisingInfo = false; - const advertisingFactors = division.getAdvertisingFactors(); - const awarenessFac = advertisingFactors[1]; - const popularityFac = advertisingFactors[2]; - const ratioFac = advertisingFactors[3]; - const totalAdvertisingFac = advertisingFactors[0]; - if (vechain) { advertisingInfo = true; } + const className = hasMaxProducts + ? "a-link-button-inactive tooltip" + : "std-button"; + const buttonStyle = { + margin: "6px", + display: "inline-block", + }; - function productionMultHelpTipOnClick(): void { - if(division === null) return; - // Wrapper for createProgressBarText() - // Converts the industry's "effectiveness factors" - // into a graphic (string) depicting how high that effectiveness is - function convertEffectFacToGraphic(fac: number): string { - return createProgressBarText({ - progress: fac, - totalTicks: 20, - }); - } - - dialogBoxCreate("Owning Hardware, Robots, AI Cores, and Real Estate " + - "can boost your Industry's production. The effect these " + - "materials have on your production varies between Industries. " + - "For example, Real Estate may be very effective for some Industries, " + - "but ineffective for others.

    " + - "This division's production multiplier is calculated by summing " + - "the individual production multiplier of each of its office locations. " + - "This production multiplier is applied to each office. Therefore, it is " + - "beneficial to expand into new cities as this can greatly increase the " + - "production multiplier of your entire Division.

    " + - "Below are approximations for how effective each material is at boosting " + - "this industry's production multiplier (Bigger bars = more effective):

    " + - `Hardware:    ${convertEffectFacToGraphic(division.hwFac)}
    ` + - `Robots:      ${convertEffectFacToGraphic(division.robFac)}
    ` + - `AI Cores:    ${convertEffectFacToGraphic(division.aiFac)}
    ` + - `Real Estate: ${convertEffectFacToGraphic(division.reFac)}`); - } - - function openResearchPopup(): void { - if(division === null) return; - const popupId = "corporation-research-popup-box"; - createPopup(popupId, ResearchPopup, { - industry: division, - popupId: popupId, - }); - } - - return ( -
    - Industry: {division.type} (Corp Funds: ) -

    - Awareness: {numeralWrapper.format(division.awareness, "0.000")}
    - Popularity: {numeralWrapper.format(division.popularity, "0.000")}
    - { - (advertisingInfo !== false) && -

    Advertising Multiplier: x{numeralWrapper.format(totalAdvertisingFac, "0.000")} - - Total multiplier for this industrys sales due to its awareness and popularity -
    - Awareness Bonus: x{numeralWrapper.format(Math.pow(awarenessFac, 0.85), "0.000")} -
    - Popularity Bonus: x{numeralWrapper.format(Math.pow(popularityFac, 0.85), "0.000")} -
    - Ratio Multiplier: x{numeralWrapper.format(Math.pow(ratioFac, 0.85), "0.000")} -
    -

    - } - {advertisingInfo} -

    - Revenue: / s
    - Expenses: /s
    - Profit: / s -

    -

    - Production Multiplier: {numeralWrapper.format(division.prodMult, "0.00")} - - Production gain from owning production-boosting materials - such as hardware, Robots, AI Cores, and Real Estate - -

    -
    ?
    -

    -

    - Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000a")} - - Scientific Research increases the quality of the materials and - products that you produce. - -

    - -
    - ) + function openMakeProductPopup(): void { + if (division === null) return; + const popupId = "cmpy-mgmt-create-product-popup"; + createPopup(popupId, MakeProductPopup, { + popupText: createProductPopupText, + division: division, + corp: props.corp, + popupId: popupId, + }); } - function renderUpgrades(): React.ReactElement[] { - const corp = props.corp; - const division = props.routing.currentDivision; // Validated inside render() - if(division === null) return ([<>]); - const office = division.offices[props.currentCity]; - if (!(office instanceof OfficeSpace)) { - throw new Error(`Current City (${props.currentCity}) for UI does not have an OfficeSpace object`); - } - - const upgrades = []; - for (const index in IndustryUpgrades) { - const upgrade = IndustryUpgrades[index]; - - // AutoBrew research disables the Coffee upgrade - if (division.hasResearch("AutoBrew") && upgrade[4] === "Coffee") { continue; } - - const i = upgrade[0]; - const baseCost = upgrade[1]; - const priceMult = upgrade[2]; - let cost = 0; - switch (i) { - case 0: //Coffee, cost is static per employee - cost = office.employees.length * baseCost; - break; - default: - cost = baseCost * Math.pow(priceMult, division.upgrades[i]); - break; - } - - function onClick(): void { - if(office === 0) return; - if(division === null) return; - if (corp.funds.lt(cost)) { - dialogBoxCreate("Insufficient funds"); - } else { - corp.funds = corp.funds.minus(cost); - division.upgrade(upgrade, { - corporation: corp, - office: office, - }); - // corp.displayDivisionContent(division, city); - corp.rerender(props.player); - } - } - - upgrades.push(renderUpgrade({ - key: index, - onClick: onClick, - text: <>{upgrade[4]} - , - tooltip: upgrade[5], - })); - } - - return upgrades; - } - - interface IRenderUpgradeProps { - key: string; - onClick: () => void; - text: JSX.Element; - tooltip: string; - } - - function renderUpgrade(props: IRenderUpgradeProps): React.ReactElement { - return ( -
    - {props.text} - { - props.tooltip != null && - {props.tooltip} - } -
    - ) - } - - const division = props.routing.currentDivision; - if (division == null) { - throw new Error(`Routing does not hold reference to the current Industry`); - } - - const makeProductButton = renderMakeProductButton(); - return ( -
    - {renderText()} -
    + + ); + } - Purchases & Upgrades
    - {renderUpgrades()}
    + function renderText(): React.ReactElement { + const corp = props.corp; + const division = props.routing.currentDivision; // Validated inside render() + if (division === null) return <>; + const vechain = corp.unlockUpgrades[4] === 1; + const profit = division.lastCycleRevenue + .minus(division.lastCycleExpenses) + .toNumber(); - { - division.makesProducts && - makeProductButton - } + let advertisingInfo = false; + const advertisingFactors = division.getAdvertisingFactors(); + const awarenessFac = advertisingFactors[1]; + const popularityFac = advertisingFactors[2]; + const ratioFac = advertisingFactors[3]; + const totalAdvertisingFac = advertisingFactors[0]; + if (vechain) { + advertisingInfo = true; + } + + function productionMultHelpTipOnClick(): void { + if (division === null) return; + // Wrapper for createProgressBarText() + // Converts the industry's "effectiveness factors" + // into a graphic (string) depicting how high that effectiveness is + function convertEffectFacToGraphic(fac: number): string { + return createProgressBarText({ + progress: fac, + totalTicks: 20, + }); + } + + dialogBoxCreate( + "Owning Hardware, Robots, AI Cores, and Real Estate " + + "can boost your Industry's production. The effect these " + + "materials have on your production varies between Industries. " + + "For example, Real Estate may be very effective for some Industries, " + + "but ineffective for others.

    " + + "This division's production multiplier is calculated by summing " + + "the individual production multiplier of each of its office locations. " + + "This production multiplier is applied to each office. Therefore, it is " + + "beneficial to expand into new cities as this can greatly increase the " + + "production multiplier of your entire Division.

    " + + "Below are approximations for how effective each material is at boosting " + + "this industry's production multiplier (Bigger bars = more effective):

    " + + `Hardware:    ${convertEffectFacToGraphic( + division.hwFac, + )}
    ` + + `Robots:      ${convertEffectFacToGraphic( + division.robFac, + )}
    ` + + `AI Cores:    ${convertEffectFacToGraphic( + division.aiFac, + )}
    ` + + `Real Estate: ${convertEffectFacToGraphic(division.reFac)}`, + ); + } + + function openResearchPopup(): void { + if (division === null) return; + const popupId = "corporation-research-popup-box"; + createPopup(popupId, ResearchPopup, { + industry: division, + popupId: popupId, + }); + } + + return ( +
    + Industry: {division.type} (Corp Funds:{" "} + ) +

    + Awareness: {numeralWrapper.format(division.awareness, "0.000")}
    + Popularity: {numeralWrapper.format(division.popularity, "0.000")}
    + {advertisingInfo !== false && ( +

    + Advertising Multiplier: x + {numeralWrapper.format(totalAdvertisingFac, "0.000")} + + Total multiplier for this industrys sales due to its awareness and + popularity +
    + Awareness Bonus: x + {numeralWrapper.format(Math.pow(awarenessFac, 0.85), "0.000")} +
    + Popularity Bonus: x + {numeralWrapper.format(Math.pow(popularityFac, 0.85), "0.000")} +
    + Ratio Multiplier: x + {numeralWrapper.format(Math.pow(ratioFac, 0.85), "0.000")} +
    +

    + )} + {advertisingInfo} +
    +
    + Revenue: / s{" "} +
    + Expenses: /s
    + Profit: / s +

    +

    + Production Multiplier:{" "} + {numeralWrapper.format(division.prodMult, "0.00")} + + Production gain from owning production-boosting materials such as + hardware, Robots, AI Cores, and Real Estate + +

    +
    + ?
    - ) +

    +

    + Scientific Research:{" "} + {numeralWrapper.format(division.sciResearch.qty, "0.000a")} + + Scientific Research increases the quality of the materials and + products that you produce. + +

    + +
    + ); + } + + function renderUpgrades(): React.ReactElement[] { + const corp = props.corp; + const division = props.routing.currentDivision; // Validated inside render() + if (division === null) return [<>]; + const office = division.offices[props.currentCity]; + if (!(office instanceof OfficeSpace)) { + throw new Error( + `Current City (${props.currentCity}) for UI does not have an OfficeSpace object`, + ); + } + + const upgrades = []; + for (const index in IndustryUpgrades) { + const upgrade = IndustryUpgrades[index]; + + // AutoBrew research disables the Coffee upgrade + if (division.hasResearch("AutoBrew") && upgrade[4] === "Coffee") { + continue; + } + + const i = upgrade[0]; + const baseCost = upgrade[1]; + const priceMult = upgrade[2]; + let cost = 0; + switch (i) { + case 0: //Coffee, cost is static per employee + cost = office.employees.length * baseCost; + break; + default: + cost = baseCost * Math.pow(priceMult, division.upgrades[i]); + break; + } + + function onClick(): void { + if (office === 0) return; + if (division === null) return; + if (corp.funds.lt(cost)) { + dialogBoxCreate("Insufficient funds"); + } else { + corp.funds = corp.funds.minus(cost); + division.upgrade(upgrade, { + corporation: corp, + office: office, + }); + // corp.displayDivisionContent(division, city); + corp.rerender(props.player); + } + } + + upgrades.push( + renderUpgrade({ + key: index, + onClick: onClick, + text: ( + <> + {upgrade[4]} - + + ), + tooltip: upgrade[5], + }), + ); + } + + return upgrades; + } + + interface IRenderUpgradeProps { + key: string; + onClick: () => void; + text: JSX.Element; + tooltip: string; + } + + function renderUpgrade(props: IRenderUpgradeProps): React.ReactElement { + return ( +
    + {props.text} + {props.tooltip != null && ( + {props.tooltip} + )} +
    + ); + } + + const division = props.routing.currentDivision; + if (division == null) { + throw new Error(`Routing does not hold reference to the current Industry`); + } + + const makeProductButton = renderMakeProductButton(); + + return ( +
    + {renderText()} +
    + + Purchases & Upgrades + +
    + {renderUpgrades()}
    + {division.makesProducts && makeProductButton} +
    + ); } diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx index cce46cefb..ff150d49d 100644 --- a/src/Corporation/ui/IndustryWarehouse.tsx +++ b/src/Corporation/ui/IndustryWarehouse.tsx @@ -2,633 +2,697 @@ // (right-side panel in the Industry UI) import React from "react"; -import { CorporationConstants } from "../data/Constants"; -import { OfficeSpace } from "../OfficeSpace"; -import { Material } from "../Material"; -import { Product } from "../Product"; -import { Warehouse } from "../Warehouse"; -import { DiscontinueProductPopup } from "./DiscontinueProductPopup"; -import { ExportPopup } from "./ExportPopup"; -import { LimitProductProductionPopup } from "./LimitProductProductionPopup"; -import { MaterialMarketTaPopup } from "./MaterialMarketTaPopup"; -import { SellMaterialPopup } from "./SellMaterialPopup"; -import { SellProductPopup } from "./SellProductPopup"; -import { PurchaseMaterialPopup } from "./PurchaseMaterialPopup"; -import { ProductMarketTaPopup } from "./ProductMarketTaPopup"; +import { CorporationConstants } from "../data/Constants"; +import { OfficeSpace } from "../OfficeSpace"; +import { Material } from "../Material"; +import { Product } from "../Product"; +import { Warehouse } from "../Warehouse"; +import { DiscontinueProductPopup } from "./DiscontinueProductPopup"; +import { ExportPopup } from "./ExportPopup"; +import { LimitProductProductionPopup } from "./LimitProductProductionPopup"; +import { MaterialMarketTaPopup } from "./MaterialMarketTaPopup"; +import { SellMaterialPopup } from "./SellMaterialPopup"; +import { SellProductPopup } from "./SellProductPopup"; +import { PurchaseMaterialPopup } from "./PurchaseMaterialPopup"; +import { ProductMarketTaPopup } from "./ProductMarketTaPopup"; -import { numeralWrapper } from "../../ui/numeralFormat"; -import { dialogBoxCreate } from "../../../utils/DialogBox"; -import { createPopup } from "../../ui/React/createPopup"; - -import { isString } from "../../../utils/helpers/isString"; -import { ICorporation } from "../ICorporation"; -import { IIndustry } from "../IIndustry"; -import { CorporationRouting } from "./Routing"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { SetSmartSupply } from "../Actions"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { createPopup } from "../../ui/React/createPopup"; +import { isString } from "../../../utils/helpers/isString"; +import { ICorporation } from "../ICorporation"; +import { IIndustry } from "../IIndustry"; +import { CorporationRouting } from "./Routing"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { SetSmartSupply } from "../Actions"; interface IProductProps { - corp: ICorporation; - division: IIndustry; - city: string; - product: Product; - player: IPlayer; + corp: ICorporation; + division: IIndustry; + city: string; + product: Product; + player: IPlayer; } // Creates the UI for a single Product type function ProductComponent(props: IProductProps): React.ReactElement { - const corp = props.corp; - const division = props.division; - const city = props.city; - const product = props.product; + const corp = props.corp; + const division = props.division; + const city = props.city; + const product = props.product; - // Numeraljs formatters - const nf = "0.000"; - const nfB = "0.000a"; // For numbers that might be big + // Numeraljs formatters + const nf = "0.000"; + const nfB = "0.000a"; // For numbers that might be big - const hasUpgradeDashboard = division.hasResearch("uPgrade: Dashboard"); + const hasUpgradeDashboard = division.hasResearch("uPgrade: Dashboard"); - // Total product gain = production - sale - const totalGain = product.data[city][1] - product.data[city][2]; + // Total product gain = production - sale + const totalGain = product.data[city][1] - product.data[city][2]; - // Sell button - let sellButtonText; - if (product.sllman[city][0]) { - if (isString(product.sllman[city][1])) { - sellButtonText = `Sell (${numeralWrapper.format(product.data[city][2], nfB)}/${product.sllman[city][1]})`; - } else { - sellButtonText = `Sell (${numeralWrapper.format(product.data[city][2], nfB)}/${numeralWrapper.format(product.sllman[city][1], nfB)})`; - } + // Sell button + let sellButtonText; + if (product.sllman[city][0]) { + if (isString(product.sllman[city][1])) { + sellButtonText = `Sell (${numeralWrapper.format( + product.data[city][2], + nfB, + )}/${product.sllman[city][1]})`; } else { - sellButtonText = "Sell (0.000/0.000)"; + sellButtonText = `Sell (${numeralWrapper.format( + product.data[city][2], + nfB, + )}/${numeralWrapper.format(product.sllman[city][1], nfB)})`; } + } else { + sellButtonText = "Sell (0.000/0.000)"; + } - if (product.marketTa2) { - sellButtonText += (" @ " + numeralWrapper.formatMoney(product.marketTa2Price[city])); - } else if (product.marketTa1) { - const markupLimit = product.rat / product.mku; - sellButtonText += (" @ " + numeralWrapper.formatMoney(product.pCost + markupLimit)); - } else if (product.sCost) { - if (isString(product.sCost)) { - sellButtonText += (" @ " + product.sCost); - } else { - sellButtonText += (" @ " + numeralWrapper.formatMoney(product.sCost as number)); - } + if (product.marketTa2) { + sellButtonText += + " @ " + numeralWrapper.formatMoney(product.marketTa2Price[city]); + } else if (product.marketTa1) { + const markupLimit = product.rat / product.mku; + sellButtonText += + " @ " + numeralWrapper.formatMoney(product.pCost + markupLimit); + } else if (product.sCost) { + if (isString(product.sCost)) { + sellButtonText += " @ " + product.sCost; + } else { + sellButtonText += + " @ " + numeralWrapper.formatMoney(product.sCost as number); } + } - function openSellProductPopup(): void { - const popupId = "cmpy-mgmt-limit-product-production-popup"; - createPopup(popupId, SellProductPopup, { - product: product, - city: city, - popupId: popupId, - }); - } + function openSellProductPopup(): void { + const popupId = "cmpy-mgmt-limit-product-production-popup"; + createPopup(popupId, SellProductPopup, { + product: product, + city: city, + popupId: popupId, + }); + } - // Limit Production button - let limitProductionButtonText = "Limit Production"; - if (product.prdman[city][0]) { - limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")"; - } + // Limit Production button + let limitProductionButtonText = "Limit Production"; + if (product.prdman[city][0]) { + limitProductionButtonText += + " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")"; + } - function openLimitProductProdutionPopup(): void { - const popupId = "cmpy-mgmt-limit-product-production-popup"; - createPopup(popupId, LimitProductProductionPopup, { - product: product, - city: city, - popupId: popupId, - }); - } + function openLimitProductProdutionPopup(): void { + const popupId = "cmpy-mgmt-limit-product-production-popup"; + createPopup(popupId, LimitProductProductionPopup, { + product: product, + city: city, + popupId: popupId, + }); + } - function openDiscontinueProductPopup(): void { - const popupId = "cmpy-mgmt-discontinue-product-popup"; - createPopup(popupId, DiscontinueProductPopup, { - product: product, - industry: division, - corp: props.corp, - popupId: popupId, - player: props.player, - }); - } + function openDiscontinueProductPopup(): void { + const popupId = "cmpy-mgmt-discontinue-product-popup"; + createPopup(popupId, DiscontinueProductPopup, { + product: product, + industry: division, + corp: props.corp, + popupId: popupId, + player: props.player, + }); + } - function openProductMarketTaPopup(): void { - const popupId = "cmpy-mgmt-marketta-popup"; - createPopup(popupId, ProductMarketTaPopup, { - product: product, - industry: division, - popupId: popupId, - }); - } + function openProductMarketTaPopup(): void { + const popupId = "cmpy-mgmt-marketta-popup"; + createPopup(popupId, ProductMarketTaPopup, { + product: product, + industry: division, + popupId: popupId, + }); + } - // Unfinished Product - if (!product.fin) { - if (hasUpgradeDashboard) { - return ( -
    -

    Designing {product.name}...


    -

    {numeralWrapper.format(product.prog, "0.00")}% complete

    -
    - -
    -
    - - - { - division.hasResearch("Market-TA.I") && - - } -
    -
    - ) - } else { - return ( -
    -

    Designing {product.name}...


    -

    {numeralWrapper.format(product.prog, "0.00")}% complete

    -
    - ); - } - } - - return ( + // Unfinished Product + if (!product.fin) { + if (hasUpgradeDashboard) { + return (
    -

    - {product.name}: {numeralWrapper.format(product.data[city][0], nfB)} ({numeralWrapper.format(totalGain, nfB)}/s) - - Prod: {numeralWrapper.format(product.data[city][1], nfB)}/s -
    - Sell: {numeralWrapper.format(product.data[city][2], nfB)} /s -
    -


    -

    - Rating: {numeralWrapper.format(product.rat, nf)} - - Quality: {numeralWrapper.format(product.qlt, nf)}
    - Performance: {numeralWrapper.format(product.per, nf)}
    - Durability: {numeralWrapper.format(product.dur, nf)}
    - Reliability: {numeralWrapper.format(product.rel, nf)}
    - Aesthetics: {numeralWrapper.format(product.aes, nf)}
    - Features: {numeralWrapper.format(product.fea, nf)} - { - corp.unlockUpgrades[2] === 1 &&
    - } - { - corp.unlockUpgrades[2] === 1 && - "Demand: " + numeralWrapper.format(product.dmd, nf) - } - { - corp.unlockUpgrades[3] === 1 &&
    - } - { - corp.unlockUpgrades[3] === 1 && - "Competition: " + numeralWrapper.format(product.cmp, nf) - } +

    Designing {product.name}...

    +
    +

    {numeralWrapper.format(product.prog, "0.00")}% complete

    +
    - -


    -

    - Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / CorporationConstants.ProductProductionCostRatio)} - - An estimate of the material cost it takes to create this Product. - -


    -

    - Est. Market Price: {numeralWrapper.formatMoney(product.pCost)} - - An estimate of how much consumers are willing to pay for this product. - Setting the sale price above this may result in less sales. Setting the sale price below this may result - in more sales. - -

    - -
    -
    - - - { - division.hasResearch("Market-TA.I") && - - } -
    +
    + +
    + + + {division.hasResearch("Market-TA.I") && ( + + )} +
    - ) + ); + } else { + return ( +
    +

    Designing {product.name}...

    +
    +

    {numeralWrapper.format(product.prog, "0.00")}% complete

    +
    + ); + } + } + + return ( +
    +

    + {product.name}: {numeralWrapper.format(product.data[city][0], nfB)} ( + {numeralWrapper.format(totalGain, nfB)}/s) + + Prod: {numeralWrapper.format(product.data[city][1], nfB)}/s +
    + Sell: {numeralWrapper.format(product.data[city][2], nfB)} /s +
    +

    +
    +

    + Rating: {numeralWrapper.format(product.rat, nf)} + + Quality: {numeralWrapper.format(product.qlt, nf)}
    + Performance: {numeralWrapper.format(product.per, nf)}
    + Durability: {numeralWrapper.format(product.dur, nf)}
    + Reliability: {numeralWrapper.format(product.rel, nf)}
    + Aesthetics: {numeralWrapper.format(product.aes, nf)}
    + Features: {numeralWrapper.format(product.fea, nf)} + {corp.unlockUpgrades[2] === 1 &&
    } + {corp.unlockUpgrades[2] === 1 && + "Demand: " + numeralWrapper.format(product.dmd, nf)} + {corp.unlockUpgrades[3] === 1 &&
    } + {corp.unlockUpgrades[3] === 1 && + "Competition: " + numeralWrapper.format(product.cmp, nf)} +
    +

    +
    +

    + Est. Production Cost:{" "} + {numeralWrapper.formatMoney( + product.pCost / CorporationConstants.ProductProductionCostRatio, + )} + + An estimate of the material cost it takes to create this Product. + +

    +
    +

    + Est. Market Price: {numeralWrapper.formatMoney(product.pCost)} + + An estimate of how much consumers are willing to pay for this product. + Setting the sale price above this may result in less sales. Setting + the sale price below this may result in more sales. + +

    + +
    + +
    + + + {division.hasResearch("Market-TA.I") && ( + + )} +
    +
    + ); } interface IMaterialProps { - corp: ICorporation; - division: IIndustry; - warehouse: Warehouse; - city: string; - mat: Material; + corp: ICorporation; + division: IIndustry; + warehouse: Warehouse; + city: string; + mat: Material; } // Creates the UI for a single Material type function MaterialComponent(props: IMaterialProps): React.ReactElement { - const corp = props.corp; - const division = props.division; - const warehouse = props.warehouse; - const city = props.city; - const mat = props.mat; - const markupLimit = mat.getMarkupLimit(); - const office = division.offices[city]; - if (!(office instanceof OfficeSpace)) { - throw new Error(`Could not get OfficeSpace object for this city (${city})`); - } + const corp = props.corp; + const division = props.division; + const warehouse = props.warehouse; + const city = props.city; + const mat = props.mat; + const markupLimit = mat.getMarkupLimit(); + const office = division.offices[city]; + if (!(office instanceof OfficeSpace)) { + throw new Error(`Could not get OfficeSpace object for this city (${city})`); + } - // Numeraljs formatter - const nf = "0.000"; - const nfB = "0.000a"; // For numbers that might be biger + // Numeraljs formatter + const nf = "0.000"; + const nfB = "0.000a"; // For numbers that might be biger - // Total gain or loss of this material (per second) - const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp; + // Total gain or loss of this material (per second) + const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp; - // Flag that determines whether this industry is "new" and the current material should be - // marked with flashing-red lights - const tutorial = division.newInd && Object.keys(division.reqMats).includes(mat.name) && - mat.buy === 0 && mat.imp === 0; + // Flag that determines whether this industry is "new" and the current material should be + // marked with flashing-red lights + const tutorial = + division.newInd && + Object.keys(division.reqMats).includes(mat.name) && + mat.buy === 0 && + mat.imp === 0; - // Purchase material button - const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`; - const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button"; + // Purchase material button + const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`; + const purchaseButtonClass = tutorial + ? "std-button flashing-button tooltip" + : "std-button"; - function openPurchaseMaterialPopup(): void { - const popupId = "cmpy-mgmt-material-purchase-popup"; - createPopup(popupId, PurchaseMaterialPopup, { - mat: mat, - industry: division, - warehouse: warehouse, - corp: props.corp, - popupId: popupId, - }); - } + function openPurchaseMaterialPopup(): void { + const popupId = "cmpy-mgmt-material-purchase-popup"; + createPopup(popupId, PurchaseMaterialPopup, { + mat: mat, + industry: division, + warehouse: warehouse, + corp: props.corp, + popupId: popupId, + }); + } - function openExportPopup(): void { - const popupId = "cmpy-mgmt-export-popup"; - createPopup(popupId, ExportPopup, { - mat: mat, - corp: props.corp, - popupId: popupId, - }); - } + function openExportPopup(): void { + const popupId = "cmpy-mgmt-export-popup"; + createPopup(popupId, ExportPopup, { + mat: mat, + corp: props.corp, + popupId: popupId, + }); + } - // Sell material button - let sellButtonText; - if (mat.sllman[0]) { - if (isString(mat.sllman[1])) { - sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${mat.sllman[1]})` - } else { - sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1] as number, nfB)})`; - } - - if (mat.marketTa2) { - sellButtonText += " @ " + numeralWrapper.formatMoney(mat.marketTa2Price); - } else if (mat.marketTa1) { - sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit); - } else if (mat.sCost) { - if (isString(mat.sCost)) { - const sCost = (mat.sCost as string).replace(/MP/g, mat.bCost+''); - sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost)); - } else { - sellButtonText += " @ " + numeralWrapper.formatMoney(mat.sCost as number); - } - } + // Sell material button + let sellButtonText; + if (mat.sllman[0]) { + if (isString(mat.sllman[1])) { + sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${ + mat.sllman[1] + })`; } else { - sellButtonText = "Sell (0.000/0.000)"; + sellButtonText = `Sell (${numeralWrapper.format( + mat.sll, + nfB, + )}/${numeralWrapper.format(mat.sllman[1] as number, nfB)})`; } - function openSellMaterialPopup(): void { - const popupId = "cmpy-mgmt-material-sell-popup"; - createPopup(popupId, SellMaterialPopup, { - mat: mat, - corp: props.corp, - popupId: popupId, - }); + if (mat.marketTa2) { + sellButtonText += " @ " + numeralWrapper.formatMoney(mat.marketTa2Price); + } else if (mat.marketTa1) { + sellButtonText += + " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit); + } else if (mat.sCost) { + if (isString(mat.sCost)) { + const sCost = (mat.sCost as string).replace(/MP/g, mat.bCost + ""); + sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost)); + } else { + sellButtonText += + " @ " + numeralWrapper.formatMoney(mat.sCost as number); + } } + } else { + sellButtonText = "Sell (0.000/0.000)"; + } - function openMaterialMarketTaPopup(): void { - const popupId = "cmpy-mgmt-export-popup"; - createPopup(popupId, MaterialMarketTaPopup, { - mat: mat, - industry: division, - corp: props.corp, - popupId: popupId, - }); - } + function openSellMaterialPopup(): void { + const popupId = "cmpy-mgmt-material-sell-popup"; + createPopup(popupId, SellMaterialPopup, { + mat: mat, + corp: props.corp, + popupId: popupId, + }); + } - return ( -
    -
    -

    - {mat.name}: {numeralWrapper.format(mat.qty, nfB)} ({numeralWrapper.format(totalGain, nfB)}/s) - - Buy: {numeralWrapper.format(mat.buy, nfB)}
    - Prod: {numeralWrapper.format(mat.prd, nfB)}
    - Sell: {numeralWrapper.format(mat.sll, nfB)}
    - Export: {numeralWrapper.format(mat.totalExp, nfB)}
    - Import: {numeralWrapper.format(mat.imp, nfB)} - { - corp.unlockUpgrades[2] === 1 &&
    - } - { - corp.unlockUpgrades[2] === 1 && - "Demand: " + numeralWrapper.format(mat.dmd, nf) - } - { - corp.unlockUpgrades[3] === 1 &&
    - } - { - corp.unlockUpgrades[3] === 1 && - "Competition: " + numeralWrapper.format(mat.cmp, nf) - } -
    -


    -

    - MP: {numeralWrapper.formatMoney(mat.bCost)} - - Market Price: The price you would pay if you were to buy this material on the market - -


    -

    - Quality: {numeralWrapper.format(mat.qlt, "0.00a")} - - The quality of your material. Higher quality will lead to more sales - -

    -
    + function openMaterialMarketTaPopup(): void { + const popupId = "cmpy-mgmt-export-popup"; + createPopup(popupId, MaterialMarketTaPopup, { + mat: mat, + industry: division, + corp: props.corp, + popupId: popupId, + }); + } -
    - + return ( +
    +
    +

    + {mat.name}: {numeralWrapper.format(mat.qty, nfB)} ( + {numeralWrapper.format(totalGain, nfB)}/s) + + Buy: {numeralWrapper.format(mat.buy, nfB)}
    + Prod: {numeralWrapper.format(mat.prd, nfB)}
    + Sell: {numeralWrapper.format(mat.sll, nfB)}
    + Export: {numeralWrapper.format(mat.totalExp, nfB)}
    + Import: {numeralWrapper.format(mat.imp, nfB)} + {corp.unlockUpgrades[2] === 1 &&
    } + {corp.unlockUpgrades[2] === 1 && + "Demand: " + numeralWrapper.format(mat.dmd, nf)} + {corp.unlockUpgrades[3] === 1 &&
    } + {corp.unlockUpgrades[3] === 1 && + "Competition: " + numeralWrapper.format(mat.cmp, nf)} +
    +

    +
    +

    + MP: {numeralWrapper.formatMoney(mat.bCost)} + + Market Price: The price you would pay if you were to buy this + material on the market + +

    {" "} +
    +

    + Quality: {numeralWrapper.format(mat.qlt, "0.00a")} + + The quality of your material. Higher quality will lead to more sales + +

    +
    - { - corp.unlockUpgrades[0] === 1 && - - } -
    +
    + - + {corp.unlockUpgrades[0] === 1 && ( + + )} +
    - { - division.hasResearch("Market-TA.I") && - - } + -
    -
    - ) + {division.hasResearch("Market-TA.I") && ( + + )} +
    +
    + ); } interface IProps { - corp: ICorporation; - routing: CorporationRouting; - currentCity: string; - player: IPlayer; + corp: ICorporation; + routing: CorporationRouting; + currentCity: string; + player: IPlayer; } export function IndustryWarehouse(props: IProps): React.ReactElement { - // Returns a boolean indicating whether the given material is relevant for the - // current industry. - function isRelevantMaterial(matName: string, division: IIndustry): boolean { - // Materials that affect Production multiplier - const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"]; + // Returns a boolean indicating whether the given material is relevant for the + // current industry. + function isRelevantMaterial(matName: string, division: IIndustry): boolean { + // Materials that affect Production multiplier + const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"]; - if (Object.keys(division.reqMats).includes(matName)) { return true; } - if (division.prodMats.includes(matName)) { return true; } - if (prodMultiplierMats.includes(matName)) { return true; } - - return false; + if (Object.keys(division.reqMats).includes(matName)) { + return true; + } + if (division.prodMats.includes(matName)) { + return true; + } + if (prodMultiplierMats.includes(matName)) { + return true; } - function renderWarehouseUI(): React.ReactElement { - const corp = props.corp; - const division = props.routing.currentDivision; // Validated in render() - if(division === null) return (<>); - const warehouse = division.warehouses[props.currentCity]; // Validated in render() - if(warehouse === 0) return (<>); + return false; + } - // General Storage information at the top - const sizeUsageStyle = { - color: warehouse.sizeUsed >= warehouse.size ? "red" : "white", - margin: "5px", - } + function renderWarehouseUI(): React.ReactElement { + const corp = props.corp; + const division = props.routing.currentDivision; // Validated in render() + if (division === null) return <>; + const warehouse = division.warehouses[props.currentCity]; // Validated in render() + if (warehouse === 0) return <>; - // Upgrade Warehouse size button - const sizeUpgradeCost = CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1); - const canAffordUpgrade = (corp.funds.gt(sizeUpgradeCost)); - const upgradeWarehouseClass = canAffordUpgrade ? "std-button" : "a-link-button-inactive"; - function upgradeWarehouseOnClick(): void { - if(division === null) return; - if(warehouse === 0) return; - ++warehouse.level; - warehouse.updateSize(corp, division); - corp.funds = corp.funds.minus(sizeUpgradeCost); - corp.rerender(props.player); - } + // General Storage information at the top + const sizeUsageStyle = { + color: warehouse.sizeUsed >= warehouse.size ? "red" : "white", + margin: "5px", + }; - // Industry material Requirements - let generalReqsText = "This Industry uses [" + Object.keys(division.reqMats).join(", ") + - "] in order to "; - if (division.prodMats.length > 0) { - generalReqsText += "produce [" + division.prodMats.join(", ") + "] "; - if (division.makesProducts) { - generalReqsText += " and " + division.getProductDescriptionText(); - } - } else if (division.makesProducts) { - generalReqsText += (division.getProductDescriptionText() + "."); - } - - const ratioLines = []; - for (const matName in division.reqMats) { - if (division.reqMats.hasOwnProperty(matName)) { - const text = [" *", division.reqMats[matName], matName].join(" "); - ratioLines.push(( -
    -

    {text}

    -
    - )); - } - } - - let createdItemsText = "in order to create "; - if (division.prodMats.length > 0) { - createdItemsText += "one of each produced Material (" + division.prodMats.join(", ") + ") "; - if (division.makesProducts) { - createdItemsText += "or to create one of its Products"; - } - } else if (division.makesProducts) { - createdItemsText += "one of its Products"; - } - - // Current State: - let stateText; - switch(division.state) { - case "START": - stateText = "Current state: Preparing..."; - break; - case "PURCHASE": - stateText = "Current state: Purchasing materials..."; - break; - case "PRODUCTION": - stateText = "Current state: Producing materials and/or products..."; - break; - case "SALE": - stateText = "Current state: Selling materials and/or products..."; - break; - case "EXPORT": - stateText = "Current state: Exporting materials and/or products..."; - break; - default: - console.error(`Invalid state: ${division.state}`); - break; - } - - // Smart Supply Checkbox - const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox"; - function smartSupplyOnChange(e: React.ChangeEvent): void { - if(warehouse === 0) return; - SetSmartSupply(warehouse, e.target.checked); - corp.rerender(props.player); - } - - // Create React components for materials - const mats = []; - for (const matName in warehouse.materials) { - if (warehouse.materials[matName] instanceof Material) { - // Only create UI for materials that are relevant for the industry - if (isRelevantMaterial(matName, division)) { - mats.push(); - } - } - } - - // Create React components for products - const products = []; - if (division.makesProducts && Object.keys(division.products).length > 0) { - for (const productName in division.products) { - const product = division.products[productName]; - if (product instanceof Product) { - products.push(); - } - } - } - - return ( -
    -

    - Storage: {numeralWrapper.formatBigNumber(warehouse.sizeUsed)} / {numeralWrapper.formatBigNumber(warehouse.size)} - -

    - - - -

    {generalReqsText}. The exact requirements for production are:


    - {ratioLines}
    -

    {createdItemsText}

    -

    - To get started with production, purchase your required materials - or import them from another of your company's divisions. -


    - -

    {stateText}

    - - { - corp.unlockUpgrades[1] && -
    - - -
    - } - - {mats} - - {products} - -
    - ) + // Upgrade Warehouse size button + const sizeUpgradeCost = + CorporationConstants.WarehouseUpgradeBaseCost * + Math.pow(1.07, warehouse.level + 1); + const canAffordUpgrade = corp.funds.gt(sizeUpgradeCost); + const upgradeWarehouseClass = canAffordUpgrade + ? "std-button" + : "a-link-button-inactive"; + function upgradeWarehouseOnClick(): void { + if (division === null) return; + if (warehouse === 0) return; + ++warehouse.level; + warehouse.updateSize(corp, division); + corp.funds = corp.funds.minus(sizeUpgradeCost); + corp.rerender(props.player); } - const division = props.routing.currentDivision; - if (division == null) { - throw new Error(`Routing does not hold reference to the current Industry`); + // Industry material Requirements + let generalReqsText = + "This Industry uses [" + + Object.keys(division.reqMats).join(", ") + + "] in order to "; + if (division.prodMats.length > 0) { + generalReqsText += "produce [" + division.prodMats.join(", ") + "] "; + if (division.makesProducts) { + generalReqsText += " and " + division.getProductDescriptionText(); + } + } else if (division.makesProducts) { + generalReqsText += division.getProductDescriptionText() + "."; } - const warehouse = division.warehouses[props.currentCity]; - function purchaseWarehouse(division: IIndustry, city: string): void { - if (props.corp.funds.lt(CorporationConstants.WarehouseInitialCost)) { - dialogBoxCreate("You do not have enough funds to do this!"); - } else { - division.warehouses[city] = new Warehouse({ - corp: props.corp, - industry: division, - loc: city, - size: CorporationConstants.WarehouseInitialSize, - }); - props.corp.funds = props.corp.funds.minus(CorporationConstants.WarehouseInitialCost); - props.corp.rerender(props.player); + const ratioLines = []; + for (const matName in division.reqMats) { + if (division.reqMats.hasOwnProperty(matName)) { + const text = [" *", division.reqMats[matName], matName].join(" "); + ratioLines.push( +
    +

    {text}

    +
    , + ); + } + } + + let createdItemsText = "in order to create "; + if (division.prodMats.length > 0) { + createdItemsText += + "one of each produced Material (" + division.prodMats.join(", ") + ") "; + if (division.makesProducts) { + createdItemsText += "or to create one of its Products"; + } + } else if (division.makesProducts) { + createdItemsText += "one of its Products"; + } + + // Current State: + let stateText; + switch (division.state) { + case "START": + stateText = "Current state: Preparing..."; + break; + case "PURCHASE": + stateText = "Current state: Purchasing materials..."; + break; + case "PRODUCTION": + stateText = "Current state: Producing materials and/or products..."; + break; + case "SALE": + stateText = "Current state: Selling materials and/or products..."; + break; + case "EXPORT": + stateText = "Current state: Exporting materials and/or products..."; + break; + default: + console.error(`Invalid state: ${division.state}`); + break; + } + + // Smart Supply Checkbox + const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox"; + function smartSupplyOnChange(e: React.ChangeEvent): void { + if (warehouse === 0) return; + SetSmartSupply(warehouse, e.target.checked); + corp.rerender(props.player); + } + + // Create React components for materials + const mats = []; + for (const matName in warehouse.materials) { + if (warehouse.materials[matName] instanceof Material) { + // Only create UI for materials that are relevant for the industry + if (isRelevantMaterial(matName, division)) { + mats.push( + , + ); } + } } - if (warehouse instanceof Warehouse) { - return renderWarehouseUI(); + // Create React components for products + const products = []; + if (division.makesProducts && Object.keys(division.products).length > 0) { + for (const productName in division.products) { + const product = division.products[productName]; + if (product instanceof Product) { + products.push( + , + ); + } + } + } + + return ( +
    +

    + Storage: {numeralWrapper.formatBigNumber(warehouse.sizeUsed)} /{" "} + {numeralWrapper.formatBigNumber(warehouse.size)} + +

    + + + +

    {generalReqsText}. The exact requirements for production are:

    +
    + {ratioLines} +
    +

    {createdItemsText}

    +

    + To get started with production, purchase your required materials or + import them from another of your company's divisions. +

    +
    + +

    {stateText}

    + + {corp.unlockUpgrades[1] && ( +
    + + +
    + )} + + {mats} + + {products} +
    + ); + } + + const division = props.routing.currentDivision; + if (division == null) { + throw new Error(`Routing does not hold reference to the current Industry`); + } + const warehouse = division.warehouses[props.currentCity]; + + function purchaseWarehouse(division: IIndustry, city: string): void { + if (props.corp.funds.lt(CorporationConstants.WarehouseInitialCost)) { + dialogBoxCreate("You do not have enough funds to do this!"); } else { - return ( -
    - -
    - ) + division.warehouses[city] = new Warehouse({ + corp: props.corp, + industry: division, + loc: city, + size: CorporationConstants.WarehouseInitialSize, + }); + props.corp.funds = props.corp.funds.minus( + CorporationConstants.WarehouseInitialCost, + ); + props.corp.rerender(props.player); } + } + + if (warehouse instanceof Warehouse) { + return renderWarehouseUI(); + } else { + return ( +
    + +
    + ); + } } diff --git a/src/Corporation/ui/IssueDividendsPopup.tsx b/src/Corporation/ui/IssueDividendsPopup.tsx index 881af18fa..83abac2e8 100644 --- a/src/Corporation/ui/IssueDividendsPopup.tsx +++ b/src/Corporation/ui/IssueDividendsPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { removePopup } from "../../ui/React/createPopup"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { CorporationConstants } from "../data/Constants"; @@ -6,53 +6,78 @@ import { ICorporation } from "../ICorporation"; import { IssueDividends } from "../Actions"; interface IProps { - popupId: string; - corp: ICorporation; + popupId: string; + corp: ICorporation; } // Create a popup that lets the player issue & manage dividends // This is created when the player clicks the "Issue Dividends" button in the overview panel export function IssueDividendsPopup(props: IProps): React.ReactElement { - const [percent, setPercent] = useState(null); + const [percent, setPercent] = useState(null); - function issueDividends(): void { - if(percent === null) return; - try { - IssueDividends(props.corp, percent/100); - } catch(err) { - dialogBoxCreate(err+''); - } - - removePopup(props.popupId); + function issueDividends(): void { + if (percent === null) return; + try { + IssueDividends(props.corp, percent / 100); + } catch (err) { + dialogBoxCreate(err + ""); } - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) issueDividends(); - } + removePopup(props.popupId); + } - function onChange(event: React.ChangeEvent): void { - if(event.target.value === "") setPercent(null); - else setPercent(parseFloat(event.target.value)); - } + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) issueDividends(); + } - return (<> -

    -Dividends are a distribution of a portion of the corporation's -profits to the shareholders. This includes yourself, as well.

    -In order to issue dividends, simply allocate some percentage -of your corporation's profits to dividends. This percentage must be an -integer between 0 and {CorporationConstants.DividendMaxPercentage}. (A percentage of 0 means no dividends will be -issued

    -Two important things to note:
    - * Issuing dividends will negatively affect your corporation's stock price
    - * Dividends are taxed. Taxes start at 50%, but can be decreased

    -Example: Assume your corporation makes $100m / sec in profit and you allocate -40% of that towards dividends. That means your corporation will gain $60m / sec -in funds and the remaining $40m / sec will be paid as dividends. Since your -corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share -per second before taxes. -

    - - - ); + function onChange(event: React.ChangeEvent): void { + if (event.target.value === "") setPercent(null); + else setPercent(parseFloat(event.target.value)); + } + + return ( + <> +

    + Dividends are a distribution of a portion of the corporation's profits + to the shareholders. This includes yourself, as well. +
    +
    + In order to issue dividends, simply allocate some percentage of your + corporation's profits to dividends. This percentage must be an integer + between 0 and {CorporationConstants.DividendMaxPercentage}. (A + percentage of 0 means no dividends will be issued +
    +
    + Two important things to note: +
    + * Issuing dividends will negatively affect your corporation's stock + price +
    + * Dividends are taxed. Taxes start at 50%, but can be decreased +
    +
    + Example: Assume your corporation makes $100m / sec in profit and you + allocate 40% of that towards dividends. That means your corporation will + gain $60m / sec in funds and the remaining $40m / sec will be paid as + dividends. Since your corporation starts with 1 billion shares, every + shareholder will be paid $0.04 per share per second before taxes. +

    + + + + ); } diff --git a/src/Corporation/ui/IssueNewSharesPopup.tsx b/src/Corporation/ui/IssueNewSharesPopup.tsx index 9591a15c7..7272bda61 100644 --- a/src/Corporation/ui/IssueNewSharesPopup.tsx +++ b/src/Corporation/ui/IssueNewSharesPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { numeralWrapper } from "../../ui/numeralFormat"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; @@ -7,128 +7,162 @@ import { CorporationConstants } from "../data/Constants"; import { ICorporation } from "../ICorporation"; interface IEffectTextProps { - corp: ICorporation; - shares: number | null; + corp: ICorporation; + shares: number | null; } function EffectText(props: IEffectTextProps): React.ReactElement { - if(props.shares === null) return (<>); - const newSharePrice = Math.round(props.corp.sharePrice * 0.9); - const maxNewSharesUnrounded = Math.round(props.corp.totalShares * 0.2); - const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); - let newShares = props.shares; - if (isNaN(newShares)) { - return (

    Invalid input

    ); - } + if (props.shares === null) return <>; + const newSharePrice = Math.round(props.corp.sharePrice * 0.9); + const maxNewSharesUnrounded = Math.round(props.corp.totalShares * 0.2); + const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); + let newShares = props.shares; + if (isNaN(newShares)) { + return

    Invalid input

    ; + } - // Round to nearest ten-millionth - newShares /= 10e6; - newShares = Math.round(newShares) * 10e6; + // Round to nearest ten-millionth + newShares /= 10e6; + newShares = Math.round(newShares) * 10e6; - if (newShares < 10e6) { - return (

    Must issue at least 10 million new shares

    ); - } + if (newShares < 10e6) { + return

    Must issue at least 10 million new shares

    ; + } - if (newShares > maxNewShares) { - return (

    You cannot issue that many shares

    ); - } + if (newShares > maxNewShares) { + return

    You cannot issue that many shares

    ; + } - return (

    - Issue ${numeralWrapper.format(newShares, "0.000a")} new - shares for {numeralWrapper.formatMoney(newShares * newSharePrice)}? -

    ); + return ( +

    + Issue ${numeralWrapper.format(newShares, "0.000a")} new shares for{" "} + {numeralWrapper.formatMoney(newShares * newSharePrice)}? +

    + ); } interface IProps { - corp: ICorporation; - popupId: string; + corp: ICorporation; + popupId: string; } // Create a popup that lets the player issue new shares // This is created when the player clicks the "Issue New Shares" buttons in the overview panel export function IssueNewSharesPopup(props: IProps): React.ReactElement { - const [shares, setShares] = useState(null); - const maxNewSharesUnrounded = Math.round(props.corp.totalShares * 0.2); - const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); + const [shares, setShares] = useState(null); + const maxNewSharesUnrounded = Math.round(props.corp.totalShares * 0.2); + const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); - function issueNewShares(): void { - if(shares === null) return; - const newSharePrice = Math.round(props.corp.sharePrice * 0.9); - let newShares = shares; - if (isNaN(newShares)) { - dialogBoxCreate("Invalid input for number of new shares"); - return; - } - - // Round to nearest ten-millionth - newShares = Math.round(newShares / 10e6) * 10e6; - - if (newShares < 10e6 || newShares > maxNewShares) { - dialogBoxCreate("Invalid input for number of new shares"); - return; - } - - const profit = newShares * newSharePrice; - props.corp.issueNewSharesCooldown = CorporationConstants.IssueNewSharesCooldown; - props.corp.totalShares += newShares; - - // Determine how many are bought by private investors - // Private investors get up to 50% at most - // Round # of private shares to the nearest millionth - let privateShares = getRandomInt(0, Math.round(newShares / 2)); - privateShares = Math.round(privateShares / 1e6) * 1e6; - - props.corp.issuedShares += (newShares - privateShares); - props.corp.funds = props.corp.funds.plus(profit); - props.corp.immediatelyUpdateSharePrice(); - - removePopup(props.popupId); - dialogBoxCreate(`Issued ${numeralWrapper.format(newShares, "0.000a")} and raised ` + - `${numeralWrapper.formatMoney(profit)}. ${numeralWrapper.format(privateShares, "0.000a")} ` + - `of these shares were bought by private investors.

    ` + - `Stock price decreased to ${numeralWrapper.formatMoney(props.corp.sharePrice)}`); + function issueNewShares(): void { + if (shares === null) return; + const newSharePrice = Math.round(props.corp.sharePrice * 0.9); + let newShares = shares; + if (isNaN(newShares)) { + dialogBoxCreate("Invalid input for number of new shares"); + return; } - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) issueNewShares(); + // Round to nearest ten-millionth + newShares = Math.round(newShares / 10e6) * 10e6; + + if (newShares < 10e6 || newShares > maxNewShares) { + dialogBoxCreate("Invalid input for number of new shares"); + return; } - function onChange(event: React.ChangeEvent): void { - if(event.target.value === "") setShares(null); - else setShares(parseFloat(event.target.value)); - } + const profit = newShares * newSharePrice; + props.corp.issueNewSharesCooldown = + CorporationConstants.IssueNewSharesCooldown; + props.corp.totalShares += newShares; - return (<> -

    -You can issue new equity shares (i.e. stocks) in order to raise -capital for your corporation.

    - * You can issue at most {numeralWrapper.formatMoney(maxNewShares)} new shares
    - * New shares are sold at a 10% discount
    - * You can only issue new shares once every 12 hours
    - * Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share
    - * Number of new shares issued must be a multiple of 10 million

    -When you choose to issue new equity, private shareholders have first priority for up to 50% of the new shares. -If they choose to exercise this option, these newly issued shares become private, restricted shares, which means -you cannot buy them back. -

    - - - - ); + // Determine how many are bought by private investors + // Private investors get up to 50% at most + // Round # of private shares to the nearest millionth + let privateShares = getRandomInt(0, Math.round(newShares / 2)); + privateShares = Math.round(privateShares / 1e6) * 1e6; + props.corp.issuedShares += newShares - privateShares; + props.corp.funds = props.corp.funds.plus(profit); + props.corp.immediatelyUpdateSharePrice(); - // let issueBtn, newSharesInput; - // const dynamicText = createElement("p", { - // display: "block", - // }); + removePopup(props.popupId); + dialogBoxCreate( + `Issued ${numeralWrapper.format(newShares, "0.000a")} and raised ` + + `${numeralWrapper.formatMoney(profit)}. ${numeralWrapper.format( + privateShares, + "0.000a", + )} ` + + `of these shares were bought by private investors.

    ` + + `Stock price decreased to ${numeralWrapper.formatMoney( + props.corp.sharePrice, + )}`, + ); + } - // function updateDynamicText(corp) { - - // } + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) issueNewShares(); + } + function onChange(event: React.ChangeEvent): void { + if (event.target.value === "") setShares(null); + else setShares(parseFloat(event.target.value)); + } + return ( + <> +

    + You can issue new equity shares (i.e. stocks) in order to raise capital + for your corporation. +
    +
    +  * You can issue at most {numeralWrapper.formatMoney( + maxNewShares, + )}{" "} + new shares +
    +  * New shares are sold at a 10% discount +
    +  * You can only issue new shares once every 12 hours +
    +  * Issuing new shares causes dilution, resulting in a decrease in + stock price and lower dividends per share +
    +  * Number of new shares issued must be a multiple of 10 million +
    +
    + When you choose to issue new equity, private shareholders have first + priority for up to 50% of the new shares. If they choose to exercise + this option, these newly issued shares become private, restricted + shares, which means you cannot buy them back. +

    + + + + + ); + // let issueBtn, newSharesInput; + // const dynamicText = createElement("p", { + // display: "block", + // }); - // createPopup(popupId, [descText, dynamicText, newSharesInput, issueBtn, cancelBtn]); - // newSharesInput.focus(); + // function updateDynamicText(corp) { + + // } + + // createPopup(popupId, [descText, dynamicText, newSharesInput, issueBtn, cancelBtn]); + // newSharesInput.focus(); } diff --git a/src/Corporation/ui/LevelableUpgrade.tsx b/src/Corporation/ui/LevelableUpgrade.tsx index aedff3800..2b1f2f4b4 100644 --- a/src/Corporation/ui/LevelableUpgrade.tsx +++ b/src/Corporation/ui/LevelableUpgrade.tsx @@ -10,34 +10,42 @@ import { LevelUpgrade } from "../Actions"; import { Money } from "../../ui/React/Money"; interface IProps { - upgrade: CorporationUpgrade; - corp: ICorporation; - player: IPlayer; + upgrade: CorporationUpgrade; + corp: ICorporation; + player: IPlayer; } export function LevelableUpgrade(props: IProps): React.ReactElement { - const data = props.upgrade; - const level = props.corp.upgrades[data[0]]; + const data = props.upgrade; + const level = props.corp.upgrades[data[0]]; - const baseCost = data[1]; - const priceMult = data[2]; - const cost = baseCost * Math.pow(priceMult, level); + const baseCost = data[1]; + const priceMult = data[2]; + const cost = baseCost * Math.pow(priceMult, level); - const text = <>{data[4]} - - const tooltip = data[5]; - function onClick(): void { - try { - LevelUpgrade(props.corp, props.upgrade); - } catch(err) { - dialogBoxCreate(err+''); - } - props.corp.rerender(props.player); + const text = ( + <> + {data[4]} - + + ); + const tooltip = data[5]; + function onClick(): void { + try { + LevelUpgrade(props.corp, props.upgrade); + } catch (err) { + dialogBoxCreate(err + ""); } + props.corp.rerender(props.player); + } - return ( -
    - {text} - {tooltip} -
    - ) + return ( +
    + {text} + {tooltip} +
    + ); } diff --git a/src/Corporation/ui/LimitProductProductionPopup.tsx b/src/Corporation/ui/LimitProductProductionPopup.tsx index bf9c51f70..1b2af8ba5 100644 --- a/src/Corporation/ui/LimitProductProductionPopup.tsx +++ b/src/Corporation/ui/LimitProductProductionPopup.tsx @@ -1,53 +1,69 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { Product } from "../Product"; interface IProps { - product: Product; - city: string; - popupId: string; + product: Product; + city: string; + popupId: string; } // Create a popup that lets the player limit the production of a product export function LimitProductProductionPopup(props: IProps): React.ReactElement { - const [limit, setLimit] = useState(null); + const [limit, setLimit] = useState(null); - function limitProductProduction(): void { - if (limit === null) { - props.product.prdman[props.city][0] = false; - removePopup(props.popupId); - return; - } - const qty = limit; - if (isNaN(qty)) { - dialogBoxCreate("Invalid value entered"); - return; - } - if (qty < 0) { - props.product.prdman[props.city][0] = false; - } else { - props.product.prdman[props.city][0] = true; - props.product.prdman[props.city][1] = qty; - } - removePopup(props.popupId); + function limitProductProduction(): void { + if (limit === null) { + props.product.prdman[props.city][0] = false; + removePopup(props.popupId); + return; } - - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) limitProductProduction(); + const qty = limit; + if (isNaN(qty)) { + dialogBoxCreate("Invalid value entered"); + return; } - - function onChange(event: React.ChangeEvent): void { - if(event.target.value === "") setLimit(null); - else setLimit(parseFloat(event.target.value)); + if (qty < 0) { + props.product.prdman[props.city][0] = false; + } else { + props.product.prdman[props.city][0] = true; + props.product.prdman[props.city][1] = qty; } + removePopup(props.popupId); + } - return (<> -

    -Enter a limit to the amount of this product you would -like to product per second. Leave the box empty to set no limit. -

    - - - ); -} \ No newline at end of file + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) limitProductProduction(); + } + + function onChange(event: React.ChangeEvent): void { + if (event.target.value === "") setLimit(null); + else setLimit(parseFloat(event.target.value)); + } + + return ( + <> +

    + Enter a limit to the amount of this product you would like to product + per second. Leave the box empty to set no limit. +

    + + + + ); +} diff --git a/src/Corporation/ui/MainPanel.tsx b/src/Corporation/ui/MainPanel.tsx index aa353291a..aae287918 100644 --- a/src/Corporation/ui/MainPanel.tsx +++ b/src/Corporation/ui/MainPanel.tsx @@ -15,82 +15,86 @@ import { ICorporation } from "../ICorporation"; import { CorporationRouting } from "./Routing"; interface IProps { - corp: ICorporation; - routing: CorporationRouting; - player: IPlayer; + corp: ICorporation; + routing: CorporationRouting; + player: IPlayer; } export function MainPanel(props: IProps): React.ReactElement { - const [division, setDivision] = useState(""); - const [city, setCity] = useState(CityName.Sector12); + const [division, setDivision] = useState(""); + const [city, setCity] = useState(CityName.Sector12); - // We can pass this setter to child components - function changeCityState(newCity: string): void { - if (Object.values(CityName).includes(newCity as CityName)) { - setCity(newCity); - } else { - console.error(`Tried to change MainPanel's city state to an invalid city: ${newCity}`); - } - } - - function renderOverviewPage(): React.ReactElement { - return ( -
    - -
    - ) - } - - function renderDivisionPage(): React.ReactElement { - // Note: Division is the same thing as Industry...I wasn't consistent with naming - const division = props.routing.currentDivision; - if (division == null) { - throw new Error(`Routing does not hold reference to the current Industry`); - } - - // City tabs - const onClicks: { [key: string]: () => void } = {}; - for (const cityName in division.offices) { - if (division.offices[cityName] instanceof OfficeSpace) { - onClicks[cityName] = () => { - setCity(cityName); - props.corp.rerender(props.player); - } - } - } - - const cityTabs = ( - - ) - - return ( -
    - {cityTabs} - -
    - ) - } - - if (props.routing.isOnOverviewPage()) { - // Corporation overview Content - return renderOverviewPage(); + // We can pass this setter to child components + function changeCityState(newCity: string): void { + if (Object.values(CityName).includes(newCity as CityName)) { + setCity(newCity); } else { - // Division content - - // First, check if we're at a new division. If so, we need to reset the city to Sector-12 - // Otherwise, just switch the 'city' state - const currentDivision = props.routing.current(); - if (currentDivision !== division) { - setDivision(currentDivision); - setCity(CityName.Sector12); - } - - return renderDivisionPage(); + console.error( + `Tried to change MainPanel's city state to an invalid city: ${newCity}`, + ); } + } + + function renderOverviewPage(): React.ReactElement { + return ( +
    + +
    + ); + } + + function renderDivisionPage(): React.ReactElement { + // Note: Division is the same thing as Industry...I wasn't consistent with naming + const division = props.routing.currentDivision; + if (division == null) { + throw new Error( + `Routing does not hold reference to the current Industry`, + ); + } + + // City tabs + const onClicks: { [key: string]: () => void } = {}; + for (const cityName in division.offices) { + if (division.offices[cityName] instanceof OfficeSpace) { + onClicks[cityName] = () => { + setCity(cityName); + props.corp.rerender(props.player); + }; + } + } + + const cityTabs = ( + + ); + + return ( +
    + {cityTabs} + +
    + ); + } + + if (props.routing.isOnOverviewPage()) { + // Corporation overview Content + return renderOverviewPage(); + } else { + // Division content + + // First, check if we're at a new division. If so, we need to reset the city to Sector-12 + // Otherwise, just switch the 'city' state + const currentDivision = props.routing.current(); + if (currentDivision !== division) { + setDivision(currentDivision); + setCity(CityName.Sector12); + } + + return renderDivisionPage(); + } } diff --git a/src/Corporation/ui/MakeProductPopup.tsx b/src/Corporation/ui/MakeProductPopup.tsx index a3806cec1..09c284df3 100644 --- a/src/Corporation/ui/MakeProductPopup.tsx +++ b/src/Corporation/ui/MakeProductPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { Industries } from "../IndustryData"; @@ -7,96 +7,135 @@ import { ICorporation } from "../ICorporation"; import { IIndustry } from "../IIndustry"; interface IProps { - popupText: string; - division: IIndustry; - corp: ICorporation; - popupId: string; + popupText: string; + division: IIndustry; + corp: ICorporation; + popupId: string; } function productPlaceholder(tpe: string): string { - if (tpe === Industries.Food) { - return "Restaurant Name"; - } else if (tpe === Industries.Healthcare) { - return "Hospital Name"; - } else if (tpe === Industries.RealEstate) { - return "Property Name"; - } - return "Product Name"; + if (tpe === Industries.Food) { + return "Restaurant Name"; + } else if (tpe === Industries.Healthcare) { + return "Hospital Name"; + } else if (tpe === Industries.RealEstate) { + return "Property Name"; + } + return "Product Name"; } // Create a popup that lets the player create a product for their current industry export function MakeProductPopup(props: IProps): React.ReactElement { - const allCities = Object.keys(props.division.offices) - .filter((cityName: string) => props.division.offices[cityName] !== 0); - const [city, setCity] = useState(allCities.length > 0 ? allCities[0] : ''); - const [name, setName] = useState(''); - const [design, setDesign] = useState(null); - const [marketing, setMarketing] = useState(null); - if (props.division.hasMaximumNumberProducts()) return (<>); + const allCities = Object.keys(props.division.offices).filter( + (cityName: string) => props.division.offices[cityName] !== 0, + ); + const [city, setCity] = useState(allCities.length > 0 ? allCities[0] : ""); + const [name, setName] = useState(""); + const [design, setDesign] = useState(null); + const [marketing, setMarketing] = useState(null); + if (props.division.hasMaximumNumberProducts()) return <>; - function makeProduct(): void { - let designInvest = design; - let marketingInvest = marketing; - if (designInvest == null || designInvest < 0) { designInvest = 0; } - if (marketingInvest == null || marketingInvest < 0) { marketingInvest = 0; } - if (name == null || name === "") { - dialogBoxCreate("You must specify a name for your product!"); - } else if (isNaN(designInvest)) { - dialogBoxCreate("Invalid value for design investment"); - } else if (isNaN(marketingInvest)) { - dialogBoxCreate("Invalid value for marketing investment"); - } else if (props.corp.funds.lt(designInvest + marketingInvest)) { - dialogBoxCreate("You don't have enough company funds to make this large of an investment"); - } else { - const product = new Product({ - name: name.replace(/[<>]/g, ''), //Sanitize for HTMl elements - createCity: city, - designCost: designInvest, - advCost: marketingInvest, - }); - if (props.division.products[product.name] instanceof Product) { - dialogBoxCreate(`You already have a product with this name!`); - return; - } - props.corp.funds = props.corp.funds.minus(designInvest + marketingInvest); - props.division.products[product.name] = product; - removePopup(props.popupId); - } + function makeProduct(): void { + let designInvest = design; + let marketingInvest = marketing; + if (designInvest == null || designInvest < 0) { + designInvest = 0; } - - function onCityChange(event: React.ChangeEvent): void { - setCity(event.target.value); + if (marketingInvest == null || marketingInvest < 0) { + marketingInvest = 0; } - - function onProductNameChange(event: React.ChangeEvent): void { - setName(event.target.value); + if (name == null || name === "") { + dialogBoxCreate("You must specify a name for your product!"); + } else if (isNaN(designInvest)) { + dialogBoxCreate("Invalid value for design investment"); + } else if (isNaN(marketingInvest)) { + dialogBoxCreate("Invalid value for marketing investment"); + } else if (props.corp.funds.lt(designInvest + marketingInvest)) { + dialogBoxCreate( + "You don't have enough company funds to make this large of an investment", + ); + } else { + const product = new Product({ + name: name.replace(/[<>]/g, ""), //Sanitize for HTMl elements + createCity: city, + designCost: designInvest, + advCost: marketingInvest, + }); + if (props.division.products[product.name] instanceof Product) { + dialogBoxCreate(`You already have a product with this name!`); + return; + } + props.corp.funds = props.corp.funds.minus(designInvest + marketingInvest); + props.division.products[product.name] = product; + removePopup(props.popupId); } + } - function onDesignChange(event: React.ChangeEvent): void { - if(event.target.value === "") setDesign(null); - else setDesign(parseFloat(event.target.value)); - } + function onCityChange(event: React.ChangeEvent): void { + setCity(event.target.value); + } - function onMarketingChange(event: React.ChangeEvent): void { - if(event.target.value === "") setMarketing(null); - else setMarketing(parseFloat(event.target.value)); - } + function onProductNameChange( + event: React.ChangeEvent, + ): void { + setName(event.target.value); + } - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) makeProduct(); - } + function onDesignChange(event: React.ChangeEvent): void { + if (event.target.value === "") setDesign(null); + else setDesign(parseFloat(event.target.value)); + } - return (<> -

    - - -
    - - - - ); + function onMarketingChange(event: React.ChangeEvent): void { + if (event.target.value === "") setMarketing(null); + else setMarketing(parseFloat(event.target.value)); + } + + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) makeProduct(); + } + + return ( + <> +

    + + +
    + + + + + ); } diff --git a/src/Corporation/ui/MaterialMarketTaPopup.tsx b/src/Corporation/ui/MaterialMarketTaPopup.tsx index 94af5f78c..c03c4e196 100644 --- a/src/Corporation/ui/MaterialMarketTaPopup.tsx +++ b/src/Corporation/ui/MaterialMarketTaPopup.tsx @@ -1,116 +1,152 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { numeralWrapper } from "../../ui/numeralFormat"; import { IIndustry } from "../IIndustry"; import { ICorporation } from "../ICorporation"; import { Material } from "../Material"; interface IMarketTA2Props { - industry: IIndustry; - mat: Material; + industry: IIndustry; + mat: Material; } function MarketTA2(props: IMarketTA2Props): React.ReactElement { - if(!props.industry.hasResearch("Market-TA.II")) return (<>); + if (!props.industry.hasResearch("Market-TA.II")) return <>; - const [newCost, setNewCost] = useState(props.mat.bCost); - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender(old => !old); - } - const markupLimit = props.mat.getMarkupLimit(); - - function onChange(event: React.ChangeEvent): void { - if(event.target.value === "") setNewCost(0); - else setNewCost(parseFloat(event.target.value)); - } + const [newCost, setNewCost] = useState(props.mat.bCost); + const setRerender = useState(false)[1]; + function rerender(): void { + setRerender((old) => !old); + } + const markupLimit = props.mat.getMarkupLimit(); - const sCost = newCost; - let markup = 1; - if (sCost > props.mat.bCost) { - //Penalty if difference between sCost and bCost is greater than markup limit - if ((sCost - props.mat.bCost) > markupLimit) { - markup = Math.pow(markupLimit / (sCost - props.mat.bCost), 2); - } - } else if (sCost < props.mat.bCost) { - if (sCost <= 0) { - markup = 1e12; //Sell everything, essentially discard - } else { - //Lower prices than market increases sales - markup = props.mat.bCost / sCost; - } - } + function onChange(event: React.ChangeEvent): void { + if (event.target.value === "") setNewCost(0); + else setNewCost(parseFloat(event.target.value)); + } - function onMarketTA2(event: React.ChangeEvent): void { - props.mat.marketTa2 = event.target.checked; - rerender(); + const sCost = newCost; + let markup = 1; + if (sCost > props.mat.bCost) { + //Penalty if difference between sCost and bCost is greater than markup limit + if (sCost - props.mat.bCost > markupLimit) { + markup = Math.pow(markupLimit / (sCost - props.mat.bCost), 2); } + } else if (sCost < props.mat.bCost) { + if (sCost <= 0) { + markup = 1e12; //Sell everything, essentially discard + } else { + //Lower prices than market increases sales + markup = props.mat.bCost / sCost; + } + } - return (<> -

    -
    Market-TA.II
    - If you sell at {numeralWrapper.formatMoney(sCost)}, - then you will sell {numeralWrapper.format(markup, "0.00000")}x as much compared - to if you sold at market price. -

    - -
    - - -
    -

    - Note that Market-TA.II overrides Market-TA.I. This means that if - both are enabled, then Market-TA.II will take effect, not Market-TA.I -

    - ); + function onMarketTA2(event: React.ChangeEvent): void { + props.mat.marketTa2 = event.target.checked; + rerender(); + } + + return ( + <> +

    +
    + + Market-TA.II + +
    + If you sell at {numeralWrapper.formatMoney(sCost)}, then you will sell{" "} + {numeralWrapper.format(markup, "0.00000")}x as much compared to if you + sold at market price. +

    + +
    + + +
    +

    + Note that Market-TA.II overrides Market-TA.I. This means that if both + are enabled, then Market-TA.II will take effect, not Market-TA.I +

    + + ); } interface IProps { - mat: Material; - industry: IIndustry; - corp: ICorporation; - popupId: string; + mat: Material; + industry: IIndustry; + corp: ICorporation; + popupId: string; } // Create a popup that lets the player use the Market TA research for Materials export function MaterialMarketTaPopup(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender(old => !old); - } - const markupLimit = props.mat.getMarkupLimit(); + const setRerender = useState(false)[1]; + function rerender(): void { + setRerender((old) => !old); + } + const markupLimit = props.mat.getMarkupLimit(); - function onMarketTA1(event: React.ChangeEvent): void { - props.mat.marketTa1 = event.target.checked; - rerender(); - } - - return (<> -

    - Market-TA.I
    - The maximum sale price you can mark this up to - is {numeralWrapper.formatMoney(props.mat.bCost + markupLimit)}. - This means that if you set the sale price higher than this, - you will begin to experience a loss in number of sales -

    -
    - - -
    - - ); + function onMarketTA1(event: React.ChangeEvent): void { + props.mat.marketTa1 = event.target.checked; + rerender(); + } + return ( + <> +

    + + Market-TA.I + +
    + The maximum sale price you can mark this up to is{" "} + {numeralWrapper.formatMoney(props.mat.bCost + markupLimit)}. This means + that if you set the sale price higher than this, you will begin to + experience a loss in number of sales +

    +
    + + +
    + + + ); } diff --git a/src/Corporation/ui/NewIndustryPopup.tsx b/src/Corporation/ui/NewIndustryPopup.tsx index 8fa4f0aff..bfe112dd0 100644 --- a/src/Corporation/ui/NewIndustryPopup.tsx +++ b/src/Corporation/ui/NewIndustryPopup.tsx @@ -1,10 +1,11 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { - Industries, - IndustryStartingCosts, - IndustryDescriptions } from "../IndustryData"; + Industries, + IndustryStartingCosts, + IndustryDescriptions, +} from "../IndustryData"; import { Industry } from "../Industry"; import { ICorporation } from "../ICorporation"; import { IIndustry } from "../IIndustry"; @@ -12,57 +13,86 @@ import { CorporationRouting } from "./Routing"; import { NewIndustry } from "../Actions"; interface IProps { - corp: ICorporation; - popupId: string; - routing: CorporationRouting; + corp: ICorporation; + popupId: string; + routing: CorporationRouting; } // Create a popup that lets the player create a new industry. // This is created when the player clicks the "Expand into new Industry" header tab export function NewIndustryPopup(props: IProps): React.ReactElement { - const allIndustries = Object.keys(Industries).sort(); - const possibleIndustries = allIndustries.filter((industryType: string) => props.corp.divisions.find((division: IIndustry) => division.type === industryType) === undefined).sort(); - const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : ''); - const [name, setName] = useState(''); + const allIndustries = Object.keys(Industries).sort(); + const possibleIndustries = allIndustries + .filter( + (industryType: string) => + props.corp.divisions.find( + (division: IIndustry) => division.type === industryType, + ) === undefined, + ) + .sort(); + const [industry, setIndustry] = useState( + possibleIndustries.length > 0 ? possibleIndustries[0] : "", + ); + const [name, setName] = useState(""); - function newIndustry(): void { - try { - NewIndustry(props.corp, industry, name); - } catch(err) { - dialogBoxCreate(err+''); - return; - } - - // Set routing to the new division so that the UI automatically switches to it - props.routing.routeTo(name); - - removePopup(props.popupId); + function newIndustry(): void { + try { + NewIndustry(props.corp, industry, name); + } catch (err) { + dialogBoxCreate(err + ""); + return; } - function onNameChange(event: React.ChangeEvent): void { - setName(event.target.value); - } + // Set routing to the new division so that the UI automatically switches to it + props.routing.routeTo(name); - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) newIndustry(); - } + removePopup(props.popupId); + } - function onIndustryChange(event: React.ChangeEvent): void { - setIndustry(event.target.value); - } + function onNameChange(event: React.ChangeEvent): void { + setName(event.target.value); + } - return (<> -

    Create a new division to expand into a new industry:

    - -

    {IndustryDescriptions[industry]}

    -

    + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) newIndustry(); + } -

    Division name:

    - - Create Division - ); + function onIndustryChange(event: React.ChangeEvent): void { + setIndustry(event.target.value); + } + return ( + <> +

    Create a new division to expand into a new industry:

    + +

    {IndustryDescriptions[industry]}

    +
    +
    + +

    Division name:

    + + + Create Division + + + ); } diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx index 3124c118f..714d04c4b 100644 --- a/src/Corporation/ui/Overview.tsx +++ b/src/Corporation/ui/Overview.tsx @@ -1,22 +1,24 @@ // React Component for displaying Corporation Overview info import React from "react"; -import { LevelableUpgrade } from "./LevelableUpgrade"; -import { UnlockUpgrade } from "./UnlockUpgrade"; -import { BribeFactionPopup } from "./BribeFactionPopup"; -import { SellSharesPopup } from "./SellSharesPopup"; -import { BuybackSharesPopup } from "./BuybackSharesPopup"; -import { IssueDividendsPopup } from "./IssueDividendsPopup"; -import { IssueNewSharesPopup } from "./IssueNewSharesPopup"; -import { FindInvestorsPopup } from "./FindInvestorsPopup"; -import { GoPublicPopup } from "./GoPublicPopup"; +import { LevelableUpgrade } from "./LevelableUpgrade"; +import { UnlockUpgrade } from "./UnlockUpgrade"; +import { BribeFactionPopup } from "./BribeFactionPopup"; +import { SellSharesPopup } from "./SellSharesPopup"; +import { BuybackSharesPopup } from "./BuybackSharesPopup"; +import { IssueDividendsPopup } from "./IssueDividendsPopup"; +import { IssueNewSharesPopup } from "./IssueNewSharesPopup"; +import { FindInvestorsPopup } from "./FindInvestorsPopup"; +import { GoPublicPopup } from "./GoPublicPopup"; -import { CorporationConstants } from "../data/Constants"; +import { CorporationConstants } from "../data/Constants"; import { - CorporationUnlockUpgrade, - CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades"; + CorporationUnlockUpgrade, + CorporationUnlockUpgrades, +} from "../data/CorporationUnlockUpgrades"; import { - CorporationUpgrade, - CorporationUpgrades } from "../data/CorporationUpgrades"; + CorporationUpgrade, + CorporationUpgrades, +} from "../data/CorporationUpgrades"; import { CONSTANTS } from "../../Constants"; import { numeralWrapper } from "../../ui/numeralFormat"; @@ -27,340 +29,423 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; import { ICorporation } from "../ICorporation"; interface IProps { - corp: ICorporation; - player: IPlayer; + corp: ICorporation; + player: IPlayer; } interface GeneralBtns { - bribeFactions: React.ReactElement; + bribeFactions: React.ReactElement; } export function Overview(props: IProps): React.ReactElement { - // Generic Function for Creating a button - interface ICreateButtonProps { - text: string; - class?: string; - className?: string; - display?: string; - tooltip?: string; - onClick?: (event: React.MouseEvent) => void; + // Generic Function for Creating a button + interface ICreateButtonProps { + text: string; + class?: string; + className?: string; + display?: string; + tooltip?: string; + onClick?: (event: React.MouseEvent) => void; + } + + function Button(props: ICreateButtonProps): React.ReactElement { + let className = props.className ? props.className : "std-button"; + const hasTooltip = props.tooltip != null; + if (hasTooltip) className += " tooltip"; + return ( + + {props.text} + {hasTooltip && {props.tooltip}} + + ); + } + + function createButton(props: ICreateButtonProps): React.ReactElement { + let className = props.class ? props.class : "std-button"; + const displayStyle = props.display ? props.display : "block"; + const hasTooltip = props.tooltip != null; + if (hasTooltip) { + className += " tooltip"; } - function Button(props: ICreateButtonProps): React.ReactElement { - let className = props.className ? props.className : "std-button"; - const hasTooltip = props.tooltip != null; - if(hasTooltip) className += " tooltip"; - return ( - - {props.text} - { - hasTooltip && - - {props.tooltip} - - } - - ); - } - - function createButton(props: ICreateButtonProps): React.ReactElement { - let className = props.class ? props.class : "std-button"; - const displayStyle = props.display ? props.display : "block"; - const hasTooltip = (props.tooltip != null); - if (hasTooltip) { - className += " tooltip"; - } - - return ( - - {props.text} - { - hasTooltip && - - {props.tooltip} - - } - - ) - } - - function openBribeFactionPopup(): void { - const popupId = "corp-bribe-popup"; - createPopup(popupId, BribeFactionPopup, { - player: props.player, - popupId: popupId, - corp: props.corp, - }); - } - - const profit: number = props.corp.revenue.minus(props.corp.expenses).toNumber(); - - function DividendsStats() { - if(props.corp.dividendPercentage <= 0 || profit <= 0) return (<>); - const totalDividends = (props.corp.dividendPercentage / 100) * profit; - const retainedEarnings = profit - totalDividends; - const dividendsPerShare = totalDividends / props.corp.totalShares; - const playerEarnings = props.corp.numShares * dividendsPerShare; - return (<> - Retained Profits (after dividends): / s

    - Dividend Percentage: {numeralWrapper.format(props.corp.dividendPercentage / 100, "0%")}
    - Dividends per share: / s
    - Your earnings as a shareholder (Pre-Tax): / s
    - Dividend Tax Rate: {props.corp.dividendTaxPercentage}%
    - Your earnings as a shareholder (Post-Tax): / s

    - ); - } - - function Mult(props: {name: string; mult: number}): React.ReactElement { - if(props.mult <= 1) return (<>); - return (

    {props.name}{numeralWrapper.format(props.mult, "0.000")}

    ); - } - - // Returns a string with general information about Corporation - function BonusTime(): React.ReactElement { - const storedTime = props.corp.storedCycles * CONSTANTS.MilliPerCycle; - if (storedTime <= 15000) return (<>); - return (

    Bonus time: {convertTimeMsToTimeElapsedString(storedTime)}

    ); - } - - function BribeButton(): React.ReactElement { - const canBribe = (props.corp.determineValuation() >= CorporationConstants.BribeThreshold) || true; - const bribeFactionsClass = (canBribe ? "a-link-button" : "a-link-button-inactive"); - return
    -
    - -
    - ) + + {props.text} + {hasTooltip && {props.tooltip}} + + ); + } + + function openBribeFactionPopup(): void { + const popupId = "corp-bribe-popup"; + createPopup(popupId, BribeFactionPopup, { + player: props.player, + popupId: popupId, + corp: props.corp, + }); + } + + const profit: number = props.corp.revenue + .minus(props.corp.expenses) + .toNumber(); + + function DividendsStats() { + if (props.corp.dividendPercentage <= 0 || profit <= 0) return <>; + const totalDividends = (props.corp.dividendPercentage / 100) * profit; + const retainedEarnings = profit - totalDividends; + const dividendsPerShare = totalDividends / props.corp.totalShares; + const playerEarnings = props.corp.numShares * dividendsPerShare; + return ( + <> + Retained Profits (after dividends): / + s
    +
    + Dividend Percentage:{" "} + {numeralWrapper.format(props.corp.dividendPercentage / 100, "0%")} +
    + Dividends per share: / s
    + Your earnings as a shareholder (Pre-Tax):{" "} + / s
    + Dividend Tax Rate: {props.corp.dividendTaxPercentage}%
    + Your earnings as a shareholder (Post-Tax):{" "} + {" "} + / s
    +
    + + ); + } + + function Mult(props: { name: string; mult: number }): React.ReactElement { + if (props.mult <= 1) return <>; + return ( +

    + {props.name} + {numeralWrapper.format(props.mult, "0.000")} +
    +

    + ); + } + + // Returns a string with general information about Corporation + function BonusTime(): React.ReactElement { + const storedTime = props.corp.storedCycles * CONSTANTS.MilliPerCycle; + if (storedTime <= 15000) return <>; + return ( +

    + Bonus time: {convertTimeMsToTimeElapsedString(storedTime)} +
    +
    +

    + ); + } + + function BribeButton(): React.ReactElement { + const canBribe = + props.corp.determineValuation() >= CorporationConstants.BribeThreshold || + true; + const bribeFactionsClass = canBribe + ? "a-link-button" + : "a-link-button-inactive"; + return ( +
    +
    + + + ); } diff --git a/src/Corporation/ui/ProductMarketTaPopup.tsx b/src/Corporation/ui/ProductMarketTaPopup.tsx index be00d9131..a649f25f5 100644 --- a/src/Corporation/ui/ProductMarketTaPopup.tsx +++ b/src/Corporation/ui/ProductMarketTaPopup.tsx @@ -1,99 +1,142 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { numeralWrapper } from "../../ui/numeralFormat"; import { IIndustry } from "../IIndustry"; import { Product } from "../Product"; interface IProps { - product: Product; - industry: IIndustry; - popupId: string; + product: Product; + industry: IIndustry; + popupId: string; } function MarketTA2(props: IProps): React.ReactElement { - const markupLimit = props.product.rat / props.product.mku; - const [value, setValue] = useState(props.product.pCost); - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender(old => !old); + const markupLimit = props.product.rat / props.product.mku; + const [value, setValue] = useState(props.product.pCost); + const setRerender = useState(false)[1]; + function rerender(): void { + setRerender((old) => !old); + } + + function onChange(event: React.ChangeEvent): void { + setValue(parseFloat(event.target.value)); + } + + function onCheckedChange(event: React.ChangeEvent): void { + props.product.marketTa2 = event.target.checked; + rerender(); + } + + const sCost = value; + let markup = 1; + if (sCost > props.product.pCost) { + if (sCost - props.product.pCost > markupLimit) { + markup = markupLimit / (sCost - props.product.pCost); } + } - function onChange(event: React.ChangeEvent): void { - setValue(parseFloat(event.target.value)); - } - - - function onCheckedChange(event: React.ChangeEvent): void { - props.product.marketTa2 = event.target.checked; - rerender(); - } - - const sCost = value; - let markup = 1; - if (sCost > props.product.pCost) { - if ((sCost - props.product.pCost) > markupLimit) { - markup = markupLimit / (sCost - props.product.pCost); - } - } - - return (<> -

    -
    Market-TA.II
    - If you sell at {numeralWrapper.formatMoney(sCost)}, - then you will sell {numeralWrapper.format(markup, "0.00000")}x as much compared - to if you sold at market price. -

    - -
    - - -
    -

    - Note that Market-TA.II overrides Market-TA.I. This means that if - both are enabled, then Market-TA.II will take effect, not Market-TA.I -

    - ); + return ( + <> +

    +
    + + Market-TA.II + +
    + If you sell at {numeralWrapper.formatMoney(sCost)}, then you will sell{" "} + {numeralWrapper.format(markup, "0.00000")}x as much compared to if you + sold at market price. +

    + +
    + + +
    +

    + Note that Market-TA.II overrides Market-TA.I. This means that if both + are enabled, then Market-TA.II will take effect, not Market-TA.I +

    + + ); } // Create a popup that lets the player use the Market TA research for Products export function ProductMarketTaPopup(props: IProps): React.ReactElement { - const markupLimit = props.product.rat / props.product.mku; - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender(old => !old); - } + const markupLimit = props.product.rat / props.product.mku; + const setRerender = useState(false)[1]; + function rerender(): void { + setRerender((old) => !old); + } - function onChange(event: React.ChangeEvent): void { - props.product.marketTa1 = event.target.checked; - rerender(); - } + function onChange(event: React.ChangeEvent): void { + props.product.marketTa1 = event.target.checked; + rerender(); + } - return (<> -

    - Market-TA.I
    - The maximum sale price you can mark this up to - is {numeralWrapper.formatMoney(props.product.pCost + markupLimit)}. - This means that if you set the sale price higher than this, - you will begin to experience a loss in number of sales. -

    -
    - - -
    - {props.industry.hasResearch("Market-TA.II") && } - ); + return ( + <> +

    + + Market-TA.I + +
    + The maximum sale price you can mark this up to is{" "} + {numeralWrapper.formatMoney(props.product.pCost + markupLimit)}. This + means that if you set the sale price higher than this, you will begin to + experience a loss in number of sales. +

    +
    + + +
    + {props.industry.hasResearch("Market-TA.II") && ( + + )} + + ); } diff --git a/src/Corporation/ui/PurchaseMaterialPopup.tsx b/src/Corporation/ui/PurchaseMaterialPopup.tsx index 19166a030..3b4a62b9e 100644 --- a/src/Corporation/ui/PurchaseMaterialPopup.tsx +++ b/src/Corporation/ui/PurchaseMaterialPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { MaterialSizes } from "../MaterialSizes"; @@ -10,121 +10,161 @@ import { numeralWrapper } from "../../ui/numeralFormat"; import { BuyMaterial } from "../Actions"; interface IBulkPurchaseTextProps { - warehouse: Warehouse; - mat: Material; - amount: string; + warehouse: Warehouse; + mat: Material; + amount: string; } function BulkPurchaseText(props: IBulkPurchaseTextProps): React.ReactElement { - const parsedAmt = parseFloat(props.amount); - const cost = parsedAmt * props.mat.bCost; + const parsedAmt = parseFloat(props.amount); + const cost = parsedAmt * props.mat.bCost; - const matSize = MaterialSizes[props.mat.name]; - const maxAmount = ((props.warehouse.size - props.warehouse.sizeUsed) / matSize); + const matSize = MaterialSizes[props.mat.name]; + const maxAmount = (props.warehouse.size - props.warehouse.sizeUsed) / matSize; - if (parsedAmt * matSize > maxAmount) { - return (<>Not enough warehouse space to purchase this amount); - } else if (isNaN(cost)) { - return (<>Invalid put for Bulk Purchase amount); - } else { - return (<>Purchasing {numeralWrapper.format(parsedAmt, "0,0.00")} of - {props.mat.name} will cost {numeralWrapper.formatMoney(cost)}); - } + if (parsedAmt * matSize > maxAmount) { + return <>Not enough warehouse space to purchase this amount; + } else if (isNaN(cost)) { + return <>Invalid put for Bulk Purchase amount; + } else { + return ( + <> + Purchasing {numeralWrapper.format(parsedAmt, "0,0.00")} of + {props.mat.name} will cost {numeralWrapper.formatMoney(cost)} + + ); + } } interface IProps { - mat: Material; - industry: IIndustry; - warehouse: Warehouse; - corp: ICorporation; - popupId: string; + mat: Material; + industry: IIndustry; + warehouse: Warehouse; + corp: ICorporation; + popupId: string; } function BulkPurchase(props: IProps): React.ReactElement { - const [buyAmt, setBuyAmt] = useState(''); + const [buyAmt, setBuyAmt] = useState(""); - function bulkPurchase(): void { - const amount = parseFloat(buyAmt); + function bulkPurchase(): void { + const amount = parseFloat(buyAmt); - const matSize = MaterialSizes[props.mat.name]; - const maxAmount = ((props.warehouse.size - props.warehouse.sizeUsed) / matSize); - if (amount * matSize > maxAmount) { - dialogBoxCreate(`You do not have enough warehouse size to fit this purchase`); - return; - } - - if (isNaN(amount)) { - dialogBoxCreate("Invalid input amount"); - } else { - const cost = amount * props.mat.bCost; - if (props.corp.funds.gt(cost)) { - props.corp.funds = props.corp.funds.minus(cost); - props.mat.qty += amount; - } else { - dialogBoxCreate(`You cannot afford this purchase.`); - return; - } - - removePopup(props.popupId); - } + const matSize = MaterialSizes[props.mat.name]; + const maxAmount = + (props.warehouse.size - props.warehouse.sizeUsed) / matSize; + if (amount * matSize > maxAmount) { + dialogBoxCreate( + `You do not have enough warehouse size to fit this purchase`, + ); + return; } - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) bulkPurchase(); - } + if (isNaN(amount)) { + dialogBoxCreate("Invalid input amount"); + } else { + const cost = amount * props.mat.bCost; + if (props.corp.funds.gt(cost)) { + props.corp.funds = props.corp.funds.minus(cost); + props.mat.qty += amount; + } else { + dialogBoxCreate(`You cannot afford this purchase.`); + return; + } - function onChange(event: React.ChangeEvent): void { - setBuyAmt(event.target.value); + removePopup(props.popupId); } + } - return (<> -

    - Enter the amount of {props.mat.name} you would like - to bulk purchase. This purchases the specified amount instantly - (all at once). -

    - - - - ); + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) bulkPurchase(); + } + + function onChange(event: React.ChangeEvent): void { + setBuyAmt(event.target.value); + } + + return ( + <> +

    + Enter the amount of {props.mat.name} you would like to bulk purchase. + This purchases the specified amount instantly (all at once). +

    + + + + + ); } // Create a popup that lets the player purchase a Material export function PurchaseMaterialPopup(props: IProps): React.ReactElement { - const [buyAmt, setBuyAmt] = useState(props.mat.buy ? props.mat.buy : null); - - function purchaseMaterial(): void { - if(buyAmt === null) return; - try { - BuyMaterial(props.mat, buyAmt) - } catch(err) { - dialogBoxCreate(err+''); - } + const [buyAmt, setBuyAmt] = useState(props.mat.buy ? props.mat.buy : null); - removePopup(props.popupId); + function purchaseMaterial(): void { + if (buyAmt === null) return; + try { + BuyMaterial(props.mat, buyAmt); + } catch (err) { + dialogBoxCreate(err + ""); } - function clearPurchase(): void { - props.mat.buy = 0; - removePopup(props.popupId); - } + removePopup(props.popupId); + } - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) purchaseMaterial(); - } + function clearPurchase(): void { + props.mat.buy = 0; + removePopup(props.popupId); + } - function onChange(event: React.ChangeEvent): void { - setBuyAmt(parseFloat(event.target.value)); - } + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) purchaseMaterial(); + } - return (<> -

    - Enter the amount of {props.mat.name} you would like - to purchase per second. This material's cost changes constantly. -

    - - - - {props.industry.hasResearch("Bulk Purchasing") && } - ); + function onChange(event: React.ChangeEvent): void { + setBuyAmt(parseFloat(event.target.value)); + } + + return ( + <> +

    + Enter the amount of {props.mat.name} you would like to purchase per + second. This material's cost changes constantly. +

    + + + + {props.industry.hasResearch("Bulk Purchasing") && ( + + )} + + ); } diff --git a/src/Corporation/ui/ResearchPopup.tsx b/src/Corporation/ui/ResearchPopup.tsx index 72010b249..433115b45 100644 --- a/src/Corporation/ui/ResearchPopup.tsx +++ b/src/Corporation/ui/ResearchPopup.tsx @@ -1,101 +1,110 @@ -import React, { useEffect } from 'react'; +import React, { useEffect } from "react"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { createElement } from "../../../utils/uiHelpers/createElement"; import { removePopup } from "../../ui/React/createPopup"; import { IndustryResearchTrees } from "../IndustryData"; import { CorporationConstants } from "../data/Constants"; import { ResearchMap } from "../ResearchMap"; -import { Treant } from 'treant-js'; +import { Treant } from "treant-js"; import { IIndustry } from "../IIndustry"; interface IProps { - industry: IIndustry; - popupId: string; + industry: IIndustry; + popupId: string; } // Create the Research Tree UI for this Industry export function ResearchPopup(props: IProps): React.ReactElement { - useEffect(() => { - const researchTree = IndustryResearchTrees[props.industry.type]; - if(researchTree === undefined) return; + useEffect(() => { + const researchTree = IndustryResearchTrees[props.industry.type]; + if (researchTree === undefined) return; - // Get the tree's markup (i.e. config) for Treant - const markup = researchTree.createTreantMarkup(); - markup.chart.container = "#" + props.popupId + "-content"; - markup.chart.nodeAlign = "BOTTOM"; - markup.chart.rootOrientation = "WEST"; - markup.chart.siblingSeparation = 40; - markup.chart.connectors = { - type: "step", - style: { - "arrow-end": "block-wide-long", - "stroke": "white", - "stroke-width": 2, - }, + // Get the tree's markup (i.e. config) for Treant + const markup = researchTree.createTreantMarkup(); + markup.chart.container = "#" + props.popupId + "-content"; + markup.chart.nodeAlign = "BOTTOM"; + markup.chart.rootOrientation = "WEST"; + markup.chart.siblingSeparation = 40; + markup.chart.connectors = { + type: "step", + style: { + "arrow-end": "block-wide-long", + stroke: "white", + "stroke-width": 2, + }, + }; + + Treant(markup); + + // Add Event Listeners for all Nodes + const allResearch = researchTree.getAllNodes(); + for (let i = 0; i < allResearch.length; ++i) { + // If this is already Researched, skip it + if (props.industry.researched[allResearch[i]] === true) { + continue; + } + + // Get the Research object + const research = ResearchMap[allResearch[i]]; + + // Get the DOM Element to add a click listener to it + const sanitizedName = allResearch[i].replace(/\s/g, ""); + const div = document.getElementById( + sanitizedName + "-corp-research-click-listener", + ); + if (div == null) { + console.warn(`Could not find Research Tree div for ${sanitizedName}`); + continue; + } + + div.addEventListener("click", () => { + if (props.industry.sciResearch.qty >= research.cost) { + props.industry.sciResearch.qty -= research.cost; + + // Get the Node from the Research Tree and set its 'researched' property + researchTree.research(allResearch[i]); + props.industry.researched[allResearch[i]] = true; + + dialogBoxCreate( + `Researched ${allResearch[i]}. It may take a market cycle ` + + `(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` + + `the Research apply.`, + ); + removePopup(props.popupId); + } else { + dialogBoxCreate( + `You do not have enough Scientific Research for ${research.name}`, + ); } + }); + } - Treant(markup); + const boxContent = document.getElementById(`${props.popupId}-content`); + if (boxContent != null) { + // Add information about multipliers from research at the bottom of the popup + //appendLineBreaks(boxContent, 2); + boxContent.appendChild( + createElement("pre", { + display: "block", + innerText: + `Multipliers from research:\n` + + ` * Advertising Multiplier: x${researchTree.getAdvertisingMultiplier()}\n` + + ` * Employee Charisma Multiplier: x${researchTree.getEmployeeChaMultiplier()}\n` + + ` * Employee Creativity Multiplier: x${researchTree.getEmployeeCreMultiplier()}\n` + + ` * Employee Efficiency Multiplier: x${researchTree.getEmployeeEffMultiplier()}\n` + + ` * Employee Intelligence Multiplier: x${researchTree.getEmployeeIntMultiplier()}\n` + + ` * Production Multiplier: x${researchTree.getProductionMultiplier()}\n` + + ` * Sales Multiplier: x${researchTree.getSalesMultiplier()}\n` + + ` * Scientific Research Multiplier: x${researchTree.getScientificResearchMultiplier()}\n` + + ` * Storage Multiplier: x${researchTree.getStorageMultiplier()}`, + }), + ); + } + }); - // Add Event Listeners for all Nodes - const allResearch = researchTree.getAllNodes(); - for (let i = 0; i < allResearch.length; ++i) { - // If this is already Researched, skip it - if (props.industry.researched[allResearch[i]] === true) { - continue; - } - - // Get the Research object - const research = ResearchMap[allResearch[i]]; - - // Get the DOM Element to add a click listener to it - const sanitizedName = allResearch[i].replace(/\s/g, ''); - const div = document.getElementById(sanitizedName + "-corp-research-click-listener"); - if (div == null) { - console.warn(`Could not find Research Tree div for ${sanitizedName}`); - continue; - } - - div.addEventListener("click", () => { - if (props.industry.sciResearch.qty >= research.cost) { - props.industry.sciResearch.qty -= research.cost; - - // Get the Node from the Research Tree and set its 'researched' property - researchTree.research(allResearch[i]); - props.industry.researched[allResearch[i]] = true; - - dialogBoxCreate(`Researched ${allResearch[i]}. It may take a market cycle ` + - `(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` + - `the Research apply.`); - removePopup(props.popupId); - } else { - dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`); - } - }); - } - - - const boxContent = document.getElementById(`${props.popupId}-content`); - if (boxContent != null) { - // Add information about multipliers from research at the bottom of the popup - //appendLineBreaks(boxContent, 2); - boxContent.appendChild(createElement("pre", { - display: "block", - innerText: `Multipliers from research:\n` + - ` * Advertising Multiplier: x${researchTree.getAdvertisingMultiplier()}\n` + - ` * Employee Charisma Multiplier: x${researchTree.getEmployeeChaMultiplier()}\n` + - ` * Employee Creativity Multiplier: x${researchTree.getEmployeeCreMultiplier()}\n` + - ` * Employee Efficiency Multiplier: x${researchTree.getEmployeeEffMultiplier()}\n` + - ` * Employee Intelligence Multiplier: x${researchTree.getEmployeeIntMultiplier()}\n` + - ` * Production Multiplier: x${researchTree.getProductionMultiplier()}\n` + - ` * Sales Multiplier: x${researchTree.getSalesMultiplier()}\n` + - ` * Scientific Research Multiplier: x${researchTree.getScientificResearchMultiplier()}\n` + - ` * Storage Multiplier: x${researchTree.getStorageMultiplier()}`, - })); - } - }); - - return
    -
    + return ( +
    +
    - + ); } diff --git a/src/Corporation/ui/Root.tsx b/src/Corporation/ui/Root.tsx index f253e0157..0e9f58524 100644 --- a/src/Corporation/ui/Root.tsx +++ b/src/Corporation/ui/Root.tsx @@ -8,16 +8,24 @@ import { ICorporation } from "../ICorporation"; import { CorporationRouting } from "./Routing"; interface IProps { - corp: ICorporation; - routing: CorporationRouting; - player: IPlayer; + corp: ICorporation; + routing: CorporationRouting; + player: IPlayer; } export function CorporationRoot(props: IProps): React.ReactElement { - return ( -
    - - -
    - ) + return ( +
    + + +
    + ); } diff --git a/src/Corporation/ui/Routing.ts b/src/Corporation/ui/Routing.ts index b39d8d164..fe0d97083 100644 --- a/src/Corporation/ui/Routing.ts +++ b/src/Corporation/ui/Routing.ts @@ -7,77 +7,84 @@ export const overviewPage = "Overview"; * Keeps track of what content is currently being displayed for the Corporation UI */ export class CorporationRouting { - private currentPage: string = overviewPage; + private currentPage: string = overviewPage; - // Stores a reference to the Corporation instance - private corp: ICorporation; + // Stores a reference to the Corporation instance + private corp: ICorporation; - // Stores a reference to the Division instance that the routing is currently on - // This will be null if routing is on the overview page - currentDivision: IIndustry | null = null; + // Stores a reference to the Division instance that the routing is currently on + // This will be null if routing is on the overview page + currentDivision: IIndustry | null = null; - constructor(corp: ICorporation) { - this.corp = corp; + constructor(corp: ICorporation) { + this.corp = corp; + } + + current(): string { + return this.currentPage; + } + + /** + * Checks that the specified page has a valid value + */ + isValidPage(page: string): boolean { + if (page === overviewPage) { + return true; } - current(): string { - return this.currentPage; + for (const division of this.corp.divisions) { + if (division.name === page) { + return true; + } } - /** - * Checks that the specified page has a valid value - */ - isValidPage(page: string): boolean { - if (page === overviewPage) { return true; } + return false; + } - for (const division of this.corp.divisions) { - if (division.name === page) { return true; } + /** + * Returns a boolean indicating whether or not the player is on the given page + */ + isOn(page: string): boolean { + if (!this.isValidPage(page)) { + return false; + } + + return page === this.currentPage; + } + + isOnOverviewPage(): boolean { + return this.currentPage === overviewPage; + } + + /** + * Routes to the specified page + */ + routeTo(page: string): void { + if (!this.isValidPage(page)) { + return; + } + + this.currentDivision = null; + if (page !== overviewPage) { + // Iterate through Corporation data to get a reference to the current division + for (let i = 0; i < this.corp.divisions.length; ++i) { + if (this.corp.divisions[i].name === page) { + this.currentDivision = this.corp.divisions[i]; } + } - return false; + // 'currentDivision' should not be null, since the routing is either on + // the overview page or a division page + if (this.currentDivision == null) { + console.warn(`Routing could not find division ${page}`); + } } - /** - * Returns a boolean indicating whether or not the player is on the given page - */ - isOn(page: string): boolean { - if (!this.isValidPage(page)) { return false; } + this.currentPage = page; + } - return page === this.currentPage; - } - - isOnOverviewPage(): boolean { - return this.currentPage === overviewPage; - } - - /** - * Routes to the specified page - */ - routeTo(page: string): void { - if (!this.isValidPage(page)) { return; } - - - this.currentDivision = null; - if (page !== overviewPage) { - // Iterate through Corporation data to get a reference to the current division - for (let i = 0; i < this.corp.divisions.length; ++i) { - if (this.corp.divisions[i].name === page) { - this.currentDivision = this.corp.divisions[i]; - } - } - - // 'currentDivision' should not be null, since the routing is either on - // the overview page or a division page - if (this.currentDivision == null) { - console.warn(`Routing could not find division ${page}`); - } - } - - this.currentPage = page; - } - - routeToOverviewPage(): void { - this.currentPage = overviewPage; - this.currentDivision = null; - } + routeToOverviewPage(): void { + this.currentPage = overviewPage; + this.currentDivision = null; + } } diff --git a/src/Corporation/ui/SellMaterialPopup.tsx b/src/Corporation/ui/SellMaterialPopup.tsx index b209a90e0..76b1557ef 100644 --- a/src/Corporation/ui/SellMaterialPopup.tsx +++ b/src/Corporation/ui/SellMaterialPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { ICorporation } from "../ICorporation"; @@ -6,67 +6,99 @@ import { Material } from "../Material"; import { SellMaterial } from "../Actions"; function initialPrice(mat: Material): string { - let val = mat.sCost ? mat.sCost+'' : ''; - if (mat.marketTa2) { - val += " (Market-TA.II)"; - } else if (mat.marketTa1) { - val += " (Market-TA.I)"; - } - return val; + let val = mat.sCost ? mat.sCost + "" : ""; + if (mat.marketTa2) { + val += " (Market-TA.II)"; + } else if (mat.marketTa1) { + val += " (Market-TA.I)"; + } + return val; } interface IProps { - mat: Material; - corp: ICorporation; - popupId: string; + mat: Material; + corp: ICorporation; + popupId: string; } // Create a popup that let the player manage sales of a material export function SellMaterialPopup(props: IProps): React.ReactElement { - const [amt, setAmt] = useState(props.mat.sllman[1] ? props.mat.sllman[1]+'' : ''); - const [price, setPrice] = useState(initialPrice(props.mat)); + const [amt, setAmt] = useState( + props.mat.sllman[1] ? props.mat.sllman[1] + "" : "", + ); + const [price, setPrice] = useState(initialPrice(props.mat)); - function sellMaterial(): void { - try { - SellMaterial(props.mat, amt, price); - } catch(err) { - dialogBoxCreate(err+''); - } - - removePopup(props.popupId); + function sellMaterial(): void { + try { + SellMaterial(props.mat, amt, price); + } catch (err) { + dialogBoxCreate(err + ""); } - function onAmtChange(event: React.ChangeEvent): void { - setAmt(event.target.value); - } + removePopup(props.popupId); + } - function onPriceChange(event: React.ChangeEvent): void { - setPrice(event.target.value); - } + function onAmtChange(event: React.ChangeEvent): void { + setAmt(event.target.value); + } - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) sellMaterial(); - } + function onPriceChange(event: React.ChangeEvent): void { + setPrice(event.target.value); + } - return (<> -

    -Enter the maximum amount of {props.mat.name} you would like -to sell per second, as well as the price at which you would -like to sell at.

    -If the sell amount is set to 0, then the material will not be sold. If the sell price -if set to 0, then the material will be discarded

    -Setting the sell amount to 'MAX' will result in you always selling the -maximum possible amount of the material.

    -When setting the sell amount, you can use the 'PROD' variable to designate a dynamically -changing amount that depends on your production. For example, if you set the sell amount -to 'PROD-5' then you will always sell 5 less of the material than you produce.

    -When setting the sell price, you can use the 'MP' variable to designate a dynamically -changing price that depends on the market price. For example, if you set the sell price -to 'MP+10' then it will always be sold at $10 above the market price. -

    + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) sellMaterial(); + } + + return ( + <> +

    + Enter the maximum amount of {props.mat.name} you would like to sell per + second, as well as the price at which you would like to sell at.
    - - - - ); +
    + If the sell amount is set to 0, then the material will not be sold. If + the sell price if set to 0, then the material will be discarded +
    +
    + Setting the sell amount to 'MAX' will result in you always selling the + maximum possible amount of the material. +
    +
    + When setting the sell amount, you can use the 'PROD' variable to + designate a dynamically changing amount that depends on your production. + For example, if you set the sell amount to 'PROD-5' then you will always + sell 5 less of the material than you produce. +
    +
    + When setting the sell price, you can use the 'MP' variable to designate + a dynamically changing price that depends on the market price. For + example, if you set the sell price to 'MP+10' then it will always be + sold at $10 above the market price. +

    +
    + + + + + ); } diff --git a/src/Corporation/ui/SellProductPopup.tsx b/src/Corporation/ui/SellProductPopup.tsx index d04f13f60..101d8369a 100644 --- a/src/Corporation/ui/SellProductPopup.tsx +++ b/src/Corporation/ui/SellProductPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { Cities } from "../../Locations/Cities"; @@ -6,78 +6,118 @@ import { Product } from "../Product"; import { SellProduct } from "../Actions"; function initialPrice(product: Product): string { - let val = product.sCost ? product.sCost+'' : ''; - if (product.marketTa2) { - val += " (Market-TA.II)"; - } else if (product.marketTa1) { - val += " (Market-TA.I)"; - } - return val; + let val = product.sCost ? product.sCost + "" : ""; + if (product.marketTa2) { + val += " (Market-TA.II)"; + } else if (product.marketTa1) { + val += " (Market-TA.I)"; + } + return val; } interface IProps { - product: Product; - city: string; - popupId: string; + product: Product; + city: string; + popupId: string; } // Create a popup that let the player manage sales of a material export function SellProductPopup(props: IProps): React.ReactElement { - const [checked, setChecked] = useState(true); - const [iQty, setQty] = useState(props.product.sllman[props.city][1] ? props.product.sllman[props.city][1] : ''); - const [px, setPx] = useState(initialPrice(props.product)); + const [checked, setChecked] = useState(true); + const [iQty, setQty] = useState( + props.product.sllman[props.city][1] + ? props.product.sllman[props.city][1] + : "", + ); + const [px, setPx] = useState(initialPrice(props.product)); - function onCheckedChange(event: React.ChangeEvent): void { - setChecked(event.target.checked); + function onCheckedChange(event: React.ChangeEvent): void { + setChecked(event.target.checked); + } + + function sellProduct(): void { + try { + SellProduct(props.product, props.city, iQty, px, checked); + } catch (err) { + dialogBoxCreate(err + ""); } - function sellProduct(): void { - try { - SellProduct(props.product, props.city, iQty, px, checked); - } catch(err) { - dialogBoxCreate(err+''); - } + removePopup(props.popupId); + } - removePopup(props.popupId); - } + function onAmtChange(event: React.ChangeEvent): void { + setQty(event.target.value); + } - function onAmtChange(event: React.ChangeEvent): void { - setQty(event.target.value); - } + function onPriceChange(event: React.ChangeEvent): void { + setPx(event.target.value); + } - function onPriceChange(event: React.ChangeEvent): void { - setPx(event.target.value); - } + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) sellProduct(); + } - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) sellProduct(); - } - - return (<> -

    -Enter the maximum amount of {props.product.name} you would like -to sell per second, as well as the price at which you would like to -sell it at.

    -If the sell amount is set to 0, then the product will not be sold. If the -sell price is set to 0, then the product will be discarded.

    -Setting the sell amount to 'MAX' will result in you always selling the -maximum possible amount of the material.

    -When setting the sell amount, you can use the 'PROD' variable to designate a -dynamically changing amount that depends on your production. For example, -if you set the sell amount to 'PROD-1' then you will always sell 1 less of -the material than you produce.

    -When setting the sell price, you can use the 'MP' variable to set a -dynamically changing price that depends on the Product's estimated -market price. For example, if you set it to 'MP*5' then it -will always be sold at five times the estimated market price. -

    + return ( + <> +

    + Enter the maximum amount of {props.product.name} you would like to sell + per second, as well as the price at which you would like to sell it at.
    - - - -

    - - -
    - ); +
    + If the sell amount is set to 0, then the product will not be sold. If + the sell price is set to 0, then the product will be discarded. +
    +
    + Setting the sell amount to 'MAX' will result in you always selling the + maximum possible amount of the material. +
    +
    + When setting the sell amount, you can use the 'PROD' variable to + designate a dynamically changing amount that depends on your production. + For example, if you set the sell amount to 'PROD-1' then you will always + sell 1 less of the material than you produce. +
    +
    + When setting the sell price, you can use the 'MP' variable to set a + dynamically changing price that depends on the Product's estimated + market price. For example, if you set it to 'MP*5' then it will always + be sold at five times the estimated market price. +

    +
    + + + +
    + + +
    + + ); } diff --git a/src/Corporation/ui/SellSharesPopup.tsx b/src/Corporation/ui/SellSharesPopup.tsx index f8b898810..eb86052d7 100644 --- a/src/Corporation/ui/SellSharesPopup.tsx +++ b/src/Corporation/ui/SellSharesPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { numeralWrapper } from "../../ui/numeralFormat"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { IPlayer } from "../../PersonObjects/IPlayer"; @@ -7,91 +7,123 @@ import { CorporationConstants } from "../data/Constants"; import { ICorporation } from "../ICorporation"; interface IProps { - corp: ICorporation; - player: IPlayer; - popupId: string; + corp: ICorporation; + player: IPlayer; + popupId: string; } // Create a popup that lets the player sell Corporation shares // This is created when the player clicks the "Sell Shares" button in the overview panel export function SellSharesPopup(props: IProps): React.ReactElement { - const [shares, setShares] = useState(null); + const [shares, setShares] = useState(null); - function changeShares(event: React.ChangeEvent): void { - if(event.target.value === "") setShares(null); - else setShares(Math.round(parseFloat(event.target.value))); + function changeShares(event: React.ChangeEvent): void { + if (event.target.value === "") setShares(null); + else setShares(Math.round(parseFloat(event.target.value))); + } + + function ProfitIndicator(props: { + shares: number | null; + corp: ICorporation; + }): React.ReactElement { + if (props.shares === null) return <>; + if (isNaN(props.shares) || props.shares <= 0) { + return <>ERROR: Invalid value entered for number of shares to sell; + } else if (props.shares > props.corp.numShares) { + return <>You don't have this many shares to sell!; + } else { + const stockSaleResults = props.corp.calculateShareSale(props.shares); + const profit = stockSaleResults[0]; + return ( + <> + Sell {props.shares} shares for a total of{" "} + {numeralWrapper.formatMoney(profit)} + + ); } + } - function ProfitIndicator(props: {shares: number | null; corp: ICorporation}): React.ReactElement { - if(props.shares === null) return (<>); - if (isNaN(props.shares) || props.shares <= 0) { - return (<>ERROR: Invalid value entered for number of shares to sell); - } else if (props.shares > props.corp.numShares) { - return (<>You don't have this many shares to sell!); + function sell(): void { + if (shares === null) return; + if (isNaN(shares) || shares <= 0) { + dialogBoxCreate("ERROR: Invalid value for number of shares"); + } else if (shares > props.corp.numShares) { + dialogBoxCreate("ERROR: You don't have this many shares to sell"); + } else { + const stockSaleResults = props.corp.calculateShareSale(shares); + const profit = stockSaleResults[0]; + const newSharePrice = stockSaleResults[1]; + const newSharesUntilUpdate = stockSaleResults[2]; + + props.corp.numShares -= shares; + if (isNaN(props.corp.issuedShares)) { + console.error( + `Corporation issuedShares is NaN: ${props.corp.issuedShares}`, + ); + const res = props.corp.issuedShares; + if (isNaN(res)) { + props.corp.issuedShares = 0; } else { - const stockSaleResults = props.corp.calculateShareSale(props.shares); - const profit = stockSaleResults[0]; - return (<>Sell {props.shares} shares for a total of {numeralWrapper.formatMoney(profit)}); + props.corp.issuedShares = res; } + } + props.corp.issuedShares += shares; + props.corp.sharePrice = newSharePrice; + props.corp.shareSalesUntilPriceUpdate = newSharesUntilUpdate; + props.corp.shareSaleCooldown = CorporationConstants.SellSharesCooldown; + props.player.gainMoney(profit); + props.player.recordMoneySource(profit, "corporation"); + removePopup(props.popupId); + dialogBoxCreate( + `Sold ${numeralWrapper.formatMoney(shares)} shares for ` + + `${numeralWrapper.formatMoney(profit)}. ` + + `The corporation's stock price fell to ${numeralWrapper.formatMoney( + props.corp.sharePrice, + )} ` + + `as a result of dilution.`, + ); + + props.corp.rerender(props.player); } + } - function sell(): void { - if(shares === null) return; - if (isNaN(shares) || shares <= 0) { - dialogBoxCreate("ERROR: Invalid value for number of shares"); - } else if (shares > props.corp.numShares) { - dialogBoxCreate("ERROR: You don't have this many shares to sell"); - } else { - const stockSaleResults = props.corp.calculateShareSale(shares); - const profit = stockSaleResults[0]; - const newSharePrice = stockSaleResults[1]; - const newSharesUntilUpdate = stockSaleResults[2]; + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) sell(); + } - props.corp.numShares -= shares; - if (isNaN(props.corp.issuedShares)) { - console.error(`Corporation issuedShares is NaN: ${props.corp.issuedShares}`); - const res = props.corp.issuedShares; - if (isNaN(res)) { - props.corp.issuedShares = 0; - } else { - props.corp.issuedShares = res; - } - } - props.corp.issuedShares += shares; - props.corp.sharePrice = newSharePrice; - props.corp.shareSalesUntilPriceUpdate = newSharesUntilUpdate; - props.corp.shareSaleCooldown = CorporationConstants.SellSharesCooldown; - props.player.gainMoney(profit); - props.player.recordMoneySource(profit, "corporation"); - removePopup(props.popupId); - dialogBoxCreate(`Sold ${numeralWrapper.formatMoney(shares)} shares for ` + - `${numeralWrapper.formatMoney(profit)}. ` + - `The corporation's stock price fell to ${numeralWrapper.formatMoney(props.corp.sharePrice)} ` + - `as a result of dilution.`); - - props.corp.rerender(props.player); - } - } - - - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) sell(); - } - - return (<> -

    -Enter the number of shares you would like to sell. The money from -selling your shares will go directly to you (NOT your Corporation).

    -Selling your shares will cause your corporation's stock price to fall due to -dilution. Furthermore, selling a large number of shares all at once will have an immediate effect -in reducing your stock price.

    -The current price of your -company's stock is {numeralWrapper.formatMoney(props.corp.sharePrice)} -

    - + return ( + <> +

    + Enter the number of shares you would like to sell. The money from + selling your shares will go directly to you (NOT your Corporation).
    - - - ); - -} \ No newline at end of file +
    + Selling your shares will cause your corporation's stock price to fall + due to dilution. Furthermore, selling a large number of shares all at + once will have an immediate effect in reducing your stock price. +
    +
    + The current price of your company's stock is{" "} + {numeralWrapper.formatMoney(props.corp.sharePrice)} +

    + +
    + + + + ); +} diff --git a/src/Corporation/ui/ThrowPartyPopup.tsx b/src/Corporation/ui/ThrowPartyPopup.tsx index 6f6fc774d..a0735bdb5 100644 --- a/src/Corporation/ui/ThrowPartyPopup.tsx +++ b/src/Corporation/ui/ThrowPartyPopup.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { removePopup } from "../../ui/React/createPopup"; import { numeralWrapper } from "../../ui/numeralFormat"; import { dialogBoxCreate } from "../../../utils/DialogBox"; @@ -6,53 +6,82 @@ import { OfficeSpace } from "../OfficeSpace"; import { ICorporation } from "../ICorporation"; interface IProps { - office: OfficeSpace; - corp: ICorporation; - popupId: string; + office: OfficeSpace; + corp: ICorporation; + popupId: string; } export function ThrowPartyPopup(props: IProps): React.ReactElement { - const [cost, setCost] = useState(null); + const [cost, setCost] = useState(null); - function changeCost(event: React.ChangeEvent): void { - setCost(parseFloat(event.target.value)); - } + function changeCost(event: React.ChangeEvent): void { + setCost(parseFloat(event.target.value)); + } - function throwParty(): void { - if (cost === null || isNaN(cost) || cost < 0) { - dialogBoxCreate("Invalid value entered"); - } else { - const totalCost = cost * props.office.employees.length; - if (props.corp.funds.lt(totalCost)) { - dialogBoxCreate("You don't have enough company funds to throw a party!"); - } else { - props.corp.funds = props.corp.funds.minus(totalCost); - let mult = 0; - for (let i = 0; i < props.office.employees.length; ++i) { - mult = props.office.employees[i].throwParty(cost); - } - dialogBoxCreate("You threw a party for the office! The morale and happiness " + - "of each employee increased by " + numeralWrapper.formatPercentage((mult-1))); - removePopup(props.popupId); - } + function throwParty(): void { + if (cost === null || isNaN(cost) || cost < 0) { + dialogBoxCreate("Invalid value entered"); + } else { + const totalCost = cost * props.office.employees.length; + if (props.corp.funds.lt(totalCost)) { + dialogBoxCreate( + "You don't have enough company funds to throw a party!", + ); + } else { + props.corp.funds = props.corp.funds.minus(totalCost); + let mult = 0; + for (let i = 0; i < props.office.employees.length; ++i) { + mult = props.office.employees[i].throwParty(cost); } + dialogBoxCreate( + "You threw a party for the office! The morale and happiness " + + "of each employee increased by " + + numeralWrapper.formatPercentage(mult - 1), + ); + removePopup(props.popupId); + } } + } - function EffectText(props: {cost: number | null; office: OfficeSpace}): React.ReactElement { - let cost = props.cost; - if(cost !== null && (isNaN(cost) || cost < 0)) return

    Invalid value entered!

    - if(cost === null) cost = 0; - return

    Throwing this party will cost a total of {numeralWrapper.formatMoney(cost * props.office.employees.length)}

    - } + function EffectText(props: { + cost: number | null; + office: OfficeSpace; + }): React.ReactElement { + let cost = props.cost; + if (cost !== null && (isNaN(cost) || cost < 0)) + return

    Invalid value entered!

    ; + if (cost === null) cost = 0; + return ( +

    + Throwing this party will cost a total of{" "} + {numeralWrapper.formatMoney(cost * props.office.employees.length)} +

    + ); + } - function onKeyDown(event: React.KeyboardEvent): void { - if (event.keyCode === 13) throwParty(); - } + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) throwParty(); + } - return (<> -

    Enter the amount of money you would like to spend PER EMPLOYEE on this office party

    - - - - ); -} \ No newline at end of file + return ( + <> +

    + Enter the amount of money you would like to spend PER EMPLOYEE on this + office party +

    + + + + + ); +} diff --git a/src/Corporation/ui/UnlockUpgrade.tsx b/src/Corporation/ui/UnlockUpgrade.tsx index 0b08c4d4a..a14466e5b 100644 --- a/src/Corporation/ui/UnlockUpgrade.tsx +++ b/src/Corporation/ui/UnlockUpgrade.tsx @@ -10,28 +10,36 @@ import { UnlockUpgrade as UU } from "../Actions"; import { Money } from "../../ui/React/Money"; interface IProps { - upgradeData: CorporationUnlockUpgrade; - corp: ICorporation; - player: IPlayer; + upgradeData: CorporationUnlockUpgrade; + corp: ICorporation; + player: IPlayer; } export function UnlockUpgrade(props: IProps): React.ReactElement { - const data = props.upgradeData; - const text = <>{data[2]} - ; - const tooltip = data[3]; - function onClick(): void { - try { - UU(props.corp, props.upgradeData); - } catch(err) { - dialogBoxCreate(err+''); - } - props.corp.rerender(props.player); + const data = props.upgradeData; + const text = ( + <> + {data[2]} - + + ); + const tooltip = data[3]; + function onClick(): void { + try { + UU(props.corp, props.upgradeData); + } catch (err) { + dialogBoxCreate(err + ""); } + props.corp.rerender(props.player); + } - return ( -
    - {text} - {tooltip} -
    - ) + return ( +
    + {text} + {tooltip} +
    + ); } diff --git a/src/Corporation/ui/UpgradeOfficeSizePopup.tsx b/src/Corporation/ui/UpgradeOfficeSizePopup.tsx index a7b948140..978650afb 100644 --- a/src/Corporation/ui/UpgradeOfficeSizePopup.tsx +++ b/src/Corporation/ui/UpgradeOfficeSizePopup.tsx @@ -8,73 +8,110 @@ import { ICorporation } from "../ICorporation"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - office: OfficeSpace; - corp: ICorporation; - popupId: string; - player: IPlayer; + office: OfficeSpace; + corp: ICorporation; + popupId: string; + player: IPlayer; } export function UpgradeOfficeSizePopup(props: IProps): React.ReactElement { - const initialPriceMult = Math.round(props.office.size / CorporationConstants.OfficeInitialSize); - const costMultiplier = 1.09; - const upgradeCost = CorporationConstants.OfficeInitialCost * Math.pow(costMultiplier, initialPriceMult); + const initialPriceMult = Math.round( + props.office.size / CorporationConstants.OfficeInitialSize, + ); + const costMultiplier = 1.09; + const upgradeCost = + CorporationConstants.OfficeInitialCost * + Math.pow(costMultiplier, initialPriceMult); - // Calculate cost to upgrade size by 15 employees - let mult = 0; - for (let i = 0; i < 5; ++i) { - mult += (Math.pow(costMultiplier, initialPriceMult + i)); + // Calculate cost to upgrade size by 15 employees + let mult = 0; + for (let i = 0; i < 5; ++i) { + mult += Math.pow(costMultiplier, initialPriceMult + i); + } + const upgradeCost15 = CorporationConstants.OfficeInitialCost * mult; + + //Calculate max upgrade size and cost + const maxMult = props.corp.funds + .dividedBy(CorporationConstants.OfficeInitialCost) + .toNumber(); + let maxNum = 1; + mult = Math.pow(costMultiplier, initialPriceMult); + while (maxNum < 50) { + //Hard cap of 50x (extra 150 employees) + if (mult >= maxMult) break; + const multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum); + if (mult + multIncrease > maxMult) { + break; + } else { + mult += multIncrease; } - const upgradeCost15 = CorporationConstants.OfficeInitialCost * mult; + ++maxNum; + } + const upgradeCostMax = CorporationConstants.OfficeInitialCost * mult; - //Calculate max upgrade size and cost - const maxMult = (props.corp.funds.dividedBy(CorporationConstants.OfficeInitialCost)).toNumber(); - let maxNum = 1; - mult = Math.pow(costMultiplier, initialPriceMult); - while(maxNum < 50) { //Hard cap of 50x (extra 150 employees) - if (mult >= maxMult) break; - const multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum); - if (mult + multIncrease > maxMult) { - break; - } else { - mult += multIncrease; + function upgradeSize(cost: number, size: number): void { + if (props.corp.funds.lt(cost)) { + dialogBoxCreate( + "You don't have enough company funds to purchase this upgrade!", + ); + } else { + props.office.size += size; + props.corp.funds = props.corp.funds.minus(cost); + dialogBoxCreate( + "Office space increased! It can now hold " + + props.office.size + + " employees", + ); + props.corp.rerender(props.player); + } + removePopup(props.popupId); + } + + interface IUpgradeButton { + cost: number; + size: number; + corp: ICorporation; + } + + function UpgradeSizeButton(props: IUpgradeButton): React.ReactElement { + return ( + + ); + } - function upgradeSize(cost: number, size: number): void { - if (props.corp.funds.lt(cost)) { - dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); - } else { - props.office.size += size; - props.corp.funds = props.corp.funds.minus(cost); - dialogBoxCreate("Office space increased! It can now hold " + props.office.size + " employees"); - props.corp.rerender(props.player); - } - removePopup(props.popupId); - } - - interface IUpgradeButton { - cost: number; - size: number; - corp: ICorporation; - } - - function UpgradeSizeButton(props: IUpgradeButton): React.ReactElement { - return (); - } - - return (<> -

    Increase the size of your office space to fit additional employees!

    -

    Upgrade size:

    - - - - ); -} \ No newline at end of file + return ( + <> +

    Increase the size of your office space to fit additional employees!

    +

    Upgrade size:

    + + + + + ); +} diff --git a/src/Crime/Crime.ts b/src/Crime/Crime.ts index bbff32d4b..e3746ef75 100644 --- a/src/Crime/Crime.ts +++ b/src/Crime/Crime.ts @@ -3,126 +3,144 @@ import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayerOrSleeve } from "../PersonObjects/IPlayerOrSleeve"; export interface IConstructorParams { - hacking_success_weight?: number; - strength_success_weight?: number; - defense_success_weight?: number; - dexterity_success_weight?: number; - agility_success_weight?: number; - charisma_success_weight?: number; - hacking_exp?: number; - strength_exp?: number; - defense_exp?: number; - dexterity_exp?: number; - agility_exp?: number; - charisma_exp?: number; - intelligence_exp?: number; + hacking_success_weight?: number; + strength_success_weight?: number; + defense_success_weight?: number; + dexterity_success_weight?: number; + agility_success_weight?: number; + charisma_success_weight?: number; + hacking_exp?: number; + strength_exp?: number; + defense_exp?: number; + dexterity_exp?: number; + agility_exp?: number; + charisma_exp?: number; + intelligence_exp?: number; - kills?: number; + kills?: number; } export class Crime { - // Number representing the difficulty of the crime. Used for success chance calculations - difficulty = 0; + // Number representing the difficulty of the crime. Used for success chance calculations + difficulty = 0; - // Amount of karma lost for SUCCESSFULLY committing this crime - karma = 0; + // Amount of karma lost for SUCCESSFULLY committing this crime + karma = 0; - // How many people die as a result of this crime - kills = 0; + // How many people die as a result of this crime + kills = 0; - // How much money is given by the - money = 0; + // How much money is given by the + money = 0; - // Name of crime - name = ""; + // Name of crime + name = ""; - // Milliseconds it takes to attempt the crime - time = 0; + // Milliseconds it takes to attempt the crime + time = 0; - // Corresponding type in CONSTANTS. Contains a description for the crime activity - type = ""; + // Corresponding type in CONSTANTS. Contains a description for the crime activity + type = ""; - // Weighting factors that determine how stats affect the success rate of this crime - hacking_success_weight = 0; - strength_success_weight = 0; - defense_success_weight = 0; - dexterity_success_weight = 0; - agility_success_weight = 0; - charisma_success_weight = 0; + // Weighting factors that determine how stats affect the success rate of this crime + hacking_success_weight = 0; + strength_success_weight = 0; + defense_success_weight = 0; + dexterity_success_weight = 0; + agility_success_weight = 0; + charisma_success_weight = 0; - // How much stat experience is granted by this crime - hacking_exp = 0; - strength_exp = 0; - defense_exp = 0; - dexterity_exp = 0; - agility_exp = 0; - charisma_exp = 0; - intelligence_exp = 0; + // How much stat experience is granted by this crime + hacking_exp = 0; + strength_exp = 0; + defense_exp = 0; + dexterity_exp = 0; + agility_exp = 0; + charisma_exp = 0; + intelligence_exp = 0; - constructor(name = "", - type = "", - time = 0, - money = 0, - difficulty = 0, - karma = 0, - params: IConstructorParams={}) { - this.name = name; - this.type = type; - this.time = time; - this.money = money; - this.difficulty = difficulty; - this.karma = karma; + constructor( + name = "", + type = "", + time = 0, + money = 0, + difficulty = 0, + karma = 0, + params: IConstructorParams = {}, + ) { + this.name = name; + this.type = type; + this.time = time; + this.money = money; + this.difficulty = difficulty; + this.karma = karma; - this.hacking_success_weight = params.hacking_success_weight ? params.hacking_success_weight : 0; - this.strength_success_weight = params.strength_success_weight ? params.strength_success_weight : 0; - this.defense_success_weight = params.defense_success_weight ? params.defense_success_weight : 0; - this.dexterity_success_weight = params.dexterity_success_weight ? params.dexterity_success_weight : 0; - this.agility_success_weight = params.agility_success_weight ? params.agility_success_weight : 0; - this.charisma_success_weight = params.charisma_success_weight ? params.charisma_success_weight : 0; + this.hacking_success_weight = params.hacking_success_weight + ? params.hacking_success_weight + : 0; + this.strength_success_weight = params.strength_success_weight + ? params.strength_success_weight + : 0; + this.defense_success_weight = params.defense_success_weight + ? params.defense_success_weight + : 0; + this.dexterity_success_weight = params.dexterity_success_weight + ? params.dexterity_success_weight + : 0; + this.agility_success_weight = params.agility_success_weight + ? params.agility_success_weight + : 0; + this.charisma_success_weight = params.charisma_success_weight + ? params.charisma_success_weight + : 0; - this.hacking_exp = params.hacking_exp ? params.hacking_exp : 0; - this.strength_exp = params.strength_exp ? params.strength_exp : 0; - this.defense_exp = params.defense_exp ? params.defense_exp : 0; - this.dexterity_exp = params.dexterity_exp ? params.dexterity_exp : 0; - this.agility_exp = params.agility_exp ? params.agility_exp : 0; - this.charisma_exp = params.charisma_exp ? params.charisma_exp : 0; - this.intelligence_exp = params.intelligence_exp ? params.intelligence_exp : 0; + this.hacking_exp = params.hacking_exp ? params.hacking_exp : 0; + this.strength_exp = params.strength_exp ? params.strength_exp : 0; + this.defense_exp = params.defense_exp ? params.defense_exp : 0; + this.dexterity_exp = params.dexterity_exp ? params.dexterity_exp : 0; + this.agility_exp = params.agility_exp ? params.agility_exp : 0; + this.charisma_exp = params.charisma_exp ? params.charisma_exp : 0; + this.intelligence_exp = params.intelligence_exp + ? params.intelligence_exp + : 0; - this.kills = params.kills ? params.kills : 0; + this.kills = params.kills ? params.kills : 0; + } + + commit(p: IPlayer, div = 1, singParams: any = null): number { + if (div <= 0) { + div = 1; } + p.startCrime( + this.type, + this.hacking_exp / div, + this.strength_exp / div, + this.defense_exp / div, + this.dexterity_exp / div, + this.agility_exp / div, + this.charisma_exp / div, + this.money / div, + this.time, + singParams, + ); - commit(p: IPlayer, div=1, singParams: any=null): number { - if (div <= 0) { div = 1; } - p.startCrime( - this.type, - this.hacking_exp/div, - this.strength_exp/div, - this.defense_exp/div, - this.dexterity_exp/div, - this.agility_exp/div, - this.charisma_exp/div, - this.money/div, - this.time, - singParams, - ); + return this.time; + } - return this.time; - } - - successRate(p: IPlayerOrSleeve): number { - let chance: number = (this.hacking_success_weight * p.hacking_skill + - this.strength_success_weight * p.strength + - this.defense_success_weight * p.defense + - this.dexterity_success_weight * p.dexterity + - this.agility_success_weight * p.agility + - this.charisma_success_weight * p.charisma + - CONSTANTS.IntelligenceCrimeWeight * p.intelligence); - chance /= CONSTANTS.MaxSkillLevel; - chance /= this.difficulty; - chance *= p.crime_success_mult; - chance *= p.getIntelligenceBonus(1); - - return Math.min(chance, 1); - } + successRate(p: IPlayerOrSleeve): number { + let chance: number = + this.hacking_success_weight * p.hacking_skill + + this.strength_success_weight * p.strength + + this.defense_success_weight * p.defense + + this.dexterity_success_weight * p.dexterity + + this.agility_success_weight * p.agility + + this.charisma_success_weight * p.charisma + + CONSTANTS.IntelligenceCrimeWeight * p.intelligence; + chance /= CONSTANTS.MaxSkillLevel; + chance /= this.difficulty; + chance *= p.crime_success_mult; + chance *= p.getIntelligenceBonus(1); + return Math.min(chance, 1); + } } diff --git a/src/Crime/CrimeHelpers.ts b/src/Crime/CrimeHelpers.ts index f617bb488..ac7610089 100644 --- a/src/Crime/CrimeHelpers.ts +++ b/src/Crime/CrimeHelpers.ts @@ -5,57 +5,60 @@ import { IPlayer } from "../PersonObjects/IPlayer"; import { dialogBoxCreate } from "../../utils/DialogBox"; export function determineCrimeSuccess(p: IPlayer, type: string): boolean { - let chance = 0; - let found = false; - for (const i in Crimes) { - const crime = Crimes[i]; - if (crime.type == type) { - chance = crime.successRate(p); - found = true; - break; - } + let chance = 0; + let found = false; + for (const i in Crimes) { + const crime = Crimes[i]; + if (crime.type == type) { + chance = crime.successRate(p); + found = true; + break; } + } - if (!found) { - dialogBoxCreate(`ERR: Unrecognized crime type: ${type} This is probably a bug please contact the developer`, false); - return false; - } + if (!found) { + dialogBoxCreate( + `ERR: Unrecognized crime type: ${type} This is probably a bug please contact the developer`, + false, + ); + return false; + } - if (Math.random() <= chance) { - //Success - return true; - } else { - //Failure - return false; - } + if (Math.random() <= chance) { + //Success + return true; + } else { + //Failure + return false; + } } export function findCrime(roughName: string): Crime | null { - if (roughName.includes("shoplift")) { - return Crimes.Shoplift; - } else if (roughName.includes("rob") && roughName.includes("store")) { - return Crimes.RobStore; - } else if (roughName.includes("mug")) { - return Crimes.Mug; - } else if (roughName.includes("larceny")) { - return Crimes.Larceny; - } else if (roughName.includes("drugs")) { - return Crimes.DealDrugs; - } else if (roughName.includes("bond") && roughName.includes("forge")) { - return Crimes.BondForgery; - } else if (roughName.includes("traffick") && roughName.includes("arms")) { - return Crimes.TraffickArms; - } else if (roughName.includes("homicide")) { - return Crimes.Homicide; - } else if (roughName.includes("grand") && roughName.includes("auto")) { - return Crimes.GrandTheftAuto; - } else if (roughName.includes("kidnap")) { - return Crimes.Kidnap; - } else if (roughName.includes("assassinate")) { - return Crimes.Assassination; - } else if (roughName.includes("heist")) { - return Crimes.Heist; - } + if (roughName.includes("shoplift")) { + return Crimes.Shoplift; + } else if (roughName.includes("rob") && roughName.includes("store")) { + return Crimes.RobStore; + } else if (roughName.includes("mug")) { + return Crimes.Mug; + } else if (roughName.includes("larceny")) { + return Crimes.Larceny; + } else if (roughName.includes("drugs")) { + return Crimes.DealDrugs; + } else if (roughName.includes("bond") && roughName.includes("forge")) { + return Crimes.BondForgery; + } else if (roughName.includes("traffick") && roughName.includes("arms")) { + return Crimes.TraffickArms; + } else if (roughName.includes("homicide")) { + return Crimes.Homicide; + } else if (roughName.includes("grand") && roughName.includes("auto")) { + return Crimes.GrandTheftAuto; + } else if (roughName.includes("kidnap")) { + return Crimes.Kidnap; + } else if (roughName.includes("assassinate")) { + return Crimes.Assassination; + } else if (roughName.includes("heist")) { + return Crimes.Heist; + } - return null; + return null; } diff --git a/src/Crime/Crimes.ts b/src/Crime/Crimes.ts index 79e9c7119..bc454d58b 100644 --- a/src/Crime/Crimes.ts +++ b/src/Crime/Crimes.ts @@ -4,27 +4,43 @@ import { CONSTANTS } from "../Constants"; import { IMap } from "../types"; export const Crimes: IMap = { - Shoplift: new Crime("Shoplift", CONSTANTS.CrimeShoplift, 2e3, 15e3, 1/20, 0.1, { - dexterity_success_weight: 1, - agility_success_weight: 1, + Shoplift: new Crime( + "Shoplift", + CONSTANTS.CrimeShoplift, + 2e3, + 15e3, + 1 / 20, + 0.1, + { + dexterity_success_weight: 1, + agility_success_weight: 1, - dexterity_exp: 2, - agility_exp: 2, - }), + dexterity_exp: 2, + agility_exp: 2, + }, + ), - RobStore: new Crime("Rob Store", CONSTANTS.CrimeRobStore, 60e3, 400e3, 1/5, 0.5, { - hacking_exp: 30, - dexterity_exp: 45, - agility_exp: 45, + RobStore: new Crime( + "Rob Store", + CONSTANTS.CrimeRobStore, + 60e3, + 400e3, + 1 / 5, + 0.5, + { + hacking_exp: 30, + dexterity_exp: 45, + agility_exp: 45, - hacking_success_weight: 0.5 , - dexterity_success_weight: 2, - agility_success_weight: 1, + hacking_success_weight: 0.5, + dexterity_success_weight: 2, + agility_success_weight: 1, - intelligence_exp: 7.5 * CONSTANTS.IntelligenceCrimeBaseExpGain, - }), + intelligence_exp: 7.5 * CONSTANTS.IntelligenceCrimeBaseExpGain, + }, + ), - Mug: new Crime("Mug", CONSTANTS.CrimeMug, 4e3, 36e3, 1/5, 0.25, { + Mug: new Crime("Mug", CONSTANTS.CrimeMug, 4e3, 36e3, 1 / 5, 0.25, { strength_exp: 3, defense_exp: 3, dexterity_exp: 3, @@ -36,52 +52,84 @@ export const Crimes: IMap = { agility_success_weight: 0.5, }), - Larceny: new Crime("Larceny", CONSTANTS.CrimeLarceny, 90e3, 800e3, 1/3, 1.5, { - hacking_exp: 45, - dexterity_exp: 60, - agility_exp: 60, + Larceny: new Crime( + "Larceny", + CONSTANTS.CrimeLarceny, + 90e3, + 800e3, + 1 / 3, + 1.5, + { + hacking_exp: 45, + dexterity_exp: 60, + agility_exp: 60, - hacking_success_weight: 0.5, - dexterity_success_weight: 1, - agility_success_weight: 1, + hacking_success_weight: 0.5, + dexterity_success_weight: 1, + agility_success_weight: 1, - intelligence_exp: 15 * CONSTANTS.IntelligenceCrimeBaseExpGain, - }), + intelligence_exp: 15 * CONSTANTS.IntelligenceCrimeBaseExpGain, + }, + ), - DealDrugs: new Crime("Deal Drugs", CONSTANTS.CrimeDrugs, 10e3, 120e3, 1, 0.5, { - dexterity_exp: 5, - agility_exp: 5, - charisma_exp: 10, + DealDrugs: new Crime( + "Deal Drugs", + CONSTANTS.CrimeDrugs, + 10e3, + 120e3, + 1, + 0.5, + { + dexterity_exp: 5, + agility_exp: 5, + charisma_exp: 10, - charisma_success_weight: 3, - dexterity_success_weight: 2, - agility_success_weight: 1, - }), + charisma_success_weight: 3, + dexterity_success_weight: 2, + agility_success_weight: 1, + }, + ), - BondForgery: new Crime("Bond Forgery", CONSTANTS.CrimeBondForgery, 300e3, 4.5e6, 1/2, 0.1, { - hacking_exp: 100, - dexterity_exp: 150, - charisma_exp: 15, + BondForgery: new Crime( + "Bond Forgery", + CONSTANTS.CrimeBondForgery, + 300e3, + 4.5e6, + 1 / 2, + 0.1, + { + hacking_exp: 100, + dexterity_exp: 150, + charisma_exp: 15, - hacking_success_weight: 0.05, - dexterity_success_weight: 1.25, + hacking_success_weight: 0.05, + dexterity_success_weight: 1.25, - intelligence_exp: 60 * CONSTANTS.IntelligenceCrimeBaseExpGain, - }), + intelligence_exp: 60 * CONSTANTS.IntelligenceCrimeBaseExpGain, + }, + ), - TraffickArms: new Crime("Traffick Arms", CONSTANTS.CrimeTraffickArms, 40e3, 600e3, 2, 1, { - strength_exp: 20, - defense_exp: 20, - dexterity_exp: 20, - agility_exp: 20, - charisma_exp: 40, + TraffickArms: new Crime( + "Traffick Arms", + CONSTANTS.CrimeTraffickArms, + 40e3, + 600e3, + 2, + 1, + { + strength_exp: 20, + defense_exp: 20, + dexterity_exp: 20, + agility_exp: 20, + charisma_exp: 40, - charisma_success_weight: 1, - strength_success_weight: 1, - defense_success_weight: 1, - dexterity_success_weight: 1, - agility_success_weight: 1, - }), + charisma_success_weight: 1, + strength_success_weight: 1, + defense_success_weight: 1, + dexterity_success_weight: 1, + agility_success_weight: 1, + }, + ), Homicide: new Crime("Homicide", CONSTANTS.CrimeHomicide, 3e3, 45e3, 1, 3, { strength_exp: 2, @@ -97,21 +145,29 @@ export const Crimes: IMap = { kills: 1, }), - GrandTheftAuto: new Crime("Grand Theft Auto", CONSTANTS.CrimeGrandTheftAuto, 80e3, 1.6e6, 8, 5, { - strength_exp: 20, - defense_exp: 20, - dexterity_exp: 20, - agility_exp: 80, - charisma_exp: 40, + GrandTheftAuto: new Crime( + "Grand Theft Auto", + CONSTANTS.CrimeGrandTheftAuto, + 80e3, + 1.6e6, + 8, + 5, + { + strength_exp: 20, + defense_exp: 20, + dexterity_exp: 20, + agility_exp: 80, + charisma_exp: 40, - hacking_success_weight: 1, - strength_success_weight: 1, - dexterity_success_weight: 4, - agility_success_weight: 2, - charisma_success_weight: 2, + hacking_success_weight: 1, + strength_success_weight: 1, + dexterity_success_weight: 4, + agility_success_weight: 2, + charisma_success_weight: 2, - intelligence_exp: 16 * CONSTANTS.IntelligenceCrimeBaseExpGain, - }), + intelligence_exp: 16 * CONSTANTS.IntelligenceCrimeBaseExpGain, + }, + ), Kidnap: new Crime("Kidnap", CONSTANTS.CrimeKidnap, 120e3, 3.6e6, 5, 6, { strength_exp: 80, @@ -128,20 +184,28 @@ export const Crimes: IMap = { intelligence_exp: 26 * CONSTANTS.IntelligenceCrimeBaseExpGain, }), - Assassination: new Crime("Assassination", CONSTANTS.CrimeAssassination, 300e3, 12e6, 8, 10, { - strength_exp: 300, - defense_exp: 300, - dexterity_exp: 300, - agility_exp: 300, + Assassination: new Crime( + "Assassination", + CONSTANTS.CrimeAssassination, + 300e3, + 12e6, + 8, + 10, + { + strength_exp: 300, + defense_exp: 300, + dexterity_exp: 300, + agility_exp: 300, - strength_success_weight: 1, - dexterity_success_weight: 2, - agility_success_weight: 1, + strength_success_weight: 1, + dexterity_success_weight: 2, + agility_success_weight: 1, - intelligence_exp: 65 * CONSTANTS.IntelligenceCrimeBaseExpGain, + intelligence_exp: 65 * CONSTANTS.IntelligenceCrimeBaseExpGain, - kills: 1, - }), + kills: 1, + }, + ), Heist: new Crime("Heist", CONSTANTS.CrimeHeist, 600e3, 120e6, 18, 15, { hacking_exp: 450, diff --git a/src/Crime/formulas/crime.ts b/src/Crime/formulas/crime.ts index c6cecb8a0..c93f4abc0 100644 --- a/src/Crime/formulas/crime.ts +++ b/src/Crime/formulas/crime.ts @@ -2,38 +2,42 @@ import { calculateIntelligenceBonus } from "../../PersonObjects/formulas/intelli import { CONSTANTS } from "../../Constants"; export interface ICrime { - hacking_success_weight: number; - strength_success_weight: number; - defense_success_weight: number; - dexterity_success_weight: number; - agility_success_weight: number; - charisma_success_weight: number; - difficulty: number; + hacking_success_weight: number; + strength_success_weight: number; + defense_success_weight: number; + dexterity_success_weight: number; + agility_success_weight: number; + charisma_success_weight: number; + difficulty: number; } export interface IPerson { - hacking_skill: number; - strength: number; - defense: number; - dexterity: number; - agility: number; - charisma: number; - intelligence: number; - crime_success_mult: number; + hacking_skill: number; + strength: number; + defense: number; + dexterity: number; + agility: number; + charisma: number; + intelligence: number; + crime_success_mult: number; } -export function calculateCrimeSuccessChance(crime: ICrime, person: IPerson): number { - let chance: number = (crime.hacking_success_weight * person.hacking_skill + - crime.strength_success_weight * person.strength + - crime.defense_success_weight * person.defense + - crime.dexterity_success_weight * person.dexterity + - crime.agility_success_weight * person.agility + - crime.charisma_success_weight * person.charisma + - CONSTANTS.IntelligenceCrimeWeight * person.intelligence); - chance /= CONSTANTS.MaxSkillLevel; - chance /= crime.difficulty; - chance *= person.crime_success_mult; - chance *= calculateIntelligenceBonus(person.intelligence); +export function calculateCrimeSuccessChance( + crime: ICrime, + person: IPerson, +): number { + let chance: number = + crime.hacking_success_weight * person.hacking_skill + + crime.strength_success_weight * person.strength + + crime.defense_success_weight * person.defense + + crime.dexterity_success_weight * person.dexterity + + crime.agility_success_weight * person.agility + + crime.charisma_success_weight * person.charisma + + CONSTANTS.IntelligenceCrimeWeight * person.intelligence; + chance /= CONSTANTS.MaxSkillLevel; + chance /= crime.difficulty; + chance *= person.crime_success_mult; + chance *= calculateIntelligenceBonus(person.intelligence); - return Math.min(chance, 1); -} \ No newline at end of file + return Math.min(chance, 1); +} diff --git a/src/DarkWeb/DarkWeb.tsx b/src/DarkWeb/DarkWeb.tsx index c778d12b3..9fd2b974f 100644 --- a/src/DarkWeb/DarkWeb.tsx +++ b/src/DarkWeb/DarkWeb.tsx @@ -1,92 +1,107 @@ import * as React from "react"; -import { DarkWebItems } from "./DarkWebItems"; +import { DarkWebItems } from "./DarkWebItems"; -import { Player } from "../Player"; -import { SpecialServerIps } from "../Server/SpecialServerIps"; -import { post, postElement } from "../ui/postToTerminal"; -import { Money } from "../ui/React/Money"; +import { Player } from "../Player"; +import { SpecialServerIps } from "../Server/SpecialServerIps"; +import { post, postElement } from "../ui/postToTerminal"; +import { Money } from "../ui/React/Money"; -import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress"; +import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress"; //Posts a "help" message if connected to DarkWeb export function checkIfConnectedToDarkweb(): void { - if (SpecialServerIps.hasOwnProperty("Darkweb Server")) { - const darkwebIp = SpecialServerIps.getIp("Darkweb Server"); - if (!isValidIPAddress(darkwebIp)) {return;} - if (darkwebIp == Player.getCurrentServer().ip) { - post("You are now connected to the dark web. From the dark web you can purchase illegal items. " + - "Use the 'buy -l' command to display a list of all the items you can buy. Use 'buy [item-name] " + - "to purchase an item."); - } + if (SpecialServerIps.hasOwnProperty("Darkweb Server")) { + const darkwebIp = SpecialServerIps.getIp("Darkweb Server"); + if (!isValidIPAddress(darkwebIp)) { + return; } + if (darkwebIp == Player.getCurrentServer().ip) { + post( + "You are now connected to the dark web. From the dark web you can purchase illegal items. " + + "Use the 'buy -l' command to display a list of all the items you can buy. Use 'buy [item-name] " + + "to purchase an item.", + ); + } + } } //Handler for dark web commands. The terminal's executeCommand() function will pass //dark web-specific commands into this. It will pass in the raw split command array //rather than the command string export function executeDarkwebTerminalCommand(commandArray: string[]): void { - if (commandArray.length == 0) {return;} - switch (commandArray[0]) { - case "buy": { - if (commandArray.length != 2) { - post("Incorrect number of arguments. Usage: "); - post("buy -l"); - post("buy [item name]"); - return; - } - const arg = commandArray[1]; - if (arg == "-l" || arg == "-1" || arg == "--list") { - listAllDarkwebItems(); - } else { - buyDarkwebItem(arg); - } - break; - } - default: - post("Command not found"); - break; + if (commandArray.length == 0) { + return; + } + switch (commandArray[0]) { + case "buy": { + if (commandArray.length != 2) { + post("Incorrect number of arguments. Usage: "); + post("buy -l"); + post("buy [item name]"); + return; + } + const arg = commandArray[1]; + if (arg == "-l" || arg == "-1" || arg == "--list") { + listAllDarkwebItems(); + } else { + buyDarkwebItem(arg); + } + break; } + default: + post("Command not found"); + break; + } } function listAllDarkwebItems(): void { - for(const key in DarkWebItems) { - const item = DarkWebItems[key]; - postElement(<>{item.program} - - {item.description}); - } + for (const key in DarkWebItems) { + const item = DarkWebItems[key]; + postElement( + <> + {item.program} - -{" "} + {item.description} + , + ); + } } function buyDarkwebItem(itemName: string): void { - itemName = itemName.toLowerCase(); + itemName = itemName.toLowerCase(); - // find the program that matches, if any - let item = null; - for(const key in DarkWebItems) { - const i = DarkWebItems[key]; - if(i.program.toLowerCase() == itemName) { - item = i; - } + // find the program that matches, if any + let item = null; + for (const key in DarkWebItems) { + const i = DarkWebItems[key]; + if (i.program.toLowerCase() == itemName) { + item = i; } + } - // return if invalid - if(item === null) { - post("Unrecognized item: "+itemName); - return; - } + // return if invalid + if (item === null) { + post("Unrecognized item: " + itemName); + return; + } - // return if the player already has it. - if(Player.hasProgram(item.program)) { - post('You already have the '+item.program+' program'); - return; - } + // return if the player already has it. + if (Player.hasProgram(item.program)) { + post("You already have the " + item.program + " program"); + return; + } - // return if the player doesn't have enough money - if(Player.money.lt(item.price)) { - post("Not enough money to purchase " + item.program); - return; - } + // return if the player doesn't have enough money + if (Player.money.lt(item.price)) { + post("Not enough money to purchase " + item.program); + return; + } - // buy and push - Player.loseMoney(item.price); - Player.getHomeComputer().programs.push(item.program); - post('You have purchased the ' + item.program + ' program. The new program can be found on your home computer.'); + // buy and push + Player.loseMoney(item.price); + Player.getHomeComputer().programs.push(item.program); + post( + "You have purchased the " + + item.program + + " program. The new program can be found on your home computer.", + ); } diff --git a/src/DarkWeb/DarkWebItem.ts b/src/DarkWeb/DarkWebItem.ts index ce506a0eb..f5dba0e58 100644 --- a/src/DarkWeb/DarkWebItem.ts +++ b/src/DarkWeb/DarkWebItem.ts @@ -1,11 +1,11 @@ export class DarkWebItem { - program: string; - price: number; - description: string; + program: string; + price: number; + description: string; - constructor(program: string, price: number, description: string) { - this.program = program; - this.price = price; - this.description = description; - } + constructor(program: string, price: number, description: string) { + this.program = program; + this.price = price; + this.description = description; + } } diff --git a/src/DarkWeb/DarkWebItems.ts b/src/DarkWeb/DarkWebItems.ts index 82379585d..2466fa23d 100644 --- a/src/DarkWeb/DarkWebItems.ts +++ b/src/DarkWeb/DarkWebItems.ts @@ -3,13 +3,49 @@ import { IMap } from "../types"; import { Programs } from "../Programs/Programs"; export const DarkWebItems: IMap = { - BruteSSHProgram: new DarkWebItem(Programs.BruteSSHProgram.name, 500e3, "Opens up SSH Ports"), - FTPCrackProgram: new DarkWebItem(Programs.FTPCrackProgram.name, 1500e3, "Opens up FTP Ports"), - RelaySMTPProgram: new DarkWebItem(Programs.RelaySMTPProgram.name, 5e6, "Opens up SMTP Ports"), - HTTPWormProgram: new DarkWebItem(Programs.HTTPWormProgram.name, 30e6, "Opens up HTTP Ports"), - SQLInjectProgram: new DarkWebItem(Programs.SQLInjectProgram.name, 250e6, "Opens up SQL Ports"), - DeepscanV1: new DarkWebItem(Programs.DeepscanV1.name, 500000, "Enables 'scan-analyze' with a depth up to 5"), - DeepscanV2: new DarkWebItem(Programs.DeepscanV2.name, 25e6, "Enables 'scan-analyze' with a depth up to 10"), - AutolinkProgram: new DarkWebItem(Programs.AutoLink.name, 1e6, "Enables direct connect via 'scan-analyze'"), - ServerProfilerProgram: new DarkWebItem(Programs.ServerProfiler.name, 1e6, "Displays hacking and Netscript-related information about a server"), + BruteSSHProgram: new DarkWebItem( + Programs.BruteSSHProgram.name, + 500e3, + "Opens up SSH Ports", + ), + FTPCrackProgram: new DarkWebItem( + Programs.FTPCrackProgram.name, + 1500e3, + "Opens up FTP Ports", + ), + RelaySMTPProgram: new DarkWebItem( + Programs.RelaySMTPProgram.name, + 5e6, + "Opens up SMTP Ports", + ), + HTTPWormProgram: new DarkWebItem( + Programs.HTTPWormProgram.name, + 30e6, + "Opens up HTTP Ports", + ), + SQLInjectProgram: new DarkWebItem( + Programs.SQLInjectProgram.name, + 250e6, + "Opens up SQL Ports", + ), + DeepscanV1: new DarkWebItem( + Programs.DeepscanV1.name, + 500000, + "Enables 'scan-analyze' with a depth up to 5", + ), + DeepscanV2: new DarkWebItem( + Programs.DeepscanV2.name, + 25e6, + "Enables 'scan-analyze' with a depth up to 10", + ), + AutolinkProgram: new DarkWebItem( + Programs.AutoLink.name, + 1e6, + "Enables direct connect via 'scan-analyze'", + ), + ServerProfilerProgram: new DarkWebItem( + Programs.ServerProfiler.name, + 1e6, + "Displays hacking and Netscript-related information about a server", + ), }; diff --git a/src/DevMenu.jsx b/src/DevMenu.jsx index cbb745785..bbcb9bd52 100644 --- a/src/DevMenu.jsx +++ b/src/DevMenu.jsx @@ -1,9 +1,9 @@ import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { CodingContractTypes } from "./CodingContracts"; import { - generateContract, - generateRandomContract, - generateRandomContractOnHome, + generateContract, + generateRandomContract, + generateRandomContractOnHome, } from "./CodingContractGenerator"; import { Companies } from "./Company/Companies"; import { Programs } from "./Programs/Programs"; @@ -27,7 +27,6 @@ import { Money } from "./ui/React/Money"; import React from "react"; import ReactDOM from "react-dom"; - const Component = React.Component; // Update as additional BitNodes get implemented @@ -38,1288 +37,1760 @@ const tonsPP = 1e27; const tonsP = 1e12; class ValueAdjusterComponent extends Component { - constructor(props) { - super(props); - this.state = { value: '' }; - this.setValue = this.setValue.bind(this); - } + constructor(props) { + super(props); + this.state = { value: "" }; + this.setValue = this.setValue.bind(this); + } - setValue(event) { - this.setState({ value: parseFloat(event.target.value) }); - } + setValue(event) { + this.setState({ value: parseFloat(event.target.value) }); + } - render() { - const { title, add, subtract, reset } = this.props; - return ( - <> - - - - - - ); - } + render() { + const { title, add, subtract, reset } = this.props; + return ( + <> + + + + + + ); + } } class DevMenuComponent extends Component { - constructor(props) { - super(props); - this.state = { - company: 'ECorp', - faction: 'Illuminati', - program: 'NUKE.exe', - server: 'home', - augmentation: 'Augmented Targeting I', - codingcontract: 'Find Largest Prime Factor', + constructor(props) { + super(props); + this.state = { + company: "ECorp", + faction: "Illuminati", + program: "NUKE.exe", + server: "home", + augmentation: "Augmented Targeting I", + codingcontract: "Find Largest Prime Factor", + }; + + this.setSF = this.setSF.bind(this); + this.setAllSF = this.setAllSF.bind(this); + this.clearExploits = this.clearExploits.bind(this); + this.processStocks = this.processStocks.bind(this); + this.setStockPrice = this.setStockPrice.bind(this); + this.viewStockCaps = this.viewStockCaps.bind(this); + + this.setFactionDropdown = this.setFactionDropdown.bind(this); + this.setCompanyDropdown = this.setCompanyDropdown.bind(this); + this.setProgramDropdown = this.setProgramDropdown.bind(this); + this.setServerDropdown = this.setServerDropdown.bind(this); + this.setAugmentationDropdown = this.setAugmentationDropdown.bind(this); + this.setCodingcontractDropdown = this.setCodingcontractDropdown.bind(this); + + this.receiveInvite = this.receiveInvite.bind(this); + this.modifyFactionRep = this.modifyFactionRep.bind(this); + this.resetFactionRep = this.resetFactionRep.bind(this); + this.modifyFactionFavor = this.modifyFactionFavor.bind(this); + this.resetFactionFavor = this.resetFactionFavor.bind(this); + this.queueAug = this.queueAug.bind(this); + this.addProgram = this.addProgram.bind(this); + this.rootServer = this.rootServer.bind(this); + this.minSecurity = this.minSecurity.bind(this); + this.maxMoney = this.maxMoney.bind(this); + this.modifyCompanyRep = this.modifyCompanyRep.bind(this); + this.resetCompanyRep = this.resetCompanyRep.bind(this); + this.modifyCompanyFavor = this.modifyCompanyFavor.bind(this); + this.resetCompanyFavor = this.resetCompanyFavor.bind(this); + this.specificContract = this.specificContract.bind(this); + } + + setFactionDropdown(event) { + this.setState({ faction: event.target.value }); + } + + setCompanyDropdown(event) { + this.setState({ company: event.target.value }); + } + + setProgramDropdown(event) { + this.setState({ program: event.target.value }); + } + + setServerDropdown(event) { + this.setState({ server: event.target.value }); + } + + setAugmentationDropdown(event) { + this.setState({ augmentation: event.target.value }); + } + + setCodingcontractDropdown(event) { + this.setState({ codingcontract: event.target.value }); + } + + addMoney(n) { + return function () { + Player.gainMoney(n); + }; + } + + upgradeRam() { + Player.getHomeComputer().maxRam *= 2; + } + + quickB1tFlum3() { + hackWorldDaemon(Player.bitNodeN, true, true); + } + + b1tflum3() { + hackWorldDaemon(Player.bitNodeN, true); + } + + quickHackW0r1dD43m0n() { + hackWorldDaemon(Player.bitNodeN, false, true); + } + + hackW0r1dD43m0n() { + hackWorldDaemon(Player.bitNodeN); + } + + modifyExp(stat, modifier) { + return function (exp) { + switch (stat) { + case "hacking": + if (exp) { + Player.gainHackingExp(exp * modifier); + } + break; + case "strength": + if (exp) { + Player.gainStrengthExp(exp * modifier); + } + break; + case "defense": + if (exp) { + Player.gainDefenseExp(exp * modifier); + } + break; + case "dexterity": + if (exp) { + Player.gainDexterityExp(exp * modifier); + } + break; + case "agility": + if (exp) { + Player.gainAgilityExp(exp * modifier); + } + break; + case "charisma": + if (exp) { + Player.gainCharismaExp(exp * modifier); + } + break; + case "intelligence": + if (exp) { + Player.gainIntelligenceExp(exp * modifier); + } + break; + } + Player.updateSkillLevels(); + }; + } + + modifyKarma(modifier) { + return function (amt) { + Player.karma += amt * modifier; + }; + } + + tonsOfExp() { + Player.gainHackingExp(tonsPP); + Player.gainStrengthExp(tonsPP); + Player.gainDefenseExp(tonsPP); + Player.gainDexterityExp(tonsPP); + Player.gainAgilityExp(tonsPP); + Player.gainCharismaExp(tonsPP); + Player.gainIntelligenceExp(tonsPP); + Player.updateSkillLevels(); + } + + resetAllExp() { + Player.hacking_exp = 0; + Player.strength_exp = 0; + Player.defense_exp = 0; + Player.dexterity_exp = 0; + Player.agility_exp = 0; + Player.charisma_exp = 0; + Player.intelligence_exp = 0; + Player.updateSkillLevels(); + } + + resetExperience(stat) { + return function () { + switch (stat) { + case "hacking": + Player.hacking_exp = 0; + break; + case "strength": + Player.strength_exp = 0; + break; + case "defense": + Player.defense_exp = 0; + break; + case "dexterity": + Player.dexterity_exp = 0; + break; + case "agility": + Player.agility_exp = 0; + break; + case "charisma": + Player.charisma_exp = 0; + break; + case "intelligence": + Player.intelligence_exp = 0; + break; + } + Player.updateSkillLevels(); + }; + } + + resetKarma() { + return function () { + Player.karma = 0; + }; + } + + enableIntelligence() { + if (Player.intelligence === 0) { + Player.intelligence = 1; + Player.updateSkillLevels(); + } + } + + disableIntelligence() { + Player.intelligence_exp = 0; + Player.intelligence = 0; + Player.updateSkillLevels(); + } + + receiveInvite() { + Player.receiveInvite(this.state.faction); + } + + receiveAllInvites() { + for (const i in Factions) { + Player.receiveInvite(Factions[i].name); + } + } + + modifyFactionRep(modifier) { + return (reputation) => { + const fac = Factions[this.state.faction]; + if (fac != null && !isNaN(reputation)) { + fac.playerReputation += reputation * modifier; + } + }; + } + + resetFactionRep() { + const fac = Factions[this.state.faction]; + if (fac != null) { + fac.playerReputation = 0; + } + } + + modifyFactionFavor(modifier) { + return (favor) => { + const fac = Factions[this.state.faction]; + if (fac != null && !isNaN(favor)) { + fac.favor += favor * modifier; + } + }; + } + + resetFactionFavor() { + const fac = Factions[this.state.faction]; + if (fac != null) { + fac.favor = 0; + } + } + + tonsOfRep() { + for (const i in Factions) { + Factions[i].playerReputation = tonsPP; + } + } + + resetAllRep() { + for (const i in Factions) { + Factions[i].playerReputation = 0; + } + } + + tonsOfFactionFavor() { + for (const i in Factions) { + Factions[i].favor = tonsPP; + } + } + + resetAllFactionFavor() { + for (const i in Factions) { + Factions[i].favor = 0; + } + } + + queueAug() { + Player.queueAugmentation(this.state.augmentation); + } + + queueAllAugs() { + for (const i in AugmentationNames) { + const augName = AugmentationNames[i]; + Player.queueAugmentation(augName); + } + } + + setSF(sfN, sfLvl) { + return function () { + if (sfLvl === 0) { + Player.sourceFiles = Player.sourceFiles.filter((sf) => sf.n !== sfN); + return; + } + + if (!Player.sourceFiles.some((sf) => sf.n === sfN)) { + Player.sourceFiles.push(new PlayerOwnedSourceFile(sfN, sfLvl)); + return; + } + + for (let i = 0; i < Player.sourceFiles.length; i++) { + if (Player.sourceFiles[i].n === sfN) { + Player.sourceFiles[i].lvl = sfLvl; } + } + }; + } - this.setSF = this.setSF.bind(this); - this.setAllSF = this.setAllSF.bind(this); - this.clearExploits = this.clearExploits.bind(this); - this.processStocks = this.processStocks.bind(this); - this.setStockPrice = this.setStockPrice.bind(this); - this.viewStockCaps = this.viewStockCaps.bind(this); + setAllSF(sfLvl) { + return () => { + for (let i = 0; i < validSFN.length; i++) { + this.setSF(validSFN[i], sfLvl)(); + } + }; + } - this.setFactionDropdown = this.setFactionDropdown.bind(this); - this.setCompanyDropdown = this.setCompanyDropdown.bind(this); - this.setProgramDropdown = this.setProgramDropdown.bind(this); - this.setServerDropdown = this.setServerDropdown.bind(this); - this.setAugmentationDropdown = this.setAugmentationDropdown.bind(this); - this.setCodingcontractDropdown = this.setCodingcontractDropdown.bind(this); + clearExploits() { + Player.exploits = []; + } - this.receiveInvite = this.receiveInvite.bind(this); - this.modifyFactionRep = this.modifyFactionRep.bind(this); - this.resetFactionRep = this.resetFactionRep.bind(this); - this.modifyFactionFavor = this.modifyFactionFavor.bind(this); - this.resetFactionFavor = this.resetFactionFavor.bind(this); - this.queueAug = this.queueAug.bind(this); - this.addProgram = this.addProgram.bind(this); - this.rootServer = this.rootServer.bind(this); - this.minSecurity = this.minSecurity.bind(this); - this.maxMoney = this.maxMoney.bind(this); - this.modifyCompanyRep = this.modifyCompanyRep.bind(this); - this.resetCompanyRep = this.resetCompanyRep.bind(this); - this.modifyCompanyFavor = this.modifyCompanyFavor.bind(this); - this.resetCompanyFavor = this.resetCompanyFavor.bind(this); - this.specificContract = this.specificContract.bind(this); + addProgram() { + const program = this.state.program; + if (!Player.hasProgram(program)) { + Player.getHomeComputer().programs.push(program); + } + } + + addAllPrograms() { + for (const i in Programs) { + if (!Player.hasProgram(Programs[i].name)) { + Player.getHomeComputer().programs.push(Programs[i].name); + } + } + } + + rootServer() { + const serverName = this.state.server; + const server = GetServerByHostname(serverName); + + server.hasAdminRights = true; + server.sshPortOpen = true; + server.ftpPortOpen = true; + server.smtpPortOpen = true; + server.httpPortOpen = true; + server.sqlPortOpen = true; + server.openPortCount = 5; + } + + rootAllServers() { + for (const i in AllServers) { + AllServers[i].hasAdminRights = true; + AllServers[i].sshPortOpen = true; + AllServers[i].ftpPortOpen = true; + AllServers[i].smtpPortOpen = true; + AllServers[i].httpPortOpen = true; + AllServers[i].sqlPortOpen = true; + AllServers[i].openPortCount = 5; + } + } + + minSecurity() { + const serverName = this.state.server; + const server = GetServerByHostname(serverName); + server.hackDifficulty = server.minDifficulty; + } + + minAllSecurity() { + for (const i in AllServers) { + AllServers[i].hackDifficulty = AllServers[i].minDifficulty; + } + } + + maxMoney() { + const serverName = this.state.server; + const server = GetServerByHostname(serverName); + server.moneyAvailable = server.moneyMax; + } + + maxAllMoney() { + for (const i in AllServers) { + AllServers[i].moneyAvailable = AllServers[i].moneyMax; + } + } + + modifyCompanyRep(modifier) { + return (reputation) => { + const company = Companies[this.state.company]; + if (company != null && !isNaN(reputation)) { + company.playerReputation += reputation * modifier; + } + }; + } + + resetCompanyRep() { + const company = Companies[this.state.company]; + company.playerReputation = 0; + } + + modifyCompanyFavor(modifier) { + return (favor) => { + const company = Companies[this.state.company]; + if (company != null && !isNaN(favor)) { + company.favor += favor * modifier; + } + }; + } + + resetCompanyFavor() { + const company = Companies[this.state.company]; + company.favor = 0; + } + + tonsOfRepCompanies() { + for (const c in Companies) { + Companies[c].playerReputation = tonsP; + } + } + + resetAllRepCompanies() { + for (const c in Companies) { + Companies[c].playerReputation = 0; + } + } + + tonsOfFavorCompanies() { + for (const c in Companies) { + Companies[c].favor = tonsP; + } + } + + resetAllFavorCompanies() { + for (const c in Companies) { + Companies[c].favor = 0; + } + } + + modifyBladeburnerRank(modify) { + return function (rank) { + if (Player.bladeburner) { + Player.bladeburner.changeRank(Player, rank * modify); + } + }; + } + + resetBladeburnerRank() { + Player.bladeburner.rank = 0; + Player.bladeburner.maxRank = 0; + } + + addTonsBladeburnerRank() { + if (Player.bladeburner) { + Player.bladeburner.changeRank(Player, tonsP); + } + } + + modifyBladeburnerCycles(modify) { + return function (cycles) { + if (Player.bladeburner) { + Player.bladeburner.storedCycles += cycles * modify; + } + }; + } + + resetBladeburnerCycles() { + if (Player.bladeburner) { + Player.bladeburner.storedCycles = 0; + } + } + + addTonsBladeburnerCycles() { + if (Player.bladeburner) { + Player.bladeburner.storedCycles += tonsP; + } + } + + addTonsGangCycles() { + if (Player.gang) { + Player.gang.storedCycles = tonsP; + } + } + + modifyGangCycles(modify) { + return function (cycles) { + if (Player.gang) { + Player.gang.storedCycles += cycles * modify; + } + }; + } + + resetGangCycles() { + if (Player.gang) { + Player.gang.storedCycles = 0; + } + } + + addTonsCorporationFunds() { + if (Player.corporation) { + Player.corporation.funds = Player.corporation.funds.plus(1e99); + } + } + + addTonsCorporationCycles() { + if (Player.corporation) { + Player.corporation.storedCycles = tonsP; + } + } + + modifyCorporationCycles(modify) { + return function (cycles) { + if (Player.corporation) { + Player.corporation.storedCycles += cycles * modify; + } + }; + } + + resetCorporationCycles() { + if (Player.corporation) { + Player.corporation.storedCycles = 0; + } + } + + finishCorporationProducts() { + if (!Player.corporation) return; + Player.corporation.divisions.forEach((div) => { + Object.keys(div.products).forEach( + (prod) => (div.products[prod].prog = 99.9), + ); + }); + } + + addCorporationResearch() { + if (!Player.corporation) return; + Player.corporation.divisions.forEach((div) => { + div.sciResearch.qty += 1e10; + }); + } + + specificContract() { + generateContract({ + problemType: this.state.codingcontract, + server: "home", + }); + } + + processStocks(sub) { + const inputSymbols = document + .getElementById("dev-stock-symbol") + .value.toString() + .replace(/\s/g, ""); + + let match = function () { + return true; + }; + + if (inputSymbols !== "" && inputSymbols !== "all") { + match = function (symbol) { + return inputSymbols.split(",").includes(symbol); + }; } - setFactionDropdown(event) { - this.setState({ faction: event.target.value }); - } - - setCompanyDropdown(event) { - this.setState({ company: event.target.value }); - } - - setProgramDropdown(event) { - this.setState({ program: event.target.value }); - } - - setServerDropdown(event) { - this.setState({ server: event.target.value }); - } - - setAugmentationDropdown(event) { - this.setState({ augmentation: event.target.value }); - } - - setCodingcontractDropdown(event) { - this.setState({ codingcontract: event.target.value }); - } - - addMoney(n) { - return function() { - Player.gainMoney(n); + for (const name in StockMarket) { + if (StockMarket.hasOwnProperty(name)) { + const stock = StockMarket[name]; + if (stock instanceof Stock && match(stock.symbol)) { + sub(stock); } - } - - upgradeRam() { - Player.getHomeComputer().maxRam *= 2; - } - - quickB1tFlum3() { - hackWorldDaemon(Player.bitNodeN, true, true); - } - - b1tflum3() { - hackWorldDaemon(Player.bitNodeN, true); - } - - quickHackW0r1dD43m0n() { - hackWorldDaemon(Player.bitNodeN, false, true); - } - - hackW0r1dD43m0n() { - hackWorldDaemon(Player.bitNodeN); - } - - modifyExp(stat, modifier) { - return function(exp) { - switch(stat) { - case "hacking": - if(exp) { - Player.gainHackingExp(exp*modifier); - } - break; - case "strength": - if(exp) { - Player.gainStrengthExp(exp*modifier); - } - break; - case "defense": - if(exp) { - Player.gainDefenseExp(exp*modifier); - } - break; - case "dexterity": - if(exp) { - Player.gainDexterityExp(exp*modifier); - } - break; - case "agility": - if(exp) { - Player.gainAgilityExp(exp*modifier); - } - break; - case "charisma": - if(exp) { - Player.gainCharismaExp(exp*modifier); - } - break; - case "intelligence": - if(exp) { - Player.gainIntelligenceExp(exp*modifier); - } - break; - } - Player.updateSkillLevels(); - } - } - - modifyKarma(modifier) { - return function(amt) { - Player.karma += (amt * modifier); - } - } - - tonsOfExp() { - Player.gainHackingExp(tonsPP); - Player.gainStrengthExp(tonsPP); - Player.gainDefenseExp(tonsPP); - Player.gainDexterityExp(tonsPP); - Player.gainAgilityExp(tonsPP); - Player.gainCharismaExp(tonsPP); - Player.gainIntelligenceExp(tonsPP); - Player.updateSkillLevels(); - } - - resetAllExp() { - Player.hacking_exp = 0; - Player.strength_exp = 0; - Player.defense_exp = 0; - Player.dexterity_exp = 0; - Player.agility_exp = 0; - Player.charisma_exp = 0; - Player.intelligence_exp = 0; - Player.updateSkillLevels(); - } - - resetExperience(stat) { - return function() { - switch(stat) { - case "hacking": - Player.hacking_exp = 0; - break; - case "strength": - Player.strength_exp = 0; - break; - case "defense": - Player.defense_exp = 0; - break; - case "dexterity": - Player.dexterity_exp = 0; - break; - case "agility": - Player.agility_exp = 0; - break; - case "charisma": - Player.charisma_exp = 0; - break; - case "intelligence": - Player.intelligence_exp = 0; - break; - } - Player.updateSkillLevels(); - } - } - - resetKarma() { - return function() { - Player.karma = 0; - } - } - - enableIntelligence() { - if(Player.intelligence === 0) { - Player.intelligence = 1; - Player.updateSkillLevels(); - } - } - - disableIntelligence() { - Player.intelligence_exp = 0; - Player.intelligence = 0; - Player.updateSkillLevels(); - } - - receiveInvite() { - Player.receiveInvite(this.state.faction); - } - - receiveAllInvites() { - for (const i in Factions) { - Player.receiveInvite(Factions[i].name); - } - } - - modifyFactionRep(modifier) { - return (reputation) => { - const fac = Factions[this.state.faction]; - if (fac != null && !isNaN(reputation)) { - fac.playerReputation += reputation*modifier; - } - } - } - - resetFactionRep() { - const fac = Factions[this.state.faction]; - if (fac != null) { - fac.playerReputation = 0; - } - } - - modifyFactionFavor(modifier) { - return (favor) => { - const fac = Factions[this.state.faction]; - if (fac != null && !isNaN(favor)) { - fac.favor += favor*modifier; - } - } - } - - resetFactionFavor() { - const fac = Factions[this.state.faction]; - if (fac != null) { - fac.favor = 0; - } - } - - tonsOfRep() { - for (const i in Factions) { - Factions[i].playerReputation = tonsPP; - } - } - - resetAllRep() { - for (const i in Factions) { - Factions[i].playerReputation = 0; - } - } - - tonsOfFactionFavor() { - for (const i in Factions) { - Factions[i].favor = tonsPP; - } - } - - resetAllFactionFavor() { - for (const i in Factions) { - Factions[i].favor = 0; - } - } - - queueAug() { - Player.queueAugmentation(this.state.augmentation); - } - - queueAllAugs() { - for (const i in AugmentationNames) { - const augName = AugmentationNames[i]; - Player.queueAugmentation(augName); - } - } - - setSF(sfN, sfLvl) { - return function() { - if (sfLvl === 0) { - Player.sourceFiles = Player.sourceFiles.filter((sf) => sf.n !== sfN); - return; - } - - if(!Player.sourceFiles.some((sf) => sf.n === sfN)) { - Player.sourceFiles.push(new PlayerOwnedSourceFile(sfN, sfLvl)); - return; - } - - for(let i = 0; i < Player.sourceFiles.length; i++) { - if (Player.sourceFiles[i].n === sfN) { - Player.sourceFiles[i].lvl = sfLvl; - } - } - } - } - - setAllSF(sfLvl) { - return () => { - for (let i = 0; i < validSFN.length; i++) { - this.setSF(validSFN[i], sfLvl)(); - } - } - } - - clearExploits() { - Player.exploits = []; - } - - addProgram() { - const program = this.state.program; - if(!Player.hasProgram(program)) { - Player.getHomeComputer().programs.push(program); - } - } - - addAllPrograms() { - for (const i in Programs) { - if(!Player.hasProgram(Programs[i].name)) { - Player.getHomeComputer().programs.push(Programs[i].name); - } - } - } - - rootServer() { - const serverName = this.state.server; - const server = GetServerByHostname(serverName); - - server.hasAdminRights = true; - server.sshPortOpen = true; - server.ftpPortOpen = true; - server.smtpPortOpen = true; - server.httpPortOpen = true; - server.sqlPortOpen = true; - server.openPortCount = 5; - } - - rootAllServers() { - for (const i in AllServers) { - AllServers[i].hasAdminRights = true; - AllServers[i].sshPortOpen = true; - AllServers[i].ftpPortOpen = true; - AllServers[i].smtpPortOpen = true; - AllServers[i].httpPortOpen = true; - AllServers[i].sqlPortOpen = true; - AllServers[i].openPortCount = 5; - } - } - - minSecurity() { - const serverName = this.state.server; - const server = GetServerByHostname(serverName); - server.hackDifficulty = server.minDifficulty; - } - - minAllSecurity() { - for (const i in AllServers) { - AllServers[i].hackDifficulty = AllServers[i].minDifficulty; - } - } - - maxMoney() { - const serverName = this.state.server; - const server = GetServerByHostname(serverName); - server.moneyAvailable = server.moneyMax; - } - - maxAllMoney() { - for (const i in AllServers) { - AllServers[i].moneyAvailable = AllServers[i].moneyMax; - } - } - - modifyCompanyRep(modifier) { - return (reputation) => { - const company = Companies[this.state.company]; - if (company != null && !isNaN(reputation)) { - company.playerReputation += reputation*modifier; - } - } - } - - resetCompanyRep() { - const company = Companies[this.state.company]; - company.playerReputation = 0; - } - - modifyCompanyFavor(modifier) { - return (favor) => { - const company = Companies[this.state.company]; - if (company != null && !isNaN(favor)) { - company.favor += favor*modifier; - } - } - } - - resetCompanyFavor() { - const company = Companies[this.state.company]; - company.favor = 0; - } - - tonsOfRepCompanies() { - for (const c in Companies) { - Companies[c].playerReputation = tonsP; - } - } - - resetAllRepCompanies() { - for (const c in Companies) { - Companies[c].playerReputation = 0; - } - } - - tonsOfFavorCompanies() { - for (const c in Companies) { - Companies[c].favor = tonsP; - } - } - - resetAllFavorCompanies() { - for (const c in Companies) { - Companies[c].favor = 0; - } - } - - modifyBladeburnerRank(modify) { - return function(rank) { - if (Player.bladeburner) { - Player.bladeburner.changeRank(Player, rank*modify); - } - } - } - - resetBladeburnerRank() { - Player.bladeburner.rank = 0; - Player.bladeburner.maxRank = 0; - } - - addTonsBladeburnerRank() { - if (Player.bladeburner) { - Player.bladeburner.changeRank(Player, tonsP); - } - } - - modifyBladeburnerCycles(modify) { - return function(cycles) { - if (Player.bladeburner) { - Player.bladeburner.storedCycles += cycles*modify; - } - } - } - - resetBladeburnerCycles() { - if (Player.bladeburner) { - Player.bladeburner.storedCycles = 0; - } - } - - addTonsBladeburnerCycles() { - if (Player.bladeburner) { - Player.bladeburner.storedCycles += tonsP; - } - } - - addTonsGangCycles() { - if (Player.gang) { - Player.gang.storedCycles = tonsP; - } - } - - modifyGangCycles(modify) { - return function(cycles) { - if (Player.gang) { - Player.gang.storedCycles += cycles*modify; - } - } - } - - resetGangCycles() { - if (Player.gang) { - Player.gang.storedCycles = 0; - } - } - - addTonsCorporationFunds() { - if(Player.corporation) { - Player.corporation.funds = Player.corporation.funds.plus(1e99); - } - } - - addTonsCorporationCycles() { - if (Player.corporation) { - Player.corporation.storedCycles = tonsP; - } - } - - modifyCorporationCycles(modify) { - return function(cycles) { - if (Player.corporation) { - Player.corporation.storedCycles += cycles*modify; - } - } - } - - resetCorporationCycles() { - if (Player.corporation) { - Player.corporation.storedCycles = 0; - } - } - - finishCorporationProducts() { - if(!Player.corporation) return; - Player.corporation.divisions.forEach(div => { - Object.keys(div.products).forEach(prod => div.products[prod].prog = 99.9) - }); - } - - addCorporationResearch() { - if(!Player.corporation) return; - Player.corporation.divisions.forEach(div => { - div.sciResearch.qty += 1e10; - }); - } - - specificContract() { - generateContract({ - problemType: this.state.codingcontract, - server: "home", - }); - } - - processStocks(sub) { - const inputSymbols = document.getElementById('dev-stock-symbol').value.toString().replace(/\s/g, ''); - - let match = function() { return true; } - - if (inputSymbols !== '' && inputSymbols !== 'all') { - match = function(symbol) { - return inputSymbols.split(',').includes(symbol); - }; - } - - for (const name in StockMarket) { - if (StockMarket.hasOwnProperty(name)) { - const stock = StockMarket[name]; - if (stock instanceof Stock && match(stock.symbol)) { - sub(stock); - } - } - } - } - - setStockPrice() { - const price = parseFloat(document.getElementById('dev-stock-price').value); - - if (!isNaN(price)) { - this.processStocks((stock) => { - stock.price = price; - }); - } - } - - viewStockCaps() { - let stocks = []; - this.processStocks((stock) => { - stocks.push( - {stock.symbol} - - ); - }); - dialogBoxCreate( - {stocks} -
    StockPrice cap
    ); - } - - sleeveMaxAllShock() { - for (let i = 0; i < Player.sleeves.length; ++i) { - Player.sleeves[i].shock = 0; - } - } - - sleeveClearAllShock() { - for (let i = 0; i < Player.sleeves.length; ++i) { - Player.sleeves[i].shock = 100; - } - } - - sleeveSyncMaxAll() { - for (let i = 0; i < Player.sleeves.length; ++i) { - Player.sleeves[i].sync = 100; - } - } - - sleeveSyncClearAll() { - for (let i = 0; i < Player.sleeves.length; ++i) { - Player.sleeves[i].sync = 0; - } - } - - timeskip(time) { - return () => { - Player.lastUpdate -= time; - Engine._lastUpdate -= time; - saveObject.saveGame(Engine.indexedDb); - setTimeout(() => location.reload(), 1000); - }; - } - - render() { - let factions = []; - for (const i in Factions) { - factions.push(); - } - - let augs = []; - for (const i in AugmentationNames) { - augs.push(); - } - - let programs = []; - for (const i in Programs) { - programs.push(); - } - - let sourceFiles = []; - validSFN.forEach( i => sourceFiles.push( - - SF-{i}: - - - - - - - , - )); - - - - let servers = []; - for (const i in AllServers) { - const hn = AllServers[i].hostname; - servers.push(); - } - - let companies = []; - for (const c in Companies) { - const name = Companies[c].name; - companies.push(); - } - - const contractTypes = []; - const contractTypeNames = Object.keys(CodingContractTypes) - for (let i = 0; i < contractTypeNames.length; i++) { - const name = contractTypeNames[i]; - contractTypes.push(); - } - - return ( -
    -
    -

    Development Menu - Only meant to be used for testing/debugging

    -
    -
    -

    Generic

    -
    -
    - - - - - - -
    -
    - - - - -
    -
    -
    + } + } + } + + setStockPrice() { + const price = parseFloat(document.getElementById("dev-stock-price").value); + + if (!isNaN(price)) { + this.processStocks((stock) => { + stock.price = price; + }); + } + } + + viewStockCaps() { + let stocks = []; + this.processStocks((stock) => { + stocks.push( + + {stock.symbol} + + + + , + ); + }); + dialogBoxCreate( + + + + + + + {stocks} + +
    StockPrice cap
    , + ); + } + + sleeveMaxAllShock() { + for (let i = 0; i < Player.sleeves.length; ++i) { + Player.sleeves[i].shock = 0; + } + } + + sleeveClearAllShock() { + for (let i = 0; i < Player.sleeves.length; ++i) { + Player.sleeves[i].shock = 100; + } + } + + sleeveSyncMaxAll() { + for (let i = 0; i < Player.sleeves.length; ++i) { + Player.sleeves[i].sync = 100; + } + } + + sleeveSyncClearAll() { + for (let i = 0; i < Player.sleeves.length; ++i) { + Player.sleeves[i].sync = 0; + } + } + + timeskip(time) { + return () => { + Player.lastUpdate -= time; + Engine._lastUpdate -= time; + saveObject.saveGame(Engine.indexedDb); + setTimeout(() => location.reload(), 1000); + }; + } + + render() { + let factions = []; + for (const i in Factions) { + factions.push( + , + ); + } + + let augs = []; + for (const i in AugmentationNames) { + augs.push( + , + ); + } + + let programs = []; + for (const i in Programs) { + programs.push( + , + ); + } + + let sourceFiles = []; + validSFN.forEach((i) => + sourceFiles.push( + + + SF-{i}: + + + + + + + + , + ), + ); + + let servers = []; + for (const i in AllServers) { + const hn = AllServers[i].hostname; + servers.push( + , + ); + } + + let companies = []; + for (const c in Companies) { + const name = Companies[c].name; + companies.push( + , + ); + } + + const contractTypes = []; + const contractTypeNames = Object.keys(CodingContractTypes); + for (let i = 0; i < contractTypeNames.length; i++) { + const name = contractTypeNames[i]; + contractTypes.push( + , + ); + } + + return ( +
    +
    +

    + Development Menu - Only meant to be used for testing/debugging +

    +
    +
    +

    Generic

    +
    +
    + + + + + + +
    +
    + + + + +
    +
    +
    -

    Experience / Stats

    +

    Experience / Stats

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    All: - - -
    - Hacking: - - -
    - Strength: - - -
    - Defense: - - -
    - Dexterity: - - -
    - Agility: - - -
    - Charisma: - - -
    - Intelligence: - - - - - - -
    - Karma: - - -
    + All: + + + +
    + Hacking: + + +
    + Strength: + + +
    + Defense: + + +
    + Dexterity: + + +
    + Agility: + + +
    + Charisma: + + +
    + Intelligence: + + + + + + +
    + Karma: + + +
    +
    -
    -
    -
    +
    +
    -

    Factions

    +

    Factions

    - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Faction:
    Invites:
    - Reputation: - - -
    - Favor: - - -
    All Reputation: - - -
    All Favor: - - -
    + Faction: + + +
    + Invites: + + + + +
    + Reputation: + + +
    + Favor: + + +
    + All Reputation: + + + +
    + All Favor: + + + +
    +
    +
    +
    +
    +
    +

    Augmentations

    +
    + + + + + + + + + + + +
    + Aug: + + +
    + Queue: + + + +
    +
    +
    +
    +
    +
    +

    Source-Files

    +
    + + + + + + + + + + + {sourceFiles} + +
    + Exploits: + + +
    + All: + + + + + +
    +
    +
    +
    +
    +
    +

    Programs

    +
    + + + + + + + + + + + +
    + Program: + + +
    + Add: + + + +
    +
    +
    +
    +
    +
    +

    Servers

    +
    + + + + + + + + + + + + + + + + + + + + + + +
    + Server: + + +
    + Root: + + + + +
    + Security: + + + + +
    + Money: + + + + +
    +
    +
    +
    +
    +
    +

    Companies

    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + Company: + + +
    + Reputation: + + +
    + Favor: + + +
    + All Reputation: + + + +
    + All Favor: + + + +
    +
    +
    - - -
    -
    -
    -
    -
    -

    Augmentations

    -
    - - - - - - - - - - - -
    Aug:
    Queue: -
    -
    -
    -
    -
    -
    -

    Source-Files

    -
    - - - - - - - - - - - {sourceFiles} - -
    Exploits: - -
    All: - - - - -
    -
    -
    -
    -
    -
    -

    Programs

    -
    - - - - - - - - - - - -
    Program:
    Add: - - -
    -
    -
    -
    -
    -
    -

    Servers

    -
    - - - - - - - - - - - - - - - - - - - - - - -
    Server:
    Root:
    Security:
    Money:
    -
    -
    -
    -
    -
    -

    Companies

    -
    - - - - - - - - - - - - - - - - - - - - - - - -
    Company:
    Reputation: - -
    Favor: - -
    All Reputation: - - -
    All Favor: - - -
    -
    -
    - - {Player.bladeburner instanceof Bladeburner && -
    -
    -
    + {Player.bladeburner instanceof Bladeburner && ( +
    +
    +

    Bladeburner

    -
    - + +
    - - - - - - - - - - + + + + + + + + + + -
    Rank: - -
    Cycles: - -
    + Rank: + + + + +
    + Cycles: + + + + +
    -
    -
    - } + +
    +
    + )} - {Player.inGang() && -
    -
    -
    + {Player.inGang() && ( +
    +
    +

    Gang

    -
    - + +
    - - - - - + + + + + -
    Cycles: - -
    + Cycles: + + + + +
    -
    -
    - } + +
    +
    + )} - {Player.hasCorporation() && -
    -
    -
    + {Player.hasCorporation() && ( +
    +
    +

    Corporation

    -
    - + +
    - - - - - - - - - - - - - - + + + + + + + + + + + + + + -
    Cycles: - -
    - -
    - -
    + +
    + Cycles: + + + + +
    + +
    + +
    -
    -
    - } + +
    +
    + )} - -
    -
    +
    +
    -

    Coding Contracts

    +

    Coding Contracts

    - + - + - + - +
    - - - + + +
    - - - - + + +
    +
    -
    - {Player.hasWseAccount && -
    -
    -
    + {Player.hasWseAccount && ( +
    +
    +

    Stock Market

    -
    - + +
    - - - - - - - - - - - - + + + + + + + + + + + + -
    Symbol:
    Price: - - -
    Caps:
    + Symbol: + + +
    + Price: + + + +
    + Caps: + + +
    -
    -
    - } + +
    +
    + )} - {Player.sleeves.length > 0 && -
    -
    -
    + {Player.sleeves.length > 0 && ( +
    +
    +

    Sleeves

    -
    - + +
    - - - - - - - - - - + + + + + + + + + + -
    Shock:
    Sync:
    + Shock: + + + + +
    + Sync: + + + + +
    -
    -
    - } + +
    +
    + )} -
    -
    +
    +
    -

    Offline time skip:

    +

    Offline time skip:

    - - - + + +
    +
    -
    - -
    - ); - } +
    + ); + } } const devMenuContainerId = "dev-menu-container"; export function createDevMenu() { - if (process.env.NODE_ENV !== "development") { - throw new Error("Cannot create Dev Menu because you are not in a dev build"); - } + if (process.env.NODE_ENV !== "development") { + throw new Error( + "Cannot create Dev Menu because you are not in a dev build", + ); + } - // Add everything to container, then append to main menu - const devMenuContainer = createElement("div", { - class: "generic-menupage-container", - id: devMenuContainerId, - }); + // Add everything to container, then append to main menu + const devMenuContainer = createElement("div", { + class: "generic-menupage-container", + id: devMenuContainerId, + }); - const entireGameContainer = document.getElementById("entire-game-container"); - if (entireGameContainer == null) { - throw new Error("Could not find entire-game-container DOM element"); - } - entireGameContainer.appendChild(devMenuContainer); + const entireGameContainer = document.getElementById("entire-game-container"); + if (entireGameContainer == null) { + throw new Error("Could not find entire-game-container DOM element"); + } + entireGameContainer.appendChild(devMenuContainer); - ReactDOM.render(, devMenuContainer); + ReactDOM.render(, devMenuContainer); } export function closeDevMenu() { - removeElementById(devMenuContainerId); + removeElementById(devMenuContainerId); } diff --git a/src/Diagnostic/FileDiagnosticPopup.tsx b/src/Diagnostic/FileDiagnosticPopup.tsx index e83420f90..7287a1d87 100644 --- a/src/Diagnostic/FileDiagnosticPopup.tsx +++ b/src/Diagnostic/FileDiagnosticPopup.tsx @@ -6,63 +6,79 @@ import { Accordion } from "../ui/React/Accordion"; import { numeralWrapper } from "../ui/numeralFormat"; interface IServerProps { - ip: string; + ip: string; } export function ServerAccordion(props: IServerProps): React.ReactElement { - const server = AllServers[props.ip]; - let totalSize = 0; - for(const f of server.scripts) { - totalSize += f.code.length; - } + const server = AllServers[props.ip]; + let totalSize = 0; + for (const f of server.scripts) { + totalSize += f.code.length; + } - for(const f of server.textFiles) { - totalSize += f.text.length; - } + for (const f of server.textFiles) { + totalSize += f.text.length; + } - if(totalSize === 0) { - return <> - } + if (totalSize === 0) { + return <>; + } - interface File { - name: string; - size: number; - } + interface File { + name: string; + size: number; + } - const files: File[] = []; + const files: File[] = []; - for(const f of server.scripts) { - files.push({name: f.filename, size: f.code.length}); - } + for (const f of server.scripts) { + files.push({ name: f.filename, size: f.code.length }); + } - for(const f of server.textFiles) { - files.push({name: f.fn, size: f.text.length}); - } + for (const f of server.textFiles) { + files.push({ name: f.fn, size: f.text.length }); + } - files.sort((a: File, b: File): number => b.size-a.size); + files.sort((a: File, b: File): number => b.size - a.size); - return {server.hostname} ({numeralWrapper.formatBigNumber(totalSize)}b)} - panelContent={
      - {files.map((file: File) =>
    • {file.name}: {numeralWrapper.formatBigNumber(file.size)}b
    • ) } -
    } + return ( + + {server.hostname} ({numeralWrapper.formatBigNumber(totalSize)}b) + + } + panelContent={ +
      + {files.map((file: File) => ( +
    • + {file.name}: {numeralWrapper.formatBigNumber(file.size)}b +
    • + ))} +
    + } /> + ); } interface IProps {} export function FileDiagnosticPopup(props: IProps): React.ReactElement { - const ips: string[] = []; - for(const ip of Object.keys(AllServers)) { - ips.push(ip); - } + const ips: string[] = []; + for (const ip of Object.keys(AllServers)) { + ips.push(ip); + } - return (<> -

    - Welcome to the file diagnostic! If your save file is really big it's - likely because you have too many text/scripts. This tool can help you - narrow down where they are. -

    - {ips.map((ip: string) => )} - ); -} \ No newline at end of file + return ( + <> +

    + Welcome to the file diagnostic! If your save file is really big it's + likely because you have too many text/scripts. This tool can help you + narrow down where they are. +

    + {ips.map((ip: string) => ( + + ))} + + ); +} diff --git a/src/Exploits/Exploit.ts b/src/Exploits/Exploit.ts index 03bb480a0..77da933c7 100644 --- a/src/Exploits/Exploit.ts +++ b/src/Exploits/Exploit.ts @@ -11,31 +11,30 @@ Source-File minus 1 is extremely weak because it can be fully level up quickly. */ export enum Exploit { - UndocumentedFunctionCall = 'UndocumentedFunctionCall', - Unclickable = 'Unclickable', - PrototypeTampering = 'PrototypeTampering', - Bypass = 'Bypass', - // To the players reading this. Yes you're supposed to add EditSaveFile by - // editing your save file, yes you could add them all, no we don't care - // that's not the point. - EditSaveFile = 'EditSaveFile' + UndocumentedFunctionCall = "UndocumentedFunctionCall", + Unclickable = "Unclickable", + PrototypeTampering = "PrototypeTampering", + Bypass = "Bypass", + // To the players reading this. Yes you're supposed to add EditSaveFile by + // editing your save file, yes you could add them all, no we don't care + // that's not the point. + EditSaveFile = "EditSaveFile", } const names: { - [key: string]: string; + [key: string]: string; } = { - 'UndocumentedFunctionCall': 'by looking beyond the documentation.', - 'EditSaveFile': 'by editing your save file.', - 'PrototypeTampering': 'by tampering with Numbers prototype.', - 'Unclickable': 'by clicking the unclickable.', - 'Bypass': 'by circumventing the ram cost of document.', -} - + UndocumentedFunctionCall: "by looking beyond the documentation.", + EditSaveFile: "by editing your save file.", + PrototypeTampering: "by tampering with Numbers prototype.", + Unclickable: "by clicking the unclickable.", + Bypass: "by circumventing the ram cost of document.", +}; export function ExploitName(exploit: string): string { - return names[exploit]; + return names[exploit]; } export function sanitizeExploits(exploits: string[]): string[] { - return exploits.filter((e: string) => Object.keys(Exploit).includes(e)); -} \ No newline at end of file + return exploits.filter((e: string) => Object.keys(Exploit).includes(e)); +} diff --git a/src/Exploits/applyExploits.ts b/src/Exploits/applyExploits.ts index fede837a4..7612e03ae 100644 --- a/src/Exploits/applyExploits.ts +++ b/src/Exploits/applyExploits.ts @@ -1,42 +1,42 @@ import { Player } from "../Player"; export function applyExploit(): void { - if (Player.exploits && Player.exploits.length === 0) { - return; - } - const inc = Math.pow(1.001, Player.exploits.length); - const dec = Math.pow(0.999, Player.exploits.length); + if (Player.exploits && Player.exploits.length === 0) { + return; + } + const inc = Math.pow(1.001, Player.exploits.length); + const dec = Math.pow(0.999, Player.exploits.length); - Player.hacking_chance_mult *= inc; - Player.hacking_speed_mult *= inc; - Player.hacking_money_mult *= inc; - Player.hacking_grow_mult *= inc; - Player.hacking_mult *= inc; + Player.hacking_chance_mult *= inc; + Player.hacking_speed_mult *= inc; + Player.hacking_money_mult *= inc; + Player.hacking_grow_mult *= inc; + Player.hacking_mult *= inc; - Player.strength_mult *= inc; - Player.defense_mult *= inc; - Player.dexterity_mult *= inc; - Player.agility_mult *= inc; - Player.charisma_mult *= inc; + Player.strength_mult *= inc; + Player.defense_mult *= inc; + Player.dexterity_mult *= inc; + Player.agility_mult *= inc; + Player.charisma_mult *= inc; - Player.hacking_exp_mult *= inc; - Player.strength_exp_mult *= inc; - Player.defense_exp_mult *= inc; - Player.dexterity_exp_mult *= inc; - Player.agility_exp_mult *= inc; - Player.charisma_exp_mult *= inc; + Player.hacking_exp_mult *= inc; + Player.strength_exp_mult *= inc; + Player.defense_exp_mult *= inc; + Player.dexterity_exp_mult *= inc; + Player.agility_exp_mult *= inc; + Player.charisma_exp_mult *= inc; - Player.company_rep_mult *= inc; - Player.faction_rep_mult *= inc; + Player.company_rep_mult *= inc; + Player.faction_rep_mult *= inc; - Player.crime_money_mult *= inc; - Player.crime_success_mult *= inc; + Player.crime_money_mult *= inc; + Player.crime_success_mult *= inc; - Player.hacknet_node_money_mult *= inc; - Player.hacknet_node_purchase_cost_mult *= dec; - Player.hacknet_node_ram_cost_mult *= dec; - Player.hacknet_node_core_cost_mult *= dec; - Player.hacknet_node_level_cost_mult *= dec; + Player.hacknet_node_money_mult *= inc; + Player.hacknet_node_purchase_cost_mult *= dec; + Player.hacknet_node_ram_cost_mult *= dec; + Player.hacknet_node_core_cost_mult *= dec; + Player.hacknet_node_level_cost_mult *= dec; - Player.work_money_mult *= inc; + Player.work_money_mult *= inc; } diff --git a/src/Exploits/tampering.ts b/src/Exploits/tampering.ts index 23a20ed3b..2ef29f492 100644 --- a/src/Exploits/tampering.ts +++ b/src/Exploits/tampering.ts @@ -1,11 +1,11 @@ import { Player } from "../Player"; import { Exploit } from "./Exploit"; -(function() { - const a = 55; - setInterval(function() { - if (a.toExponential() !== "5.5e+1") { - Player.giveExploit(Exploit.PrototypeTampering); - } - }, 15*60*1000); // 15 minutes -})() \ No newline at end of file +(function () { + const a = 55; + setInterval(function () { + if (a.toExponential() !== "5.5e+1") { + Player.giveExploit(Exploit.PrototypeTampering); + } + }, 15 * 60 * 1000); // 15 minutes +})(); diff --git a/src/Exploits/unclickable.ts b/src/Exploits/unclickable.ts index 97cdbc214..752d048b6 100644 --- a/src/Exploits/unclickable.ts +++ b/src/Exploits/unclickable.ts @@ -1,24 +1,25 @@ import { Player } from "../Player"; import { Exploit } from "./Exploit"; -(function() { - function clickTheUnclickable(event: MouseEvent): void { - if(!event.target || !(event.target instanceof Element)) return; - const display = window.getComputedStyle(event.target as Element).display; - if(display === 'none' && event.isTrusted) - Player.giveExploit(Exploit.Unclickable); - } +(function () { + function clickTheUnclickable(event: MouseEvent): void { + if (!event.target || !(event.target instanceof Element)) return; + const display = window.getComputedStyle(event.target as Element).display; + if (display === "none" && event.isTrusted) + Player.giveExploit(Exploit.Unclickable); + } + function targetElement(): void { + const elem = document.getElementById("unclickable"); + if (elem == null) { + console.error( + "Could not find the unclickable elem for the related exploit.", + ); + return; + } + elem.addEventListener("click", clickTheUnclickable); + document.removeEventListener("DOMContentLoaded", targetElement); + } - function targetElement(): void { - const elem = document.getElementById('unclickable'); - if(elem == null) { - console.error('Could not find the unclickable elem for the related exploit.'); - return; - } - elem.addEventListener("click", clickTheUnclickable); - document.removeEventListener('DOMContentLoaded', targetElement); - } - - document.addEventListener('DOMContentLoaded', targetElement); -})(); \ No newline at end of file + document.addEventListener("DOMContentLoaded", targetElement); +})(); diff --git a/src/ExportBonus.tsx b/src/ExportBonus.tsx index 3a4861308..c9a245cf0 100644 --- a/src/ExportBonus.tsx +++ b/src/ExportBonus.tsx @@ -3,21 +3,21 @@ import { IPlayer } from "./PersonObjects/IPlayer"; export let LastExportBonus = 0; -const bonusTimer = 24*60*60*1000; // 24h +const bonusTimer = 24 * 60 * 60 * 1000; // 24h export function canGetBonus(): boolean { - const now = (new Date()).getTime() - if(now - LastExportBonus > bonusTimer) return true; - return false; + const now = new Date().getTime(); + if (now - LastExportBonus > bonusTimer) return true; + return false; } export function onExport(p: IPlayer): void { - if(!canGetBonus()) return; - for (const facName of p.factions) { - Factions[facName].favor++; - } - LastExportBonus = (new Date()).getTime(); + if (!canGetBonus()) return; + for (const facName of p.factions) { + Factions[facName].favor++; + } + LastExportBonus = new Date().getTime(); } export function setLastExportBonus(unixTime: number): void { - LastExportBonus = unixTime; -} \ No newline at end of file + LastExportBonus = unixTime; +} diff --git a/src/Faction/Faction.ts b/src/Faction/Faction.ts index 082023060..80292615a 100644 --- a/src/Faction/Faction.ts +++ b/src/Faction/Faction.ts @@ -1,103 +1,115 @@ import { CONSTANTS } from "../Constants"; -import { FactionInfo, - FactionInfos } from "./FactionInfo"; +import { FactionInfo, FactionInfos } from "./FactionInfo"; import { favorToRep, repToFavor } from "./formulas/favor"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; export class Faction { + /** + * Flag signalling whether the player has already received an invitation + * to this faction + */ + alreadyInvited = false; - /** - * Flag signalling whether the player has already received an invitation - * to this faction - */ - alreadyInvited = false; + /** + * Holds names of all augmentations that this Faction offers + */ + augmentations: string[] = []; - /** - * Holds names of all augmentations that this Faction offers - */ - augmentations: string[] = []; + /** + * Amount of favor the player has with this faction. + */ + favor = 0; - /** - * Amount of favor the player has with this faction. - */ - favor = 0; + /** + * Flag signalling whether player has been banned from this faction + */ + isBanned = false; - /** - * Flag signalling whether player has been banned from this faction - */ - isBanned = false; + /** + * Flag signalling whether player is a member of this faction + */ + isMember = false; - /** - * Flag signalling whether player is a member of this faction - */ - isMember = false; + /** + * Name of faction + */ + name = ""; - /** - * Name of faction - */ - name = ""; + /** + * Amount of reputation player has with this faction + */ + playerReputation = 0; - /** - * Amount of reputation player has with this faction - */ - playerReputation = 0; + /** + * Reputation from the last "prestige" that was not converted to favor. + * This reputation rolls over and is used for the next favor calculation + */ + rolloverRep = 0; - /** - * Reputation from the last "prestige" that was not converted to favor. - * This reputation rolls over and is used for the next favor calculation - */ - rolloverRep = 0; + constructor(name = "") { + this.name = name; + } - constructor(name="") { - this.name = name; + getInfo(): FactionInfo { + const info = FactionInfos[this.name]; + if (info == null) { + throw new Error( + `Missing faction from FactionInfos: ${this.name} this probably means the faction got corrupted somehow`, + ); } - getInfo(): FactionInfo { - const info = FactionInfos[this.name]; - if (info == null) { - throw new Error(`Missing faction from FactionInfos: ${this.name} this probably means the faction got corrupted somehow`); - } + return info; + } - return info; + gainFavor(): void { + if (this.favor == null) { + this.favor = 0; } + if (this.rolloverRep == null) { + this.rolloverRep = 0; + } + const res = this.getFavorGain(); + if (res.length !== 2) { + console.error("Invalid result from getFavorGain() function"); + return; + } + this.favor += res[0]; + this.rolloverRep = res[1]; + } - gainFavor(): void { - if (this.favor == null) { this.favor = 0; } - if (this.rolloverRep == null) { this.rolloverRep = 0; } - const res = this.getFavorGain(); - if (res.length !== 2) { - console.error("Invalid result from getFavorGain() function"); - return; - } - this.favor += res[0]; - this.rolloverRep = res[1]; + //Returns an array with [How much favor would be gained, how much rep would be left over] + getFavorGain(): number[] { + if (this.favor == null) { + this.favor = 0; } + if (this.rolloverRep == null) { + this.rolloverRep = 0; + } + const storedRep = favorToRep(this.favor - 1); + const totalRep = storedRep + this.rolloverRep + this.playerReputation; + const newFavor = Math.floor(repToFavor(totalRep)); + const newRep = favorToRep(newFavor); + return [newFavor - this.favor + 1, totalRep - newRep]; + } - //Returns an array with [How much favor would be gained, how much rep would be left over] - getFavorGain(): number[] { - if (this.favor == null) { this.favor = 0; } - if (this.rolloverRep == null) { this.rolloverRep = 0; } - const storedRep = favorToRep(this.favor-1); - const totalRep = storedRep+this.rolloverRep+this.playerReputation; - const newFavor = Math.floor(repToFavor(totalRep)); - const newRep = favorToRep(newFavor); - return [newFavor-this.favor+1, totalRep-newRep]; - } + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Faction", this); + } - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("Faction", this); - } - - /** - * Initiatizes a Faction object from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Faction { - return Generic_fromJSON(Faction, value.data); - } + /** + * Initiatizes a Faction object from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Faction { + return Generic_fromJSON(Faction, value.data); + } } Reviver.constructors.Faction = Faction; diff --git a/src/Faction/FactionHelpers.d.ts b/src/Faction/FactionHelpers.d.ts index 820c568c5..c16cda0fd 100644 --- a/src/Faction/FactionHelpers.d.ts +++ b/src/Faction/FactionHelpers.d.ts @@ -3,7 +3,17 @@ import { Faction } from "../Faction/Faction"; export declare function getNextNeurofluxLevel(): number; export declare function hasAugmentationPrereqs(aug: Augmentation): boolean; -export declare function purchaseAugmentationBoxCreate(aug: Augmentation, fac: Faction): void; -export declare function purchaseAugmentation(aug: Augmentation, fac: Faction, sing?: boolean): void; -export declare function displayFactionContent(factionName: string, initiallyOnAugmentationsPage: boolean=false); -export declare function joinFaction(faction: Faction): void; \ No newline at end of file +export declare function purchaseAugmentationBoxCreate( + aug: Augmentation, + fac: Faction, +): void; +export declare function purchaseAugmentation( + aug: Augmentation, + fac: Faction, + sing?: boolean, +): void; +export declare function displayFactionContent( + factionName: string, + initiallyOnAugmentationsPage: boolean = false, +); +export declare function joinFaction(faction: Faction): void; diff --git a/src/Faction/FactionHelpers.jsx b/src/Faction/FactionHelpers.jsx index a356468e7..5987878bb 100644 --- a/src/Faction/FactionHelpers.jsx +++ b/src/Faction/FactionHelpers.jsx @@ -15,10 +15,10 @@ import { Factions } from "./Factions"; import { HackingMission, setInMission } from "../Missions"; import { Player } from "../Player"; import { Settings } from "../Settings/Settings"; -import { - getHackingWorkRepGain, - getFactionSecurityWorkRepGain, - getFactionFieldWorkRepGain, +import { + getHackingWorkRepGain, + getFactionSecurityWorkRepGain, + getFactionFieldWorkRepGain, } from "../PersonObjects/formulas/reputation"; import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; @@ -27,247 +27,299 @@ import { dialogBoxCreate } from "../../utils/DialogBox"; import { factionInvitationBoxCreate } from "../../utils/FactionInvitationBox"; import { Money } from "../ui/React/Money"; import { - yesNoBoxCreate, - yesNoBoxGetYesButton, - yesNoBoxGetNoButton, - yesNoBoxClose, + yesNoBoxCreate, + yesNoBoxGetYesButton, + yesNoBoxGetNoButton, + yesNoBoxClose, } from "../../utils/YesNoBox"; export function inviteToFaction(faction) { - if (Settings.SuppressFactionInvites) { - faction.alreadyInvited = true; - Player.factionInvitations.push(faction.name); - if (routing.isOn(Page.Factions)) { - Engine.loadFactionsContent(); - } - } else { - factionInvitationBoxCreate(faction); + if (Settings.SuppressFactionInvites) { + faction.alreadyInvited = true; + Player.factionInvitations.push(faction.name); + if (routing.isOn(Page.Factions)) { + Engine.loadFactionsContent(); } + } else { + factionInvitationBoxCreate(faction); + } } export function joinFaction(faction) { - if(faction.isMember) return; - faction.isMember = true; - Player.factions.push(faction.name); - const factionInfo = faction.getInfo(); + if (faction.isMember) return; + faction.isMember = true; + Player.factions.push(faction.name); + const factionInfo = faction.getInfo(); - //Determine what factions you are banned from now that you have joined this faction - for(const i in factionInfo.enemies) { - const enemy = factionInfo.enemies[i]; - if (Factions[enemy] instanceof Faction) { - Factions[enemy].isBanned = true; - } + //Determine what factions you are banned from now that you have joined this faction + for (const i in factionInfo.enemies) { + const enemy = factionInfo.enemies[i]; + if (Factions[enemy] instanceof Faction) { + Factions[enemy].isBanned = true; } - for (var i = 0; i < Player.factionInvitations.length; ++i) { - if (Player.factionInvitations[i] == faction.name || Factions[Player.factionInvitations[i]].isBanned) { - Player.factionInvitations.splice(i, 1); - i--; - } + } + for (var i = 0; i < Player.factionInvitations.length; ++i) { + if ( + Player.factionInvitations[i] == faction.name || + Factions[Player.factionInvitations[i]].isBanned + ) { + Player.factionInvitations.splice(i, 1); + i--; } + } } export function startHackingMission(faction) { - const mission = new HackingMission(faction.playerReputation, faction); - setInMission(true, mission); //Sets inMission flag to true - mission.init(); + const mission = new HackingMission(faction.playerReputation, faction); + setInMission(true, mission); //Sets inMission flag to true + mission.init(); } //Displays the HTML content for a specific faction -export function displayFactionContent(factionName, initiallyOnAugmentationsPage=false) { - const faction = Factions[factionName]; - if (faction == null) { - throw new Error(`Invalid factionName passed into displayFactionContent(): ${factionName}`); - } +export function displayFactionContent( + factionName, + initiallyOnAugmentationsPage = false, +) { + const faction = Factions[factionName]; + if (faction == null) { + throw new Error( + `Invalid factionName passed into displayFactionContent(): ${factionName}`, + ); + } - if (!faction.isMember) { - throw new Error(`Not a member of this faction. Cannot display faction information`); - } + if (!faction.isMember) { + throw new Error( + `Not a member of this faction. Cannot display faction information`, + ); + } - ReactDOM.render( - , - Engine.Display.factionContent, - ) + ReactDOM.render( + , + Engine.Display.factionContent, + ); } - export function purchaseAugmentationBoxCreate(aug, fac) { - const factionInfo = fac.getInfo(); + const factionInfo = fac.getInfo(); - const yesBtn = yesNoBoxGetYesButton(); - yesBtn.innerHTML = "Purchase"; - yesBtn.addEventListener("click", function() { - if (!isRepeatableAug(aug) && Player.hasAugmentation(aug)) { - return; - } - - purchaseAugmentation(aug, fac); - yesNoBoxClose(); - }); - - const noBtn = yesNoBoxGetNoButton(); - noBtn.innerHTML = "Cancel"; - noBtn.addEventListener("click", function() { - yesNoBoxClose(); - }); - - let content = (
    ); - if(typeof aug.info !== 'string') { - content =
    {aug.info}
    + const yesBtn = yesNoBoxGetYesButton(); + yesBtn.innerHTML = "Purchase"; + yesBtn.addEventListener("click", function () { + if (!isRepeatableAug(aug) && Player.hasAugmentation(aug)) { + return; } - yesNoBoxCreate(<> -

    {aug.name}


    -{content}

    -
    Would you like to purchase the {aug.name} Augmentation for  -? - ); + + purchaseAugmentation(aug, fac); + yesNoBoxClose(); + }); + + const noBtn = yesNoBoxGetNoButton(); + noBtn.innerHTML = "Cancel"; + noBtn.addEventListener("click", function () { + yesNoBoxClose(); + }); + + let content =
    ; + if (typeof aug.info !== "string") { + content =
    {aug.info}
    ; + } + yesNoBoxCreate( + <> +

    {aug.name}

    +
    + {content} +
    +
    +
    + Would you like to purchase the {aug.name} Augmentation for  + ? + , + ); } //Returns a boolean indicating whether the player has the prerequisites for the //specified Augmentation export function hasAugmentationPrereqs(aug) { - let hasPrereqs = true; - if (aug.prereqs && aug.prereqs.length > 0) { - for (let i = 0; i < aug.prereqs.length; ++i) { - const prereqAug = Augmentations[aug.prereqs[i]]; - if (prereqAug == null) { - console.error(`Invalid prereq Augmentation ${aug.prereqs[i]}`); - continue; - } - if (prereqAug.owned === false) { - hasPrereqs = false; + let hasPrereqs = true; + if (aug.prereqs && aug.prereqs.length > 0) { + for (let i = 0; i < aug.prereqs.length; ++i) { + const prereqAug = Augmentations[aug.prereqs[i]]; + if (prereqAug == null) { + console.error(`Invalid prereq Augmentation ${aug.prereqs[i]}`); + continue; + } + if (prereqAug.owned === false) { + hasPrereqs = false; - // Check if the aug is purchased - for (let j = 0; j < Player.queuedAugmentations.length; ++j) { - if (Player.queuedAugmentations[j].name === prereqAug.name) { - hasPrereqs = true; - break; - } - } - } + // Check if the aug is purchased + for (let j = 0; j < Player.queuedAugmentations.length; ++j) { + if (Player.queuedAugmentations[j].name === prereqAug.name) { + hasPrereqs = true; + break; + } } + } } + } - return hasPrereqs; + return hasPrereqs; } -export function purchaseAugmentation(aug, fac, sing=false) { - const factionInfo = fac.getInfo(); - var hasPrereqs = hasAugmentationPrereqs(aug); - if (!hasPrereqs) { - var txt = "You must first purchase or install " + aug.prereqs.join(",") + " before you can " + - "purchase this one."; - if (sing) {return txt;} else {dialogBoxCreate(txt);} - } else if (aug.baseCost !== 0 && Player.money.lt(aug.baseCost * factionInfo.augmentationPriceMult)) { - let txt = "You don't have enough money to purchase " + aug.name; - if (sing) {return txt;} - dialogBoxCreate(txt); - } else if (fac.playerReputation < aug.baseRepRequirement) { - let txt = "You don't have enough faction reputation to purchase " + aug.name; - if (sing) {return txt;} - dialogBoxCreate(txt); - } else if (aug.baseCost === 0 || Player.money.gte(aug.baseCost * factionInfo.augmentationPriceMult)) { - if (Player.firstAugPurchased === false) { - Player.firstAugPurchased = true; - document.getElementById("augmentations-tab").style.display = "list-item"; - document.getElementById("character-menu-header").click(); - document.getElementById("character-menu-header").click(); - } - - var queuedAugmentation = new PlayerOwnedAugmentation(aug.name); - if (aug.name == AugmentationNames.NeuroFluxGovernor) { - queuedAugmentation.level = getNextNeurofluxLevel(); - } - Player.queuedAugmentations.push(queuedAugmentation); - - Player.loseMoney((aug.baseCost * factionInfo.augmentationPriceMult)); - - // If you just purchased Neuroflux Governor, recalculate the cost - if (aug.name == AugmentationNames.NeuroFluxGovernor) { - var nextLevel = getNextNeurofluxLevel(); - --nextLevel; - var mult = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel); - aug.baseRepRequirement = 500 * mult * BitNodeMultipliers.AugmentationRepCost; - aug.baseCost = 750e3 * mult * BitNodeMultipliers.AugmentationMoneyCost; - - for (var i = 0; i < Player.queuedAugmentations.length-1; ++i) { - aug.baseCost *= (CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]]); - } - } - - for (var name in Augmentations) { - if (Augmentations.hasOwnProperty(name)) { - Augmentations[name].baseCost *= (CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]]); - } - } - - if (sing) { - return "You purchased " + aug.name; - } else { - if(!Settings.SuppressBuyAugmentationConfirmation){ - dialogBoxCreate("You purchased " + aug.name + ". It's enhancements will not take " + - "effect until they are installed. To install your augmentations, go to the " + - "'Augmentations' tab on the left-hand navigation menu. Purchasing additional " + - "augmentations will now be more expensive."); - } - } - - // Force a rerender of the Augmentations page - displayFactionContent(fac.name, true); +export function purchaseAugmentation(aug, fac, sing = false) { + const factionInfo = fac.getInfo(); + var hasPrereqs = hasAugmentationPrereqs(aug); + if (!hasPrereqs) { + var txt = + "You must first purchase or install " + + aug.prereqs.join(",") + + " before you can " + + "purchase this one."; + if (sing) { + return txt; } else { - dialogBoxCreate("Hmm, something went wrong when trying to purchase an Augmentation. " + - "Please report this to the game developer with an explanation of how to " + - "reproduce this."); + dialogBoxCreate(txt); } + } else if ( + aug.baseCost !== 0 && + Player.money.lt(aug.baseCost * factionInfo.augmentationPriceMult) + ) { + let txt = "You don't have enough money to purchase " + aug.name; + if (sing) { + return txt; + } + dialogBoxCreate(txt); + } else if (fac.playerReputation < aug.baseRepRequirement) { + let txt = + "You don't have enough faction reputation to purchase " + aug.name; + if (sing) { + return txt; + } + dialogBoxCreate(txt); + } else if ( + aug.baseCost === 0 || + Player.money.gte(aug.baseCost * factionInfo.augmentationPriceMult) + ) { + if (Player.firstAugPurchased === false) { + Player.firstAugPurchased = true; + document.getElementById("augmentations-tab").style.display = "list-item"; + document.getElementById("character-menu-header").click(); + document.getElementById("character-menu-header").click(); + } + + var queuedAugmentation = new PlayerOwnedAugmentation(aug.name); + if (aug.name == AugmentationNames.NeuroFluxGovernor) { + queuedAugmentation.level = getNextNeurofluxLevel(); + } + Player.queuedAugmentations.push(queuedAugmentation); + + Player.loseMoney(aug.baseCost * factionInfo.augmentationPriceMult); + + // If you just purchased Neuroflux Governor, recalculate the cost + if (aug.name == AugmentationNames.NeuroFluxGovernor) { + var nextLevel = getNextNeurofluxLevel(); + --nextLevel; + var mult = Math.pow(CONSTANTS.NeuroFluxGovernorLevelMult, nextLevel); + aug.baseRepRequirement = + 500 * mult * BitNodeMultipliers.AugmentationRepCost; + aug.baseCost = 750e3 * mult * BitNodeMultipliers.AugmentationMoneyCost; + + for (var i = 0; i < Player.queuedAugmentations.length - 1; ++i) { + aug.baseCost *= + CONSTANTS.MultipleAugMultiplier * + [1, 0.96, 0.94, 0.93][SourceFileFlags[11]]; + } + } + + for (var name in Augmentations) { + if (Augmentations.hasOwnProperty(name)) { + Augmentations[name].baseCost *= + CONSTANTS.MultipleAugMultiplier * + [1, 0.96, 0.94, 0.93][SourceFileFlags[11]]; + } + } + + if (sing) { + return "You purchased " + aug.name; + } else { + if (!Settings.SuppressBuyAugmentationConfirmation) { + dialogBoxCreate( + "You purchased " + + aug.name + + ". It's enhancements will not take " + + "effect until they are installed. To install your augmentations, go to the " + + "'Augmentations' tab on the left-hand navigation menu. Purchasing additional " + + "augmentations will now be more expensive.", + ); + } + } + + // Force a rerender of the Augmentations page + displayFactionContent(fac.name, true); + } else { + dialogBoxCreate( + "Hmm, something went wrong when trying to purchase an Augmentation. " + + "Please report this to the game developer with an explanation of how to " + + "reproduce this.", + ); + } } export function getNextNeurofluxLevel() { - // Get current Neuroflux level based on Player's augmentations - let currLevel = 0; - for (var i = 0; i < Player.augmentations.length; ++i) { - if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) { - currLevel = Player.augmentations[i].level; - } + // Get current Neuroflux level based on Player's augmentations + let currLevel = 0; + for (var i = 0; i < Player.augmentations.length; ++i) { + if (Player.augmentations[i].name === AugmentationNames.NeuroFluxGovernor) { + currLevel = Player.augmentations[i].level; } + } - // Account for purchased but uninstalled Augmentations - for (var i = 0; i < Player.queuedAugmentations.length; ++i) { - if (Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor) { - ++currLevel; - } + // Account for purchased but uninstalled Augmentations + for (var i = 0; i < Player.queuedAugmentations.length; ++i) { + if ( + Player.queuedAugmentations[i].name == AugmentationNames.NeuroFluxGovernor + ) { + ++currLevel; } - return currLevel + 1; + } + return currLevel + 1; } export function processPassiveFactionRepGain(numCycles) { - for (const name in Factions) { - if (name === Player.currentWorkFactionName) continue; - if (!Factions.hasOwnProperty(name)) continue; - const faction = Factions[name]; - if (!faction.isMember) continue; - // No passive rep for special factions - const info = faction.getInfo(); - if(!info.offersWork()) continue; - // No passive rep for gangs. - if(Player.getGangName() === name) continue; - // 0 favor = 1%/s - // 50 favor = 6%/s - // 100 favor = 11%/s - const favorMult = Math.min(0.1, (faction.favor / 1000) + 0.01); - // Find the best of all possible favor gain, minimum 1 rep / 2 minute. - const hRep = getHackingWorkRepGain(Player, faction); - const sRep = getFactionSecurityWorkRepGain(Player, faction); - const fRep = getFactionFieldWorkRepGain(Player, faction); - const rate = Math.max(hRep * favorMult, sRep * favorMult, fRep * favorMult, 1/120); + for (const name in Factions) { + if (name === Player.currentWorkFactionName) continue; + if (!Factions.hasOwnProperty(name)) continue; + const faction = Factions[name]; + if (!faction.isMember) continue; + // No passive rep for special factions + const info = faction.getInfo(); + if (!info.offersWork()) continue; + // No passive rep for gangs. + if (Player.getGangName() === name) continue; + // 0 favor = 1%/s + // 50 favor = 6%/s + // 100 favor = 11%/s + const favorMult = Math.min(0.1, faction.favor / 1000 + 0.01); + // Find the best of all possible favor gain, minimum 1 rep / 2 minute. + const hRep = getHackingWorkRepGain(Player, faction); + const sRep = getFactionSecurityWorkRepGain(Player, faction); + const fRep = getFactionFieldWorkRepGain(Player, faction); + const rate = Math.max( + hRep * favorMult, + sRep * favorMult, + fRep * favorMult, + 1 / 120, + ); - faction.playerReputation += rate * - (numCycles) * - Player.faction_rep_mult * - BitNodeMultipliers.FactionPassiveRepGain; - } + faction.playerReputation += + rate * + numCycles * + Player.faction_rep_mult * + BitNodeMultipliers.FactionPassiveRepGain; + } } diff --git a/src/Faction/FactionInfo.ts b/src/Faction/FactionInfo.ts index 4fb3ec705..db5afa1ef 100644 --- a/src/Faction/FactionInfo.ts +++ b/src/Faction/FactionInfo.ts @@ -44,8 +44,14 @@ export class FactionInfo { */ offerSecurityWork: boolean; - constructor(infoText: string, enemies: string[], offerHackingMission: boolean, offerHackingWork: boolean, - offerFieldWork: boolean, offerSecurityWork: boolean) { + constructor( + infoText: string, + enemies: string[], + offerHackingMission: boolean, + offerHackingWork: boolean, + offerFieldWork: boolean, + offerSecurityWork: boolean, + ) { this.infoText = infoText; this.enemies = enemies; this.offerHackingMission = offerHackingMission; @@ -59,10 +65,12 @@ export class FactionInfo { } offersWork(): boolean { - return this.offerFieldWork || - this.offerHackingMission || - this.offerHackingWork || - this.offerSecurityWork; + return ( + this.offerFieldWork || + this.offerHackingMission || + this.offerHackingWork || + this.offerSecurityWork + ); } } @@ -74,12 +82,13 @@ export const FactionInfos: IMap = { // Endgame Illuminati: new FactionInfo( "Humanity never changes. No matter how civilized society becomes, it will eventually fall back into chaos. " + - "And from this chaos, we are the invisible hand that guides them to order. ", + "And from this chaos, we are the invisible hand that guides them to order. ", [], true, true, true, - false), + false, + ), Daedalus: new FactionInfo( "Yesterday we obeyed kings and bent our necks to emperors. Today we kneel only to truth.", @@ -87,51 +96,56 @@ export const FactionInfos: IMap = { true, true, true, - false), + false, + ), "The Covenant": new FactionInfo( "Surrender yourself. Give up your empty individuality to become part of something great, something eternal. " + - "Become a slave. Submit your mind, body, and soul. Only then can you set yourself free.
    " + - "
    " + - "Only then can you discover immortality.", + "Become a slave. Submit your mind, body, and soul. Only then can you set yourself free.
    " + + "
    " + + "Only then can you discover immortality.", [], true, true, true, - false), + false, + ), // Megacorporations, each forms its own faction ECorp: new FactionInfo( "ECorp's mission is simple: to connect the world of today with the technology of tomorrow. With our wide " + - "range of Internet-related software and commercial hardware, ECorp makes the world's information " + - "universally accessible.", + "range of Internet-related software and commercial hardware, ECorp makes the world's information " + + "universally accessible.", [], true, true, true, - true), + true, + ), MegaCorp: new FactionInfo( "MegaCorp does what no other dares to do. We imagine. We create. We invent. We create what others have " + - "never even dreamed of. Our work fills the world's needs for food, water, power, and transporation on an " + - "unprecendented scale, in ways that no other company can.
    " + - "
    " + - "In our labs and factories and on the ground with customers, MegaCorp is ushering in a new era for the world.", + "never even dreamed of. Our work fills the world's needs for food, water, power, and transporation on an " + + "unprecendented scale, in ways that no other company can.
    " + + "
    " + + "In our labs and factories and on the ground with customers, MegaCorp is ushering in a new era for the world.", [], true, true, true, - true), + true, + ), "Bachman & Associates": new FactionInfo( "Where Law and Business meet - thats where we are.
    " + - "
    " + - "Legal Insight - Business Instinct - Innovative Experience.", + "
    " + + "Legal Insight - Business Instinct - Innovative Experience.", [], true, true, true, - true), + true, + ), "Blade Industries": new FactionInfo( "Augmentation is Salvation.", @@ -139,18 +153,20 @@ export const FactionInfos: IMap = { true, true, true, - true), + true, + ), NWO: new FactionInfo( "Humans don't truly desire freedom. They want to be observed, understood, and judged. They want to " + - "be given purpose and direction in life. That is why they created God. And that is why they created " + - "civilization - not because of willingness, but because of a need to be incorporated into higher orders of " + - "structure and meaning.", + "be given purpose and direction in life. That is why they created God. And that is why they created " + + "civilization - not because of willingness, but because of a need to be incorporated into higher orders of " + + "structure and meaning.", [], true, true, true, - true), + true, + ), "Clarke Incorporated": new FactionInfo( "The Power of the Genome - Unlocked.", @@ -158,7 +174,8 @@ export const FactionInfos: IMap = { true, true, true, - true), + true, + ), "OmniTek Incorporated": new FactionInfo( "Simply put, our mission is to design and build robots that make a difference.", @@ -166,16 +183,18 @@ export const FactionInfos: IMap = { true, true, true, - true), + true, + ), "Four Sigma": new FactionInfo( "The scientific method is the best way to approach investing. Big strategies backed up with big data. Driven " + - "by deep learning and innovative ideas. And improved by iteration. That's Four Sigma.", + "by deep learning and innovative ideas. And improved by iteration. That's Four Sigma.", [], true, true, true, - true), + true, + ), "KuaiGong International": new FactionInfo( "Dream big. Work hard. Make history.", @@ -183,158 +202,141 @@ export const FactionInfos: IMap = { true, true, true, - true), + true, + ), // Other Corporations "Fulcrum Secret Technologies": new FactionInfo( "The human organism has an innate desire to worship. That is why they created gods. If there were no gods, it " + - "would be necessary to create them. And now we can.", + "would be necessary to create them. And now we can.", [], true, true, false, - true), + true, + ), // Hacker groups BitRunners: new FactionInfo( "Our entire lives are controlled by bits. All of our actions, our thoughts, our personal information. It's " + - "all transformed into bits, stored in bits, communicated through bits. It’s impossible for any person to move, " + - "to live, to operate at any level without the use of bits. And when a person moves, lives, and operates, they " + - "leave behind their bits, mere traces of seemingly meaningless fragments of information. But these bits can be " + - "reconstructed. Transformed. Used.
    " + - "
    " + - "Those who run the bits, run the world.", + "all transformed into bits, stored in bits, communicated through bits. It’s impossible for any person to move, " + + "to live, to operate at any level without the use of bits. And when a person moves, lives, and operates, they " + + "leave behind their bits, mere traces of seemingly meaningless fragments of information. But these bits can be " + + "reconstructed. Transformed. Used.
    " + + "
    " + + "Those who run the bits, run the world.", [], true, true, false, - false), + false, + ), "The Black Hand": new FactionInfo( "The world, so afraid of strong government, now has no government. Only power - Digital power. Financial " + - "power. Technological power. And those at the top rule with an invisible hand. They built a society where the " + - "rich get richer, and everyone else suffers.
    " + - "
    " + - "So much pain. So many lives. Their darkness must end.", + "power. Technological power. And those at the top rule with an invisible hand. They built a society where the " + + "rich get richer, and everyone else suffers.
    " + + "
    " + + "So much pain. So many lives. Their darkness must end.", [], true, true, true, - false), + false, + ), NiteSec: new FactionInfo( " __..__
    " + - " _.nITESECNIt.
    " + - " .-'NITESECNITESEc.
    " + - " .' NITESECNITESECn
    " + - " / NITESECNITESEC;
    " + - " : :NITESECNITESEC;
    " + - " ; $ NITESECNITESECN
    " + - " : _, ,N'ITESECNITESEC
    " + - " : .+^^`, : `NITESECNIT
    " + - " ) /), `-,-=,NITESECNI
    " + - " / ^ ,-;|NITESECN;
    " + - " / _.' '-';NITESECN
    " + - " ( , ,-''`^NITE'
    " + - " )` :`. .'
    " + - " )-- ; `- /
    " + - " \' _.-' :
    " + - " ( _.-' \. \
    " + - " \------. \ \
    " + - " \. \ \
    " + - " \ _.nIt
    " + - " \ _.nITESECNi
    " + - " nITESECNIT^' \
    " + - " NITE^' ___ \
    " + - " / .gP''''Tp. \
    " + - " : d' . `b \
    " + - " ; d' o `b ;
    " + - " / d; `b|
    " + - " /, $; @ `:
    " + - " /' $$ ;
    " + - " .' $$b o |
    " + - " .' d$$$; :
    " + - " / .d$$$$; , ;
    " + - " d .dNITESEC $ |
    " + - " :bp.__.gNITESEC$$ :$ ;
    " + - " NITESECNITESECNIT $$b :
    ", + " _.nITESECNIt.
    " + + " .-'NITESECNITESEc.
    " + + " .' NITESECNITESECn
    " + + " / NITESECNITESEC;
    " + + " : :NITESECNITESEC;
    " + + " ; $ NITESECNITESECN
    " + + " : _, ,N'ITESECNITESEC
    " + + " : .+^^`, : `NITESECNIT
    " + + " ) /), `-,-=,NITESECNI
    " + + " / ^ ,-;|NITESECN;
    " + + " / _.' '-';NITESECN
    " + + " ( , ,-''`^NITE'
    " + + " )` :`. .'
    " + + " )-- ; `- /
    " + + " ' _.-' :
    " + + " ( _.-' .
    " + + " ------.
    " + + " .
    " + + " _.nIt
    " + + " _.nITESECNi
    " + + " nITESECNIT^'
    " + + " NITE^' ___
    " + + " / .gP''''Tp.
    " + + " : d' . `b
    " + + " ; d' o `b ;
    " + + " / d; `b|
    " + + " /, $; @ `:
    " + + " /' $$ ;
    " + + " .' $$b o |
    " + + " .' d$$$; :
    " + + " / .d$$$$; , ;
    " + + " d .dNITESEC $ |
    " + + " :bp.__.gNITESEC$$ :$ ;
    " + + " NITESECNITESECNIT $$b :
    ", [], true, true, false, - false), + false, + ), // City factions, essentially governments Aevum: new FactionInfo( "The Silicon City.", - [ - "Chongqing", - "New Tokyo", - "Ishima", - "Volhaven", - ], + ["Chongqing", "New Tokyo", "Ishima", "Volhaven"], true, true, true, - true), + true, + ), Chongqing: new FactionInfo( "Serve the People.", - [ - "Sector-12", - "Aevum", - "Volhaven", - ], + ["Sector-12", "Aevum", "Volhaven"], true, true, true, - true), + true, + ), Ishima: new FactionInfo( "The East Asian Order of the Future.", - [ - "Sector-12", - "Aevum", - "Volhaven", - ], + ["Sector-12", "Aevum", "Volhaven"], true, true, true, - true), + true, + ), "New Tokyo": new FactionInfo( "Asia's World City.", - [ - "Sector-12", - "Aevum", - "Volhaven", - ], + ["Sector-12", "Aevum", "Volhaven"], true, true, true, - true), + true, + ), "Sector-12": new FactionInfo( "The City of the Future.", - [ - "Chongqing", - "New Tokyo", - "Ishima", - "Volhaven", - ], + ["Chongqing", "New Tokyo", "Ishima", "Volhaven"], true, true, true, - true), + true, + ), Volhaven: new FactionInfo( "Benefit, Honor, and Glory.", - [ - "Chongqing", - "Sector-12", - "New Tokyo", - "Aevum", - "Ishima", - ], + ["Chongqing", "Sector-12", "New Tokyo", "Aevum", "Ishima"], true, true, true, - true), + true, + ), // Criminal Organizations/Gangs "Speakers for the Dead": new FactionInfo( @@ -343,7 +345,8 @@ export const FactionInfos: IMap = { true, true, true, - true), + true, + ), "The Dark Army": new FactionInfo( "The World doesn't care about right or wrong. It only cares about power.", @@ -351,7 +354,8 @@ export const FactionInfos: IMap = { true, true, true, - false), + false, + ), "The Syndicate": new FactionInfo( "Honor holds you back.", @@ -359,19 +363,21 @@ export const FactionInfos: IMap = { true, true, true, - true), + true, + ), Silhouette: new FactionInfo( "Corporations have filled the void of power left behind by the collapse of Western government. The issue is " + - "they've become so big that you don't know who they're working for. And if you're employed at one of these " + - "corporations, you don't even know who you're working for.
    " + - "
    " + - "That's terror. Terror, fear, and corruption. All born into the system, all propagated by the system.", + "they've become so big that you don't know who they're working for. And if you're employed at one of these " + + "corporations, you don't even know who you're working for.
    " + + "
    " + + "That's terror. Terror, fear, and corruption. All born into the system, all propagated by the system.", [], true, true, true, - false), + false, + ), Tetrads: new FactionInfo( "Following the mandate of Heaven and carrying out the way.", @@ -379,7 +385,8 @@ export const FactionInfos: IMap = { false, false, true, - true), + true, + ), "Slum Snakes": new FactionInfo( "Slum Snakes rule!", @@ -387,16 +394,18 @@ export const FactionInfos: IMap = { false, false, true, - true), + true, + ), // Earlygame factions - factions the player will prestige with early on that don't belong in other categories. Netburners: new FactionInfo( - "~~//*>H4CK|\|3T 8URN3R5**>?>\\~~", + "~~//*>H4CK||3T 8URN3R5**>?>\\~~", [], true, true, false, - false), + false, + ), "Tian Di Hui": new FactionInfo( "Obey Heaven and work righteously.", @@ -404,27 +413,30 @@ export const FactionInfos: IMap = { true, true, false, - true), + true, + ), CyberSec: new FactionInfo( "The Internet is the first thing that was built that we don't fully understand, the largest " + - "experiment in anarchy that we have ever had. And as the world becomes increasingly dominated by it, " + - "society approaches the brink of total chaos. We serve only to protect society, to protect humanity, to " + - "protect the world from imminent collapse.", + "experiment in anarchy that we have ever had. And as the world becomes increasingly dominated by it, " + + "society approaches the brink of total chaos. We serve only to protect society, to protect humanity, to " + + "protect the world from imminent collapse.", [], true, true, false, - false), + false, + ), // Special Factions Bladeburners: new FactionInfo( "It's too bad they won't live. But then again, who does?

    Note that for this faction, reputation can " + - "only be gained through Bladeburner actions. Completing Bladeburner contracts/operations will increase your " + - "reputation.", + "only be gained through Bladeburner actions. Completing Bladeburner contracts/operations will increase your " + + "reputation.", [], false, false, false, - false), + false, + ), }; diff --git a/src/Faction/FactionWorkTypeEnum.ts b/src/Faction/FactionWorkTypeEnum.ts index 5de9e80e2..40374c360 100644 --- a/src/Faction/FactionWorkTypeEnum.ts +++ b/src/Faction/FactionWorkTypeEnum.ts @@ -1,6 +1,6 @@ export enum FactionWorkType { - Field, - Hacking, - None, - Security, + Field, + Hacking, + None, + Security, } diff --git a/src/Faction/Factions.ts b/src/Faction/Factions.ts index 8616018f0..fa097336d 100644 --- a/src/Faction/Factions.ts +++ b/src/Faction/Factions.ts @@ -9,39 +9,40 @@ import { IMap } from "../types"; import { Reviver } from "../../utils/JSONReviver"; - export let Factions: IMap = {}; export function loadFactions(saveString: string): void { - Factions = JSON.parse(saveString, Reviver); + Factions = JSON.parse(saveString, Reviver); } export function AddToFactions(faction: Faction): void { - const name: string = faction.name; - Factions[name] = faction; + const name: string = faction.name; + Factions[name] = faction; } export function factionExists(name: string): boolean { - return Factions.hasOwnProperty(name); + return Factions.hasOwnProperty(name); } export function initFactions(): void { - for (const name in FactionInfos) { - resetFaction(new Faction(name)); - } + for (const name in FactionInfos) { + resetFaction(new Faction(name)); + } } //Resets a faction during (re-)initialization. Saves the favor in the new //Faction object and deletes the old Faction Object from "Factions". Then //reinserts the new Faction object export function resetFaction(newFactionObject: Faction): void { - if (!(newFactionObject instanceof Faction)) { - throw new Error("Invalid argument 'newFactionObject' passed into resetFaction()"); - } - const factionName: string = newFactionObject.name; - if (factionExists(factionName)) { - newFactionObject.favor = Factions[factionName].favor; - delete Factions[factionName]; - } - AddToFactions(newFactionObject); + if (!(newFactionObject instanceof Faction)) { + throw new Error( + "Invalid argument 'newFactionObject' passed into resetFaction()", + ); + } + const factionName: string = newFactionObject.name; + if (factionExists(factionName)) { + newFactionObject.favor = Factions[factionName].favor; + delete Factions[factionName]; + } + AddToFactions(newFactionObject); } diff --git a/src/Faction/README.md b/src/Faction/README.md index 8f01b60fb..b2ddec644 100644 --- a/src/Faction/README.md +++ b/src/Faction/README.md @@ -1 +1 @@ -Implementation of Faction-related mechanics +Implementation of Faction-related mechanics diff --git a/src/Faction/formulas/donation.ts b/src/Faction/formulas/donation.ts index cd9fd0d6c..21c5869d1 100644 --- a/src/Faction/formulas/donation.ts +++ b/src/Faction/formulas/donation.ts @@ -2,5 +2,5 @@ import { CONSTANTS } from "../../Constants"; import { IPlayer } from "../../PersonObjects/IPlayer"; export function repFromDonation(amt: number, player: IPlayer): number { - return amt / CONSTANTS.DonateMoneyToRepDivisor * player.faction_rep_mult; -} \ No newline at end of file + return (amt / CONSTANTS.DonateMoneyToRepDivisor) * player.faction_rep_mult; +} diff --git a/src/Faction/formulas/favor.ts b/src/Faction/formulas/favor.ts index e387aca62..f5c6d639c 100644 --- a/src/Faction/formulas/favor.ts +++ b/src/Faction/formulas/favor.ts @@ -6,14 +6,14 @@ // Then we use https://herbie.uwplse.org/demo/ to simplify it and prevent // Infinity issues. export function favorToRep(f: number): number { - function fma(a: number, b: number, c: number): number { - return a * b + c; - } - const ex = fma(f, (Math.log(51.0) - Math.log(50.0)), Math.log(51.0)); - return fma(500.0, Math.exp(ex), -25000.0); + function fma(a: number, b: number, c: number): number { + return a * b + c; + } + const ex = fma(f, Math.log(51.0) - Math.log(50.0), Math.log(51.0)); + return fma(500.0, Math.exp(ex), -25000.0); } // Wolfram Alpha: 500 (50^(-n) 51^(n + 1) - 50) solve for n export function repToFavor(r: number): number { - return -(Math.log(25500/(r + 25000)))/Math.log(51/50); -} \ No newline at end of file + return -Math.log(25500 / (r + 25000)) / Math.log(51 / 50); +} diff --git a/src/Faction/ui/AugmentationsPage.tsx b/src/Faction/ui/AugmentationsPage.tsx index 5ac4ee8b5..a0505b913 100644 --- a/src/Faction/ui/AugmentationsPage.tsx +++ b/src/Faction/ui/AugmentationsPage.tsx @@ -15,171 +15,184 @@ import { Settings } from "../../Settings/Settings"; import { StdButton } from "../../ui/React/StdButton"; type IProps = { - faction: Faction; - p: IPlayer; - routeToMainPage: () => void; -} + faction: Faction; + p: IPlayer; + routeToMainPage: () => void; +}; type IState = { - rerenderFlag: boolean; - sortOrder: PurchaseAugmentationsOrderSetting; -} + rerenderFlag: boolean; + sortOrder: PurchaseAugmentationsOrderSetting; +}; const infoStyleMarkup = { - width: "70%", -} + width: "70%", +}; export class AugmentationsPage extends React.Component { - // Flag for whether the player has a gang with this faction - isPlayersGang: boolean; - constructor(props: IProps) { - super(props); + // Flag for whether the player has a gang with this faction + isPlayersGang: boolean; + constructor(props: IProps) { + super(props); - this.isPlayersGang = props.p.inGang() && (props.p.getGangName() === props.faction.name); + this.isPlayersGang = + props.p.inGang() && props.p.getGangName() === props.faction.name; - this.state = { - rerenderFlag: false, - sortOrder: PurchaseAugmentationsOrderSetting.Default, + this.state = { + rerenderFlag: false, + sortOrder: PurchaseAugmentationsOrderSetting.Default, + }; + + this.rerender = this.rerender.bind(this); + } + + getAugs(): string[] { + if (this.isPlayersGang) { + const augs: string[] = []; + for (const augName in Augmentations) { + const aug = Augmentations[augName]; + if (!aug.isSpecial) { + augs.push(augName); } + } - this.rerender = this.rerender.bind(this); + return augs; + } else { + return this.props.faction.augmentations.slice(); + } + } + + getAugsSorted(): string[] { + switch (Settings.PurchaseAugmentationsOrder) { + case PurchaseAugmentationsOrderSetting.Cost: { + return this.getAugsSortedByCost(); + } + case PurchaseAugmentationsOrderSetting.Reputation: { + return this.getAugsSortedByReputation(); + } + default: + return this.getAugsSortedByDefault(); + } + } + + getAugsSortedByCost(): string[] { + const augs = this.getAugs(); + augs.sort((augName1, augName2) => { + const aug1 = Augmentations[augName1], + aug2 = Augmentations[augName2]; + if (aug1 == null || aug2 == null) { + throw new Error("Invalid Augmentation Names"); + } + + return aug1.baseCost - aug2.baseCost; + }); + + return augs; + } + + getAugsSortedByReputation(): string[] { + const augs = this.getAugs(); + augs.sort((augName1, augName2) => { + const aug1 = Augmentations[augName1], + aug2 = Augmentations[augName2]; + if (aug1 == null || aug2 == null) { + throw new Error("Invalid Augmentation Names"); + } + return aug1.baseRepRequirement - aug2.baseRepRequirement; + }); + + return augs; + } + + getAugsSortedByDefault(): string[] { + return this.getAugs(); + } + + switchSortOrder(newOrder: PurchaseAugmentationsOrderSetting): void { + Settings.PurchaseAugmentationsOrder = newOrder; + this.rerender(); + } + + rerender(): void { + this.setState((prevState) => { + return { + rerenderFlag: !prevState.rerenderFlag, + }; + }); + } + + render(): React.ReactNode { + const augs = this.getAugsSorted(); + const purchasable = augs.filter( + (aug: string) => + aug === AugmentationNames.NeuroFluxGovernor || + (!this.props.p.augmentations.some((a) => a.name === aug) && + !this.props.p.queuedAugmentations.some((a) => a.name === aug)), + ); + + const purchaseableAugmentation = (aug: string): React.ReactNode => { + return ( + + ); + }; + + const augListElems = purchasable.map((aug) => + purchaseableAugmentation(aug), + ); + + let ownedElem = <>; + const owned = augs.filter((aug: string) => !purchasable.includes(aug)); + if (owned.length !== 0) { + ownedElem = ( + <> +
    +

    Purchased Augmentations

    +

    + This factions also offers these augmentations but you already own + them. +

    + {owned.map((aug) => purchaseableAugmentation(aug))} + + ); } - getAugs(): string[] { - if (this.isPlayersGang) { - const augs: string[] = []; - for (const augName in Augmentations) { - const aug = Augmentations[augName]; - if (!aug.isSpecial) { - augs.push(augName); - } - } - - return augs; - } else { - return this.props.faction.augmentations.slice(); - } - } - - getAugsSorted(): string[] { - switch (Settings.PurchaseAugmentationsOrder) { - case PurchaseAugmentationsOrderSetting.Cost: { - return this.getAugsSortedByCost(); - } - case PurchaseAugmentationsOrderSetting.Reputation: { - return this.getAugsSortedByReputation(); - } - default: - return this.getAugsSortedByDefault(); - } - } - - getAugsSortedByCost(): string[] { - const augs = this.getAugs(); - augs.sort((augName1, augName2)=>{ - const aug1 = Augmentations[augName1], aug2 = Augmentations[augName2]; - if (aug1 == null || aug2 == null) { - throw new Error("Invalid Augmentation Names"); - } - - return aug1.baseCost - aug2.baseCost; - }); - - return augs; - } - - getAugsSortedByReputation(): string[] { - const augs = this.getAugs(); - augs.sort((augName1, augName2)=>{ - const aug1 = Augmentations[augName1], aug2 = Augmentations[augName2]; - if (aug1 == null || aug2 == null) { - throw new Error("Invalid Augmentation Names"); - } - return aug1.baseRepRequirement - aug2.baseRepRequirement; - }); - - return augs; - } - - getAugsSortedByDefault(): string[] { - return this.getAugs(); - } - - switchSortOrder(newOrder: PurchaseAugmentationsOrderSetting): void { - Settings.PurchaseAugmentationsOrder = newOrder; - this.rerender(); - } - - rerender(): void { - this.setState((prevState) => { - return { - rerenderFlag: !prevState.rerenderFlag, - } - }); - } - - render(): React.ReactNode { - const augs = this.getAugsSorted(); - const purchasable = augs.filter((aug: string) => aug === AugmentationNames.NeuroFluxGovernor || - (!this.props.p.augmentations.some(a => a.name === aug) && - !this.props.p.queuedAugmentations.some(a => a.name === aug)), - ) - - const purchaseableAugmentation = (aug: string): React.ReactNode => { - return ( - - ) - } - - const augListElems = purchasable.map(aug => purchaseableAugmentation(aug)); - - let ownedElem = <> - const owned = augs.filter((aug: string) => !purchasable.includes(aug)); - if (owned.length !== 0) { - ownedElem = <> -
    -

    Purchased Augmentations

    -

    - This factions also offers these augmentations but you already own them. -

    - {owned.map(aug => purchaseableAugmentation(aug))} - - } - - return ( -
    - -

    Faction Augmentations

    -

    - These are all of the Augmentations that are available to purchase - from {this.props.faction.name}. Augmentations are powerful upgrades - that will enhance your abilities. -

    - this.switchSortOrder(PurchaseAugmentationsOrderSetting.Cost)} - text={"Sort by Cost"} - /> - this.switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation)} - text={"Sort by Reputation"} - /> - this.switchSortOrder(PurchaseAugmentationsOrderSetting.Default)} - text={"Sort by Default Order"} - /> -
    - {augListElems} - {ownedElem} -
    - ) - } + return ( +
    + +

    Faction Augmentations

    +

    + These are all of the Augmentations that are available to purchase from{" "} + {this.props.faction.name}. Augmentations are powerful upgrades that + will enhance your abilities. +

    + + this.switchSortOrder(PurchaseAugmentationsOrderSetting.Cost) + } + text={"Sort by Cost"} + /> + + this.switchSortOrder(PurchaseAugmentationsOrderSetting.Reputation) + } + text={"Sort by Reputation"} + /> + + this.switchSortOrder(PurchaseAugmentationsOrderSetting.Default) + } + text={"Sort by Default Order"} + /> +
    + {augListElems} + {ownedElem} +
    + ); + } } diff --git a/src/Faction/ui/DonateOption.tsx b/src/Faction/ui/DonateOption.tsx index f4ec7aff6..56043a114 100644 --- a/src/Faction/ui/DonateOption.tsx +++ b/src/Faction/ui/DonateOption.tsx @@ -16,88 +16,99 @@ import { StdButton } from "../../ui/React/StdButton"; import { numeralWrapper } from "../../ui/numeralFormat"; import { dialogBoxCreate } from "../../../utils/DialogBox"; -import { MathComponent } from 'mathjax-react'; +import { MathComponent } from "mathjax-react"; type IProps = { - faction: Faction; - disabled: boolean; - favorToDonate: number; - p: IPlayer; - rerender: () => void; -} - + faction: Faction; + disabled: boolean; + favorToDonate: number; + p: IPlayer; + rerender: () => void; +}; const inputStyleMarkup = { - margin: "5px", - height: "26px", -} + margin: "5px", + height: "26px", +}; const blockStyle = { display: "block" }; export function DonateOption(props: IProps): React.ReactElement { - const [donateAmt, setDonateAmt] = useState(null); - const digits = (CONSTANTS.DonateMoneyToRepDivisor+'').length-1; + const [donateAmt, setDonateAmt] = useState(null); + const digits = (CONSTANTS.DonateMoneyToRepDivisor + "").length - 1; - function canDonate(): boolean { - if(donateAmt === null) return false; - if (isNaN(donateAmt) || donateAmt <= 0) return false; - if(props.p.money.lt(donateAmt)) return false; - return true; + function canDonate(): boolean { + if (donateAmt === null) return false; + if (isNaN(donateAmt) || donateAmt <= 0) return false; + if (props.p.money.lt(donateAmt)) return false; + return true; + } + + function onChange(event: React.ChangeEvent): void { + const amt = numeralWrapper.parseMoney(event.target.value); + if (event.target.value === "" || isNaN(amt)) setDonateAmt(null); + else setDonateAmt(amt); + } + + function donate(): void { + const fac = props.faction; + const amt = donateAmt; + if (amt === null) return; + if (!canDonate()) return; + props.p.loseMoney(amt); + const repGain = repFromDonation(amt, props.p); + props.faction.playerReputation += repGain; + dialogBoxCreate( + <> + You just donated to {fac.name} to gain{" "} + {Reputation(repGain)} reputation. + , + ); + props.rerender(); + } + + function Status(): React.ReactElement { + if (donateAmt === null) return <>; + if (!canDonate()) { + if (props.p.money.lt(donateAmt)) return

    Insufficient funds

    ; + return

    Invalid donate amount entered!

    ; } + return ( +

    + This donation will result in{" "} + {Reputation(repFromDonation(donateAmt, props.p))} reputation gain +

    + ); + } - function onChange(event: React.ChangeEvent): void { - const amt = numeralWrapper.parseMoney(event.target.value); - if(event.target.value === "" || isNaN(amt)) setDonateAmt(null); - else setDonateAmt(amt); - } - - function donate(): void { - const fac = props.faction; - const amt = donateAmt; - if(amt === null) return; - if(!canDonate()) return; - props.p.loseMoney(amt); - const repGain = repFromDonation(amt, props.p); - props.faction.playerReputation += repGain; - dialogBoxCreate(<> - You just donated to {fac.name} to gain {Reputation(repGain)} reputation. - ); - props.rerender(); - } - - function Status(): React.ReactElement { - if(donateAmt === null) return (<>); - if(!canDonate()) { - if(props.p.money.lt(donateAmt)) - return (

    Insufficient funds

    ); - return (

    Invalid donate amount entered!

    ); - } - return (

    This donation will result in {Reputation(repFromDonation(donateAmt, props.p))} reputation gain

    ); - } - - - return (
    -
    - +
    + + + + {props.disabled ? ( +

    + Unlocked at {props.favorToDonate} favor with {props.faction.name} +

    + ) : ( +
    + - - - { - props.disabled ? -

    Unlocked at {props.favorToDonate} favor with {props.faction.name}

    : -
    - -
    - } -
    -
    ); +
    + )} +
    +
    + ); } diff --git a/src/Faction/ui/FactionList.tsx b/src/Faction/ui/FactionList.tsx index 15a6bda48..9767d7489 100644 --- a/src/Faction/ui/FactionList.tsx +++ b/src/Faction/ui/FactionList.tsx @@ -1,50 +1,71 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { IEngine } from "../../IEngine"; import { Factions } from "../Factions"; import { displayFactionContent, joinFaction } from "../FactionHelpers"; interface IProps { - player: IPlayer; - engine: IEngine; + player: IPlayer; + engine: IEngine; } export function FactionList(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; + const setRerender = useState(false)[1]; - function openFaction(faction: string): void { - props.engine.loadFactionContent(); - displayFactionContent(faction); - } + function openFaction(faction: string): void { + props.engine.loadFactionContent(); + displayFactionContent(faction); + } - function acceptInvitation(event: React.MouseEvent, faction: string): void { - if (!event.isTrusted) return; - joinFaction(Factions[faction]); - setRerender(x => !x); - } + function acceptInvitation( + event: React.MouseEvent, + faction: string, + ): void { + if (!event.isTrusted) return; + joinFaction(Factions[faction]); + setRerender((x) => !x); + } - return (<> -

    Factions

    -

    Lists all factions you have joined

    -
    - -
    -

    Outstanding Faction Invitations

    -

    Lists factions you have been invited to. You can accept these faction invitations at any time.

    - - ); -} \ No newline at end of file + return ( + <> +

    Factions

    +

    Lists all factions you have joined

    +
    + +
    +

    Outstanding Faction Invitations

    +

    + Lists factions you have been invited to. You can accept these faction + invitations at any time. +

    + + + ); +} diff --git a/src/Faction/ui/Info.tsx b/src/Faction/ui/Info.tsx index 3915b2c50..968ef4c19 100644 --- a/src/Faction/ui/Info.tsx +++ b/src/Faction/ui/Info.tsx @@ -11,88 +11,98 @@ import { AutoupdatingParagraph } from "../../ui/React/AutoupdatingParagraph"; import { ParagraphWithTooltip } from "../../ui/React/ParagraphWithTooltip"; import { Reputation } from "../../ui/React/Reputation"; import { Favor } from "../../ui/React/Favor"; -import { MathComponent } from 'mathjax-react'; +import { MathComponent } from "mathjax-react"; type IProps = { - faction: Faction; - factionInfo: FactionInfo; -} + faction: Faction; + factionInfo: FactionInfo; +}; type IInnerHTMLMarkup = { - __html: string; -} + __html: string; +}; const blockStyleMarkup = { - display: "block", -} + display: "block", +}; const infoStyleMarkup = { - display: "block", - width: "70%", -} + display: "block", + width: "70%", +}; export class Info extends React.Component { - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.getFavorGainContent = this.getFavorGainContent.bind(this); - this.getReputationContent = this.getReputationContent.bind(this); - } + this.getFavorGainContent = this.getFavorGainContent.bind(this); + this.getReputationContent = this.getReputationContent.bind(this); + } - getFavorGainContent(): JSX.Element { - const favorGain = this.props.faction.getFavorGain()[0]; - return (<> - You will have {Favor(this.props.faction.favor+favorGain)} faction favor after installing an Augmentation. - - - ); - } + getFavorGainContent(): JSX.Element { + const favorGain = this.props.faction.getFavorGain()[0]; + return ( + <> + You will have {Favor(this.props.faction.favor + favorGain)} faction + favor after installing an Augmentation. + + + + ); + } - getReputationContent(): JSX.Element { - return <>Reputation: {Reputation(this.props.faction.playerReputation)} - } + getReputationContent(): JSX.Element { + return <>Reputation: {Reputation(this.props.faction.playerReputation)}; + } - render(): React.ReactNode { - const favorTooltip = <> - Faction favor increases the rate at which you earn reputation for - this faction by 1% per favor. Faction favor is gained whenever you - install an Augmentation. The amount of - favor you gain depends on the total amount of reputation you earned with this faction. - Across all resets. - - - ; + render(): React.ReactNode { + const favorTooltip = ( + <> + Faction favor increases the rate at which you earn reputation for this + faction by 1% per favor. Faction favor is gained whenever you install an + Augmentation. The amount of favor you gain depends on the total amount + of reputation you earned with this faction. Across all resets. + + + + ); + const infoText: IInnerHTMLMarkup = { + __html: this.props.factionInfo.infoText, + }; - const infoText: IInnerHTMLMarkup = { - __html: this.props.factionInfo.infoText, - } - - return ( -
    -
    -                    
    -                
    -

    -------------------------

    - -

    -------------------------

    - Faction Favor: {Favor(this.props.faction.favor)}} - tooltip={favorTooltip} - /> -

    -------------------------

    -

    - Perform work/carry out assignments for your faction to help further its cause! - By doing so you will earn reputation for your faction. You will also gain - reputation passively over time, although at a very slow rate. Earning - reputation will allow you to purchase Augmentations through this faction, which - are powerful upgrades that enhance your abilities. -

    -
    - ) - } + return ( +
    +
    +          
    +        
    +

    -------------------------

    + +

    -------------------------

    + Faction Favor: {Favor(this.props.faction.favor)}} + tooltip={favorTooltip} + /> +

    -------------------------

    +

    + Perform work/carry out assignments for your faction to help further + its cause! By doing so you will earn reputation for your faction. You + will also gain reputation passively over time, although at a very slow + rate. Earning reputation will allow you to purchase Augmentations + through this faction, which are powerful upgrades that enhance your + abilities. +

    +
    + ); + } } diff --git a/src/Faction/ui/Option.tsx b/src/Faction/ui/Option.tsx index 148790886..239fad417 100644 --- a/src/Faction/ui/Option.tsx +++ b/src/Faction/ui/Option.tsx @@ -8,23 +8,23 @@ import * as React from "react"; import { StdButton } from "../../ui/React/StdButton"; type IProps = { - buttonText: string; - infoText: string; - onClick: (e: React.MouseEvent) => void; -} + buttonText: string; + infoText: string; + onClick: (e: React.MouseEvent) => void; +}; export class Option extends React.Component { - render(): React.ReactNode { - return ( -
    -
    - -

    {this.props.infoText}

    -
    -
    - ) - } + render(): React.ReactNode { + return ( +
    +
    + +

    {this.props.infoText}

    +
    +
    + ); + } } diff --git a/src/Faction/ui/PurchaseableAugmentation.tsx b/src/Faction/ui/PurchaseableAugmentation.tsx index c61a13320..c5bad029d 100644 --- a/src/Faction/ui/PurchaseableAugmentation.tsx +++ b/src/Faction/ui/PurchaseableAugmentation.tsx @@ -5,10 +5,10 @@ import * as React from "react"; import { - getNextNeurofluxLevel, - hasAugmentationPrereqs, - purchaseAugmentation, - purchaseAugmentationBoxCreate, + getNextNeurofluxLevel, + hasAugmentationPrereqs, + purchaseAugmentation, + purchaseAugmentationBoxCreate, } from "../FactionHelpers"; import { Augmentation } from "../../Augmentation/Augmentation"; @@ -25,137 +25,179 @@ import { StdButton } from "../../ui/React/StdButton"; import { Augmentation as AugFormat } from "../../ui/React/Augmentation"; type IProps = { - augName: string; - faction: Faction; - p: IPlayer; - rerender: () => void; -} + augName: string; + faction: Faction; + p: IPlayer; + rerender: () => void; +}; const spanStyleMarkup = { - margin: "4px", - padding: "4px", -} + margin: "4px", + padding: "4px", +}; const inlineStyleMarkup = { - display: "inline-block", -} + display: "inline-block", +}; export class PurchaseableAugmentation extends React.Component { - aug: Augmentation; + aug: Augmentation; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - const aug = Augmentations[this.props.augName]; - if(aug == null) throw new Error(`aug ${this.props.augName} does not exists`); - this.aug = aug; + const aug = Augmentations[this.props.augName]; + if (aug == null) + throw new Error(`aug ${this.props.augName} does not exists`); + this.aug = aug; - this.handleClick = this.handleClick.bind(this); + this.handleClick = this.handleClick.bind(this); + } + + getMoneyCost(): number { + return ( + this.aug.baseCost * this.props.faction.getInfo().augmentationPriceMult + ); + } + + getRepCost(): number { + return ( + this.aug.baseRepRequirement * + this.props.faction.getInfo().augmentationRepRequirementMult + ); + } + + handleClick(): void { + if (!Settings.SuppressBuyAugmentationConfirmation) { + purchaseAugmentationBoxCreate(this.aug, this.props.faction); + } else { + purchaseAugmentation(this.aug, this.props.faction); + } + } + + // Whether the player has the prerequisite Augmentations + hasPrereqs(): boolean { + return hasAugmentationPrereqs(this.aug); + } + + // Whether the player has enough rep for this Augmentation + hasReputation(): boolean { + return this.props.faction.playerReputation >= this.getRepCost(); + } + + // Whether the player has this augmentations (purchased OR installed) + owned(): boolean { + let owned = false; + for (const queuedAug of this.props.p.queuedAugmentations) { + if (queuedAug.name === this.props.augName) { + owned = true; + break; + } } - getMoneyCost(): number { - return this.aug.baseCost * this.props.faction.getInfo().augmentationPriceMult; + for (const installedAug of this.props.p.augmentations) { + if (installedAug.name === this.props.augName) { + owned = true; + break; + } } - getRepCost(): number { - return this.aug.baseRepRequirement * this.props.faction.getInfo().augmentationRepRequirementMult; + return owned; + } + + render(): React.ReactNode { + if (this.aug == null) { + console.error( + `Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${this.props.augName}`, + ); + return null; } - handleClick(): void { - if (!Settings.SuppressBuyAugmentationConfirmation) { - purchaseAugmentationBoxCreate(this.aug, this.props.faction); - } else { - purchaseAugmentation(this.aug, this.props.faction); - } + const moneyCost = this.getMoneyCost(); + const repCost = this.getRepCost(); + + // Determine UI properties + let disabled = false; + let status: JSX.Element = <>; + let color = ""; + if (!this.hasPrereqs()) { + disabled = true; + status = ( + <> + LOCKED (Requires {this.aug.prereqs.map((aug) => AugFormat(aug))} as + prerequisite) + + ); + color = "red"; + } else if ( + this.aug.name !== AugmentationNames.NeuroFluxGovernor && + (this.aug.owned || this.owned()) + ) { + disabled = true; + } else if (this.hasReputation()) { + status = ( + <> + UNLOCKED (at {Reputation(repCost)} faction reputation) -{" "} + + + ); + } else { + disabled = true; + status = ( + <> + LOCKED (Requires {Reputation(repCost)} faction reputation -{" "} + ) + + ); + color = "red"; } - // Whether the player has the prerequisite Augmentations - hasPrereqs(): boolean { - return hasAugmentationPrereqs(this.aug); + const txtStyle: IMap = { + display: "inline-block", + }; + if (color !== "") { + txtStyle.color = color; } - // Whether the player has enough rep for this Augmentation - hasReputation(): boolean { - return this.props.faction.playerReputation >= this.getRepCost(); + // Determine button txt + let btnTxt = this.aug.name; + if (this.aug.name === AugmentationNames.NeuroFluxGovernor) { + btnTxt += ` - Level ${getNextNeurofluxLevel()}`; } - // Whether the player has this augmentations (purchased OR installed) - owned(): boolean { - let owned = false; - for (const queuedAug of this.props.p.queuedAugmentations) { - if (queuedAug.name === this.props.augName) { - owned = true; - break; - } - } + let tooltip = <>; + if (typeof this.aug.info === "string") + tooltip = ( + <> + +
    +
    + {this.aug.stats} + + ); + else + tooltip = ( + <> + {this.aug.info} +
    +
    + {this.aug.stats} + + ); - for (const installedAug of this.props.p.augmentations) { - if (installedAug.name === this.props.augName) { - owned = true; - break; - } - } - - return owned; - } - - render(): React.ReactNode { - if (this.aug == null) { - console.error(`Invalid Augmentation when trying to create PurchaseableAugmentation display element: ${this.props.augName}`); - return null; - } - - const moneyCost = this.getMoneyCost(); - const repCost = this.getRepCost(); - - // Determine UI properties - let disabled = false; - let status: JSX.Element = <>; - let color = ""; - if (!this.hasPrereqs()) { - disabled = true; - status = <>LOCKED (Requires {this.aug.prereqs.map(aug => AugFormat(aug))} as prerequisite); - color = "red"; - } else if (this.aug.name !== AugmentationNames.NeuroFluxGovernor && (this.aug.owned || this.owned())) { - disabled = true; - } else if (this.hasReputation()) { - status = <>UNLOCKED (at {Reputation(repCost)} faction reputation) - ; - } else { - disabled = true; - status = <>LOCKED (Requires {Reputation(repCost)} faction reputation - ); - color = "red"; - } - - const txtStyle: IMap = { - display: "inline-block", - } - if (color !== "") { txtStyle.color = color; } - - // Determine button txt - let btnTxt = this.aug.name; - if (this.aug.name === AugmentationNames.NeuroFluxGovernor) { - btnTxt += ` - Level ${getNextNeurofluxLevel()}`; - } - - let tooltip = <>; - if(typeof this.aug.info === "string") - tooltip = <>

    {this.aug.stats} - else - tooltip = <>{this.aug.info}

    {this.aug.stats} - - return ( -
  • - - -

    {status}

    -
    -
  • - ) - } + return ( +
  • + + +

    {status}

    +
    +
  • + ); + } } diff --git a/src/Faction/ui/Root.tsx b/src/Faction/ui/Root.tsx index 621772374..0de93f1ec 100644 --- a/src/Faction/ui/Root.tsx +++ b/src/Faction/ui/Root.tsx @@ -20,286 +20,298 @@ import { createSleevePurchasesFromCovenantPopup } from "../../PersonObjects/Slee import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; import { - yesNoBoxClose, - yesNoBoxCreate, - yesNoBoxGetNoButton, - yesNoBoxGetYesButton, + yesNoBoxClose, + yesNoBoxCreate, + yesNoBoxGetNoButton, + yesNoBoxGetYesButton, } from "../../../utils/YesNoBox"; type IProps = { - engine: IEngine; - initiallyOnAugmentationsPage?: boolean; - faction: Faction; - p: IPlayer; - startHackingMissionFn: (faction: Faction) => void; -} + engine: IEngine; + initiallyOnAugmentationsPage?: boolean; + faction: Faction; + p: IPlayer; + startHackingMissionFn: (faction: Faction) => void; +}; type IState = { - rerenderFlag: boolean; - purchasingAugs: boolean; -} + rerenderFlag: boolean; + purchasingAugs: boolean; +}; // Info text for all options on the UI -const gangInfo = "Create and manage a gang for this Faction. Gangs will earn you money and " + - "faction reputation"; -const hackingMissionInfo = "Attempt a hacking mission for your faction. " + - "A mission is a mini game that, if won, earns you " + - "significant reputation with this faction. (Recommended hacking level: 200+)"; -const hackingContractsInfo = "Complete hacking contracts for your faction. " + - "Your effectiveness, which determines how much " + - "reputation you gain for this faction, is based on your hacking skill. " + - "You will gain hacking exp."; -const fieldWorkInfo = "Carry out field missions for your faction. " + - "Your effectiveness, which determines how much " + - "reputation you gain for this faction, is based on all of your stats. " + - "You will gain exp for all stats."; -const securityWorkInfo = "Serve in a security detail for your faction. " + - "Your effectiveness, which determines how much " + - "reputation you gain for this faction, is based on your combat stats. " + - "You will gain exp for all combat stats."; -const augmentationsInfo = "As your reputation with this faction rises, you will " + - "unlock Augmentations, which you can purchase to enhance " + - "your abilities."; -const sleevePurchasesInfo = "Purchase Duplicate Sleeves and upgrades. These are permanent!"; +const gangInfo = + "Create and manage a gang for this Faction. Gangs will earn you money and " + + "faction reputation"; +const hackingMissionInfo = + "Attempt a hacking mission for your faction. " + + "A mission is a mini game that, if won, earns you " + + "significant reputation with this faction. (Recommended hacking level: 200+)"; +const hackingContractsInfo = + "Complete hacking contracts for your faction. " + + "Your effectiveness, which determines how much " + + "reputation you gain for this faction, is based on your hacking skill. " + + "You will gain hacking exp."; +const fieldWorkInfo = + "Carry out field missions for your faction. " + + "Your effectiveness, which determines how much " + + "reputation you gain for this faction, is based on all of your stats. " + + "You will gain exp for all stats."; +const securityWorkInfo = + "Serve in a security detail for your faction. " + + "Your effectiveness, which determines how much " + + "reputation you gain for this faction, is based on your combat stats. " + + "You will gain exp for all combat stats."; +const augmentationsInfo = + "As your reputation with this faction rises, you will " + + "unlock Augmentations, which you can purchase to enhance " + + "your abilities."; +const sleevePurchasesInfo = + "Purchase Duplicate Sleeves and upgrades. These are permanent!"; const GangNames = [ - "Slum Snakes", - "Tetrads", - "The Syndicate", - "The Dark Army", - "Speakers for the Dead", - "NiteSec", - "The Black Hand", + "Slum Snakes", + "Tetrads", + "The Syndicate", + "The Dark Army", + "Speakers for the Dead", + "NiteSec", + "The Black Hand", ]; export class FactionRoot extends React.Component { - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.state = { - rerenderFlag: false, - purchasingAugs: props.initiallyOnAugmentationsPage ? props.initiallyOnAugmentationsPage : false, - } + this.state = { + rerenderFlag: false, + purchasingAugs: props.initiallyOnAugmentationsPage + ? props.initiallyOnAugmentationsPage + : false, + }; - this.manageGang = this.manageGang.bind(this); - this.rerender = this.rerender.bind(this); - this.routeToMain = this.routeToMain.bind(this); - this.routeToPurchaseAugs = this.routeToPurchaseAugs.bind(this); - this.sleevePurchases = this.sleevePurchases.bind(this); - this.startFieldWork = this.startFieldWork.bind(this); - this.startHackingContracts = this.startHackingContracts.bind(this); - this.startHackingMission = this.startHackingMission.bind(this); - this.startSecurityWork = this.startSecurityWork.bind(this); + this.manageGang = this.manageGang.bind(this); + this.rerender = this.rerender.bind(this); + this.routeToMain = this.routeToMain.bind(this); + this.routeToPurchaseAugs = this.routeToPurchaseAugs.bind(this); + this.sleevePurchases = this.sleevePurchases.bind(this); + this.startFieldWork = this.startFieldWork.bind(this); + this.startHackingContracts = this.startHackingContracts.bind(this); + this.startHackingMission = this.startHackingMission.bind(this); + this.startSecurityWork = this.startSecurityWork.bind(this); + } + + manageGang(): void { + // If player already has a gang, just go to the gang UI + if (this.props.p.inGang()) { + return this.props.engine.loadGangContent(); } - manageGang(): void { - // If player already has a gang, just go to the gang UI - if (this.props.p.inGang()) { - return this.props.engine.loadGangContent(); - } - - // Otherwise, we have to create the gang - const facName = this.props.faction.name; - let isHacking = false; - if (facName === "NiteSec" || facName === "The Black Hand") { - isHacking = true; - } - - // A Yes/No popup box will allow the player to confirm gang creation - const yesBtn = yesNoBoxGetYesButton(); - const noBtn = yesNoBoxGetNoButton(); - if (yesBtn == null || noBtn == null) { return; } - - yesBtn.innerHTML = "Create Gang"; - yesBtn.addEventListener("click", () => { - this.props.p.startGang(facName, isHacking); - const worldMenuHeader = document.getElementById("world-menu-header"); - if (worldMenuHeader instanceof HTMLElement) { - worldMenuHeader.click(); worldMenuHeader.click(); - } - - this.props.engine.loadGangContent(); - yesNoBoxClose(); - }); - - noBtn.innerHTML = "Cancel"; - noBtn.addEventListener("click", () => { - yesNoBoxClose(); - }); - - // Pop-up text - let gangTypeText = ""; - if (isHacking) { - gangTypeText = "This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " + - "Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " + - "is not as important.

    "; - } else { - gangTypeText = "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " + - "Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " + - "is more important. However, well-managed combat gangs can progress faster than hacking ones.

    "; - } - yesNoBoxCreate(`Would you like to create a new Gang with ${facName}?

    ` + - "Note that this will prevent you from creating a Gang with any other Faction until " + - "this BitNode is destroyed. It also resets your reputation with this faction.

    " + - gangTypeText + - "Other than hacking vs combat, there are NO differences between the Factions you can " + - "create a Gang with, and each of these Factions have all Augmentations available."); + // Otherwise, we have to create the gang + const facName = this.props.faction.name; + let isHacking = false; + if (facName === "NiteSec" || facName === "The Black Hand") { + isHacking = true; } - rerender(): void { - this.setState((prevState) => { - return { - rerenderFlag: !prevState.rerenderFlag, - } - }); + // A Yes/No popup box will allow the player to confirm gang creation + const yesBtn = yesNoBoxGetYesButton(); + const noBtn = yesNoBoxGetNoButton(); + if (yesBtn == null || noBtn == null) { + return; } - // Route to the main faction page - routeToMain(): void { - this.setState({ purchasingAugs: false }); + yesBtn.innerHTML = "Create Gang"; + yesBtn.addEventListener("click", () => { + this.props.p.startGang(facName, isHacking); + const worldMenuHeader = document.getElementById("world-menu-header"); + if (worldMenuHeader instanceof HTMLElement) { + worldMenuHeader.click(); + worldMenuHeader.click(); + } + + this.props.engine.loadGangContent(); + yesNoBoxClose(); + }); + + noBtn.innerHTML = "Cancel"; + noBtn.addEventListener("click", () => { + yesNoBoxClose(); + }); + + // Pop-up text + let gangTypeText = ""; + if (isHacking) { + gangTypeText = + "This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " + + "Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " + + "is not as important.

    "; + } else { + gangTypeText = + "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " + + "Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " + + "is more important. However, well-managed combat gangs can progress faster than hacking ones.

    "; + } + yesNoBoxCreate( + `Would you like to create a new Gang with ${facName}?

    ` + + "Note that this will prevent you from creating a Gang with any other Faction until " + + "this BitNode is destroyed. It also resets your reputation with this faction.

    " + + gangTypeText + + "Other than hacking vs combat, there are NO differences between the Factions you can " + + "create a Gang with, and each of these Factions have all Augmentations available.", + ); + } + + rerender(): void { + this.setState((prevState) => { + return { + rerenderFlag: !prevState.rerenderFlag, + }; + }); + } + + // Route to the main faction page + routeToMain(): void { + this.setState({ purchasingAugs: false }); + } + + // Route to the purchase augmentation UI for this faction + routeToPurchaseAugs(): void { + this.setState({ purchasingAugs: true }); + } + + sleevePurchases(): void { + createSleevePurchasesFromCovenantPopup(this.props.p); + } + + startFieldWork(): void { + this.props.p.startFactionFieldWork(this.props.faction); + } + + startHackingContracts(): void { + this.props.p.startFactionHackWork(this.props.faction); + } + + startHackingMission(): void { + const fac = this.props.faction; + this.props.p.singularityStopWork(); + this.props.engine.loadMissionContent(); + this.props.startHackingMissionFn(fac); + } + + startSecurityWork(): void { + this.props.p.startFactionSecurityWork(this.props.faction); + } + + render(): React.ReactNode { + return this.state.purchasingAugs + ? this.renderAugmentationsPage() + : this.renderMainPage(); + } + + renderMainPage(): React.ReactNode { + const p = this.props.p; + const faction = this.props.faction; + const factionInfo = faction.getInfo(); + + // We have a special flag for whether the player this faction is the player's + // gang faction because if the player has a gang, they cannot do any other action + const isPlayersGang = p.inGang() && p.getGangName() === faction.name; + + // Flags for whether special options (gang, sleeve purchases, donate, etc.) + // should be shown + const favorToDonate = Math.floor( + CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction, + ); + const canDonate = faction.favor >= favorToDonate; + + const canPurchaseSleeves = + faction.name === "The Covenant" && + p.bitNodeN >= 10 && + SourceFileFlags[10]; + + let canAccessGang = p.canAccessGang() && GangNames.includes(faction.name); + if (p.inGang()) { + if (p.getGangName() !== faction.name) { + canAccessGang = false; + } else if (p.getGangName() === faction.name) { + canAccessGang = true; + } } - // Route to the purchase augmentation UI for this faction - routeToPurchaseAugs(): void { - this.setState({ purchasingAugs: true }); - } + return ( +
    +

    {faction.name}

    + + {canAccessGang && ( +
    + ); + } - sleevePurchases(): void { - createSleevePurchasesFromCovenantPopup(this.props.p); - } - - startFieldWork(): void { - this.props.p.startFactionFieldWork(this.props.faction); - } - - startHackingContracts(): void { - this.props.p.startFactionHackWork(this.props.faction); - } - - startHackingMission(): void { - const fac = this.props.faction; - this.props.p.singularityStopWork(); - this.props.engine.loadMissionContent(); - this.props.startHackingMissionFn(fac); - } - - startSecurityWork(): void { - this.props.p.startFactionSecurityWork(this.props.faction); - } - - render(): React.ReactNode { - return this.state.purchasingAugs ? this.renderAugmentationsPage() : this.renderMainPage(); - } - - renderMainPage(): React.ReactNode { - const p = this.props.p; - const faction = this.props.faction; - const factionInfo = faction.getInfo(); - - // We have a special flag for whether the player this faction is the player's - // gang faction because if the player has a gang, they cannot do any other action - const isPlayersGang = p.inGang() && (p.getGangName() === faction.name); - - - // Flags for whether special options (gang, sleeve purchases, donate, etc.) - // should be shown - const favorToDonate = Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction); - const canDonate = faction.favor >= favorToDonate; - - const canPurchaseSleeves = (faction.name === "The Covenant" && p.bitNodeN >= 10 && SourceFileFlags[10]); - - let canAccessGang = (p.canAccessGang() && GangNames.includes(faction.name)); - if (p.inGang()) { - if (p.getGangName() !== faction.name) { - canAccessGang = false; - } else if (p.getGangName() === faction.name) { - canAccessGang = true; - } - } - - return ( -
    -

    {faction.name}

    - - { - canAccessGang && -
    - ) - } - - renderAugmentationsPage(): React.ReactNode { - return ( -
    - -
    - ) - } + renderAugmentationsPage(): React.ReactNode { + return ( +
    + +
    + ); + } } diff --git a/src/Fconf/Fconf.d.ts b/src/Fconf/Fconf.d.ts index 01f636bfd..d5e264e27 100644 --- a/src/Fconf/Fconf.d.ts +++ b/src/Fconf/Fconf.d.ts @@ -1 +1 @@ -export declare function parseFconfSettings(config: string): void; \ No newline at end of file +export declare function parseFconfSettings(config: string): void; diff --git a/src/Fconf/Fconf.js b/src/Fconf/Fconf.js index dbed7892a..f8719dce3 100644 --- a/src/Fconf/Fconf.js +++ b/src/Fconf/Fconf.js @@ -1,248 +1,302 @@ -import { FconfSettings } from "./FconfSettings"; +import { FconfSettings } from "./FconfSettings"; -import { parse, Node } from "acorn"; -import { dialogBoxCreate } from "../../utils/DialogBox"; +import { parse, Node } from "acorn"; +import { dialogBoxCreate } from "../../utils/DialogBox"; var FconfComments = { - ENABLE_BASH_HOTKEYS: "Improved Bash emulation mode. Setting this to 1 enables several\n" + - "new Terminal shortcuts and features that more closely resemble\n" + - "a real Bash-style shell. Note that when this mode is enabled,\n" + - "the default browser shortcuts are overriden by the new Bash\n" + - "shortcuts.\n\n" + - "To see a full list of the Terminal shortcuts that this enables, see:\n" + - "http://bitburner.readthedocs.io/en/latest/shortcuts.html", - ENABLE_TIMESTAMPS: "Terminal commands and log entries will be timestamped. The timestamp\n" + - "will have the format: M/D h:m", - MAIN_MENU_STYLE: "Customize the main navigation menu on the left-hand side. Current options:\n\n" + - "default, classic, compact", - THEME_BACKGROUND_COLOR: "Sets the background color for not only the Terminal, but also for\n" + - "most of the game's UI.\n\n" + - "The color must be specified as a pound sign (#) followed by a \n" + - "3-digit or 6-digit hex color code (e.g. #123456). Default color: #000000", - THEME_FONT_COLOR: "Sets the font color for not only the Terminal, but also for\n" + - "most of the game's UI.\n\n" + - "The color must be specified as a pound sign (#) followed by a \n" + - "3-digit or 6-digit hex color code (e.g. #123456). Default color: #66ff33", - THEME_HIGHLIGHT_COLOR: "Sets the highlight color for not only the Terminal, but also for \n" + - "most of the game's UI.\n\n" + - "The color must be specified as a pound sign (#) followed by a \n" + - "3-digit or 6-digit hex color code (e.g. #123456). Default color: #ffffff", - THEME_PROMPT_COLOR: "Sets the prompt color in the Terminal\n\n" + - "The color must be specified as a pound sign (#) followed by a \n" + - "3-digit or 6-digit hex color code (e.g. #123456). Default color: #f92672", - WRAP_INPUT: "Wrap Terminal Input. If this is enabled, then when a Terminal command is\n" + - "too long and overflows, then it will wrap to the next line instead of\n" + - "side-scrolling\n\n" + - "Note that after you enable/disable this, you'll have to run a command\n" + - "before its effect takes place.", -} + ENABLE_BASH_HOTKEYS: + "Improved Bash emulation mode. Setting this to 1 enables several\n" + + "new Terminal shortcuts and features that more closely resemble\n" + + "a real Bash-style shell. Note that when this mode is enabled,\n" + + "the default browser shortcuts are overriden by the new Bash\n" + + "shortcuts.\n\n" + + "To see a full list of the Terminal shortcuts that this enables, see:\n" + + "http://bitburner.readthedocs.io/en/latest/shortcuts.html", + ENABLE_TIMESTAMPS: + "Terminal commands and log entries will be timestamped. The timestamp\n" + + "will have the format: M/D h:m", + MAIN_MENU_STYLE: + "Customize the main navigation menu on the left-hand side. Current options:\n\n" + + "default, classic, compact", + THEME_BACKGROUND_COLOR: + "Sets the background color for not only the Terminal, but also for\n" + + "most of the game's UI.\n\n" + + "The color must be specified as a pound sign (#) followed by a \n" + + "3-digit or 6-digit hex color code (e.g. #123456). Default color: #000000", + THEME_FONT_COLOR: + "Sets the font color for not only the Terminal, but also for\n" + + "most of the game's UI.\n\n" + + "The color must be specified as a pound sign (#) followed by a \n" + + "3-digit or 6-digit hex color code (e.g. #123456). Default color: #66ff33", + THEME_HIGHLIGHT_COLOR: + "Sets the highlight color for not only the Terminal, but also for \n" + + "most of the game's UI.\n\n" + + "The color must be specified as a pound sign (#) followed by a \n" + + "3-digit or 6-digit hex color code (e.g. #123456). Default color: #ffffff", + THEME_PROMPT_COLOR: + "Sets the prompt color in the Terminal\n\n" + + "The color must be specified as a pound sign (#) followed by a \n" + + "3-digit or 6-digit hex color code (e.g. #123456). Default color: #f92672", + WRAP_INPUT: + "Wrap Terminal Input. If this is enabled, then when a Terminal command is\n" + + "too long and overflows, then it will wrap to the next line instead of\n" + + "side-scrolling\n\n" + + "Note that after you enable/disable this, you'll have to run a command\n" + + "before its effect takes place.", +}; const MainMenuStyleOptions = ["default", "classic", "compact"]; //Parse Fconf settings from the config text //Throws an exception if parsing fails function parseFconfSettings(config) { - var ast = parse(config, {sourceType:"module"}); - var queue = []; - queue.push(ast); - while (queue.length != 0) { - var exp = queue.shift(); - switch (exp.type) { - case "BlockStatement": - case "Program": - for (var i = 0; i < exp.body.length; ++i) { - if (exp.body[i] instanceof Node) { - queue.push(exp.body[i]); - } - } - break; - case "AssignmentExpression": - var setting, value; - if (exp.left != null && exp.left.name != null) { - setting = exp.left.name; - } else { - break; - } - if (exp.right != null && exp.right.raw != null) { - value = exp.right.raw; - } else { - break; - } - parseFconfSetting(setting, value); - break; - default: - break; + var ast = parse(config, { sourceType: "module" }); + var queue = []; + queue.push(ast); + while (queue.length != 0) { + var exp = queue.shift(); + switch (exp.type) { + case "BlockStatement": + case "Program": + for (var i = 0; i < exp.body.length; ++i) { + if (exp.body[i] instanceof Node) { + queue.push(exp.body[i]); + } } - - for (var prop in exp) { - if (exp.hasOwnProperty(prop)) { - if (exp[prop] instanceof Node) { - queue.push(exp[prop]); - } - } + break; + case "AssignmentExpression": + var setting, value; + if (exp.left != null && exp.left.name != null) { + setting = exp.left.name; + } else { + break; } + if (exp.right != null && exp.right.raw != null) { + value = exp.right.raw; + } else { + break; + } + parseFconfSetting(setting, value); + break; + default: + break; } - setTheme(); - setMainMenuStyle(); + for (var prop in exp) { + if (exp.hasOwnProperty(prop)) { + if (exp[prop] instanceof Node) { + queue.push(exp[prop]); + } + } + } + } + + setTheme(); + setMainMenuStyle(); } function parseFconfSetting(setting, value) { - setting = String(setting); - value = String(value); - if (setting == null || value == null || FconfSettings[setting] == null) { - console.warn(`Invalid .fconf setting: ${setting}`); - return; - } + setting = String(setting); + value = String(value); + if (setting == null || value == null || FconfSettings[setting] == null) { + console.warn(`Invalid .fconf setting: ${setting}`); + return; + } - function sanitizeString(value) { - value = value.toLowerCase(); - if (value.startsWith('"')) { value = value.slice(1); } - if (value.endsWith('"')) { value = value.slice(0, -1); } - return value; + function sanitizeString(value) { + value = value.toLowerCase(); + if (value.startsWith('"')) { + value = value.slice(1); } + if (value.endsWith('"')) { + value = value.slice(0, -1); + } + return value; + } - switch(setting) { - case "ENABLE_BASH_HOTKEYS": - case "ENABLE_TIMESTAMPS": - case "WRAP_INPUT": - // Need to convert entered value to boolean/strings accordingly - var value = value.toLowerCase(); - if (value === "1" || value === "true" || value === "y") { - value = true; - } else { - value = false; - } - FconfSettings[setting] = value; - break; - case "MAIN_MENU_STYLE": - var value = sanitizeString(value); - if (MainMenuStyleOptions.includes(value)) { - FconfSettings[setting] = value; - } else { - dialogBoxCreate(`Invalid option specified for ${setting}. Options: ${MainMenuStyleOptions.toString()}`); - } - break; - case "THEME_BACKGROUND_COLOR": - case "THEME_FONT_COLOR": - case "THEME_HIGHLIGHT_COLOR": - case "THEME_PROMPT_COLOR": - var value = sanitizeString(value); - if ((/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(value))) { - FconfSettings[setting] = value; - } else { - dialogBoxCreate(`Invalid color specified for ${setting}. Must be a hex color code preceded by a pound (#)`); - } - break; - default: - break; - } + switch (setting) { + case "ENABLE_BASH_HOTKEYS": + case "ENABLE_TIMESTAMPS": + case "WRAP_INPUT": + // Need to convert entered value to boolean/strings accordingly + var value = value.toLowerCase(); + if (value === "1" || value === "true" || value === "y") { + value = true; + } else { + value = false; + } + FconfSettings[setting] = value; + break; + case "MAIN_MENU_STYLE": + var value = sanitizeString(value); + if (MainMenuStyleOptions.includes(value)) { + FconfSettings[setting] = value; + } else { + dialogBoxCreate( + `Invalid option specified for ${setting}. Options: ${MainMenuStyleOptions.toString()}`, + ); + } + break; + case "THEME_BACKGROUND_COLOR": + case "THEME_FONT_COLOR": + case "THEME_HIGHLIGHT_COLOR": + case "THEME_PROMPT_COLOR": + var value = sanitizeString(value); + if (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(value)) { + FconfSettings[setting] = value; + } else { + dialogBoxCreate( + `Invalid color specified for ${setting}. Must be a hex color code preceded by a pound (#)`, + ); + } + break; + default: + break; + } } //Create the .fconf file text from the settings function createFconf() { - var res = ""; - for (var setting in FconfSettings) { - if (FconfSettings.hasOwnProperty(setting)) { - //Setting comments (description) - var comment = FconfComments[setting]; - if (comment == null) {continue;} - var comment = comment.split("\n"); - for (var i = 0; i < comment.length; ++i) { - res += ("//" + comment[i] + "\n"); - } + var res = ""; + for (var setting in FconfSettings) { + if (FconfSettings.hasOwnProperty(setting)) { + //Setting comments (description) + var comment = FconfComments[setting]; + if (comment == null) { + continue; + } + var comment = comment.split("\n"); + for (var i = 0; i < comment.length; ++i) { + res += "//" + comment[i] + "\n"; + } - var value = 0; - if (FconfSettings[setting] === true) { - value = "1"; - } else if (FconfSettings[setting] === false) { - value = "0"; - } else { - value = '"' + String(FconfSettings[setting]) + '"'; - } - res += (`${setting} = ${value}\n\n`); - } + var value = 0; + if (FconfSettings[setting] === true) { + value = "1"; + } else if (FconfSettings[setting] === false) { + value = "0"; + } else { + value = '"' + String(FconfSettings[setting]) + '"'; + } + res += `${setting} = ${value}\n\n`; } - return res; + } + return res; } function loadFconf(saveString) { - let tempFconfSettings = JSON.parse(saveString); - for (var setting in tempFconfSettings) { - if (tempFconfSettings.hasOwnProperty(setting)) { - FconfSettings[setting] = tempFconfSettings[setting]; - } + let tempFconfSettings = JSON.parse(saveString); + for (var setting in tempFconfSettings) { + if (tempFconfSettings.hasOwnProperty(setting)) { + FconfSettings[setting] = tempFconfSettings[setting]; } + } - // Initialize themes/styles after loading - setTheme(); - setMainMenuStyle(); + // Initialize themes/styles after loading + setTheme(); + setMainMenuStyle(); } function setTheme() { - if (FconfSettings.THEME_HIGHLIGHT_COLOR == null || - FconfSettings.THEME_FONT_COLOR == null || - FconfSettings.THEME_BACKGROUND_COLOR == null || - FconfSettings.THEME_PROMPT_COLOR == null) { - console.error("Cannot find Theme Settings"); - return; - } - if (/^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_HIGHLIGHT_COLOR) && - /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_FONT_COLOR) && - /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_BACKGROUND_COLOR) && - /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_PROMPT_COLOR)) { - document.body.style.setProperty('--my-highlight-color', FconfSettings.THEME_HIGHLIGHT_COLOR); - document.body.style.setProperty('--my-font-color', FconfSettings.THEME_FONT_COLOR); - document.body.style.setProperty('--my-background-color', FconfSettings.THEME_BACKGROUND_COLOR); - document.body.style.setProperty('--my-prompt-color', FconfSettings.THEME_PROMPT_COLOR); - } + if ( + FconfSettings.THEME_HIGHLIGHT_COLOR == null || + FconfSettings.THEME_FONT_COLOR == null || + FconfSettings.THEME_BACKGROUND_COLOR == null || + FconfSettings.THEME_PROMPT_COLOR == null + ) { + console.error("Cannot find Theme Settings"); + return; + } + if ( + /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test( + FconfSettings.THEME_HIGHLIGHT_COLOR, + ) && + /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_FONT_COLOR) && + /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test( + FconfSettings.THEME_BACKGROUND_COLOR, + ) && + /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_PROMPT_COLOR) + ) { + document.body.style.setProperty( + "--my-highlight-color", + FconfSettings.THEME_HIGHLIGHT_COLOR, + ); + document.body.style.setProperty( + "--my-font-color", + FconfSettings.THEME_FONT_COLOR, + ); + document.body.style.setProperty( + "--my-background-color", + FconfSettings.THEME_BACKGROUND_COLOR, + ); + document.body.style.setProperty( + "--my-prompt-color", + FconfSettings.THEME_PROMPT_COLOR, + ); + } } function setMainMenuStyle() { - const mainMenu = document.getElementById("mainmenu"); - const hackingMenuHdr = document.getElementById("hacking-menu-header"); - const characterMenuHdr = document.getElementById("character-menu-header"); - const worldMenuHdr = document.getElementById("world-menu-header"); - const helpMenuHdr = document.getElementById("help-menu-header"); + const mainMenu = document.getElementById("mainmenu"); + const hackingMenuHdr = document.getElementById("hacking-menu-header"); + const characterMenuHdr = document.getElementById("character-menu-header"); + const worldMenuHdr = document.getElementById("world-menu-header"); + const helpMenuHdr = document.getElementById("help-menu-header"); - function removeAllAccordionHeaderClasses() { - hackingMenuHdr.classList.remove("mainmenu-accordion-header", "mainmenu-accordion-header-classic"); - characterMenuHdr.classList.remove("mainmenu-accordion-header", "mainmenu-accordion-header-classic"); - worldMenuHdr.classList.remove("mainmenu-accordion-header", "mainmenu-accordion-header-classic"); - helpMenuHdr.classList.remove("mainmenu-accordion-header", "mainmenu-accordion-header-classic"); - } + function removeAllAccordionHeaderClasses() { + hackingMenuHdr.classList.remove( + "mainmenu-accordion-header", + "mainmenu-accordion-header-classic", + ); + characterMenuHdr.classList.remove( + "mainmenu-accordion-header", + "mainmenu-accordion-header-classic", + ); + worldMenuHdr.classList.remove( + "mainmenu-accordion-header", + "mainmenu-accordion-header-classic", + ); + helpMenuHdr.classList.remove( + "mainmenu-accordion-header", + "mainmenu-accordion-header-classic", + ); + } - function addClassToAllAccordionHeaders(clsName) { - hackingMenuHdr.classList.add(clsName); - characterMenuHdr.classList.add(clsName); - worldMenuHdr.classList.add(clsName); - helpMenuHdr.classList.add(clsName); - } + function addClassToAllAccordionHeaders(clsName) { + hackingMenuHdr.classList.add(clsName); + characterMenuHdr.classList.add(clsName); + worldMenuHdr.classList.add(clsName); + helpMenuHdr.classList.add(clsName); + } - if (FconfSettings["MAIN_MENU_STYLE"] === "default") { - removeAllAccordionHeaderClasses(); - mainMenu.classList.remove("classic"); - mainMenu.classList.remove("compact"); - addClassToAllAccordionHeaders("mainmenu-accordion-header"); - } else if (FconfSettings["MAIN_MENU_STYLE"] === "classic") { - removeAllAccordionHeaderClasses(); - mainMenu.classList.remove("compact"); - mainMenu.classList.add("classic"); - addClassToAllAccordionHeaders("mainmenu-accordion-header-classic"); - } else if (FconfSettings["MAIN_MENU_STYLE"] === "compact") { - removeAllAccordionHeaderClasses(); - mainMenu.classList.remove("classic"); - mainMenu.classList.add("compact"); - addClassToAllAccordionHeaders("mainmenu-accordion-header-compact"); - } else { - return; - } + if (FconfSettings["MAIN_MENU_STYLE"] === "default") { + removeAllAccordionHeaderClasses(); + mainMenu.classList.remove("classic"); + mainMenu.classList.remove("compact"); + addClassToAllAccordionHeaders("mainmenu-accordion-header"); + } else if (FconfSettings["MAIN_MENU_STYLE"] === "classic") { + removeAllAccordionHeaderClasses(); + mainMenu.classList.remove("compact"); + mainMenu.classList.add("classic"); + addClassToAllAccordionHeaders("mainmenu-accordion-header-classic"); + } else if (FconfSettings["MAIN_MENU_STYLE"] === "compact") { + removeAllAccordionHeaderClasses(); + mainMenu.classList.remove("classic"); + mainMenu.classList.add("compact"); + addClassToAllAccordionHeaders("mainmenu-accordion-header-compact"); + } else { + return; + } - // Click each header twice to reset lol - hackingMenuHdr.click(); hackingMenuHdr.click(); - characterMenuHdr.click(); characterMenuHdr.click(); - worldMenuHdr.click(); worldMenuHdr.click(); - helpMenuHdr.click(); helpMenuHdr.click(); + // Click each header twice to reset lol + hackingMenuHdr.click(); + hackingMenuHdr.click(); + characterMenuHdr.click(); + characterMenuHdr.click(); + worldMenuHdr.click(); + worldMenuHdr.click(); + helpMenuHdr.click(); + helpMenuHdr.click(); } -export {FconfSettings, createFconf, parseFconfSettings, loadFconf} +export { FconfSettings, createFconf, parseFconfSettings, loadFconf }; diff --git a/src/Fconf/FconfSettings.ts b/src/Fconf/FconfSettings.ts index f1c08bc73..12c5e8a0e 100644 --- a/src/Fconf/FconfSettings.ts +++ b/src/Fconf/FconfSettings.ts @@ -1,10 +1,10 @@ export const FconfSettings = { - ENABLE_BASH_HOTKEYS: false, - ENABLE_TIMESTAMPS: false, - MAIN_MENU_STYLE: "default", - THEME_BACKGROUND_COLOR: "#000000", - THEME_FONT_COLOR: "#66ff33", - THEME_HIGHLIGHT_COLOR: "#ffffff", - THEME_PROMPT_COLOR: "#f92672", - WRAP_INPUT: false, -} + ENABLE_BASH_HOTKEYS: false, + ENABLE_TIMESTAMPS: false, + MAIN_MENU_STYLE: "default", + THEME_BACKGROUND_COLOR: "#000000", + THEME_FONT_COLOR: "#66ff33", + THEME_HIGHLIGHT_COLOR: "#ffffff", + THEME_PROMPT_COLOR: "#f92672", + WRAP_INPUT: false, +}; diff --git a/src/Gang/AllGangs.ts b/src/Gang/AllGangs.ts index 9bbfebb68..f2e1a502f 100644 --- a/src/Gang/AllGangs.ts +++ b/src/Gang/AllGangs.ts @@ -1,76 +1,76 @@ import { Reviver } from "../../utils/JSONReviver"; interface GangTerritory { - power: number; - territory: number; + power: number; + territory: number; } export let AllGangs: { - [key: string]: GangTerritory; + [key: string]: GangTerritory; } = { - "Slum Snakes" : { - power: 1, - territory: 1/7, - }, - "Tetrads" : { - power: 1, - territory: 1/7, - }, - "The Syndicate" : { - power: 1, - territory: 1/7, - }, - "The Dark Army" : { - power: 1, - territory: 1/7, - }, - "Speakers for the Dead" : { - power: 1, - territory: 1/7, - }, - "NiteSec" : { - power: 1, - territory: 1/7, - }, - "The Black Hand" : { - power: 1, - territory: 1/7, - }, -} + "Slum Snakes": { + power: 1, + territory: 1 / 7, + }, + Tetrads: { + power: 1, + territory: 1 / 7, + }, + "The Syndicate": { + power: 1, + territory: 1 / 7, + }, + "The Dark Army": { + power: 1, + territory: 1 / 7, + }, + "Speakers for the Dead": { + power: 1, + territory: 1 / 7, + }, + NiteSec: { + power: 1, + territory: 1 / 7, + }, + "The Black Hand": { + power: 1, + territory: 1 / 7, + }, +}; export function resetGangs(): void { - AllGangs = { - "Slum Snakes" : { - power: 1, - territory: 1/7, - }, - "Tetrads" : { - power: 1, - territory: 1/7, - }, - "The Syndicate" : { - power: 1, - territory: 1/7, - }, - "The Dark Army" : { - power: 1, - territory: 1/7, - }, - "Speakers for the Dead" : { - power: 1, - territory: 1/7, - }, - "NiteSec" : { - power: 1, - territory: 1/7, - }, - "The Black Hand" : { - power: 1, - territory: 1/7, - }, - } + AllGangs = { + "Slum Snakes": { + power: 1, + territory: 1 / 7, + }, + Tetrads: { + power: 1, + territory: 1 / 7, + }, + "The Syndicate": { + power: 1, + territory: 1 / 7, + }, + "The Dark Army": { + power: 1, + territory: 1 / 7, + }, + "Speakers for the Dead": { + power: 1, + territory: 1 / 7, + }, + NiteSec: { + power: 1, + territory: 1 / 7, + }, + "The Black Hand": { + power: 1, + territory: 1 / 7, + }, + }; } export function loadAllGangs(saveString: string): void { - AllGangs = JSON.parse(saveString, Reviver); -} \ No newline at end of file + AllGangs = JSON.parse(saveString, Reviver); +} diff --git a/src/Gang/Gang.ts b/src/Gang/Gang.ts index b530b38b5..73db17dcd 100644 --- a/src/Gang/Gang.ts +++ b/src/Gang/Gang.ts @@ -1,18 +1,17 @@ - /** * TODO * Add police clashes * balance point to keep them from running out of control -*/ + */ import { Faction } from "../Faction/Faction"; import { Factions } from "../Faction/Factions"; import { dialogBoxCreate } from "../../utils/DialogBox"; import { - Reviver, - Generic_toJSON, - Generic_fromJSON, + Reviver, + Generic_toJSON, + Generic_fromJSON, } from "../../utils/JSONReviver"; import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; @@ -30,382 +29,410 @@ import { GangMember } from "./GangMember"; import { WorkerScript } from "../Netscript/WorkerScript"; import { IPlayer } from "../PersonObjects/IPlayer"; - export class Gang { - facName: string; - members: GangMember[]; - wanted: number; - respect: number; + facName: string; + members: GangMember[]; + wanted: number; + respect: number; - isHackingGang: boolean; + isHackingGang: boolean; - respectGainRate: number; - wantedGainRate: number; - moneyGainRate: number; + respectGainRate: number; + wantedGainRate: number; + moneyGainRate: number; - storedCycles: number; + storedCycles: number; - storedTerritoryAndPowerCycles: number; + storedTerritoryAndPowerCycles: number; - territoryClashChance: number; - territoryWarfareEngaged: boolean; + territoryClashChance: number; + territoryWarfareEngaged: boolean; - notifyMemberDeath: boolean; + notifyMemberDeath: boolean; - constructor(facName = "", hacking = false) { - this.facName = facName; - this.members = []; - this.wanted = 1; - this.respect = 1; + constructor(facName = "", hacking = false) { + this.facName = facName; + this.members = []; + this.wanted = 1; + this.respect = 1; - this.isHackingGang = hacking; + this.isHackingGang = hacking; - this.respectGainRate = 0; - this.wantedGainRate = 0; - this.moneyGainRate = 0; + this.respectGainRate = 0; + this.wantedGainRate = 0; + this.moneyGainRate = 0; - // When processing gains, this stores the number of cycles until some - // limit is reached, and then calculates and applies the gains only at that limit - this.storedCycles = 0; + // When processing gains, this stores the number of cycles until some + // limit is reached, and then calculates and applies the gains only at that limit + this.storedCycles = 0; - // Separate variable to keep track of cycles for Territry + Power gang, which - // happens on a slower "clock" than normal processing - this.storedTerritoryAndPowerCycles = 0; + // Separate variable to keep track of cycles for Territry + Power gang, which + // happens on a slower "clock" than normal processing + this.storedTerritoryAndPowerCycles = 0; - this.territoryClashChance = 0; - this.territoryWarfareEngaged = false; + this.territoryClashChance = 0; + this.territoryWarfareEngaged = false; - this.notifyMemberDeath = true; + this.notifyMemberDeath = true; + } + + getPower(): number { + return AllGangs[this.facName].power; + } + + getTerritory(): number { + return AllGangs[this.facName].territory; + } + + process(numCycles = 1, player: IPlayer): void { + const CyclesPerSecond = 1000 / CONSTANTS._idleSpeed; + + if (isNaN(numCycles)) { + console.error(`NaN passed into Gang.process(): ${numCycles}`); } + this.storedCycles += numCycles; - getPower(): number { - return AllGangs[this.facName].power; + // Only process if there are at least 2 seconds, and at most 5 seconds + if (this.storedCycles < 2 * CyclesPerSecond) return; + const cycles = Math.min(this.storedCycles, 5 * CyclesPerSecond); + + try { + this.processGains(cycles, player); + this.processExperienceGains(cycles); + this.processTerritoryAndPowerGains(cycles); + this.storedCycles -= cycles; + } catch (e) { + console.error(`Exception caught when processing Gang: ${e}`); } + } - getTerritory(): number { - return AllGangs[this.facName].territory; + processGains(numCycles = 1, player: IPlayer): void { + // Get gains per cycle + let moneyGains = 0; + let respectGains = 0; + let wantedLevelGains = 0; + let justice = 0; + for (let i = 0; i < this.members.length; ++i) { + respectGains += this.members[i].calculateRespectGain(this); + moneyGains += this.members[i].calculateMoneyGain(this); + const wantedLevelGain = this.members[i].calculateWantedLevelGain(this); + wantedLevelGains += wantedLevelGain; + if (this.members[i].getTask().baseWanted < 0) justice++; // this member is lowering wanted. } + this.respectGainRate = respectGains; + this.wantedGainRate = wantedLevelGains; + this.moneyGainRate = moneyGains; + const gain = respectGains * numCycles; + this.respect += gain; + // Faction reputation gains is respect gain divided by some constant + const fac = Factions[this.facName]; + if (!(fac instanceof Faction)) { + dialogBoxCreate( + "ERROR: Could not get Faction associates with your gang. This is a bug, please report to game dev", + ); + throw new Error("Could not find the faction associated with this gang."); + } + const favorMult = 1 + fac.favor / 100; - process(numCycles = 1, player: IPlayer): void { - const CyclesPerSecond = 1000 / CONSTANTS._idleSpeed; + fac.playerReputation += + (player.faction_rep_mult * gain * favorMult) / + GangConstants.GangRespectToReputationRatio; - if (isNaN(numCycles)) { - console.error(`NaN passed into Gang.process(): ${numCycles}`); + // Keep track of respect gained per member + for (let i = 0; i < this.members.length; ++i) { + this.members[i].recordEarnedRespect(numCycles, this); + } + if (!(this.wanted === 1 && wantedLevelGains < 0)) { + const oldWanted = this.wanted; + let newWanted = oldWanted + wantedLevelGains * numCycles; + newWanted = newWanted * (1 - justice * 0.001); // safeguard + // Prevent overflow + if (wantedLevelGains <= 0 && newWanted > oldWanted) newWanted = 1; + + this.wanted = newWanted; + if (this.wanted < 1) this.wanted = 1; + } + player.gainMoney(moneyGains * numCycles); + player.recordMoneySource(moneyGains * numCycles, "gang"); + } + + processTerritoryAndPowerGains(numCycles = 1): void { + this.storedTerritoryAndPowerCycles += numCycles; + if ( + this.storedTerritoryAndPowerCycles < + GangConstants.CyclesPerTerritoryAndPowerUpdate + ) + return; + this.storedTerritoryAndPowerCycles -= + GangConstants.CyclesPerTerritoryAndPowerUpdate; + + // Process power first + const gangName = this.facName; + for (const name in AllGangs) { + if (AllGangs.hasOwnProperty(name)) { + if (name == gangName) { + AllGangs[name].power += this.calculatePower(); + } else { + // All NPC gangs get random power gains + const gainRoll = Math.random(); + if (gainRoll < 0.5) { + // Multiplicative gain (50% chance) + // This is capped per cycle, to prevent it from getting out of control + const multiplicativeGain = AllGangs[name].power * 0.005; + AllGangs[name].power += Math.min(0.85, multiplicativeGain); + } else { + // Additive gain (50% chance) + const additiveGain = 0.75 * gainRoll * AllGangs[name].territory; + AllGangs[name].power += additiveGain; + } } - this.storedCycles += numCycles; + } + } - // Only process if there are at least 2 seconds, and at most 5 seconds - if (this.storedCycles < 2 * CyclesPerSecond) return; - const cycles = Math.min(this.storedCycles, 5 * CyclesPerSecond); + // Determine if territory should be processed + if (this.territoryWarfareEngaged) { + this.territoryClashChance = 1; + } else if (this.territoryClashChance > 0) { + // Engagement turned off, but still a positive clash chance. So there's + // still a chance of clashing but it slowly goes down over time + this.territoryClashChance = Math.max(0, this.territoryClashChance - 0.01); + } - try { - this.processGains(cycles, player); - this.processExperienceGains(cycles); - this.processTerritoryAndPowerGains(cycles); - this.storedCycles -= cycles; - } catch(e) { - console.error(`Exception caught when processing Gang: ${e}`); + // Then process territory + for (let i = 0; i < GangConstants.Names.length; ++i) { + const others = GangConstants.Names.filter((e) => { + return e !== GangConstants.Names[i]; + }); + const other = getRandomInt(0, others.length - 1); + + const thisGang = GangConstants.Names[i]; + const otherGang = others[other]; + + // If either of the gangs involved in this clash is the player, determine + // whether to skip or process it using the clash chance + if (thisGang === gangName || otherGang === gangName) { + if (!(Math.random() < this.territoryClashChance)) continue; + } + + const thisPwr = AllGangs[thisGang].power; + const otherPwr = AllGangs[otherGang].power; + const thisChance = thisPwr / (thisPwr + otherPwr); + + function calculateTerritoryGain( + winGang: string, + loseGang: string, + ): number { + const powerBonus = Math.max( + 1, + 1 + + Math.log(AllGangs[winGang].power / AllGangs[loseGang].power) / + Math.log(50), + ); + const gains = Math.min( + AllGangs[loseGang].territory, + powerBonus * 0.0001 * (Math.random() + 0.5), + ); + return gains; + } + + if (Math.random() < thisChance) { + if (AllGangs[otherGang].territory <= 0) return; + const territoryGain = calculateTerritoryGain(thisGang, otherGang); + AllGangs[thisGang].territory += territoryGain; + AllGangs[otherGang].territory -= territoryGain; + if (thisGang === gangName) { + this.clash(true); // Player won + AllGangs[otherGang].power *= 1 / 1.01; + } else if (otherGang === gangName) { + this.clash(false); // Player lost + } else { + AllGangs[otherGang].power *= 1 / 1.01; } - } - - - processGains(numCycles = 1, player: IPlayer): void { - // Get gains per cycle - let moneyGains = 0; - let respectGains = 0; - let wantedLevelGains = 0; - let justice = 0; - for (let i = 0; i < this.members.length; ++i) { - respectGains += (this.members[i].calculateRespectGain(this)); - moneyGains += (this.members[i].calculateMoneyGain(this)); - const wantedLevelGain = this.members[i].calculateWantedLevelGain(this); - wantedLevelGains += wantedLevelGain; - if(this.members[i].getTask().baseWanted < 0) justice++; // this member is lowering wanted. + } else { + if (AllGangs[thisGang].territory <= 0) return; + const territoryGain = calculateTerritoryGain(otherGang, thisGang); + AllGangs[thisGang].territory -= territoryGain; + AllGangs[otherGang].territory += territoryGain; + if (thisGang === gangName) { + this.clash(false); // Player lost + } else if (otherGang === gangName) { + this.clash(true); // Player won + AllGangs[thisGang].power *= 1 / 1.01; + } else { + AllGangs[thisGang].power *= 1 / 1.01; } - this.respectGainRate = respectGains; - this.wantedGainRate = wantedLevelGains; - this.moneyGainRate = moneyGains; - const gain = respectGains * numCycles; - this.respect += gain; - // Faction reputation gains is respect gain divided by some constant - const fac = Factions[this.facName]; - if (!(fac instanceof Faction)) { - dialogBoxCreate("ERROR: Could not get Faction associates with your gang. This is a bug, please report to game dev"); - throw new Error('Could not find the faction associated with this gang.'); - } - const favorMult = 1 + (fac.favor / 100); + } + } + } - fac.playerReputation += ((player.faction_rep_mult * gain * favorMult) / GangConstants.GangRespectToReputationRatio); + processExperienceGains(numCycles = 1): void { + for (let i = 0; i < this.members.length; ++i) { + this.members[i].gainExperience(numCycles); + this.members[i].updateSkillLevels(); + } + } - // Keep track of respect gained per member - for (let i = 0; i < this.members.length; ++i) { - this.members[i].recordEarnedRespect(numCycles, this); - } - if (!(this.wanted === 1 && wantedLevelGains < 0)) { - const oldWanted = this.wanted; - let newWanted = oldWanted + (wantedLevelGains * numCycles); - newWanted = newWanted * (1 - justice * 0.001); // safeguard - // Prevent overflow - if (wantedLevelGains <= 0 && newWanted > oldWanted) newWanted = 1; + clash(won = false): void { + // Determine if a gang member should die + let baseDeathChance = 0.01; + if (won) baseDeathChance /= 2; + // If the clash was lost, the player loses a small percentage of power + else AllGangs[this.facName].power *= 1 / 1.008; - this.wanted = newWanted; - if (this.wanted < 1) this.wanted = 1; - } - player.gainMoney(moneyGains * numCycles); - player.recordMoneySource(moneyGains * numCycles, "gang"); + // Deaths can only occur during X% of clashes + if (Math.random() < 0.65) return; + + for (let i = this.members.length - 1; i >= 0; --i) { + const member = this.members[i]; + + // Only members assigned to Territory Warfare can die + if (member.task !== "Territory Warfare") continue; + + // Chance to die is decreased based on defense + const modifiedDeathChance = baseDeathChance / Math.pow(member.def, 0.6); + if (Math.random() < modifiedDeathChance) { + this.killMember(member); + } + } + } + + canRecruitMember(): boolean { + if (this.members.length >= GangConstants.MaximumGangMembers) return false; + return this.respect >= this.getRespectNeededToRecruitMember(); + } + + getRespectNeededToRecruitMember(): number { + // First N gang members are free (can be recruited at 0 respect) + const numFreeMembers = 3; + if (this.members.length < numFreeMembers) return 0; + + const i = this.members.length - (numFreeMembers - 1); + return Math.pow(5, i); + } + + recruitMember(name: string): boolean { + name = String(name); + if (name === "" || !this.canRecruitMember()) return false; + + // Check for already-existing names + const sameNames = this.members.filter((m) => m.name === name); + if (sameNames.length >= 1) return false; + + const member = new GangMember(name); + this.members.push(member); + return true; + } + + // Money and Respect gains multiplied by this number (< 1) + getWantedPenalty(): number { + return this.respect / (this.respect + this.wanted); + } + + //Calculates power GAIN, which is added onto the Gang's existing power + calculatePower(): number { + let memberTotal = 0; + for (let i = 0; i < this.members.length; ++i) { + if ( + !GangMemberTasks.hasOwnProperty(this.members[i].task) || + this.members[i].task !== "Territory Warfare" + ) + continue; + memberTotal += this.members[i].calculatePower(); + } + return 0.015 * Math.max(0.002, this.getTerritory()) * memberTotal; + } + + killMember(member: GangMember): void { + // Player loses a percentage of total respect, plus whatever respect that member has earned + const totalRespect = this.respect; + const lostRespect = 0.05 * totalRespect + member.earnedRespect; + this.respect = Math.max(0, totalRespect - lostRespect); + + for (let i = 0; i < this.members.length; ++i) { + if (member.name === this.members[i].name) { + this.members.splice(i, 1); + break; + } } - processTerritoryAndPowerGains(numCycles = 1): void { - this.storedTerritoryAndPowerCycles += numCycles; - if (this.storedTerritoryAndPowerCycles < GangConstants.CyclesPerTerritoryAndPowerUpdate) return; - this.storedTerritoryAndPowerCycles -= GangConstants.CyclesPerTerritoryAndPowerUpdate; - - // Process power first - const gangName = this.facName; - for (const name in AllGangs) { - if (AllGangs.hasOwnProperty(name)) { - if (name == gangName) { - AllGangs[name].power += this.calculatePower(); - } else { - // All NPC gangs get random power gains - const gainRoll = Math.random(); - if (gainRoll < 0.5) { - // Multiplicative gain (50% chance) - // This is capped per cycle, to prevent it from getting out of control - const multiplicativeGain = AllGangs[name].power * 0.005; - AllGangs[name].power += Math.min(0.85, multiplicativeGain); - } else { - // Additive gain (50% chance) - const additiveGain = 0.75 * gainRoll * AllGangs[name].territory; - AllGangs[name].power += (additiveGain); - } - } - } - } - - // Determine if territory should be processed - if (this.territoryWarfareEngaged) { - this.territoryClashChance = 1; - } else if (this.territoryClashChance > 0) { - // Engagement turned off, but still a positive clash chance. So there's - // still a chance of clashing but it slowly goes down over time - this.territoryClashChance = Math.max(0, this.territoryClashChance - 0.01); - } - - // Then process territory - for (let i = 0; i < GangConstants.Names.length; ++i) { - const others = GangConstants.Names.filter((e) => { - return e !== GangConstants.Names[i]; - }); - const other = getRandomInt(0, others.length - 1); - - const thisGang = GangConstants.Names[i]; - const otherGang = others[other]; - - // If either of the gangs involved in this clash is the player, determine - // whether to skip or process it using the clash chance - if (thisGang === gangName || otherGang === gangName) { - if (!(Math.random() < this.territoryClashChance)) continue; - } - - const thisPwr = AllGangs[thisGang].power; - const otherPwr = AllGangs[otherGang].power; - const thisChance = thisPwr / (thisPwr + otherPwr); - - function calculateTerritoryGain(winGang: string, loseGang: string): number { - const powerBonus = Math.max(1, 1+Math.log(AllGangs[winGang].power/AllGangs[loseGang].power)/Math.log(50)); - const gains = Math.min(AllGangs[loseGang].territory, powerBonus*0.0001*(Math.random()+.5)) - return gains; - } - - if (Math.random() < thisChance) { - if (AllGangs[otherGang].territory <= 0) return; - const territoryGain = calculateTerritoryGain(thisGang, otherGang); - AllGangs[thisGang].territory += territoryGain; - AllGangs[otherGang].territory -= territoryGain; - if (thisGang === gangName) { - this.clash(true); // Player won - AllGangs[otherGang].power *= (1 / 1.01); - } else if (otherGang === gangName) { - this.clash(false); // Player lost - } else { - AllGangs[otherGang].power *= (1 / 1.01); - } - } else { - if (AllGangs[thisGang].territory <= 0) return; - const territoryGain = calculateTerritoryGain(otherGang, thisGang); - AllGangs[thisGang].territory -= territoryGain; - AllGangs[otherGang].territory += territoryGain; - if (thisGang === gangName) { - this.clash(false); // Player lost - } else if (otherGang === gangName) { - this.clash(true); // Player won - AllGangs[thisGang].power *= (1 / 1.01); - } else { - AllGangs[thisGang].power *= (1 / 1.01); - } - } - } + // Notify of death + if (this.notifyMemberDeath) { + dialogBoxCreate( + `${member.name} was killed in a gang clash! You lost ${lostRespect} respect`, + ); } + } - processExperienceGains(numCycles = 1): void { - for (let i = 0; i < this.members.length; ++i) { - this.members[i].gainExperience(numCycles); - this.members[i].updateSkillLevels(); - } + ascendMember( + member: GangMember, + workerScript?: WorkerScript, + ): IAscensionResult { + try { + const res = member.ascend(); + this.respect = Math.max(1, this.respect - res.respect); + if (workerScript) { + workerScript.log("ascend", `Ascended Gang member ${member.name}`); + } + return res; + } catch (e) { + if (workerScript == null) { + exceptionAlert(e); + } + throw e; // Re-throw, will be caught in the Netscript Function } + } - clash(won = false): void { - // Determine if a gang member should die - let baseDeathChance = 0.01; - if (won) baseDeathChance /= 2; + // Cost of upgrade gets cheaper as gang increases in respect + power + getDiscount(): number { + const power = this.getPower(); + const respect = this.respect; - // If the clash was lost, the player loses a small percentage of power - else AllGangs[this.facName].power *= (1 / 1.008); + const respectLinearFac = 5e6; + const powerLinearFac = 1e6; + const discount = + Math.pow(respect, 0.01) + + respect / respectLinearFac + + Math.pow(power, 0.01) + + power / powerLinearFac - + 1; + return Math.max(1, discount); + } - // Deaths can only occur during X% of clashes - if (Math.random() < 0.65) return; + // Returns only valid tasks for this gang. Excludes 'Unassigned' + getAllTaskNames(): string[] { + return Object.keys(GangMemberTasks).filter((taskName: string) => { + const task = GangMemberTasks[taskName]; + if (task == null) return false; + if (task.name === "Unassigned") return false; + // yes you need both checks + return ( + this.isHackingGang === task.isHacking || + !this.isHackingGang === task.isCombat + ); + }); + } - for (let i = this.members.length - 1; i >= 0; --i) { - const member = this.members[i]; - - // Only members assigned to Territory Warfare can die - if (member.task !== "Territory Warfare") continue; - - // Chance to die is decreased based on defense - const modifiedDeathChance = baseDeathChance / Math.pow(member.def, 0.6); - if (Math.random() < modifiedDeathChance) { - this.killMember(member); - } - } + getUpgradeCost(upg: GangMemberUpgrade | null): number { + if (upg == null) { + return Infinity; } + return upg.cost / this.getDiscount(); + } - canRecruitMember(): boolean { - if (this.members.length >= GangConstants.MaximumGangMembers) return false; - return (this.respect >= this.getRespectNeededToRecruitMember()); - } - - getRespectNeededToRecruitMember(): number { - // First N gang members are free (can be recruited at 0 respect) - const numFreeMembers = 3; - if (this.members.length < numFreeMembers) return 0; - - const i = this.members.length - (numFreeMembers - 1); - return Math.pow(5, i); - } - - recruitMember(name: string): boolean { - name = String(name); - if (name === "" || !this.canRecruitMember()) return false; - - // Check for already-existing names - const sameNames = this.members.filter((m) => m.name === name); - if (sameNames.length >= 1) return false; - - const member = new GangMember(name); - this.members.push(member); - return true; - } - - // Money and Respect gains multiplied by this number (< 1) - getWantedPenalty(): number { - return (this.respect) / (this.respect + this.wanted); - } - - - //Calculates power GAIN, which is added onto the Gang's existing power - calculatePower(): number { - let memberTotal = 0; - for (let i = 0; i < this.members.length; ++i) { - if (!GangMemberTasks.hasOwnProperty(this.members[i].task) || - this.members[i].task !== "Territory Warfare") continue; - memberTotal += this.members[i].calculatePower(); - } - return (0.015 * Math.max(0.002, this.getTerritory()) * memberTotal); - } - - - killMember(member: GangMember): void { - // Player loses a percentage of total respect, plus whatever respect that member has earned - const totalRespect = this.respect; - const lostRespect = (0.05 * totalRespect) + member.earnedRespect; - this.respect = Math.max(0, totalRespect - lostRespect); - - for (let i = 0; i < this.members.length; ++i) { - if (member.name === this.members[i].name) { - this.members.splice(i, 1); - break; - } - } - - // Notify of death - if (this.notifyMemberDeath) { - dialogBoxCreate(`${member.name} was killed in a gang clash! You lost ${lostRespect} respect`); - } - } - - ascendMember(member: GangMember, workerScript?: WorkerScript): IAscensionResult { - try { - const res = member.ascend(); - this.respect = Math.max(1, this.respect - res.respect); - if (workerScript) { - workerScript.log('ascend', `Ascended Gang member ${member.name}`); - } - return res; - } catch(e) { - if (workerScript == null) { - exceptionAlert(e); - } - throw e; // Re-throw, will be caught in the Netscript Function - } - } - - // Cost of upgrade gets cheaper as gang increases in respect + power - getDiscount(): number { - const power = this.getPower(); - const respect = this.respect; - - const respectLinearFac = 5e6; - const powerLinearFac = 1e6; - const discount = Math.pow(respect, 0.01) + - respect / respectLinearFac + - Math.pow(power, 0.01) + - power / powerLinearFac - 1; - return Math.max(1, discount); - } - - // Returns only valid tasks for this gang. Excludes 'Unassigned' - getAllTaskNames(): string[] { - return Object.keys(GangMemberTasks).filter((taskName: string) => { - const task = GangMemberTasks[taskName]; - if (task == null) return false; - if (task.name === "Unassigned") return false; - // yes you need both checks - return this.isHackingGang === task.isHacking || - !this.isHackingGang === task.isCombat; - }); - } - - getUpgradeCost(upg: GangMemberUpgrade | null): number { - if (upg == null) { return Infinity; } - return upg.cost/this.getDiscount(); - } - - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("Gang", this); - } - - /** - * Initiatizes a Gang object from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Gang { - return Generic_fromJSON(Gang, value.data); - } + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Gang", this); + } + /** + * Initiatizes a Gang object from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Gang { + return Generic_fromJSON(Gang, value.data); + } } Reviver.constructors.Gang = Gang; diff --git a/src/Gang/GangMember.ts b/src/Gang/GangMember.ts index 04cb35005..ba757dd45 100644 --- a/src/Gang/GangMember.ts +++ b/src/Gang/GangMember.ts @@ -6,315 +6,373 @@ import { IAscensionResult } from "./IAscensionResult"; import { IPlayer } from "../PersonObjects/IPlayer"; import { AllGangs } from "./AllGangs"; import { IGang } from "./IGang"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; interface IMults { - hack: number; - str: number; - def: number; - dex: number; - agi: number; - cha: number; + hack: number; + str: number; + def: number; + dex: number; + agi: number; + cha: number; } export class GangMember { - name: string; - task = "Unassigned"; + name: string; + task = "Unassigned"; - earnedRespect = 0; + earnedRespect = 0; - hack = 1; - str = 1; - def = 1; - dex = 1; - agi = 1; - cha = 1; + hack = 1; + str = 1; + def = 1; + dex = 1; + agi = 1; + cha = 1; - hack_exp = 0; - str_exp = 0; - def_exp = 0; - dex_exp = 0; - agi_exp = 0; - cha_exp = 0; + hack_exp = 0; + str_exp = 0; + def_exp = 0; + dex_exp = 0; + agi_exp = 0; + cha_exp = 0; - hack_mult = 1; - str_mult = 1; - def_mult = 1; - dex_mult = 1; - agi_mult = 1; - cha_mult = 1; + hack_mult = 1; + str_mult = 1; + def_mult = 1; + dex_mult = 1; + agi_mult = 1; + cha_mult = 1; - hack_asc_points = 0; - str_asc_points = 0; - def_asc_points = 0; - dex_asc_points = 0; - agi_asc_points = 0; - cha_asc_points = 0; + hack_asc_points = 0; + str_asc_points = 0; + def_asc_points = 0; + dex_asc_points = 0; + agi_asc_points = 0; + cha_asc_points = 0; - upgrades: string[] = []; // Names of upgrades - augmentations: string[] = []; // Names of augmentations only + upgrades: string[] = []; // Names of upgrades + augmentations: string[] = []; // Names of augmentations only - constructor(name = "") { - this.name = name; + constructor(name = "") { + this.name = name; + } + + calculateSkill(exp: number, mult = 1): number { + return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1); + } + + calculateAscensionMult(points: number): number { + return Math.max(Math.pow(points / 4000, 0.7), 1); + } + + updateSkillLevels(): void { + this.hack = this.calculateSkill( + this.hack_exp, + this.hack_mult * this.calculateAscensionMult(this.hack_asc_points), + ); + this.str = this.calculateSkill( + this.str_exp, + this.str_mult * this.calculateAscensionMult(this.str_asc_points), + ); + this.def = this.calculateSkill( + this.def_exp, + this.def_mult * this.calculateAscensionMult(this.def_asc_points), + ); + this.dex = this.calculateSkill( + this.dex_exp, + this.dex_mult * this.calculateAscensionMult(this.dex_asc_points), + ); + this.agi = this.calculateSkill( + this.agi_exp, + this.agi_mult * this.calculateAscensionMult(this.agi_asc_points), + ); + this.cha = this.calculateSkill( + this.cha_exp, + this.cha_mult * this.calculateAscensionMult(this.cha_asc_points), + ); + } + + calculatePower(): number { + return ( + (this.hack + this.str + this.def + this.dex + this.agi + this.cha) / 95 + ); + } + + assignToTask(taskName: string): boolean { + if (!GangMemberTasks.hasOwnProperty(taskName)) { + this.task = "Unassigned"; + return false; + } + this.task = taskName; + return true; + } + + unassignFromTask(): void { + this.task = "Unassigned"; + } + + getTask(): GangMemberTask { + // TODO(hydroflame): transfer that to a save file migration function + // Backwards compatibility + if ((this.task as any) instanceof GangMemberTask) { + this.task = (this.task as any).name; } - calculateSkill(exp: number, mult = 1): number { - return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1); + if (GangMemberTasks.hasOwnProperty(this.task)) { + return GangMemberTasks[this.task]; + } + return GangMemberTasks["Unassigned"]; + } + + calculateRespectGain(gang: IGang): number { + const task = this.getTask(); + if (task.baseRespect === 0) return 0; + let statWeight = + (task.hackWeight / 100) * this.hack + + (task.strWeight / 100) * this.str + + (task.defWeight / 100) * this.def + + (task.dexWeight / 100) * this.dex + + (task.agiWeight / 100) * this.agi + + (task.chaWeight / 100) * this.cha; + statWeight -= 4 * task.difficulty; + if (statWeight <= 0) return 0; + const territoryMult = Math.max( + 0.005, + Math.pow(AllGangs[gang.facName].territory * 100, task.territory.respect) / + 100, + ); + if (isNaN(territoryMult) || territoryMult <= 0) return 0; + const respectMult = gang.getWantedPenalty(); + return 11 * task.baseRespect * statWeight * territoryMult * respectMult; + } + + calculateWantedLevelGain(gang: IGang): number { + const task = this.getTask(); + if (task.baseWanted === 0) return 0; + let statWeight = + (task.hackWeight / 100) * this.hack + + (task.strWeight / 100) * this.str + + (task.defWeight / 100) * this.def + + (task.dexWeight / 100) * this.dex + + (task.agiWeight / 100) * this.agi + + (task.chaWeight / 100) * this.cha; + statWeight -= 3.5 * task.difficulty; + if (statWeight <= 0) return 0; + const territoryMult = Math.max( + 0.005, + Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / + 100, + ); + if (isNaN(territoryMult) || territoryMult <= 0) return 0; + if (task.baseWanted < 0) { + return 0.4 * task.baseWanted * statWeight * territoryMult; + } + const calc = + (7 * task.baseWanted) / Math.pow(3 * statWeight * territoryMult, 0.8); + + // Put an arbitrary cap on this to prevent wanted level from rising too fast if the + // denominator is very small. Might want to rethink formula later + return Math.min(100, calc); + } + + calculateMoneyGain(gang: IGang): number { + const task = this.getTask(); + if (task.baseMoney === 0) return 0; + let statWeight = + (task.hackWeight / 100) * this.hack + + (task.strWeight / 100) * this.str + + (task.defWeight / 100) * this.def + + (task.dexWeight / 100) * this.dex + + (task.agiWeight / 100) * this.agi + + (task.chaWeight / 100) * this.cha; + + statWeight -= 3.2 * task.difficulty; + if (statWeight <= 0) return 0; + const territoryMult = Math.max( + 0.005, + Math.pow(AllGangs[gang.facName].territory * 100, task.territory.money) / + 100, + ); + if (isNaN(territoryMult) || territoryMult <= 0) return 0; + const respectMult = gang.getWantedPenalty(); + return 5 * task.baseMoney * statWeight * territoryMult * respectMult; + } + + expMult(): IMults { + return { + hack: (this.hack_mult - 1) / 4 + 1, + str: (this.str_mult - 1) / 4 + 1, + def: (this.def_mult - 1) / 4 + 1, + dex: (this.dex_mult - 1) / 4 + 1, + agi: (this.agi_mult - 1) / 4 + 1, + cha: (this.cha_mult - 1) / 4 + 1, + }; + } + + gainExperience(numCycles = 1): void { + const task = this.getTask(); + if (task === GangMemberTasks["Unassigned"]) return; + const difficultyMult = Math.pow(task.difficulty, 0.9); + const difficultyPerCycles = difficultyMult * numCycles; + const weightDivisor = 1500; + const expMult = this.expMult(); + this.hack_exp += + (task.hackWeight / weightDivisor) * difficultyPerCycles * expMult.hack; + this.str_exp += + (task.strWeight / weightDivisor) * difficultyPerCycles * expMult.str; + this.def_exp += + (task.defWeight / weightDivisor) * difficultyPerCycles * expMult.def; + this.dex_exp += + (task.dexWeight / weightDivisor) * difficultyPerCycles * expMult.dex; + this.agi_exp += + (task.agiWeight / weightDivisor) * difficultyPerCycles * expMult.agi; + this.cha_exp += + (task.chaWeight / weightDivisor) * difficultyPerCycles * expMult.cha; + } + + recordEarnedRespect(numCycles = 1, gang: IGang): void { + this.earnedRespect += this.calculateRespectGain(gang) * numCycles; + } + + getGainedAscensionPoints(): IMults { + return { + hack: Math.max(this.hack_exp - 1000, 0), + str: Math.max(this.str_exp - 1000, 0), + def: Math.max(this.def_exp - 1000, 0), + dex: Math.max(this.dex_exp - 1000, 0), + agi: Math.max(this.agi_exp - 1000, 0), + cha: Math.max(this.cha_exp - 1000, 0), + }; + } + + canAscend(): boolean { + const points = this.getGainedAscensionPoints(); + return ( + points.hack > 0 || + points.str > 0 || + points.def > 0 || + points.dex > 0 || + points.agi > 0 || + points.cha > 0 + ); + } + + getAscensionResults(): IMults { + const points = this.getGainedAscensionPoints(); + return { + hack: + this.calculateAscensionMult(this.hack_asc_points + points.hack) / + this.calculateAscensionMult(this.hack_asc_points), + str: + this.calculateAscensionMult(this.str_asc_points + points.str) / + this.calculateAscensionMult(this.str_asc_points), + def: + this.calculateAscensionMult(this.def_asc_points + points.def) / + this.calculateAscensionMult(this.def_asc_points), + dex: + this.calculateAscensionMult(this.dex_asc_points + points.dex) / + this.calculateAscensionMult(this.dex_asc_points), + agi: + this.calculateAscensionMult(this.agi_asc_points + points.agi) / + this.calculateAscensionMult(this.agi_asc_points), + cha: + this.calculateAscensionMult(this.cha_asc_points + points.cha) / + this.calculateAscensionMult(this.cha_asc_points), + }; + } + + ascend(): IAscensionResult { + const res = this.getAscensionResults(); + const points = this.getGainedAscensionPoints(); + this.hack_asc_points += points.hack; + this.str_asc_points += points.str; + this.def_asc_points += points.def; + this.dex_asc_points += points.dex; + this.agi_asc_points += points.agi; + this.cha_asc_points += points.cha; + + // Remove upgrades. Then re-calculate multipliers and stats + this.upgrades.length = 0; + this.hack_mult = 1; + this.str_mult = 1; + this.def_mult = 1; + this.dex_mult = 1; + this.agi_mult = 1; + this.cha_mult = 1; + for (let i = 0; i < this.augmentations.length; ++i) { + const aug = GangMemberUpgrades[this.augmentations[i]]; + this.applyUpgrade(aug); } - calculateAscensionMult(points: number): number { - return Math.max(Math.pow(points/4000, 0.7), 1); + // Clear exp and recalculate stats + this.hack_exp = 0; + this.str_exp = 0; + this.def_exp = 0; + this.dex_exp = 0; + this.agi_exp = 0; + this.cha_exp = 0; + this.updateSkillLevels(); + + const respectToDeduct = this.earnedRespect; + this.earnedRespect = 0; + return { + respect: respectToDeduct, + hack: res.hack, + str: res.str, + def: res.def, + dex: res.dex, + agi: res.agi, + cha: res.cha, + }; + } + + applyUpgrade(upg: GangMemberUpgrade): void { + if (upg.mults.str != null) this.str_mult *= upg.mults.str; + if (upg.mults.def != null) this.def_mult *= upg.mults.def; + if (upg.mults.dex != null) this.dex_mult *= upg.mults.dex; + if (upg.mults.agi != null) this.agi_mult *= upg.mults.agi; + if (upg.mults.cha != null) this.cha_mult *= upg.mults.cha; + if (upg.mults.hack != null) this.hack_mult *= upg.mults.hack; + } + + buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: IGang): boolean { + // Prevent purchasing of already-owned upgrades + if ( + this.augmentations.includes(upg.name) || + this.upgrades.includes(upg.name) + ) + return false; + + if (player.money.lt(gang.getUpgradeCost(upg))) return false; + player.loseMoney(gang.getUpgradeCost(upg)); + if (upg.type === "g") { + this.augmentations.push(upg.name); + } else { + this.upgrades.push(upg.name); } + this.applyUpgrade(upg); + return true; + } - updateSkillLevels(): void { - this.hack = this.calculateSkill(this.hack_exp, this.hack_mult * this.calculateAscensionMult(this.hack_asc_points)); - this.str = this.calculateSkill(this.str_exp, this.str_mult * this.calculateAscensionMult(this.str_asc_points)); - this.def = this.calculateSkill(this.def_exp, this.def_mult * this.calculateAscensionMult(this.def_asc_points)); - this.dex = this.calculateSkill(this.dex_exp, this.dex_mult * this.calculateAscensionMult(this.dex_asc_points)); - this.agi = this.calculateSkill(this.agi_exp, this.agi_mult * this.calculateAscensionMult(this.agi_asc_points)); - this.cha = this.calculateSkill(this.cha_exp, this.cha_mult * this.calculateAscensionMult(this.cha_asc_points)); - } + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("GangMember", this); + } - calculatePower(): number { - return (this.hack + - this.str + - this.def + - this.dex + - this.agi + - this.cha) / 95; - } - - assignToTask(taskName: string): boolean { - if (!GangMemberTasks.hasOwnProperty(taskName)) { - this.task = "Unassigned"; - return false; - } - this.task = taskName; - return true; - } - - unassignFromTask(): void { - this.task = "Unassigned"; - } - - getTask(): GangMemberTask { - // TODO(hydroflame): transfer that to a save file migration function - // Backwards compatibility - if ((this.task as any) instanceof GangMemberTask) { - this.task = (this.task as any).name; - } - - if (GangMemberTasks.hasOwnProperty(this.task)) { - return GangMemberTasks[this.task]; - } - return GangMemberTasks["Unassigned"]; - } - - calculateRespectGain(gang: IGang): number { - const task = this.getTask(); - if (task.baseRespect === 0) return 0; - let statWeight = (task.hackWeight/100) * this.hack + - (task.strWeight/100) * this.str + - (task.defWeight/100) * this.def + - (task.dexWeight/100) * this.dex + - (task.agiWeight/100) * this.agi + - (task.chaWeight/100) * this.cha; - statWeight -= (4 * task.difficulty); - if (statWeight <= 0) return 0; - const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.respect) / 100); - if (isNaN(territoryMult) || territoryMult <= 0) return 0; - const respectMult = gang.getWantedPenalty(); - return 11 * task.baseRespect * statWeight * territoryMult * respectMult; - } - - calculateWantedLevelGain(gang: IGang): number { - const task = this.getTask(); - if (task.baseWanted === 0) return 0; - let statWeight = (task.hackWeight / 100) * this.hack + - (task.strWeight / 100) * this.str + - (task.defWeight / 100) * this.def + - (task.dexWeight / 100) * this.dex + - (task.agiWeight / 100) * this.agi + - (task.chaWeight / 100) * this.cha; - statWeight -= (3.5 * task.difficulty); - if (statWeight <= 0) return 0; - const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / 100); - if (isNaN(territoryMult) || territoryMult <= 0) return 0; - if (task.baseWanted < 0) { - return 0.4 * task.baseWanted * statWeight * territoryMult; - } - const calc = 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8)); - - // Put an arbitrary cap on this to prevent wanted level from rising too fast if the - // denominator is very small. Might want to rethink formula later - return Math.min(100, calc); - } - - calculateMoneyGain(gang: IGang): number { - const task = this.getTask(); - if (task.baseMoney === 0) return 0; - let statWeight = (task.hackWeight/100) * this.hack + - (task.strWeight/100) * this.str + - (task.defWeight/100) * this.def + - (task.dexWeight/100) * this.dex + - (task.agiWeight/100) * this.agi + - (task.chaWeight/100) * this.cha; - - statWeight -= (3.2 * task.difficulty); - if (statWeight <= 0) return 0; - const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.money) / 100); - if (isNaN(territoryMult) || territoryMult <= 0) return 0; - const respectMult = gang.getWantedPenalty(); - return 5 * task.baseMoney * statWeight * territoryMult * respectMult; - } - - expMult(): IMults { - return { - hack: (this.hack_mult-1)/4+1, - str: (this.str_mult-1)/4+1, - def: (this.def_mult-1)/4+1, - dex: (this.dex_mult-1)/4+1, - agi: (this.agi_mult-1)/4+1, - cha: (this.cha_mult-1)/4+1, - }; - } - - gainExperience(numCycles = 1): void { - const task = this.getTask(); - if (task === GangMemberTasks["Unassigned"]) return; - const difficultyMult = Math.pow(task.difficulty, 0.9); - const difficultyPerCycles = difficultyMult * numCycles; - const weightDivisor = 1500; - const expMult = this.expMult(); - this.hack_exp += (task.hackWeight / weightDivisor) * difficultyPerCycles * expMult.hack; - this.str_exp += (task.strWeight / weightDivisor) * difficultyPerCycles * expMult.str; - this.def_exp += (task.defWeight / weightDivisor) * difficultyPerCycles * expMult.def; - this.dex_exp += (task.dexWeight / weightDivisor) * difficultyPerCycles * expMult.dex; - this.agi_exp += (task.agiWeight / weightDivisor) * difficultyPerCycles * expMult.agi; - this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles * expMult.cha; - } - - recordEarnedRespect(numCycles = 1, gang: IGang): void { - this.earnedRespect += (this.calculateRespectGain(gang) * numCycles); - } - - getGainedAscensionPoints(): IMults { - return { - hack: Math.max(this.hack_exp - 1000, 0), - str: Math.max(this.str_exp - 1000, 0), - def: Math.max(this.def_exp - 1000, 0), - dex: Math.max(this.dex_exp - 1000, 0), - agi: Math.max(this.agi_exp - 1000, 0), - cha: Math.max(this.cha_exp - 1000, 0), - } - } - - canAscend(): boolean { - const points = this.getGainedAscensionPoints(); - return points.hack > 0 || - points.str > 0 || - points.def > 0 || - points.dex > 0 || - points.agi > 0 || - points.cha > 0; - } - - getAscensionResults(): IMults { - const points = this.getGainedAscensionPoints(); - return { - hack: this.calculateAscensionMult(this.hack_asc_points+points.hack)/this.calculateAscensionMult(this.hack_asc_points), - str: this.calculateAscensionMult(this.str_asc_points+points.str)/this.calculateAscensionMult(this.str_asc_points), - def: this.calculateAscensionMult(this.def_asc_points+points.def)/this.calculateAscensionMult(this.def_asc_points), - dex: this.calculateAscensionMult(this.dex_asc_points+points.dex)/this.calculateAscensionMult(this.dex_asc_points), - agi: this.calculateAscensionMult(this.agi_asc_points+points.agi)/this.calculateAscensionMult(this.agi_asc_points), - cha: this.calculateAscensionMult(this.cha_asc_points+points.cha)/this.calculateAscensionMult(this.cha_asc_points), - } - } - - ascend(): IAscensionResult { - const res = this.getAscensionResults(); - const points = this.getGainedAscensionPoints(); - this.hack_asc_points += points.hack; - this.str_asc_points += points.str; - this.def_asc_points += points.def; - this.dex_asc_points += points.dex; - this.agi_asc_points += points.agi; - this.cha_asc_points += points.cha; - - // Remove upgrades. Then re-calculate multipliers and stats - this.upgrades.length = 0; - this.hack_mult = 1; - this.str_mult = 1; - this.def_mult = 1; - this.dex_mult = 1; - this.agi_mult = 1; - this.cha_mult = 1; - for (let i = 0; i < this.augmentations.length; ++i) { - const aug = GangMemberUpgrades[this.augmentations[i]]; - this.applyUpgrade(aug); - } - - // Clear exp and recalculate stats - this.hack_exp = 0; - this.str_exp = 0; - this.def_exp = 0; - this.dex_exp = 0; - this.agi_exp = 0; - this.cha_exp = 0; - this.updateSkillLevels(); - - const respectToDeduct = this.earnedRespect; - this.earnedRespect = 0; - return { - respect: respectToDeduct, - hack: res.hack, - str: res.str, - def: res.def, - dex: res.dex, - agi: res.agi, - cha: res.cha, - }; - } - - applyUpgrade(upg: GangMemberUpgrade): void { - if (upg.mults.str != null) this.str_mult *= upg.mults.str; - if (upg.mults.def != null) this.def_mult *= upg.mults.def; - if (upg.mults.dex != null) this.dex_mult *= upg.mults.dex; - if (upg.mults.agi != null) this.agi_mult *= upg.mults.agi; - if (upg.mults.cha != null) this.cha_mult *= upg.mults.cha; - if (upg.mults.hack != null) this.hack_mult *= upg.mults.hack; - } - - buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: IGang): boolean { - // Prevent purchasing of already-owned upgrades - if (this.augmentations.includes(upg.name) || - this.upgrades.includes(upg.name)) return false; - - if (player.money.lt(gang.getUpgradeCost(upg))) return false; - player.loseMoney(gang.getUpgradeCost(upg)); - if (upg.type === "g") { - this.augmentations.push(upg.name); - } else { - this.upgrades.push(upg.name); - } - this.applyUpgrade(upg); - return true; - } - - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("GangMember", this); - } - - /** - * Initiatizes a GangMember object from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): GangMember { - return Generic_fromJSON(GangMember, value.data); - } + /** + * Initiatizes a GangMember object from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): GangMember { + return Generic_fromJSON(GangMember, value.data); + } } -Reviver.constructors.GangMember = GangMember; \ No newline at end of file +Reviver.constructors.GangMember = GangMember; diff --git a/src/Gang/GangMemberTask.ts b/src/Gang/GangMemberTask.ts index 053fe0777..186f16096 100644 --- a/src/Gang/GangMemberTask.ts +++ b/src/Gang/GangMemberTask.ts @@ -1,62 +1,78 @@ import { ITaskParams, ITerritory } from "./ITaskParams"; export class GangMemberTask { - name: string; - desc: string; + name: string; + desc: string; - isHacking: boolean; - isCombat: boolean; + isHacking: boolean; + isCombat: boolean; - baseRespect: number; - baseWanted: number; - baseMoney: number; + baseRespect: number; + baseWanted: number; + baseMoney: number; - hackWeight: number; - strWeight: number; - defWeight: number; - dexWeight: number; - agiWeight: number; - chaWeight: number; + hackWeight: number; + strWeight: number; + defWeight: number; + dexWeight: number; + agiWeight: number; + chaWeight: number; - difficulty: number; + difficulty: number; - territory: ITerritory; + territory: ITerritory; - // Defines tasks that Gang Members can work on - constructor(name: string, desc: string, isHacking: boolean, isCombat: boolean, params: ITaskParams) { - this.name = name; - this.desc = desc; + // Defines tasks that Gang Members can work on + constructor( + name: string, + desc: string, + isHacking: boolean, + isCombat: boolean, + params: ITaskParams, + ) { + this.name = name; + this.desc = desc; - // Flags that describe whether this Task is applicable for Hacking/Combat gangs - this.isHacking = isHacking; - this.isCombat = isCombat; + // Flags that describe whether this Task is applicable for Hacking/Combat gangs + this.isHacking = isHacking; + this.isCombat = isCombat; - // Base gain rates for respect/wanted/money - this.baseRespect = params.baseRespect ? params.baseRespect : 0; - this.baseWanted = params.baseWanted ? params.baseWanted : 0; - this.baseMoney = params.baseMoney ? params.baseMoney : 0; + // Base gain rates for respect/wanted/money + this.baseRespect = params.baseRespect ? params.baseRespect : 0; + this.baseWanted = params.baseWanted ? params.baseWanted : 0; + this.baseMoney = params.baseMoney ? params.baseMoney : 0; - // Weighting for the effect that each stat has on the tasks effectiveness. - // Weights must add up to 100 - this.hackWeight = params.hackWeight ? params.hackWeight : 0; - this.strWeight = params.strWeight ? params.strWeight : 0; - this.defWeight = params.defWeight ? params.defWeight : 0; - this.dexWeight = params.dexWeight ? params.dexWeight : 0; - this.agiWeight = params.agiWeight ? params.agiWeight : 0; - this.chaWeight = params.chaWeight ? params.chaWeight : 0; + // Weighting for the effect that each stat has on the tasks effectiveness. + // Weights must add up to 100 + this.hackWeight = params.hackWeight ? params.hackWeight : 0; + this.strWeight = params.strWeight ? params.strWeight : 0; + this.defWeight = params.defWeight ? params.defWeight : 0; + this.dexWeight = params.dexWeight ? params.dexWeight : 0; + this.agiWeight = params.agiWeight ? params.agiWeight : 0; + this.chaWeight = params.chaWeight ? params.chaWeight : 0; - if (Math.round(this.hackWeight + this.strWeight + this.defWeight + this.dexWeight + this.agiWeight + this.chaWeight) != 100) { - console.error(`GangMemberTask ${this.name} weights do not add up to 100`); - } - - // 1 - 100 - this.difficulty = params.difficulty ? params.difficulty : 1; - - // Territory Factors. Exponential factors that dictate how territory affects gains - // Formula: Territory Mutiplier = (Territory * 100) ^ factor / 100 - // So factor should be > 1 if something should scale exponentially with territory - // and should be < 1 if it should have diminshing returns - this.territory = params.territory ? params.territory : {money: 1, respect: 1, wanted: 1}; + if ( + Math.round( + this.hackWeight + + this.strWeight + + this.defWeight + + this.dexWeight + + this.agiWeight + + this.chaWeight, + ) != 100 + ) { + console.error(`GangMemberTask ${this.name} weights do not add up to 100`); } + // 1 - 100 + this.difficulty = params.difficulty ? params.difficulty : 1; + + // Territory Factors. Exponential factors that dictate how territory affects gains + // Formula: Territory Mutiplier = (Territory * 100) ^ factor / 100 + // So factor should be > 1 if something should scale exponentially with territory + // and should be < 1 if it should have diminshing returns + this.territory = params.territory + ? params.territory + : { money: 1, respect: 1, wanted: 1 }; + } } diff --git a/src/Gang/GangMemberTasks.ts b/src/Gang/GangMemberTasks.ts index ccc198d98..ff9077777 100644 --- a/src/Gang/GangMemberTasks.ts +++ b/src/Gang/GangMemberTasks.ts @@ -2,11 +2,17 @@ import { gangMemberTasksMetadata } from "./data/tasks"; import { GangMemberTask } from "./GangMemberTask"; export const GangMemberTasks: { - [key: string]: GangMemberTask; + [key: string]: GangMemberTask; } = {}; -(function() { - gangMemberTasksMetadata.forEach((e) => { - GangMemberTasks[e.name] = new GangMemberTask(e.name, e.desc, e.isHacking, e.isCombat, e.params); - }); +(function () { + gangMemberTasksMetadata.forEach((e) => { + GangMemberTasks[e.name] = new GangMemberTask( + e.name, + e.desc, + e.isHacking, + e.isCombat, + e.params, + ); + }); })(); diff --git a/src/Gang/GangMemberUpgrade.ts b/src/Gang/GangMemberUpgrade.ts index 3475a71ee..05319503c 100644 --- a/src/Gang/GangMemberUpgrade.ts +++ b/src/Gang/GangMemberUpgrade.ts @@ -2,65 +2,130 @@ import { IMults, UpgradeType } from "./data/upgrades"; import { numeralWrapper } from "../ui/numeralFormat"; export class GangMemberUpgrade { - name: string; - cost: number; - type: UpgradeType; - desc: string; - mults: IMults; + name: string; + cost: number; + type: UpgradeType; + desc: string; + mults: IMults; - constructor(name = "", cost = 0, type: UpgradeType = UpgradeType.Weapon, mults: IMults = {}) { - this.name = name; - this.cost = cost; - this.type = type; - this.mults = mults; + constructor( + name = "", + cost = 0, + type: UpgradeType = UpgradeType.Weapon, + mults: IMults = {}, + ) { + this.name = name; + this.cost = cost; + this.type = type; + this.mults = mults; - this.desc = this.createDescription(); + this.desc = this.createDescription(); + } + + createDescription(): string { + const lines = ["Effects:"]; + if (this.mults.str != null) { + lines.push( + `+${numeralWrapper.formatPercentage( + this.mults.str - 1, + 0, + )} strength skill`, + ); + lines.push( + `+${numeralWrapper.formatPercentage( + (this.mults.str - 1) / 4, + 2, + )} strength exp`, + ); } - - createDescription(): string { - const lines = ["Effects:"]; - if (this.mults.str != null) { - lines.push(`+${numeralWrapper.formatPercentage(this.mults.str-1, 0)} strength skill`); - lines.push(`+${numeralWrapper.formatPercentage((this.mults.str-1)/4, 2)} strength exp`); - } - if (this.mults.def != null) { - lines.push(`+${numeralWrapper.formatPercentage(this.mults.def-1, 0)} defense skill`); - lines.push(`+${numeralWrapper.formatPercentage((this.mults.def-1)/4, 2)} defense exp`); - } - if (this.mults.dex != null) { - lines.push(`+${numeralWrapper.formatPercentage(this.mults.dex-1, 0)} dexterity skill`); - lines.push(`+${numeralWrapper.formatPercentage((this.mults.dex-1)/4, 2)} dexterity exp`); - } - if (this.mults.agi != null) { - lines.push(`+${numeralWrapper.formatPercentage(this.mults.agi-1, 0)} agility skill`); - lines.push(`+${numeralWrapper.formatPercentage((this.mults.agi-1)/4, 2)} agility exp`); - } - if (this.mults.cha != null) { - lines.push(`+${numeralWrapper.formatPercentage(this.mults.cha-1, 0)} charisma skill`); - lines.push(`+${numeralWrapper.formatPercentage((this.mults.cha-1)/4, 2)} charisma exp`); - } - if (this.mults.hack != null) { - lines.push(`+${numeralWrapper.formatPercentage(this.mults.hack-1, 0)} hacking skill`); - lines.push(`+${numeralWrapper.formatPercentage((this.mults.hack-1)/4, 2)} hacking exp`); - } - return lines.join("
    "); + if (this.mults.def != null) { + lines.push( + `+${numeralWrapper.formatPercentage( + this.mults.def - 1, + 0, + )} defense skill`, + ); + lines.push( + `+${numeralWrapper.formatPercentage( + (this.mults.def - 1) / 4, + 2, + )} defense exp`, + ); } - - // User friendly version of type. - getType(): string { - switch (this.type) { - case UpgradeType.Weapon: - return "Weapon"; - case UpgradeType.Armor: - return "Armor"; - case UpgradeType.Vehicle: - return "Vehicle"; - case UpgradeType.Rootkit: - return "Rootkit"; - case UpgradeType.Augmentation: - return "Augmentation"; - default: - return ""; - } + if (this.mults.dex != null) { + lines.push( + `+${numeralWrapper.formatPercentage( + this.mults.dex - 1, + 0, + )} dexterity skill`, + ); + lines.push( + `+${numeralWrapper.formatPercentage( + (this.mults.dex - 1) / 4, + 2, + )} dexterity exp`, + ); } -} \ No newline at end of file + if (this.mults.agi != null) { + lines.push( + `+${numeralWrapper.formatPercentage( + this.mults.agi - 1, + 0, + )} agility skill`, + ); + lines.push( + `+${numeralWrapper.formatPercentage( + (this.mults.agi - 1) / 4, + 2, + )} agility exp`, + ); + } + if (this.mults.cha != null) { + lines.push( + `+${numeralWrapper.formatPercentage( + this.mults.cha - 1, + 0, + )} charisma skill`, + ); + lines.push( + `+${numeralWrapper.formatPercentage( + (this.mults.cha - 1) / 4, + 2, + )} charisma exp`, + ); + } + if (this.mults.hack != null) { + lines.push( + `+${numeralWrapper.formatPercentage( + this.mults.hack - 1, + 0, + )} hacking skill`, + ); + lines.push( + `+${numeralWrapper.formatPercentage( + (this.mults.hack - 1) / 4, + 2, + )} hacking exp`, + ); + } + return lines.join("
    "); + } + + // User friendly version of type. + getType(): string { + switch (this.type) { + case UpgradeType.Weapon: + return "Weapon"; + case UpgradeType.Armor: + return "Armor"; + case UpgradeType.Vehicle: + return "Vehicle"; + case UpgradeType.Rootkit: + return "Rootkit"; + case UpgradeType.Augmentation: + return "Augmentation"; + default: + return ""; + } + } +} diff --git a/src/Gang/GangMemberUpgrades.ts b/src/Gang/GangMemberUpgrades.ts index 0b9637d5f..cb4d95fe1 100644 --- a/src/Gang/GangMemberUpgrades.ts +++ b/src/Gang/GangMemberUpgrades.ts @@ -2,11 +2,16 @@ import { gangMemberUpgradesMetadata } from "./data/upgrades"; import { GangMemberUpgrade } from "./GangMemberUpgrade"; export const GangMemberUpgrades: { - [key: string]: GangMemberUpgrade; + [key: string]: GangMemberUpgrade; } = {}; -(function() { - gangMemberUpgradesMetadata.forEach((e) => { - GangMemberUpgrades[e.name] = new GangMemberUpgrade(e.name, e.cost, e.upgType, e.mults); - }); +(function () { + gangMemberUpgradesMetadata.forEach((e) => { + GangMemberUpgrades[e.name] = new GangMemberUpgrade( + e.name, + e.cost, + e.upgType, + e.mults, + ); + }); })(); diff --git a/src/Gang/Helpers.tsx b/src/Gang/Helpers.tsx index ddcac421c..6a8a53ff5 100644 --- a/src/Gang/Helpers.tsx +++ b/src/Gang/Helpers.tsx @@ -8,25 +8,28 @@ import { Page, routing } from ".././ui/navigationTracking"; let gangContainer: HTMLElement; -(function() { - function set(): void { - const c = document.getElementById("gang-container"); - if(c === null) throw new Error("Could not find element 'gang-container'"); - gangContainer = c; - document.removeEventListener("DOMContentLoaded", set); - } +(function () { + function set(): void { + const c = document.getElementById("gang-container"); + if (c === null) throw new Error("Could not find element 'gang-container'"); + gangContainer = c; + document.removeEventListener("DOMContentLoaded", set); + } - document.addEventListener("DOMContentLoaded", set); + document.addEventListener("DOMContentLoaded", set); })(); +export function displayGangContent( + engine: IEngine, + gang: Gang, + player: IPlayer, +): void { + if (!routing.isOn(Page.Gang)) { + return; + } -export function displayGangContent(engine: IEngine, gang: Gang, player: IPlayer): void { - if (!routing.isOn(Page.Gang)) { - return; - } - - ReactDOM.render(, gangContainer); -} \ No newline at end of file + ReactDOM.render( + , + gangContainer, + ); +} diff --git a/src/Gang/IAscensionResult.ts b/src/Gang/IAscensionResult.ts index 224573cd1..c65ee0082 100644 --- a/src/Gang/IAscensionResult.ts +++ b/src/Gang/IAscensionResult.ts @@ -1,9 +1,9 @@ export interface IAscensionResult { - respect: number; - hack: number; - str: number; - def: number; - dex: number; - agi: number; - cha: number; -} \ No newline at end of file + respect: number; + hack: number; + str: number; + def: number; + dex: number; + agi: number; + cha: number; +} diff --git a/src/Gang/IGang.ts b/src/Gang/IGang.ts index f1a40f76d..b99f52f1d 100644 --- a/src/Gang/IGang.ts +++ b/src/Gang/IGang.ts @@ -4,41 +4,41 @@ import { WorkerScript } from "../Netscript/WorkerScript"; import { IPlayer } from "../PersonObjects/IPlayer"; export interface IGang { - facName: string; - members: GangMember[]; - wanted: number; - respect: number; + facName: string; + members: GangMember[]; + wanted: number; + respect: number; - isHackingGang: boolean; + isHackingGang: boolean; - respectGainRate: number; - wantedGainRate: number; - moneyGainRate: number; + respectGainRate: number; + wantedGainRate: number; + moneyGainRate: number; - storedCycles: number; + storedCycles: number; - storedTerritoryAndPowerCycles: number; + storedTerritoryAndPowerCycles: number; - territoryClashChance: number; - territoryWarfareEngaged: boolean; + territoryClashChance: number; + territoryWarfareEngaged: boolean; - notifyMemberDeath: boolean; + notifyMemberDeath: boolean; - getPower(): number; - getTerritory(): number; - process(numCycles: number, player: IPlayer): void; - processGains(numCycles: number, player: IPlayer): void; - processTerritoryAndPowerGains(numCycles: number): void; - processExperienceGains(numCycles: number): void; - clash(won: boolean): void; - canRecruitMember(): boolean; - getRespectNeededToRecruitMember(): number; - recruitMember(name: string): boolean; - getWantedPenalty(): number; - calculatePower(): number; - killMember(member: GangMember): void; - ascendMember(member: GangMember, workerScript: WorkerScript): void; - getDiscount(): number; - getAllTaskNames(): string[]; - getUpgradeCost(upg: GangMemberUpgrade): number; -} \ No newline at end of file + getPower(): number; + getTerritory(): number; + process(numCycles: number, player: IPlayer): void; + processGains(numCycles: number, player: IPlayer): void; + processTerritoryAndPowerGains(numCycles: number): void; + processExperienceGains(numCycles: number): void; + clash(won: boolean): void; + canRecruitMember(): boolean; + getRespectNeededToRecruitMember(): number; + recruitMember(name: string): boolean; + getWantedPenalty(): number; + calculatePower(): number; + killMember(member: GangMember): void; + ascendMember(member: GangMember, workerScript: WorkerScript): void; + getDiscount(): number; + getAllTaskNames(): string[]; + getUpgradeCost(upg: GangMemberUpgrade): number; +} diff --git a/src/Gang/ITaskParams.ts b/src/Gang/ITaskParams.ts index 44178e42f..eb8885ecc 100644 --- a/src/Gang/ITaskParams.ts +++ b/src/Gang/ITaskParams.ts @@ -1,20 +1,19 @@ - export interface ITerritory { - money: number; - respect: number; - wanted: number; + money: number; + respect: number; + wanted: number; } export interface ITaskParams { - baseRespect?: number; - baseWanted?: number; - baseMoney?: number; - hackWeight?: number; - strWeight?: number; - defWeight?: number; - dexWeight?: number; - agiWeight?: number; - chaWeight?: number; - difficulty?: number; - territory?: ITerritory; -} \ No newline at end of file + baseRespect?: number; + baseWanted?: number; + baseMoney?: number; + hackWeight?: number; + strWeight?: number; + defWeight?: number; + dexWeight?: number; + agiWeight?: number; + chaWeight?: number; + difficulty?: number; + territory?: ITerritory; +} diff --git a/src/Gang/data/Constants.ts b/src/Gang/data/Constants.ts index 6b2887526..8572785aa 100644 --- a/src/Gang/data/Constants.ts +++ b/src/Gang/data/Constants.ts @@ -1,24 +1,24 @@ export const GangConstants: { - GangRespectToReputationRatio: number; - MaximumGangMembers: number; - CyclesPerTerritoryAndPowerUpdate: number; - AscensionMultiplierRatio: number; - Names: string[]; + GangRespectToReputationRatio: number; + MaximumGangMembers: number; + CyclesPerTerritoryAndPowerUpdate: number; + AscensionMultiplierRatio: number; + Names: string[]; } = { - // Respect is divided by this to get rep gain - GangRespectToReputationRatio: 25, - MaximumGangMembers: 12, - CyclesPerTerritoryAndPowerUpdate: 100, - // Portion of upgrade multiplier that is kept after ascending - AscensionMultiplierRatio: .15, - // Names of possible Gangs - Names: [ - "Slum Snakes", - "Tetrads", - "The Syndicate", - "The Dark Army", - "Speakers for the Dead", - "NiteSec", - "The Black Hand", - ], -}; \ No newline at end of file + // Respect is divided by this to get rep gain + GangRespectToReputationRatio: 25, + MaximumGangMembers: 12, + CyclesPerTerritoryAndPowerUpdate: 100, + // Portion of upgrade multiplier that is kept after ascending + AscensionMultiplierRatio: 0.15, + // Names of possible Gangs + Names: [ + "Slum Snakes", + "Tetrads", + "The Syndicate", + "The Dark Army", + "Speakers for the Dead", + "NiteSec", + "The Black Hand", + ], +}; diff --git a/src/Gang/data/tasks.ts b/src/Gang/data/tasks.ts index dc14a0fd7..b9143ba79 100644 --- a/src/Gang/data/tasks.ts +++ b/src/Gang/data/tasks.ts @@ -6,31 +6,31 @@ import { ITaskParams } from "../ITaskParams"; * (defined in Gang.js) */ export interface IGangMemberTaskMetadata { - /** - * Description of the task - */ - desc: string; + /** + * Description of the task + */ + desc: string; - /** - * Whether or not this task is meant for Combat-type gangs - */ - isCombat: boolean; + /** + * Whether or not this task is meant for Combat-type gangs + */ + isCombat: boolean; - /** - * Whether or not this task is for Hacking-type gangs - */ - isHacking: boolean; + /** + * Whether or not this task is for Hacking-type gangs + */ + isHacking: boolean; - /** - * Name of the task - */ - name: string; + /** + * Name of the task + */ + name: string; - /** - * An object containing weighting parameters for the task. These parameters are used for - * various calculations (respect gain, wanted gain, etc.) - */ - params: ITaskParams; + /** + * An object containing weighting parameters for the task. These parameters are used for + * various calculations (respect gain, wanted gain, etc.) + */ + params: ITaskParams; } /** @@ -38,248 +38,368 @@ export interface IGangMemberTaskMetadata { * objects in Gang.js */ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [ - { - desc: "This gang member is currently idle", - isCombat: true, - isHacking: true, - name: "Unassigned", - params: {hackWeight: 100}, // This is just to get by the weight check in the GangMemberTask constructor + { + desc: "This gang member is currently idle", + isCombat: true, + isHacking: true, + name: "Unassigned", + params: { hackWeight: 100 }, // This is just to get by the weight check in the GangMemberTask constructor + }, + { + desc: "Assign this gang member to create and distribute ransomware

    Earns money - Slightly increases respect - Slightly increases wanted level", + isCombat: false, + isHacking: true, + name: "Ransomware", + params: { + baseRespect: 0.00005, + baseWanted: 0.0001, + baseMoney: 1, + hackWeight: 100, + difficulty: 1, }, - { - desc: "Assign this gang member to create and distribute ransomware

    Earns money - Slightly increases respect - Slightly increases wanted level", - isCombat: false, - isHacking: true, - name: "Ransomware", - params: {baseRespect: 0.00005, baseWanted: 0.0001, baseMoney: 1, hackWeight: 100, difficulty: 1}, + }, + { + desc: "Assign this gang member to attempt phishing scams and attacks

    Earns money - Slightly increases respect - Slightly increases wanted level", + isCombat: false, + isHacking: true, + name: "Phishing", + params: { + baseRespect: 0.00008, + baseWanted: 0.003, + baseMoney: 2.5, + hackWeight: 85, + chaWeight: 15, + difficulty: 3.5, }, - { - desc: "Assign this gang member to attempt phishing scams and attacks

    Earns money - Slightly increases respect - Slightly increases wanted level", - isCombat: false, - isHacking: true, - name: "Phishing", - params: {baseRespect: 0.00008, baseWanted: 0.003, baseMoney: 2.5, hackWeight: 85, chaWeight: 15, difficulty: 3.5}, + }, + { + desc: "Assign this gang member to attempt identity theft

    Earns money - Increases respect - Increases wanted level", + isCombat: false, + isHacking: true, + name: "Identity Theft", + params: { + baseRespect: 0.0001, + baseWanted: 0.075, + baseMoney: 6, + hackWeight: 80, + chaWeight: 20, + difficulty: 5, }, - { - desc: "Assign this gang member to attempt identity theft

    Earns money - Increases respect - Increases wanted level", - isCombat: false, - isHacking: true, - name: "Identity Theft", - params: {baseRespect: 0.0001, baseWanted: 0.075, baseMoney: 6, hackWeight: 80, chaWeight: 20, difficulty: 5}, + }, + { + desc: "Assign this gang member to carry out DDoS attacks

    Increases respect - Increases wanted level", + isCombat: false, + isHacking: true, + name: "DDoS Attacks", + params: { + baseRespect: 0.0004, + baseWanted: 0.2, + hackWeight: 100, + difficulty: 8, }, - { - desc: "Assign this gang member to carry out DDoS attacks

    Increases respect - Increases wanted level", - isCombat: false, - isHacking: true, - name: "DDoS Attacks", - params: {baseRespect: 0.0004, baseWanted: 0.2, hackWeight: 100, difficulty: 8}, + }, + { + desc: "Assign this gang member to create and distribute malicious viruses

    Increases respect - Increases wanted level", + isCombat: false, + isHacking: true, + name: "Plant Virus", + params: { + baseRespect: 0.0006, + baseWanted: 0.4, + hackWeight: 100, + difficulty: 12, }, - { - desc: "Assign this gang member to create and distribute malicious viruses

    Increases respect - Increases wanted level", - isCombat: false, - isHacking: true, - name: "Plant Virus", - params: {baseRespect: 0.0006, baseWanted: 0.4, hackWeight: 100, difficulty: 12}, + }, + { + desc: "Assign this gang member to commit financial fraud and digital counterfeiting

    Earns money - Slightly increases respect - Slightly increases wanted level", + isCombat: false, + isHacking: true, + name: "Fraud & Counterfeiting", + params: { + baseRespect: 0.0004, + baseWanted: 0.3, + baseMoney: 15, + hackWeight: 80, + chaWeight: 20, + difficulty: 20, }, - { - desc: "Assign this gang member to commit financial fraud and digital counterfeiting

    Earns money - Slightly increases respect - Slightly increases wanted level", - isCombat: false, - isHacking: true, - name: "Fraud & Counterfeiting", - params: {baseRespect: 0.0004, baseWanted: 0.3, baseMoney: 15, hackWeight: 80, chaWeight: 20, difficulty: 20}, + }, + { + desc: "Assign this gang member to launder money

    Earns money - Increases respect - Increases wanted level", + isCombat: false, + isHacking: true, + name: "Money Laundering", + params: { + baseRespect: 0.001, + baseWanted: 1.25, + baseMoney: 120, + hackWeight: 75, + chaWeight: 25, + difficulty: 25, }, - { - desc: "Assign this gang member to launder money

    Earns money - Increases respect - Increases wanted level", - isCombat: false, - isHacking: true, - name: "Money Laundering", - params: {baseRespect: 0.001, baseWanted: 1.25, baseMoney: 120, hackWeight: 75, chaWeight: 25, difficulty: 25}, + }, + { + desc: "Assign this gang member to commit acts of cyberterrorism

    Greatly increases respect - Greatly increases wanted level", + isCombat: false, + isHacking: true, + name: "Cyberterrorism", + params: { + baseRespect: 0.01, + baseWanted: 6, + hackWeight: 80, + chaWeight: 20, + difficulty: 36, }, - { - desc: "Assign this gang member to commit acts of cyberterrorism

    Greatly increases respect - Greatly increases wanted level", - isCombat: false, - isHacking: true, - name: "Cyberterrorism", - params: {baseRespect: 0.01, baseWanted: 6, hackWeight: 80, chaWeight: 20, difficulty: 36}, + }, + { + desc: "Assign this gang member to be an ethical hacker for corporations

    Earns money - Lowers wanted level", + isCombat: false, + isHacking: true, + name: "Ethical Hacking", + params: { + baseWanted: -0.001, + baseMoney: 1, + hackWeight: 90, + chaWeight: 10, + difficulty: 1, }, - { - desc: "Assign this gang member to be an ethical hacker for corporations

    Earns money - Lowers wanted level", - isCombat: false, - isHacking: true, - name: "Ethical Hacking", - params: {baseWanted: -0.001, baseMoney: 1, hackWeight: 90, chaWeight: 10, difficulty: 1}, + }, + { + desc: "Assign this gang member to mug random people on the streets

    Earns money - Slightly increases respect - Very slightly increases wanted level", + isCombat: true, + isHacking: false, + name: "Mug People", + params: { + baseRespect: 0.00005, + baseWanted: 0.00005, + baseMoney: 1.2, + strWeight: 25, + defWeight: 25, + dexWeight: 25, + agiWeight: 10, + chaWeight: 15, + difficulty: 1, }, - { - desc: "Assign this gang member to mug random people on the streets

    Earns money - Slightly increases respect - Very slightly increases wanted level", - isCombat: true, - isHacking: false, - name: "Mug People", - params: { - baseRespect: 0.00005, baseWanted: 0.00005, baseMoney: 1.2, - strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 10, chaWeight: 15, - difficulty: 1, - }, + }, + { + desc: "Assign this gang member to sell drugs

    Earns money - Slightly increases respect - Slightly increases wanted level - Scales slightly with territory", + isCombat: true, + isHacking: false, + name: "Deal Drugs", + params: { + baseRespect: 0.00006, + baseWanted: 0.002, + baseMoney: 5, + agiWeight: 20, + dexWeight: 20, + chaWeight: 60, + difficulty: 3.5, + territory: { + money: 1.2, + respect: 1, + wanted: 1.15, + }, }, - { - desc: "Assign this gang member to sell drugs

    Earns money - Slightly increases respect - Slightly increases wanted level - Scales slightly with territory", - isCombat: true, - isHacking: false, - name: "Deal Drugs", - params: { - baseRespect: 0.00006, baseWanted: 0.002, baseMoney: 5, - agiWeight: 20, dexWeight: 20, chaWeight: 60, - difficulty: 3.5, - territory: { - money: 1.2, - respect: 1, - wanted: 1.15, - }, - }, + }, + { + desc: "Assign this gang member to extort civilians in your territory

    Earns money - Slightly increases respect - Increases wanted - Scales heavily with territory", + isCombat: true, + isHacking: false, + name: "Strongarm Civilians", + params: { + baseRespect: 0.00004, + baseWanted: 0.02, + baseMoney: 2.5, + hackWeight: 10, + strWeight: 25, + defWeight: 25, + dexWeight: 20, + agiWeight: 10, + chaWeight: 10, + difficulty: 5, + territory: { + money: 1.6, + respect: 1.1, + wanted: 1.5, + }, }, - { - desc: "Assign this gang member to extort civilians in your territory

    Earns money - Slightly increases respect - Increases wanted - Scales heavily with territory", - isCombat: true, - isHacking: false, - name: "Strongarm Civilians", - params: { - baseRespect: 0.00004, baseWanted: 0.02, baseMoney: 2.5, - hackWeight: 10, strWeight: 25, defWeight: 25, dexWeight: 20, agiWeight: 10, chaWeight: 10, - difficulty: 5, - territory: { - money: 1.6, - respect: 1.1, - wanted: 1.5, - }, - }, + }, + { + desc: "Assign this gang member to run cons

    Earns money - Increases respect - Increases wanted level", + isCombat: true, + isHacking: false, + name: "Run a Con", + params: { + baseRespect: 0.00012, + baseWanted: 0.05, + baseMoney: 15, + strWeight: 5, + defWeight: 5, + agiWeight: 25, + dexWeight: 25, + chaWeight: 40, + difficulty: 14, }, - { - desc: "Assign this gang member to run cons

    Earns money - Increases respect - Increases wanted level", - isCombat: true, - isHacking: false, - name: "Run a Con", - params: { - baseRespect: 0.00012, baseWanted: 0.05, baseMoney: 15, - strWeight: 5, defWeight: 5, agiWeight: 25, dexWeight: 25, chaWeight: 40, - difficulty: 14, - }, + }, + { + desc: "Assign this gang member to commit armed robbery on stores, banks and armored cars

    Earns money - Increases respect - Increases wanted level", + isCombat: true, + isHacking: false, + name: "Armed Robbery", + params: { + baseRespect: 0.00014, + baseWanted: 0.1, + baseMoney: 38, + hackWeight: 20, + strWeight: 15, + defWeight: 15, + agiWeight: 10, + dexWeight: 20, + chaWeight: 20, + difficulty: 20, }, - { - desc: "Assign this gang member to commit armed robbery on stores, banks and armored cars

    Earns money - Increases respect - Increases wanted level", - isCombat: true, - isHacking: false, - name: "Armed Robbery", - params: { - baseRespect: 0.00014, baseWanted: 0.1, baseMoney: 38, - hackWeight: 20, strWeight: 15, defWeight: 15, agiWeight: 10, dexWeight: 20, chaWeight: 20, - difficulty: 20, - }, + }, + { + desc: "Assign this gang member to traffick illegal arms

    Earns money - Increases respect - Increases wanted level - Scales heavily with territory", + isCombat: true, + isHacking: false, + name: "Traffick Illegal Arms", + params: { + baseRespect: 0.0002, + baseWanted: 0.24, + baseMoney: 58, + hackWeight: 15, + strWeight: 20, + defWeight: 20, + dexWeight: 20, + chaWeight: 25, + difficulty: 32, + territory: { + money: 1.4, + respect: 1.3, + wanted: 1.25, + }, }, - { - desc: "Assign this gang member to traffick illegal arms

    Earns money - Increases respect - Increases wanted level - Scales heavily with territory", - isCombat: true, - isHacking: false, - name: "Traffick Illegal Arms", - params: { - baseRespect: 0.0002, baseWanted: 0.24, baseMoney: 58, - hackWeight: 15, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 25, - difficulty: 32, - territory: { - money: 1.4, - respect: 1.3, - wanted: 1.25, - }, - }, + }, + { + desc: "Assign this gang member to threaten and black mail high-profile targets

    Earns money - Slightly increases respect - Slightly increases wanted level", + isCombat: true, + isHacking: false, + name: "Threaten & Blackmail", + params: { + baseRespect: 0.0002, + baseWanted: 0.125, + baseMoney: 24, + hackWeight: 25, + strWeight: 25, + dexWeight: 25, + chaWeight: 25, + difficulty: 28, }, - { - desc: "Assign this gang member to threaten and black mail high-profile targets

    Earns money - Slightly increases respect - Slightly increases wanted level", - isCombat: true, - isHacking: false, - name: "Threaten & Blackmail", - params: { - baseRespect: 0.0002, baseWanted: 0.125, baseMoney: 24, - hackWeight: 25, strWeight: 25, dexWeight: 25, chaWeight: 25, - difficulty: 28, - }, + }, + { + desc: "Assign this gang member to engage in human trafficking operations

    Earns money - Increases respect - Increases wanted level - Scales heavily with territory", + isCombat: true, + isHacking: false, + name: "Human Trafficking", + params: { + baseRespect: 0.004, + baseWanted: 1.25, + baseMoney: 120, + hackWeight: 30, + strWeight: 5, + defWeight: 5, + dexWeight: 30, + chaWeight: 30, + difficulty: 36, + territory: { + money: 1.5, + respect: 1.5, + wanted: 1.6, + }, }, - { - desc: "Assign this gang member to engage in human trafficking operations

    Earns money - Increases respect - Increases wanted level - Scales heavily with territory", - isCombat: true, - isHacking: false, - name: "Human Trafficking", - params: { - baseRespect: 0.004, baseWanted: 1.25, baseMoney: 120, - hackWeight: 30, strWeight: 5, defWeight: 5, dexWeight: 30, chaWeight: 30, - difficulty: 36, - territory: { - money: 1.5, - respect: 1.5, - wanted: 1.6, - }, - }, + }, + { + desc: "Assign this gang member to commit acts of terrorism

    Greatly increases respect - Greatly increases wanted level - Scales heavily with territory", + isCombat: true, + isHacking: false, + name: "Terrorism", + params: { + baseRespect: 0.01, + baseWanted: 6, + hackWeight: 20, + strWeight: 20, + defWeight: 20, + dexWeight: 20, + chaWeight: 20, + difficulty: 36, + territory: { + money: 1, + respect: 2, + wanted: 2, + }, }, - { - desc: "Assign this gang member to commit acts of terrorism

    Greatly increases respect - Greatly increases wanted level - Scales heavily with territory", - isCombat: true, - isHacking: false, - name: "Terrorism", - params: { - baseRespect: 0.01, baseWanted: 6, - hackWeight: 20, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 20, - difficulty: 36, - territory: { - money: 1, - respect: 2, - wanted: 2, - }, - }, + }, + { + desc: "Assign this gang member to be a vigilante and protect the city from criminals

    Decreases wanted level", + isCombat: true, + isHacking: true, + name: "Vigilante Justice", + params: { + baseWanted: -0.001, + hackWeight: 20, + strWeight: 20, + defWeight: 20, + dexWeight: 20, + agiWeight: 20, + difficulty: 1, + territory: { + money: 1, + respect: 1, + wanted: 0.9, // Gets harder with more territory + }, }, - { - desc: "Assign this gang member to be a vigilante and protect the city from criminals

    Decreases wanted level", - isCombat: true, - isHacking: true, - name: "Vigilante Justice", - params: { - baseWanted: -0.001, - hackWeight: 20, strWeight: 20, defWeight: 20, dexWeight: 20, agiWeight: 20, - difficulty: 1, - territory: { - money: 1, - respect: 1, - wanted: 0.9, // Gets harder with more territory - }, - }, + }, + { + desc: "Assign this gang member to increase their combat stats (str, def, dex, agi)", + isCombat: true, + isHacking: true, + name: "Train Combat", + params: { + strWeight: 25, + defWeight: 25, + dexWeight: 25, + agiWeight: 25, + difficulty: 100, }, - { - desc: "Assign this gang member to increase their combat stats (str, def, dex, agi)", - isCombat: true, - isHacking: true, - name: "Train Combat", - params: { - strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 25, - difficulty: 100, - }, + }, + { + desc: "Assign this gang member to train their hacking skills", + isCombat: true, + isHacking: true, + name: "Train Hacking", + params: { hackWeight: 100, difficulty: 45 }, + }, + { + desc: "Assign this gang member to train their charisma", + isCombat: true, + isHacking: true, + name: "Train Charisma", + params: { chaWeight: 100, difficulty: 8 }, + }, + { + desc: "Assign this gang member to engage in territorial warfare with other gangs. Members assigned to this task will help increase your gang's territory and will defend your territory from being taken.", + isCombat: true, + isHacking: true, + name: "Territory Warfare", + params: { + hackWeight: 15, + strWeight: 20, + defWeight: 20, + dexWeight: 20, + agiWeight: 20, + chaWeight: 5, + difficulty: 5, }, - { - desc: "Assign this gang member to train their hacking skills", - isCombat: true, - isHacking: true, - name: "Train Hacking", - params: {hackWeight: 100, difficulty: 45}, - }, - { - desc: "Assign this gang member to train their charisma", - isCombat: true, - isHacking: true, - name: "Train Charisma", - params: {chaWeight: 100, difficulty: 8}, - }, - { - desc: "Assign this gang member to engage in territorial warfare with other gangs. Members assigned to this task will help increase your gang's territory and will defend your territory from being taken.", - isCombat: true, - isHacking: true, - name: "Territory Warfare", - params: { - hackWeight: 15, strWeight: 20, defWeight: 20, dexWeight: 20, agiWeight: 20, chaWeight: 5, - difficulty: 5, - }, - }, -]; \ No newline at end of file + }, +]; diff --git a/src/Gang/data/upgrades.ts b/src/Gang/data/upgrades.ts index 7b4b80dd0..c558f00e1 100644 --- a/src/Gang/data/upgrades.ts +++ b/src/Gang/data/upgrades.ts @@ -1,18 +1,18 @@ export interface IMults { - hack?: number; - str?: number; - def?: number; - dex?: number; - agi?: number; - cha?: number; + hack?: number; + str?: number; + def?: number; + dex?: number; + agi?: number; + cha?: number; } export enum UpgradeType { - Weapon = "w", - Armor = "a", - Vehicle = "v", - Rootkit = "r", - Augmentation = "g", + Weapon = "w", + Armor = "a", + Vehicle = "v", + Rootkit = "r", + Augmentation = "g", } /** @@ -20,10 +20,10 @@ export enum UpgradeType { * (defined in Gang.js) */ export interface IGangMemberUpgradeMetadata { - cost: number; - mults: IMults; - name: string; - upgType: UpgradeType; + cost: number; + mults: IMults; + name: string; + upgType: UpgradeType; } /** @@ -31,196 +31,196 @@ export interface IGangMemberUpgradeMetadata { * objects in Gang.js */ export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [ - { - cost: 1e6, - mults: {str: 1.04, def: 1.04}, - name: "Baseball Bat", - upgType: UpgradeType.Weapon, - }, - { - cost: 12e6, - mults: {str: 1.08, def: 1.08, dex: 1.08}, - name: "Katana", - upgType: UpgradeType.Weapon, - }, - { - cost: 25e6, - mults: {str: 1.1, def: 1.1, dex: 1.1, agi: 1.1}, - name: "Glock 18C", - upgType: UpgradeType.Weapon, - }, - { - cost: 50e6, - mults: {str: 1.12, def: 1.1, agi: 1.1}, - name: "P90C", - upgType: UpgradeType.Weapon, - }, - { - cost: 60e6, - mults: {str: 1.2, def: 1.15}, - name: "Steyr AUG", - upgType: UpgradeType.Weapon, - }, - { - cost: 100e6, - mults: {str: 1.25, def: 1.2}, - name: "AK-47", - upgType: UpgradeType.Weapon, - }, - { - cost: 150e6, - mults: {str: 1.3, def: 1.25}, - name: "M15A10 Assault Rifle", - upgType: UpgradeType.Weapon, - }, - { - cost: 225e6, - mults: {str: 1.3, dex: 1.25, agi: 1.3}, - name: "AWM Sniper Rifle", - upgType: UpgradeType.Weapon, - }, - { - cost: 2e6, - mults: {def: 1.04}, - name: "Bulletproof Vest", - upgType: UpgradeType.Armor, - }, - { - cost: 5e6, - mults: {def: 1.08}, - name: "Full Body Armor", - upgType: UpgradeType.Armor, - }, - { - cost: 25e6, - mults: {def: 1.15, agi: 1.15}, - name: "Liquid Body Armor", - upgType: UpgradeType.Armor, - }, - { - cost: 40e6, - mults: {def: 1.2}, - name: "Graphene Plating Armor", - upgType: UpgradeType.Armor, - }, - { - cost: 3e6, - mults: {agi: 1.04, cha: 1.04}, - name: "Ford Flex V20", - upgType: UpgradeType.Vehicle, - }, - { - cost: 9e6, - mults: {agi: 1.08, cha: 1.08}, - name: "ATX1070 Superbike", - upgType: UpgradeType.Vehicle, - }, - { - cost: 18e6, - mults: {agi: 1.12, cha: 1.12}, - name: "Mercedes-Benz S9001", - upgType: UpgradeType.Vehicle, - }, - { - cost: 30e6, - mults: {agi: 1.16, cha: 1.16}, - name: "White Ferrari", - upgType: UpgradeType.Vehicle, - }, - { - cost: 5e6, - mults: {hack: 1.05}, - name: "NUKE Rootkit", - upgType: UpgradeType.Rootkit, - }, - { - cost: 25e6, - mults: {hack: 1.1}, - name: "Soulstealer Rootkit", - upgType: UpgradeType.Rootkit, - }, - { - cost: 75e6, - mults: {hack: 1.15}, - name: "Demon Rootkit", - upgType: UpgradeType.Rootkit, - }, - { - cost: 40e6, - mults: {hack: 1.12}, - name: "Hmap Node", - upgType: UpgradeType.Rootkit, - }, - { - cost: 75e6, - mults: {hack: 1.15}, - name: "Jack the Ripper", - upgType: UpgradeType.Rootkit, - }, - { - cost: 10e9, - mults: {str: 1.3, dex: 1.3}, - name: "Bionic Arms", - upgType: UpgradeType.Augmentation, - }, - { - cost: 10e9, - mults: {agi: 1.6}, - name: "Bionic Legs", - upgType: UpgradeType.Augmentation, - }, - { - cost: 15e9, - mults: {str: 1.15, def: 1.15, dex: 1.15, agi: 1.15}, - name: "Bionic Spine", - upgType: UpgradeType.Augmentation, - }, - { - cost: 20e9, - mults: {str: 1.4, def: 1.4}, - name: "BrachiBlades", - upgType: UpgradeType.Augmentation, - }, - { - cost: 12e9, - mults: {str: 1.2, def: 1.2}, - name: "Nanofiber Weave", - upgType: UpgradeType.Augmentation, - }, - { - cost: 25e9, - mults: {str: 1.5, agi: 1.5}, - name: "Synthetic Heart", - upgType: UpgradeType.Augmentation, - }, - { - cost: 15e9, - mults: {str: 1.3, def: 1.3}, - name: "Synfibril Muscle", - upgType: UpgradeType.Augmentation, - }, - { - cost: 5e9, - mults: {hack: 1.05}, - name: "BitWire", - upgType: UpgradeType.Augmentation, - }, - { - cost: 10e9, - mults: {hack: 1.15}, - name: "Neuralstimulator", - upgType: UpgradeType.Augmentation, - }, - { - cost: 7.5e9, - mults: {hack: 1.1}, - name: "DataJack", - upgType: UpgradeType.Augmentation, - }, - { - cost: 50e9, - mults: {str: 1.7, def: 1.7}, - name: "Graphene Bone Lacings", - upgType: UpgradeType.Augmentation, - }, + { + cost: 1e6, + mults: { str: 1.04, def: 1.04 }, + name: "Baseball Bat", + upgType: UpgradeType.Weapon, + }, + { + cost: 12e6, + mults: { str: 1.08, def: 1.08, dex: 1.08 }, + name: "Katana", + upgType: UpgradeType.Weapon, + }, + { + cost: 25e6, + mults: { str: 1.1, def: 1.1, dex: 1.1, agi: 1.1 }, + name: "Glock 18C", + upgType: UpgradeType.Weapon, + }, + { + cost: 50e6, + mults: { str: 1.12, def: 1.1, agi: 1.1 }, + name: "P90C", + upgType: UpgradeType.Weapon, + }, + { + cost: 60e6, + mults: { str: 1.2, def: 1.15 }, + name: "Steyr AUG", + upgType: UpgradeType.Weapon, + }, + { + cost: 100e6, + mults: { str: 1.25, def: 1.2 }, + name: "AK-47", + upgType: UpgradeType.Weapon, + }, + { + cost: 150e6, + mults: { str: 1.3, def: 1.25 }, + name: "M15A10 Assault Rifle", + upgType: UpgradeType.Weapon, + }, + { + cost: 225e6, + mults: { str: 1.3, dex: 1.25, agi: 1.3 }, + name: "AWM Sniper Rifle", + upgType: UpgradeType.Weapon, + }, + { + cost: 2e6, + mults: { def: 1.04 }, + name: "Bulletproof Vest", + upgType: UpgradeType.Armor, + }, + { + cost: 5e6, + mults: { def: 1.08 }, + name: "Full Body Armor", + upgType: UpgradeType.Armor, + }, + { + cost: 25e6, + mults: { def: 1.15, agi: 1.15 }, + name: "Liquid Body Armor", + upgType: UpgradeType.Armor, + }, + { + cost: 40e6, + mults: { def: 1.2 }, + name: "Graphene Plating Armor", + upgType: UpgradeType.Armor, + }, + { + cost: 3e6, + mults: { agi: 1.04, cha: 1.04 }, + name: "Ford Flex V20", + upgType: UpgradeType.Vehicle, + }, + { + cost: 9e6, + mults: { agi: 1.08, cha: 1.08 }, + name: "ATX1070 Superbike", + upgType: UpgradeType.Vehicle, + }, + { + cost: 18e6, + mults: { agi: 1.12, cha: 1.12 }, + name: "Mercedes-Benz S9001", + upgType: UpgradeType.Vehicle, + }, + { + cost: 30e6, + mults: { agi: 1.16, cha: 1.16 }, + name: "White Ferrari", + upgType: UpgradeType.Vehicle, + }, + { + cost: 5e6, + mults: { hack: 1.05 }, + name: "NUKE Rootkit", + upgType: UpgradeType.Rootkit, + }, + { + cost: 25e6, + mults: { hack: 1.1 }, + name: "Soulstealer Rootkit", + upgType: UpgradeType.Rootkit, + }, + { + cost: 75e6, + mults: { hack: 1.15 }, + name: "Demon Rootkit", + upgType: UpgradeType.Rootkit, + }, + { + cost: 40e6, + mults: { hack: 1.12 }, + name: "Hmap Node", + upgType: UpgradeType.Rootkit, + }, + { + cost: 75e6, + mults: { hack: 1.15 }, + name: "Jack the Ripper", + upgType: UpgradeType.Rootkit, + }, + { + cost: 10e9, + mults: { str: 1.3, dex: 1.3 }, + name: "Bionic Arms", + upgType: UpgradeType.Augmentation, + }, + { + cost: 10e9, + mults: { agi: 1.6 }, + name: "Bionic Legs", + upgType: UpgradeType.Augmentation, + }, + { + cost: 15e9, + mults: { str: 1.15, def: 1.15, dex: 1.15, agi: 1.15 }, + name: "Bionic Spine", + upgType: UpgradeType.Augmentation, + }, + { + cost: 20e9, + mults: { str: 1.4, def: 1.4 }, + name: "BrachiBlades", + upgType: UpgradeType.Augmentation, + }, + { + cost: 12e9, + mults: { str: 1.2, def: 1.2 }, + name: "Nanofiber Weave", + upgType: UpgradeType.Augmentation, + }, + { + cost: 25e9, + mults: { str: 1.5, agi: 1.5 }, + name: "Synthetic Heart", + upgType: UpgradeType.Augmentation, + }, + { + cost: 15e9, + mults: { str: 1.3, def: 1.3 }, + name: "Synfibril Muscle", + upgType: UpgradeType.Augmentation, + }, + { + cost: 5e9, + mults: { hack: 1.05 }, + name: "BitWire", + upgType: UpgradeType.Augmentation, + }, + { + cost: 10e9, + mults: { hack: 1.15 }, + name: "Neuralstimulator", + upgType: UpgradeType.Augmentation, + }, + { + cost: 7.5e9, + mults: { hack: 1.1 }, + name: "DataJack", + upgType: UpgradeType.Augmentation, + }, + { + cost: 50e9, + mults: { str: 1.7, def: 1.7 }, + name: "Graphene Bone Lacings", + upgType: UpgradeType.Augmentation, + }, ]; diff --git a/src/Gang/ui/AscensionPopup.tsx b/src/Gang/ui/AscensionPopup.tsx index ce3954192..4dfe802a1 100644 --- a/src/Gang/ui/AscensionPopup.tsx +++ b/src/Gang/ui/AscensionPopup.tsx @@ -10,61 +10,89 @@ import { removePopup } from "../../ui/React/createPopup"; import { dialogBoxCreate } from "../../../utils/DialogBox"; interface IProps { - member: GangMember; - gang: Gang; - popupId: string; - onAscend: () => void; + member: GangMember; + gang: Gang; + popupId: string; + onAscend: () => void; } export function AscensionPopup(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; + const setRerender = useState(false)[1]; - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); + useEffect(() => { + const id = setInterval(() => setRerender((old) => !old), 1000); + return () => clearInterval(id); + }, []); - function confirm(): void { - props.onAscend(); - const res = props.gang.ascendMember(props.member); - dialogBoxCreate(

    - You ascended {props.member.name}!
    -
    - Your gang lost {numeralWrapper.formatRespect(res.respect)} respect.
    -
    - {props.member.name} gained the following stat multipliers for ascending:
    - Hacking: x{numeralWrapper.format(res.hack, '0.000')}
    - Strength: x{numeralWrapper.format(res.str, '0.000')}
    - Defense: x{numeralWrapper.format(res.def, '0.000')}
    - Dexterity: x{numeralWrapper.format(res.dex, '0.000')}
    - Agility: x{numeralWrapper.format(res.agi, '0.000')}
    - Charisma: x{numeralWrapper.format(res.cha, '0.000')}
    -

    ); - removePopup(props.popupId); - } + function confirm(): void { + props.onAscend(); + const res = props.gang.ascendMember(props.member); + dialogBoxCreate( +

    + You ascended {props.member.name}!
    +
    + Your gang lost {numeralWrapper.formatRespect(res.respect)} respect. +
    +
    + {props.member.name} gained the following stat multipliers for ascending: +
    + Hacking: x{numeralWrapper.format(res.hack, "0.000")} +
    + Strength: x{numeralWrapper.format(res.str, "0.000")} +
    + Defense: x{numeralWrapper.format(res.def, "0.000")} +
    + Dexterity: x{numeralWrapper.format(res.dex, "0.000")} +
    + Agility: x{numeralWrapper.format(res.agi, "0.000")} +
    + Charisma: x{numeralWrapper.format(res.cha, "0.000")} +
    +

    , + ); + removePopup(props.popupId); + } - function cancel(): void { - removePopup(props.popupId); - } + function cancel(): void { + removePopup(props.popupId); + } - const ascendBenefits = props.member.getAscensionResults(); + const ascendBenefits = props.member.getAscensionResults(); - return (<> -
    -Are you sure you want to ascend this member? They will lose all of
    -their non-Augmentation upgrades and their stats will reset back to 1.
    -
    -Furthermore, your gang will lose {numeralWrapper.formatRespect(props.member.earnedRespect)} respect
    -
    -In return, they will gain the following permanent boost to stat multipliers:
    -Hacking: x{numeralWrapper.format(ascendBenefits.hack, '0.000')}
    -Strength: x{numeralWrapper.format(ascendBenefits.str, '0.000')}
    -Defense: x{numeralWrapper.format(ascendBenefits.def, '0.000')}
    -Dexterity: x{numeralWrapper.format(ascendBenefits.dex, '0.000')}
    -Agility: x{numeralWrapper.format(ascendBenefits.agi, '0.000')}
    -Charisma: x{numeralWrapper.format(ascendBenefits.cha, '0.000')}
    -
    - - - ); -} \ No newline at end of file + return ( + <> +
    +        Are you sure you want to ascend this member? They will lose all of
    +        
    + their non-Augmentation upgrades and their stats will reset back to 1. +
    +
    + Furthermore, your gang will lose{" "} + {numeralWrapper.formatRespect(props.member.earnedRespect)} respect +
    +
    + In return, they will gain the following permanent boost to stat + multipliers: +
    + Hacking: x{numeralWrapper.format(ascendBenefits.hack, "0.000")} +
    + Strength: x{numeralWrapper.format(ascendBenefits.str, "0.000")} +
    + Defense: x{numeralWrapper.format(ascendBenefits.def, "0.000")} +
    + Dexterity: x{numeralWrapper.format(ascendBenefits.dex, "0.000")} +
    + Agility: x{numeralWrapper.format(ascendBenefits.agi, "0.000")} +
    + Charisma: x{numeralWrapper.format(ascendBenefits.cha, "0.000")} +
    +
    + + + + ); +} diff --git a/src/Gang/ui/BonusTime.tsx b/src/Gang/ui/BonusTime.tsx index 1c71b87c7..4a0e6edfe 100644 --- a/src/Gang/ui/BonusTime.tsx +++ b/src/Gang/ui/BonusTime.tsx @@ -7,23 +7,24 @@ import { CONSTANTS } from "../../Constants"; import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; interface IProps { - gang: Gang; + gang: Gang; } export function BonusTime(props: IProps): React.ReactElement { - const CyclerPerSecond = 1000 / CONSTANTS._idleSpeed; - if (props.gang.storedCycles / CyclerPerSecond*1000 <= 5000) return (<>); - const bonusMillis = props.gang.storedCycles / CyclerPerSecond * 1000; - return (<> -

    - Bonus time: {convertTimeMsToTimeElapsedString(bonusMillis)} - - You gain bonus time while offline or when the game is inactive - (e.g. when the tab is throttled by the browser). Bonus time - makes the Gang mechanic progress faster, up to 5x the normal - speed. - -

    -
    - ); -} \ No newline at end of file + const CyclerPerSecond = 1000 / CONSTANTS._idleSpeed; + if ((props.gang.storedCycles / CyclerPerSecond) * 1000 <= 5000) return <>; + const bonusMillis = (props.gang.storedCycles / CyclerPerSecond) * 1000; + return ( + <> +

    + Bonus time: {convertTimeMsToTimeElapsedString(bonusMillis)} + + You gain bonus time while offline or when the game is inactive (e.g. + when the tab is throttled by the browser). Bonus time makes the Gang + mechanic progress faster, up to 5x the normal speed. + +

    +
    + + ); +} diff --git a/src/Gang/ui/GangMemberAccordion.tsx b/src/Gang/ui/GangMemberAccordion.tsx index 9cef3efc3..454a05931 100644 --- a/src/Gang/ui/GangMemberAccordion.tsx +++ b/src/Gang/ui/GangMemberAccordion.tsx @@ -8,15 +8,18 @@ import { Accordion } from "../../ui/React/Accordion"; import { GangMemberAccordionContent } from "./GangMemberAccordionContent"; interface IProps { - gang: Gang; - member: GangMember; + gang: Gang; + member: GangMember; } export function GangMemberAccordion(props: IProps): React.ReactElement { - return {props.member.name}} - panelContent={} /> -} \ No newline at end of file + return ( + {props.member.name}} + panelContent={ + + } + /> + ); +} diff --git a/src/Gang/ui/GangMemberAccordionContent.tsx b/src/Gang/ui/GangMemberAccordionContent.tsx index baf298058..63ae59a8f 100644 --- a/src/Gang/ui/GangMemberAccordionContent.tsx +++ b/src/Gang/ui/GangMemberAccordionContent.tsx @@ -1,5 +1,5 @@ /** - * React Component for the content of the accordion of gang members on the + * React Component for the content of the accordion of gang members on the * management subpage. */ import React, { useState } from "react"; @@ -10,27 +10,31 @@ import { Gang } from "../Gang"; import { GangMember } from "../GangMember"; interface IProps { - gang: Gang; - member: GangMember; + gang: Gang; + member: GangMember; } export function GangMemberAccordionContent(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - return (<> -
    - setRerender(old => !old)} - gang={props.gang} - member={props.member} /> -
    -
    - setRerender(old => !old)} - gang={props.gang} - member={props.member} /> -
    -
    - -
    - ); + const setRerender = useState(false)[1]; + return ( + <> +
    + setRerender((old) => !old)} + gang={props.gang} + member={props.member} + /> +
    +
    + setRerender((old) => !old)} + gang={props.gang} + member={props.member} + /> +
    +
    + +
    + + ); } diff --git a/src/Gang/ui/GangMemberList.tsx b/src/Gang/ui/GangMemberList.tsx index 032c375bb..8815f1a89 100644 --- a/src/Gang/ui/GangMemberList.tsx +++ b/src/Gang/ui/GangMemberList.tsx @@ -11,48 +11,60 @@ import { GangMember } from "../GangMember"; import { RecruitButton } from "./RecruitButton"; interface IProps { - gang: Gang; - player: IPlayer; + gang: Gang; + player: IPlayer; } export function GangMemberList(props: IProps): React.ReactElement { - const [filter, setFilter] = useState(""); - const setRerender = useState(false)[1]; + const [filter, setFilter] = useState(""); + const setRerender = useState(false)[1]; - function openUpgradePopup(): void { - const popupId = `gang-upgrade-popup`; - createPopup(popupId, GangMemberUpgradePopup, { - gang: props.gang, - player: props.player, - popupId: popupId, - }); - } + function openUpgradePopup(): void { + const popupId = `gang-upgrade-popup`; + createPopup(popupId, GangMemberUpgradePopup, { + gang: props.gang, + player: props.player, + popupId: popupId, + }); + } - function onFilterChange(event: React.ChangeEvent): void { - setFilter(event.target.value); - } + function onFilterChange(event: React.ChangeEvent): void { + setFilter(event.target.value); + } - const members = props.gang.members.filter((member: GangMember) => member.name.indexOf(filter) > -1 || member.task.indexOf(filter) > -1); + const members = props.gang.members.filter( + (member: GangMember) => + member.name.indexOf(filter) > -1 || member.task.indexOf(filter) > -1, + ); - return (<> - setRerender(old => !old)} - gang={props.gang} /> -
    - - Manage Equipment -
      - {members.map((member: GangMember) =>
    • - -
    • )} -
    - ); -} \ No newline at end of file + return ( + <> + setRerender((old) => !old)} + gang={props.gang} + /> +
    + + + Manage Equipment + +
      + {members.map((member: GangMember) => ( +
    • + +
    • + ))} +
    + + ); +} diff --git a/src/Gang/ui/GangMemberStats.tsx b/src/Gang/ui/GangMemberStats.tsx index 3a5c08e01..108d666bb 100644 --- a/src/Gang/ui/GangMemberStats.tsx +++ b/src/Gang/ui/GangMemberStats.tsx @@ -12,66 +12,112 @@ import { GangMember } from "../GangMember"; import { AscensionPopup } from "./AscensionPopup"; interface IProps { - member: GangMember; - gang: Gang; - onAscend: () => void; + member: GangMember; + gang: Gang; + onAscend: () => void; } export function GangMemberStats(props: IProps): React.ReactElement { - function ascend(): void { - const popupId = `gang-management-ascend-member ${props.member.name}`; - createPopup(popupId, AscensionPopup, { - member: props.member, - gang: props.gang, - popupId: popupId, - onAscend: props.onAscend, - }); - } + function ascend(): void { + const popupId = `gang-management-ascend-member ${props.member.name}`; + createPopup(popupId, AscensionPopup, { + member: props.member, + gang: props.gang, + popupId: popupId, + onAscend: props.onAscend, + }); + } - function openAscensionHelp(): void { - dialogBoxCreate(<> - Ascending a Gang Member resets the member's progress and stats in - exchange for a permanent boost to their stat multipliers. -

    - The additional stat multiplier that the Gang Member gains upon - ascension is based on the amount of exp they have. -

    - Upon ascension, the member will lose all of its non-Augmentation - Equipment and your gang will lose respect equal to the total respect - earned by the member. - ); - } - - const asc = { - hack: props.member.calculateAscensionMult(props.member.hack_asc_points), - str: props.member.calculateAscensionMult(props.member.str_asc_points), - def: props.member.calculateAscensionMult(props.member.def_asc_points), - dex: props.member.calculateAscensionMult(props.member.dex_asc_points), - agi: props.member.calculateAscensionMult(props.member.agi_asc_points), - cha: props.member.calculateAscensionMult(props.member.cha_asc_points), - }; - - return (<> - -Hk: x{numeralWrapper.formatMultiplier(props.member.hack_mult * asc.hack)}(x{numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.hack)} Asc)
    -St: x{numeralWrapper.formatMultiplier(props.member.str_mult * asc.str)}(x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.str)} Asc)
    -Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * asc.def)}(x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.def)} Asc)
    -Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * asc.dex)}(x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.dex)} Asc)
    -Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * asc.agi)}(x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.agi)} Asc)
    -Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * asc.cha)}(x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.cha)} Asc) -
    -
    -Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)
    -Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)
    -Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)
    -Dexterity: {formatNumber(props.member.dex, 0)} ({numeralWrapper.formatExp(props.member.dex_exp)} exp)
    -Agility: {formatNumber(props.member.agi, 0)} ({numeralWrapper.formatExp(props.member.agi_exp)} exp)
    -Charisma: {formatNumber(props.member.cha, 0)} ({numeralWrapper.formatExp(props.member.cha_exp)} exp)
    -
    + function openAscensionHelp(): void { + dialogBoxCreate( + <> + Ascending a Gang Member resets the member's progress and stats in + exchange for a permanent boost to their stat multipliers.
    - { props.member.canAscend() && <> - -
    ?
    - } - ); +
    + The additional stat multiplier that the Gang Member gains upon ascension + is based on the amount of exp they have. +
    +
    + Upon ascension, the member will lose all of its non-Augmentation + Equipment and your gang will lose respect equal to the total respect + earned by the member. + , + ); + } + + const asc = { + hack: props.member.calculateAscensionMult(props.member.hack_asc_points), + str: props.member.calculateAscensionMult(props.member.str_asc_points), + def: props.member.calculateAscensionMult(props.member.def_asc_points), + dex: props.member.calculateAscensionMult(props.member.dex_asc_points), + agi: props.member.calculateAscensionMult(props.member.agi_asc_points), + cha: props.member.calculateAscensionMult(props.member.cha_asc_points), + }; + + return ( + <> + + Hk: x + {numeralWrapper.formatMultiplier(props.member.hack_mult * asc.hack)}(x + {numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x + {numeralWrapper.formatMultiplier(asc.hack)} Asc) +
    + St: x{numeralWrapper.formatMultiplier(props.member.str_mult * asc.str)} + (x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x + {numeralWrapper.formatMultiplier(asc.str)} Asc) +
    + Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * asc.def)} + (x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x + {numeralWrapper.formatMultiplier(asc.def)} Asc) +
    + Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * asc.dex)} + (x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x + {numeralWrapper.formatMultiplier(asc.dex)} Asc) +
    + Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * asc.agi)} + (x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x + {numeralWrapper.formatMultiplier(asc.agi)} Asc) +
    + Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * asc.cha)} + (x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x + {numeralWrapper.formatMultiplier(asc.cha)} Asc) +
    +
    +        Hacking: {formatNumber(props.member.hack, 0)} (
    +        {numeralWrapper.formatExp(props.member.hack_exp)} exp)
    +        
    + Strength: {formatNumber(props.member.str, 0)} ( + {numeralWrapper.formatExp(props.member.str_exp)} exp) +
    + Defense: {formatNumber(props.member.def, 0)} ( + {numeralWrapper.formatExp(props.member.def_exp)} exp) +
    + Dexterity: {formatNumber(props.member.dex, 0)} ( + {numeralWrapper.formatExp(props.member.dex_exp)} exp) +
    + Agility: {formatNumber(props.member.agi, 0)} ( + {numeralWrapper.formatExp(props.member.agi_exp)} exp) +
    + Charisma: {formatNumber(props.member.cha, 0)} ( + {numeralWrapper.formatExp(props.member.cha_exp)} exp) +
    +
    +
    + {props.member.canAscend() && ( + <> + +
    + ? +
    + + )} + + ); } diff --git a/src/Gang/ui/GangMemberUpgradePopup.tsx b/src/Gang/ui/GangMemberUpgradePopup.tsx index 0a21d4ae8..c786685b3 100644 --- a/src/Gang/ui/GangMemberUpgradePopup.tsx +++ b/src/Gang/ui/GangMemberUpgradePopup.tsx @@ -14,136 +14,213 @@ import { Gang } from "../Gang"; import { UpgradeType } from "../data/upgrades"; interface IPanelProps { - member: GangMember; - gang: Gang; - player: IPlayer; + member: GangMember; + gang: Gang; + player: IPlayer; } function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement { - const setRerender = useState(false)[1]; - function filterUpgrades(list: string[], type: UpgradeType): GangMemberUpgrade[] { - return Object.keys(GangMemberUpgrades).filter((upgName: string) => { - const upg = GangMemberUpgrades[upgName]; - if (props.player.money.lt(props.gang.getUpgradeCost(upg))) - return false; - if(upg.type !== type) return false; - if(list.includes(upgName)) return false; - return true; - }).map((upgName: string) => GangMemberUpgrades[upgName]); - } - const weaponUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Weapon); - const armorUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Armor); - const vehicleUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Vehicle); - const rootkitUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Rootkit); - const augUpgrades = filterUpgrades(props.member.augmentations, UpgradeType.Augmentation); + const setRerender = useState(false)[1]; + function filterUpgrades( + list: string[], + type: UpgradeType, + ): GangMemberUpgrade[] { + return Object.keys(GangMemberUpgrades) + .filter((upgName: string) => { + const upg = GangMemberUpgrades[upgName]; + if (props.player.money.lt(props.gang.getUpgradeCost(upg))) return false; + if (upg.type !== type) return false; + if (list.includes(upgName)) return false; + return true; + }) + .map((upgName: string) => GangMemberUpgrades[upgName]); + } + const weaponUpgrades = filterUpgrades( + props.member.upgrades, + UpgradeType.Weapon, + ); + const armorUpgrades = filterUpgrades( + props.member.upgrades, + UpgradeType.Armor, + ); + const vehicleUpgrades = filterUpgrades( + props.member.upgrades, + UpgradeType.Vehicle, + ); + const rootkitUpgrades = filterUpgrades( + props.member.upgrades, + UpgradeType.Rootkit, + ); + const augUpgrades = filterUpgrades( + props.member.augmentations, + UpgradeType.Augmentation, + ); - function purchasedUpgrade(upgName: string): React.ReactElement { - const upg = GangMemberUpgrades[upgName] - return (
    - {upg.name} - -
    ); - } + function purchasedUpgrade(upgName: string): React.ReactElement { + const upg = GangMemberUpgrades[upgName]; + return ( +
    + {upg.name} + +
    + ); + } - function upgradeButton(upg: GangMemberUpgrade, left = false): React.ReactElement { - function onClick(): void { - props.member.buyUpgrade(upg, props.player, props.gang); - setRerender(old => !old); - } - return ( - {upg.name} - - - ); + function upgradeButton( + upg: GangMemberUpgrade, + left = false, + ): React.ReactElement { + function onClick(): void { + props.member.buyUpgrade(upg, props.player, props.gang); + setRerender((old) => !old); } + return ( + + {upg.name} -{" "} + + + + ); + } - const asc = { - hack: props.member.calculateAscensionMult(props.member.hack_asc_points), - str: props.member.calculateAscensionMult(props.member.str_asc_points), - def: props.member.calculateAscensionMult(props.member.def_asc_points), - dex: props.member.calculateAscensionMult(props.member.dex_asc_points), - agi: props.member.calculateAscensionMult(props.member.agi_asc_points), - cha: props.member.calculateAscensionMult(props.member.cha_asc_points), - }; - return (
    -

    {props.member.name}({props.member.task})

    -
    -Hack: {props.member.hack} (x{formatNumber(props.member.hack_mult * asc.hack, 2)})
    -Str: {props.member.str} (x{formatNumber(props.member.str_mult * asc.str, 2)})
    -Def: {props.member.def} (x{formatNumber(props.member.def_mult * asc.def, 2)})
    -Dex: {props.member.dex} (x{formatNumber(props.member.dex_mult * asc.dex, 2)})
    -Agi: {props.member.agi} (x{formatNumber(props.member.agi_mult * asc.agi, 2)})
    -Cha: {props.member.cha} (x{formatNumber(props.member.cha_mult * asc.cha, 2)}) -
    -
    - Purchased Upgrades: {props.member.upgrades.map((upg: string) => purchasedUpgrade(upg))} + const asc = { + hack: props.member.calculateAscensionMult(props.member.hack_asc_points), + str: props.member.calculateAscensionMult(props.member.str_asc_points), + def: props.member.calculateAscensionMult(props.member.def_asc_points), + dex: props.member.calculateAscensionMult(props.member.dex_asc_points), + agi: props.member.calculateAscensionMult(props.member.agi_asc_points), + cha: props.member.calculateAscensionMult(props.member.cha_asc_points), + }; + return ( +
    +

    + {props.member.name}({props.member.task}) +

    +
    +        Hack: {props.member.hack} (x
    +        {formatNumber(props.member.hack_mult * asc.hack, 2)})
    + Str: {props.member.str} (x + {formatNumber(props.member.str_mult * asc.str, 2)})
    + Def: {props.member.def} (x + {formatNumber(props.member.def_mult * asc.def, 2)})
    + Dex: {props.member.dex} (x + {formatNumber(props.member.dex_mult * asc.dex, 2)})
    + Agi: {props.member.agi} (x + {formatNumber(props.member.agi_mult * asc.agi, 2)})
    + Cha: {props.member.cha} (x + {formatNumber(props.member.cha_mult * asc.cha, 2)}) +
    +
    + Purchased Upgrades:{" "} + {props.member.upgrades.map((upg: string) => purchasedUpgrade(upg))} {props.member.augmentations.map((upg: string) => purchasedUpgrade(upg))} -
    -
    -

    Weapons

    - {weaponUpgrades.map(upg => upgradeButton(upg))} -
    -
    -

    Armor

    - {armorUpgrades.map(upg => upgradeButton(upg))} -
    -
    -

    Vehicles

    - {vehicleUpgrades.map(upg => upgradeButton(upg))} -
    -
    -

    Rootkits

    - {rootkitUpgrades.map(upg => upgradeButton(upg, true))} -
    -
    -

    Augmentations

    - {augUpgrades.map(upg => upgradeButton(upg, true))} -
    -
    ); +
    +
    +

    Weapons

    + {weaponUpgrades.map((upg) => upgradeButton(upg))} +
    +
    +

    Armor

    + {armorUpgrades.map((upg) => upgradeButton(upg))} +
    +
    +

    Vehicles

    + {vehicleUpgrades.map((upg) => upgradeButton(upg))} +
    +
    +

    Rootkits

    + {rootkitUpgrades.map((upg) => upgradeButton(upg, true))} +
    +
    +

    Augmentations

    + {augUpgrades.map((upg) => upgradeButton(upg, true))} +
    +
    + ); } interface IProps { - gang: Gang; - player: IPlayer; - popupId: string; + gang: Gang; + player: IPlayer; + popupId: string; } export function GangMemberUpgradePopup(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - const [filter, setFilter] = useState(""); + const setRerender = useState(false)[1]; + const [filter, setFilter] = useState(""); - function closePopup(this: Window, ev: KeyboardEvent): void { - if(ev.keyCode !== 27) return; - removePopup(props.popupId); - } + function closePopup(this: Window, ev: KeyboardEvent): void { + if (ev.keyCode !== 27) return; + removePopup(props.popupId); + } - useEffect(() => { - window.addEventListener<'keydown'>('keydown', closePopup); - const id = setInterval(() => setRerender(old => !old), 1000); - return () => { - clearInterval(id); - window.removeEventListener<'keydown'>('keydown', closePopup); - } - }, []); + useEffect(() => { + window.addEventListener<"keydown">("keydown", closePopup); + const id = setInterval(() => setRerender((old) => !old), 1000); + return () => { + clearInterval(id); + window.removeEventListener<"keydown">("keydown", closePopup); + }; + }, []); - return (<> - setFilter(event.target.value)} /> -

    - Discount: -{numeralWrapper.formatPercentage(1 - 1 / props.gang.getDiscount())} - - You get a discount on equipment and upgrades based on your - gang's respect and power. More respect and power leads to more - discounts. - -

    - {props.gang.members.map((member: GangMember) => ) - } - ); + return ( + <> + setFilter(event.target.value)} + /> +

    + Discount: - + {numeralWrapper.formatPercentage(1 - 1 / props.gang.getDiscount())} + + You get a discount on equipment and upgrades based on your gang's + respect and power. More respect and power leads to more discounts. + +

    + {props.gang.members.map((member: GangMember) => ( + + ))} + + ); } diff --git a/src/Gang/ui/GangStats.tsx b/src/Gang/ui/GangStats.tsx index cdc009d96..381fff882 100644 --- a/src/Gang/ui/GangStats.tsx +++ b/src/Gang/ui/GangStats.tsx @@ -14,59 +14,71 @@ import { AllGangs } from "../AllGangs"; import { BonusTime } from "./BonusTime"; interface IProps { - gang: Gang; + gang: Gang; } export function GangStats(props: IProps): React.ReactElement { - const territoryMult = AllGangs[props.gang.facName].territory * 100; - let territoryStr; - if (territoryMult <= 0) { - territoryStr = formatNumber(0, 2); - } else if (territoryMult >= 100) { - territoryStr = formatNumber(100, 2); - } else { - territoryStr = formatNumber(territoryMult, 2); - } + const territoryMult = AllGangs[props.gang.facName].territory * 100; + let territoryStr; + if (territoryMult <= 0) { + territoryStr = formatNumber(0, 2); + } else if (territoryMult >= 100) { + territoryStr = formatNumber(100, 2); + } else { + territoryStr = formatNumber(territoryMult, 2); + } - return (<> -

    - Respect: {numeralWrapper.formatRespect(props.gang.respect)} ({numeralWrapper.formatRespect(5*props.gang.respectGainRate)} / sec) - - Represents the amount of respect your gang has from other gangs and criminal organizations. Your respect affects the amount of money your gang members will earn, and also determines how much reputation you are earning with your gang's corresponding Faction. - + return ( + <> +

    + Respect: {numeralWrapper.formatRespect(props.gang.respect)} ( + {numeralWrapper.formatRespect(5 * props.gang.respectGainRate)} / sec) + + Represents the amount of respect your gang has from other gangs and + criminal organizations. Your respect affects the amount of money your + gang members will earn, and also determines how much reputation you + are earning with your gang's corresponding Faction. + +

    +
    +

    + Wanted Level: {numeralWrapper.formatWanted(props.gang.wanted)} ( + {numeralWrapper.formatWanted(5 * props.gang.wantedGainRate)} / sec) + + Represents how much the gang is wanted by law enforcement. The higher + your gang's wanted level, the harder it will be for your gang members + to make money and earn respect. Note that the minimum wanted level is + 1. + +

    +
    +

    + Wanted Level Penalty: - + {formatNumber((1 - props.gang.getWantedPenalty()) * 100, 2)}% + + Penalty for respect and money gain rates due to Wanted Level + +

    +
    +
    +

    + Money gain rate: {MoneyRate(5 * props.gang.moneyGainRate)}

    -
    -

    - Wanted Level: {numeralWrapper.formatWanted(props.gang.wanted)} ({numeralWrapper.formatWanted(5*props.gang.wantedGainRate)} / sec) - - Represents how much the gang is wanted by law enforcement. The higher your gang's wanted level, the harder it will be for your gang members to make money and earn respect. Note that the minimum wanted level is 1. - -

    -
    -

    - Wanted Level Penalty: -{formatNumber((1 - props.gang.getWantedPenalty()) * 100, 2)}% - - Penalty for respect and money gain rates due to Wanted Level - -

    -
    -
    -

    - Money gain rate: {MoneyRate(5 * props.gang.moneyGainRate)} -

    -
    -
    -

    - Territory: {territoryStr}% - - The percentage of total territory your Gang controls - -

    -
    -

    - Faction reputation: {Reputation(Factions[props.gang.facName].playerReputation)} -

    -
    - - ); -} \ No newline at end of file +
    +
    +

    + Territory: {territoryStr}% + + The percentage of total territory your Gang controls + +

    +
    +

    + Faction reputation:{" "} + {Reputation(Factions[props.gang.facName].playerReputation)} +

    +
    + + + ); +} diff --git a/src/Gang/ui/ManagementSubpage.tsx b/src/Gang/ui/ManagementSubpage.tsx index 712ecb847..cb64eed16 100644 --- a/src/Gang/ui/ManagementSubpage.tsx +++ b/src/Gang/ui/ManagementSubpage.tsx @@ -8,39 +8,40 @@ import { Gang } from "../Gang"; import { GangMemberList } from "./GangMemberList"; interface IProps { - gang: Gang; - player: IPlayer; + gang: Gang; + player: IPlayer; } export function ManagementSubpage(props: IProps): React.ReactElement { - return (
    -

    - This page is used to manage your gang members and get an overview of - your gang's stats. -
    -
    - If a gang member is not earning much money or respect, the task that - you have assigned to that member might be too difficult. Consider - training that member's stats or choosing an easier task. The tasks - closer to the top of the dropdown list are generally easier. - Alternatively, the gang member's low production might be due to the - fact that your wanted level is too high. Consider assigning a few - members to the '{props.gang.isHackingGang?"Ethical Hacking":"Vigilante Justice"}' - task to lower your wanted level. -
    -
    - Installing Augmentations does NOT reset your progress with your - Gang. Furthermore, after installing Augmentations, you will - automatically be a member of whatever Faction you created your gang - with. -
    -
    - You can also manage your gang programmatically through Netscript - using the Gang API -

    + return ( +
    +

    + This page is used to manage your gang members and get an overview of + your gang's stats.
    -
    - -

    ); + If a gang member is not earning much money or respect, the task that you + have assigned to that member might be too difficult. Consider training + that member's stats or choosing an easier task. The tasks closer to the + top of the dropdown list are generally easier. Alternatively, the gang + member's low production might be due to the fact that your wanted level + is too high. Consider assigning a few members to the ' + {props.gang.isHackingGang ? "Ethical Hacking" : "Vigilante Justice"}' + task to lower your wanted level. +
    +
    + Installing Augmentations does NOT reset your progress with your Gang. + Furthermore, after installing Augmentations, you will automatically be a + member of whatever Faction you created your gang with. +
    +
    + You can also manage your gang programmatically through Netscript using + the Gang API +

    +
    + +
    + +
    + ); } diff --git a/src/Gang/ui/RecruitButton.tsx b/src/Gang/ui/RecruitButton.tsx index aebcaa700..107820f41 100644 --- a/src/Gang/ui/RecruitButton.tsx +++ b/src/Gang/ui/RecruitButton.tsx @@ -9,42 +9,50 @@ import { formatNumber } from "../../../utils/StringHelperFunctions"; import { createPopup } from "../../ui/React/createPopup"; interface IProps { - gang: Gang; - onRecruit: () => void; + gang: Gang; + onRecruit: () => void; } export function RecruitButton(props: IProps): React.ReactElement { - if (props.gang.members.length >= GangConstants.MaximumGangMembers) { - return (<>); - } + if (props.gang.members.length >= GangConstants.MaximumGangMembers) { + return <>; + } - if (!props.gang.canRecruitMember()) { - const respect = props.gang.getRespectNeededToRecruitMember(); - return (<> - - Recruit Gang Member - -

    - {formatNumber(respect, 2)} respect needed to recruit next member -

    - ); - } - - function onClick(): void { - const popupId = "recruit-gang-member-popup"; - createPopup(popupId, RecruitPopup, { - gang: props.gang, - popupId: popupId, - onRecruit: props.onRecruit, - }); - } - - return (<> - - Recruit Gang Member + if (!props.gang.canRecruitMember()) { + const respect = props.gang.getRespectNeededToRecruitMember(); + return ( + <> + + Recruit Gang Member - ); -} \ No newline at end of file +

    + {formatNumber(respect, 2)} respect needed to recruit next member +

    + + ); + } + + function onClick(): void { + const popupId = "recruit-gang-member-popup"; + createPopup(popupId, RecruitPopup, { + gang: props.gang, + popupId: popupId, + onRecruit: props.onRecruit, + }); + } + + return ( + <> + + Recruit Gang Member + + + ); +} diff --git a/src/Gang/ui/RecruitPopup.tsx b/src/Gang/ui/RecruitPopup.tsx index 28e0dbb18..cac545511 100644 --- a/src/Gang/ui/RecruitPopup.tsx +++ b/src/Gang/ui/RecruitPopup.tsx @@ -7,57 +7,66 @@ import { removePopup } from "../../ui/React/createPopup"; import { dialogBoxCreate } from "../../../utils/DialogBox"; interface IRecruitPopupProps { - gang: Gang; - popupId: string; - onRecruit: () => void; + gang: Gang; + popupId: string; + onRecruit: () => void; } export function RecruitPopup(props: IRecruitPopupProps): React.ReactElement { - const [name, setName] = useState(""); + const [name, setName] = useState(""); - function recruit(): void { - if (name === "") { - dialogBoxCreate("You must enter a name for your Gang member!"); - return; - } - if (!props.gang.canRecruitMember()) { - dialogBoxCreate("You cannot recruit another Gang member!"); - return; - } - - // At this point, the only way this can fail is if you already - // have a gang member with the same name - if (!props.gang.recruitMember(name)) { - dialogBoxCreate("You already have a gang member with this name!"); - return; - } - - props.onRecruit(); - removePopup(props.popupId); + function recruit(): void { + if (name === "") { + dialogBoxCreate("You must enter a name for your Gang member!"); + return; + } + if (!props.gang.canRecruitMember()) { + dialogBoxCreate("You cannot recruit another Gang member!"); + return; } - function cancel(): void { - removePopup(props.popupId); + // At this point, the only way this can fail is if you already + // have a gang member with the same name + if (!props.gang.recruitMember(name)) { + dialogBoxCreate("You already have a gang member with this name!"); + return; } - function onKeyUp(event: React.KeyboardEvent): void { - if(event.keyCode === 13) recruit(); - if(event.keyCode === 27) cancel(); - } + props.onRecruit(); + removePopup(props.popupId); + } - function onChange(event: React.ChangeEvent): void { - setName(event.target.value); - } + function cancel(): void { + removePopup(props.popupId); + } - return (<> -

    Enter a name for your new Gang member:


    - - Recruit Gang Member - Cancel - ); -} \ No newline at end of file + function onKeyUp(event: React.KeyboardEvent): void { + if (event.keyCode === 13) recruit(); + if (event.keyCode === 27) cancel(); + } + + function onChange(event: React.ChangeEvent): void { + setName(event.target.value); + } + + return ( + <> +

    Enter a name for your new Gang member:

    +
    + + + Recruit Gang Member + + + Cancel + + + ); +} diff --git a/src/Gang/ui/Root.tsx b/src/Gang/ui/Root.tsx index 94122856b..334899ad7 100644 --- a/src/Gang/ui/Root.tsx +++ b/src/Gang/ui/Root.tsx @@ -10,40 +10,53 @@ import { Gang } from "../Gang"; import { displayFactionContent } from "../../Faction/FactionHelpers"; interface IProps { - gang: Gang; - player: IPlayer; - engine: IEngine; + gang: Gang; + player: IPlayer; + engine: IEngine; } export function Root(props: IProps): React.ReactElement { - const [management, setManagement] = useState(true); - const setRerender = useState(false)[1]; + const [management, setManagement] = useState(true); + const setRerender = useState(false)[1]; - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); + useEffect(() => { + const id = setInterval(() => setRerender((old) => !old), 1000); + return () => clearInterval(id); + }, []); - function back(): void { - props.engine.loadFactionContent(); - displayFactionContent(props.gang.facName); - } + function back(): void { + props.engine.loadFactionContent(); + displayFactionContent(props.gang.facName); + } - return (<> - Back - setManagement(true)}> - Gang Management - - setManagement(false)}> - Gang Territory - - {management ? - : - } - ); -} \ No newline at end of file + return ( + <> + + Back + + setManagement(true)} + > + Gang Management + + setManagement(false)} + > + Gang Territory + + {management ? ( + + ) : ( + + )} + + ); +} diff --git a/src/Gang/ui/TaskDescription.tsx b/src/Gang/ui/TaskDescription.tsx index f5fd2038d..894c2a25c 100644 --- a/src/Gang/ui/TaskDescription.tsx +++ b/src/Gang/ui/TaskDescription.tsx @@ -7,12 +7,14 @@ import { GangMemberTasks } from "../GangMemberTasks"; import { GangMember } from "../GangMember"; interface IProps { - member: GangMember; + member: GangMember; } export function TaskDescription(props: IProps): React.ReactElement { - const task = GangMemberTasks[props.member.task]; - const desc = task ? task.desc: GangMemberTasks["Unassigned"].desc; + const task = GangMemberTasks[props.member.task]; + const desc = task ? task.desc : GangMemberTasks["Unassigned"].desc; - return (

    ); -} \ No newline at end of file + return ( +

    + ); +} diff --git a/src/Gang/ui/TaskSelector.tsx b/src/Gang/ui/TaskSelector.tsx index c0427aa68..291e3a586 100644 --- a/src/Gang/ui/TaskSelector.tsx +++ b/src/Gang/ui/TaskSelector.tsx @@ -10,38 +10,60 @@ import { Gang } from "../Gang"; import { GangMember } from "../GangMember"; interface IProps { - member: GangMember; - gang: Gang; - onTaskChange: () => void; + member: GangMember; + gang: Gang; + onTaskChange: () => void; } export function TaskSelector(props: IProps): React.ReactElement { - const [currentTask, setCurrentTask] = useState(props.member.task); + const [currentTask, setCurrentTask] = useState(props.member.task); - function onChange(event: React.ChangeEvent): void { - const task = event.target.value; - props.member.assignToTask(task); - setCurrentTask(task); - props.onTaskChange(); - } + function onChange(event: React.ChangeEvent): void { + const task = event.target.value; + props.member.assignToTask(task); + setCurrentTask(task); + props.onTaskChange(); + } - const tasks = props.gang.getAllTaskNames(); + const tasks = props.gang.getAllTaskNames(); - const data = [ - [`Money:`, MoneyRate(5*props.member.calculateMoneyGain(props.gang))], - [`Respect:`, `${numeralWrapper.formatRespect(5*props.member.calculateRespectGain(props.gang))} / sec`], - [`Wanted Level:`, `${numeralWrapper.formatWanted(5*props.member.calculateWantedLevelGain(props.gang))} / sec`], - [`Total Respect:`, `${numeralWrapper.formatRespect(props.member.earnedRespect)}`], - ]; + const data = [ + [`Money:`, MoneyRate(5 * props.member.calculateMoneyGain(props.gang))], + [ + `Respect:`, + `${numeralWrapper.formatRespect( + 5 * props.member.calculateRespectGain(props.gang), + )} / sec`, + ], + [ + `Wanted Level:`, + `${numeralWrapper.formatWanted( + 5 * props.member.calculateWantedLevelGain(props.gang), + )} / sec`, + ], + [ + `Total Respect:`, + `${numeralWrapper.formatRespect(props.member.earnedRespect)}`, + ], + ]; - return (<> - -

    {StatsTable(data)}
    - ); -} \ No newline at end of file + return ( + <> + +
    {StatsTable(data)}
    + + ); +} diff --git a/src/Gang/ui/TerritorySubpage.tsx b/src/Gang/ui/TerritorySubpage.tsx index 106b32423..52b50c76a 100644 --- a/src/Gang/ui/TerritorySubpage.tsx +++ b/src/Gang/ui/TerritorySubpage.tsx @@ -9,122 +9,152 @@ import { AllGangs } from "../AllGangs"; import { Gang } from "../Gang"; interface IProps { - gang: Gang; + gang: Gang; } export function TerritorySubpage(props: IProps): React.ReactElement { - function openWarfareHelp(): void { - dialogBoxCreate("This percentage represents the chance you have of " + - "'clashing' with with another gang. If you do not " + - "wish to gain/lose territory, then keep this " + - "percentage at 0% by not engaging in territory warfare."); - } + function openWarfareHelp(): void { + dialogBoxCreate( + "This percentage represents the chance you have of " + + "'clashing' with with another gang. If you do not " + + "wish to gain/lose territory, then keep this " + + "percentage at 0% by not engaging in territory warfare.", + ); + } - function formatTerritory(n: number): string { - const v = n * 100; - if (v <= 0) { - return formatNumber(0, 2); - } else if (v >= 100) { - return formatNumber(100, 2); - } else { - return formatNumber(v, 2); + function formatTerritory(n: number): string { + const v = n * 100; + if (v <= 0) { + return formatNumber(0, 2); + } else if (v >= 100) { + return formatNumber(100, 2); + } else { + return formatNumber(v, 2); + } + } + + const playerPower = AllGangs[props.gang.facName].power; + function otherGangTerritory(name: string): React.ReactElement { + const power = AllGangs[name].power; + const clashVictoryChance = playerPower / (power + playerPower); + return ( + + {name} +
    + Power: {formatNumber(power, 6)} +
    + Territory: {formatTerritory(AllGangs[name].territory)}%
    + Chance to win clash with this gang:{" "} + {numeralWrapper.formatPercentage(clashVictoryChance, 3)} +
    +
    +
    + ); + } + + const gangNames = Object.keys(AllGangs).filter( + (g) => g != props.gang.facName, + ); + + return ( +
    +

    + This page shows how much territory your Gang controls. This statistic is + listed as a percentage, which represents how much of the total territory + you control. +
    +
    + Every ~20 seconds, your gang has a chance to 'clash' with other gangs. + Your chance to win a clash depends on your gang's power, which is listed + in the display below. Your gang's power slowly accumulates over time. + The accumulation rate is determined by the stats of all Gang members you + have assigned to the 'Territory Warfare' task. Gang members that are not + assigned to this task do not contribute to your gang's power. Your gang + also loses a small amount of power whenever you lose a clash. +
    +
    + NOTE: Gang members assigned to 'Territory Warfare' can be killed during + clashes. This can happen regardless of whether you win or lose the + clash. A gang member being killed results in both respect and power loss + for your gang. +
    +
    + The amount of territory you have affects all aspects of your Gang + members' production, including money, respect, and wanted level. It is + very beneficial to have high territory control. +
    +
    +

    + + (props.gang.territoryWarfareEngaged = event.target.checked) } - } + /> + +
    +

    + Territory Clash Chance:{" "} + {numeralWrapper.formatPercentage(props.gang.territoryClashChance, 3)} +

    +
    + ? +
    +
    - const playerPower = AllGangs[props.gang.facName].power; - function otherGangTerritory(name: string): React.ReactElement { - const power = AllGangs[name].power - const clashVictoryChance = playerPower / (power + playerPower); - return ( - {name}
    - Power: {formatNumber(power, 6)}
    - Territory: {formatTerritory(AllGangs[name].territory)}%
    - Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}
    -
    -
    ); - } - - const gangNames = Object.keys(AllGangs).filter(g => g != props.gang.facName); - - return (
    -

    - This page shows how much territory your Gang controls. This - statistic is listed as a percentage, which represents how much of - the total territory you control. -
    -
    - Every ~20 seconds, your gang has a chance to 'clash' with other - gangs. Your chance to win a clash depends on your gang's power, - which is listed in the display below. Your gang's power slowly - accumulates over time. The accumulation rate is determined by the - stats of all Gang members you have assigned to the 'Territory - Warfare' task. Gang members that are not assigned to this task do - not contribute to your gang's power. Your gang also loses a small - amount of power whenever you lose a clash. -
    -
    - NOTE: Gang members assigned to 'Territory Warfare' can be killed - during clashes. This can happen regardless of whether you win or - lose the clash. A gang member being killed results in both respect - and power loss for your gang. -
    -
    - The amount of territory you have affects all aspects of your Gang - members' production, including money, respect, and wanted level. It - is very beneficial to have high territory control. -
    -
    + + (props.gang.notifyMemberDeath = event.target.checked) + } + /> + +
    +

    +

    + + {props.gang.facName} + +
    + Power: {formatNumber(AllGangs[props.gang.facName].power, 6)} +
    + Territory: {formatTerritory(AllGangs[props.gang.facName].territory)}% +
    +
    + {gangNames.map((name) => otherGangTerritory(name))}

    - props.gang.territoryWarfareEngaged = event.target.checked}/> - -
    -

    - Territory Clash Chance: {numeralWrapper.formatPercentage(props.gang.territoryClashChance, 3)} -

    -
    ?
    -
    - - props.gang.notifyMemberDeath = event.target.checked}/> - -
    -
    -

    - {props.gang.facName}
    - Power: {formatNumber(AllGangs[props.gang.facName].power, 6)}
    - Territory: {formatTerritory(AllGangs[props.gang.facName].territory)}%
    -
    - {gangNames.map(name => otherGangTerritory(name))} -

    -
    -
    ); + +
    + ); } - diff --git a/src/Hacking.ts b/src/Hacking.ts index b45d49ee6..719de8820 100644 --- a/src/Hacking.ts +++ b/src/Hacking.ts @@ -6,84 +6,111 @@ import { Server } from "./Server/Server"; /** * Returns the chance the player has to successfully hack a server */ -export function calculateHackingChance(server: Server, player: IPlayer): number { - const hackFactor = 1.75; - const difficultyMult = (100 - server.hackDifficulty) / 100; - const skillMult = hackFactor * player.hacking_skill; - const skillChance = (skillMult - server.requiredHackingSkill) / skillMult; - const chance = skillChance * difficultyMult * player.hacking_chance_mult * calculateIntelligenceBonus(player.intelligence, 1); - if (chance > 1) { return 1; } - if (chance < 0) { return 0; } +export function calculateHackingChance( + server: Server, + player: IPlayer, +): number { + const hackFactor = 1.75; + const difficultyMult = (100 - server.hackDifficulty) / 100; + const skillMult = hackFactor * player.hacking_skill; + const skillChance = (skillMult - server.requiredHackingSkill) / skillMult; + const chance = + skillChance * + difficultyMult * + player.hacking_chance_mult * + calculateIntelligenceBonus(player.intelligence, 1); + if (chance > 1) { + return 1; + } + if (chance < 0) { + return 0; + } - return chance; + return chance; } /** * Returns the amount of hacking experience the player will gain upon * successfully hacking a server */ -export function calculateHackingExpGain(server: Server, player: IPlayer): number { - const baseExpGain = 3; - const diffFactor = 0.3; - if (server.baseDifficulty == null) { - server.baseDifficulty = server.hackDifficulty; - } - let expGain = baseExpGain; - expGain += (server.baseDifficulty * player.hacking_exp_mult * diffFactor); +export function calculateHackingExpGain( + server: Server, + player: IPlayer, +): number { + const baseExpGain = 3; + const diffFactor = 0.3; + if (server.baseDifficulty == null) { + server.baseDifficulty = server.hackDifficulty; + } + let expGain = baseExpGain; + expGain += server.baseDifficulty * player.hacking_exp_mult * diffFactor; - return expGain * BitNodeMultipliers.HackExpGain; + return expGain * BitNodeMultipliers.HackExpGain; } /** * Returns the percentage of money that will be stolen from a server if * it is successfully hacked (returns the decimal form, not the actual percent value) */ -export function calculatePercentMoneyHacked(server: Server, player: IPlayer): number { - // Adjust if needed for balancing. This is the divisor for the final calculation - const balanceFactor = 240; +export function calculatePercentMoneyHacked( + server: Server, + player: IPlayer, +): number { + // Adjust if needed for balancing. This is the divisor for the final calculation + const balanceFactor = 240; - const difficultyMult = (100 - server.hackDifficulty) / 100; - const skillMult = (player.hacking_skill - (server.requiredHackingSkill - 1)) / player.hacking_skill; - const percentMoneyHacked = difficultyMult * skillMult * player.hacking_money_mult / balanceFactor; - if (percentMoneyHacked < 0) { return 0; } - if (percentMoneyHacked > 1) { return 1; } + const difficultyMult = (100 - server.hackDifficulty) / 100; + const skillMult = + (player.hacking_skill - (server.requiredHackingSkill - 1)) / + player.hacking_skill; + const percentMoneyHacked = + (difficultyMult * skillMult * player.hacking_money_mult) / balanceFactor; + if (percentMoneyHacked < 0) { + return 0; + } + if (percentMoneyHacked > 1) { + return 1; + } - return percentMoneyHacked * BitNodeMultipliers.ScriptHackMoney; + return percentMoneyHacked * BitNodeMultipliers.ScriptHackMoney; } /** * Returns time it takes to complete a hack on a server, in seconds */ export function calculateHackingTime(server: Server, player: IPlayer): number { - const difficultyMult = server.requiredHackingSkill * server.hackDifficulty; + const difficultyMult = server.requiredHackingSkill * server.hackDifficulty; - const baseDiff = 500; - const baseSkill = 50; - const diffFactor = 2.5; - let skillFactor = (diffFactor * difficultyMult + baseDiff); - // tslint:disable-next-line - skillFactor /= (player.hacking_skill + baseSkill); + const baseDiff = 500; + const baseSkill = 50; + const diffFactor = 2.5; + let skillFactor = diffFactor * difficultyMult + baseDiff; + // tslint:disable-next-line + skillFactor /= player.hacking_skill + baseSkill; - const hackTimeMultiplier = 5; - const hackingTime = hackTimeMultiplier * skillFactor / (player.hacking_speed_mult * calculateIntelligenceBonus(player.intelligence, 1)); + const hackTimeMultiplier = 5; + const hackingTime = + (hackTimeMultiplier * skillFactor) / + (player.hacking_speed_mult * + calculateIntelligenceBonus(player.intelligence, 1)); - return hackingTime; + return hackingTime; } /** * Returns time it takes to complete a grow operation on a server, in seconds */ export function calculateGrowTime(server: Server, player: IPlayer): number { - const growTimeMultiplier = 3.2; // Relative to hacking time. 16/5 = 3.2 + const growTimeMultiplier = 3.2; // Relative to hacking time. 16/5 = 3.2 - return growTimeMultiplier * calculateHackingTime(server, player); + return growTimeMultiplier * calculateHackingTime(server, player); } /** * Returns time it takes to complete a weaken operation on a server, in seconds */ export function calculateWeakenTime(server: Server, player: IPlayer): number { - const weakenTimeMultiplier = 4; // Relative to hacking time + const weakenTimeMultiplier = 4; // Relative to hacking time - return weakenTimeMultiplier * calculateHackingTime(server, player); + return weakenTimeMultiplier * calculateHackingTime(server, player); } diff --git a/src/Hacking/netscriptCanHack.ts b/src/Hacking/netscriptCanHack.ts index ee48ace6a..12035232e 100644 --- a/src/Hacking/netscriptCanHack.ts +++ b/src/Hacking/netscriptCanHack.ts @@ -6,48 +6,50 @@ */ import { IReturnStatus } from "../types"; -import { IPlayer } from "../PersonObjects/IPlayer"; -import { Server } from "../Server/Server"; +import { IPlayer } from "../PersonObjects/IPlayer"; +import { Server } from "../Server/Server"; function baseCheck(server: Server, fnName: string): IReturnStatus { - const hostname = server.hostname; + const hostname = server.hostname; - if (!("requiredHackingSkill" in server)) { - return { - res: false, - msg: `Cannot ${fnName} ${hostname} server because it is a Hacknet Node`, - } - } + if (!("requiredHackingSkill" in server)) { + return { + res: false, + msg: `Cannot ${fnName} ${hostname} server because it is a Hacknet Node`, + }; + } - if (server.hasAdminRights === false) { - return { - res: false, - msg: `Cannot ${fnName} ${hostname} server because you do not have root access`, - } - } + if (server.hasAdminRights === false) { + return { + res: false, + msg: `Cannot ${fnName} ${hostname} server because you do not have root access`, + }; + } - return { res: true } + return { res: true }; } export function netscriptCanHack(server: Server, p: IPlayer): IReturnStatus { - const initialCheck = baseCheck(server, "hack"); - if (!initialCheck.res) { return initialCheck; } + const initialCheck = baseCheck(server, "hack"); + if (!initialCheck.res) { + return initialCheck; + } - const s = server; - if (s.requiredHackingSkill > p.hacking_skill) { - return { - res: false, - msg: `Cannot hack ${server.hostname} server because your hacking skill is not high enough`, - } - } + const s = server; + if (s.requiredHackingSkill > p.hacking_skill) { + return { + res: false, + msg: `Cannot hack ${server.hostname} server because your hacking skill is not high enough`, + }; + } - return { res: true } + return { res: true }; } export function netscriptCanGrow(server: Server): IReturnStatus { - return baseCheck(server, "grow"); + return baseCheck(server, "grow"); } export function netscriptCanWeaken(server: Server): IReturnStatus { - return baseCheck(server, "weaken"); + return baseCheck(server, "weaken"); } diff --git a/src/Hacknet/HacknetHelpers.jsx b/src/Hacknet/HacknetHelpers.jsx index 7fb298e48..bf284d534 100644 --- a/src/Hacknet/HacknetHelpers.jsx +++ b/src/Hacknet/HacknetHelpers.jsx @@ -18,9 +18,9 @@ import { HashUpgrades } from "./HashUpgrades"; import { generateRandomContract } from "../CodingContractGenerator"; import { - iTutorialSteps, - iTutorialNextStep, - ITutorial, + iTutorialSteps, + iTutorialNextStep, + ITutorial, } from "../InteractiveTutorial"; import { Player } from "../Player"; import { AllServers } from "../Server/AllServers"; @@ -34,8 +34,8 @@ import { HacknetRoot } from "./ui/Root"; let hacknetNodesDiv; function hacknetNodesInit() { - hacknetNodesDiv = document.getElementById("hacknet-nodes-container"); - document.removeEventListener("DOMContentLoaded", hacknetNodesInit); + hacknetNodesDiv = document.getElementById("hacknet-nodes-container"); + document.removeEventListener("DOMContentLoaded", hacknetNodesInit); } document.addEventListener("DOMContentLoaded", hacknetNodesInit); @@ -43,536 +43,697 @@ document.addEventListener("DOMContentLoaded", hacknetNodesInit); // Returns a boolean indicating whether the player has Hacknet Servers // (the upgraded form of Hacknet Nodes) export function hasHacknetServers() { - return (Player.bitNodeN === 9 || SourceFileFlags[9] > 0); + return Player.bitNodeN === 9 || SourceFileFlags[9] > 0; } export function purchaseHacknet() { - /* INTERACTIVE TUTORIAL */ - if (ITutorial.isRunning) { - if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) { - iTutorialNextStep(); - } else { - return; - } - } - /* END INTERACTIVE TUTORIAL */ - - const numOwned = Player.hacknetNodes.length; - if (hasHacknetServers()) { - const cost = getCostOfNextHacknetServer(); - if (isNaN(cost)) { - throw new Error(`Calculated cost of purchasing HacknetServer is NaN`) - } - - if (!Player.canAfford(cost)) { return -1; } - Player.loseMoney(cost); - Player.createHacknetServer(); - updateHashManagerCapacity(); - - return numOwned; + /* INTERACTIVE TUTORIAL */ + if (ITutorial.isRunning) { + if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) { + iTutorialNextStep(); } else { - const cost = getCostOfNextHacknetNode(); - if (isNaN(cost)) { - throw new Error(`Calculated cost of purchasing HacknetNode is NaN`); - } - - if (!Player.canAfford(cost)) { return -1; } - - // Auto generate a name for the Node - const name = "hacknet-node-" + numOwned; - const node = new HacknetNode(name, Player.hacknet_node_money_mult); - - Player.loseMoney(cost); - Player.hacknetNodes.push(node); - - return numOwned; + return; } + } + /* END INTERACTIVE TUTORIAL */ + + const numOwned = Player.hacknetNodes.length; + if (hasHacknetServers()) { + const cost = getCostOfNextHacknetServer(); + if (isNaN(cost)) { + throw new Error(`Calculated cost of purchasing HacknetServer is NaN`); + } + + if (!Player.canAfford(cost)) { + return -1; + } + Player.loseMoney(cost); + Player.createHacknetServer(); + updateHashManagerCapacity(); + + return numOwned; + } else { + const cost = getCostOfNextHacknetNode(); + if (isNaN(cost)) { + throw new Error(`Calculated cost of purchasing HacknetNode is NaN`); + } + + if (!Player.canAfford(cost)) { + return -1; + } + + // Auto generate a name for the Node + const name = "hacknet-node-" + numOwned; + const node = new HacknetNode(name, Player.hacknet_node_money_mult); + + Player.loseMoney(cost); + Player.hacknetNodes.push(node); + + return numOwned; + } } export function hasMaxNumberHacknetServers() { - return hasHacknetServers() && Player.hacknetNodes.length >= HacknetServerConstants.MaxServers; + return ( + hasHacknetServers() && + Player.hacknetNodes.length >= HacknetServerConstants.MaxServers + ); } export function getCostOfNextHacknetNode() { - return calculateNodeCost(Player.hacknetNodes.length+1, Player.hacknet_node_purchase_cost_mult); + return calculateNodeCost( + Player.hacknetNodes.length + 1, + Player.hacknet_node_purchase_cost_mult, + ); } export function getCostOfNextHacknetServer() { - return calculateServerCost(Player.hacknetNodes.length+1, Player.hacknet_node_purchase_cost_mult); + return calculateServerCost( + Player.hacknetNodes.length + 1, + Player.hacknet_node_purchase_cost_mult, + ); } // Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's level export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) { - if (maxLevel == null) { - throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`); - } + if (maxLevel == null) { + throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`); + } - if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1, Player.hacknet_node_level_cost_mult))) { - return 0; - } - - let min = 1; - let max = maxLevel - 1; - let levelsToMax = maxLevel - nodeObj.level; - if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, Player.hacknet_node_level_cost_mult))) { - return levelsToMax; - } - - while (min <= max) { - var curr = (min + max) / 2 | 0; - if (curr !== maxLevel && - Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult)) && - Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player.hacknet_node_level_cost_mult))) { - return Math.min(levelsToMax, curr); - } else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult))) { - max = curr - 1; - } else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult))) { - min = curr + 1; - } else { - return Math.min(levelsToMax, curr); - } - } + if ( + Player.money.lt( + nodeObj.calculateLevelUpgradeCost(1, Player.hacknet_node_level_cost_mult), + ) + ) { return 0; + } + + let min = 1; + let max = maxLevel - 1; + let levelsToMax = maxLevel - nodeObj.level; + if ( + Player.money.gt( + nodeObj.calculateLevelUpgradeCost( + levelsToMax, + Player.hacknet_node_level_cost_mult, + ), + ) + ) { + return levelsToMax; + } + + while (min <= max) { + var curr = ((min + max) / 2) | 0; + if ( + curr !== maxLevel && + Player.money.gt( + nodeObj.calculateLevelUpgradeCost( + curr, + Player.hacknet_node_level_cost_mult, + ), + ) && + Player.money.lt( + nodeObj.calculateLevelUpgradeCost( + curr + 1, + Player.hacknet_node_level_cost_mult, + ), + ) + ) { + return Math.min(levelsToMax, curr); + } else if ( + Player.money.lt( + nodeObj.calculateLevelUpgradeCost( + curr, + Player.hacknet_node_level_cost_mult, + ), + ) + ) { + max = curr - 1; + } else if ( + Player.money.gt( + nodeObj.calculateLevelUpgradeCost( + curr, + Player.hacknet_node_level_cost_mult, + ), + ) + ) { + min = curr + 1; + } else { + return Math.min(levelsToMax, curr); + } + } + return 0; } // Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's RAM export function getMaxNumberRamUpgrades(nodeObj, maxLevel) { - if (maxLevel == null) { - throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`); - } + if (maxLevel == null) { + throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`); + } - if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1, Player.hacknet_node_ram_cost_mult))) { - return 0; - } - - let levelsToMax; - if (nodeObj instanceof HacknetServer) { - levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.maxRam)); - } else { - levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram)); - } - if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, Player.hacknet_node_ram_cost_mult))) { - return levelsToMax; - } - - //We'll just loop until we find the max - for (let i = levelsToMax-1; i >= 0; --i) { - if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i, Player.hacknet_node_ram_cost_mult))) { - return i; - } - } + if ( + Player.money.lt( + nodeObj.calculateRamUpgradeCost(1, Player.hacknet_node_ram_cost_mult), + ) + ) { return 0; + } + + let levelsToMax; + if (nodeObj instanceof HacknetServer) { + levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.maxRam)); + } else { + levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram)); + } + if ( + Player.money.gt( + nodeObj.calculateRamUpgradeCost( + levelsToMax, + Player.hacknet_node_ram_cost_mult, + ), + ) + ) { + return levelsToMax; + } + + //We'll just loop until we find the max + for (let i = levelsToMax - 1; i >= 0; --i) { + if ( + Player.money.gt( + nodeObj.calculateRamUpgradeCost(i, Player.hacknet_node_ram_cost_mult), + ) + ) { + return i; + } + } + return 0; } // Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cores export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) { - if (maxLevel == null) { - throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`); - } - - if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1, Player.hacknet_node_core_cost_mult))) { - return 0; - } - - let min = 1; - let max = maxLevel - 1; - const levelsToMax = maxLevel - nodeObj.cores; - if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, Player.hacknet_node_core_cost_mult))) { - return levelsToMax; - } - - // Use a binary search to find the max possible number of upgrades - while (min <= max) { - let curr = (min + max) / 2 | 0; - if (curr != maxLevel && - Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult)) && - Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player.hacknet_node_core_cost_mult))) { - return Math.min(levelsToMax, curr); - } else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult))) { - max = curr - 1; - } else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult))) { - min = curr + 1; - } else { - return Math.min(levelsToMax, curr); - } - } + if (maxLevel == null) { + throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`); + } + if ( + Player.money.lt( + nodeObj.calculateCoreUpgradeCost(1, Player.hacknet_node_core_cost_mult), + ) + ) { return 0; + } + + let min = 1; + let max = maxLevel - 1; + const levelsToMax = maxLevel - nodeObj.cores; + if ( + Player.money.gt( + nodeObj.calculateCoreUpgradeCost( + levelsToMax, + Player.hacknet_node_core_cost_mult, + ), + ) + ) { + return levelsToMax; + } + + // Use a binary search to find the max possible number of upgrades + while (min <= max) { + let curr = ((min + max) / 2) | 0; + if ( + curr != maxLevel && + Player.money.gt( + nodeObj.calculateCoreUpgradeCost( + curr, + Player.hacknet_node_core_cost_mult, + ), + ) && + Player.money.lt( + nodeObj.calculateCoreUpgradeCost( + curr + 1, + Player.hacknet_node_core_cost_mult, + ), + ) + ) { + return Math.min(levelsToMax, curr); + } else if ( + Player.money.lt( + nodeObj.calculateCoreUpgradeCost( + curr, + Player.hacknet_node_core_cost_mult, + ), + ) + ) { + max = curr - 1; + } else if ( + Player.money.gt( + nodeObj.calculateCoreUpgradeCost( + curr, + Player.hacknet_node_core_cost_mult, + ), + ) + ) { + min = curr + 1; + } else { + return Math.min(levelsToMax, curr); + } + } + + return 0; } // Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cache export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) { - if (maxLevel == null) { - throw new Error(`getMaxNumberCacheUpgrades() called without maxLevel arg`); - } - - if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(1))) { - return 0; - } - - let min = 1; - let max = maxLevel - 1; - const levelsToMax = maxLevel - nodeObj.cache; - if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(levelsToMax))) { - return levelsToMax; - } - - // Use a binary search to find the max possible number of upgrades - while (min <= max) { - let curr = (min + max) / 2 | 0; - if (curr != maxLevel && - Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr)) && - !Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr + 1))) { - return Math.min(levelsToMax, curr); - } else if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) { - max = curr -1 ; - } else if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) { - min = curr + 1; - } else { - return Math.min(levelsToMax, curr); - } - } + if (maxLevel == null) { + throw new Error(`getMaxNumberCacheUpgrades() called without maxLevel arg`); + } + if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(1))) { return 0; -} + } -export function purchaseLevelUpgrade(node, levels=1) { - const sanitizedLevels = Math.round(levels); - const cost = node.calculateLevelUpgradeCost(sanitizedLevels, Player.hacknet_node_level_cost_mult); - if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { - return false; - } + let min = 1; + let max = maxLevel - 1; + const levelsToMax = maxLevel - nodeObj.cache; + if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(levelsToMax))) { + return levelsToMax; + } - const isServer = (node instanceof HacknetServer); - - // If we're at max level, return false - if (node.level >= (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel)) { - return false; - } - - // If the number of specified upgrades would exceed the max level, calculate - // the maximum number of upgrades and use that - if (node.level + sanitizedLevels > (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel)) { - const diff = Math.max(0, (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel) - node.level); - return purchaseLevelUpgrade(node, diff); - } - - if (!Player.canAfford(cost)) { - return false; - } - - Player.loseMoney(cost); - node.upgradeLevel(sanitizedLevels, Player.hacknet_node_money_mult); - - return true; -} - -export function purchaseRamUpgrade(node, levels=1) { - const sanitizedLevels = Math.round(levels); - const cost = node.calculateRamUpgradeCost(sanitizedLevels, Player.hacknet_node_ram_cost_mult); - if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { - return false; - } - - const isServer = (node instanceof HacknetServer); - - // Fail if we're already at max - if (node.ram >= (isServer ? HacknetServerConstants.MaxRam : HacknetNodeConstants.MaxRam)) { - return false; - } - - // If the number of specified upgrades would exceed the max RAM, calculate the - // max possible number of upgrades and use that - if (isServer) { - if (node.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerConstants.MaxRam) { - const diff = Math.max(0, Math.log2(Math.round(HacknetServerConstants.MaxRam / node.maxRam))); - return purchaseRamUpgrade(node, diff); - } + // Use a binary search to find the max possible number of upgrades + while (min <= max) { + let curr = ((min + max) / 2) | 0; + if ( + curr != maxLevel && + Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr)) && + !Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr + 1)) + ) { + return Math.min(levelsToMax, curr); + } else if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) { + max = curr - 1; + } else if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) { + min = curr + 1; } else { - if (node.ram * Math.pow(2, sanitizedLevels) > HacknetNodeConstants.MaxRam) { - const diff = Math.max(0, Math.log2(Math.round(HacknetNodeConstants.MaxRam / node.ram))); - return purchaseRamUpgrade(node, diff); - } + return Math.min(levelsToMax, curr); } + } - - if (!Player.canAfford(cost)) { - return false; - } - - Player.loseMoney(cost); - node.upgradeRam(sanitizedLevels, Player.hacknet_node_money_mult); - - return true; + return 0; } -export function purchaseCoreUpgrade(node, levels=1) { - const sanitizedLevels = Math.round(levels); - const cost = node.calculateCoreUpgradeCost(sanitizedLevels, Player.hacknet_node_core_cost_mult); - if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { - return false; - } +export function purchaseLevelUpgrade(node, levels = 1) { + const sanitizedLevels = Math.round(levels); + const cost = node.calculateLevelUpgradeCost( + sanitizedLevels, + Player.hacknet_node_level_cost_mult, + ); + if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { + return false; + } - const isServer = (node instanceof HacknetServer); + const isServer = node instanceof HacknetServer; - // Fail if we're already at max - if (node.cores >= (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores)) { - return false; - } + // If we're at max level, return false + if ( + node.level >= + (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel) + ) { + return false; + } - // If the specified number of upgrades would exceed the max Cores, calculate - // the max possible number of upgrades and use that - if (node.cores + sanitizedLevels > (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores)) { - const diff = Math.max(0, (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores) - node.cores); - return purchaseCoreUpgrade(node, diff); - } + // If the number of specified upgrades would exceed the max level, calculate + // the maximum number of upgrades and use that + if ( + node.level + sanitizedLevels > + (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel) + ) { + const diff = Math.max( + 0, + (isServer + ? HacknetServerConstants.MaxLevel + : HacknetNodeConstants.MaxLevel) - node.level, + ); + return purchaseLevelUpgrade(node, diff); + } - if (!Player.canAfford(cost)) { - return false; - } + if (!Player.canAfford(cost)) { + return false; + } - Player.loseMoney(cost); - node.upgradeCore(sanitizedLevels, Player.hacknet_node_money_mult); + Player.loseMoney(cost); + node.upgradeLevel(sanitizedLevels, Player.hacknet_node_money_mult); - return true; + return true; } -export function purchaseCacheUpgrade(node, levels=1) { - const sanitizedLevels = Math.round(levels); - const cost = node.calculateCacheUpgradeCost(sanitizedLevels); - if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { - return false; +export function purchaseRamUpgrade(node, levels = 1) { + const sanitizedLevels = Math.round(levels); + const cost = node.calculateRamUpgradeCost( + sanitizedLevels, + Player.hacknet_node_ram_cost_mult, + ); + if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { + return false; + } + + const isServer = node instanceof HacknetServer; + + // Fail if we're already at max + if ( + node.ram >= + (isServer ? HacknetServerConstants.MaxRam : HacknetNodeConstants.MaxRam) + ) { + return false; + } + + // If the number of specified upgrades would exceed the max RAM, calculate the + // max possible number of upgrades and use that + if (isServer) { + if ( + node.maxRam * Math.pow(2, sanitizedLevels) > + HacknetServerConstants.MaxRam + ) { + const diff = Math.max( + 0, + Math.log2(Math.round(HacknetServerConstants.MaxRam / node.maxRam)), + ); + return purchaseRamUpgrade(node, diff); } - - if (!(node instanceof HacknetServer)) { - console.warn(`purchaseCacheUpgrade() called for a non-HacknetNode`); - return false; + } else { + if (node.ram * Math.pow(2, sanitizedLevels) > HacknetNodeConstants.MaxRam) { + const diff = Math.max( + 0, + Math.log2(Math.round(HacknetNodeConstants.MaxRam / node.ram)), + ); + return purchaseRamUpgrade(node, diff); } + } - // Fail if we're already at max - if (node.cache + sanitizedLevels > HacknetServerConstants.MaxCache) { - const diff = Math.max(0, HacknetServerConstants.MaxCache - node.cache); - return purchaseCacheUpgrade(node, diff); - } + if (!Player.canAfford(cost)) { + return false; + } - if (!Player.canAfford(cost)) { - return false; - } + Player.loseMoney(cost); + node.upgradeRam(sanitizedLevels, Player.hacknet_node_money_mult); - Player.loseMoney(cost); - node.upgradeCache(sanitizedLevels); + return true; +} - return true; +export function purchaseCoreUpgrade(node, levels = 1) { + const sanitizedLevels = Math.round(levels); + const cost = node.calculateCoreUpgradeCost( + sanitizedLevels, + Player.hacknet_node_core_cost_mult, + ); + if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { + return false; + } + + const isServer = node instanceof HacknetServer; + + // Fail if we're already at max + if ( + node.cores >= + (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores) + ) { + return false; + } + + // If the specified number of upgrades would exceed the max Cores, calculate + // the max possible number of upgrades and use that + if ( + node.cores + sanitizedLevels > + (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores) + ) { + const diff = Math.max( + 0, + (isServer + ? HacknetServerConstants.MaxCores + : HacknetNodeConstants.MaxCores) - node.cores, + ); + return purchaseCoreUpgrade(node, diff); + } + + if (!Player.canAfford(cost)) { + return false; + } + + Player.loseMoney(cost); + node.upgradeCore(sanitizedLevels, Player.hacknet_node_money_mult); + + return true; +} + +export function purchaseCacheUpgrade(node, levels = 1) { + const sanitizedLevels = Math.round(levels); + const cost = node.calculateCacheUpgradeCost(sanitizedLevels); + if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { + return false; + } + + if (!(node instanceof HacknetServer)) { + console.warn(`purchaseCacheUpgrade() called for a non-HacknetNode`); + return false; + } + + // Fail if we're already at max + if (node.cache + sanitizedLevels > HacknetServerConstants.MaxCache) { + const diff = Math.max(0, HacknetServerConstants.MaxCache - node.cache); + return purchaseCacheUpgrade(node, diff); + } + + if (!Player.canAfford(cost)) { + return false; + } + + Player.loseMoney(cost); + node.upgradeCache(sanitizedLevels); + + return true; } // Create/Refresh Hacknet Nodes UI export function renderHacknetNodesUI() { - if (!routing.isOn(Page.HacknetNodes)) { return; } + if (!routing.isOn(Page.HacknetNodes)) { + return; + } - ReactDOM.render(, hacknetNodesDiv); + ReactDOM.render(, hacknetNodesDiv); } export function clearHacknetNodesUI() { - if (hacknetNodesDiv instanceof HTMLElement) { - ReactDOM.unmountComponentAtNode(hacknetNodesDiv); - } + if (hacknetNodesDiv instanceof HTMLElement) { + ReactDOM.unmountComponentAtNode(hacknetNodesDiv); + } - hacknetNodesDiv.style.display = "none"; + hacknetNodesDiv.style.display = "none"; } export function processHacknetEarnings(numCycles) { - // Determine if player has Hacknet Nodes or Hacknet Servers, then - // call the appropriate function - if (Player.hacknetNodes.length === 0) { return 0; } - if (hasHacknetServers()) { - return processAllHacknetServerEarnings(numCycles); - } else if (Player.hacknetNodes[0] instanceof HacknetNode) { - return processAllHacknetNodeEarnings(numCycles); - } else { - return 0; - } + // Determine if player has Hacknet Nodes or Hacknet Servers, then + // call the appropriate function + if (Player.hacknetNodes.length === 0) { + return 0; + } + if (hasHacknetServers()) { + return processAllHacknetServerEarnings(numCycles); + } else if (Player.hacknetNodes[0] instanceof HacknetNode) { + return processAllHacknetNodeEarnings(numCycles); + } else { + return 0; + } } function processAllHacknetNodeEarnings(numCycles) { - let total = 0; - for (let i = 0; i < Player.hacknetNodes.length; ++i) { - total += processSingleHacknetNodeEarnings(numCycles, Player.hacknetNodes[i]); - } + let total = 0; + for (let i = 0; i < Player.hacknetNodes.length; ++i) { + total += processSingleHacknetNodeEarnings( + numCycles, + Player.hacknetNodes[i], + ); + } - return total; + return total; } function processSingleHacknetNodeEarnings(numCycles, nodeObj) { - const totalEarnings = nodeObj.process(numCycles); - Player.gainMoney(totalEarnings); - Player.recordMoneySource(totalEarnings, "hacknetnode"); + const totalEarnings = nodeObj.process(numCycles); + Player.gainMoney(totalEarnings); + Player.recordMoneySource(totalEarnings, "hacknetnode"); - return totalEarnings; + return totalEarnings; } function processAllHacknetServerEarnings(numCycles) { - if (!(Player.hashManager instanceof HashManager)) { - throw new Error(`Player does not have a HashManager (should be in 'hashManager' prop)`) - } + if (!(Player.hashManager instanceof HashManager)) { + throw new Error( + `Player does not have a HashManager (should be in 'hashManager' prop)`, + ); + } - let hashes = 0; - for (let i = 0; i < Player.hacknetNodes.length; ++i) { - // hacknetNodes array only contains the IP addresses of the servers. - // Also, update the hash rate before processing - const hserver = AllServers[Player.hacknetNodes[i]]; - hserver.updateHashRate(Player.hacknet_node_money_mult); - const h = hserver.process(numCycles); - hserver.totalHashesGenerated += h; - hashes += h; - } + let hashes = 0; + for (let i = 0; i < Player.hacknetNodes.length; ++i) { + // hacknetNodes array only contains the IP addresses of the servers. + // Also, update the hash rate before processing + const hserver = AllServers[Player.hacknetNodes[i]]; + hserver.updateHashRate(Player.hacknet_node_money_mult); + const h = hserver.process(numCycles); + hserver.totalHashesGenerated += h; + hashes += h; + } - Player.hashManager.storeHashes(hashes); + Player.hashManager.storeHashes(hashes); - return hashes; + return hashes; } export function updateHashManagerCapacity() { - if (!(Player.hashManager instanceof HashManager)) { - console.error(`Player does not have a HashManager`); - return; + if (!(Player.hashManager instanceof HashManager)) { + console.error(`Player does not have a HashManager`); + return; + } + + const nodes = Player.hacknetNodes; + if (nodes.length === 0) { + Player.hashManager.updateCapacity(0); + return; + } + + let total = 0; + for (let i = 0; i < nodes.length; ++i) { + if (typeof nodes[i] !== "string") { + Player.hashManager.updateCapacity(0); + return; } - const nodes = Player.hacknetNodes; - if (nodes.length === 0) { - Player.hashManager.updateCapacity(0); - return; + const h = AllServers[nodes[i]]; + if (!(h instanceof HacknetServer)) { + Player.hashManager.updateCapacity(0); + return; } - let total = 0; - for (let i = 0; i < nodes.length; ++i) { - if (typeof nodes[i] !== "string") { - Player.hashManager.updateCapacity(0); - return; - } + total += h.hashCapacity; + } - const h = AllServers[nodes[i]]; - if (!(h instanceof HacknetServer)) { - Player.hashManager.updateCapacity(0); - return; - } - - total += h.hashCapacity; - } - - Player.hashManager.updateCapacity(total); + Player.hashManager.updateCapacity(total); } export function purchaseHashUpgrade(upgName, upgTarget) { - if (!(Player.hashManager instanceof HashManager)) { - console.error(`Player does not have a HashManager`); + if (!(Player.hashManager instanceof HashManager)) { + console.error(`Player does not have a HashManager`); + return false; + } + + // HashManager handles the transaction. This just needs to actually implement + // the effects of the upgrade + if (Player.hashManager.upgrade(upgName)) { + const upg = HashUpgrades[upgName]; + + switch (upgName) { + case "Sell for Money": { + Player.gainMoney(upg.value); + Player.recordMoneySource(upg.value, "hacknetnode"); + break; + } + case "Sell for Corporation Funds": { + // This will throw if player doesn't have a corporation + try { + Player.corporation.funds = Player.corporation.funds.plus(upg.value); + } catch (e) { + Player.hashManager.refundUpgrade(upgName); + return false; + } + break; + } + case "Reduce Minimum Security": { + try { + const target = GetServerByHostname(upgTarget); + if (target == null) { + console.error( + `Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`, + ); + return false; + } + + target.changeMinimumSecurity(upg.value, true); + } catch (e) { + Player.hashManager.refundUpgrade(upgName); + return false; + } + break; + } + case "Increase Maximum Money": { + try { + const target = GetServerByHostname(upgTarget); + if (target == null) { + console.error( + `Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`, + ); + return false; + } + + target.changeMaximumMoney(upg.value, true); + } catch (e) { + Player.hashManager.refundUpgrade(upgName); + return false; + } + break; + } + case "Improve Studying": { + // Multiplier handled by HashManager + break; + } + case "Improve Gym Training": { + // Multiplier handled by HashManager + break; + } + case "Exchange for Corporation Research": { + // This will throw if player doesn't have a corporation + try { + for (const division of Player.corporation.divisions) { + division.sciResearch.qty += upg.value; + } + } catch (e) { + Player.hashManager.refundUpgrade(upgName); + return false; + } + break; + } + case "Exchange for Bladeburner Rank": { + // This will throw if player isnt in Bladeburner + try { + Player.bladeburner.changeRank(Player, upg.value); + } catch (e) { + Player.hashManager.refundUpgrade(upgName); + return false; + } + break; + } + case "Exchange for Bladeburner SP": { + // This will throw if player isn't in Bladeburner + try { + // As long as we don't change `Bladeburner.totalSkillPoints`, this + // shouldn't affect anything else + Player.bladeburner.skillPoints += upg.value; + } catch (e) { + Player.hashManager.refundUpgrade(upgName); + return false; + } + break; + } + case "Generate Coding Contract": { + generateRandomContract(); + break; + } + default: + console.warn( + `Unrecognized upgrade name ${upgName}. Upgrade has no effect`, + ); + Player.hashManager.refundUpgrade(upgName); return false; } - // HashManager handles the transaction. This just needs to actually implement - // the effects of the upgrade - if (Player.hashManager.upgrade(upgName)) { - const upg = HashUpgrades[upgName]; + return true; + } - switch (upgName) { - case "Sell for Money": { - Player.gainMoney(upg.value); - Player.recordMoneySource(upg.value, "hacknetnode"); - break; - } - case "Sell for Corporation Funds": { - // This will throw if player doesn't have a corporation - try { - Player.corporation.funds = Player.corporation.funds.plus(upg.value); - } catch(e) { - Player.hashManager.refundUpgrade(upgName); - return false; - } - break; - } - case "Reduce Minimum Security": { - try { - const target = GetServerByHostname(upgTarget); - if (target == null) { - console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`); - return false; - } - - target.changeMinimumSecurity(upg.value, true); - } catch(e) { - Player.hashManager.refundUpgrade(upgName); - return false; - } - break; - } - case "Increase Maximum Money": { - try { - const target = GetServerByHostname(upgTarget); - if (target == null) { - console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`); - return false; - } - - target.changeMaximumMoney(upg.value, true); - } catch(e) { - Player.hashManager.refundUpgrade(upgName); - return false; - } - break; - } - case "Improve Studying": { - // Multiplier handled by HashManager - break; - } - case "Improve Gym Training": { - // Multiplier handled by HashManager - break; - } - case "Exchange for Corporation Research": { - // This will throw if player doesn't have a corporation - try { - for (const division of Player.corporation.divisions) { - division.sciResearch.qty += upg.value; - } - } catch(e) { - Player.hashManager.refundUpgrade(upgName); - return false; - } - break; - } - case "Exchange for Bladeburner Rank": { - // This will throw if player isnt in Bladeburner - try { - Player.bladeburner.changeRank(Player, upg.value); - } catch(e) { - Player.hashManager.refundUpgrade(upgName); - return false; - } - break; - } - case "Exchange for Bladeburner SP": { - // This will throw if player isn't in Bladeburner - try { - // As long as we don't change `Bladeburner.totalSkillPoints`, this - // shouldn't affect anything else - Player.bladeburner.skillPoints += upg.value; - } catch(e) { - Player.hashManager.refundUpgrade(upgName); - return false; - } - break; - } - case "Generate Coding Contract": { - generateRandomContract(); - break; - } - default: - console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`) - Player.hashManager.refundUpgrade(upgName); - return false; - } - - return true; - } - - return false; + return false; } diff --git a/src/Hacknet/HacknetNode.ts b/src/Hacknet/HacknetNode.ts index 6a356ef41..948d752af 100644 --- a/src/Hacknet/HacknetNode.ts +++ b/src/Hacknet/HacknetNode.ts @@ -9,125 +9,140 @@ import { IHacknetNode } from "./IHacknetNode"; import { CONSTANTS } from "../Constants"; import { - calculateMoneyGainRate, - calculateLevelUpgradeCost, - calculateCoreUpgradeCost, - calculateRamUpgradeCost, + calculateMoneyGainRate, + calculateLevelUpgradeCost, + calculateCoreUpgradeCost, + calculateRamUpgradeCost, } from "./formulas/HacknetNodes"; import { HacknetNodeConstants } from "./data/Constants"; import { dialogBoxCreate } from "../../utils/DialogBox"; -import { Generic_fromJSON, - Generic_toJSON, - Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; export class HacknetNode implements IHacknetNode { + // Node's number of cores + cores = 1; - // Node's number of cores - cores = 1; + // Node's Level + level = 1; - // Node's Level - level = 1; + // Node's production per second + moneyGainRatePerSecond = 0; - // Node's production per second - moneyGainRatePerSecond = 0; + // Identifier for Node. Includes the full "name" (hacknet-node-N) + name: string; - // Identifier for Node. Includes the full "name" (hacknet-node-N) - name: string; + // How long this Node has existed, in seconds + onlineTimeSeconds = 0; - // How long this Node has existed, in seconds - onlineTimeSeconds = 0; + // Node's RAM (GB) + ram = 1; - // Node's RAM (GB) - ram = 1; + // Total money earned by this Node + totalMoneyGenerated = 0; - // Total money earned by this Node - totalMoneyGenerated = 0; + constructor(name = "", prodMult = 1) { + this.name = name; - constructor(name="", prodMult=1) { - this.name = name; + this.updateMoneyGainRate(prodMult); + } - this.updateMoneyGainRate(prodMult); + // Get the cost to upgrade this Node's number of cores + calculateCoreUpgradeCost(levels = 1, costMult: number): number { + return calculateCoreUpgradeCost(this.cores, levels, costMult); + } + + // Get the cost to upgrade this Node's level + calculateLevelUpgradeCost(levels = 1, costMult: number): number { + return calculateLevelUpgradeCost(this.level, levels, costMult); + } + + // Get the cost to upgrade this Node's RAM + calculateRamUpgradeCost(levels = 1, costMult: number): number { + return calculateRamUpgradeCost(this.ram, levels, costMult); + } + + // Process this Hacknet Node in the game loop. + // Returns the amount of money generated + process(numCycles = 1): number { + const seconds = (numCycles * CONSTANTS.MilliPerCycle) / 1000; + let gain = this.moneyGainRatePerSecond * seconds; + if (isNaN(gain)) { + console.error(`Hacknet Node ${this.name} calculated earnings of NaN`); + gain = 0; } - // Get the cost to upgrade this Node's number of cores - calculateCoreUpgradeCost(levels=1, costMult: number): number { - return calculateCoreUpgradeCost(this.cores, levels, costMult); + this.totalMoneyGenerated += gain; + this.onlineTimeSeconds += seconds; + + return gain; + } + + // Upgrade this Node's number of cores, if possible + // Returns a boolean indicating whether new cores were successfully bought + upgradeCore(levels = 1, prodMult: number): void { + this.cores = Math.min( + HacknetNodeConstants.MaxCores, + Math.round(this.cores + levels), + ); + this.updateMoneyGainRate(prodMult); + } + + // Upgrade this Node's level, if possible + // Returns a boolean indicating whether the level was successfully updated + upgradeLevel(levels = 1, prodMult: number): void { + this.level = Math.min( + HacknetNodeConstants.MaxLevel, + Math.round(this.level + levels), + ); + this.updateMoneyGainRate(prodMult); + } + + // Upgrade this Node's RAM, if possible + // Returns a boolean indicating whether the RAM was successfully upgraded + upgradeRam(levels = 1, prodMult: number): void { + for (let i = 0; i < levels; ++i) { + this.ram *= 2; // Ram is always doubled } + this.ram = Math.round(this.ram); // Handle any floating point precision issues + this.updateMoneyGainRate(prodMult); + } - // Get the cost to upgrade this Node's level - calculateLevelUpgradeCost(levels=1, costMult: number): number { - return calculateLevelUpgradeCost(this.level, levels, costMult); + // Re-calculate this Node's production and update the moneyGainRatePerSecond prop + updateMoneyGainRate(prodMult: number): void { + this.moneyGainRatePerSecond = calculateMoneyGainRate( + this.level, + this.ram, + this.cores, + prodMult, + ); + if (isNaN(this.moneyGainRatePerSecond)) { + this.moneyGainRatePerSecond = 0; + dialogBoxCreate( + "Error in calculating Hacknet Node production. Please report to game developer", + false, + ); } + } - // Get the cost to upgrade this Node's RAM - calculateRamUpgradeCost(levels=1, costMult: number): number { - return calculateRamUpgradeCost(this.ram, levels, costMult); - } + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("HacknetNode", this); + } - // Process this Hacknet Node in the game loop. - // Returns the amount of money generated - process(numCycles=1): number { - const seconds = numCycles * CONSTANTS.MilliPerCycle / 1000; - let gain = this.moneyGainRatePerSecond * seconds; - if (isNaN(gain)) { - console.error(`Hacknet Node ${this.name} calculated earnings of NaN`); - gain = 0; - } - - this.totalMoneyGenerated += gain; - this.onlineTimeSeconds += seconds; - - return gain; - } - - // Upgrade this Node's number of cores, if possible - // Returns a boolean indicating whether new cores were successfully bought - upgradeCore(levels=1, prodMult: number): void { - this.cores = Math.min(HacknetNodeConstants.MaxCores, Math.round(this.cores + levels)); - this.updateMoneyGainRate(prodMult); - } - - // Upgrade this Node's level, if possible - // Returns a boolean indicating whether the level was successfully updated - upgradeLevel(levels=1, prodMult: number): void { - this.level = Math.min(HacknetNodeConstants.MaxLevel, Math.round(this.level + levels)); - this.updateMoneyGainRate(prodMult); - } - - // Upgrade this Node's RAM, if possible - // Returns a boolean indicating whether the RAM was successfully upgraded - upgradeRam(levels=1, prodMult: number): void { - for (let i = 0; i < levels; ++i) { - this.ram *= 2; // Ram is always doubled - } - this.ram = Math.round(this.ram); // Handle any floating point precision issues - this.updateMoneyGainRate(prodMult); - } - - // Re-calculate this Node's production and update the moneyGainRatePerSecond prop - updateMoneyGainRate(prodMult: number): void { - this.moneyGainRatePerSecond = calculateMoneyGainRate(this.level, this.ram, this.cores, prodMult); - if (isNaN(this.moneyGainRatePerSecond)) { - this.moneyGainRatePerSecond = 0; - dialogBoxCreate("Error in calculating Hacknet Node production. Please report to game developer", false); - } - } - - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("HacknetNode", this); - } - - /** - * Initiatizes a HacknetNode object from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): HacknetNode { - return Generic_fromJSON(HacknetNode, value.data); - } + /** + * Initiatizes a HacknetNode object from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): HacknetNode { + return Generic_fromJSON(HacknetNode, value.data); + } } Reviver.constructors.HacknetNode = HacknetNode; diff --git a/src/Hacknet/HacknetServer.ts b/src/Hacknet/HacknetServer.ts index 74f093ac5..ebe9076ca 100644 --- a/src/Hacknet/HacknetServer.ts +++ b/src/Hacknet/HacknetServer.ts @@ -8,140 +8,162 @@ import { IHacknetNode } from "./IHacknetNode"; import { BaseServer } from "../Server/BaseServer"; import { RunningScript } from "../Script/RunningScript"; import { HacknetServerConstants } from "./data/Constants"; -import { - calculateHashGainRate, - calculateLevelUpgradeCost, - calculateRamUpgradeCost, - calculateCoreUpgradeCost, - calculateCacheUpgradeCost, +import { + calculateHashGainRate, + calculateLevelUpgradeCost, + calculateRamUpgradeCost, + calculateCoreUpgradeCost, + calculateCacheUpgradeCost, } from "./formulas/HacknetServers"; import { createRandomIp } from "../../utils/IPAddress"; import { - Generic_fromJSON, - Generic_toJSON, - Reviver, + Generic_fromJSON, + Generic_toJSON, + Reviver, } from "../../utils/JSONReviver"; interface IConstructorParams { - adminRights?: boolean; - hostname: string; - ip?: string; - isConnectedTo?: boolean; - maxRam?: number; - organizationName?: string; + adminRights?: boolean; + hostname: string; + ip?: string; + isConnectedTo?: boolean; + maxRam?: number; + organizationName?: string; } export class HacknetServer extends BaseServer implements IHacknetNode { + // Cache level. Affects hash Capacity + cache = 1; - // Cache level. Affects hash Capacity - cache = 1; + // Number of cores. Improves hash production + cores = 1; - // Number of cores. Improves hash production - cores = 1; + // Number of hashes that can be stored by this Hacknet Server + hashCapacity = 0; - // Number of hashes that can be stored by this Hacknet Server - hashCapacity = 0; + // Hashes produced per second + hashRate = 0; - // Hashes produced per second - hashRate = 0; + // Similar to Node level. Improves hash production + level = 1; - // Similar to Node level. Improves hash production - level = 1; + // How long this HacknetServer has existed, in seconds + onlineTimeSeconds = 0; - // How long this HacknetServer has existed, in seconds - onlineTimeSeconds = 0; + // Total number of hashes earned by this server + totalHashesGenerated = 0; - // Total number of hashes earned by this server - totalHashesGenerated = 0; + constructor( + params: IConstructorParams = { hostname: "", ip: createRandomIp() }, + ) { + super(params); - constructor(params: IConstructorParams={ hostname: "", ip: createRandomIp() }) { - super(params); + this.maxRam = 1; + this.updateHashCapacity(); + } - this.maxRam = 1; - this.updateHashCapacity(); + calculateCacheUpgradeCost(levels: number): number { + return calculateCacheUpgradeCost(this.cache, levels); + } + + calculateCoreUpgradeCost(levels: number, costMult: number): number { + return calculateCoreUpgradeCost(this.cores, levels, costMult); + } + + calculateLevelUpgradeCost(levels: number, costMult: number): number { + return calculateLevelUpgradeCost(this.level, levels, costMult); + } + + calculateRamUpgradeCost(levels: number, costMult: number): number { + return calculateRamUpgradeCost(this.maxRam, levels, costMult); + } + + // Process this Hacknet Server in the game loop. Returns the number of hashes generated + process(numCycles = 1): number { + const seconds = (numCycles * CONSTANTS.MilliPerCycle) / 1000; + + return this.hashRate * seconds; + } + + upgradeCache(levels: number): void { + this.cache = Math.min( + HacknetServerConstants.MaxCache, + Math.round(this.cache + levels), + ); + this.updateHashCapacity(); + } + + upgradeCore(levels: number, prodMult: number): void { + this.cores = Math.min( + HacknetServerConstants.MaxCores, + Math.round(this.cores + levels), + ); + this.updateHashRate(prodMult); + } + + upgradeLevel(levels: number, prodMult: number): void { + this.level = Math.min( + HacknetServerConstants.MaxLevel, + Math.round(this.level + levels), + ); + this.updateHashRate(prodMult); + } + + upgradeRam(levels: number, prodMult: number): boolean { + for (let i = 0; i < levels; ++i) { + this.maxRam *= 2; } + this.maxRam = Math.min( + HacknetServerConstants.MaxRam, + Math.round(this.maxRam), + ); + this.updateHashRate(prodMult); - calculateCacheUpgradeCost(levels: number): number { - return calculateCacheUpgradeCost(this.cache, levels); + return true; + } + + // Whenever a script is run, we must update this server's hash rate + runScript(script: RunningScript, prodMult?: number): void { + super.runScript(script); + if (prodMult != null && typeof prodMult === "number") { + this.updateHashRate(prodMult); } + } - calculateCoreUpgradeCost(levels: number, costMult: number): number { - return calculateCoreUpgradeCost(this.cores, levels, costMult); + updateHashCapacity(): void { + this.hashCapacity = 32 * Math.pow(2, this.cache); + } + + updateHashRate(prodMult: number): void { + this.hashRate = calculateHashGainRate( + this.level, + this.ramUsed, + this.maxRam, + this.cores, + prodMult, + ); + + if (isNaN(this.hashRate)) { + this.hashRate = 0; + console.error( + `Error calculating Hacknet Server hash production. This is a bug. Please report to game dev`, + false, + ); } + } - calculateLevelUpgradeCost(levels: number, costMult: number): number { - return calculateLevelUpgradeCost(this.level, levels, costMult); - } + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("HacknetServer", this); + } - calculateRamUpgradeCost(levels: number, costMult: number): number { - return calculateRamUpgradeCost(this.maxRam, levels, costMult); - } - - // Process this Hacknet Server in the game loop. Returns the number of hashes generated - process(numCycles=1): number { - const seconds = numCycles * CONSTANTS.MilliPerCycle / 1000; - - return this.hashRate * seconds; - } - - upgradeCache(levels: number): void { - this.cache = Math.min(HacknetServerConstants.MaxCache, Math.round(this.cache + levels)); - this.updateHashCapacity(); - } - - upgradeCore(levels: number, prodMult: number): void { - this.cores = Math.min(HacknetServerConstants.MaxCores, Math.round(this.cores + levels)); - this.updateHashRate(prodMult); - } - - upgradeLevel(levels: number, prodMult: number): void { - this.level = Math.min(HacknetServerConstants.MaxLevel, Math.round(this.level + levels)); - this.updateHashRate(prodMult); - } - - upgradeRam(levels: number, prodMult: number): boolean { - for (let i = 0; i < levels; ++i) { - this.maxRam *= 2; - } - this.maxRam = Math.min(HacknetServerConstants.MaxRam, Math.round(this.maxRam)); - this.updateHashRate(prodMult); - - return true; - } - - // Whenever a script is run, we must update this server's hash rate - runScript(script: RunningScript, prodMult?: number): void { - super.runScript(script); - if (prodMult != null && typeof prodMult === "number") { - this.updateHashRate(prodMult); - } - } - - updateHashCapacity(): void { - this.hashCapacity = 32 * Math.pow(2, this.cache); - } - - updateHashRate(prodMult: number): void { - this.hashRate = calculateHashGainRate(this.level, this.ramUsed, this.maxRam, this.cores, prodMult) - - if (isNaN(this.hashRate)) { - this.hashRate = 0; - console.error(`Error calculating Hacknet Server hash production. This is a bug. Please report to game dev`, false); - } - } - - // Serialize the current object to a JSON save state - toJSON(): any { - return Generic_toJSON("HacknetServer", this); - } - - // Initializes a HacknetServer Object from a JSON save state - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): HacknetServer { - return Generic_fromJSON(HacknetServer, value.data); - } + // Initializes a HacknetServer Object from a JSON save state + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): HacknetServer { + return Generic_fromJSON(HacknetServer, value.data); + } } Reviver.constructors.HacknetServer = HacknetServer; diff --git a/src/Hacknet/HashManager.ts b/src/Hacknet/HashManager.ts index d3a454740..b41278158 100644 --- a/src/Hacknet/HashManager.ts +++ b/src/Hacknet/HashManager.ts @@ -10,157 +10,166 @@ import { HashUpgrades } from "./HashUpgrades"; import { HashUpgrade } from "./HashUpgrade"; import { IMap } from "../types"; -import { Generic_fromJSON, - Generic_toJSON, - Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; export class HashManager { + // Max number of hashes this can hold. Equal to the sum of capacities of + // all Hacknet Servers + capacity = 0; - // Max number of hashes this can hold. Equal to the sum of capacities of - // all Hacknet Servers - capacity = 0; + // Number of hashes currently in storage + hashes = 0; - // Number of hashes currently in storage - hashes = 0; + // Map of Hash Upgrade Name -> levels in that upgrade + upgrades: IMap = {}; - // Map of Hash Upgrade Name -> levels in that upgrade - upgrades: IMap = {}; + constructor() { + for (const name in HashUpgrades) { + this.upgrades[name] = 0; + } + } - constructor() { - for (const name in HashUpgrades) { - this.upgrades[name] = 0; - } + /** + * Generic helper function for getting a multiplier from a HashUpgrade + */ + getMult(upgName: string): number { + const upg = HashUpgrades[upgName]; + const currLevel = this.upgrades[upgName]; + if (upg == null || currLevel == null) { + console.error(`Could not find Hash Study upgrade`); + return 1; } - /** - * Generic helper function for getting a multiplier from a HashUpgrade - */ - getMult(upgName: string): number { - const upg = HashUpgrades[upgName]; - const currLevel = this.upgrades[upgName]; - if (upg == null || currLevel == null) { - console.error(`Could not find Hash Study upgrade`); - return 1; - } + return 1 + (upg.value * currLevel) / 100; + } - return 1 + ((upg.value * currLevel) / 100); + /** + * One of the Hash upgrades improves studying. This returns that multiplier + */ + getStudyMult(): number { + const upgName = "Improve Studying"; + + return this.getMult(upgName); + } + + /** + * One of the Hash upgrades improves gym training. This returns that multiplier + */ + getTrainingMult(): number { + const upgName = "Improve Gym Training"; + + return this.getMult(upgName); + } + + getUpgrade(upgName: string): HashUpgrade | null { + const upg = HashUpgrades[upgName]; + if (!upg) { + console.error( + `Invalid Upgrade Name given to HashManager.getUpgrade(): ${upgName}`, + ); + return null; + } + return upg; + } + + /** + * Get the cost (in hashes) of an upgrade + */ + getUpgradeCost(upgName: string): number { + const upg = this.getUpgrade(upgName); + const currLevel = this.upgrades[upgName]; + if (upg == null || currLevel == null) { + console.error( + `Invalid Upgrade Name given to HashManager.getUpgradeCost(): ${upgName}`, + ); + return Infinity; } - /** - * One of the Hash upgrades improves studying. This returns that multiplier - */ - getStudyMult(): number { - const upgName = "Improve Studying"; + return upg.getCost(currLevel); + } - return this.getMult(upgName); + prestige(): void { + for (const name in HashUpgrades) { + this.upgrades[name] = 0; + } + this.hashes = 0; + + // When prestiging, player's hacknet nodes are always reset. So capacity = 0 + this.updateCapacity(0); + } + + /** + * Reverts an upgrade and refunds the hashes used to buy it + */ + refundUpgrade(upgName: string): void { + const upg = HashUpgrades[upgName]; + + // Reduce the level first, so we get the right cost + --this.upgrades[upgName]; + + const currLevel = this.upgrades[upgName]; + if (upg == null || currLevel == null || currLevel < 0) { + console.error( + `Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`, + ); + return; } - /** - * One of the Hash upgrades improves gym training. This returns that multiplier - */ - getTrainingMult(): number { - const upgName = "Improve Gym Training"; + const cost = upg.getCost(currLevel); + this.hashes += cost; + } - return this.getMult(upgName); + storeHashes(numHashes: number): void { + this.hashes += numHashes; + this.hashes = Math.min(this.hashes, this.capacity); + } + + updateCapacity(newCap: number): void { + if (newCap < 0) { + this.capacity = 0; + } + this.capacity = Math.max(newCap, 0); + } + + /** + * Returns boolean indicating whether or not the upgrade was successfully purchased + * Note that this does NOT actually implement the effect + */ + upgrade(upgName: string): boolean { + const upg = HashUpgrades[upgName]; + if (upg == null) { + console.error( + `Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`, + ); + return false; } - getUpgrade(upgName: string): HashUpgrade | null { - const upg = HashUpgrades[upgName]; - if (!upg) { - console.error(`Invalid Upgrade Name given to HashManager.getUpgrade(): ${upgName}`); - return null; - } - return upg; + const cost = this.getUpgradeCost(upgName); + + if (this.hashes < cost) { + return false; } - /** - * Get the cost (in hashes) of an upgrade - */ - getUpgradeCost(upgName: string): number { - const upg = this.getUpgrade(upgName); - const currLevel = this.upgrades[upgName]; - if (upg == null || currLevel == null) { - console.error(`Invalid Upgrade Name given to HashManager.getUpgradeCost(): ${upgName}`); - return Infinity; - } + this.hashes -= cost; + ++this.upgrades[upgName]; - return upg.getCost(currLevel); - } + return true; + } - prestige(): void { - for (const name in HashUpgrades) { - this.upgrades[name] = 0; - } - this.hashes = 0; + //Serialize the current object to a JSON save state. + toJSON(): any { + return Generic_toJSON("HashManager", this); + } - // When prestiging, player's hacknet nodes are always reset. So capacity = 0 - this.updateCapacity(0); - } - - /** - * Reverts an upgrade and refunds the hashes used to buy it - */ - refundUpgrade(upgName: string): void { - const upg = HashUpgrades[upgName]; - - // Reduce the level first, so we get the right cost - --this.upgrades[upgName]; - - const currLevel = this.upgrades[upgName]; - if (upg == null || currLevel == null || currLevel < 0) { - console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`); - return; - } - - const cost = upg.getCost(currLevel); - this.hashes += cost; - } - - storeHashes(numHashes: number): void { - this.hashes += numHashes; - this.hashes = Math.min(this.hashes, this.capacity); - } - - updateCapacity(newCap: number): void { - if (newCap < 0) { - this.capacity = 0; - } - this.capacity = Math.max(newCap, 0); - } - - /** - * Returns boolean indicating whether or not the upgrade was successfully purchased - * Note that this does NOT actually implement the effect - */ - upgrade(upgName: string): boolean { - const upg = HashUpgrades[upgName]; - if (upg == null) { - console.error(`Invalid Upgrade Name given to HashManager.upgrade(): ${upgName}`); - return false; - } - - const cost = this.getUpgradeCost(upgName); - - if (this.hashes < cost) { - return false; - } - - this.hashes -= cost; - ++this.upgrades[upgName]; - - return true; - } - - //Serialize the current object to a JSON save state. - toJSON(): any { - return Generic_toJSON("HashManager", this); - } - - // Initiatizes a HashManager object from a JSON save state. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): HashManager { - return Generic_fromJSON(HashManager, value.data); - } + // Initiatizes a HashManager object from a JSON save state. + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): HashManager { + return Generic_fromJSON(HashManager, value.data); + } } Reviver.constructors.HashManager = HashManager; diff --git a/src/Hacknet/HashUpgrade.ts b/src/Hacknet/HashUpgrade.ts index e93e1e7a0..4dfd4742d 100644 --- a/src/Hacknet/HashUpgrade.ts +++ b/src/Hacknet/HashUpgrade.ts @@ -2,65 +2,71 @@ * Object representing an upgrade that can be purchased with hashes */ export interface IConstructorParams { - cost?: number; - costPerLevel: number; - desc: string; - hasTargetServer?: boolean; - name: string; - value: number; - effectText?: (level: number) => JSX.Element | null; + cost?: number; + costPerLevel: number; + desc: string; + hasTargetServer?: boolean; + name: string; + value: number; + effectText?: (level: number) => JSX.Element | null; } export class HashUpgrade { - /** - * If the upgrade has a flat cost (never increases), it goes here - * Otherwise, this property should be undefined - * - * This property overrides the 'costPerLevel' property - */ - cost?: number; + /** + * If the upgrade has a flat cost (never increases), it goes here + * Otherwise, this property should be undefined + * + * This property overrides the 'costPerLevel' property + */ + cost?: number; - /** - * Base cost for this upgrade. Every time the upgrade is purchased, - * its cost increases by this same amount (so its 1x, 2x, 3x, 4x, etc.) - */ - costPerLevel = 0; + /** + * Base cost for this upgrade. Every time the upgrade is purchased, + * its cost increases by this same amount (so its 1x, 2x, 3x, 4x, etc.) + */ + costPerLevel = 0; - /** - * Description of what the upgrade does - */ - desc = ""; + /** + * Description of what the upgrade does + */ + desc = ""; - /** - * Boolean indicating that this upgrade's effect affects a single server, - * the "target" server - */ - hasTargetServer = false; + /** + * Boolean indicating that this upgrade's effect affects a single server, + * the "target" server + */ + hasTargetServer = false; - // Name of upgrade - name = ""; + // Name of upgrade + name = ""; - // Generic value used to indicate the potency/amount of this upgrade's effect - // The meaning varies between different upgrades - value = 0; + // Generic value used to indicate the potency/amount of this upgrade's effect + // The meaning varies between different upgrades + value = 0; - constructor(p: IConstructorParams) { - if (p.cost != null) { this.cost = p.cost; } - if (p.effectText != null) { this.effectText = p.effectText; } - - this.costPerLevel = p.costPerLevel; - this.desc = p.desc; - this.hasTargetServer = p.hasTargetServer ? p.hasTargetServer : false; - this.name = p.name; - this.value = p.value; + constructor(p: IConstructorParams) { + if (p.cost != null) { + this.cost = p.cost; + } + if (p.effectText != null) { + this.effectText = p.effectText; } - // Functions that returns the UI element to display the effect of this upgrade. - effectText: (level: number) => JSX.Element | null = () => null; + this.costPerLevel = p.costPerLevel; + this.desc = p.desc; + this.hasTargetServer = p.hasTargetServer ? p.hasTargetServer : false; + this.name = p.name; + this.value = p.value; + } - getCost(levels: number): number { - if (typeof this.cost === "number") { return this.cost; } + // Functions that returns the UI element to display the effect of this upgrade. + effectText: (level: number) => JSX.Element | null = () => null; - return Math.round((levels + 1) * this.costPerLevel); + getCost(levels: number): number { + if (typeof this.cost === "number") { + return this.cost; } + + return Math.round((levels + 1) * this.costPerLevel); + } } diff --git a/src/Hacknet/HashUpgrades.ts b/src/Hacknet/HashUpgrades.ts index da7f8f800..59b8644dd 100644 --- a/src/Hacknet/HashUpgrades.ts +++ b/src/Hacknet/HashUpgrades.ts @@ -2,17 +2,16 @@ * Map of all Hash Upgrades * Key = Hash name, Value = HashUpgrade object */ -import { HashUpgrade, - IConstructorParams } from "./HashUpgrade"; +import { HashUpgrade, IConstructorParams } from "./HashUpgrade"; import { HashUpgradesMetadata } from "./data/HashUpgradesMetadata"; import { IMap } from "../types"; export const HashUpgrades: IMap = {}; function createHashUpgrade(p: IConstructorParams): void { - HashUpgrades[p.name] = new HashUpgrade(p); + HashUpgrades[p.name] = new HashUpgrade(p); } for (const metadata of HashUpgradesMetadata) { - createHashUpgrade(metadata); + createHashUpgrade(metadata); } diff --git a/src/Hacknet/IHacknetNode.ts b/src/Hacknet/IHacknetNode.ts index 5f00ffee1..8ed786cd3 100644 --- a/src/Hacknet/IHacknetNode.ts +++ b/src/Hacknet/IHacknetNode.ts @@ -1,14 +1,14 @@ // Interface for a Hacknet Node. Implemented by both a basic Hacknet Node, // and the upgraded Hacknet Server in BitNode-9 export interface IHacknetNode { - cores: number; - level: number; - onlineTimeSeconds: number; + cores: number; + level: number; + onlineTimeSeconds: number; - calculateCoreUpgradeCost: (levels: number, costMult: number) => number; - calculateLevelUpgradeCost: (levels: number, costMult: number) => number; - calculateRamUpgradeCost: (levels: number, costMult: number) => number; - upgradeCore: (levels: number, prodMult: number) => void; - upgradeLevel: (levels: number, prodMult: number) => void; - upgradeRam: (levels: number, prodMult: number) => void; + calculateCoreUpgradeCost: (levels: number, costMult: number) => number; + calculateLevelUpgradeCost: (levels: number, costMult: number) => number; + calculateRamUpgradeCost: (levels: number, costMult: number) => number; + upgradeCore: (levels: number, prodMult: number) => void; + upgradeLevel: (levels: number, prodMult: number) => void; + upgradeRam: (levels: number, prodMult: number) => void; } diff --git a/src/Hacknet/data/Constants.ts b/src/Hacknet/data/Constants.ts index fdf048dbd..b34ea8145 100644 --- a/src/Hacknet/data/Constants.ts +++ b/src/Hacknet/data/Constants.ts @@ -1,80 +1,80 @@ export const HacknetNodeConstants: { - // Constants for Hacknet Node production - MoneyGainPerLevel: number; + // Constants for Hacknet Node production + MoneyGainPerLevel: number; - // Constants for Hacknet Node purchase/upgrade costs - BaseCost: number; - LevelBaseCost: number; - RamBaseCost: number; - CoreBaseCost: number; + // Constants for Hacknet Node purchase/upgrade costs + BaseCost: number; + LevelBaseCost: number; + RamBaseCost: number; + CoreBaseCost: number; - PurchaseNextMult: number; - UpgradeLevelMult: number; - UpgradeRamMult: number; - UpgradeCoreMult: number; + PurchaseNextMult: number; + UpgradeLevelMult: number; + UpgradeRamMult: number; + UpgradeCoreMult: number; - // Constants for max upgrade levels for Hacknet Nodes - MaxLevel: number; - MaxRam: number; - MaxCores: number; + // Constants for max upgrade levels for Hacknet Nodes + MaxLevel: number; + MaxRam: number; + MaxCores: number; } = { - MoneyGainPerLevel: 1.6, + MoneyGainPerLevel: 1.6, - BaseCost: 1000, - LevelBaseCost: 1, - RamBaseCost: 30e3, - CoreBaseCost: 500e3, - - PurchaseNextMult: 1.85, - UpgradeLevelMult: 1.04, - UpgradeRamMult: 1.28, - UpgradeCoreMult: 1.48, + BaseCost: 1000, + LevelBaseCost: 1, + RamBaseCost: 30e3, + CoreBaseCost: 500e3, - MaxLevel: 200, - MaxRam: 64, - MaxCores: 16, -} + PurchaseNextMult: 1.85, + UpgradeLevelMult: 1.04, + UpgradeRamMult: 1.28, + UpgradeCoreMult: 1.48, + + MaxLevel: 200, + MaxRam: 64, + MaxCores: 16, +}; export const HacknetServerConstants: { - // Constants for Hacknet Server stats/production - HashesPerLevel: number; + // Constants for Hacknet Server stats/production + HashesPerLevel: number; - // Constants for Hacknet Server purchase/upgrade costs - BaseCost: number; - RamBaseCost: number; - CoreBaseCost: number; - CacheBaseCost: number; + // Constants for Hacknet Server purchase/upgrade costs + BaseCost: number; + RamBaseCost: number; + CoreBaseCost: number; + CacheBaseCost: number; - PurchaseMult: number; // Multiplier for puchasing an additional Hacknet Server - UpgradeLevelMult: number; // Multiplier for cost when upgrading level - UpgradeRamMult: number; // Multiplier for cost when upgrading RAM - UpgradeCoreMult: number; // Multiplier for cost when buying another core - UpgradeCacheMult: number; // Multiplier for cost when upgrading cache - MaxServers: number; // Max number of Hacknet Servers you can own + PurchaseMult: number; // Multiplier for puchasing an additional Hacknet Server + UpgradeLevelMult: number; // Multiplier for cost when upgrading level + UpgradeRamMult: number; // Multiplier for cost when upgrading RAM + UpgradeCoreMult: number; // Multiplier for cost when buying another core + UpgradeCacheMult: number; // Multiplier for cost when upgrading cache + MaxServers: number; // Max number of Hacknet Servers you can own - // Constants for max upgrade levels for Hacknet Server - MaxLevel: number; - MaxRam: number; - MaxCores: number; - MaxCache: number; + // Constants for max upgrade levels for Hacknet Server + MaxLevel: number; + MaxRam: number; + MaxCores: number; + MaxCache: number; } = { - HashesPerLevel: 0.001, + HashesPerLevel: 0.001, - BaseCost: 50e3, - RamBaseCost: 200e3, - CoreBaseCost: 1e6, - CacheBaseCost: 10e6, - - PurchaseMult: 3.20, - UpgradeLevelMult: 1.10, - UpgradeRamMult: 1.40, - UpgradeCoreMult: 1.55, - UpgradeCacheMult: 1.85, - - MaxServers: 20, + BaseCost: 50e3, + RamBaseCost: 200e3, + CoreBaseCost: 1e6, + CacheBaseCost: 10e6, - MaxLevel: 300, - MaxRam: 8192, - MaxCores: 128, - MaxCache: 15, -} + PurchaseMult: 3.2, + UpgradeLevelMult: 1.1, + UpgradeRamMult: 1.4, + UpgradeCoreMult: 1.55, + UpgradeCacheMult: 1.85, + + MaxServers: 20, + + MaxLevel: 300, + MaxRam: 8192, + MaxCores: 128, + MaxCache: 15, +}; diff --git a/src/Hacknet/data/HashUpgradesMetadata.tsx b/src/Hacknet/data/HashUpgradesMetadata.tsx index 6244d7dd2..f1b690df9 100644 --- a/src/Hacknet/data/HashUpgradesMetadata.tsx +++ b/src/Hacknet/data/HashUpgradesMetadata.tsx @@ -5,81 +5,109 @@ import { numeralWrapper } from "../../ui/numeralFormat"; import { Money } from "../../ui/React/Money"; export const HashUpgradesMetadata: IConstructorParams[] = [ - { - cost: 4, - costPerLevel: 4, - desc: "Sell hashes for $1m", - name: "Sell for Money", - effectText: (level: number): JSX.Element | null => (<>Sold for ), - value: 1e6, - }, - { - costPerLevel: 100, - desc: "Sell hashes for $1b in Corporation funds", - name: "Sell for Corporation Funds", - effectText: (level: number): JSX.Element | null => (<>Sold for Corporation funds.), - value: 1e9, - }, - { - costPerLevel: 50, - desc: "Use hashes to decrease the minimum security of a single server by 2%. " + - "Note that a server's minimum security cannot go below 1. This effect persists " + - "until you install Augmentations (since servers are reset at that time).", - hasTargetServer: true, - name: "Reduce Minimum Security", - value: 0.98, - }, - { - costPerLevel: 50, - desc: "Use hashes to increase the maximum amount of money on a single server by 2%. " + - "This effect persists until you install Augmentations (since servers " + - "are reset at that time).", - hasTargetServer: true, - name: "Increase Maximum Money", - value: 1.02, - }, - { - costPerLevel: 50, - desc: "Use hashes to improve the experience earned when studying at a university by 20%. " + - "This effect persists until you install Augmentations", - name: "Improve Studying", - //effectText: (level: number) => JSX.Element | null = <>Improves studying by ${level*20}%, - value: 20, // Improves studying by value% - }, - { - costPerLevel: 50, - desc: "Use hashes to improve the experience earned when training at the gym by 20%. This effect " + - "persists until you install Augmentations", - name: "Improve Gym Training", - effectText: (level: number): JSX.Element | null => (<>Improves training by ${level*20}%), - value: 20, // Improves training by value% - }, - { - costPerLevel: 200, - desc: "Exchange hashes for 1k Scientific Research in all of your Corporation's Industries", - name: "Exchange for Corporation Research", - effectText: (level: number): JSX.Element | null => (<>Acquired a total of {level}k Scientific Research in your industries.), - value: 1000, - }, - { - costPerLevel: 250, - desc: "Exchange hashes for 100 Bladeburner Rank", - name: "Exchange for Bladeburner Rank", - effectText: (level: number): JSX.Element | null => (<>Acquired a total of {numeralWrapper.format(100*level, '0a')} Bladeburner rank), - value: 100, - }, - { - costPerLevel: 250, - desc: "Exchanges hashes for 10 Bladeburner Skill Points", - name: "Exchange for Bladeburner SP", - effectText: (level: number): JSX.Element | null => (<>Acquired a total of {numeralWrapper.format(10*level, '0a')} Bladeburner Skill Points), - value: 10, - }, - { - costPerLevel: 200, - desc: "Generate a random Coding Contract somewhere on the network", - name: "Generate Coding Contract", - effectText: (level: number): JSX.Element | null => (<>Generated {level} contracts.), - value: 1, - }, -] + { + cost: 4, + costPerLevel: 4, + desc: "Sell hashes for $1m", + name: "Sell for Money", + effectText: (level: number): JSX.Element | null => ( + <> + Sold for + + ), + value: 1e6, + }, + { + costPerLevel: 100, + desc: "Sell hashes for $1b in Corporation funds", + name: "Sell for Corporation Funds", + effectText: (level: number): JSX.Element | null => ( + <> + Sold for Corporation funds. + + ), + value: 1e9, + }, + { + costPerLevel: 50, + desc: + "Use hashes to decrease the minimum security of a single server by 2%. " + + "Note that a server's minimum security cannot go below 1. This effect persists " + + "until you install Augmentations (since servers are reset at that time).", + hasTargetServer: true, + name: "Reduce Minimum Security", + value: 0.98, + }, + { + costPerLevel: 50, + desc: + "Use hashes to increase the maximum amount of money on a single server by 2%. " + + "This effect persists until you install Augmentations (since servers " + + "are reset at that time).", + hasTargetServer: true, + name: "Increase Maximum Money", + value: 1.02, + }, + { + costPerLevel: 50, + desc: + "Use hashes to improve the experience earned when studying at a university by 20%. " + + "This effect persists until you install Augmentations", + name: "Improve Studying", + //effectText: (level: number) => JSX.Element | null = <>Improves studying by ${level*20}%, + value: 20, // Improves studying by value% + }, + { + costPerLevel: 50, + desc: + "Use hashes to improve the experience earned when training at the gym by 20%. This effect " + + "persists until you install Augmentations", + name: "Improve Gym Training", + effectText: (level: number): JSX.Element | null => ( + <>Improves training by ${level * 20}% + ), + value: 20, // Improves training by value% + }, + { + costPerLevel: 200, + desc: "Exchange hashes for 1k Scientific Research in all of your Corporation's Industries", + name: "Exchange for Corporation Research", + effectText: (level: number): JSX.Element | null => ( + <>Acquired a total of {level}k Scientific Research in your industries. + ), + value: 1000, + }, + { + costPerLevel: 250, + desc: "Exchange hashes for 100 Bladeburner Rank", + name: "Exchange for Bladeburner Rank", + effectText: (level: number): JSX.Element | null => ( + <> + Acquired a total of {numeralWrapper.format(100 * level, "0a")}{" "} + Bladeburner rank + + ), + value: 100, + }, + { + costPerLevel: 250, + desc: "Exchanges hashes for 10 Bladeburner Skill Points", + name: "Exchange for Bladeburner SP", + effectText: (level: number): JSX.Element | null => ( + <> + Acquired a total of {numeralWrapper.format(10 * level, "0a")}{" "} + Bladeburner Skill Points + + ), + value: 10, + }, + { + costPerLevel: 200, + desc: "Generate a random Coding Contract somewhere on the network", + name: "Generate Coding Contract", + effectText: (level: number): JSX.Element | null => ( + <>Generated {level} contracts. + ), + value: 1, + }, +]; diff --git a/src/Hacknet/formulas/HacknetNodes.ts b/src/Hacknet/formulas/HacknetNodes.ts index 176027c98..2edadfc8c 100644 --- a/src/Hacknet/formulas/HacknetNodes.ts +++ b/src/Hacknet/formulas/HacknetNodes.ts @@ -1,96 +1,116 @@ import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { HacknetNodeConstants } from "../data/Constants"; -export function calculateMoneyGainRate(level: number, ram: number, cores: number, mult: number): number { - const gainPerLevel = HacknetNodeConstants.MoneyGainPerLevel; +export function calculateMoneyGainRate( + level: number, + ram: number, + cores: number, + mult: number, +): number { + const gainPerLevel = HacknetNodeConstants.MoneyGainPerLevel; - const levelMult = (level * gainPerLevel); - const ramMult = Math.pow(1.035, ram - 1); - const coresMult = ((cores + 5) / 6); - return levelMult * - ramMult * - coresMult * - mult * - BitNodeMultipliers.HacknetNodeMoney; + const levelMult = level * gainPerLevel; + const ramMult = Math.pow(1.035, ram - 1); + const coresMult = (cores + 5) / 6; + return ( + levelMult * ramMult * coresMult * mult * BitNodeMultipliers.HacknetNodeMoney + ); } -export function calculateLevelUpgradeCost(startingLevel: number, extraLevels=1, costMult=1): number { - const sanitizedLevels = Math.round(extraLevels); - if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { - return 0; - } +export function calculateLevelUpgradeCost( + startingLevel: number, + extraLevels = 1, + costMult = 1, +): number { + const sanitizedLevels = Math.round(extraLevels); + if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { + return 0; + } - if (startingLevel >= HacknetNodeConstants.MaxLevel) { - return Infinity; - } + if (startingLevel >= HacknetNodeConstants.MaxLevel) { + return Infinity; + } - const mult = HacknetNodeConstants.UpgradeLevelMult; - let totalMultiplier = 0; - let currLevel = startingLevel; - for (let i = 0; i < sanitizedLevels; ++i) { - totalMultiplier += (HacknetNodeConstants.LevelBaseCost * Math.pow(mult, currLevel)); - ++currLevel; - } + const mult = HacknetNodeConstants.UpgradeLevelMult; + let totalMultiplier = 0; + let currLevel = startingLevel; + for (let i = 0; i < sanitizedLevels; ++i) { + totalMultiplier += + HacknetNodeConstants.LevelBaseCost * Math.pow(mult, currLevel); + ++currLevel; + } - return HacknetNodeConstants.BaseCost / 2 * totalMultiplier * costMult; + return (HacknetNodeConstants.BaseCost / 2) * totalMultiplier * costMult; } -export function calculateRamUpgradeCost(startingRam: number, extraLevels=1, costMult=1): number { - const sanitizedLevels = Math.round(extraLevels); - if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { - return 0; - } +export function calculateRamUpgradeCost( + startingRam: number, + extraLevels = 1, + costMult = 1, +): number { + const sanitizedLevels = Math.round(extraLevels); + if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { + return 0; + } - if (startingRam >= HacknetNodeConstants.MaxRam) { - return Infinity; - } + if (startingRam >= HacknetNodeConstants.MaxRam) { + return Infinity; + } - let totalCost = 0; - let numUpgrades = Math.round(Math.log2(startingRam)); - let currentRam = startingRam; + let totalCost = 0; + let numUpgrades = Math.round(Math.log2(startingRam)); + let currentRam = startingRam; - for (let i = 0; i < sanitizedLevels; ++i) { - const baseCost = currentRam * HacknetNodeConstants.RamBaseCost; - const mult = Math.pow(HacknetNodeConstants.UpgradeRamMult, numUpgrades); + for (let i = 0; i < sanitizedLevels; ++i) { + const baseCost = currentRam * HacknetNodeConstants.RamBaseCost; + const mult = Math.pow(HacknetNodeConstants.UpgradeRamMult, numUpgrades); - totalCost += (baseCost * mult); + totalCost += baseCost * mult; - currentRam *= 2; - ++numUpgrades; - } + currentRam *= 2; + ++numUpgrades; + } - totalCost *= costMult; + totalCost *= costMult; - return totalCost; + return totalCost; } -export function calculateCoreUpgradeCost(startingCore: number, extraLevels=1, costMult=1): number { - const sanitizedCores = Math.round(extraLevels); - if (isNaN(sanitizedCores) || sanitizedCores < 1) { - return 0; - } +export function calculateCoreUpgradeCost( + startingCore: number, + extraLevels = 1, + costMult = 1, +): number { + const sanitizedCores = Math.round(extraLevels); + if (isNaN(sanitizedCores) || sanitizedCores < 1) { + return 0; + } - if (startingCore >= HacknetNodeConstants.MaxCores) { - return Infinity; - } + if (startingCore >= HacknetNodeConstants.MaxCores) { + return Infinity; + } - const coreBaseCost = HacknetNodeConstants.CoreBaseCost; - const mult = HacknetNodeConstants.UpgradeCoreMult; - let totalCost = 0; - let currentCores = startingCore; - for (let i = 0; i < sanitizedCores; ++i) { - totalCost += (coreBaseCost * Math.pow(mult, currentCores-1)); - ++currentCores; - } + const coreBaseCost = HacknetNodeConstants.CoreBaseCost; + const mult = HacknetNodeConstants.UpgradeCoreMult; + let totalCost = 0; + let currentCores = startingCore; + for (let i = 0; i < sanitizedCores; ++i) { + totalCost += coreBaseCost * Math.pow(mult, currentCores - 1); + ++currentCores; + } - totalCost *= costMult; + totalCost *= costMult; - return totalCost; + return totalCost; } -export function calculateNodeCost(n: number, mult=1): number { - if(n <= 0) { - return 0; - } - return HacknetNodeConstants.BaseCost * Math.pow(HacknetNodeConstants.PurchaseNextMult, n-1) * mult; -} \ No newline at end of file +export function calculateNodeCost(n: number, mult = 1): number { + if (n <= 0) { + return 0; + } + return ( + HacknetNodeConstants.BaseCost * + Math.pow(HacknetNodeConstants.PurchaseNextMult, n - 1) * + mult + ); +} diff --git a/src/Hacknet/formulas/HacknetServers.ts b/src/Hacknet/formulas/HacknetServers.ts index 8908da045..98f0d1c0c 100644 --- a/src/Hacknet/formulas/HacknetServers.ts +++ b/src/Hacknet/formulas/HacknetServers.ts @@ -1,115 +1,144 @@ import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { HacknetServerConstants } from "../data/Constants"; -export function calculateHashGainRate(level: number, ramUsed: number, maxRam: number, cores: number, mult: number): number { - const baseGain = HacknetServerConstants.HashesPerLevel * level; - const ramMultiplier = Math.pow(1.07, Math.log2(maxRam)); - const coreMultiplier = 1 + (cores - 1) / 5; - const ramRatio = (1 - ramUsed / maxRam); +export function calculateHashGainRate( + level: number, + ramUsed: number, + maxRam: number, + cores: number, + mult: number, +): number { + const baseGain = HacknetServerConstants.HashesPerLevel * level; + const ramMultiplier = Math.pow(1.07, Math.log2(maxRam)); + const coreMultiplier = 1 + (cores - 1) / 5; + const ramRatio = 1 - ramUsed / maxRam; - return baseGain * - ramMultiplier * - coreMultiplier * - ramRatio * - mult * - BitNodeMultipliers.HacknetNodeMoney; + return ( + baseGain * + ramMultiplier * + coreMultiplier * + ramRatio * + mult * + BitNodeMultipliers.HacknetNodeMoney + ); } -export function calculateLevelUpgradeCost(startingLevel: number, extraLevels=1, costMult=1): number { - const sanitizedLevels = Math.round(extraLevels); - if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { - return 0; - } +export function calculateLevelUpgradeCost( + startingLevel: number, + extraLevels = 1, + costMult = 1, +): number { + const sanitizedLevels = Math.round(extraLevels); + if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { + return 0; + } - if (startingLevel >= HacknetServerConstants.MaxLevel) { - return Infinity; - } + if (startingLevel >= HacknetServerConstants.MaxLevel) { + return Infinity; + } - const mult = HacknetServerConstants.UpgradeLevelMult; - let totalMultiplier = 0; - let currLevel = startingLevel; - for (let i = 0; i < sanitizedLevels; ++i) { - totalMultiplier += Math.pow(mult, currLevel); - ++currLevel; - } + const mult = HacknetServerConstants.UpgradeLevelMult; + let totalMultiplier = 0; + let currLevel = startingLevel; + for (let i = 0; i < sanitizedLevels; ++i) { + totalMultiplier += Math.pow(mult, currLevel); + ++currLevel; + } - return 10 * HacknetServerConstants.BaseCost * totalMultiplier * costMult; + return 10 * HacknetServerConstants.BaseCost * totalMultiplier * costMult; } -export function calculateRamUpgradeCost(startingRam: number, extraLevels=1, costMult=1): number { - const sanitizedLevels = Math.round(extraLevels); - if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { - return 0; - } +export function calculateRamUpgradeCost( + startingRam: number, + extraLevels = 1, + costMult = 1, +): number { + const sanitizedLevels = Math.round(extraLevels); + if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { + return 0; + } - if (startingRam >= HacknetServerConstants.MaxRam) { - return Infinity; - } + if (startingRam >= HacknetServerConstants.MaxRam) { + return Infinity; + } - let totalCost = 0; - let numUpgrades = Math.round(Math.log2(startingRam)); - let currentRam = startingRam; - for (let i = 0; i < sanitizedLevels; ++i) { - const baseCost = currentRam * HacknetServerConstants.RamBaseCost; - const mult = Math.pow(HacknetServerConstants.UpgradeRamMult, numUpgrades); + let totalCost = 0; + let numUpgrades = Math.round(Math.log2(startingRam)); + let currentRam = startingRam; + for (let i = 0; i < sanitizedLevels; ++i) { + const baseCost = currentRam * HacknetServerConstants.RamBaseCost; + const mult = Math.pow(HacknetServerConstants.UpgradeRamMult, numUpgrades); - totalCost += (baseCost * mult); + totalCost += baseCost * mult; - currentRam *= 2; - ++numUpgrades; - } - totalCost *= costMult; + currentRam *= 2; + ++numUpgrades; + } + totalCost *= costMult; - return totalCost; + return totalCost; } -export function calculateCoreUpgradeCost(startingCores: number, extraLevels=1, costMult=1): number { - const sanitizedLevels = Math.round(extraLevels); - if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { - return 0; - } +export function calculateCoreUpgradeCost( + startingCores: number, + extraLevels = 1, + costMult = 1, +): number { + const sanitizedLevels = Math.round(extraLevels); + if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { + return 0; + } - if (startingCores >= HacknetServerConstants.MaxCores) { - return Infinity; - } + if (startingCores >= HacknetServerConstants.MaxCores) { + return Infinity; + } - const mult = HacknetServerConstants.UpgradeCoreMult; - let totalCost = 0; - let currentCores = startingCores; - for (let i = 0; i < sanitizedLevels; ++i) { - totalCost += Math.pow(mult, currentCores-1); - ++currentCores; - } - totalCost *= HacknetServerConstants.CoreBaseCost; - totalCost *= costMult; + const mult = HacknetServerConstants.UpgradeCoreMult; + let totalCost = 0; + let currentCores = startingCores; + for (let i = 0; i < sanitizedLevels; ++i) { + totalCost += Math.pow(mult, currentCores - 1); + ++currentCores; + } + totalCost *= HacknetServerConstants.CoreBaseCost; + totalCost *= costMult; - return totalCost; + return totalCost; } -export function calculateCacheUpgradeCost(startingCache: number, extraLevels=1): number { - const sanitizedLevels = Math.round(extraLevels); - if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { - return 0; - } +export function calculateCacheUpgradeCost( + startingCache: number, + extraLevels = 1, +): number { + const sanitizedLevels = Math.round(extraLevels); + if (isNaN(sanitizedLevels) || sanitizedLevels < 1) { + return 0; + } - if (startingCache >= HacknetServerConstants.MaxCache) { - return Infinity; - } + if (startingCache >= HacknetServerConstants.MaxCache) { + return Infinity; + } - const mult = HacknetServerConstants.UpgradeCacheMult; - let totalCost = 0; - let currentCache = startingCache; - for (let i = 0; i < sanitizedLevels; ++i) { - totalCost += Math.pow(mult, currentCache - 1); - ++currentCache; - } - totalCost *= HacknetServerConstants.CacheBaseCost; + const mult = HacknetServerConstants.UpgradeCacheMult; + let totalCost = 0; + let currentCache = startingCache; + for (let i = 0; i < sanitizedLevels; ++i) { + totalCost += Math.pow(mult, currentCache - 1); + ++currentCache; + } + totalCost *= HacknetServerConstants.CacheBaseCost; - return totalCost; + return totalCost; } -export function calculateServerCost(n: number, mult=1): number { - if (n-1 >= HacknetServerConstants.MaxServers) { return Infinity; } +export function calculateServerCost(n: number, mult = 1): number { + if (n - 1 >= HacknetServerConstants.MaxServers) { + return Infinity; + } - return HacknetServerConstants.BaseCost * Math.pow(HacknetServerConstants.PurchaseMult, n-1) * mult; -} \ No newline at end of file + return ( + HacknetServerConstants.BaseCost * + Math.pow(HacknetServerConstants.PurchaseMult, n - 1) * + mult + ); +} diff --git a/src/Hacknet/ui/GeneralInfo.jsx b/src/Hacknet/ui/GeneralInfo.jsx index 328f212f3..15ed81614 100644 --- a/src/Hacknet/ui/GeneralInfo.jsx +++ b/src/Hacknet/ui/GeneralInfo.jsx @@ -8,47 +8,51 @@ import React from "react"; import { hasHacknetServers } from "../HacknetHelpers"; export class GeneralInfo extends React.Component { - getSecondParagraph() { - if (hasHacknetServers()) { - return `Here, you can purchase a Hacknet Server, an upgraded version of the Hacknet Node. ` + - `Hacknet Servers will perform computations and operations on the network, earning ` + - `you hashes. Hashes can be spent on a variety of different upgrades.`; - } else { - return `Here, you can purchase a Hacknet Node, a specialized machine that can connect ` + - `and contribute its resources to the Hacknet network. This allows you to take ` + - `a small percentage of profits from hacks performed on the network. Essentially, ` + - `you are renting out your Node's computing power.`; - } + getSecondParagraph() { + if (hasHacknetServers()) { + return ( + `Here, you can purchase a Hacknet Server, an upgraded version of the Hacknet Node. ` + + `Hacknet Servers will perform computations and operations on the network, earning ` + + `you hashes. Hashes can be spent on a variety of different upgrades.` + ); + } else { + return ( + `Here, you can purchase a Hacknet Node, a specialized machine that can connect ` + + `and contribute its resources to the Hacknet network. This allows you to take ` + + `a small percentage of profits from hacks performed on the network. Essentially, ` + + `you are renting out your Node's computing power.` + ); } + } - getThirdParagraph() { - if (hasHacknetServers()) { - return `Hacknet Servers can also be used as servers to run scripts. However, running scripts ` + - `on a server will reduce its hash rate (hashes generated per second). A Hacknet Server's hash ` + - `rate will be reduced by the percentage of RAM that is being used by that Server to run ` + - `scripts.` - } else { - return `Each Hacknet Node you purchase will passively earn you money. Each Hacknet Node ` + - `can be upgraded in order to increase its computing power and thereby increase ` + - `the profit you earn from it.`; - } + getThirdParagraph() { + if (hasHacknetServers()) { + return ( + `Hacknet Servers can also be used as servers to run scripts. However, running scripts ` + + `on a server will reduce its hash rate (hashes generated per second). A Hacknet Server's hash ` + + `rate will be reduced by the percentage of RAM that is being used by that Server to run ` + + `scripts.` + ); + } else { + return ( + `Each Hacknet Node you purchase will passively earn you money. Each Hacknet Node ` + + `can be upgraded in order to increase its computing power and thereby increase ` + + `the profit you earn from it.` + ); } + } - render() { - return ( -
    -

    - The Hacknet is a global, decentralized network of machines. It is used by - hackers all around the world to anonymously share computing power and - perform distributed cyberattacks without the fear of being traced. -

    -

    - {this.getSecondParagraph()} -

    -

    - {this.getThirdParagraph()} -

    -
    - ) - } + render() { + return ( +
    +

    + The Hacknet is a global, decentralized network of machines. It is used + by hackers all around the world to anonymously share computing power + and perform distributed cyberattacks without the fear of being traced. +

    +

    {this.getSecondParagraph()}

    +

    {this.getThirdParagraph()}

    +
    + ); + } } diff --git a/src/Hacknet/ui/HacknetNode.jsx b/src/Hacknet/ui/HacknetNode.jsx index d9bb6c552..afd877e60 100644 --- a/src/Hacknet/ui/HacknetNode.jsx +++ b/src/Hacknet/ui/HacknetNode.jsx @@ -6,12 +6,12 @@ import React from "react"; import { HacknetNodeConstants } from "../data/Constants"; import { - getMaxNumberLevelUpgrades, - getMaxNumberRamUpgrades, - getMaxNumberCoreUpgrades, - purchaseLevelUpgrade, - purchaseRamUpgrade, - purchaseCoreUpgrade, + getMaxNumberLevelUpgrades, + getMaxNumberRamUpgrades, + getMaxNumberCoreUpgrades, + purchaseLevelUpgrade, + purchaseRamUpgrade, + purchaseCoreUpgrade, } from "../HacknetHelpers"; import { Player } from "../../Player"; @@ -20,140 +20,182 @@ import { Money } from "../../ui/React/Money"; import { MoneyRate } from "../../ui/React/MoneyRate"; export class HacknetNode extends React.Component { - render() { - const node = this.props.node; - const purchaseMult = this.props.purchaseMultiplier; - const recalculate = this.props.recalculate; + render() { + const node = this.props.node; + const purchaseMult = this.props.purchaseMultiplier; + const recalculate = this.props.recalculate; - // Upgrade Level Button - let upgradeLevelContent, upgradeLevelClass; - if (node.level >= HacknetNodeConstants.MaxLevel) { - upgradeLevelContent = <>MAX LEVEL; - upgradeLevelClass = "std-button-disabled"; - } else { - let multiplier = 0; - if (purchaseMult === "MAX") { - multiplier = getMaxNumberLevelUpgrades(node, HacknetNodeConstants.MaxLevel); - } else { - const levelsToMax = HacknetNodeConstants.MaxLevel - node.level; - multiplier = Math.min(levelsToMax, purchaseMult); - } + // Upgrade Level Button + let upgradeLevelContent, upgradeLevelClass; + if (node.level >= HacknetNodeConstants.MaxLevel) { + upgradeLevelContent = <>MAX LEVEL; + upgradeLevelClass = "std-button-disabled"; + } else { + let multiplier = 0; + if (purchaseMult === "MAX") { + multiplier = getMaxNumberLevelUpgrades( + node, + HacknetNodeConstants.MaxLevel, + ); + } else { + const levelsToMax = HacknetNodeConstants.MaxLevel - node.level; + multiplier = Math.min(levelsToMax, purchaseMult); + } - const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player.hacknet_node_level_cost_mult); - upgradeLevelContent = <>Upgrade x{multiplier} - ; - if (Player.money.lt(upgradeLevelCost)) { - upgradeLevelClass = "std-button-disabled"; - } else { - upgradeLevelClass = "std-button"; - } - } - const upgradeLevelOnClick = () => { - let numUpgrades = purchaseMult; - if (purchaseMult === "MAX") { - numUpgrades = getMaxNumberLevelUpgrades(node, HacknetNodeConstants.MaxLevel); - } - purchaseLevelUpgrade(node, numUpgrades); - recalculate(); - return false; - } - - let upgradeRamContent, upgradeRamClass; - if (node.ram >= HacknetNodeConstants.MaxRam) { - upgradeRamContent = <>MAX RAM; - upgradeRamClass = "std-button-disabled"; - } else { - let multiplier = 0; - if (purchaseMult === "MAX") { - multiplier = getMaxNumberRamUpgrades(node, HacknetNodeConstants.MaxRam); - } else { - const levelsToMax = Math.round(Math.log2(HacknetNodeConstants.MaxRam / node.ram)); - multiplier = Math.min(levelsToMax, purchaseMult); - } - - const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player.hacknet_node_ram_cost_mult); - upgradeRamContent = <>Upgrade x{multiplier} - ; - if (Player.money.lt(upgradeRamCost)) { - upgradeRamClass = "std-button-disabled"; - } else { - upgradeRamClass = "std-button"; - } - } - const upgradeRamOnClick = () => { - let numUpgrades = purchaseMult; - if (purchaseMult === "MAX") { - numUpgrades = getMaxNumberRamUpgrades(node, HacknetNodeConstants.MaxRam); - } - purchaseRamUpgrade(node, numUpgrades); - recalculate(); - return false; - } - - let upgradeCoresContent, upgradeCoresClass; - if (node.cores >= HacknetNodeConstants.MaxCores) { - upgradeCoresContent = <>MAX CORES; - upgradeCoresClass = "std-button-disabled"; - } else { - let multiplier = 0; - if (purchaseMult === "MAX") { - multiplier = getMaxNumberCoreUpgrades(node, HacknetNodeConstants.MaxCores); - } else { - const levelsToMax = HacknetNodeConstants.MaxCores - node.cores; - multiplier = Math.min(levelsToMax, purchaseMult); - } - - const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player.hacknet_node_core_cost_mult); - upgradeCoresContent = <>Upgrade x{multiplier} - ; - if (Player.money.lt(upgradeCoreCost)) { - upgradeCoresClass = "std-button-disabled"; - } else { - upgradeCoresClass = "std-button"; - } - } - const upgradeCoresOnClick = () => { - let numUpgrades = purchaseMult; - if (purchaseMult === "MAX") { - numUpgrades = getMaxNumberCoreUpgrades(node, HacknetNodeConstants.MaxCores); - } - purchaseCoreUpgrade(node, numUpgrades); - recalculate(); - return false; - } - - return ( -
  • -
    -
    -

    {node.name}

    -
    -
    -

    Production:

    - - ({MoneyRate(node.moneyGainRatePerSecond)}) - -
    -
    -

    Level:

    - {node.level} - -
    -
    -

    RAM:

    - {node.ram}GB - -
    -
    -

    Cores:

    - {node.cores} - -
    -
    -
  • - ) + const upgradeLevelCost = node.calculateLevelUpgradeCost( + multiplier, + Player.hacknet_node_level_cost_mult, + ); + upgradeLevelContent = ( + <> + Upgrade x{multiplier} -{" "} + + + ); + if (Player.money.lt(upgradeLevelCost)) { + upgradeLevelClass = "std-button-disabled"; + } else { + upgradeLevelClass = "std-button"; + } } + const upgradeLevelOnClick = () => { + let numUpgrades = purchaseMult; + if (purchaseMult === "MAX") { + numUpgrades = getMaxNumberLevelUpgrades( + node, + HacknetNodeConstants.MaxLevel, + ); + } + purchaseLevelUpgrade(node, numUpgrades); + recalculate(); + return false; + }; + + let upgradeRamContent, upgradeRamClass; + if (node.ram >= HacknetNodeConstants.MaxRam) { + upgradeRamContent = <>MAX RAM; + upgradeRamClass = "std-button-disabled"; + } else { + let multiplier = 0; + if (purchaseMult === "MAX") { + multiplier = getMaxNumberRamUpgrades(node, HacknetNodeConstants.MaxRam); + } else { + const levelsToMax = Math.round( + Math.log2(HacknetNodeConstants.MaxRam / node.ram), + ); + multiplier = Math.min(levelsToMax, purchaseMult); + } + + const upgradeRamCost = node.calculateRamUpgradeCost( + multiplier, + Player.hacknet_node_ram_cost_mult, + ); + upgradeRamContent = ( + <> + Upgrade x{multiplier} -{" "} + + + ); + if (Player.money.lt(upgradeRamCost)) { + upgradeRamClass = "std-button-disabled"; + } else { + upgradeRamClass = "std-button"; + } + } + const upgradeRamOnClick = () => { + let numUpgrades = purchaseMult; + if (purchaseMult === "MAX") { + numUpgrades = getMaxNumberRamUpgrades( + node, + HacknetNodeConstants.MaxRam, + ); + } + purchaseRamUpgrade(node, numUpgrades); + recalculate(); + return false; + }; + + let upgradeCoresContent, upgradeCoresClass; + if (node.cores >= HacknetNodeConstants.MaxCores) { + upgradeCoresContent = <>MAX CORES; + upgradeCoresClass = "std-button-disabled"; + } else { + let multiplier = 0; + if (purchaseMult === "MAX") { + multiplier = getMaxNumberCoreUpgrades( + node, + HacknetNodeConstants.MaxCores, + ); + } else { + const levelsToMax = HacknetNodeConstants.MaxCores - node.cores; + multiplier = Math.min(levelsToMax, purchaseMult); + } + + const upgradeCoreCost = node.calculateCoreUpgradeCost( + multiplier, + Player.hacknet_node_core_cost_mult, + ); + upgradeCoresContent = ( + <> + Upgrade x{multiplier} -{" "} + + + ); + if (Player.money.lt(upgradeCoreCost)) { + upgradeCoresClass = "std-button-disabled"; + } else { + upgradeCoresClass = "std-button"; + } + } + const upgradeCoresOnClick = () => { + let numUpgrades = purchaseMult; + if (purchaseMult === "MAX") { + numUpgrades = getMaxNumberCoreUpgrades( + node, + HacknetNodeConstants.MaxCores, + ); + } + purchaseCoreUpgrade(node, numUpgrades); + recalculate(); + return false; + }; + + return ( +
  • +
    +
    +

    {node.name}

    +
    +
    +

    Production:

    + + ( + {MoneyRate(node.moneyGainRatePerSecond)}) + +
    +
    +

    Level:

    + {node.level} + +
    +
    +

    RAM:

    + {node.ram}GB + +
    +
    +

    Cores:

    + {node.cores} + +
    +
    +
  • + ); + } } diff --git a/src/Hacknet/ui/HacknetServer.jsx b/src/Hacknet/ui/HacknetServer.jsx index 8f94133e7..cc945b072 100644 --- a/src/Hacknet/ui/HacknetServer.jsx +++ b/src/Hacknet/ui/HacknetServer.jsx @@ -6,15 +6,15 @@ import React from "react"; import { HacknetServerConstants } from "../data/Constants"; import { - getMaxNumberLevelUpgrades, - getMaxNumberRamUpgrades, - getMaxNumberCoreUpgrades, - getMaxNumberCacheUpgrades, - purchaseLevelUpgrade, - purchaseRamUpgrade, - purchaseCoreUpgrade, - purchaseCacheUpgrade, - updateHashManagerCapacity, + getMaxNumberLevelUpgrades, + getMaxNumberRamUpgrades, + getMaxNumberCoreUpgrades, + getMaxNumberCacheUpgrades, + purchaseLevelUpgrade, + purchaseRamUpgrade, + purchaseCoreUpgrade, + purchaseCacheUpgrade, + updateHashManagerCapacity, } from "../HacknetHelpers"; import { Player } from "../../Player"; @@ -24,182 +24,241 @@ import { Hashes } from "../../ui/React/Hashes"; import { HashRate } from "../../ui/React/HashRate"; export class HacknetServer extends React.Component { - render() { - const node = this.props.node; - const purchaseMult = this.props.purchaseMultiplier; - const recalculate = this.props.recalculate; + render() { + const node = this.props.node; + const purchaseMult = this.props.purchaseMultiplier; + const recalculate = this.props.recalculate; - // Upgrade Level Button - let upgradeLevelContent, upgradeLevelClass; - if (node.level >= HacknetServerConstants.MaxLevel) { - upgradeLevelContent = <>MAX LEVEL; - upgradeLevelClass = "std-button-disabled"; - } else { - let multiplier = 0; - if (purchaseMult === "MAX") { - multiplier = getMaxNumberLevelUpgrades(node, HacknetServerConstants.MaxLevel); - } else { - const levelsToMax = HacknetServerConstants.MaxLevel - node.level; - multiplier = Math.min(levelsToMax, purchaseMult); - } + // Upgrade Level Button + let upgradeLevelContent, upgradeLevelClass; + if (node.level >= HacknetServerConstants.MaxLevel) { + upgradeLevelContent = <>MAX LEVEL; + upgradeLevelClass = "std-button-disabled"; + } else { + let multiplier = 0; + if (purchaseMult === "MAX") { + multiplier = getMaxNumberLevelUpgrades( + node, + HacknetServerConstants.MaxLevel, + ); + } else { + const levelsToMax = HacknetServerConstants.MaxLevel - node.level; + multiplier = Math.min(levelsToMax, purchaseMult); + } - const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, Player.hacknet_node_level_cost_mult); - upgradeLevelContent = <>Upgrade x{multiplier} - ; - if (Player.money.lt(upgradeLevelCost)) { - upgradeLevelClass = "std-button-disabled"; - } else { - upgradeLevelClass = "std-button"; - } - } - const upgradeLevelOnClick = () => { - let numUpgrades = purchaseMult; - if (purchaseMult === "MAX") { - numUpgrades = getMaxNumberLevelUpgrades(node, HacknetServerConstants.MaxLevel); - } - purchaseLevelUpgrade(node, numUpgrades); - recalculate(); - return false; - } - - // Upgrade RAM Button - let upgradeRamContent, upgradeRamClass; - if (node.maxRam >= HacknetServerConstants.MaxRam) { - upgradeRamContent = <>MAX RAM; - upgradeRamClass = "std-button-disabled"; - } else { - let multiplier = 0; - if (purchaseMult === "MAX") { - multiplier = getMaxNumberRamUpgrades(node, HacknetServerConstants.MaxRam); - } else { - const levelsToMax = Math.round(Math.log2(HacknetServerConstants.MaxRam / node.maxRam)); - multiplier = Math.min(levelsToMax, purchaseMult); - } - - const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, Player.hacknet_node_ram_cost_mult); - upgradeRamContent = <>Upgrade x{multiplier} - ; - if (Player.money.lt(upgradeRamCost)) { - upgradeRamClass = "std-button-disabled"; - } else { - upgradeRamClass = "std-button"; - } - } - const upgradeRamOnClick = () => { - let numUpgrades = purchaseMult; - if (purchaseMult === "MAX") { - numUpgrades = getMaxNumberRamUpgrades(node, HacknetServerConstants.MaxRam); - } - purchaseRamUpgrade(node, numUpgrades); - recalculate(); - return false; - } - - // Upgrade Cores Button - let upgradeCoresContent, upgradeCoresClass; - if (node.cores >= HacknetServerConstants.MaxCores) { - upgradeCoresContent = <>MAX CORES; - upgradeCoresClass = "std-button-disabled"; - } else { - let multiplier = 0; - if (purchaseMult === "MAX") { - multiplier = getMaxNumberCoreUpgrades(node, HacknetServerConstants.MaxCores); - } else { - const levelsToMax = HacknetServerConstants.MaxCores - node.cores; - multiplier = Math.min(levelsToMax, purchaseMult); - } - - const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, Player.hacknet_node_core_cost_mult); - upgradeCoresContent = <>Upgrade x{multiplier} - ; - if (Player.money.lt(upgradeCoreCost)) { - upgradeCoresClass = "std-button-disabled"; - } else { - upgradeCoresClass = "std-button"; - } - } - const upgradeCoresOnClick = () => { - let numUpgrades = purchaseMult; - if (purchaseMult === "MAX") { - numUpgrades = getMaxNumberCoreUpgrades(node, HacknetServerConstants.MaxCores); - } - purchaseCoreUpgrade(node, numUpgrades); - recalculate(); - return false; - } - - // Upgrade Cache button - let upgradeCacheContent, upgradeCacheClass; - if (node.cache >= HacknetServerConstants.MaxCache) { - upgradeCacheContent = <>MAX CACHE; - upgradeCacheClass = "std-button-disabled"; - } else { - let multiplier = 0; - if (purchaseMult === "MAX") { - multiplier = getMaxNumberCacheUpgrades(node, HacknetServerConstants.MaxCache); - } else { - const levelsToMax = HacknetServerConstants.MaxCache - node.cache; - multiplier = Math.min(levelsToMax, purchaseMult); - } - - const upgradeCacheCost = node.calculateCacheUpgradeCost(multiplier); - upgradeCacheContent = <>Upgrade x{multiplier} - ; - if (Player.money.lt(upgradeCacheCost)) { - upgradeCacheClass = "std-button-disabled"; - } else { - upgradeCacheClass = "std-button"; - } - } - const upgradeCacheOnClick = () => { - let numUpgrades = purchaseMult; - if (purchaseMult === "MAX") { - numUpgrades = getMaxNumberCacheUpgrades(node, HacknetServerConstants.MaxCache); - } - purchaseCacheUpgrade(node, numUpgrades); - recalculate(); - updateHashManagerCapacity(); - return false; - } - - return ( -
  • -
    -
    -

    {node.hostname}

    -
    -
    -

    Production:

    - - {Hashes(node.totalHashesGenerated)} ({HashRate(node.hashRate)}) - -
    -
    -

    Hash Capacity:

    - {Hashes(node.hashCapacity)} -
    -
    -

    Level:

    {node.level} - -
    -
    -

    RAM:

    {node.maxRam}GB - -
    -
    -

    Cores:

    {node.cores} - -
    -
    -

    Cache Level:

    {node.cache} - -
    -
    -
  • - ) + const upgradeLevelCost = node.calculateLevelUpgradeCost( + multiplier, + Player.hacknet_node_level_cost_mult, + ); + upgradeLevelContent = ( + <> + Upgrade x{multiplier} -{" "} + + + ); + if (Player.money.lt(upgradeLevelCost)) { + upgradeLevelClass = "std-button-disabled"; + } else { + upgradeLevelClass = "std-button"; + } } + const upgradeLevelOnClick = () => { + let numUpgrades = purchaseMult; + if (purchaseMult === "MAX") { + numUpgrades = getMaxNumberLevelUpgrades( + node, + HacknetServerConstants.MaxLevel, + ); + } + purchaseLevelUpgrade(node, numUpgrades); + recalculate(); + return false; + }; + + // Upgrade RAM Button + let upgradeRamContent, upgradeRamClass; + if (node.maxRam >= HacknetServerConstants.MaxRam) { + upgradeRamContent = <>MAX RAM; + upgradeRamClass = "std-button-disabled"; + } else { + let multiplier = 0; + if (purchaseMult === "MAX") { + multiplier = getMaxNumberRamUpgrades( + node, + HacknetServerConstants.MaxRam, + ); + } else { + const levelsToMax = Math.round( + Math.log2(HacknetServerConstants.MaxRam / node.maxRam), + ); + multiplier = Math.min(levelsToMax, purchaseMult); + } + + const upgradeRamCost = node.calculateRamUpgradeCost( + multiplier, + Player.hacknet_node_ram_cost_mult, + ); + upgradeRamContent = ( + <> + Upgrade x{multiplier} -{" "} + + + ); + if (Player.money.lt(upgradeRamCost)) { + upgradeRamClass = "std-button-disabled"; + } else { + upgradeRamClass = "std-button"; + } + } + const upgradeRamOnClick = () => { + let numUpgrades = purchaseMult; + if (purchaseMult === "MAX") { + numUpgrades = getMaxNumberRamUpgrades( + node, + HacknetServerConstants.MaxRam, + ); + } + purchaseRamUpgrade(node, numUpgrades); + recalculate(); + return false; + }; + + // Upgrade Cores Button + let upgradeCoresContent, upgradeCoresClass; + if (node.cores >= HacknetServerConstants.MaxCores) { + upgradeCoresContent = <>MAX CORES; + upgradeCoresClass = "std-button-disabled"; + } else { + let multiplier = 0; + if (purchaseMult === "MAX") { + multiplier = getMaxNumberCoreUpgrades( + node, + HacknetServerConstants.MaxCores, + ); + } else { + const levelsToMax = HacknetServerConstants.MaxCores - node.cores; + multiplier = Math.min(levelsToMax, purchaseMult); + } + + const upgradeCoreCost = node.calculateCoreUpgradeCost( + multiplier, + Player.hacknet_node_core_cost_mult, + ); + upgradeCoresContent = ( + <> + Upgrade x{multiplier} -{" "} + + + ); + if (Player.money.lt(upgradeCoreCost)) { + upgradeCoresClass = "std-button-disabled"; + } else { + upgradeCoresClass = "std-button"; + } + } + const upgradeCoresOnClick = () => { + let numUpgrades = purchaseMult; + if (purchaseMult === "MAX") { + numUpgrades = getMaxNumberCoreUpgrades( + node, + HacknetServerConstants.MaxCores, + ); + } + purchaseCoreUpgrade(node, numUpgrades); + recalculate(); + return false; + }; + + // Upgrade Cache button + let upgradeCacheContent, upgradeCacheClass; + if (node.cache >= HacknetServerConstants.MaxCache) { + upgradeCacheContent = <>MAX CACHE; + upgradeCacheClass = "std-button-disabled"; + } else { + let multiplier = 0; + if (purchaseMult === "MAX") { + multiplier = getMaxNumberCacheUpgrades( + node, + HacknetServerConstants.MaxCache, + ); + } else { + const levelsToMax = HacknetServerConstants.MaxCache - node.cache; + multiplier = Math.min(levelsToMax, purchaseMult); + } + + const upgradeCacheCost = node.calculateCacheUpgradeCost(multiplier); + upgradeCacheContent = ( + <> + Upgrade x{multiplier} -{" "} + + + ); + if (Player.money.lt(upgradeCacheCost)) { + upgradeCacheClass = "std-button-disabled"; + } else { + upgradeCacheClass = "std-button"; + } + } + const upgradeCacheOnClick = () => { + let numUpgrades = purchaseMult; + if (purchaseMult === "MAX") { + numUpgrades = getMaxNumberCacheUpgrades( + node, + HacknetServerConstants.MaxCache, + ); + } + purchaseCacheUpgrade(node, numUpgrades); + recalculate(); + updateHashManagerCapacity(); + return false; + }; + + return ( +
  • +
    +
    +

    {node.hostname}

    +
    +
    +

    Production:

    + + {Hashes(node.totalHashesGenerated)} ({HashRate(node.hashRate)}) + +
    +
    +

    Hash Capacity:

    + {Hashes(node.hashCapacity)} +
    +
    +

    Level:

    + {node.level} + +
    +
    +

    RAM:

    + {node.maxRam}GB + +
    +
    +

    Cores:

    + {node.cores} + +
    +
    +

    Cache Level:

    + {node.cache} + +
    +
    +
  • + ); + } } diff --git a/src/Hacknet/ui/HashUpgradePopup.jsx b/src/Hacknet/ui/HashUpgradePopup.jsx index a04b16d17..3567b86da 100644 --- a/src/Hacknet/ui/HashUpgradePopup.jsx +++ b/src/Hacknet/ui/HashUpgradePopup.jsx @@ -3,129 +3,143 @@ */ import React from "react"; -import { purchaseHashUpgrade } from "../HacknetHelpers"; -import { HashManager } from "../HashManager"; -import { HashUpgrades } from "../HashUpgrades"; +import { purchaseHashUpgrade } from "../HacknetHelpers"; +import { HashManager } from "../HashManager"; +import { HashUpgrades } from "../HashUpgrades"; -import { Player } from "../../Player"; +import { Player } from "../../Player"; -import { numeralWrapper } from "../../ui/numeralFormat"; +import { numeralWrapper } from "../../ui/numeralFormat"; -import { ServerDropdown, - ServerType } from "../../ui/React/ServerDropdown" +import { ServerDropdown, ServerType } from "../../ui/React/ServerDropdown"; -import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; import { CopyableText } from "../../ui/React/CopyableText"; import { Hashes } from "../../ui/React/Hashes"; class HashUpgrade extends React.Component { - constructor(props) { - super(props); + constructor(props) { + super(props); - this.state = { - selectedServer: "ecorp", - } + this.state = { + selectedServer: "ecorp", + }; - this.changeTargetServer = this.changeTargetServer.bind(this); - this.purchase = this.purchase.bind(this, this.props.hashManager, this.props.upg); + this.changeTargetServer = this.changeTargetServer.bind(this); + this.purchase = this.purchase.bind( + this, + this.props.hashManager, + this.props.upg, + ); + } + + changeTargetServer(e) { + this.setState({ + selectedServer: e.target.value, + }); + } + + purchase(hashManager, upg) { + const canPurchase = + hashManager.hashes >= hashManager.getUpgradeCost(upg.name); + if (canPurchase) { + const res = purchaseHashUpgrade(upg.name, this.state.selectedServer); + if (res) { + this.props.rerender(); + } else { + dialogBoxCreate( + "Failed to purchase upgrade. This may be because you do not have enough hashes, " + + "or because you do not have access to the feature this upgrade affects.", + ); + } } + } - changeTargetServer(e) { - this.setState({ - selectedServer: e.target.value, - }); - } + render() { + const hashManager = this.props.hashManager; + const upg = this.props.upg; + const cost = hashManager.getUpgradeCost(upg.name); + const level = hashManager.upgrades[upg.name]; + const effect = upg.effectText(level); - purchase(hashManager, upg) { - const canPurchase = hashManager.hashes >= hashManager.getUpgradeCost(upg.name); - if (canPurchase) { - const res = purchaseHashUpgrade(upg.name, this.state.selectedServer); - if (res) { - this.props.rerender(); - } else { - dialogBoxCreate("Failed to purchase upgrade. This may be because you do not have enough hashes, " + - "or because you do not have access to the feature this upgrade affects."); - } - } - } + // Purchase button + const canPurchase = hashManager.hashes >= cost; + const btnClass = canPurchase ? "std-button" : "std-button-disabled"; - render() { - const hashManager = this.props.hashManager; - const upg = this.props.upg; - const cost = hashManager.getUpgradeCost(upg.name); - const level = hashManager.upgrades[upg.name]; - const effect = upg.effectText(level); + // We'll reuse a Bladeburner css class + return ( +
    + +

    + Cost: {Hashes(cost)}, Bought: {level} times +

    - // Purchase button - const canPurchase = hashManager.hashes >= cost; - const btnClass = canPurchase ? "std-button" : "std-button-disabled"; - - // We'll reuse a Bladeburner css class - return ( -
    - -

    Cost: {Hashes(cost)}, Bought: {level} times

    - -

    {upg.desc}

    - - {level > 0 && effect &&

    {effect}

    } - { - upg.hasTargetServer && - - } -
    - ) - } +

    {upg.desc}

    + + {level > 0 && effect &&

    {effect}

    } + {upg.hasTargetServer && ( + + )} +
    + ); + } } export class HashUpgradePopup extends React.Component { - constructor(props) { - super(props); + constructor(props) { + super(props); - this.state = { - totalHashes: Player.hashManager.hashes, - } + this.state = { + totalHashes: Player.hashManager.hashes, + }; + } + + componentDidMount() { + this.interval = setInterval(() => this.tick(), 1e3); + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + tick() { + this.setState({ + totalHashes: Player.hashManager.hashes, + }); + } + + render() { + const rerender = this.props.rerender; + + const hashManager = Player.hashManager; + if (!(hashManager instanceof HashManager)) { + throw new Error(`Player does not have a HashManager)`); } - componentDidMount() { - this.interval = setInterval(() => this.tick(), 1e3); - } + const upgradeElems = Object.keys(HashUpgrades).map((upgName) => { + const upg = HashUpgrades[upgName]; + return ( + + ); + }); - componentWillUnmount() { - clearInterval(this.interval); - } - - tick() { - this.setState({ - totalHashes: Player.hashManager.hashes, - }) - } - - render() { - const rerender = this.props.rerender; - - const hashManager = Player.hashManager; - if (!(hashManager instanceof HashManager)) { - throw new Error(`Player does not have a HashManager)`); - } - - const upgradeElems = Object.keys(HashUpgrades).map((upgName) => { - const upg = HashUpgrades[upgName]; - return - }); - - return ( -
    -

    Spend your hashes on a variety of different upgrades

    -

    Hashes: {numeralWrapper.formatHashes(this.state.totalHashes)}

    - {upgradeElems} -
    - ) - } + return ( +
    +

    Spend your hashes on a variety of different upgrades

    +

    Hashes: {numeralWrapper.formatHashes(this.state.totalHashes)}

    + {upgradeElems} +
    + ); + } } diff --git a/src/Hacknet/ui/MultiplierButtons.jsx b/src/Hacknet/ui/MultiplierButtons.jsx index 3fe12477c..ed46d21c3 100644 --- a/src/Hacknet/ui/MultiplierButtons.jsx +++ b/src/Hacknet/ui/MultiplierButtons.jsx @@ -8,34 +8,35 @@ import React from "react"; import { PurchaseMultipliers } from "./Root"; function MultiplierButton(props) { - return ( - - ) + return ( + + ); } export function MultiplierButtons(props) { - if (props.purchaseMultiplier == null) { - throw new Error(`MultiplierButtons constructed without required props`); - } + if (props.purchaseMultiplier == null) { + throw new Error(`MultiplierButtons constructed without required props`); + } - const mults = ["x1", "x5", "x10", "MAX"]; - const onClicks = props.onClicks; - const buttons = []; - for (let i = 0; i < mults.length; ++i) { - const mult = mults[i]; - const btnProps = { - className: props.purchaseMultiplier === PurchaseMultipliers[mult] ? "std-button-disabled" : "std-button", - key: mult, - onClick: onClicks[i], - text: mult, - } + const mults = ["x1", "x5", "x10", "MAX"]; + const onClicks = props.onClicks; + const buttons = []; + for (let i = 0; i < mults.length; ++i) { + const mult = mults[i]; + const btnProps = { + className: + props.purchaseMultiplier === PurchaseMultipliers[mult] + ? "std-button-disabled" + : "std-button", + key: mult, + onClick: onClicks[i], + text: mult, + }; - buttons.push() - } + buttons.push(); + } - return ( - - {buttons} - - ) + return {buttons}; } diff --git a/src/Hacknet/ui/PlayerInfo.jsx b/src/Hacknet/ui/PlayerInfo.jsx index 06bc057f9..f88dc88c2 100644 --- a/src/Hacknet/ui/PlayerInfo.jsx +++ b/src/Hacknet/ui/PlayerInfo.jsx @@ -14,26 +14,33 @@ import { HashRate } from "../../ui/React/HashRate"; import { Hashes } from "../../ui/React/Hashes"; export function PlayerInfo(props) { - const hasServers = hasHacknetServers(); + const hasServers = hasHacknetServers(); - let prod; - if (hasServers) { - prod = HashRate(props.totalProduction); - } else { - prod = MoneyRate(props.totalProduction); - } + let prod; + if (hasServers) { + prod = HashRate(props.totalProduction); + } else { + prod = MoneyRate(props.totalProduction); + } - return ( -

    - Money: -
    + return ( +

    + Money: + +
    - { - hasServers && <>Hashes: {Hashes(Player.hashManager.hashes)} / {Hashes(Player.hashManager.capacity)}
    - } + {hasServers && ( + <> + + Hashes: {Hashes(Player.hashManager.hashes)} /{" "} + {Hashes(Player.hashManager.capacity)} + +
    + + )} - Total Hacknet {hasServers ? 'Server' : 'Node'} Production: - {prod} -

    - ) + Total Hacknet {hasServers ? "Server" : "Node"} Production: + {prod} +

    + ); } diff --git a/src/Hacknet/ui/PurchaseButton.jsx b/src/Hacknet/ui/PurchaseButton.jsx index 3695ac009..7f3bcd44f 100644 --- a/src/Hacknet/ui/PurchaseButton.jsx +++ b/src/Hacknet/ui/PurchaseButton.jsx @@ -3,38 +3,45 @@ */ import React from "react"; -import { hasHacknetServers, - hasMaxNumberHacknetServers } from "../HacknetHelpers"; +import { + hasHacknetServers, + hasMaxNumberHacknetServers, +} from "../HacknetHelpers"; import { Player } from "../../Player"; import { Money } from "../../ui/React/Money"; export function PurchaseButton(props) { - if (props.multiplier == null || props.onClick == null) { - throw new Error(`PurchaseButton constructed without required props`); - } + if (props.multiplier == null || props.onClick == null) { + throw new Error(`PurchaseButton constructed without required props`); + } - const cost = props.cost; - let className = Player.canAfford(cost) ? "std-button" : "std-button-disabled"; - let text; - let style = null; - if (hasHacknetServers()) { - if (hasMaxNumberHacknetServers()) { - className = "std-button-disabled"; - text = <>Hacknet Server limit reached; - style = {color: "red"}; - } else { - text = <>Purchase Hacknet Server - ; - } + const cost = props.cost; + let className = Player.canAfford(cost) ? "std-button" : "std-button-disabled"; + let text; + let style = null; + if (hasHacknetServers()) { + if (hasMaxNumberHacknetServers()) { + className = "std-button-disabled"; + text = <>Hacknet Server limit reached; + style = { color: "red" }; } else { - text = <>Purchase Hacknet Node - ; + text = ( + <> + Purchase Hacknet Server - + + ); } + } else { + text = ( + <> + Purchase Hacknet Node - + + ); + } - return ( - - - ) + return ( + + ); } diff --git a/src/Hacknet/ui/Root.jsx b/src/Hacknet/ui/Root.jsx index 008289033..cd00b8501 100644 --- a/src/Hacknet/ui/Root.jsx +++ b/src/Hacknet/ui/Root.jsx @@ -11,10 +11,12 @@ import { MultiplierButtons } from "./MultiplierButtons"; import { PlayerInfo } from "./PlayerInfo"; import { PurchaseButton } from "./PurchaseButton"; -import { getCostOfNextHacknetNode, - getCostOfNextHacknetServer, - hasHacknetServers, - purchaseHacknet } from "../HacknetHelpers"; +import { + getCostOfNextHacknetNode, + getCostOfNextHacknetServer, + hasHacknetServers, + purchaseHacknet, +} from "../HacknetHelpers"; import { Player } from "../../Player"; import { AllServers } from "../../Server/AllServers"; @@ -22,133 +24,151 @@ import { AllServers } from "../../Server/AllServers"; import { createPopup } from "../../ui/React/createPopup"; export const PurchaseMultipliers = Object.freeze({ - "x1": 1, - "x5": 5, - "x10": 10, - "MAX": "MAX", + x1: 1, + x5: 5, + x10: 10, + MAX: "MAX", }); export class HacknetRoot extends React.Component { - constructor(props) { - super(props); + constructor(props) { + super(props); - this.state = { - purchaseMultiplier: PurchaseMultipliers.x1, - totalProduction: 0, // Total production ($ / s) of Hacknet Nodes - } + this.state = { + purchaseMultiplier: PurchaseMultipliers.x1, + totalProduction: 0, // Total production ($ / s) of Hacknet Nodes + }; - this.createHashUpgradesPopup = this.createHashUpgradesPopup.bind(this); - this.handlePurchaseButtonClick = this.handlePurchaseButtonClick.bind(this); - this.recalculateTotalProduction = this.recalculateTotalProduction.bind(this); + this.createHashUpgradesPopup = this.createHashUpgradesPopup.bind(this); + this.handlePurchaseButtonClick = this.handlePurchaseButtonClick.bind(this); + this.recalculateTotalProduction = + this.recalculateTotalProduction.bind(this); + } + + componentDidMount() { + this.recalculateTotalProduction(); + } + + createHashUpgradesPopup() { + const id = "hacknet-server-hash-upgrades-popup"; + createPopup(id, HashUpgradePopup, { + popupId: id, + rerender: this.createHashUpgradesPopup, + }); + } + + handlePurchaseButtonClick() { + if (purchaseHacknet() >= 0) { + this.recalculateTotalProduction(); } + } - componentDidMount() { - this.recalculateTotalProduction(); - } - - createHashUpgradesPopup() { - const id = "hacknet-server-hash-upgrades-popup"; - createPopup(id, HashUpgradePopup, { popupId: id, rerender: this.createHashUpgradesPopup }); - } - - handlePurchaseButtonClick() { - if (purchaseHacknet() >= 0) { - this.recalculateTotalProduction(); - } - } - - recalculateTotalProduction() { - let total = 0; - for (let i = 0; i < Player.hacknetNodes.length; ++i) { - if (hasHacknetServers()) { - const hserver = AllServers[Player.hacknetNodes[i]]; - if (hserver) { - total += hserver.hashRate; - } else { - console.warn(`Could not find Hacknet Server object in AllServers map (i=${i})`) - } - } else { - total += Player.hacknetNodes[i].moneyGainRatePerSecond; - } - } - - this.setState({ - totalProduction: total, - }); - } - - setPurchaseMultiplier(mult) { - this.setState({ - purchaseMultiplier: mult, - }); - } - - render() { - // Cost to purchase a new Hacknet Node - let purchaseCost; - if (hasHacknetServers()) { - purchaseCost = getCostOfNextHacknetServer(); + recalculateTotalProduction() { + let total = 0; + for (let i = 0; i < Player.hacknetNodes.length; ++i) { + if (hasHacknetServers()) { + const hserver = AllServers[Player.hacknetNodes[i]]; + if (hserver) { + total += hserver.hashRate; } else { - purchaseCost = getCostOfNextHacknetNode(); + console.warn( + `Could not find Hacknet Server object in AllServers map (i=${i})`, + ); } - - // onClick event handlers for purchase multiplier buttons - const purchaseMultiplierOnClicks = [ - this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x1), - this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x5), - this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x10), - this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.MAX), - ]; - - // HacknetNode components - const nodes = Player.hacknetNodes.map((node) => { - if (hasHacknetServers()) { - const hserver = AllServers[node]; - if (hserver == null) { - throw new Error(`Could not find Hacknet Server object in AllServers map for IP: ${node}`); - } - return ( - - ) - } else { - return ( - - ) - } - }); - - return ( -
    -

    Hacknet {hasHacknetServers() ? "Servers" : "Nodes"}

    - - - - -
    -
    - - -
    - - { - hasHacknetServers() && - - } - -
      {nodes}
    -
    - ) + } else { + total += Player.hacknetNodes[i].moneyGainRatePerSecond; + } } + + this.setState({ + totalProduction: total, + }); + } + + setPurchaseMultiplier(mult) { + this.setState({ + purchaseMultiplier: mult, + }); + } + + render() { + // Cost to purchase a new Hacknet Node + let purchaseCost; + if (hasHacknetServers()) { + purchaseCost = getCostOfNextHacknetServer(); + } else { + purchaseCost = getCostOfNextHacknetNode(); + } + + // onClick event handlers for purchase multiplier buttons + const purchaseMultiplierOnClicks = [ + this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x1), + this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x5), + this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x10), + this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.MAX), + ]; + + // HacknetNode components + const nodes = Player.hacknetNodes.map((node) => { + if (hasHacknetServers()) { + const hserver = AllServers[node]; + if (hserver == null) { + throw new Error( + `Could not find Hacknet Server object in AllServers map for IP: ${node}`, + ); + } + return ( + + ); + } else { + return ( + + ); + } + }); + + return ( +
    +

    Hacknet {hasHacknetServers() ? "Servers" : "Nodes"}

    + + + + +
    +
    + + +
    + + {hasHacknetServers() && ( + + )} + +
      {nodes}
    +
    + ); + } } diff --git a/src/Hospital/Hospital.ts b/src/Hospital/Hospital.ts index 5400b5944..7682dcb14 100644 --- a/src/Hospital/Hospital.ts +++ b/src/Hospital/Hospital.ts @@ -1,25 +1,28 @@ import { CONSTANTS } from "../Constants"; -import { IPlayer } from "../PersonObjects/IPlayer" +import { IPlayer } from "../PersonObjects/IPlayer"; export function getHospitalizationCost(p: IPlayer): number { - let money; - if (typeof p.money === 'number') { - money = p.money; - } else { - money = p.money.toNumber(); - } + let money; + if (typeof p.money === "number") { + money = p.money; + } else { + money = p.money.toNumber(); + } - if (money < 0) { - return 0; - } + if (money < 0) { + return 0; + } - return Math.min(money*0.1, (p.max_hp - p.hp) * CONSTANTS.HospitalCostPerHp); + return Math.min(money * 0.1, (p.max_hp - p.hp) * CONSTANTS.HospitalCostPerHp); } -export function calculateHospitalizationCost(p: IPlayer, damage: number): number { - const oldhp = p.hp; - p.hp -= damage - const cost = getHospitalizationCost(p); - p.hp = oldhp; - return cost; -} \ No newline at end of file +export function calculateHospitalizationCost( + p: IPlayer, + damage: number, +): number { + const oldhp = p.hp; + p.hp -= damage; + const cost = getHospitalizationCost(p); + p.hp = oldhp; + return cost; +} diff --git a/src/IEngine.ts b/src/IEngine.ts index a2961b683..d22bb7acb 100644 --- a/src/IEngine.ts +++ b/src/IEngine.ts @@ -3,15 +3,19 @@ * to TypeScript at the moment */ export interface IEngine { - hideAllContent: () => void; - loadBladeburnerContent: () => void; - loadFactionContent: () => void; - loadFactionsContent: () => void; - loadGangContent: () => void; - loadInfiltrationContent: (name: string, difficulty: number, maxLevel: number) => void; - loadLocationContent: () => void; - loadMissionContent: () => void; - loadResleevingContent: () => void; - loadStockMarketContent: () => void; - loadTerminalContent: () => void; + hideAllContent: () => void; + loadBladeburnerContent: () => void; + loadFactionContent: () => void; + loadFactionsContent: () => void; + loadGangContent: () => void; + loadInfiltrationContent: ( + name: string, + difficulty: number, + maxLevel: number, + ) => void; + loadLocationContent: () => void; + loadMissionContent: () => void; + loadResleevingContent: () => void; + loadStockMarketContent: () => void; + loadTerminalContent: () => void; } diff --git a/src/Infiltration/Helper.tsx b/src/Infiltration/Helper.tsx index aa8a151e2..c07ab3de2 100644 --- a/src/Infiltration/Helper.tsx +++ b/src/Infiltration/Helper.tsx @@ -5,44 +5,55 @@ import { IEngine } from "../IEngine"; import * as React from "react"; import * as ReactDOM from "react-dom"; -let container: HTMLElement = document.createElement('div'); +let container: HTMLElement = document.createElement("div"); -(function() { - function setContainer(): void { - const c = document.getElementById("infiltration-container"); - if(c === null) throw new Error("huh?"); - container = c; - document.removeEventListener("DOMContentLoaded", setContainer); - } +(function () { + function setContainer(): void { + const c = document.getElementById("infiltration-container"); + if (c === null) throw new Error("huh?"); + container = c; + document.removeEventListener("DOMContentLoaded", setContainer); + } - document.addEventListener("DOMContentLoaded", setContainer); + document.addEventListener("DOMContentLoaded", setContainer); })(); function calcDifficulty(player: IPlayer, startingDifficulty: number): number { - const totalStats = player.strength+ - player.defense+ - player.dexterity+ - player.agility+ - player.charisma; - const difficulty = startingDifficulty- - Math.pow(totalStats, 0.9)/250- - (player.intelligence/1600); - if(difficulty < 0) return 0; - if(difficulty > 3) return 3; - return difficulty; + const totalStats = + player.strength + + player.defense + + player.dexterity + + player.agility + + player.charisma; + const difficulty = + startingDifficulty - + Math.pow(totalStats, 0.9) / 250 - + player.intelligence / 1600; + if (difficulty < 0) return 0; + if (difficulty > 3) return 3; + return difficulty; } -export function displayInfiltrationContent(engine: IEngine, player: IPlayer, location: string, startingDifficulty: number, maxLevel: number): void { - if (!routing.isOn(Page.Infiltration)) return; +export function displayInfiltrationContent( + engine: IEngine, + player: IPlayer, + location: string, + startingDifficulty: number, + maxLevel: number, +): void { + if (!routing.isOn(Page.Infiltration)) return; - const difficulty = calcDifficulty(player, startingDifficulty); + const difficulty = calcDifficulty(player, startingDifficulty); - ReactDOM.render(, container); -} \ No newline at end of file + ReactDOM.render( + , + container, + ); +} diff --git a/src/Infiltration/ui/BackwardGame.tsx b/src/Infiltration/ui/BackwardGame.tsx index 7dcaf4254..5ee67339f 100644 --- a/src/Infiltration/ui/BackwardGame.tsx +++ b/src/Infiltration/ui/BackwardGame.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import Grid from '@material-ui/core/Grid'; +import React, { useState } from "react"; +import Grid from "@material-ui/core/Grid"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; import { GameTimer } from "./GameTimer"; @@ -8,100 +8,304 @@ import { interpolate } from "./Difficulty"; import { BlinkingCursor } from "./BlinkingCursor"; interface Difficulty { - [key: string]: number; - timer: number; - min: number; - max: number; + [key: string]: number; + timer: number; + min: number; + max: number; } const difficulties: { - Trivial: Difficulty; - Normal: Difficulty; - Hard: Difficulty; - Impossible: Difficulty; + Trivial: Difficulty; + Normal: Difficulty; + Hard: Difficulty; + Impossible: Difficulty; } = { - Trivial: {timer: 16000, min: 3, max: 4}, - Normal: {timer: 12500, min: 2, max: 3}, - Hard: {timer: 15000, min: 3, max: 4}, - Impossible: {timer: 8000, min: 4, max: 4}, -} + Trivial: { timer: 16000, min: 3, max: 4 }, + Normal: { timer: 12500, min: 2, max: 3 }, + Hard: { timer: 15000, min: 3, max: 4 }, + Impossible: { timer: 8000, min: 4, max: 4 }, +}; export function BackwardGame(props: IMinigameProps): React.ReactElement { - const difficulty: Difficulty = {timer: 0, min: 0, max: 0}; - interpolate(difficulties, props.difficulty, difficulty); - const timer = difficulty.timer; - const [answer] = useState(makeAnswer(difficulty)); - const [guess, setGuess] = useState(""); + const difficulty: Difficulty = { timer: 0, min: 0, max: 0 }; + interpolate(difficulties, props.difficulty, difficulty); + const timer = difficulty.timer; + const [answer] = useState(makeAnswer(difficulty)); + const [guess, setGuess] = useState(""); - function press(event: React.KeyboardEvent): void { - event.preventDefault(); - if(event.keyCode === 16) return; - const nextGuess = guess + event.key.toUpperCase(); - if(!answer.startsWith(nextGuess)) props.onFailure(); - else if (answer === nextGuess) props.onSuccess(); - else setGuess(nextGuess); - } + function press(event: React.KeyboardEvent): void { + event.preventDefault(); + if (event.keyCode === 16) return; + const nextGuess = guess + event.key.toUpperCase(); + if (!answer.startsWith(nextGuess)) props.onFailure(); + else if (answer === nextGuess) props.onSuccess(); + else setGuess(nextGuess); + } - return ( - - -

    Type it backward

    - -
    - -

    {answer}

    -
    - -

    {guess}

    -
    -
    ) + return ( + + + +

    Type it backward

    + +
    + +

    {answer}

    +
    + +

    + {guess} + +

    +
    +
    + ); } function makeAnswer(difficulty: Difficulty): string { - const length = random(difficulty.min, difficulty.max); - let answer = ""; - for(let i = 0; i < length; i++) { - if(i > 0) answer += " " - answer += words[Math.floor(Math.random() * words.length)]; - } + const length = random(difficulty.min, difficulty.max); + let answer = ""; + for (let i = 0; i < length; i++) { + if (i > 0) answer += " "; + answer += words[Math.floor(Math.random() * words.length)]; + } - return answer; + return answer; } -const words = ["ALGORITHM", "ANALOG", "APP", "APPLICATION", "ARRAY", "BACKUP", - "BANDWIDTH", "BINARY", "BIT", "BITE", "BITMAP", "BLOG", "BLOGGER", - "BOOKMARK", "BOOT", "BROADBAND", "BROWSER", "BUFFER", "BUG", "BUS", "BYTE", - "CACHE", "CAPS LOCK", "CAPTCHA", "CD", "CD-ROM", "CLIENT", - "CLIPBOARD", "CLOUD", "COMPUTING", "COMMAND", "COMPILE", "COMPRESS", - "COMPUTER", "CONFIGURE", "COOKIE", "COPY", "CPU", - "CYBERCRIME", "CYBERSPACE", "DASHBOARD", "DATA", "MINING", "DATABASE", - "DEBUG", "DECOMPRESS", "DELETE", "DESKTOP", "DEVELOPMENT", "DIGITAL", - "DISK", "DNS", "DOCUMENT", "DOMAIN", "DOMAIN NAME", "DOT", "DOT MATRIX", - "DOWNLOAD", "DRAG", "DVD", "DYNAMIC", "EMAIL", "EMOTICON", "ENCRYPT", - "ENCRYPTION", "ENTER", "EXABYTE", "FAQ", "FILE", "FINDER", "FIREWALL", - "FIRMWARE", "FLAMING", "FLASH", "FLASH DRIVE", "FLOPPY DISK", "FLOWCHART", - "FOLDER", "FONT", "FORMAT", "FRAME", "FREEWARE", "GIGABYTE", "GRAPHICS", - "HACK", "HACKER", "HARDWARE", "HOME PAGE", "HOST", "HTML", "HYPERLINK", - "HYPERTEXT", "ICON", "INBOX", "INTEGER", "INTERFACE", "INTERNET", - "IP ADDRESS", "ITERATION", "JAVA", "JOYSTICK", "JUNKMAIL", "KERNEL", - "KEY", "KEYBOARD", "KEYWORD", "LAPTOP", "LASER PRINTER", "LINK", "LINUX", - "LOG OUT", "LOGIC", "LOGIN", "LURKING", "MACINTOSH", "MACRO", "MAINFRAME", - "MALWARE", "MEDIA", "MEMORY", "MIRROR", "MODEM", "MONITOR", "MOTHERBOARD", - "MOUSE", "MULTIMEDIA", "NET", "NETWORK", "NODE", "NOTEBOOK", "COMPUTER", - "OFFLINE", "ONLINE", "OPENSOURCE", "OPERATING", "SYSTEM", "OPTION", "OUTPUT", - "PAGE", "PASSWORD", "PASTE", "PATH", "PHISHING", "PIRACY", "PIRATE", - "PLATFORM", "PLUGIN", "PODCAST", "POPUP", "PORTAL", "PRINT", "PRINTER", - "PRIVACY", "PROCESS", "PROGRAM", "PROGRAMMER", "PROTOCOL", "QUEUE", - "QWERTY", "RAM", "REALTIME", "REBOOT", "RESOLUTION", "RESTORE", "ROM", - "ROOT", "ROUTER", "RUNTIME", "SAVE", "SCAN", "SCANNER", "SCREEN", - "SCREENSHOT", "SCRIPT", "SCROLL", "SCROLL", "SEARCH", "ENGINE", - "SECURITY", "SERVER", "SHAREWARE", "SHELL", "SHIFT", "SHIFT KEY", - "SNAPSHOT", "SOCIAL NETWORKING", "SOFTWARE", "SPAM", "SPAMMER", - "SPREADSHEET", "SPYWARE", "STATUS", "STORAGE", "SUPERCOMPUTER", "SURF", - "SYNTAX", "TABLE", "TAG", "TERMINAL", "TEMPLATE", "TERABYTE", "TEXT EDITOR", - "THREAD", "TOOLBAR", "TRASH", "TROJAN HORSE", "TYPEFACE", "UNDO", "UNIX", - "UPLOAD", "URL", "USER", "USER INTERFACE", "USERNAME", "UTILITY", "VERSION", - "VIRTUAL", "VIRTUAL MEMORY", "VIRUS", "WEB", "WEBMASTER", - "WEBSITE", "WIDGET", "WIKI", "WINDOW", "WINDOWS", "WIRELESS", - "PROCESSOR", "WORKSTATION", "WEB", "WORM", "WWW", "XML", - "ZIP"]; \ No newline at end of file +const words = [ + "ALGORITHM", + "ANALOG", + "APP", + "APPLICATION", + "ARRAY", + "BACKUP", + "BANDWIDTH", + "BINARY", + "BIT", + "BITE", + "BITMAP", + "BLOG", + "BLOGGER", + "BOOKMARK", + "BOOT", + "BROADBAND", + "BROWSER", + "BUFFER", + "BUG", + "BUS", + "BYTE", + "CACHE", + "CAPS LOCK", + "CAPTCHA", + "CD", + "CD-ROM", + "CLIENT", + "CLIPBOARD", + "CLOUD", + "COMPUTING", + "COMMAND", + "COMPILE", + "COMPRESS", + "COMPUTER", + "CONFIGURE", + "COOKIE", + "COPY", + "CPU", + "CYBERCRIME", + "CYBERSPACE", + "DASHBOARD", + "DATA", + "MINING", + "DATABASE", + "DEBUG", + "DECOMPRESS", + "DELETE", + "DESKTOP", + "DEVELOPMENT", + "DIGITAL", + "DISK", + "DNS", + "DOCUMENT", + "DOMAIN", + "DOMAIN NAME", + "DOT", + "DOT MATRIX", + "DOWNLOAD", + "DRAG", + "DVD", + "DYNAMIC", + "EMAIL", + "EMOTICON", + "ENCRYPT", + "ENCRYPTION", + "ENTER", + "EXABYTE", + "FAQ", + "FILE", + "FINDER", + "FIREWALL", + "FIRMWARE", + "FLAMING", + "FLASH", + "FLASH DRIVE", + "FLOPPY DISK", + "FLOWCHART", + "FOLDER", + "FONT", + "FORMAT", + "FRAME", + "FREEWARE", + "GIGABYTE", + "GRAPHICS", + "HACK", + "HACKER", + "HARDWARE", + "HOME PAGE", + "HOST", + "HTML", + "HYPERLINK", + "HYPERTEXT", + "ICON", + "INBOX", + "INTEGER", + "INTERFACE", + "INTERNET", + "IP ADDRESS", + "ITERATION", + "JAVA", + "JOYSTICK", + "JUNKMAIL", + "KERNEL", + "KEY", + "KEYBOARD", + "KEYWORD", + "LAPTOP", + "LASER PRINTER", + "LINK", + "LINUX", + "LOG OUT", + "LOGIC", + "LOGIN", + "LURKING", + "MACINTOSH", + "MACRO", + "MAINFRAME", + "MALWARE", + "MEDIA", + "MEMORY", + "MIRROR", + "MODEM", + "MONITOR", + "MOTHERBOARD", + "MOUSE", + "MULTIMEDIA", + "NET", + "NETWORK", + "NODE", + "NOTEBOOK", + "COMPUTER", + "OFFLINE", + "ONLINE", + "OPENSOURCE", + "OPERATING", + "SYSTEM", + "OPTION", + "OUTPUT", + "PAGE", + "PASSWORD", + "PASTE", + "PATH", + "PHISHING", + "PIRACY", + "PIRATE", + "PLATFORM", + "PLUGIN", + "PODCAST", + "POPUP", + "PORTAL", + "PRINT", + "PRINTER", + "PRIVACY", + "PROCESS", + "PROGRAM", + "PROGRAMMER", + "PROTOCOL", + "QUEUE", + "QWERTY", + "RAM", + "REALTIME", + "REBOOT", + "RESOLUTION", + "RESTORE", + "ROM", + "ROOT", + "ROUTER", + "RUNTIME", + "SAVE", + "SCAN", + "SCANNER", + "SCREEN", + "SCREENSHOT", + "SCRIPT", + "SCROLL", + "SCROLL", + "SEARCH", + "ENGINE", + "SECURITY", + "SERVER", + "SHAREWARE", + "SHELL", + "SHIFT", + "SHIFT KEY", + "SNAPSHOT", + "SOCIAL NETWORKING", + "SOFTWARE", + "SPAM", + "SPAMMER", + "SPREADSHEET", + "SPYWARE", + "STATUS", + "STORAGE", + "SUPERCOMPUTER", + "SURF", + "SYNTAX", + "TABLE", + "TAG", + "TERMINAL", + "TEMPLATE", + "TERABYTE", + "TEXT EDITOR", + "THREAD", + "TOOLBAR", + "TRASH", + "TROJAN HORSE", + "TYPEFACE", + "UNDO", + "UNIX", + "UPLOAD", + "URL", + "USER", + "USER INTERFACE", + "USERNAME", + "UTILITY", + "VERSION", + "VIRTUAL", + "VIRTUAL MEMORY", + "VIRUS", + "WEB", + "WEBMASTER", + "WEBSITE", + "WIDGET", + "WIKI", + "WINDOW", + "WINDOWS", + "WIRELESS", + "PROCESSOR", + "WORKSTATION", + "WEB", + "WORM", + "WWW", + "XML", + "ZIP", +]; diff --git a/src/Infiltration/ui/BlinkingCursor.tsx b/src/Infiltration/ui/BlinkingCursor.tsx index 93142e98a..e76e9e982 100644 --- a/src/Infiltration/ui/BlinkingCursor.tsx +++ b/src/Infiltration/ui/BlinkingCursor.tsx @@ -1,5 +1,9 @@ -import React from 'react'; +import React from "react"; export function BlinkingCursor(): React.ReactElement { - return (|); -} \ No newline at end of file + return ( + + | + + ); +} diff --git a/src/Infiltration/ui/BracketGame.tsx b/src/Infiltration/ui/BracketGame.tsx index f3d6cd16d..e7a3104a2 100644 --- a/src/Infiltration/ui/BracketGame.tsx +++ b/src/Infiltration/ui/BracketGame.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import Grid from '@material-ui/core/Grid'; +import React, { useState } from "react"; +import Grid from "@material-ui/core/Grid"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; import { GameTimer } from "./GameTimer"; @@ -8,77 +8,84 @@ import { interpolate } from "./Difficulty"; import { BlinkingCursor } from "./BlinkingCursor"; interface Difficulty { - [key: string]: number; - timer: number; - min: number; - max: number; + [key: string]: number; + timer: number; + min: number; + max: number; } const difficulties: { - Trivial: Difficulty; - Normal: Difficulty; - Hard: Difficulty; - Impossible: Difficulty; + Trivial: Difficulty; + Normal: Difficulty; + Hard: Difficulty; + Impossible: Difficulty; } = { - Trivial: {timer:8000, min: 2, max: 3}, - Normal: {timer:6000, min: 4, max: 5}, - Hard: {timer:4000, min: 4, max: 6}, - Impossible: {timer: 2500, min: 7, max: 7}, -} + Trivial: { timer: 8000, min: 2, max: 3 }, + Normal: { timer: 6000, min: 4, max: 5 }, + Hard: { timer: 4000, min: 4, max: 6 }, + Impossible: { timer: 2500, min: 7, max: 7 }, +}; function generateLeftSide(difficulty: Difficulty): string { - let str = ""; - const length = random(difficulty.min, difficulty.max); - for(let i = 0; i < length; i++) { - str += ["[", '<', '(', '{'][Math.floor(Math.random()*4)]; - } + let str = ""; + const length = random(difficulty.min, difficulty.max); + for (let i = 0; i < length; i++) { + str += ["[", "<", "(", "{"][Math.floor(Math.random() * 4)]; + } - return str; + return str; } function getChar(event: React.KeyboardEvent): string { - if(event.keyCode == 48 && event.shiftKey) return ")"; - if(event.keyCode == 221 && !event.shiftKey) return "]"; - if(event.keyCode == 221 && event.shiftKey) return "}"; - if(event.keyCode == 190 && event.shiftKey) return ">"; - return ""; + if (event.keyCode == 48 && event.shiftKey) return ")"; + if (event.keyCode == 221 && !event.shiftKey) return "]"; + if (event.keyCode == 221 && event.shiftKey) return "}"; + if (event.keyCode == 190 && event.shiftKey) return ">"; + return ""; } function match(left: string, right: string): boolean { - return (left === '[' && right === ']') || - (left === '<' && right === '>') || - (left === '(' && right === ')') || - (left === '{' && right === '}'); + return ( + (left === "[" && right === "]") || + (left === "<" && right === ">") || + (left === "(" && right === ")") || + (left === "{" && right === "}") + ); } export function BracketGame(props: IMinigameProps): React.ReactElement { - const difficulty: Difficulty = {timer:0, min: 0, max: 0}; - interpolate(difficulties, props.difficulty, difficulty); - const timer = difficulty.timer; - const [right, setRight] = useState(""); - const [left] = useState(generateLeftSide(difficulty)); + const difficulty: Difficulty = { timer: 0, min: 0, max: 0 }; + interpolate(difficulties, props.difficulty, difficulty); + const timer = difficulty.timer; + const [right, setRight] = useState(""); + const [left] = useState(generateLeftSide(difficulty)); - function press(event: React.KeyboardEvent): void { - event.preventDefault(); - const char = getChar(event); - if(!char) return; - if(!match(left[left.length-right.length-1], char)) { - props.onFailure(); - return; - } - if(left.length === right.length+1) { - props.onSuccess(); - return; - } - setRight(right+char); + function press(event: React.KeyboardEvent): void { + event.preventDefault(); + const char = getChar(event); + if (!char) return; + if (!match(left[left.length - right.length - 1], char)) { + props.onFailure(); + return; } + if (left.length === right.length + 1) { + props.onSuccess(); + return; + } + setRight(right + char); + } - return ( - - -

    Close the brackets

    -

    {`${left}${right}`}

    - -
    -
    ) -} \ No newline at end of file + return ( + + + +

    Close the brackets

    +

    + {`${left}${right}`} + +

    + +
    +
    + ); +} diff --git a/src/Infiltration/ui/BribeGame.tsx b/src/Infiltration/ui/BribeGame.tsx index c4160a23e..bc97240dd 100644 --- a/src/Infiltration/ui/BribeGame.tsx +++ b/src/Infiltration/ui/BribeGame.tsx @@ -1,96 +1,134 @@ -import React, { useState } from 'react'; -import Grid from '@material-ui/core/Grid'; +import React, { useState } from "react"; +import Grid from "@material-ui/core/Grid"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; import { GameTimer } from "./GameTimer"; import { interpolate } from "./Difficulty"; interface Difficulty { - [key: string]: number; - timer: number; - size: number; + [key: string]: number; + timer: number; + size: number; } const difficulties: { - Trivial: Difficulty; - Normal: Difficulty; - Hard: Difficulty; - Impossible: Difficulty; + Trivial: Difficulty; + Normal: Difficulty; + Hard: Difficulty; + Impossible: Difficulty; } = { - Trivial: {timer: 12000, size: 6}, - Normal: {timer: 9000, size: 8}, - Hard: {timer: 5000, size: 9}, - Impossible: {timer: 2500, size: 12}, -} + Trivial: { timer: 12000, size: 6 }, + Normal: { timer: 9000, size: 8 }, + Hard: { timer: 5000, size: 9 }, + Impossible: { timer: 2500, size: 12 }, +}; export function BribeGame(props: IMinigameProps): React.ReactElement { - const difficulty: Difficulty = {timer: 0, size: 0}; - interpolate(difficulties, props.difficulty, difficulty); - const timer = difficulty.timer; - const [choices] = useState(makeChoices(difficulty)); - const [index, setIndex] = useState(0); + const difficulty: Difficulty = { timer: 0, size: 0 }; + interpolate(difficulties, props.difficulty, difficulty); + const timer = difficulty.timer; + const [choices] = useState(makeChoices(difficulty)); + const [index, setIndex] = useState(0); - function press(event: React.KeyboardEvent): void { - event.preventDefault(); - const k = event.keyCode; - if(k === 32) { - if(positive.includes(choices[index])) props.onSuccess(); - else props.onFailure(); - return; - } - - let newIndex = index; - if([38, 87, 68, 39].includes(k)) newIndex++; - if([65, 37, 83, 40].includes(k)) newIndex--; - while(newIndex < 0) newIndex += choices.length; - while(newIndex > choices.length-1) newIndex -= choices.length; - setIndex(newIndex); + function press(event: React.KeyboardEvent): void { + event.preventDefault(); + const k = event.keyCode; + if (k === 32) { + if (positive.includes(choices[index])) props.onSuccess(); + else props.onFailure(); + return; } - return ( - - -

    Say something nice about the guard.

    - -
    - -

    -

    {choices[index]}

    -

    -
    -
    ) + let newIndex = index; + if ([38, 87, 68, 39].includes(k)) newIndex++; + if ([65, 37, 83, 40].includes(k)) newIndex--; + while (newIndex < 0) newIndex += choices.length; + while (newIndex > choices.length - 1) newIndex -= choices.length; + setIndex(newIndex); + } + + return ( + + + +

    Say something nice about the guard.

    + +
    + +

    +

    {choices[index]}

    +

    +
    +
    + ); } function shuffleArray(array: string[]): void { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - const temp = array[i]; - array[i] = array[j]; - array[j] = temp; - } + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + const temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } } function makeChoices(difficulty: Difficulty): string[] { - const choices = []; - choices.push(positive[Math.floor(Math.random()*positive.length)]); - for(let i = 0; i < difficulty.size; i++) { - const option = negative[Math.floor(Math.random()*negative.length)]; - if(choices.includes(option)) { - i--; - continue; - } - choices.push(option); + const choices = []; + choices.push(positive[Math.floor(Math.random() * positive.length)]); + for (let i = 0; i < difficulty.size; i++) { + const option = negative[Math.floor(Math.random() * negative.length)]; + if (choices.includes(option)) { + i--; + continue; } - shuffleArray(choices); - return choices; + choices.push(option); + } + shuffleArray(choices); + return choices; } -const positive = ["affectionate","agreeable","bright","charming","creative", - "determined","energetic","friendly","funny","generous","polite","likable", - "diplomatic","helpful","giving","kind","hardworking","patient","dynamic", - "loyal"]; +const positive = [ + "affectionate", + "agreeable", + "bright", + "charming", + "creative", + "determined", + "energetic", + "friendly", + "funny", + "generous", + "polite", + "likable", + "diplomatic", + "helpful", + "giving", + "kind", + "hardworking", + "patient", + "dynamic", + "loyal", +]; -const negative = ["aggressive","aloof","arrogant","big-headed","boastful", - "boring","bossy","careless","clingy","couch potato","cruel","cynical", - "grumpy","hot air","know it all","obnoxious","pain in the neck","picky", - "tactless","thoughtless"]; \ No newline at end of file +const negative = [ + "aggressive", + "aloof", + "arrogant", + "big-headed", + "boastful", + "boring", + "bossy", + "careless", + "clingy", + "couch potato", + "cruel", + "cynical", + "grumpy", + "hot air", + "know it all", + "obnoxious", + "pain in the neck", + "picky", + "tactless", + "thoughtless", +]; diff --git a/src/Infiltration/ui/CheatCodeGame.tsx b/src/Infiltration/ui/CheatCodeGame.tsx index 943b314ae..c5e3e504a 100644 --- a/src/Infiltration/ui/CheatCodeGame.tsx +++ b/src/Infiltration/ui/CheatCodeGame.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import Grid from '@material-ui/core/Grid'; +import React, { useState } from "react"; +import Grid from "@material-ui/core/Grid"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; import { GameTimer } from "./GameTimer"; @@ -7,59 +7,62 @@ import { random, getArrow } from "../utils"; import { interpolate } from "./Difficulty"; interface Difficulty { - [key: string]: number; - timer: number; - min: number; - max: number; + [key: string]: number; + timer: number; + min: number; + max: number; } const difficulties: { - Trivial: Difficulty; - Normal: Difficulty; - Hard: Difficulty; - Impossible: Difficulty; + Trivial: Difficulty; + Normal: Difficulty; + Hard: Difficulty; + Impossible: Difficulty; } = { - Trivial: {timer: 13000, min: 6, max: 8}, - Normal: {timer: 7000, min: 7, max: 8}, - Hard: {timer: 5000, min: 8, max: 9}, - Impossible: {timer: 3000, min: 9, max: 9}, -} + Trivial: { timer: 13000, min: 6, max: 8 }, + Normal: { timer: 7000, min: 7, max: 8 }, + Hard: { timer: 5000, min: 8, max: 9 }, + Impossible: { timer: 3000, min: 9, max: 9 }, +}; export function CheatCodeGame(props: IMinigameProps): React.ReactElement { - const difficulty: Difficulty = {timer: 0, min: 0, max: 0}; - interpolate(difficulties, props.difficulty, difficulty); - const timer = difficulty.timer; - const [code] = useState(generateCode(difficulty)); - const [index, setIndex] = useState(0); + const difficulty: Difficulty = { timer: 0, min: 0, max: 0 }; + interpolate(difficulties, props.difficulty, difficulty); + const timer = difficulty.timer; + const [code] = useState(generateCode(difficulty)); + const [index, setIndex] = useState(0); - function press(event: React.KeyboardEvent): void { - event.preventDefault(); - if(code[index] !== getArrow(event)) { - props.onFailure(); - return; - } - setIndex(index+1); - if(index+1 >= code.length) props.onSuccess(); + function press(event: React.KeyboardEvent): void { + event.preventDefault(); + if (code[index] !== getArrow(event)) { + props.onFailure(); + return; } + setIndex(index + 1); + if (index + 1 >= code.length) props.onSuccess(); + } - return ( - - -

    Enter the Code!

    -

    {code[index]}

    - -
    -
    ) + return ( + + + +

    Enter the Code!

    +

    {code[index]}

    + +
    +
    + ); } function generateCode(difficulty: Difficulty): string { - const arrows = ['←', '→', '↑', '↓']; - let code = ''; - for(let i = 0; i < random(difficulty.min, difficulty.max); i++) { - let arrow = arrows[Math.floor(4*Math.random())]; - while(arrow === code[code.length-1]) arrow = arrows[Math.floor(4*Math.random())]; - code += arrow; - } + const arrows = ["←", "→", "↑", "↓"]; + let code = ""; + for (let i = 0; i < random(difficulty.min, difficulty.max); i++) { + let arrow = arrows[Math.floor(4 * Math.random())]; + while (arrow === code[code.length - 1]) + arrow = arrows[Math.floor(4 * Math.random())]; + code += arrow; + } - return code; + return code; } diff --git a/src/Infiltration/ui/Countdown.tsx b/src/Infiltration/ui/Countdown.tsx index 7916aa1ae..8edd0e5e5 100644 --- a/src/Infiltration/ui/Countdown.tsx +++ b/src/Infiltration/ui/Countdown.tsx @@ -1,26 +1,28 @@ -import React, { useState, useEffect } from 'react'; -import Grid from '@material-ui/core/Grid'; +import React, { useState, useEffect } from "react"; +import Grid from "@material-ui/core/Grid"; interface IProps { - onFinish: () => void; + onFinish: () => void; } export function Countdown(props: IProps): React.ReactElement { - const [x, setX] = useState(3); - useEffect(() => { - if(x === 0) { - props.onFinish(); - return; - } - setTimeout(()=>setX(x-1), 200); - }); + const [x, setX] = useState(3); + useEffect(() => { + if (x === 0) { + props.onFinish(); + return; + } + setTimeout(() => setX(x - 1), 200); + }); - return (<> - - -

    Get Ready!

    -

    {x}

    -
    + return ( + <> + + +

    Get Ready!

    +

    {x}

    - ) -} \ No newline at end of file +
    + + ); +} diff --git a/src/Infiltration/ui/Cyberpunk2077Game.tsx b/src/Infiltration/ui/Cyberpunk2077Game.tsx index 49b8cac58..aaf36c9c9 100644 --- a/src/Infiltration/ui/Cyberpunk2077Game.tsx +++ b/src/Infiltration/ui/Cyberpunk2077Game.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import Grid from '@material-ui/core/Grid'; +import React, { useState } from "react"; +import Grid from "@material-ui/core/Grid"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; import { GameTimer } from "./GameTimer"; @@ -7,110 +7,145 @@ import { interpolate } from "./Difficulty"; import { getArrow } from "../utils"; interface Difficulty { - [key: string]: number; - timer: number; - width: number; - height: number; - symbols: number; + [key: string]: number; + timer: number; + width: number; + height: number; + symbols: number; } const difficulties: { - Trivial: Difficulty; - Normal: Difficulty; - Hard: Difficulty; - Impossible: Difficulty; + Trivial: Difficulty; + Normal: Difficulty; + Hard: Difficulty; + Impossible: Difficulty; } = { - Trivial: {timer: 12500, width: 3, height: 3, symbols: 6}, - Normal: {timer: 15000, width: 4, height: 4, symbols: 7}, - Hard: {timer: 12500, width: 5, height: 5, symbols: 8}, - Impossible: {timer: 10000, width: 6, height: 6, symbols: 9}, -} + Trivial: { timer: 12500, width: 3, height: 3, symbols: 6 }, + Normal: { timer: 15000, width: 4, height: 4, symbols: 7 }, + Hard: { timer: 12500, width: 5, height: 5, symbols: 8 }, + Impossible: { timer: 10000, width: 6, height: 6, symbols: 9 }, +}; export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement { - const difficulty: Difficulty = {timer: 0, width: 0, height: 0, symbols: 0}; - interpolate(difficulties, props.difficulty, difficulty); - const timer = difficulty.timer; - const [grid] = useState(generatePuzzle(difficulty)); - const [answer] = useState(generateAnswer(grid, difficulty)); - const [index, setIndex] = useState(0); - const [pos, setPos] = useState([0, 0]); + const difficulty: Difficulty = { timer: 0, width: 0, height: 0, symbols: 0 }; + interpolate(difficulties, props.difficulty, difficulty); + const timer = difficulty.timer; + const [grid] = useState(generatePuzzle(difficulty)); + const [answer] = useState(generateAnswer(grid, difficulty)); + const [index, setIndex] = useState(0); + const [pos, setPos] = useState([0, 0]); - function press(event: React.KeyboardEvent): void { - event.preventDefault(); - const move = [0, 0]; - const arrow = getArrow(event); - switch(arrow) { - case "↑": - move[1]--; - break; - case "←": - move[0]--; - break; - case "↓": - move[1]++; - break; - case "→": - move[0]++; - break; - } - const next = [pos[0]+move[0], pos[1]+move[1]]; - next[0] = (next[0]+grid[0].length)%grid[0].length; - next[1] = (next[1]+grid.length)%grid.length; - setPos(next); - - if(event.keyCode == 32) { - const selected = grid[pos[1]][pos[0]]; - const expected = answer[index]; - if(selected !== expected) { - props.onFailure(); - return; - } - setIndex(index+1); - if(answer.length === index+1) props.onSuccess(); - } + function press(event: React.KeyboardEvent): void { + event.preventDefault(); + const move = [0, 0]; + const arrow = getArrow(event); + switch (arrow) { + case "↑": + move[1]--; + break; + case "←": + move[0]--; + break; + case "↓": + move[1]++; + break; + case "→": + move[0]++; + break; } + const next = [pos[0] + move[0], pos[1] + move[1]]; + next[0] = (next[0] + grid[0].length) % grid[0].length; + next[1] = (next[1] + grid.length) % grid.length; + setPos(next); - const fontSize = "2em"; - return ( - - -

    Match the symbols!

    -

    Targets: {answer.map((a, i) => { - if(i == index) - return {a}  - return {a}  - })}

    + if (event.keyCode == 32) { + const selected = grid[pos[1]][pos[0]]; + const expected = answer[index]; + if (selected !== expected) { + props.onFailure(); + return; + } + setIndex(index + 1); + if (answer.length === index + 1) props.onSuccess(); + } + } + + const fontSize = "2em"; + return ( + + + +

    Match the symbols!

    +

    + Targets:{" "} + {answer.map((a, i) => { + if (i == index) + return ( + + {a}  + + ); + return ( + + {a}  + + ); + })} +

    +
    + {grid.map((line, y) => ( +
    +
    +              {line.map((cell, x) => {
    +                if (x == pos[0] && y == pos[1])
    +                  return (
    +                    
    +                      {cell} 
    +                    
    +                  );
    +                return (
    +                  
    +                    {cell} 
    +                  
    +                );
    +              })}
    +            

    - {grid.map((line, y) =>
    {line.map((cell, x) => {
    -                if(x == pos[0] && y == pos[1])
    -                    return {cell} 
    -                return {cell} 
    -            })}

    )} - - - ) +
    + ))} + +
    +
    + ); } function generateAnswer(grid: string[][], difficulty: Difficulty): string[] { - const answer = []; - for(let i = 0; i < Math.round(difficulty.symbols); i++) { - answer.push(grid[Math.floor(Math.random()*grid.length)][Math.floor(Math.random()*grid[0].length)]); - } - return answer; + const answer = []; + for (let i = 0; i < Math.round(difficulty.symbols); i++) { + answer.push( + grid[Math.floor(Math.random() * grid.length)][ + Math.floor(Math.random() * grid[0].length) + ], + ); + } + return answer; } function randChar(): string { - return "ABCDEF0123456789"[Math.floor(Math.random()*16)]; + return "ABCDEF0123456789"[Math.floor(Math.random() * 16)]; } function generatePuzzle(difficulty: Difficulty): string[][] { - const puzzle = []; - for(let i = 0; i < Math.round(difficulty.height); i++) { - const line = []; - for(let j = 0; j < Math.round(difficulty.width); j++) { - line.push(randChar()+randChar()); - } - puzzle.push(line); + const puzzle = []; + for (let i = 0; i < Math.round(difficulty.height); i++) { + const line = []; + for (let j = 0; j < Math.round(difficulty.width); j++) { + line.push(randChar() + randChar()); } - return puzzle; + puzzle.push(line); + } + return puzzle; } diff --git a/src/Infiltration/ui/Difficulty.ts b/src/Infiltration/ui/Difficulty.ts index 00c819593..ce8acf06c 100644 --- a/src/Infiltration/ui/Difficulty.ts +++ b/src/Infiltration/ui/Difficulty.ts @@ -1,32 +1,40 @@ interface DifficultySetting { - [key: string]: number; + [key: string]: number; } interface DifficultySettings { - Trivial: DifficultySetting; - Normal: DifficultySetting; - Hard: DifficultySetting; - Impossible: DifficultySetting; + Trivial: DifficultySetting; + Normal: DifficultySetting; + Hard: DifficultySetting; + Impossible: DifficultySetting; } // I could use `any` to simply some of this but I also want to take advantage // of the type safety that typescript provides. I'm just not sure how in this // case. -export function interpolate(settings: DifficultySettings, n: number, out: DifficultySetting): DifficultySetting { - // interpolates between 2 difficulties. - function lerpD(a: DifficultySetting, b: DifficultySetting, t: number): DifficultySetting { - // interpolates between 2 numbers. - function lerp(x: number, y: number, t: number): number { - return (1 - t) * x + t * y; - } - for(const key of Object.keys(a)) { - out[key] = lerp(a[key], b[key], t); - } - return a; +export function interpolate( + settings: DifficultySettings, + n: number, + out: DifficultySetting, +): DifficultySetting { + // interpolates between 2 difficulties. + function lerpD( + a: DifficultySetting, + b: DifficultySetting, + t: number, + ): DifficultySetting { + // interpolates between 2 numbers. + function lerp(x: number, y: number, t: number): number { + return (1 - t) * x + t * y; } - if(n < 0) return lerpD(settings.Trivial, settings.Trivial, 0); - if(n >= 0 && n < 1) return lerpD(settings.Trivial, settings.Normal, n); - if(n >= 1 && n < 2) return lerpD(settings.Normal, settings.Hard, n-1); - if(n >= 2 && n < 3) return lerpD(settings.Hard, settings.Impossible, n-2); - return lerpD(settings.Impossible, settings.Impossible, 0); -} \ No newline at end of file + for (const key of Object.keys(a)) { + out[key] = lerp(a[key], b[key], t); + } + return a; + } + if (n < 0) return lerpD(settings.Trivial, settings.Trivial, 0); + if (n >= 0 && n < 1) return lerpD(settings.Trivial, settings.Normal, n); + if (n >= 1 && n < 2) return lerpD(settings.Normal, settings.Hard, n - 1); + if (n >= 2 && n < 3) return lerpD(settings.Hard, settings.Impossible, n - 2); + return lerpD(settings.Impossible, settings.Impossible, 0); +} diff --git a/src/Infiltration/ui/Game.tsx b/src/Infiltration/ui/Game.tsx index e4c15188e..6f8d06516 100644 --- a/src/Infiltration/ui/Game.tsx +++ b/src/Infiltration/ui/Game.tsx @@ -1,7 +1,7 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; import { IEngine } from "../../IEngine"; -import React, { useState } from 'react'; -import Grid from '@material-ui/core/Grid'; +import React, { useState } from "react"; +import Grid from "@material-ui/core/Grid"; import { Countdown } from "./Countdown"; import { BracketGame } from "./BracketGame"; import { SlashGame } from "./SlashGame"; @@ -14,122 +14,147 @@ import { WireCuttingGame } from "./WireCuttingGame"; import { Victory } from "./Victory"; interface IProps { - Player: IPlayer; - Engine: IEngine; - StartingDifficulty: number; - Difficulty: number; - MaxLevel: number; + Player: IPlayer; + Engine: IEngine; + StartingDifficulty: number; + Difficulty: number; + MaxLevel: number; } enum Stage { - Countdown = 0, - Minigame, - Result, - Sell, + Countdown = 0, + Minigame, + Result, + Sell, } const minigames = [ - SlashGame, - BracketGame, - BackwardGame, - BribeGame, - CheatCodeGame, - Cyberpunk2077Game, - MinesweeperGame, - WireCuttingGame, -] + SlashGame, + BracketGame, + BackwardGame, + BribeGame, + CheatCodeGame, + Cyberpunk2077Game, + MinesweeperGame, + WireCuttingGame, +]; export function Game(props: IProps): React.ReactElement { - const [level, setLevel] = useState(1); - const [stage, setStage] = useState(Stage.Countdown); - const [results, setResults] = useState(''); - const [gameIds, setGameIds] = useState({ - lastGames: [-1, -1], - id: Math.floor(Math.random()*minigames.length), + const [level, setLevel] = useState(1); + const [stage, setStage] = useState(Stage.Countdown); + const [results, setResults] = useState(""); + const [gameIds, setGameIds] = useState({ + lastGames: [-1, -1], + id: Math.floor(Math.random() * minigames.length), + }); + + function nextGameId(): number { + let id = gameIds.lastGames[0]; + const ids = [gameIds.lastGames[0], gameIds.lastGames[1], gameIds.id]; + while (ids.includes(id)) { + id = Math.floor(Math.random() * minigames.length); + } + return id; + } + + function setupNextGame(): void { + setGameIds({ + lastGames: [gameIds.lastGames[1], gameIds.id], + id: nextGameId(), }); + } - function nextGameId(): number { - let id = gameIds.lastGames[0]; - const ids = [gameIds.lastGames[0], gameIds.lastGames[1], gameIds.id]; - while(ids.includes(id)) { - id = Math.floor(Math.random()*minigames.length); - } - return id; + function success(): void { + pushResult(true); + if (level === props.MaxLevel) { + setStage(Stage.Sell); + } else { + setStage(Stage.Countdown); + setLevel(level + 1); } + setupNextGame(); + } + function pushResult(win: boolean): void { + setResults((old) => { + let next = old; + next += win ? "✓" : "✗"; + if (next.length > 15) next = next.slice(1); + return next; + }); + } - function setupNextGame(): void { - setGameIds({ - lastGames: [gameIds.lastGames[1], gameIds.id], - id: nextGameId(), - }) + function failure(options?: { automated: boolean }): void { + setStage(Stage.Countdown); + pushResult(false); + // Kill the player immediately if they use automation, so + // it's clear they're not meant to + const damage = options?.automated + ? props.Player.hp + : props.StartingDifficulty * 3; + if (props.Player.takeDamage(damage)) { + const menu = document.getElementById("mainmenu-container"); + if (menu === null) throw new Error("mainmenu-container not found"); + menu.style.visibility = "visible"; + props.Engine.loadLocationContent(); } + setupNextGame(); + } - function success(): void { - pushResult(true); - if(level === props.MaxLevel) { - setStage(Stage.Sell); - } else { - setStage(Stage.Countdown); - setLevel(level+1); - } - setupNextGame(); - } - - function pushResult(win: boolean): void { - setResults(old => { - let next = old; - next += win ? "✓" : "✗"; - if(next.length > 15) next = next.slice(1); - return next; - }) - } - - function failure(options?: { automated: boolean }): void { - setStage(Stage.Countdown); - pushResult(false); - // Kill the player immediately if they use automation, so - // it's clear they're not meant to - const damage = options?.automated ? props.Player.hp : props.StartingDifficulty*3; - if(props.Player.takeDamage(damage)) { - const menu = document.getElementById("mainmenu-container"); - if(menu === null) throw new Error("mainmenu-container not found"); - menu.style.visibility = "visible"; - props.Engine.loadLocationContent(); - } - setupNextGame(); - } - - let stageComponent: React.ReactNode; - switch(stage) { + let stageComponent: React.ReactNode; + switch (stage) { case Stage.Countdown: - stageComponent = (setStage(Stage.Minigame)} />); - break; + stageComponent = setStage(Stage.Minigame)} />; + break; case Stage.Minigame: { - const MiniGame = minigames[gameIds.id]; - stageComponent = (); - break; + const MiniGame = minigames[gameIds.id]; + stageComponent = ( + + ); + break; } case Stage.Sell: - stageComponent = (); - break; - } + stageComponent = ( + + ); + break; + } + function Progress(): React.ReactElement { + return ( +

    + + {results.slice(0, results.length - 1)} + + {results[results.length - 1]} +

    + ); + } - function Progress(): React.ReactElement { - return

    {results.slice(0, results.length-1)}{results[results.length-1]}

    - } - - return (<> - - -

    Level: {level} / {props.MaxLevel}

    - -
    - - - {stageComponent} - + return ( + <> + + +

    + Level: {level} / {props.MaxLevel} +

    +
    - ) -} \ No newline at end of file + + + {stageComponent} + +
    + + ); +} diff --git a/src/Infiltration/ui/GameTimer.tsx b/src/Infiltration/ui/GameTimer.tsx index ac403a5bd..3ed5b8ee9 100644 --- a/src/Infiltration/ui/GameTimer.tsx +++ b/src/Infiltration/ui/GameTimer.tsx @@ -1,7 +1,7 @@ -import LinearProgress from '@material-ui/core/LinearProgress'; -import React, { useState, useEffect } from 'react'; +import LinearProgress from "@material-ui/core/LinearProgress"; +import React, { useState, useEffect } from "react"; import { withStyles } from "@material-ui/core/styles"; -import Grid from '@material-ui/core/Grid'; +import Grid from "@material-ui/core/Grid"; const TimerProgress = withStyles(() => ({ bar: { @@ -11,31 +11,32 @@ const TimerProgress = withStyles(() => ({ }))(LinearProgress); interface IProps { - millis: number; - onExpire: () => void; + millis: number; + onExpire: () => void; } export function GameTimer(props: IProps): React.ReactElement { - const [v, setV] = useState(100); + const [v, setV] = useState(100); - const tick = 200; - useEffect(() => { - const intervalId = setInterval(() => { - setV(old => { - if(old <= 0) props.onExpire(); - return old - tick / props.millis * 100; - }); - }, tick); - return () => { - clearInterval(intervalId); - } - }, []); + const tick = 200; + useEffect(() => { + const intervalId = setInterval(() => { + setV((old) => { + if (old <= 0) props.onExpire(); + return old - (tick / props.millis) * 100; + }); + }, tick); + return () => { + clearInterval(intervalId); + }; + }, []); - // https://stackoverflow.com/questions/55593367/disable-material-uis-linearprogress-animation - // TODO(hydroflame): there's like a bug where it triggers the end before the - // bar physically reaches the end - return ( - - ); + // https://stackoverflow.com/questions/55593367/disable-material-uis-linearprogress-animation + // TODO(hydroflame): there's like a bug where it triggers the end before the + // bar physically reaches the end + return ( + + + + ); } - diff --git a/src/Infiltration/ui/IMinigameProps.tsx b/src/Infiltration/ui/IMinigameProps.tsx index d3ba7ab63..7438378a0 100644 --- a/src/Infiltration/ui/IMinigameProps.tsx +++ b/src/Infiltration/ui/IMinigameProps.tsx @@ -1,8 +1,8 @@ export interface IMinigameProps { - onSuccess: () => void; - onFailure: (options?: { - /** Failed due to using untrusted events (automation) */ - automated: boolean; - }) => void; - difficulty: number; -} \ No newline at end of file + onSuccess: () => void; + onFailure: (options?: { + /** Failed due to using untrusted events (automation) */ + automated: boolean; + }) => void; + difficulty: number; +} diff --git a/src/Infiltration/ui/Intro.tsx b/src/Infiltration/ui/Intro.tsx index c0a9a1b27..a632323d2 100644 --- a/src/Infiltration/ui/Intro.tsx +++ b/src/Infiltration/ui/Intro.tsx @@ -1,76 +1,97 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; import { IEngine } from "../../IEngine"; -import React from 'react'; +import React from "react"; import { StdButton } from "../../ui/React/StdButton"; -import Grid from '@material-ui/core/Grid'; +import Grid from "@material-ui/core/Grid"; interface IProps { - Player: IPlayer; - Engine: IEngine; - Location: string; - Difficulty: number; - MaxLevel: number; - start: () => void; - cancel: () => void; + Player: IPlayer; + Engine: IEngine; + Location: string; + Difficulty: number; + MaxLevel: number; + start: () => void; + cancel: () => void; } function arrowPart(color: string, length: number): JSX.Element { - let arrow = ""; - if(length <= 0) length = 0; - else if(length > 13) length = 13; - else { - length--; - arrow = ">"; - } - return {"=".repeat(length)}{arrow}{" ".repeat(13-arrow.length-length)} + let arrow = ""; + if (length <= 0) length = 0; + else if (length > 13) length = 13; + else { + length--; + arrow = ">"; + } + return ( + + {"=".repeat(length)} + {arrow} + {" ".repeat(13 - arrow.length - length)} + + ); } function coloredArrow(difficulty: number): JSX.Element { - if(difficulty === 0) { - return {'>'}{" ".repeat(38)} - } else { - return <>{arrowPart('white', difficulty*13)}{arrowPart('orange', (difficulty-1)*13)}{arrowPart('red', (difficulty-2)*13)} - } + if (difficulty === 0) { + return ( + + {">"} + {" ".repeat(38)} + + ); + } else { + return ( + <> + {arrowPart("white", difficulty * 13)} + {arrowPart("orange", (difficulty - 1) * 13)} + {arrowPart("red", (difficulty - 2) * 13)} + + ); + } } export function Intro(props: IProps): React.ReactElement { - return (<> - - -

    Infiltrating {props.Location}

    -
    - -

    Maximum level: {props.MaxLevel}

    -
    - - -
    [{coloredArrow(props.Difficulty)}]
    -
     ^            ^            ^           ^
    -
     Trivial    Normal        Hard    Impossible
    -
    - -

    Infiltration is a series of short minigames that get - progressively harder. You take damage for failing them. Reaching - the maximum level rewards you with intel you can trade for money - or reputation.

    -
    -

    The minigames you play are randomly selected. It might take you - few tries to get used to them.

    -
    -

    No game require use of the mouse.

    -
    -

    Spacebar is the default action/confirm button.

    -
    -

    Everything that uses arrow can also use WASD

    -
    -

    Sometimes the rest of the keyboard is used.

    -
    - - - - - - + return ( + <> + + +

    Infiltrating {props.Location}

    - ) -} \ No newline at end of file + +

    Maximum level: {props.MaxLevel}

    +
    + +
    [{coloredArrow(props.Difficulty)}]
    +
     ^ ^ ^ ^
    +
     Trivial Normal Hard Impossible
    +
    + +

    + Infiltration is a series of short minigames that get progressively + harder. You take damage for failing them. Reaching the maximum level + rewards you with intel you can trade for money or reputation. +

    +
    +

    + The minigames you play are randomly selected. It might take you few + tries to get used to them. +

    +
    +

    No game require use of the mouse.

    +
    +

    Spacebar is the default action/confirm button.

    +
    +

    Everything that uses arrow can also use WASD

    +
    +

    Sometimes the rest of the keyboard is used.

    +
    + + + + + + +
    + + ); +} diff --git a/src/Infiltration/ui/KeyHandler.tsx b/src/Infiltration/ui/KeyHandler.tsx index 384f6f8c6..7ca89f810 100644 --- a/src/Infiltration/ui/KeyHandler.tsx +++ b/src/Infiltration/ui/KeyHandler.tsx @@ -1,24 +1,24 @@ -import React, { useEffect } from 'react'; +import React, { useEffect } from "react"; interface IProps { - onKeyDown: (event: React.KeyboardEvent) => void; - onFailure: (options?: { automated: boolean }) => void; + onKeyDown: (event: React.KeyboardEvent) => void; + onFailure: (options?: { automated: boolean }) => void; } export function KeyHandler(props: IProps): React.ReactElement { - let elem: any; - useEffect(() => elem.focus()); + let elem: any; + useEffect(() => elem.focus()); - function onKeyDown(event: React.KeyboardEvent): void { - console.log("isTrusted?", event.isTrusted) - if(!event.isTrusted) { - console.log("untrusted event!") - props.onFailure({ automated: true }); - return; - } - props.onKeyDown(event); + function onKeyDown(event: React.KeyboardEvent): void { + console.log("isTrusted?", event.isTrusted); + if (!event.isTrusted) { + console.log("untrusted event!"); + props.onFailure({ automated: true }); + return; } + props.onKeyDown(event); + } - // invisible autofocused element that eats all the keypress for the minigames. - return (
    elem = c} onKeyDown={onKeyDown} />) -} \ No newline at end of file + // invisible autofocused element that eats all the keypress for the minigames. + return
    (elem = c)} onKeyDown={onKeyDown} />; +} diff --git a/src/Infiltration/ui/MinesweeperGame.tsx b/src/Infiltration/ui/MinesweeperGame.tsx index 3f1434c32..b3febd7d2 100644 --- a/src/Infiltration/ui/MinesweeperGame.tsx +++ b/src/Infiltration/ui/MinesweeperGame.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from 'react'; -import Grid from '@material-ui/core/Grid'; +import React, { useState, useEffect } from "react"; +import Grid from "@material-ui/core/Grid"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; import { GameTimer } from "./GameTimer"; @@ -7,123 +7,132 @@ import { interpolate } from "./Difficulty"; import { getArrow } from "../utils"; interface Difficulty { - [key: string]: number; - timer: number; - width: number; - height: number; - mines: number; + [key: string]: number; + timer: number; + width: number; + height: number; + mines: number; } const difficulties: { - Trivial: Difficulty; - Normal: Difficulty; - Hard: Difficulty; - Impossible: Difficulty; + Trivial: Difficulty; + Normal: Difficulty; + Hard: Difficulty; + Impossible: Difficulty; } = { - Trivial: {timer: 15000, width: 3, height: 3, mines: 4}, - Normal: {timer: 15000, width: 4, height: 4, mines: 7}, - Hard: {timer: 15000, width: 5, height: 5, mines: 11}, - Impossible: {timer: 15000, width: 6, height: 6, mines: 15}, -} + Trivial: { timer: 15000, width: 3, height: 3, mines: 4 }, + Normal: { timer: 15000, width: 4, height: 4, mines: 7 }, + Hard: { timer: 15000, width: 5, height: 5, mines: 11 }, + Impossible: { timer: 15000, width: 6, height: 6, mines: 15 }, +}; export function MinesweeperGame(props: IMinigameProps): React.ReactElement { - const difficulty: Difficulty = {timer: 0, width: 0, height: 0, mines: 0}; - interpolate(difficulties, props.difficulty, difficulty); - const timer = difficulty.timer; - const [minefield] = useState(generateMinefield(difficulty)); - const [answer, setAnswer] = useState(generateEmptyField(difficulty)); - const [pos, setPos] = useState([0, 0]); - const [memoryPhase, setMemoryPhase] = useState(true); + const difficulty: Difficulty = { timer: 0, width: 0, height: 0, mines: 0 }; + interpolate(difficulties, props.difficulty, difficulty); + const timer = difficulty.timer; + const [minefield] = useState(generateMinefield(difficulty)); + const [answer, setAnswer] = useState(generateEmptyField(difficulty)); + const [pos, setPos] = useState([0, 0]); + const [memoryPhase, setMemoryPhase] = useState(true); - function press(event: React.KeyboardEvent): void { - event.preventDefault(); - if(memoryPhase) return; - const move = [0, 0]; - const arrow = getArrow(event); - switch(arrow) { - case "↑": - move[1]--; - break; - case "←": - move[0]--; - break; - case "↓": - move[1]++; - break; - case "→": - move[0]++; - break; - } - const next = [pos[0]+move[0], pos[1]+move[1]]; - next[0] = (next[0]+minefield[0].length)%minefield[0].length; - next[1] = (next[1]+minefield.length)%minefield.length; - setPos(next); - - if(event.keyCode == 32) { - if(!minefield[pos[1]][pos[0]]) { - props.onFailure(); - return; - } - setAnswer(old => { - old[pos[1]][pos[0]] = true; - if(fieldEquals(minefield, old)) props.onSuccess(); - return old; - }); - } + function press(event: React.KeyboardEvent): void { + event.preventDefault(); + if (memoryPhase) return; + const move = [0, 0]; + const arrow = getArrow(event); + switch (arrow) { + case "↑": + move[1]--; + break; + case "←": + move[0]--; + break; + case "↓": + move[1]++; + break; + case "→": + move[0]++; + break; } + const next = [pos[0] + move[0], pos[1] + move[1]]; + next[0] = (next[0] + minefield[0].length) % minefield[0].length; + next[1] = (next[1] + minefield.length) % minefield.length; + setPos(next); - useEffect(() => { - const id = setTimeout(() => setMemoryPhase(false), 2000); - return () => clearInterval(id); - }, []); + if (event.keyCode == 32) { + if (!minefield[pos[1]][pos[0]]) { + props.onFailure(); + return; + } + setAnswer((old) => { + old[pos[1]][pos[0]] = true; + if (fieldEquals(minefield, old)) props.onSuccess(); + return old; + }); + } + } - return ( - - -

    {memoryPhase?"Remember all the mines!": "Mark all the mines!"}

    - {minefield.map((line, y) =>
    {line.map((cell, x) => {
    -                if(memoryPhase) {
    -                    if(minefield[y][x])
    -                        return [?] 
    -                    return [ ] 
    +  useEffect(() => {
    +    const id = setTimeout(() => setMemoryPhase(false), 2000);
    +    return () => clearInterval(id);
    +  }, []);
    +
    +  return (
    +    
    +      
    +      
    +        

    + {memoryPhase ? "Remember all the mines!" : "Mark all the mines!"} +

    + {minefield.map((line, y) => ( +
    +
    +              {line.map((cell, x) => {
    +                if (memoryPhase) {
    +                  if (minefield[y][x]) return [?] ;
    +                  return [ ] ;
                     } else {
    -                    if(x == pos[0] && y == pos[1])
    -                        return [X] 
    -                    if(answer[y][x])
    -                        return [.] 
    -                    return [ ] 
    +                  if (x == pos[0] && y == pos[1])
    +                    return [X] ;
    +                  if (answer[y][x]) return [.] ;
    +                  return [ ] ;
                     }
    -            })}

    )} - -
    -
    ) + })} +
    +
    +
    + ))} + +
    +
    + ); } function fieldEquals(a: boolean[][], b: boolean[][]): boolean { - function count(field: boolean[][]): number { - return field.flat().reduce((a, b) => a + (b?1:0), 0); - } - return count(a) === count(b); + function count(field: boolean[][]): number { + return field.flat().reduce((a, b) => a + (b ? 1 : 0), 0); + } + return count(a) === count(b); } function generateEmptyField(difficulty: Difficulty): boolean[][] { - const field = []; - for(let i = 0; i < difficulty.height; i++) { - field.push((new Array(Math.round(difficulty.width))).fill(false)); - } - return field; + const field = []; + for (let i = 0; i < difficulty.height; i++) { + field.push(new Array(Math.round(difficulty.width)).fill(false)); + } + return field; } function generateMinefield(difficulty: Difficulty): boolean[][] { - const field = generateEmptyField(difficulty); - for(let i = 0; i < difficulty.mines; i++) { - const x = Math.floor(Math.random()*field.length); - const y = Math.floor(Math.random()*field[0].length); - if (field[x][y]) { - i--; - continue; - } - field[x][y] = true; + const field = generateEmptyField(difficulty); + for (let i = 0; i < difficulty.mines; i++) { + const x = Math.floor(Math.random() * field.length); + const y = Math.floor(Math.random() * field[0].length); + if (field[x][y]) { + i--; + continue; } - return field; + field[x][y] = true; + } + return field; } diff --git a/src/Infiltration/ui/Root.tsx b/src/Infiltration/ui/Root.tsx index 55ffd4e5f..67631fe0f 100644 --- a/src/Infiltration/ui/Root.tsx +++ b/src/Infiltration/ui/Root.tsx @@ -1,45 +1,49 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; import { IEngine } from "../../IEngine"; -import React, { useState } from 'react'; +import React, { useState } from "react"; import { Intro } from "./Intro"; import { Game } from "./Game"; interface IProps { - Player: IPlayer; - Engine: IEngine; - Location: string; - StartingDifficulty: number; - Difficulty: number; - MaxLevel: number; + Player: IPlayer; + Engine: IEngine; + Location: string; + StartingDifficulty: number; + Difficulty: number; + MaxLevel: number; } export function Root(props: IProps): React.ReactElement { - const [start, setStart] = useState(false); + const [start, setStart] = useState(false); - function cancel(): void { - const menu = document.getElementById("mainmenu-container"); - if(menu === null) throw new Error("mainmenu-container not found"); - menu.style.visibility = "visible"; - props.Engine.loadLocationContent(); - } + function cancel(): void { + const menu = document.getElementById("mainmenu-container"); + if (menu === null) throw new Error("mainmenu-container not found"); + menu.style.visibility = "visible"; + props.Engine.loadLocationContent(); + } - if(!start) { - return ( setStart(true)} - cancel={cancel} - />) - } - - return (); -} \ No newline at end of file + start={() => setStart(true)} + cancel={cancel} + /> + ); + } + + return ( + + ); +} diff --git a/src/Infiltration/ui/SlashGame.tsx b/src/Infiltration/ui/SlashGame.tsx index c9b987646..8b3e0ad45 100644 --- a/src/Infiltration/ui/SlashGame.tsx +++ b/src/Infiltration/ui/SlashGame.tsx @@ -1,60 +1,64 @@ -import React, { useState, useEffect } from 'react'; -import Grid from '@material-ui/core/Grid'; +import React, { useState, useEffect } from "react"; +import Grid from "@material-ui/core/Grid"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; import { GameTimer } from "./GameTimer"; import { interpolate } from "./Difficulty"; interface Difficulty { - [key: string]: number; - window: number; + [key: string]: number; + window: number; } const difficulties: { - Trivial: Difficulty; - Normal: Difficulty; - Hard: Difficulty; - Impossible: Difficulty; + Trivial: Difficulty; + Normal: Difficulty; + Hard: Difficulty; + Impossible: Difficulty; } = { - Trivial: {window: 600}, - Normal: {window: 325}, - Hard: {window: 250}, - Impossible: {window: 150}, -} + Trivial: { window: 600 }, + Normal: { window: 325 }, + Hard: { window: 250 }, + Impossible: { window: 150 }, +}; export function SlashGame(props: IMinigameProps): React.ReactElement { - const difficulty: Difficulty = {window: 0}; - interpolate(difficulties, props.difficulty, difficulty); - const [guarding, setGuarding] = useState(true); + const difficulty: Difficulty = { window: 0 }; + interpolate(difficulties, props.difficulty, difficulty); + const [guarding, setGuarding] = useState(true); - function press(event: React.KeyboardEvent): void { - event.preventDefault(); - if(event.keyCode !== 32) return; - if(guarding) { - props.onFailure(); - } else { - props.onSuccess(); - } + function press(event: React.KeyboardEvent): void { + event.preventDefault(); + if (event.keyCode !== 32) return; + if (guarding) { + props.onFailure(); + } else { + props.onSuccess(); } + } - useEffect(() => { - let id2 = -1; - const id = window.setTimeout(() => { - setGuarding(false); - id2 = window.setTimeout(()=>setGuarding(true), difficulty.window) - }, Math.random()*3250+1500); - return () => { - clearInterval(id); - if(id2 !== -1) clearInterval(id2); - } - }, []); + useEffect(() => { + let id2 = -1; + const id = window.setTimeout(() => { + setGuarding(false); + id2 = window.setTimeout(() => setGuarding(true), difficulty.window); + }, Math.random() * 3250 + 1500); + return () => { + clearInterval(id); + if (id2 !== -1) clearInterval(id2); + }; + }, []); - return ( - - -

    Slash when his guard is down!

    -

    {guarding ? "!Guarding!" : "!ATTACKING!"}

    - -
    -
    ) -} \ No newline at end of file + return ( + + + +

    Slash when his guard is down!

    +

    + {guarding ? "!Guarding!" : "!ATTACKING!"} +

    + +
    +
    + ); +} diff --git a/src/Infiltration/ui/Victory.tsx b/src/Infiltration/ui/Victory.tsx index e44f863b2..086a519fe 100644 --- a/src/Infiltration/ui/Victory.tsx +++ b/src/Infiltration/ui/Victory.tsx @@ -1,77 +1,112 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; import { IEngine } from "../../IEngine"; import { Factions } from "../../Faction/Factions"; -import React, { useState } from 'react'; +import React, { useState } from "react"; import { StdButton } from "../../ui/React/StdButton"; -import Grid from '@material-ui/core/Grid'; +import Grid from "@material-ui/core/Grid"; import { Money } from "../../ui/React/Money"; import { Reputation } from "../../ui/React/Reputation"; import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; - interface IProps { - Player: IPlayer; - Engine: IEngine; - StartingDifficulty: number; - Difficulty: number; - MaxLevel: number; + Player: IPlayer; + Engine: IEngine; + StartingDifficulty: number; + Difficulty: number; + MaxLevel: number; } export function Victory(props: IProps): React.ReactElement { - const [faction, setFaction] = useState('none'); + const [faction, setFaction] = useState("none"); - function quitInfiltration(): void { - const menu = document.getElementById("mainmenu-container"); - if(!menu) throw new Error('mainmenu-container somehow null'); - menu.style.visibility = "visible"; - props.Engine.loadLocationContent(); - } + function quitInfiltration(): void { + const menu = document.getElementById("mainmenu-container"); + if (!menu) throw new Error("mainmenu-container somehow null"); + menu.style.visibility = "visible"; + props.Engine.loadLocationContent(); + } - const levelBonus = props.MaxLevel*Math.pow(1.01, props.MaxLevel); + const levelBonus = props.MaxLevel * Math.pow(1.01, props.MaxLevel); - const repGain = Math.pow(props.Difficulty+1, 1.1)* - Math.pow(props.StartingDifficulty, 1.2)* - 30*levelBonus*BitNodeMultipliers.InfiltrationRep; + const repGain = + Math.pow(props.Difficulty + 1, 1.1) * + Math.pow(props.StartingDifficulty, 1.2) * + 30 * + levelBonus * + BitNodeMultipliers.InfiltrationRep; - const moneyGain = Math.pow(props.Difficulty+1, 2)* - Math.pow(props.StartingDifficulty, 3)* - 3e3*levelBonus*BitNodeMultipliers.InfiltrationMoney; + const moneyGain = + Math.pow(props.Difficulty + 1, 2) * + Math.pow(props.StartingDifficulty, 3) * + 3e3 * + levelBonus * + BitNodeMultipliers.InfiltrationMoney; - function sell(): void { - props.Player.gainMoney(moneyGain); - props.Player.recordMoneySource(moneyGain, 'infiltration'); - quitInfiltration(); - } + function sell(): void { + props.Player.gainMoney(moneyGain); + props.Player.recordMoneySource(moneyGain, "infiltration"); + quitInfiltration(); + } - function trade(): void { - if(faction === 'none') return; - Factions[faction].playerReputation += repGain; - quitInfiltration(); - } + function trade(): void { + if (faction === "none") return; + Factions[faction].playerReputation += repGain; + quitInfiltration(); + } - function changeDropdown(event: React.ChangeEvent): void { - setFaction(event.target.value); - } + function changeDropdown(event: React.ChangeEvent): void { + setFaction(event.target.value); + } - return (<> - - -

    Infiltration successful!

    -
    - -

    You can trade the confidential information you found for money or reputation.

    - - {"Trade for "}{Reputation(repGain)}{" reputation"}} /> -
    - - {"Sell for "}} /> - - - - + return ( + <> + + +

    Infiltration successful!

    - ) -} \ No newline at end of file + +

    + You can trade the confidential information you found for money or + reputation. +

    + + + {"Trade for "} + {Reputation(repGain)} + {" reputation"} + + } + /> +
    + + + {"Sell for "} + + + } + /> + + + + +
    + + ); +} diff --git a/src/Infiltration/ui/WireCuttingGame.tsx b/src/Infiltration/ui/WireCuttingGame.tsx index 92201baeb..cd22e70b6 100644 --- a/src/Infiltration/ui/WireCuttingGame.tsx +++ b/src/Infiltration/ui/WireCuttingGame.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import Grid from '@material-ui/core/Grid'; +import React, { useState } from "react"; +import Grid from "@material-ui/core/Grid"; import { IMinigameProps } from "./IMinigameProps"; import { KeyHandler } from "./KeyHandler"; import { GameTimer } from "./GameTimer"; @@ -7,165 +7,178 @@ import { random } from "../utils"; import { interpolate } from "./Difficulty"; interface Difficulty { - [key: string]: number; - timer: number; - wiresmin: number; - wiresmax: number; - rules: number; + [key: string]: number; + timer: number; + wiresmin: number; + wiresmax: number; + rules: number; } const difficulties: { - Trivial: Difficulty; - Normal: Difficulty; - Hard: Difficulty; - Impossible: Difficulty; + Trivial: Difficulty; + Normal: Difficulty; + Hard: Difficulty; + Impossible: Difficulty; } = { - Trivial: {timer: 9000, wiresmin: 4, wiresmax: 4, rules: 2}, - Normal: {timer: 7000, wiresmin: 6, wiresmax: 6, rules: 2}, - Hard: {timer: 5000, wiresmin: 8, wiresmax: 8, rules: 3}, - Impossible: {timer: 4000, wiresmin: 9, wiresmax: 9, rules: 4}, -} + Trivial: { timer: 9000, wiresmin: 4, wiresmax: 4, rules: 2 }, + Normal: { timer: 7000, wiresmin: 6, wiresmax: 6, rules: 2 }, + Hard: { timer: 5000, wiresmin: 8, wiresmax: 8, rules: 3 }, + Impossible: { timer: 4000, wiresmin: 9, wiresmax: 9, rules: 4 }, +}; +const types = ["|", ".", "/", "-", "█", "#"]; -const types = [ - "|", - ".", - "/", - "-", - "█", - "#", -] - -const colors = [ - "red", - "#FFC107", - "blue", - "white", -] +const colors = ["red", "#FFC107", "blue", "white"]; const colorNames: any = { - "red": "red", - "#FFC107": "yellow", - "blue": "blue", - "white": "white", -} + red: "red", + "#FFC107": "yellow", + blue: "blue", + white: "white", +}; interface Wire { - tpe: string; - colors: string[]; + tpe: string; + colors: string[]; } interface Question { - toString: () => string; - shouldCut: (wire: Wire, index: number) => boolean; + toString: () => string; + shouldCut: (wire: Wire, index: number) => boolean; } export function WireCuttingGame(props: IMinigameProps): React.ReactElement { - const difficulty: Difficulty = {timer: 0, wiresmin: 0, wiresmax: 0, rules: 0}; - interpolate(difficulties, props.difficulty, difficulty); - const timer = difficulty.timer; - const [wires] = useState(generateWires(difficulty)); - const [cutWires, setCutWires] = useState((new Array(wires.length)).fill(false)); - const [questions] = useState(generateQuestion(wires, difficulty)); + const difficulty: Difficulty = { + timer: 0, + wiresmin: 0, + wiresmax: 0, + rules: 0, + }; + interpolate(difficulties, props.difficulty, difficulty); + const timer = difficulty.timer; + const [wires] = useState(generateWires(difficulty)); + const [cutWires, setCutWires] = useState(new Array(wires.length).fill(false)); + const [questions] = useState(generateQuestion(wires, difficulty)); - function press(event: React.KeyboardEvent): void { - event.preventDefault(); - const wireNum = parseInt(event.key); - - if(wireNum < 1 || wireNum > wires.length || isNaN(wireNum)) return; - setCutWires(old => { - const next = [...old]; - next[wireNum-1] = true; - if(!questions.some((q => q.shouldCut(wires[wireNum-1], wireNum-1)))) { - props.onFailure(); - } + function press(event: React.KeyboardEvent): void { + event.preventDefault(); + const wireNum = parseInt(event.key); - // check if we won - const wiresToBeCut = []; - for(let j = 0; j < wires.length; j++) { - let shouldBeCut = false; - for(let i = 0; i < questions.length; i++) { - shouldBeCut = shouldBeCut || questions[i].shouldCut(wires[j], j) - } - wiresToBeCut.push(shouldBeCut); - } - if(wiresToBeCut.every((b, i) => b === next[i])) { - props.onSuccess(); - } + if (wireNum < 1 || wireNum > wires.length || isNaN(wireNum)) return; + setCutWires((old) => { + const next = [...old]; + next[wireNum - 1] = true; + if ( + !questions.some((q) => q.shouldCut(wires[wireNum - 1], wireNum - 1)) + ) { + props.onFailure(); + } - return next; - }); - } + // check if we won + const wiresToBeCut = []; + for (let j = 0; j < wires.length; j++) { + let shouldBeCut = false; + for (let i = 0; i < questions.length; i++) { + shouldBeCut = shouldBeCut || questions[i].shouldCut(wires[j], j); + } + wiresToBeCut.push(shouldBeCut); + } + if (wiresToBeCut.every((b, i) => b === next[i])) { + props.onSuccess(); + } - return ( - - -

    Cut the wires with the following properties! (keyboard 1 to 9)

    - {questions.map((question, i) =>

    {question.toString()}

    )} -
    {(new Array(wires.length)).fill(0).map((_, i) =>  {i+1}    )}
    - {(new Array(8)).fill(0).map((_, i) =>
    -
    -                {wires.map((wire, j) => {
    -                    if((i === 3 || i === 4) && cutWires[j]) return       ;
    -                    return |{wire.tpe}|   
    -                })}
    -                
    -
    )} - -
    -
    ) + return next; + }); + } + + return ( + + + +

    + Cut the wires with the following properties! (keyboard 1 to 9) +

    + {questions.map((question, i) => ( +

    {question.toString()}

    + ))} +
    +          {new Array(wires.length).fill(0).map((_, i) => (
    +             {i + 1}    
    +          ))}
    +        
    + {new Array(8).fill(0).map((_, i) => ( +
    +
    +              {wires.map((wire, j) => {
    +                if ((i === 3 || i === 4) && cutWires[j])
    +                  return (
    +                          
    +                  );
    +                return (
    +                  
    +                    |{wire.tpe}|   
    +                  
    +                );
    +              })}
    +            
    +
    + ))} + +
    +
    + ); } function randomPositionQuestion(wires: Wire[]): Question { - const index = Math.floor(Math.random() * wires.length); - return { - toString: (): string => { - return `Cut wires number ${index+1}.`; - }, - shouldCut: (wire: Wire, i: number): boolean => { - return index === i; - }, - } + const index = Math.floor(Math.random() * wires.length); + return { + toString: (): string => { + return `Cut wires number ${index + 1}.`; + }, + shouldCut: (wire: Wire, i: number): boolean => { + return index === i; + }, + }; } function randomColorQuestion(wires: Wire[]): Question { - const index = Math.floor(Math.random() * wires.length); - const cutColor = wires[index].colors[0]; - return { - toString: (): string => { - return `Cut all wires colored ${colorNames[cutColor]}.`; - }, - shouldCut: (wire: Wire): boolean => { - return wire.colors.includes(cutColor); - }, - } + const index = Math.floor(Math.random() * wires.length); + const cutColor = wires[index].colors[0]; + return { + toString: (): string => { + return `Cut all wires colored ${colorNames[cutColor]}.`; + }, + shouldCut: (wire: Wire): boolean => { + return wire.colors.includes(cutColor); + }, + }; } function generateQuestion(wires: Wire[], difficulty: Difficulty): Question[] { - const numQuestions = difficulty.rules; - const questionGenerators = [ - randomPositionQuestion, - randomColorQuestion, - ] - const questions = []; - for(let i = 0; i < numQuestions; i++) { - questions.push(questionGenerators[i%2](wires)); - } - return questions; + const numQuestions = difficulty.rules; + const questionGenerators = [randomPositionQuestion, randomColorQuestion]; + const questions = []; + for (let i = 0; i < numQuestions; i++) { + questions.push(questionGenerators[i % 2](wires)); + } + return questions; } function generateWires(difficulty: Difficulty): Wire[] { - const wires = []; - const numWires = random(difficulty.wiresmin, difficulty.wiresmax); - for(let i = 0; i < numWires; i++) { - const wireColors = [colors[Math.floor(Math.random()*colors.length)]]; - if(Math.random() < 0.15) { - wireColors.push(colors[Math.floor(Math.random()*colors.length)]); - } - wires.push({ - tpe: types[Math.floor(Math.random()*types.length)], - colors: wireColors, - }); + const wires = []; + const numWires = random(difficulty.wiresmin, difficulty.wiresmax); + for (let i = 0; i < numWires; i++) { + const wireColors = [colors[Math.floor(Math.random() * colors.length)]]; + if (Math.random() < 0.15) { + wireColors.push(colors[Math.floor(Math.random() * colors.length)]); } - return wires; -} \ No newline at end of file + wires.push({ + tpe: types[Math.floor(Math.random() * types.length)], + colors: wireColors, + }); + } + return wires; +} diff --git a/src/Infiltration/utils.ts b/src/Infiltration/utils.ts index 8c71e779d..81fbe2223 100644 --- a/src/Infiltration/utils.ts +++ b/src/Infiltration/utils.ts @@ -1,23 +1,23 @@ -import React from 'react'; +import React from "react"; export function random(min: number, max: number): number { - return Math.random()*(max-min)+min; + return Math.random() * (max - min) + min; } export function getArrow(event: React.KeyboardEvent): string { - switch(event.keyCode) { + switch (event.keyCode) { case 38: case 87: - return "↑"; + return "↑"; case 65: case 37: - return "←"; + return "←"; case 40: case 83: - return "↓"; + return "↓"; case 39: case 68: - return "→"; - } - return ''; -} \ No newline at end of file + return "→"; + } + return ""; +} diff --git a/src/InteractiveTutorial.d.ts b/src/InteractiveTutorial.d.ts index d99d083d8..e92fff21c 100644 --- a/src/InteractiveTutorial.d.ts +++ b/src/InteractiveTutorial.d.ts @@ -1,3 +1,3 @@ export declare function iTutorialNextStep(): void; -export declare const ITutorial: {isRunning: boolean; currStep: number}; -export declare const iTutorialSteps: {[key: string]: number}; \ No newline at end of file +export declare const ITutorial: { isRunning: boolean; currStep: number }; +export declare const iTutorialSteps: { [key: string]: number }; diff --git a/src/InteractiveTutorial.js b/src/InteractiveTutorial.js index c35f91c24..ce82d5058 100644 --- a/src/InteractiveTutorial.js +++ b/src/InteractiveTutorial.js @@ -13,512 +13,587 @@ import { removeElementById } from "../utils/uiHelpers/removeElementById"; // Ordered array of keys to Interactive Tutorial Steps const orderedITutorialSteps = [ - "Start", - "GoToCharacterPage", // Click on 'Stats' page - "CharacterPage", // Introduction to 'Stats' page - "CharacterGoToTerminalPage", // Go back to Terminal - "TerminalIntro", // Introduction to Terminal - "TerminalHelp", // Using 'help' Terminal command - "TerminalLs", // Using 'ls' Terminal command - "TerminalScan", // Using 'scan' Terminal command - "TerminalScanAnalyze1", // Using 'scan-analyze' Terminal command - "TerminalScanAnalyze2", // Using 'scan-analyze 3' Terminal command - "TerminalConnect", // Connecting to n00dles - "TerminalAnalyze", // Analyzing n00dles - "TerminalNuke", // NUKE n00dles - "TerminalManualHack", // Hack n00dles - "TerminalHackingMechanics", // Explanation of hacking mechanics - "TerminalCreateScript", // Create a script using 'nano' - "TerminalTypeScript", // Script Editor page - Type script and then save & close - "TerminalFree", // Using 'Free' Terminal command - "TerminalRunScript", // Running script using 'run' Terminal command - "TerminalGoToActiveScriptsPage", - "ActiveScriptsPage", - "ActiveScriptsToTerminal", - "TerminalTailScript", - "GoToHacknetNodesPage", - "HacknetNodesIntroduction", - "HacknetNodesGoToWorldPage", - "WorldDescription", - "TutorialPageInfo", - "End", -] + "Start", + "GoToCharacterPage", // Click on 'Stats' page + "CharacterPage", // Introduction to 'Stats' page + "CharacterGoToTerminalPage", // Go back to Terminal + "TerminalIntro", // Introduction to Terminal + "TerminalHelp", // Using 'help' Terminal command + "TerminalLs", // Using 'ls' Terminal command + "TerminalScan", // Using 'scan' Terminal command + "TerminalScanAnalyze1", // Using 'scan-analyze' Terminal command + "TerminalScanAnalyze2", // Using 'scan-analyze 3' Terminal command + "TerminalConnect", // Connecting to n00dles + "TerminalAnalyze", // Analyzing n00dles + "TerminalNuke", // NUKE n00dles + "TerminalManualHack", // Hack n00dles + "TerminalHackingMechanics", // Explanation of hacking mechanics + "TerminalCreateScript", // Create a script using 'nano' + "TerminalTypeScript", // Script Editor page - Type script and then save & close + "TerminalFree", // Using 'Free' Terminal command + "TerminalRunScript", // Running script using 'run' Terminal command + "TerminalGoToActiveScriptsPage", + "ActiveScriptsPage", + "ActiveScriptsToTerminal", + "TerminalTailScript", + "GoToHacknetNodesPage", + "HacknetNodesIntroduction", + "HacknetNodesGoToWorldPage", + "WorldDescription", + "TutorialPageInfo", + "End", +]; // Create an 'enum' for the Steps const iTutorialSteps = {}; for (let i = 0; i < orderedITutorialSteps.length; ++i) { - iTutorialSteps[orderedITutorialSteps[i]] = i; + iTutorialSteps[orderedITutorialSteps[i]] = i; } const ITutorial = { - currStep: 0, // iTutorialSteps.Start - isRunning: false, + currStep: 0, // iTutorialSteps.Start + isRunning: false, - // Keeps track of whether each step has been done - stepIsDone: {}, -} + // Keeps track of whether each step has been done + stepIsDone: {}, +}; function iTutorialStart() { - // Initialize Interactive Tutorial state by settings 'done' for each state to false - ITutorial.stepIsDone = {}; - for (let i = 0; i < orderedITutorialSteps.length; ++i) { - ITutorial.stepIsDone[i] = false; - } + // Initialize Interactive Tutorial state by settings 'done' for each state to false + ITutorial.stepIsDone = {}; + for (let i = 0; i < orderedITutorialSteps.length; ++i) { + ITutorial.stepIsDone[i] = false; + } - Engine.loadTerminalContent(); + Engine.loadTerminalContent(); - // Don't autosave during this interactive tutorial - Engine.Counters.autoSaveCounter = Infinity; - ITutorial.currStep = 0; - ITutorial.isRunning = true; + // Don't autosave during this interactive tutorial + Engine.Counters.autoSaveCounter = Infinity; + ITutorial.currStep = 0; + ITutorial.isRunning = true; - document.getElementById("interactive-tutorial-container").style.display = "block"; + document.getElementById("interactive-tutorial-container").style.display = + "block"; - // Exit tutorial button - const exitButton = clearEventListeners("interactive-tutorial-exit"); - exitButton.addEventListener("click", function() { - iTutorialEnd(); - return false; - }); + // Exit tutorial button + const exitButton = clearEventListeners("interactive-tutorial-exit"); + exitButton.addEventListener("click", function () { + iTutorialEnd(); + return false; + }); - // Back button - const backButton = clearEventListeners("interactive-tutorial-back"); - backButton.addEventListener("click", function() { - iTutorialPrevStep(); - return false; - }); + // Back button + const backButton = clearEventListeners("interactive-tutorial-back"); + backButton.addEventListener("click", function () { + iTutorialPrevStep(); + return false; + }); - // Next button - const nextButton = clearEventListeners("interactive-tutorial-next"); - nextButton.addEventListener("click", function() { - iTutorialNextStep(); - return false; - }); + // Next button + const nextButton = clearEventListeners("interactive-tutorial-next"); + nextButton.addEventListener("click", function () { + iTutorialNextStep(); + return false; + }); - iTutorialEvaluateStep(); + iTutorialEvaluateStep(); } function iTutorialEvaluateStep() { - if (!ITutorial.isRunning) {return;} + if (!ITutorial.isRunning) { + return; + } - // Disable and clear main menu - const terminalMainMenu = clearEventListeners("terminal-menu-link"); - const statsMainMenu = clearEventListeners("stats-menu-link"); - const activeScriptsMainMenu = clearEventListeners("active-scripts-menu-link"); - const hacknetMainMenu = clearEventListeners("hacknet-nodes-menu-link"); - const cityMainMenu = clearEventListeners("city-menu-link"); - const tutorialMainMenu = clearEventListeners("tutorial-menu-link"); - terminalMainMenu.removeAttribute("class"); - statsMainMenu.removeAttribute("class"); - activeScriptsMainMenu.removeAttribute("class"); - hacknetMainMenu.removeAttribute("class"); - cityMainMenu.removeAttribute("class"); - tutorialMainMenu.removeAttribute("class"); + // Disable and clear main menu + const terminalMainMenu = clearEventListeners("terminal-menu-link"); + const statsMainMenu = clearEventListeners("stats-menu-link"); + const activeScriptsMainMenu = clearEventListeners("active-scripts-menu-link"); + const hacknetMainMenu = clearEventListeners("hacknet-nodes-menu-link"); + const cityMainMenu = clearEventListeners("city-menu-link"); + const tutorialMainMenu = clearEventListeners("tutorial-menu-link"); + terminalMainMenu.removeAttribute("class"); + statsMainMenu.removeAttribute("class"); + activeScriptsMainMenu.removeAttribute("class"); + hacknetMainMenu.removeAttribute("class"); + cityMainMenu.removeAttribute("class"); + tutorialMainMenu.removeAttribute("class"); - // Interactive Tutorial Next button - const nextBtn = document.getElementById("interactive-tutorial-next"); + // Interactive Tutorial Next button + const nextBtn = document.getElementById("interactive-tutorial-next"); - switch(ITutorial.currStep) { + switch (ITutorial.currStep) { case iTutorialSteps.Start: - Engine.loadTerminalContent(); - iTutorialSetText("Welcome to Bitburner, a cyberpunk-themed incremental RPG! " + - "The game takes place in a dark, dystopian future... The year is 2077...

    " + - "This tutorial will show you the basics of the game. " + - "You may skip the tutorial at any time."); - nextBtn.style.display = "inline-block"; - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "Welcome to Bitburner, a cyberpunk-themed incremental RPG! " + + "The game takes place in a dark, dystopian future... The year is 2077...

    " + + "This tutorial will show you the basics of the game. " + + "You may skip the tutorial at any time.", + ); + nextBtn.style.display = "inline-block"; + break; case iTutorialSteps.GoToCharacterPage: - Engine.loadTerminalContent(); - iTutorialSetText("Let's start by heading to the Stats page. Click the Stats tab on " + - "the main navigation menu (left-hand side of the screen)"); - nextBtn.style.display = "none"; + Engine.loadTerminalContent(); + iTutorialSetText( + "Let's start by heading to the Stats page. Click the Stats tab on " + + "the main navigation menu (left-hand side of the screen)", + ); + nextBtn.style.display = "none"; - // Flash 'Stats' menu and set its tutorial click handler - statsMainMenu.setAttribute("class", "flashing-button"); - statsMainMenu.addEventListener("click", function() { - Engine.loadCharacterContent(); - iTutorialNextStep(); //Opening the character page will go to the next step - return false; - }); - break; + // Flash 'Stats' menu and set its tutorial click handler + statsMainMenu.setAttribute("class", "flashing-button"); + statsMainMenu.addEventListener("click", function () { + Engine.loadCharacterContent(); + iTutorialNextStep(); //Opening the character page will go to the next step + return false; + }); + break; case iTutorialSteps.CharacterPage: - Engine.loadCharacterContent(); - iTutorialSetText("The Stats page shows a lot of important information about your progress, " + - "such as your skills, money, and bonuses. ") - nextBtn.style.display = "inline-block"; - break; + Engine.loadCharacterContent(); + iTutorialSetText( + "The Stats page shows a lot of important information about your progress, " + + "such as your skills, money, and bonuses. ", + ); + nextBtn.style.display = "inline-block"; + break; case iTutorialSteps.CharacterGoToTerminalPage: - Engine.loadCharacterContent(); - iTutorialSetText("Let's head to your computer's terminal by clicking the Terminal tab on the " + - "main navigation menu."); - nextBtn.style.display = "none"; + Engine.loadCharacterContent(); + iTutorialSetText( + "Let's head to your computer's terminal by clicking the Terminal tab on the " + + "main navigation menu.", + ); + nextBtn.style.display = "none"; - // Flash 'Terminal' menu and set its tutorial click handler - terminalMainMenu.setAttribute("class", "flashing-button"); - terminalMainMenu.addEventListener("click", function() { - Engine.loadTerminalContent(); - iTutorialNextStep(); - return false; - }); - break; + // Flash 'Terminal' menu and set its tutorial click handler + terminalMainMenu.setAttribute("class", "flashing-button"); + terminalMainMenu.addEventListener("click", function () { + Engine.loadTerminalContent(); + iTutorialNextStep(); + return false; + }); + break; case iTutorialSteps.TerminalIntro: - Engine.loadTerminalContent(); - iTutorialSetText("The Terminal is used to interface with your home computer as well as " + - "all of the other machines around the world."); - nextBtn.style.display = "inline-block"; - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "The Terminal is used to interface with your home computer as well as " + + "all of the other machines around the world.", + ); + nextBtn.style.display = "inline-block"; + break; case iTutorialSteps.TerminalHelp: - Engine.loadTerminalContent(); - iTutorialSetText("Let's try it out. Start by entering the help command into the Terminal " + - "(Don't forget to press Enter after typing the command)"); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "Let's try it out. Start by entering the help command into the Terminal " + + "(Don't forget to press Enter after typing the command)", + ); + nextBtn.style.display = "none"; // next step triggered by terminal command + break; case iTutorialSteps.TerminalLs: - Engine.loadTerminalContent(); - iTutorialSetText("The help command displays a list of all available Terminal commands, how to use them, " + - "and a description of what they do.

    Let's try another command. Enter the ls command."); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "The help command displays a list of all available Terminal commands, how to use them, " + + "and a description of what they do.

    Let's try another command. Enter the ls command.", + ); + nextBtn.style.display = "none"; // next step triggered by terminal command + break; case iTutorialSteps.TerminalScan: - Engine.loadTerminalContent(); - iTutorialSetText(" ls is a basic command that shows files " + - "on the computer. Right now, it shows that you have a program called NUKE.exe on your computer. " + - "We'll get to what this does later.

    Using your home computer's terminal, you can connect " + - "to other machines throughout the world. Let's do that now by first entering " + - "the scan command."); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; + Engine.loadTerminalContent(); + iTutorialSetText( + " ls is a basic command that shows files " + + "on the computer. Right now, it shows that you have a program called NUKE.exe on your computer. " + + "We'll get to what this does later.

    Using your home computer's terminal, you can connect " + + "to other machines throughout the world. Let's do that now by first entering " + + "the scan command.", + ); + nextBtn.style.display = "none"; // next step triggered by terminal command + break; case iTutorialSteps.TerminalScanAnalyze1: - Engine.loadTerminalContent(); - iTutorialSetText("The scan command shows all available network connections. In other words, " + - "it displays a list of all servers that can be connected to from your " + - "current machine. A server is identified by its hostname.

    " + - "That's great and all, but there's so many servers. Which one should you go to? " + - "The scan-analyze command gives some more detailed information about servers on the " + - "network. Try it now!"); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "The scan command shows all available network connections. In other words, " + + "it displays a list of all servers that can be connected to from your " + + "current machine. A server is identified by its hostname.

    " + + "That's great and all, but there's so many servers. Which one should you go to? " + + "The scan-analyze command gives some more detailed information about servers on the " + + "network. Try it now!", + ); + nextBtn.style.display = "none"; // next step triggered by terminal command + break; case iTutorialSteps.TerminalScanAnalyze2: - Engine.loadTerminalContent(); - iTutorialSetText("You just ran scan-analyze with a depth of one. This command shows more detailed " + - "information about each server that you can connect to (servers that are a distance of " + - "one node away).

    It is also possible to run scan-analyze with " + - "a higher depth. Let's try a depth of two with the following command: scan-analyze 2.") - nextBtn.style.display = "none"; // next step triggered by terminal command - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "You just ran scan-analyze with a depth of one. This command shows more detailed " + + "information about each server that you can connect to (servers that are a distance of " + + "one node away).

    It is also possible to run scan-analyze with " + + "a higher depth. Let's try a depth of two with the following command: scan-analyze 2.", + ); + nextBtn.style.display = "none"; // next step triggered by terminal command + break; case iTutorialSteps.TerminalConnect: - Engine.loadTerminalContent(); - iTutorialSetText("Now you can see information about all servers that are up to two nodes away, as well " + - "as figure out how to navigate to those servers through the network. You can only connect to " + - "a server that is one node away. To connect to a machine, use the connect [hostname] command.

    " + - "From the results of the scan-analyze command, we can see that the n00dles server is " + - "only one node away. Let's connect so it now using: connect n00dles"); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "Now you can see information about all servers that are up to two nodes away, as well " + + "as figure out how to navigate to those servers through the network. You can only connect to " + + "a server that is one node away. To connect to a machine, use the connect [hostname] command.

    " + + "From the results of the scan-analyze command, we can see that the n00dles server is " + + "only one node away. Let's connect so it now using: connect n00dles", + ); + nextBtn.style.display = "none"; // next step triggered by terminal command + break; case iTutorialSteps.TerminalAnalyze: - Engine.loadTerminalContent(); - iTutorialSetText("You are now connected to another machine! What can you do now? You can hack it!

    In the year 2077, currency has " + - "become digital and decentralized. People and corporations store their money " + - "on servers and computers. Using your hacking abilities, you can hack servers " + - "to steal money and gain experience.

    " + - "Before you try to hack a server, you should run diagnostics using the analyze command."); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "You are now connected to another machine! What can you do now? You can hack it!

    In the year 2077, currency has " + + "become digital and decentralized. People and corporations store their money " + + "on servers and computers. Using your hacking abilities, you can hack servers " + + "to steal money and gain experience.

    " + + "Before you try to hack a server, you should run diagnostics using the analyze command.", + ); + nextBtn.style.display = "none"; // next step triggered by terminal command + break; case iTutorialSteps.TerminalNuke: - Engine.loadTerminalContent(); - iTutorialSetText("When the analyze command finishes running it will show useful information " + - "about hacking the server.

    For this server, the required hacking skill is only 1, " + - "which means you can hack it right now. However, in order to hack a server " + - "you must first gain root access. The NUKE.exe program that we saw earlier on your " + - "home computer is a virus that will grant you root access to a machine if there are enough " + - "open ports.

    The analyze results shows that there do not need to be any open ports " + - "on this machine for the NUKE virus to work, so go ahead and run the virus using the " + - "run NUKE.exe command."); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "When the analyze command finishes running it will show useful information " + + "about hacking the server.

    For this server, the required hacking skill is only 1, " + + "which means you can hack it right now. However, in order to hack a server " + + "you must first gain root access. The NUKE.exe program that we saw earlier on your " + + "home computer is a virus that will grant you root access to a machine if there are enough " + + "open ports.

    The analyze results shows that there do not need to be any open ports " + + "on this machine for the NUKE virus to work, so go ahead and run the virus using the " + + "run NUKE.exe command.", + ); + nextBtn.style.display = "none"; // next step triggered by terminal command + break; case iTutorialSteps.TerminalManualHack: - Engine.loadTerminalContent(); - iTutorialSetText("You now have root access! You can hack the server using the hack command. " + - "Try doing that now."); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "You now have root access! You can hack the server using the hack command. " + + "Try doing that now.", + ); + nextBtn.style.display = "none"; // next step triggered by terminal command + break; case iTutorialSteps.TerminalHackingMechanics: - Engine.loadTerminalContent(); - iTutorialSetText("You are now attempting to hack the server. Performing a hack takes time and " + - "only has a certain percentage chance " + - "of success. This time and success chance is determined by a variety of factors, including " + - "your hacking skill and the server's security level.

    " + - "If your attempt to hack the server is successful, you will steal a certain percentage " + - "of the server's total money. This percentage is affected by your hacking skill and " + - "the server's security level.

    The amount of money on a server is not limitless. So, if " + - "you constantly hack a server and deplete its money, then you will encounter " + - "diminishing returns in your hacking."); - nextBtn.style.display = "inline-block"; - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "You are now attempting to hack the server. Performing a hack takes time and " + + "only has a certain percentage chance " + + "of success. This time and success chance is determined by a variety of factors, including " + + "your hacking skill and the server's security level.

    " + + "If your attempt to hack the server is successful, you will steal a certain percentage " + + "of the server's total money. This percentage is affected by your hacking skill and " + + "the server's security level.

    The amount of money on a server is not limitless. So, if " + + "you constantly hack a server and deplete its money, then you will encounter " + + "diminishing returns in your hacking.", + ); + nextBtn.style.display = "inline-block"; + break; case iTutorialSteps.TerminalCreateScript: - Engine.loadTerminalContent(); - iTutorialSetText("Hacking is the core mechanic of the game and is necessary for progressing. However, " + - "you don't want to be hacking manually the entire time. You can automate your hacking " + - "by writing scripts!

    To create a new script or edit an existing one, you can use the nano " + - "command. Scripts must end with the .script extension. Let's make a script now by " + - "entering nano n00dles.script after the hack command finishes running (Sidenote: Pressing ctrl + c" + - " will end a command like hack early)"); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "Hacking is the core mechanic of the game and is necessary for progressing. However, " + + "you don't want to be hacking manually the entire time. You can automate your hacking " + + "by writing scripts!

    To create a new script or edit an existing one, you can use the nano " + + "command. Scripts must end with the .script extension. Let's make a script now by " + + "entering nano n00dles.script after the hack command finishes running (Sidenote: Pressing ctrl + c" + + " will end a command like hack early)", + ); + nextBtn.style.display = "none"; // next step triggered by terminal command + break; case iTutorialSteps.TerminalTypeScript: - Engine.loadScriptEditorContent("n00dles.script", ""); - iTutorialSetText("This is the script editor. You can use it to program your scripts. Scripts are " + - "written in a simplified version of javascript. Copy and paste the following code into the script editor:

    " + - "
    " +
    -                         "while(true) {\n" +
    -                         "  hack('n00dles');\n" +
    -                         "}
    " + - "For anyone with basic programming experience, this code should be straightforward. " + - "This script will continuously hack the n00dles server.

    " + - "To save and close the script editor, press the button in the bottom left, or press ctrl + b."); - nextBtn.style.display = "none"; // next step triggered in saveAndCloseScriptEditor() (Script.js) - break; + Engine.loadScriptEditorContent("n00dles.script", ""); + iTutorialSetText( + "This is the script editor. You can use it to program your scripts. Scripts are " + + "written in a simplified version of javascript. Copy and paste the following code into the script editor:

    " + + "
    " +
    +          "while(true) {\n" +
    +          "  hack('n00dles');\n" +
    +          "}
    " + + "For anyone with basic programming experience, this code should be straightforward. " + + "This script will continuously hack the n00dles server.

    " + + "To save and close the script editor, press the button in the bottom left, or press ctrl + b.", + ); + nextBtn.style.display = "none"; // next step triggered in saveAndCloseScriptEditor() (Script.js) + break; case iTutorialSteps.TerminalFree: - Engine.loadTerminalContent(); - iTutorialSetText("Now we'll run the script. Scripts require a certain amount of RAM to run, and can be " + - "run on any machine which you have root access to. Different servers have different " + - "amounts of RAM. You can also purchase more RAM for your home server.

    To check how much " + - "RAM is available on this machine, enter the free command."); - nextBtn.style.display = "none"; // next step triggered by terminal commmand - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "Now we'll run the script. Scripts require a certain amount of RAM to run, and can be " + + "run on any machine which you have root access to. Different servers have different " + + "amounts of RAM. You can also purchase more RAM for your home server.

    To check how much " + + "RAM is available on this machine, enter the free command.", + ); + nextBtn.style.display = "none"; // next step triggered by terminal commmand + break; case iTutorialSteps.TerminalRunScript: - Engine.loadTerminalContent(); - iTutorialSetText("We have 4GB of free RAM on this machine, which is enough to run our " + - "script. Let's run our script using run n00dles.script."); - nextBtn.style.display = "none"; // next step triggered by terminal commmand - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "We have 4GB of free RAM on this machine, which is enough to run our " + + "script. Let's run our script using run n00dles.script.", + ); + nextBtn.style.display = "none"; // next step triggered by terminal commmand + break; case iTutorialSteps.TerminalGoToActiveScriptsPage: - Engine.loadTerminalContent(); - iTutorialSetText("Your script is now running! " + - "It will continuously run in the background and will automatically stop if " + - "the code ever completes (the n00dles.script will never complete because it " + - "runs an infinite loop).

    These scripts can passively earn you income and hacking experience. " + - "Your scripts will also earn money and experience while you are offline, although at a " + - "slightly slower rate.

    " + - "Let's check out some statistics for our running scripts by clicking the " + - "Active Scripts link in the main navigation menu."); - nextBtn.style.display = "none"; + Engine.loadTerminalContent(); + iTutorialSetText( + "Your script is now running! " + + "It will continuously run in the background and will automatically stop if " + + "the code ever completes (the n00dles.script will never complete because it " + + "runs an infinite loop).

    These scripts can passively earn you income and hacking experience. " + + "Your scripts will also earn money and experience while you are offline, although at a " + + "slightly slower rate.

    " + + "Let's check out some statistics for our running scripts by clicking the " + + "Active Scripts link in the main navigation menu.", + ); + nextBtn.style.display = "none"; - // Flash 'Active Scripts' menu and set its tutorial click handler - activeScriptsMainMenu.setAttribute("class", "flashing-button"); - activeScriptsMainMenu.addEventListener("click", function() { - Engine.loadActiveScriptsContent(); - iTutorialNextStep(); - return false; - }); - break; - case iTutorialSteps.ActiveScriptsPage: + // Flash 'Active Scripts' menu and set its tutorial click handler + activeScriptsMainMenu.setAttribute("class", "flashing-button"); + activeScriptsMainMenu.addEventListener("click", function () { Engine.loadActiveScriptsContent(); - iTutorialSetText("This page displays information about all of your scripts that are " + - "running across every server. You can use this to gauge how well " + - "your scripts are doing. Let's go back to the Terminal"); - nextBtn.style.display = "none"; + iTutorialNextStep(); + return false; + }); + break; + case iTutorialSteps.ActiveScriptsPage: + Engine.loadActiveScriptsContent(); + iTutorialSetText( + "This page displays information about all of your scripts that are " + + "running across every server. You can use this to gauge how well " + + "your scripts are doing. Let's go back to the Terminal", + ); + nextBtn.style.display = "none"; - // Flash 'Terminal' button and set its tutorial click handler - terminalMainMenu.setAttribute("class", "flashing-button"); - terminalMainMenu.addEventListener("click", function() { - Engine.loadTerminalContent(); - iTutorialNextStep(); - return false; - }); - break; + // Flash 'Terminal' button and set its tutorial click handler + terminalMainMenu.setAttribute("class", "flashing-button"); + terminalMainMenu.addEventListener("click", function () { + Engine.loadTerminalContent(); + iTutorialNextStep(); + return false; + }); + break; case iTutorialSteps.ActiveScriptsToTerminal: - Engine.loadTerminalContent(); - iTutorialSetText("One last thing about scripts, each active script contains logs that detail " + - "what it's doing. We can check these logs using the tail command. Do that " + - "now for the script we just ran by typing tail n00dles.script"); - nextBtn.style.display = "none"; // next step triggered by terminal command - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "One last thing about scripts, each active script contains logs that detail " + + "what it's doing. We can check these logs using the tail command. Do that " + + "now for the script we just ran by typing tail n00dles.script", + ); + nextBtn.style.display = "none"; // next step triggered by terminal command + break; case iTutorialSteps.TerminalTailScript: - Engine.loadTerminalContent(); - iTutorialSetText("The log for this script won't show much right now (it might show nothing at all) because it " + - "just started running...but check back again in a few minutes!

    " + - "This covers the basics of hacking. To learn more about writing " + - "scripts, select the Tutorial link in the " + - "main navigation menu to look at the documentation. " + - "If you are an experienced JavaScript " + - "developer, I would highly suggest you check out the section on " + - "NetscriptJS/Netscript 2.0, it's faster and more powerful.

    For now, let's move on to something else!"); - nextBtn.style.display = "inline-block"; - break; + Engine.loadTerminalContent(); + iTutorialSetText( + "The log for this script won't show much right now (it might show nothing at all) because it " + + "just started running...but check back again in a few minutes!

    " + + "This covers the basics of hacking. To learn more about writing " + + "scripts, select the Tutorial link in the " + + "main navigation menu to look at the documentation. " + + "If you are an experienced JavaScript " + + "developer, I would highly suggest you check out the section on " + + "NetscriptJS/Netscript 2.0, it's faster and more powerful.

    For now, let's move on to something else!", + ); + nextBtn.style.display = "inline-block"; + break; case iTutorialSteps.GoToHacknetNodesPage: - Engine.loadTerminalContent(); - iTutorialSetText("Hacking is not the only way to earn money. One other way to passively " + - "earn money is by purchasing and upgrading Hacknet Nodes. Let's go to " + - "the Hacknet page through the main navigation menu now."); - nextBtn.style.display = "none"; + Engine.loadTerminalContent(); + iTutorialSetText( + "Hacking is not the only way to earn money. One other way to passively " + + "earn money is by purchasing and upgrading Hacknet Nodes. Let's go to " + + "the Hacknet page through the main navigation menu now.", + ); + nextBtn.style.display = "none"; - // Flash 'Hacknet' menu and set its tutorial click handler - hacknetMainMenu.setAttribute("class", "flashing-button"); - hacknetMainMenu.addEventListener("click", function() { - Engine.loadHacknetNodesContent(); - iTutorialNextStep(); - return false; - }); - break; + // Flash 'Hacknet' menu and set its tutorial click handler + hacknetMainMenu.setAttribute("class", "flashing-button"); + hacknetMainMenu.addEventListener("click", function () { + Engine.loadHacknetNodesContent(); + iTutorialNextStep(); + return false; + }); + break; case iTutorialSteps.HacknetNodesIntroduction: - Engine.loadHacknetNodesContent(); - iTutorialSetText("here you can purchase new Hacknet Nodes and upgrade your " + - "existing ones. Let's purchase a new one now."); - nextBtn.style.display = "none"; // Next step triggered by purchaseHacknet() (HacknetNode.js) - break; + Engine.loadHacknetNodesContent(); + iTutorialSetText( + "here you can purchase new Hacknet Nodes and upgrade your " + + "existing ones. Let's purchase a new one now.", + ); + nextBtn.style.display = "none"; // Next step triggered by purchaseHacknet() (HacknetNode.js) + break; case iTutorialSteps.HacknetNodesGoToWorldPage: - Engine.loadHacknetNodesContent(); - iTutorialSetText("You just purchased a Hacknet Node! This Hacknet Node will passively " + - "earn you money over time, both online and offline. When you get enough " + - " money, you can upgrade " + - "your newly-purchased Hacknet Node below.

    " + - "Let's go to the City page through the main navigation menu."); - nextBtn.style.display = "none"; + Engine.loadHacknetNodesContent(); + iTutorialSetText( + "You just purchased a Hacknet Node! This Hacknet Node will passively " + + "earn you money over time, both online and offline. When you get enough " + + " money, you can upgrade " + + "your newly-purchased Hacknet Node below.

    " + + "Let's go to the City page through the main navigation menu.", + ); + nextBtn.style.display = "none"; - // Flash 'City' menu and set its tutorial click handler - cityMainMenu.setAttribute("class", "flashing-button"); - cityMainMenu.addEventListener("click", function() { - Engine.loadLocationContent(); - iTutorialNextStep(); - return false; - }); - break; - case iTutorialSteps.WorldDescription: + // Flash 'City' menu and set its tutorial click handler + cityMainMenu.setAttribute("class", "flashing-button"); + cityMainMenu.addEventListener("click", function () { Engine.loadLocationContent(); - iTutorialSetText("This page lists all of the different locations you can currently " + - "travel to. Each location has something that you can do. " + - "There's a lot of content out in the world, make sure " + - "you explore and discover!

    " + - "Lastly, click on the Tutorial link in the main navigation menu."); - nextBtn.style.display = "none"; + iTutorialNextStep(); + return false; + }); + break; + case iTutorialSteps.WorldDescription: + Engine.loadLocationContent(); + iTutorialSetText( + "This page lists all of the different locations you can currently " + + "travel to. Each location has something that you can do. " + + "There's a lot of content out in the world, make sure " + + "you explore and discover!

    " + + "Lastly, click on the Tutorial link in the main navigation menu.", + ); + nextBtn.style.display = "none"; - // Flash 'Tutorial' menu and set its tutorial click handler - tutorialMainMenu.setAttribute("class", "flashing-button"); - tutorialMainMenu.addEventListener("click", function() { - Engine.loadTutorialContent(); - iTutorialNextStep(); - return false; - }); - break; - case iTutorialSteps.TutorialPageInfo: + // Flash 'Tutorial' menu and set its tutorial click handler + tutorialMainMenu.setAttribute("class", "flashing-button"); + tutorialMainMenu.addEventListener("click", function () { Engine.loadTutorialContent(); - iTutorialSetText("This page contains a lot of different documentation about the game's " + - "content and mechanics. I know it's a lot, but I highly suggest you read " + - "(or at least skim) through this before you start playing. That's the end of the tutorial. " + - "Hope you enjoy the game!"); - nextBtn.style.display = "inline-block"; - nextBtn.innerHTML = "Finish Tutorial"; - break; + iTutorialNextStep(); + return false; + }); + break; + case iTutorialSteps.TutorialPageInfo: + Engine.loadTutorialContent(); + iTutorialSetText( + "This page contains a lot of different documentation about the game's " + + "content and mechanics. I know it's a lot, but I highly suggest you read " + + "(or at least skim) through this before you start playing. That's the end of the tutorial. " + + "Hope you enjoy the game!", + ); + nextBtn.style.display = "inline-block"; + nextBtn.innerHTML = "Finish Tutorial"; + break; case iTutorialSteps.End: - iTutorialEnd(); - break; + iTutorialEnd(); + break; default: - throw new Error("Invalid tutorial step"); - } + throw new Error("Invalid tutorial step"); + } - if (ITutorial.stepIsDone[ITutorial.currStep] === true) { - nextBtn.style.display = "inline-block"; - } + if (ITutorial.stepIsDone[ITutorial.currStep] === true) { + nextBtn.style.display = "inline-block"; + } } // Go to the next step and evaluate it function iTutorialNextStep() { - // Special behavior for certain steps - if (ITutorial.currStep === iTutorialSteps.GoToCharacterPage) { - document.getElementById("stats-menu-link").removeAttribute("class"); - } - if (ITutorial.currStep === iTutorialSteps.CharacterGoToTerminalPage) { - document.getElementById("terminal-menu-link").removeAttribute("class"); - } - if (ITutorial.currStep === iTutorialSteps.TerminalGoToActiveScriptsPage) { - document.getElementById("active-scripts-menu-link").removeAttribute("class"); - } - if (ITutorial.currStep === iTutorialSteps.ActiveScriptsPage) { - document.getElementById("terminal-menu-link").removeAttribute("class"); - } - if (ITutorial.currStep === iTutorialSteps.GoToHacknetNodesPage) { - document.getElementById("hacknet-nodes-menu-link").removeAttribute("class"); - } - if (ITutorial.currStep === iTutorialSteps.HacknetNodesGoToWorldPage) { - document.getElementById("city-menu-link").removeAttribute("class"); - } - if (ITutorial.currStep === iTutorialSteps.WorldDescription) { - document.getElementById("tutorial-menu-link").removeAttribute("class"); - } + // Special behavior for certain steps + if (ITutorial.currStep === iTutorialSteps.GoToCharacterPage) { + document.getElementById("stats-menu-link").removeAttribute("class"); + } + if (ITutorial.currStep === iTutorialSteps.CharacterGoToTerminalPage) { + document.getElementById("terminal-menu-link").removeAttribute("class"); + } + if (ITutorial.currStep === iTutorialSteps.TerminalGoToActiveScriptsPage) { + document + .getElementById("active-scripts-menu-link") + .removeAttribute("class"); + } + if (ITutorial.currStep === iTutorialSteps.ActiveScriptsPage) { + document.getElementById("terminal-menu-link").removeAttribute("class"); + } + if (ITutorial.currStep === iTutorialSteps.GoToHacknetNodesPage) { + document.getElementById("hacknet-nodes-menu-link").removeAttribute("class"); + } + if (ITutorial.currStep === iTutorialSteps.HacknetNodesGoToWorldPage) { + document.getElementById("city-menu-link").removeAttribute("class"); + } + if (ITutorial.currStep === iTutorialSteps.WorldDescription) { + document.getElementById("tutorial-menu-link").removeAttribute("class"); + } - ITutorial.stepIsDone[ITutorial.currStep] = true; - if (ITutorial.currStep < iTutorialSteps.End) { - ITutorial.currStep += 1; - } - iTutorialEvaluateStep(); + ITutorial.stepIsDone[ITutorial.currStep] = true; + if (ITutorial.currStep < iTutorialSteps.End) { + ITutorial.currStep += 1; + } + iTutorialEvaluateStep(); } // Go to previous step and evaluate function iTutorialPrevStep() { - if (ITutorial.currStep > iTutorialSteps.Start) { - ITutorial.currStep -= 1; - } - iTutorialEvaluateStep(); + if (ITutorial.currStep > iTutorialSteps.Start) { + ITutorial.currStep -= 1; + } + iTutorialEvaluateStep(); } function iTutorialEnd() { - // Re-enable auto save - if (Settings.AutosaveInterval === 0) { - Engine.Counters.autoSaveCounter = Infinity; - } else { - Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5; - } + // Re-enable auto save + if (Settings.AutosaveInterval === 0) { + Engine.Counters.autoSaveCounter = Infinity; + } else { + Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5; + } - // Initialize references to main menu links - // We have to call initializeMainMenuLinks() again because the Interactive Tutorial - // re-creates Main menu links with clearEventListeners() - if (!initializeMainMenuLinks()) { - const errorMsg = "Failed to initialize Main Menu Links. Please try refreshing the page. " + - "If that doesn't work, report the issue to the developer"; - exceptionAlert(new Error(errorMsg)); - console.error(errorMsg); - return; - } - Engine.init(); + // Initialize references to main menu links + // We have to call initializeMainMenuLinks() again because the Interactive Tutorial + // re-creates Main menu links with clearEventListeners() + if (!initializeMainMenuLinks()) { + const errorMsg = + "Failed to initialize Main Menu Links. Please try refreshing the page. " + + "If that doesn't work, report the issue to the developer"; + exceptionAlert(new Error(errorMsg)); + console.error(errorMsg); + return; + } + Engine.init(); - ITutorial.currStep = iTutorialSteps.End; - ITutorial.isRunning = false; - document.getElementById("interactive-tutorial-container").style.display = "none"; + ITutorial.currStep = iTutorialSteps.End; + ITutorial.isRunning = false; + document.getElementById("interactive-tutorial-container").style.display = + "none"; - // Create a popup with final introductory stuff - const popupId = "interactive-tutorial-ending-popup"; - const txt = createElement("p", { - innerHTML: - "If you are new to the game, the following links may be useful for you!

    " + - "Getting Started Guide" + - "Documentation

    " + - "The Beginner's Guide to Hacking was added to your home computer! It contains some tips/pointers for starting out with the game. " + - "To read it, go to Terminal and enter

    cat " + LiteratureNames.HackersStartingHandbook, - }); - const gotitBtn = createElement("a", { - class:"a-link-button", float:"right", padding:"6px", innerText:"Got it!", - clickListener:()=>{ - removeElementById(popupId); - }, - }); - createPopup(popupId, [txt, gotitBtn]); + // Create a popup with final introductory stuff + const popupId = "interactive-tutorial-ending-popup"; + const txt = createElement("p", { + innerHTML: + "If you are new to the game, the following links may be useful for you!

    " + + "Getting Started Guide" + + "Documentation

    " + + "The Beginner's Guide to Hacking was added to your home computer! It contains some tips/pointers for starting out with the game. " + + "To read it, go to Terminal and enter

    cat " + + LiteratureNames.HackersStartingHandbook, + }); + const gotitBtn = createElement("a", { + class: "a-link-button", + float: "right", + padding: "6px", + innerText: "Got it!", + clickListener: () => { + removeElementById(popupId); + }, + }); + createPopup(popupId, [txt, gotitBtn]); - Player.getHomeComputer().messages.push(LiteratureNames.HackersStartingHandbook); + Player.getHomeComputer().messages.push( + LiteratureNames.HackersStartingHandbook, + ); } let textBox = null; -(function() { - function set() { - textBox = document.getElementById("interactive-tutorial-text"); - document.removeEventListener("DOMContentLoaded", set); - } - document.addEventListener("DOMContentLoaded", set); +(function () { + function set() { + textBox = document.getElementById("interactive-tutorial-text"); + document.removeEventListener("DOMContentLoaded", set); + } + document.addEventListener("DOMContentLoaded", set); })(); function iTutorialSetText(txt) { - textBox.innerHTML = txt; - textBox.parentElement.scrollTop = 0; // this resets scroll position + textBox.innerHTML = txt; + textBox.parentElement.scrollTop = 0; // this resets scroll position } -export {iTutorialSteps, iTutorialEnd, iTutorialStart, iTutorialNextStep, ITutorial}; +export { + iTutorialSteps, + iTutorialEnd, + iTutorialStart, + iTutorialNextStep, + ITutorial, +}; diff --git a/src/JSInterpreter.js b/src/JSInterpreter.js index 179ca0b74..7cf5e8054 100644 --- a/src/JSInterpreter.js +++ b/src/JSInterpreter.js @@ -22,7 +22,7 @@ import * as acorn from "acorn"; * @fileoverview Interpreting JavaScript in JavaScript. * @author fraser@google.com (Neil Fraser) */ -'use strict'; +("use strict"); /** * Create a new interpreter. @@ -33,10 +33,10 @@ import * as acorn from "acorn"; * @param {Number} Bitburner-specific number used for determining exception line numbers * @constructor */ -var Interpreter = function(code, opt_initFunc, lineOffset=0) { +var Interpreter = function (code, opt_initFunc, lineOffset = 0) { this.sourceCode = code; this.sourceCodeLineOffset = lineOffset; - if (typeof code === 'string') { + if (typeof code === "string") { code = acorn.parse(code, Interpreter.PARSE_OPTIONS); } this.ast = code; @@ -51,16 +51,18 @@ var Interpreter = function(code, opt_initFunc, lineOffset=0) { var stepMatch = /^step([A-Z]\w*)$/; var m; for (var methodName in this) { - if ((typeof this[methodName] === 'function') && - (m = methodName.match(stepMatch))) { + if ( + typeof this[methodName] === "function" && + (m = methodName.match(stepMatch)) + ) { this.stepFunctions_[m[1]] = this[methodName].bind(this); } } // Create and initialize the global scope. this.global = this.createScope(this.ast, null); // Run the polyfills. - this.ast = acorn.parse(this.polyfills_.join('\n'), Interpreter.PARSE_OPTIONS); - this.polyfills_ = undefined; // Allow polyfill strings to garbage collect. + this.ast = acorn.parse(this.polyfills_.join("\n"), Interpreter.PARSE_OPTIONS); + this.polyfills_ = undefined; // Allow polyfill strings to garbage collect. this.stripLocations_(this.ast, undefined, undefined); var state = new Interpreter.State(this.ast, this.global); state.done = false; @@ -77,7 +79,7 @@ var Interpreter = function(code, opt_initFunc, lineOffset=0) { this.nodeConstructor = state.node.constructor; // Preserve publicly properties from being pruned/renamed by JS compilers. // Add others as needed. - this['stateStack'] = this.stateStack; + this["stateStack"] = this.stateStack; }; /** @@ -155,52 +157,57 @@ Interpreter.toStringCycles_ = []; * Determine error/exception line number in Bitburner source code * @param {Object} AST Node that causes Error/Exception */ -Interpreter.prototype.getErrorLineNumber = function(node) { +Interpreter.prototype.getErrorLineNumber = function (node) { var code = this.sourceCode; - if (node == null || node.start == null) {return NaN;} + if (node == null || node.start == null) { + return NaN; + } try { code = code.substring(0, node.start); return (code.match(/\n/g) || []).length + 1 - this.sourceCodeLineOffset; - } catch(e) { + } catch (e) { return NaN; } -} +}; /** * Generate the appropriate line number error message for Bitburner * @param {Number} lineNumber */ -Interpreter.prototype.getErrorLineNumberMessage = function(lineNumber) { +Interpreter.prototype.getErrorLineNumberMessage = function (lineNumber) { if (isNaN(lineNumber)) { - return " (Unknown line number)"; + return " (Unknown line number)"; } else if (lineNumber <= 0) { - return " (Error occurred in an imported function)"; + return " (Error occurred in an imported function)"; } else { - return " (Line Number " + lineNumber + ". This line number is probably incorrect " + - "if your script is importing any functions. This is being worked on)"; + return ( + " (Line Number " + + lineNumber + + ". This line number is probably incorrect " + + "if your script is importing any functions. This is being worked on)" + ); } - -} +}; /** * Add more code to the interpreter. * @param {string|!Object} code Raw JavaScript text or AST. */ -Interpreter.prototype.appendCode = function(code) { +Interpreter.prototype.appendCode = function (code) { var state = this.stateStack[0]; - if (!state || state.node['type'] !== 'Program') { - throw Error('Expecting original AST to start with a Program node.'); + if (!state || state.node["type"] !== "Program") { + throw Error("Expecting original AST to start with a Program node."); } - if (typeof code === 'string') { + if (typeof code === "string") { code = acorn.parse(code, Interpreter.PARSE_OPTIONS); } - if (!code || code['type'] !== 'Program') { - throw Error('Expecting new AST to start with a Program node.'); + if (!code || code["type"] !== "Program") { + throw Error("Expecting new AST to start with a Program node."); } this.populateScope_(code, state.scope); // Append the new program to the old one. - for (var i = 0, node; (node = code['body'][i]); i++) { - state.node['body'].push(node); + for (var i = 0, node; (node = code["body"][i]); i++) { + state.node["body"].push(node); } state.done = false; }; @@ -209,14 +216,15 @@ Interpreter.prototype.appendCode = function(code) { * Execute one step of the interpreter. * @return {boolean} True if a step was executed, false if no more instructions. */ -Interpreter.prototype.step = function() { +Interpreter.prototype.step = function () { var stack = this.stateStack; var state = stack[stack.length - 1]; if (!state) { return false; } - var node = state.node, type = node['type']; - if (type === 'Program' && state.done) { + var node = state.node, + type = node["type"]; + if (type === "Program" && state.done) { return false; } else if (this.paused_) { return true; @@ -233,7 +241,7 @@ Interpreter.prototype.step = function() { if (nextState) { stack.push(nextState); } - if (!node['end']) { + if (!node["end"]) { // This is polyfill code. Keep executing until we arrive at user code. return this.step(); } @@ -245,7 +253,7 @@ Interpreter.prototype.step = function() { * @return {boolean} True if a execution is asynchronously blocked, * false if no more instructions. */ -Interpreter.prototype.run = function() { +Interpreter.prototype.run = function () { while (!this.paused_ && this.step()) {} return this.paused_; }; @@ -254,19 +262,24 @@ Interpreter.prototype.run = function() { * Initialize the global scope with buitin properties and functions. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initGlobalScope = function(scope) { +Interpreter.prototype.initGlobalScope = function (scope) { // Initialize uneditable global properties. - this.setProperty(scope, 'NaN', NaN, - Interpreter.READONLY_DESCRIPTOR); - this.setProperty(scope, 'Infinity', Infinity, - Interpreter.READONLY_DESCRIPTOR); - this.setProperty(scope, 'undefined', undefined, - Interpreter.READONLY_DESCRIPTOR); - this.setProperty(scope, 'window', scope, - Interpreter.READONLY_DESCRIPTOR); - this.setProperty(scope, 'this', scope, - Interpreter.READONLY_DESCRIPTOR); - this.setProperty(scope, 'self', scope); // Editable. + this.setProperty(scope, "NaN", NaN, Interpreter.READONLY_DESCRIPTOR); + this.setProperty( + scope, + "Infinity", + Infinity, + Interpreter.READONLY_DESCRIPTOR, + ); + this.setProperty( + scope, + "undefined", + undefined, + Interpreter.READONLY_DESCRIPTOR, + ); + this.setProperty(scope, "window", scope, Interpreter.READONLY_DESCRIPTOR); + this.setProperty(scope, "this", scope, Interpreter.READONLY_DESCRIPTOR); + this.setProperty(scope, "self", scope); // Editable. // Create the objects which will become Object.prototype and // Function.prototype, which are needed to bootstrap everything else. @@ -279,8 +292,12 @@ Interpreter.prototype.initGlobalScope = function(scope) { // Note that in a browser this would be 'Window', whereas in Node.js it would // be 'Object'. This interpreter is closer to Node in that it has no DOM. scope.proto = this.OBJECT_PROTO; - this.setProperty(scope, 'constructor', this.OBJECT, - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + scope, + "constructor", + this.OBJECT, + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); this.initArray(scope); this.initString(scope); this.initBoolean(scope); @@ -293,30 +310,42 @@ Interpreter.prototype.initGlobalScope = function(scope) { // Initialize global functions. var thisInterpreter = this; - var func = this.createNativeFunction( - function(x) {throw EvalError("Can't happen");}, false); + var func = this.createNativeFunction(function (x) { + throw EvalError("Can't happen"); + }, false); func.eval = true; - this.setProperty(scope, 'eval', func); + this.setProperty(scope, "eval", func); - this.setProperty(scope, 'parseInt', - this.createNativeFunction(parseInt, false)); - this.setProperty(scope, 'parseFloat', - this.createNativeFunction(parseFloat, false)); + this.setProperty( + scope, + "parseInt", + this.createNativeFunction(parseInt, false), + ); + this.setProperty( + scope, + "parseFloat", + this.createNativeFunction(parseFloat, false), + ); - this.setProperty(scope, 'isNaN', - this.createNativeFunction(isNaN, false)); + this.setProperty(scope, "isNaN", this.createNativeFunction(isNaN, false)); - this.setProperty(scope, 'isFinite', - this.createNativeFunction(isFinite, false)); + this.setProperty( + scope, + "isFinite", + this.createNativeFunction(isFinite, false), + ); var strFunctions = [ - [escape, 'escape'], [unescape, 'unescape'], - [decodeURI, 'decodeURI'], [decodeURIComponent, 'decodeURIComponent'], - [encodeURI, 'encodeURI'], [encodeURIComponent, 'encodeURIComponent'], + [escape, "escape"], + [unescape, "unescape"], + [decodeURI, "decodeURI"], + [decodeURIComponent, "decodeURIComponent"], + [encodeURI, "encodeURI"], + [encodeURIComponent, "encodeURIComponent"], ]; for (var i = 0; i < strFunctions.length; i++) { - var wrapper = (function(nativeFunc) { - return function(str) { + var wrapper = (function (nativeFunc) { + return function (str) { try { return nativeFunc(str); } catch (e) { @@ -325,21 +354,34 @@ Interpreter.prototype.initGlobalScope = function(scope) { } }; })(strFunctions[i][0]); - this.setProperty(scope, strFunctions[i][1], - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + scope, + strFunctions[i][1], + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); } // Preserve publicly properties from being pruned/renamed by JS compilers. // Add others as needed. - this['OBJECT'] = this.OBJECT; this['OBJECT_PROTO'] = this.OBJECT_PROTO; - this['FUNCTION'] = this.FUNCTION; this['FUNCTION_PROTO'] = this.FUNCTION_PROTO; - this['ARRAY'] = this.ARRAY; this['ARRAY_PROTO'] = this.ARRAY_PROTO; - this['REGEXP'] = this.REGEXP; this['REGEXP_PROTO'] = this.REGEXP_PROTO; - this['DATE'] = this.DATE; this['DATE_PROTO'] = this.DATE_PROTO; + this["OBJECT"] = this.OBJECT; + this["OBJECT_PROTO"] = this.OBJECT_PROTO; + this["FUNCTION"] = this.FUNCTION; + this["FUNCTION_PROTO"] = this.FUNCTION_PROTO; + this["ARRAY"] = this.ARRAY; + this["ARRAY_PROTO"] = this.ARRAY_PROTO; + this["REGEXP"] = this.REGEXP; + this["REGEXP_PROTO"] = this.REGEXP_PROTO; + this["DATE"] = this.DATE; + this["DATE_PROTO"] = this.DATE_PROTO; // The following properties are obsolete. Do not use. - this['UNDEFINED'] = undefined; this['NULL'] = null; this['NAN'] = NaN; - this['TRUE'] = true; this['FALSE'] = false; this['STRING_EMPTY'] = ''; - this['NUMBER_ZERO'] = 0; this['NUMBER_ONE'] = 1; + this["UNDEFINED"] = undefined; + this["NULL"] = null; + this["NAN"] = NaN; + this["TRUE"] = true; + this["FALSE"] = false; + this["STRING_EMPTY"] = ""; + this["NUMBER_ZERO"] = 0; + this["NUMBER_ONE"] = 1; // Run any user-provided initialization. if (this.initFunc_) { @@ -351,36 +393,39 @@ Interpreter.prototype.initGlobalScope = function(scope) { * Initialize the Function class. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initFunction = function(scope) { +Interpreter.prototype.initFunction = function (scope) { var thisInterpreter = this; var wrapper; var identifierRegexp = /^[A-Za-z_$][\w$]*$/; // Function constructor. - wrapper = function(var_args) { + wrapper = function (var_args) { if (thisInterpreter.calledWithNew()) { // Called as new Function(). var newFunc = this; } else { // Called as Function(). - var newFunc = - thisInterpreter.createObjectProto(thisInterpreter.FUNCTION_PROTO); + var newFunc = thisInterpreter.createObjectProto( + thisInterpreter.FUNCTION_PROTO, + ); } if (arguments.length) { var code = String(arguments[arguments.length - 1]); } else { - var code = ''; + var code = ""; } - var argsStr = Array.prototype.slice.call(arguments, 0, -1).join(',').trim(); + var argsStr = Array.prototype.slice.call(arguments, 0, -1).join(",").trim(); if (argsStr) { var args = argsStr.split(/\s*,\s*/); for (var i = 0; i < args.length; i++) { var name = args[i]; if (!identifierRegexp.test(name)) { - thisInterpreter.throwException(thisInterpreter.SYNTAX_ERROR, - 'Invalid function argument: ' + name); + thisInterpreter.throwException( + thisInterpreter.SYNTAX_ERROR, + "Invalid function argument: " + name, + ); } } - argsStr = args.join(', '); + argsStr = args.join(", "); } // Interestingly, the scope for constructed functions is the global scope, // even if they were constructed in some other scope. @@ -388,41 +433,59 @@ Interpreter.prototype.initFunction = function(scope) { // Acorn needs to parse code in the context of a function or else 'return' // statements will be syntax errors. try { - var ast = acorn.parse('(function(' + argsStr + ') {' + code + '})', - Interpreter.PARSE_OPTIONS); + var ast = acorn.parse( + "(function(" + argsStr + ") {" + code + "})", + Interpreter.PARSE_OPTIONS, + ); } catch (e) { // Acorn threw a SyntaxError. Rethrow as a trappable error. - thisInterpreter.throwException(thisInterpreter.SYNTAX_ERROR, - 'Invalid code: ' + e.message); + thisInterpreter.throwException( + thisInterpreter.SYNTAX_ERROR, + "Invalid code: " + e.message, + ); } - if (ast['body'].length !== 1) { + if (ast["body"].length !== 1) { // Function('a', 'return a + 6;}; {alert(1);'); - thisInterpreter.throwException(thisInterpreter.SYNTAX_ERROR, - 'Invalid code in function body.'); + thisInterpreter.throwException( + thisInterpreter.SYNTAX_ERROR, + "Invalid code in function body.", + ); } - newFunc.node = ast['body'][0]['expression']; - thisInterpreter.setProperty(newFunc, 'length', newFunc.node['length'], - Interpreter.READONLY_DESCRIPTOR); + newFunc.node = ast["body"][0]["expression"]; + thisInterpreter.setProperty( + newFunc, + "length", + newFunc.node["length"], + Interpreter.READONLY_DESCRIPTOR, + ); return newFunc; }; wrapper.id = this.functionCounter_++; this.FUNCTION = this.createObjectProto(this.FUNCTION_PROTO); - this.setProperty(scope, 'Function', this.FUNCTION); + this.setProperty(scope, "Function", this.FUNCTION); // Manually setup type and prototype because createObj doesn't recognize // this object as a function (this.FUNCTION did not exist). - this.setProperty(this.FUNCTION, 'prototype', this.FUNCTION_PROTO); + this.setProperty(this.FUNCTION, "prototype", this.FUNCTION_PROTO); this.FUNCTION.nativeFunc = wrapper; // Configure Function.prototype. - this.setProperty(this.FUNCTION_PROTO, 'constructor', this.FUNCTION, - Interpreter.NONENUMERABLE_DESCRIPTOR); - this.FUNCTION_PROTO.nativeFunc = function() {}; + this.setProperty( + this.FUNCTION_PROTO, + "constructor", + this.FUNCTION, + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); + this.FUNCTION_PROTO.nativeFunc = function () {}; this.FUNCTION_PROTO.nativeFunc.id = this.functionCounter_++; - this.setProperty(this.FUNCTION_PROTO, 'length', 0, - Interpreter.READONLY_DESCRIPTOR); + this.setProperty( + this.FUNCTION_PROTO, + "length", + 0, + Interpreter.READONLY_DESCRIPTOR, + ); - var boxThis = function(value) { + var boxThis = function (value) { // In non-strict mode 'this' must be an object. if ((!value || !value.isObject) && !thisInterpreter.getScope().strict) { if (value === undefined || value === null) { @@ -431,7 +494,8 @@ Interpreter.prototype.initFunction = function(scope) { } else { // Primitives must be boxed in non-strict mode. var box = thisInterpreter.createObjectProto( - thisInterpreter.getPrototype(value)); + thisInterpreter.getPrototype(value), + ); box.data = value; value = box; } @@ -439,9 +503,9 @@ Interpreter.prototype.initFunction = function(scope) { return value; }; - wrapper = function(thisArg, args) { + wrapper = function (thisArg, args) { var state = - thisInterpreter.stateStack[thisInterpreter.stateStack.length - 1]; + thisInterpreter.stateStack[thisInterpreter.stateStack.length - 1]; // Rewrite the current 'CallExpression' to apply a different function. state.func_ = this; // Assign the 'this' object. @@ -452,17 +516,19 @@ Interpreter.prototype.initFunction = function(scope) { if (args.isObject) { state.arguments_ = thisInterpreter.arrayPseudoToNative(args); } else { - thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, - 'CreateListFromArrayLike called on non-object'); + thisInterpreter.throwException( + thisInterpreter.TYPE_ERROR, + "CreateListFromArrayLike called on non-object", + ); } } state.doneExec_ = false; }; - this.setNativeFunctionPrototype(this.FUNCTION, 'apply', wrapper); + this.setNativeFunctionPrototype(this.FUNCTION, "apply", wrapper); - wrapper = function(thisArg /*, var_args */) { + wrapper = function (thisArg /*, var_args */) { var state = - thisInterpreter.stateStack[thisInterpreter.stateStack.length - 1]; + thisInterpreter.stateStack[thisInterpreter.stateStack.length - 1]; // Rewrite the current 'CallExpression' to call a different function. state.func_ = this; // Assign the 'this' object. @@ -474,62 +540,69 @@ Interpreter.prototype.initFunction = function(scope) { } state.doneExec_ = false; }; - this.setNativeFunctionPrototype(this.FUNCTION, 'call', wrapper); + this.setNativeFunctionPrototype(this.FUNCTION, "call", wrapper); this.polyfills_.push( -// Polyfill copied from: -// developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind -"Object.defineProperty(Function.prototype, 'bind',", + // Polyfill copied from: + // developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind + "Object.defineProperty(Function.prototype, 'bind',", "{configurable: true, writable: true, value:", - "function(oThis) {", + "function(oThis) {", "if (typeof this !== 'function') {", - "throw TypeError('What is trying to be bound is not callable');", + "throw TypeError('What is trying to be bound is not callable');", "}", "var aArgs = Array.prototype.slice.call(arguments, 1),", - "fToBind = this,", - "fNOP = function() {},", - "fBound = function() {", - "return fToBind.apply(this instanceof fNOP", - "? this", - ": oThis,", - "aArgs.concat(Array.prototype.slice.call(arguments)));", - "};", + "fToBind = this,", + "fNOP = function() {},", + "fBound = function() {", + "return fToBind.apply(this instanceof fNOP", + "? this", + ": oThis,", + "aArgs.concat(Array.prototype.slice.call(arguments)));", + "};", "if (this.prototype) {", - "fNOP.prototype = this.prototype;", + "fNOP.prototype = this.prototype;", "}", "fBound.prototype = new fNOP();", "return fBound;", - "}", -"});", -""); + "}", + "});", + "", + ); // Function has no parent to inherit from, so it needs its own mandatory // toString and valueOf functions. - wrapper = function() { + wrapper = function () { return this.toString(); }; - this.setNativeFunctionPrototype(this.FUNCTION, 'toString', wrapper); - this.setProperty(this.FUNCTION, 'toString', - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); - wrapper = function() { + this.setNativeFunctionPrototype(this.FUNCTION, "toString", wrapper); + this.setProperty( + this.FUNCTION, + "toString", + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); + wrapper = function () { return this.valueOf(); }; - this.setNativeFunctionPrototype(this.FUNCTION, 'valueOf', wrapper); - this.setProperty(this.FUNCTION, 'valueOf', - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setNativeFunctionPrototype(this.FUNCTION, "valueOf", wrapper); + this.setProperty( + this.FUNCTION, + "valueOf", + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); }; /** * Initialize the Object class. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initObject = function(scope) { +Interpreter.prototype.initObject = function (scope) { var thisInterpreter = this; var wrapper; // Object constructor. - wrapper = function(value) { + wrapper = function (value) { if (value === undefined || value === null) { // Create a new object. if (thisInterpreter.calledWithNew()) { @@ -543,7 +616,8 @@ Interpreter.prototype.initObject = function(scope) { if (!value.isObject) { // Wrap the value as an object. var box = thisInterpreter.createObjectProto( - thisInterpreter.getPrototype(value)); + thisInterpreter.getPrototype(value), + ); box.data = value; return box; } @@ -552,101 +626,133 @@ Interpreter.prototype.initObject = function(scope) { }; this.OBJECT = this.createNativeFunction(wrapper, true); // Throw away the created prototype and use the root prototype. - this.setProperty(this.OBJECT, 'prototype', this.OBJECT_PROTO); - this.setProperty(this.OBJECT_PROTO, 'constructor', this.OBJECT, - Interpreter.NONENUMERABLE_DESCRIPTOR); - this.setProperty(scope, 'Object', this.OBJECT); + this.setProperty(this.OBJECT, "prototype", this.OBJECT_PROTO); + this.setProperty( + this.OBJECT_PROTO, + "constructor", + this.OBJECT, + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); + this.setProperty(scope, "Object", this.OBJECT); /** * Checks if the provided value is null or undefined. * If so, then throw an error in the call stack. * @param {Interpreter.Value} value Value to check. */ - var throwIfNullUndefined = function(value) { + var throwIfNullUndefined = function (value) { if (value === undefined || value === null) { - thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, - "Cannot convert '" + value + "' to object"); + thisInterpreter.throwException( + thisInterpreter.TYPE_ERROR, + "Cannot convert '" + value + "' to object", + ); } }; // Static methods on Object. - wrapper = function(obj) { + wrapper = function (obj) { throwIfNullUndefined(obj); var props = obj.isObject ? obj.properties : obj; return thisInterpreter.arrayNativeToPseudo( - Object.getOwnPropertyNames(props)); + Object.getOwnPropertyNames(props), + ); }; - this.setProperty(this.OBJECT, 'getOwnPropertyNames', - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.OBJECT, + "getOwnPropertyNames", + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); - wrapper = function(obj) { + wrapper = function (obj) { throwIfNullUndefined(obj); if (obj.isObject) { obj = obj.properties; } return thisInterpreter.arrayNativeToPseudo(Object.keys(obj)); }; - this.setProperty(this.OBJECT, 'keys', - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.OBJECT, + "keys", + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); - wrapper = function(proto) { + wrapper = function (proto) { // Support for the second argument is the responsibility of a polyfill. if (proto === null) { return thisInterpreter.createObjectProto(null); } if (proto === undefined || !proto.isObject) { - thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, - 'Object prototype may only be an Object or null'); + thisInterpreter.throwException( + thisInterpreter.TYPE_ERROR, + "Object prototype may only be an Object or null", + ); } return thisInterpreter.createObjectProto(proto); }; - this.setProperty(this.OBJECT, 'create', - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.OBJECT, + "create", + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); // Add a polyfill to handle create's second argument. this.polyfills_.push( -"(function() {", - "var create_ = Object.create;", - "Object.create = function(proto, props) {", + "(function() {", + "var create_ = Object.create;", + "Object.create = function(proto, props) {", "var obj = create_(proto);", "props && Object.defineProperties(obj, props);", "return obj;", - "};", -"})();", -""); + "};", + "})();", + "", + ); - wrapper = function(obj, prop, descriptor) { + wrapper = function (obj, prop, descriptor) { prop = String(prop); if (!obj || !obj.isObject) { - thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, - 'Object.defineProperty called on non-object'); + thisInterpreter.throwException( + thisInterpreter.TYPE_ERROR, + "Object.defineProperty called on non-object", + ); } if (!descriptor || !descriptor.isObject) { - thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, - 'Property description must be an object'); + thisInterpreter.throwException( + thisInterpreter.TYPE_ERROR, + "Property description must be an object", + ); } if (!obj.properties[prop] && obj.preventExtensions) { - thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, - "Can't define property '" + prop + "', object is not extensible"); + thisInterpreter.throwException( + thisInterpreter.TYPE_ERROR, + "Can't define property '" + prop + "', object is not extensible", + ); } // The polyfill guarantees no inheritance and no getter functions. // Therefore the descriptor properties map is the native object needed. - thisInterpreter.setProperty(obj, prop, Interpreter.VALUE_IN_DESCRIPTOR, - descriptor.properties); + thisInterpreter.setProperty( + obj, + prop, + Interpreter.VALUE_IN_DESCRIPTOR, + descriptor.properties, + ); return obj; }; - this.setProperty(this.OBJECT, 'defineProperty', - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.OBJECT, + "defineProperty", + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); this.polyfills_.push( -// Flatten the descriptor to remove any inheritance or getter functions. -"(function() {", - "var defineProperty_ = Object.defineProperty;", - "Object.defineProperty = function(obj, prop, d1) {", + // Flatten the descriptor to remove any inheritance or getter functions. + "(function() {", + "var defineProperty_ = Object.defineProperty;", + "Object.defineProperty = function(obj, prop, d1) {", "var d2 = {};", "if ('configurable' in d1) d2.configurable = d1.configurable;", "if ('enumerable' in d1) d2.enumerable = d1.enumerable;", @@ -655,25 +761,28 @@ Interpreter.prototype.initObject = function(scope) { "if ('get' in d1) d2.get = d1.get;", "if ('set' in d1) d2.set = d1.set;", "return defineProperty_(obj, prop, d2);", - "};", -"})();", + "};", + "})();", -"Object.defineProperty(Object, 'defineProperties',", + "Object.defineProperty(Object, 'defineProperties',", "{configurable: true, writable: true, value:", - "function(obj, props) {", + "function(obj, props) {", "var keys = Object.keys(props);", "for (var i = 0; i < keys.length; i++) {", - "Object.defineProperty(obj, keys[i], props[keys[i]]);", + "Object.defineProperty(obj, keys[i], props[keys[i]]);", "}", "return obj;", - "}", -"});", -""); + "}", + "});", + "", + ); - wrapper = function(obj, prop) { + wrapper = function (obj, prop) { if (!obj || !obj.isObject) { - thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, - 'Object.getOwnPropertyDescriptor called on non-object'); + thisInterpreter.throwException( + thisInterpreter.TYPE_ERROR, + "Object.getOwnPropertyDescriptor called on non-object", + ); } prop = String(prop); if (!(prop in obj.properties)) { @@ -691,70 +800,91 @@ Interpreter.prototype.initObject = function(scope) { } // Preserve value, but remove it for the nativeToPseudo call. var value = descriptor.value; - var hasValue = 'value' in descriptor; + var hasValue = "value" in descriptor; delete descriptor.value; var pseudoDescriptor = thisInterpreter.nativeToPseudo(descriptor); if (hasValue) { - thisInterpreter.setProperty(pseudoDescriptor, 'value', value); + thisInterpreter.setProperty(pseudoDescriptor, "value", value); } return pseudoDescriptor; }; - this.setProperty(this.OBJECT, 'getOwnPropertyDescriptor', - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.OBJECT, + "getOwnPropertyDescriptor", + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); - wrapper = function(obj) { + wrapper = function (obj) { throwIfNullUndefined(obj); return thisInterpreter.getPrototype(obj); }; - this.setProperty(this.OBJECT, 'getPrototypeOf', - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.OBJECT, + "getPrototypeOf", + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); - wrapper = function(obj) { + wrapper = function (obj) { return Boolean(obj) && !obj.preventExtensions; }; - this.setProperty(this.OBJECT, 'isExtensible', - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.OBJECT, + "isExtensible", + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); - wrapper = function(obj) { + wrapper = function (obj) { if (obj && obj.isObject) { obj.preventExtensions = true; } return obj; }; - this.setProperty(this.OBJECT, 'preventExtensions', - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.OBJECT, + "preventExtensions", + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); // Instance methods on Object. - this.setNativeFunctionPrototype(this.OBJECT, 'toString', - Interpreter.Object.prototype.toString); - this.setNativeFunctionPrototype(this.OBJECT, 'toLocaleString', - Interpreter.Object.prototype.toString); - this.setNativeFunctionPrototype(this.OBJECT, 'valueOf', - Interpreter.Object.prototype.valueOf); + this.setNativeFunctionPrototype( + this.OBJECT, + "toString", + Interpreter.Object.prototype.toString, + ); + this.setNativeFunctionPrototype( + this.OBJECT, + "toLocaleString", + Interpreter.Object.prototype.toString, + ); + this.setNativeFunctionPrototype( + this.OBJECT, + "valueOf", + Interpreter.Object.prototype.valueOf, + ); - wrapper = function(prop) { + wrapper = function (prop) { throwIfNullUndefined(this); if (!this.isObject) { return this.hasOwnProperty(prop); } return String(prop) in this.properties; }; - this.setNativeFunctionPrototype(this.OBJECT, 'hasOwnProperty', wrapper); + this.setNativeFunctionPrototype(this.OBJECT, "hasOwnProperty", wrapper); - wrapper = function(prop) { + wrapper = function (prop) { throwIfNullUndefined(this); if (!this.isObject) { return this.propertyIsEnumerable(prop); } return Object.prototype.propertyIsEnumerable.call(this.properties, prop); }; - this.setNativeFunctionPrototype(this.OBJECT, 'propertyIsEnumerable', wrapper); + this.setNativeFunctionPrototype(this.OBJECT, "propertyIsEnumerable", wrapper); - wrapper = function(obj) { + wrapper = function (obj) { while (true) { // Note, circular loops shouldn't be possible. obj = thisInterpreter.getPrototype(obj); @@ -767,31 +897,34 @@ Interpreter.prototype.initObject = function(scope) { } } }; - this.setNativeFunctionPrototype(this.OBJECT, 'isPrototypeOf', wrapper); + this.setNativeFunctionPrototype(this.OBJECT, "isPrototypeOf", wrapper); }; /** * Initialize the Array class. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initArray = function(scope) { +Interpreter.prototype.initArray = function (scope) { var thisInterpreter = this; var wrapper; // Array constructor. - wrapper = function(var_args) { + wrapper = function (var_args) { if (thisInterpreter.calledWithNew()) { // Called as new Array(). var newArray = this; } else { // Called as Array(). - var newArray = - thisInterpreter.createObjectProto(thisInterpreter.ARRAY_PROTO); + var newArray = thisInterpreter.createObjectProto( + thisInterpreter.ARRAY_PROTO, + ); } var first = arguments[0]; - if (arguments.length === 1 && typeof first === 'number') { + if (arguments.length === 1 && typeof first === "number") { if (isNaN(Interpreter.legalArrayLength(first))) { - thisInterpreter.throwException(thisInterpreter.RANGE_ERROR, - 'Invalid array length'); + thisInterpreter.throwException( + thisInterpreter.RANGE_ERROR, + "Invalid array length", + ); } newArray.properties.length = first; } else { @@ -803,66 +936,69 @@ Interpreter.prototype.initArray = function(scope) { return newArray; }; this.ARRAY = this.createNativeFunction(wrapper, true); - this.ARRAY_PROTO = this.ARRAY.properties['prototype']; - this.setProperty(scope, 'Array', this.ARRAY); + this.ARRAY_PROTO = this.ARRAY.properties["prototype"]; + this.setProperty(scope, "Array", this.ARRAY); // Static methods on Array. - wrapper = function(obj) { - return obj && obj.class === 'Array'; + wrapper = function (obj) { + return obj && obj.class === "Array"; }; - this.setProperty(this.ARRAY, 'isArray', - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.ARRAY, + "isArray", + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); // Instance methods on Array. - wrapper = function() { + wrapper = function () { return Array.prototype.pop.call(this.properties); }; - this.setNativeFunctionPrototype(this.ARRAY, 'pop', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "pop", wrapper); - wrapper = function(var_args) { + wrapper = function (var_args) { return Array.prototype.push.apply(this.properties, arguments); }; - this.setNativeFunctionPrototype(this.ARRAY, 'push', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "push", wrapper); - wrapper = function() { + wrapper = function () { return Array.prototype.shift.call(this.properties); }; - this.setNativeFunctionPrototype(this.ARRAY, 'shift', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "shift", wrapper); - wrapper = function(var_args) { + wrapper = function (var_args) { return Array.prototype.unshift.apply(this.properties, arguments); }; - this.setNativeFunctionPrototype(this.ARRAY, 'unshift', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "unshift", wrapper); - wrapper = function() { + wrapper = function () { Array.prototype.reverse.call(this.properties); return this; }; - this.setNativeFunctionPrototype(this.ARRAY, 'reverse', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "reverse", wrapper); - wrapper = function(index, howmany /*, var_args*/) { + wrapper = function (index, howmany /*, var_args*/) { var list = Array.prototype.splice.apply(this.properties, arguments); return thisInterpreter.arrayNativeToPseudo(list); }; - this.setNativeFunctionPrototype(this.ARRAY, 'splice', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "splice", wrapper); - wrapper = function(opt_begin, opt_end) { + wrapper = function (opt_begin, opt_end) { var list = Array.prototype.slice.call(this.properties, opt_begin, opt_end); return thisInterpreter.arrayNativeToPseudo(list); }; - this.setNativeFunctionPrototype(this.ARRAY, 'slice', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "slice", wrapper); - wrapper = function(opt_separator) { + wrapper = function (opt_separator) { return Array.prototype.join.call(this.properties, opt_separator); }; - this.setNativeFunctionPrototype(this.ARRAY, 'join', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "join", wrapper); - wrapper = function(var_args) { + wrapper = function (var_args) { var list = []; var length = 0; // Start by copying the current array. - var iLength = thisInterpreter.getProperty(this, 'length'); + var iLength = thisInterpreter.getProperty(this, "length"); for (var i = 0; i < iLength; i++) { if (thisInterpreter.hasProperty(this, i)) { var element = thisInterpreter.getProperty(this, i); @@ -874,7 +1010,7 @@ Interpreter.prototype.initArray = function(scope) { for (var i = 0; i < arguments.length; i++) { var value = arguments[i]; if (thisInterpreter.isa(value, thisInterpreter.ARRAY)) { - var jLength = thisInterpreter.getProperty(value, 'length'); + var jLength = thisInterpreter.getProperty(value, "length"); for (var j = 0; j < jLength; j++) { if (thisInterpreter.hasProperty(value, j)) { list[length] = thisInterpreter.getProperty(value, j); @@ -887,30 +1023,30 @@ Interpreter.prototype.initArray = function(scope) { } return thisInterpreter.arrayNativeToPseudo(list); }; - this.setNativeFunctionPrototype(this.ARRAY, 'concat', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "concat", wrapper); - wrapper = function(searchElement, opt_fromIndex) { + wrapper = function (searchElement, opt_fromIndex) { return Array.prototype.indexOf.apply(this.properties, arguments); }; - this.setNativeFunctionPrototype(this.ARRAY, 'indexOf', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "indexOf", wrapper); - wrapper = function(searchElement, opt_fromIndex) { + wrapper = function (searchElement, opt_fromIndex) { return Array.prototype.lastIndexOf.apply(this.properties, arguments); }; - this.setNativeFunctionPrototype(this.ARRAY, 'lastIndexOf', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "lastIndexOf", wrapper); - wrapper = function() { + wrapper = function () { Array.prototype.sort.call(this.properties); return this; }; - this.setNativeFunctionPrototype(this.ARRAY, 'sort', wrapper); + this.setNativeFunctionPrototype(this.ARRAY, "sort", wrapper); this.polyfills_.push( -// Polyfill copied from: -// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/every -"Object.defineProperty(Array.prototype, 'every',", + // Polyfill copied from: + // developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/every + "Object.defineProperty(Array.prototype, 'every',", "{configurable: true, writable: true, value:", - "function(callbackfn, thisArg) {", + "function(callbackfn, thisArg) {", "if (!this || typeof callbackfn !== 'function') throw TypeError();", "var T, k;", "var O = Object(this);", @@ -918,98 +1054,98 @@ Interpreter.prototype.initArray = function(scope) { "if (arguments.length > 1) T = thisArg;", "k = 0;", "while (k < len) {", - "if (k in O && !callbackfn.call(T, O[k], k, O)) return false;", - "k++;", + "if (k in O && !callbackfn.call(T, O[k], k, O)) return false;", + "k++;", "}", "return true;", - "}", -"});", + "}", + "});", -// Polyfill copied from: -// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/filter -"Object.defineProperty(Array.prototype, 'filter',", + // Polyfill copied from: + // developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/filter + "Object.defineProperty(Array.prototype, 'filter',", "{configurable: true, writable: true, value:", - "function(fun/*, thisArg*/) {", + "function(fun/*, thisArg*/) {", "if (this === void 0 || this === null || typeof fun !== 'function') throw TypeError();", "var t = Object(this);", "var len = t.length >>> 0;", "var res = [];", "var thisArg = arguments.length >= 2 ? arguments[1] : void 0;", "for (var i = 0; i < len; i++) {", - "if (i in t) {", - "var val = t[i];", - "if (fun.call(thisArg, val, i, t)) res.push(val);", - "}", + "if (i in t) {", + "var val = t[i];", + "if (fun.call(thisArg, val, i, t)) res.push(val);", + "}", "}", "return res;", - "}", -"});", + "}", + "});", -// Polyfill copied from: -// https://tc39.github.io/ecma262/#sec-array.prototype.find -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find -"if (!Array.prototype.find) {", - "Object.defineProperty(Array.prototype, 'find', {", + // Polyfill copied from: + // https://tc39.github.io/ecma262/#sec-array.prototype.find + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find + "if (!Array.prototype.find) {", + "Object.defineProperty(Array.prototype, 'find', {", "value: function(predicate) {", - "if (this == null) {", - "throw new TypeError('\"this\" is null or not defined');", + "if (this == null) {", + "throw new TypeError('\"this\" is null or not defined');", "}", - "var o = Object(this);", - "var len = o.length >>> 0;", - "if (typeof predicate !== 'function') {", - "throw new TypeError('predicate must be a function');", + "var o = Object(this);", + "var len = o.length >>> 0;", + "if (typeof predicate !== 'function') {", + "throw new TypeError('predicate must be a function');", "}", - "var thisArg = arguments[1];", - "var k = 0;", - "while (k < len) {", - "var kValue = o[k];", - "if (predicate.call(thisArg, kValue, k, o)) {", - "return kValue;", - "}", - "k++;", + "var thisArg = arguments[1];", + "var k = 0;", + "while (k < len) {", + "var kValue = o[k];", + "if (predicate.call(thisArg, kValue, k, o)) {", + "return kValue;", "}", - "return undefined;", - "},", - "configurable: true,", - "writable: true", -"});", -"}", - -// Poly fill copied from: -// https://tc39.github.io/ecma262/#sec-array.prototype.findIndex -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex -"if (!Array.prototype.findIndex) {", - "Object.defineProperty(Array.prototype, 'findIndex', {", - "value: function(predicate) {", - "if (this == null) {", - "throw new TypeError('\"this\" is null or not defined');", - "}", - "var o = Object(this);", - "var len = o.length >>> 0;", - "if (typeof predicate !== 'function') {", - "throw new TypeError('predicate must be a function');", - "}", - "var thisArg = arguments[1];", - "var k = 0;", - "while (k < len) {", - "var kValue = o[k];", - "if (predicate.call(thisArg, kValue, k, o)) {", - "return k;", - "}", - "k++;", - "}", - "return -1;", + "k++;", + "}", + "return undefined;", "},", "configurable: true,", "writable: true", - "});", -"}", + "});", + "}", -// Polyfill copied from: -// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach -"Object.defineProperty(Array.prototype, 'forEach',", + // Poly fill copied from: + // https://tc39.github.io/ecma262/#sec-array.prototype.findIndex + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex + "if (!Array.prototype.findIndex) {", + "Object.defineProperty(Array.prototype, 'findIndex', {", + "value: function(predicate) {", + "if (this == null) {", + "throw new TypeError('\"this\" is null or not defined');", + "}", + "var o = Object(this);", + "var len = o.length >>> 0;", + "if (typeof predicate !== 'function') {", + "throw new TypeError('predicate must be a function');", + "}", + "var thisArg = arguments[1];", + "var k = 0;", + "while (k < len) {", + "var kValue = o[k];", + "if (predicate.call(thisArg, kValue, k, o)) {", + "return k;", + "}", + "k++;", + "}", + "return -1;", + "},", + "configurable: true,", + "writable: true", + "});", + "}", + + // Polyfill copied from: + // developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach + "Object.defineProperty(Array.prototype, 'forEach',", "{configurable: true, writable: true, value:", - "function(callback, thisArg) {", + "function(callback, thisArg) {", "if (!this || typeof callback !== 'function') throw TypeError();", "var T, k;", "var O = Object(this);", @@ -1017,59 +1153,59 @@ Interpreter.prototype.initArray = function(scope) { "if (arguments.length > 1) T = thisArg;", "k = 0;", "while (k < len) {", - "if (k in O) callback.call(T, O[k], k, O);", - "k++;", + "if (k in O) callback.call(T, O[k], k, O);", + "k++;", "}", - "}", -"});", + "}", + "});", -// Polyfill copied from: -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Polyfill -"Object.defineProperty(Array.prototype, 'includes', {", + // Polyfill copied from: + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Polyfill + "Object.defineProperty(Array.prototype, 'includes', {", "value: function(searchElement, fromIndex) {", - "if (this == null) {", - "throw new TypeError('\"this\" is null or not defined');", + "if (this == null) {", + "throw new TypeError('\"this\" is null or not defined');", "}", - "// 1. Let O be ? ToObject(this value).", - "var o = Object(this);", - "// 2. Let len be ? ToLength(? Get(O, \"length\")).", - "var len = o.length >>> 0;", - "// 3. If len is 0, return false.", - "if (len === 0) {", - "return false;", + "// 1. Let O be ? ToObject(this value).", + "var o = Object(this);", + '// 2. Let len be ? ToLength(? Get(O, "length")).', + "var len = o.length >>> 0;", + "// 3. If len is 0, return false.", + "if (len === 0) {", + "return false;", "}", - "// 4. Let n be ? ToInteger(fromIndex).", - "// (If fromIndex is undefined, this step produces the value 0.)", - "var n = fromIndex | 0;", - "// 5. If n ≥ 0, then", - "// a. Let k be n.", - "// 6. Else n < 0,", - "// a. Let k be len + n.", - "// b. If k < 0, let k be 0.", - "var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);", - "function sameValueZero(x, y) {", - "return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));", + "// 4. Let n be ? ToInteger(fromIndex).", + "// (If fromIndex is undefined, this step produces the value 0.)", + "var n = fromIndex | 0;", + "// 5. If n ≥ 0, then", + "// a. Let k be n.", + "// 6. Else n < 0,", + "// a. Let k be len + n.", + "// b. If k < 0, let k be 0.", + "var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);", + "function sameValueZero(x, y) {", + "return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));", "}", - "// 7. Repeat, while k < len", - "while (k < len) {", - "// a. Let elementK be the result of ? Get(O, ! ToString(k)).", - "// b. If SameValueZero(searchElement, elementK) is true, return true.", - "if (sameValueZero(o[k], searchElement)) {", - "return true;", - "}", - "// c. Increase k by 1. ", - "k++;", + "// 7. Repeat, while k < len", + "while (k < len) {", + "// a. Let elementK be the result of ? Get(O, ! ToString(k)).", + "// b. If SameValueZero(searchElement, elementK) is true, return true.", + "if (sameValueZero(o[k], searchElement)) {", + "return true;", "}", - "// 8. Return false", - "return false;", - "}", -"});", + "// c. Increase k by 1. ", + "k++;", + "}", + "// 8. Return false", + "return false;", + "}", + "});", -// Polyfill copied from: -// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map -"Object.defineProperty(Array.prototype, 'map',", + // Polyfill copied from: + // developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map + "Object.defineProperty(Array.prototype, 'map',", "{configurable: true, writable: true, value:", - "function(callback, thisArg) {", + "function(callback, thisArg) {", "if (!this || typeof callback !== 'function') new TypeError;", "var T, A, k;", "var O = Object(this);", @@ -1078,123 +1214,124 @@ Interpreter.prototype.initArray = function(scope) { "A = new Array(len);", "k = 0;", "while (k < len) {", - "if (k in O) A[k] = callback.call(T, O[k], k, O);", - "k++;", + "if (k in O) A[k] = callback.call(T, O[k], k, O);", + "k++;", "}", "return A;", - "}", -"});", + "}", + "});", -// Polyfill copied from: -// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce -"Object.defineProperty(Array.prototype, 'reduce',", + // Polyfill copied from: + // developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce + "Object.defineProperty(Array.prototype, 'reduce',", "{configurable: true, writable: true, value:", - "function(callback /*, initialValue*/) {", + "function(callback /*, initialValue*/) {", "if (!this || typeof callback !== 'function') throw TypeError();", "var t = Object(this), len = t.length >>> 0, k = 0, value;", "if (arguments.length === 2) {", - "value = arguments[1];", + "value = arguments[1];", "} else {", - "while (k < len && !(k in t)) k++;", - "if (k >= len) {", - "throw TypeError('Reduce of empty array with no initial value');", - "}", - "value = t[k++];", + "while (k < len && !(k in t)) k++;", + "if (k >= len) {", + "throw TypeError('Reduce of empty array with no initial value');", + "}", + "value = t[k++];", "}", "for (; k < len; k++) {", - "if (k in t) value = callback(value, t[k], k, t);", + "if (k in t) value = callback(value, t[k], k, t);", "}", "return value;", - "}", -"});", + "}", + "});", -// Polyfill copied from: -// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight -"Object.defineProperty(Array.prototype, 'reduceRight',", + // Polyfill copied from: + // developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight + "Object.defineProperty(Array.prototype, 'reduceRight',", "{configurable: true, writable: true, value:", - "function(callback /*, initialValue*/) {", + "function(callback /*, initialValue*/) {", "if (null === this || 'undefined' === typeof this || 'function' !== typeof callback) throw TypeError();", "var t = Object(this), len = t.length >>> 0, k = len - 1, value;", "if (arguments.length >= 2) {", - "value = arguments[1];", + "value = arguments[1];", "} else {", - "while (k >= 0 && !(k in t)) k--;", - "if (k < 0) {", - "throw TypeError('Reduce of empty array with no initial value');", - "}", - "value = t[k--];", + "while (k >= 0 && !(k in t)) k--;", + "if (k < 0) {", + "throw TypeError('Reduce of empty array with no initial value');", + "}", + "value = t[k--];", "}", "for (; k >= 0; k--) {", - "if (k in t) value = callback(value, t[k], k, t);", + "if (k in t) value = callback(value, t[k], k, t);", "}", "return value;", - "}", -"});", + "}", + "});", -// Polyfill copied from: -// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/some -"Object.defineProperty(Array.prototype, 'some',", + // Polyfill copied from: + // developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/some + "Object.defineProperty(Array.prototype, 'some',", "{configurable: true, writable: true, value:", - "function(fun/*, thisArg*/) {", + "function(fun/*, thisArg*/) {", "if (!this || typeof fun !== 'function') throw TypeError();", "var t = Object(this);", "var len = t.length >>> 0;", "var thisArg = arguments.length >= 2 ? arguments[1] : void 0;", "for (var i = 0; i < len; i++) {", - "if (i in t && fun.call(thisArg, t[i], i, t)) {", - "return true;", - "}", + "if (i in t && fun.call(thisArg, t[i], i, t)) {", + "return true;", + "}", "}", "return false;", - "}", -"});", + "}", + "});", -"(function() {", - "var sort_ = Array.prototype.sort;", - "Array.prototype.sort = function(opt_comp) {", + "(function() {", + "var sort_ = Array.prototype.sort;", + "Array.prototype.sort = function(opt_comp) {", // Fast native sort. "if (typeof opt_comp !== 'function') {", - "return sort_.call(this);", + "return sort_.call(this);", "}", // Slow bubble sort. "for (var i = 0; i < this.length; i++) {", - "var changes = 0;", - "for (var j = 0; j < this.length - i - 1; j++) {", - "if (opt_comp(this[j], this[j + 1]) > 0) {", - "var swap = this[j];", - "this[j] = this[j + 1];", - "this[j + 1] = swap;", - "changes++;", - "}", - "}", - "if (!changes) break;", + "var changes = 0;", + "for (var j = 0; j < this.length - i - 1; j++) {", + "if (opt_comp(this[j], this[j + 1]) > 0) {", + "var swap = this[j];", + "this[j] = this[j + 1];", + "this[j + 1] = swap;", + "changes++;", + "}", + "}", + "if (!changes) break;", "}", "return this;", - "};", -"})();", + "};", + "})();", -"Object.defineProperty(Array.prototype, 'toLocaleString',", + "Object.defineProperty(Array.prototype, 'toLocaleString',", "{configurable: true, writable: true, value:", - "function() {", + "function() {", "var out = [];", "for (var i = 0; i < this.length; i++) {", - "out[i] = (this[i] === null || this[i] === undefined) ? '' : this[i].toLocaleString();", + "out[i] = (this[i] === null || this[i] === undefined) ? '' : this[i].toLocaleString();", "}", "return out.join(',');", - "}", -"});", -""); + "}", + "});", + "", + ); }; /** * Initialize the String class. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initString = function(scope) { +Interpreter.prototype.initString = function (scope) { var thisInterpreter = this; var wrapper; // String constructor. - wrapper = function(value) { + wrapper = function (value) { value = String(value); if (thisInterpreter.calledWithNew()) { // Called as new String(). @@ -1206,147 +1343,166 @@ Interpreter.prototype.initString = function(scope) { } }; this.STRING = this.createNativeFunction(wrapper, true); - this.setProperty(scope, 'String', this.STRING); + this.setProperty(scope, "String", this.STRING); // Static methods on String. - this.setProperty(this.STRING, 'fromCharCode', - this.createNativeFunction(String.fromCharCode, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.STRING, + "fromCharCode", + this.createNativeFunction(String.fromCharCode, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); // Instance methods on String. // Methods with exclusively primitive arguments. - var functions = ['charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', - 'slice', 'substr', 'substring', 'toLocaleLowerCase', 'toLocaleUpperCase', - 'toLowerCase', 'toUpperCase', 'trim']; + var functions = [ + "charAt", + "charCodeAt", + "concat", + "indexOf", + "lastIndexOf", + "slice", + "substr", + "substring", + "toLocaleLowerCase", + "toLocaleUpperCase", + "toLowerCase", + "toUpperCase", + "trim", + ]; for (var i = 0; i < functions.length; i++) { - this.setNativeFunctionPrototype(this.STRING, functions[i], - String.prototype[functions[i]]); + this.setNativeFunctionPrototype( + this.STRING, + functions[i], + String.prototype[functions[i]], + ); } - wrapper = function(compareString, locales, options) { + wrapper = function (compareString, locales, options) { locales = locales ? thisInterpreter.pseudoToNative(locales) : undefined; options = options ? thisInterpreter.pseudoToNative(options) : undefined; return String(this).localeCompare(compareString, locales, options); }; - this.setNativeFunctionPrototype(this.STRING, 'localeCompare', wrapper); + this.setNativeFunctionPrototype(this.STRING, "localeCompare", wrapper); - wrapper = function(separator, limit) { + wrapper = function (separator, limit) { if (thisInterpreter.isa(separator, thisInterpreter.REGEXP)) { separator = separator.data; } var jsList = String(this).split(separator, limit); return thisInterpreter.arrayNativeToPseudo(jsList); }; - this.setNativeFunctionPrototype(this.STRING, 'split', wrapper); + this.setNativeFunctionPrototype(this.STRING, "split", wrapper); - wrapper = function(regexp) { + wrapper = function (regexp) { if (thisInterpreter.isa(regexp, thisInterpreter.REGEXP)) { regexp = regexp.data; } var m = String(this).match(regexp); return m && thisInterpreter.arrayNativeToPseudo(m); }; - this.setNativeFunctionPrototype(this.STRING, 'match', wrapper); + this.setNativeFunctionPrototype(this.STRING, "match", wrapper); - wrapper = function(regexp) { + wrapper = function (regexp) { if (thisInterpreter.isa(regexp, thisInterpreter.REGEXP)) { regexp = regexp.data; } return String(this).search(regexp); }; - this.setNativeFunctionPrototype(this.STRING, 'search', wrapper); + this.setNativeFunctionPrototype(this.STRING, "search", wrapper); - wrapper = function(substr, newSubstr) { + wrapper = function (substr, newSubstr) { // Support for function replacements is the responsibility of a polyfill. if (thisInterpreter.isa(substr, thisInterpreter.REGEXP)) { substr = substr.data; } return String(this).replace(substr, newSubstr); }; - this.setNativeFunctionPrototype(this.STRING, 'replace', wrapper); + this.setNativeFunctionPrototype(this.STRING, "replace", wrapper); // Add a polyfill to handle replace's second argument being a function. this.polyfills_.push( -"(function() {", - "var replace_ = String.prototype.replace;", - "String.prototype.replace = function(substr, newSubstr) {", + "(function() {", + "var replace_ = String.prototype.replace;", + "String.prototype.replace = function(substr, newSubstr) {", "if (typeof newSubstr !== 'function') {", - // string.replace(string|regexp, string) - "return replace_.call(this, substr, newSubstr);", + // string.replace(string|regexp, string) + "return replace_.call(this, substr, newSubstr);", "}", "var str = this;", - "if (substr instanceof RegExp) {", // string.replace(regexp, function) - "var subs = [];", - "var m = substr.exec(str);", - "while (m) {", - "m.push(m.index, str);", - "var inject = newSubstr.apply(null, m);", - "subs.push([m.index, m[0].length, inject]);", - "m = substr.global ? substr.exec(str) : null;", - "}", - "for (var i = subs.length - 1; i >= 0; i--) {", - "str = str.substring(0, subs[i][0]) + subs[i][2] + " + - "str.substring(subs[i][0] + subs[i][1]);", - "}", - "} else {", // string.replace(string, function) - "var i = str.indexOf(substr);", - "if (i !== -1) {", - "var inject = newSubstr(str.substr(i, substr.length), i, str);", - "str = str.substring(0, i) + inject + " + - "str.substring(i + substr.length);", - "}", + "if (substr instanceof RegExp) {", // string.replace(regexp, function) + "var subs = [];", + "var m = substr.exec(str);", + "while (m) {", + "m.push(m.index, str);", + "var inject = newSubstr.apply(null, m);", + "subs.push([m.index, m[0].length, inject]);", + "m = substr.global ? substr.exec(str) : null;", + "}", + "for (var i = subs.length - 1; i >= 0; i--) {", + "str = str.substring(0, subs[i][0]) + subs[i][2] + " + + "str.substring(subs[i][0] + subs[i][1]);", + "}", + "} else {", // string.replace(string, function) + "var i = str.indexOf(substr);", + "if (i !== -1) {", + "var inject = newSubstr(str.substr(i, substr.length), i, str);", + "str = str.substring(0, i) + inject + " + + "str.substring(i + substr.length);", + "}", "}", "return str;", - "};", -"})();", + "};", + "})();", -// Polyfill copied from: -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith -"if (!String.prototype.endsWith) {", - "String.prototype.endsWith = function(search, this_len) {", - "if (this_len === undefined || this_len > this.length) {", - "this_len = this.length;", - "}", - "return this.substring(this_len - search.length, this_len) === search;", - "};", -"}", + // Polyfill copied from: + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith + "if (!String.prototype.endsWith) {", + "String.prototype.endsWith = function(search, this_len) {", + "if (this_len === undefined || this_len > this.length) {", + "this_len = this.length;", + "}", + "return this.substring(this_len - search.length, this_len) === search;", + "};", + "}", -//Polyfill copied from: -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes -"if (!String.prototype.includes) {", - "String.prototype.includes = function(search, start) {", + //Polyfill copied from: + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes + "if (!String.prototype.includes) {", + "String.prototype.includes = function(search, start) {", "'use strict';", "if (typeof start !== 'number') {", - "start = 0;", + "start = 0;", "}", -" ", + " ", "if (start + search.length > this.length) {", - "return false;", + "return false;", "} else {", - "return this.indexOf(search, start) !== -1;", + "return this.indexOf(search, start) !== -1;", + "}", + "};", "}", - "};", -"}", -// Polyfill copied from: -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith -"if (!String.prototype.startsWith) {", - "String.prototype.startsWith = function(search, pos) {", - "return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;", - "};", -"}", + // Polyfill copied from: + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith + "if (!String.prototype.startsWith) {", + "String.prototype.startsWith = function(search, pos) {", + "return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;", + "};", + "}", -""); + "", + ); }; /** * Initialize the Boolean class. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initBoolean = function(scope) { +Interpreter.prototype.initBoolean = function (scope) { var thisInterpreter = this; var wrapper; // Boolean constructor. - wrapper = function(value) { + wrapper = function (value) { value = Boolean(value); if (thisInterpreter.calledWithNew()) { // Called as new Boolean(). @@ -1358,18 +1514,18 @@ Interpreter.prototype.initBoolean = function(scope) { } }; this.BOOLEAN = this.createNativeFunction(wrapper, true); - this.setProperty(scope, 'Boolean', this.BOOLEAN); + this.setProperty(scope, "Boolean", this.BOOLEAN); }; /** * Initialize the Number class. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initNumber = function(scope) { +Interpreter.prototype.initNumber = function (scope) { var thisInterpreter = this; var wrapper; // Number constructor. - wrapper = function(value) { + wrapper = function (value) { value = Number(value); if (thisInterpreter.calledWithNew()) { // Called as new Number(). @@ -1381,17 +1537,26 @@ Interpreter.prototype.initNumber = function(scope) { } }; this.NUMBER = this.createNativeFunction(wrapper, true); - this.setProperty(scope, 'Number', this.NUMBER); + this.setProperty(scope, "Number", this.NUMBER); - var numConsts = ['MAX_VALUE', 'MIN_VALUE', 'NaN', 'NEGATIVE_INFINITY', - 'POSITIVE_INFINITY']; + var numConsts = [ + "MAX_VALUE", + "MIN_VALUE", + "NaN", + "NEGATIVE_INFINITY", + "POSITIVE_INFINITY", + ]; for (var i = 0; i < numConsts.length; i++) { - this.setProperty(this.NUMBER, numConsts[i], Number[numConsts[i]], - Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.NUMBER, + numConsts[i], + Number[numConsts[i]], + Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR, + ); } // Instance methods on Number. - wrapper = function(fractionDigits) { + wrapper = function (fractionDigits) { try { return Number(this).toExponential(fractionDigits); } catch (e) { @@ -1399,9 +1564,9 @@ Interpreter.prototype.initNumber = function(scope) { thisInterpreter.throwException(thisInterpreter.ERROR, e.message); } }; - this.setNativeFunctionPrototype(this.NUMBER, 'toExponential', wrapper); + this.setNativeFunctionPrototype(this.NUMBER, "toExponential", wrapper); - wrapper = function(digits) { + wrapper = function (digits) { try { return Number(this).toFixed(digits); } catch (e) { @@ -1409,9 +1574,9 @@ Interpreter.prototype.initNumber = function(scope) { thisInterpreter.throwException(thisInterpreter.ERROR, e.message); } }; - this.setNativeFunctionPrototype(this.NUMBER, 'toFixed', wrapper); + this.setNativeFunctionPrototype(this.NUMBER, "toFixed", wrapper); - wrapper = function(precision) { + wrapper = function (precision) { try { return Number(this).toPrecision(precision); } catch (e) { @@ -1419,9 +1584,9 @@ Interpreter.prototype.initNumber = function(scope) { thisInterpreter.throwException(thisInterpreter.ERROR, e.message); } }; - this.setNativeFunctionPrototype(this.NUMBER, 'toPrecision', wrapper); + this.setNativeFunctionPrototype(this.NUMBER, "toPrecision", wrapper); - wrapper = function(radix) { + wrapper = function (radix) { try { return Number(this).toString(radix); } catch (e) { @@ -1429,25 +1594,25 @@ Interpreter.prototype.initNumber = function(scope) { thisInterpreter.throwException(thisInterpreter.ERROR, e.message); } }; - this.setNativeFunctionPrototype(this.NUMBER, 'toString', wrapper); + this.setNativeFunctionPrototype(this.NUMBER, "toString", wrapper); - wrapper = function(locales, options) { + wrapper = function (locales, options) { locales = locales ? thisInterpreter.pseudoToNative(locales) : undefined; options = options ? thisInterpreter.pseudoToNative(options) : undefined; return Number(this).toLocaleString(locales, options); }; - this.setNativeFunctionPrototype(this.NUMBER, 'toLocaleString', wrapper); + this.setNativeFunctionPrototype(this.NUMBER, "toLocaleString", wrapper); }; /** * Initialize the Date class. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initDate = function(scope) { +Interpreter.prototype.initDate = function (scope) { var thisInterpreter = this; var wrapper; // Date constructor. - wrapper = function(value, var_args) { + wrapper = function (value, var_args) { if (!thisInterpreter.calledWithNew()) { // Called as Date(). // Calling Date() as a function returns a string, no arguments are heeded. @@ -1455,40 +1620,85 @@ Interpreter.prototype.initDate = function(scope) { } // Called as new Date(). var args = [null].concat(Array.from(arguments)); - this.data = new (Function.prototype.bind.apply(Date, args)); + this.data = new (Function.prototype.bind.apply(Date, args))(); return this; }; this.DATE = this.createNativeFunction(wrapper, true); - this.DATE_PROTO = this.DATE.properties['prototype']; - this.setProperty(scope, 'Date', this.DATE); + this.DATE_PROTO = this.DATE.properties["prototype"]; + this.setProperty(scope, "Date", this.DATE); // Static methods on Date. - this.setProperty(this.DATE, 'now', this.createNativeFunction(Date.now, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.DATE, + "now", + this.createNativeFunction(Date.now, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); - this.setProperty(this.DATE, 'parse', - this.createNativeFunction(Date.parse, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.DATE, + "parse", + this.createNativeFunction(Date.parse, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); - this.setProperty(this.DATE, 'UTC', this.createNativeFunction(Date.UTC, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.DATE, + "UTC", + this.createNativeFunction(Date.UTC, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); // Instance methods on Date. - var functions = ['getDate', 'getDay', 'getFullYear', 'getHours', - 'getMilliseconds', 'getMinutes', 'getMonth', 'getSeconds', 'getTime', - 'getTimezoneOffset', 'getUTCDate', 'getUTCDay', 'getUTCFullYear', - 'getUTCHours', 'getUTCMilliseconds', 'getUTCMinutes', 'getUTCMonth', - 'getUTCSeconds', 'getYear', - 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', - 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', - 'setUTCFullYear', 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', - 'setUTCMonth', 'setUTCSeconds', 'setYear', - 'toDateString', 'toISOString', 'toJSON', 'toGMTString', - 'toLocaleDateString', 'toLocaleString', 'toLocaleTimeString', - 'toTimeString', 'toUTCString']; + var functions = [ + "getDate", + "getDay", + "getFullYear", + "getHours", + "getMilliseconds", + "getMinutes", + "getMonth", + "getSeconds", + "getTime", + "getTimezoneOffset", + "getUTCDate", + "getUTCDay", + "getUTCFullYear", + "getUTCHours", + "getUTCMilliseconds", + "getUTCMinutes", + "getUTCMonth", + "getUTCSeconds", + "getYear", + "setDate", + "setFullYear", + "setHours", + "setMilliseconds", + "setMinutes", + "setMonth", + "setSeconds", + "setTime", + "setUTCDate", + "setUTCFullYear", + "setUTCHours", + "setUTCMilliseconds", + "setUTCMinutes", + "setUTCMonth", + "setUTCSeconds", + "setYear", + "toDateString", + "toISOString", + "toJSON", + "toGMTString", + "toLocaleDateString", + "toLocaleString", + "toLocaleTimeString", + "toTimeString", + "toUTCString", + ]; for (var i = 0; i < functions.length; i++) { - wrapper = (function(nativeFunc) { - return function(var_args) { + wrapper = (function (nativeFunc) { + return function (var_args) { var args = []; for (var i = 0; i < arguments.length; i++) { args[i] = thisInterpreter.pseudoToNative(arguments[i]); @@ -1504,11 +1714,11 @@ Interpreter.prototype.initDate = function(scope) { * Initialize Regular Expression object. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initRegExp = function(scope) { +Interpreter.prototype.initRegExp = function (scope) { var thisInterpreter = this; var wrapper; // RegExp constructor. - wrapper = function(pattern, flags) { + wrapper = function (pattern, flags) { if (thisInterpreter.calledWithNew()) { // Called as new RegExp(). var rgx = this; @@ -1516,61 +1726,79 @@ Interpreter.prototype.initRegExp = function(scope) { // Called as RegExp(). var rgx = thisInterpreter.createObjectProto(thisInterpreter.REGEXP_PROTO); } - pattern = pattern ? pattern.toString() : ''; - flags = flags ? flags.toString() : ''; + pattern = pattern ? pattern.toString() : ""; + flags = flags ? flags.toString() : ""; thisInterpreter.populateRegExp(rgx, new RegExp(pattern, flags)); return rgx; }; this.REGEXP = this.createNativeFunction(wrapper, true); - this.REGEXP_PROTO = this.REGEXP.properties['prototype']; - this.setProperty(scope, 'RegExp', this.REGEXP); + this.REGEXP_PROTO = this.REGEXP.properties["prototype"]; + this.setProperty(scope, "RegExp", this.REGEXP); - this.setProperty(this.REGEXP.properties['prototype'], 'global', undefined, - Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR); - this.setProperty(this.REGEXP.properties['prototype'], 'ignoreCase', undefined, - Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR); - this.setProperty(this.REGEXP.properties['prototype'], 'multiline', undefined, - Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR); - this.setProperty(this.REGEXP.properties['prototype'], 'source', '(?:)', - Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR); + this.setProperty( + this.REGEXP.properties["prototype"], + "global", + undefined, + Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR, + ); + this.setProperty( + this.REGEXP.properties["prototype"], + "ignoreCase", + undefined, + Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR, + ); + this.setProperty( + this.REGEXP.properties["prototype"], + "multiline", + undefined, + Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR, + ); + this.setProperty( + this.REGEXP.properties["prototype"], + "source", + "(?:)", + Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR, + ); - wrapper = function(str) { + wrapper = function (str) { return this.data.test(str); }; - this.setNativeFunctionPrototype(this.REGEXP, 'test', wrapper); + this.setNativeFunctionPrototype(this.REGEXP, "test", wrapper); - wrapper = function(str) { + wrapper = function (str) { str = str.toString(); // Get lastIndex from wrapped regex, since this is settable. - this.data.lastIndex = - Number(thisInterpreter.getProperty(this, 'lastIndex')); + this.data.lastIndex = Number( + thisInterpreter.getProperty(this, "lastIndex"), + ); var match = this.data.exec(str); - thisInterpreter.setProperty(this, 'lastIndex', this.data.lastIndex); + thisInterpreter.setProperty(this, "lastIndex", this.data.lastIndex); if (match) { - var result = - thisInterpreter.createObjectProto(thisInterpreter.ARRAY_PROTO); + var result = thisInterpreter.createObjectProto( + thisInterpreter.ARRAY_PROTO, + ); for (var i = 0; i < match.length; i++) { thisInterpreter.setProperty(result, i, match[i]); } // match has additional properties. - thisInterpreter.setProperty(result, 'index', match.index); - thisInterpreter.setProperty(result, 'input', match.input); + thisInterpreter.setProperty(result, "index", match.index); + thisInterpreter.setProperty(result, "input", match.input); return result; } return null; }; - this.setNativeFunctionPrototype(this.REGEXP, 'exec', wrapper); + this.setNativeFunctionPrototype(this.REGEXP, "exec", wrapper); }; /** * Initialize the Error class. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initError = function(scope) { +Interpreter.prototype.initError = function (scope) { var thisInterpreter = this; // Error constructor. - this.ERROR = this.createNativeFunction(function(opt_message) { + this.ERROR = this.createNativeFunction(function (opt_message) { if (thisInterpreter.calledWithNew()) { // Called as new Error(). var newError = this; @@ -1579,71 +1807,128 @@ Interpreter.prototype.initError = function(scope) { var newError = thisInterpreter.createObject(thisInterpreter.ERROR); } if (opt_message) { - thisInterpreter.setProperty(newError, 'message', String(opt_message), - Interpreter.NONENUMERABLE_DESCRIPTOR); + thisInterpreter.setProperty( + newError, + "message", + String(opt_message), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); } return newError; }, true); - this.setProperty(scope, 'Error', this.ERROR); - this.setProperty(this.ERROR.properties['prototype'], 'message', '', - Interpreter.NONENUMERABLE_DESCRIPTOR); - this.setProperty(this.ERROR.properties['prototype'], 'name', 'Error', - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty(scope, "Error", this.ERROR); + this.setProperty( + this.ERROR.properties["prototype"], + "message", + "", + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); + this.setProperty( + this.ERROR.properties["prototype"], + "name", + "Error", + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); - var createErrorSubclass = function(name) { - var constructor = thisInterpreter.createNativeFunction( - function(opt_message) { - if (thisInterpreter.calledWithNew()) { - // Called as new XyzError(). - var newError = this; - } else { - // Called as XyzError(). - var newError = thisInterpreter.createObject(constructor); - } - if (opt_message) { - thisInterpreter.setProperty(newError, 'message', - String(opt_message), Interpreter.NONENUMERABLE_DESCRIPTOR); - } - return newError; - }, true); - thisInterpreter.setProperty(constructor, 'prototype', - thisInterpreter.createObject(thisInterpreter.ERROR)); - thisInterpreter.setProperty(constructor.properties['prototype'], 'name', - name, Interpreter.NONENUMERABLE_DESCRIPTOR); + var createErrorSubclass = function (name) { + var constructor = thisInterpreter.createNativeFunction(function ( + opt_message, + ) { + if (thisInterpreter.calledWithNew()) { + // Called as new XyzError(). + var newError = this; + } else { + // Called as XyzError(). + var newError = thisInterpreter.createObject(constructor); + } + if (opt_message) { + thisInterpreter.setProperty( + newError, + "message", + String(opt_message), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); + } + return newError; + }, + true); + thisInterpreter.setProperty( + constructor, + "prototype", + thisInterpreter.createObject(thisInterpreter.ERROR), + ); + thisInterpreter.setProperty( + constructor.properties["prototype"], + "name", + name, + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); thisInterpreter.setProperty(scope, name, constructor); return constructor; }; - this.EVAL_ERROR = createErrorSubclass('EvalError'); - this.RANGE_ERROR = createErrorSubclass('RangeError'); - this.REFERENCE_ERROR = createErrorSubclass('ReferenceError'); - this.SYNTAX_ERROR = createErrorSubclass('SyntaxError'); - this.TYPE_ERROR = createErrorSubclass('TypeError'); - this.URI_ERROR = createErrorSubclass('URIError'); + this.EVAL_ERROR = createErrorSubclass("EvalError"); + this.RANGE_ERROR = createErrorSubclass("RangeError"); + this.REFERENCE_ERROR = createErrorSubclass("ReferenceError"); + this.SYNTAX_ERROR = createErrorSubclass("SyntaxError"); + this.TYPE_ERROR = createErrorSubclass("TypeError"); + this.URI_ERROR = createErrorSubclass("URIError"); }; /** * Initialize Math object. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initMath = function(scope) { +Interpreter.prototype.initMath = function (scope) { var thisInterpreter = this; var myMath = this.createObjectProto(this.OBJECT_PROTO); - this.setProperty(scope, 'Math', myMath); - var mathConsts = ['E', 'LN2', 'LN10', 'LOG2E', 'LOG10E', 'PI', - 'SQRT1_2', 'SQRT2']; + this.setProperty(scope, "Math", myMath); + var mathConsts = [ + "E", + "LN2", + "LN10", + "LOG2E", + "LOG10E", + "PI", + "SQRT1_2", + "SQRT2", + ]; for (var i = 0; i < mathConsts.length; i++) { - this.setProperty(myMath, mathConsts[i], Math[mathConsts[i]], - Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR); + this.setProperty( + myMath, + mathConsts[i], + Math[mathConsts[i]], + Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR, + ); } - var numFunctions = ['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', - 'exp', 'floor', 'log', 'max', 'min', 'pow', 'random', - 'round', 'sin', 'sqrt', 'tan']; + var numFunctions = [ + "abs", + "acos", + "asin", + "atan", + "atan2", + "ceil", + "cos", + "exp", + "floor", + "log", + "max", + "min", + "pow", + "random", + "round", + "sin", + "sqrt", + "tan", + ]; for (var i = 0; i < numFunctions.length; i++) { - this.setProperty(myMath, numFunctions[i], - this.createNativeFunction(Math[numFunctions[i]], false), - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + myMath, + numFunctions[i], + this.createNativeFunction(Math[numFunctions[i]], false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); } }; @@ -1651,12 +1936,12 @@ Interpreter.prototype.initMath = function(scope) { * Initialize JSON object. * @param {!Interpreter.Object} scope Global scope. */ -Interpreter.prototype.initJSON = function(scope) { +Interpreter.prototype.initJSON = function (scope) { var thisInterpreter = this; var myJSON = thisInterpreter.createObjectProto(this.OBJECT_PROTO); - this.setProperty(scope, 'JSON', myJSON); + this.setProperty(scope, "JSON", myJSON); - var wrapper = function(text) { + var wrapper = function (text) { try { var nativeObj = JSON.parse(text.toString()); } catch (e) { @@ -1664,9 +1949,9 @@ Interpreter.prototype.initJSON = function(scope) { } return thisInterpreter.nativeToPseudo(nativeObj); }; - this.setProperty(myJSON, 'parse', this.createNativeFunction(wrapper, false)); + this.setProperty(myJSON, "parse", this.createNativeFunction(wrapper, false)); - wrapper = function(value) { + wrapper = function (value) { var nativeObj = thisInterpreter.pseudoToNative(value); try { var str = JSON.stringify(nativeObj); @@ -1675,8 +1960,11 @@ Interpreter.prototype.initJSON = function(scope) { } return str; }; - this.setProperty(myJSON, 'stringify', - this.createNativeFunction(wrapper, false)); + this.setProperty( + myJSON, + "stringify", + this.createNativeFunction(wrapper, false), + ); }; /** @@ -1686,11 +1974,11 @@ Interpreter.prototype.initJSON = function(scope) { * @return {boolean} True if object is the class or inherits from it. * False otherwise. */ -Interpreter.prototype.isa = function(child, constructor) { +Interpreter.prototype.isa = function (child, constructor) { if (child === null || child === undefined || !constructor) { return false; } - var proto = constructor.properties['prototype']; + var proto = constructor.properties["prototype"]; if (child === proto) { return true; } @@ -1712,10 +2000,10 @@ Interpreter.prototype.isa = function(child, constructor) { * @return {number} Zero, or a positive integer if the value can be * converted to such. NaN otherwise. */ -Interpreter.legalArrayLength = function(x) { +Interpreter.legalArrayLength = function (x) { var n = x >>> 0; // Array length must be between 0 and 2^32-1 (inclusive). - return (n === Number(x)) ? n : NaN; + return n === Number(x) ? n : NaN; }; /** @@ -1724,11 +2012,11 @@ Interpreter.legalArrayLength = function(x) { * @return {number} Zero, or a positive integer if the value can be * converted to such. NaN otherwise. */ -Interpreter.legalArrayIndex = function(x) { +Interpreter.legalArrayIndex = function (x) { var n = x >>> 0; // Array index cannot be 2^32-1, otherwise length would be 2^32. // 0xffffffff is 2^32-1. - return (String(n) === String(x) && n !== 0xffffffff) ? n : NaN; + return String(n) === String(x) && n !== 0xffffffff ? n : NaN; }; /** @@ -1742,7 +2030,7 @@ Interpreter.Value; * @param {Interpreter.Object} proto Prototype object or null. * @constructor */ -Interpreter.Object = function(proto) { +Interpreter.Object = function (proto) { this.getter = Object.create(null); this.setter = Object.create(null); this.properties = Object.create(null); @@ -1756,7 +2044,7 @@ Interpreter.Object.prototype.proto = null; Interpreter.Object.prototype.isObject = true; /** @type {string} */ -Interpreter.Object.prototype.class = 'Object'; +Interpreter.Object.prototype.class = "Object"; /** @type {Date|RegExp|boolean|number|string|undefined|null} */ Interpreter.Object.prototype.data = null; @@ -1766,8 +2054,8 @@ Interpreter.Object.prototype.data = null; * @return {string} String value. * @override */ -Interpreter.Object.prototype.toString = function() { - if (this.class === 'Array') { +Interpreter.Object.prototype.toString = function () { + if (this.class === "Array") { // Array var cycles = Interpreter.toStringCycles_; cycles.push(this); @@ -1775,32 +2063,34 @@ Interpreter.Object.prototype.toString = function() { var strs = []; for (var i = 0; i < this.properties.length; i++) { var value = this.properties[i]; - strs[i] = (value && value.isObject && cycles.indexOf(value) !== -1) ? - '...' : value; + strs[i] = + value && value.isObject && cycles.indexOf(value) !== -1 + ? "..." + : value; } } finally { cycles.pop(); } - return strs.join(','); + return strs.join(","); } - if (this.class === 'Error') { + if (this.class === "Error") { var cycles = Interpreter.toStringCycles_; if (cycles.indexOf(this) !== -1) { - return '[object Error]'; + return "[object Error]"; } var name, message; // Bug: Does not support getters and setters for name or message. var obj = this; do { - if ('name' in obj.properties) { - name = obj.properties['name']; + if ("name" in obj.properties) { + name = obj.properties["name"]; break; } } while ((obj = obj.proto)); var obj = this; do { - if ('message' in obj.properties) { - message = obj.properties['message']; + if ("message" in obj.properties) { + message = obj.properties["message"]; break; } } while ((obj = obj.proto)); @@ -1811,7 +2101,7 @@ Interpreter.Object.prototype.toString = function() { } finally { cycles.pop(); } - return message ? name + ': ' + message : String(name); + return message ? name + ": " + message : String(name); } // RegExp, Date, and boxed primitives. @@ -1819,7 +2109,7 @@ Interpreter.Object.prototype.toString = function() { return String(this.data); } - return '[object ' + this.class + ']'; + return "[object " + this.class + "]"; }; /** @@ -1827,15 +2117,18 @@ Interpreter.Object.prototype.toString = function() { * @return {Interpreter.Value} Value. * @override */ -Interpreter.Object.prototype.valueOf = function() { - if (this.data === undefined || this.data === null || - this.data instanceof RegExp) { +Interpreter.Object.prototype.valueOf = function () { + if ( + this.data === undefined || + this.data === null || + this.data instanceof RegExp + ) { return this; // An Object. } if (this.data instanceof Date) { - return this.data.valueOf(); // Milliseconds. + return this.data.valueOf(); // Milliseconds. } - return /** @type {(boolean|number|string)} */ (this.data); // Boxed primitive. + return /** @type {(boolean|number|string)} */ (this.data); // Boxed primitive. }; /** @@ -1844,9 +2137,10 @@ Interpreter.Object.prototype.valueOf = function() { * or null if scope object. * @return {!Interpreter.Object} New data object. */ -Interpreter.prototype.createObject = function(constructor) { - return this.createObjectProto(constructor && - constructor.properties['prototype']); +Interpreter.prototype.createObject = function (constructor) { + return this.createObjectProto( + constructor && constructor.properties["prototype"], + ); }; /** @@ -1854,25 +2148,31 @@ Interpreter.prototype.createObject = function(constructor) { * @param {Interpreter.Object} proto Prototype object. * @return {!Interpreter.Object} New data object. */ -Interpreter.prototype.createObjectProto = function(proto) { - if (typeof proto !== 'object') { - throw Error('Non object prototype'); +Interpreter.prototype.createObjectProto = function (proto) { + if (typeof proto !== "object") { + throw Error("Non object prototype"); } var obj = new Interpreter.Object(proto); // Functions have prototype objects. if (this.isa(obj, this.FUNCTION)) { - this.setProperty(obj, 'prototype', - this.createObjectProto(this.OBJECT_PROTO || null)); - obj.class = 'Function'; + this.setProperty( + obj, + "prototype", + this.createObjectProto(this.OBJECT_PROTO || null), + ); + obj.class = "Function"; } // Arrays have length. if (this.isa(obj, this.ARRAY)) { - this.setProperty(obj, 'length', 0, - {configurable: false, enumerable: false, writable: true}); - obj.class = 'Array'; + this.setProperty(obj, "length", 0, { + configurable: false, + enumerable: false, + writable: true, + }); + obj.class = "Array"; } if (this.isa(obj, this.ERROR)) { - obj.class = 'Error'; + obj.class = "Error"; } return obj; }; @@ -1883,19 +2183,39 @@ Interpreter.prototype.createObjectProto = function(proto) { * @param {!Interpreter.Object} pseudoRegexp The existing object to set. * @param {!RegExp} nativeRegexp The native regular expression. */ -Interpreter.prototype.populateRegExp = function(pseudoRegexp, nativeRegexp) { +Interpreter.prototype.populateRegExp = function (pseudoRegexp, nativeRegexp) { pseudoRegexp.data = nativeRegexp; // lastIndex is settable, all others are read-only attributes - this.setProperty(pseudoRegexp, 'lastIndex', nativeRegexp.lastIndex, - Interpreter.NONENUMERABLE_DESCRIPTOR); - this.setProperty(pseudoRegexp, 'source', nativeRegexp.source, - Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR); - this.setProperty(pseudoRegexp, 'global', nativeRegexp.global, - Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR); - this.setProperty(pseudoRegexp, 'ignoreCase', nativeRegexp.ignoreCase, - Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR); - this.setProperty(pseudoRegexp, 'multiline', nativeRegexp.multiline, - Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR); + this.setProperty( + pseudoRegexp, + "lastIndex", + nativeRegexp.lastIndex, + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); + this.setProperty( + pseudoRegexp, + "source", + nativeRegexp.source, + Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR, + ); + this.setProperty( + pseudoRegexp, + "global", + nativeRegexp.global, + Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR, + ); + this.setProperty( + pseudoRegexp, + "ignoreCase", + nativeRegexp.ignoreCase, + Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR, + ); + this.setProperty( + pseudoRegexp, + "multiline", + nativeRegexp.multiline, + Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR, + ); }; /** @@ -1904,12 +2224,16 @@ Interpreter.prototype.populateRegExp = function(pseudoRegexp, nativeRegexp) { * @param {!Object} scope Parent scope. * @return {!Interpreter.Object} New function. */ -Interpreter.prototype.createFunction = function(node, scope) { +Interpreter.prototype.createFunction = function (node, scope) { var func = this.createObjectProto(this.FUNCTION_PROTO); func.parentScope = scope; func.node = node; - this.setProperty(func, 'length', func.node['params'].length, - Interpreter.READONLY_DESCRIPTOR); + this.setProperty( + func, + "length", + func.node["params"].length, + Interpreter.READONLY_DESCRIPTOR, + ); return func; }; @@ -1922,19 +2246,29 @@ Interpreter.prototype.createFunction = function(node, scope) { * Defaults to undefined. * @return {!Interpreter.Object} New function. */ -Interpreter.prototype.createNativeFunction = - function(nativeFunc, opt_constructor) { +Interpreter.prototype.createNativeFunction = function ( + nativeFunc, + opt_constructor, +) { var func = this.createObjectProto(this.FUNCTION_PROTO); func.nativeFunc = nativeFunc; nativeFunc.id = this.functionCounter_++; - this.setProperty(func, 'length', nativeFunc.length, - Interpreter.READONLY_DESCRIPTOR); + this.setProperty( + func, + "length", + nativeFunc.length, + Interpreter.READONLY_DESCRIPTOR, + ); if (opt_constructor) { - this.setProperty(func.properties['prototype'], 'constructor', func, - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + func.properties["prototype"], + "constructor", + func, + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); } else if (opt_constructor === false) { func.illegalConstructor = true; - this.setProperty(func, 'prototype', undefined); + this.setProperty(func, "prototype", undefined); } return func; }; @@ -1944,12 +2278,16 @@ Interpreter.prototype.createNativeFunction = * @param {!Function} asyncFunc JavaScript function. * @return {!Interpreter.Object} New function. */ -Interpreter.prototype.createAsyncFunction = function(asyncFunc) { +Interpreter.prototype.createAsyncFunction = function (asyncFunc) { var func = this.createObjectProto(this.FUNCTION_PROTO); func.asyncFunc = asyncFunc; asyncFunc.id = this.functionCounter_++; - this.setProperty(func, 'length', asyncFunc.length, - Interpreter.READONLY_DESCRIPTOR); + this.setProperty( + func, + "length", + asyncFunc.length, + Interpreter.READONLY_DESCRIPTOR, + ); return func; }; @@ -1959,9 +2297,11 @@ Interpreter.prototype.createAsyncFunction = function(asyncFunc) { * @param {*} nativeObj The native JS object to be converted. * @return {Interpreter.Value} The equivalent JS interpreter object. */ -Interpreter.prototype.nativeToPseudo = function(nativeObj) { - if ((typeof nativeObj !== 'object' && typeof nativeObj !== 'function') || - nativeObj === null) { +Interpreter.prototype.nativeToPseudo = function (nativeObj) { + if ( + (typeof nativeObj !== "object" && typeof nativeObj !== "function") || + nativeObj === null + ) { return nativeObj; } @@ -1979,11 +2319,11 @@ Interpreter.prototype.nativeToPseudo = function(nativeObj) { if (nativeObj instanceof Function) { var interpreter = this; - var wrapper = function() { + var wrapper = function () { return interpreter.nativeToPseudo( - nativeObj.apply(interpreter, - Array.prototype.slice.call(arguments) - .map(function(i) { + nativeObj.apply( + interpreter, + Array.prototype.slice.call(arguments).map(function (i) { return interpreter.pseudoToNative(i); }), ), @@ -1993,14 +2333,16 @@ Interpreter.prototype.nativeToPseudo = function(nativeObj) { } var pseudoObj; - if (Array.isArray(nativeObj)) { // Array. + if (Array.isArray(nativeObj)) { + // Array. pseudoObj = this.createObjectProto(this.ARRAY_PROTO); for (var i = 0; i < nativeObj.length; i++) { if (i in nativeObj) { this.setProperty(pseudoObj, i, this.nativeToPseudo(nativeObj[i])); } } - } else { // Object. + } else { + // Object. pseudoObj = this.createObjectProto(this.OBJECT_PROTO); for (var key in nativeObj) { this.setProperty(pseudoObj, key, this.nativeToPseudo(nativeObj[key])); @@ -2017,17 +2359,21 @@ Interpreter.prototype.nativeToPseudo = function(nativeObj) { * @param {Object=} opt_cycles Cycle detection (used in recursive calls). * @return {*} The equivalent native JS object or value. */ -Interpreter.prototype.pseudoToNative = function(pseudoObj, opt_cycles) { - if ((typeof pseudoObj !== 'object' && typeof pseudoObj !== 'function') || - pseudoObj === null) { +Interpreter.prototype.pseudoToNative = function (pseudoObj, opt_cycles) { + if ( + (typeof pseudoObj !== "object" && typeof pseudoObj !== "function") || + pseudoObj === null + ) { return pseudoObj; } - if (this.isa(pseudoObj, this.REGEXP)) { // Regular expression. + if (this.isa(pseudoObj, this.REGEXP)) { + // Regular expression. return pseudoObj.data; } - if (this.isa(pseudoObj, this.DATE)) { // Date. + if (this.isa(pseudoObj, this.DATE)) { + // Date. return pseudoObj.data; } @@ -2041,17 +2387,21 @@ Interpreter.prototype.pseudoToNative = function(pseudoObj, opt_cycles) { } cycles.pseudo.push(pseudoObj); var nativeObj; - if (this.isa(pseudoObj, this.ARRAY)) { // Array. + if (this.isa(pseudoObj, this.ARRAY)) { + // Array. nativeObj = []; cycles.native.push(nativeObj); - var length = this.getProperty(pseudoObj, 'length'); + var length = this.getProperty(pseudoObj, "length"); for (var i = 0; i < length; i++) { if (this.hasProperty(pseudoObj, i)) { - nativeObj[i] = - this.pseudoToNative(this.getProperty(pseudoObj, i), cycles); + nativeObj[i] = this.pseudoToNative( + this.getProperty(pseudoObj, i), + cycles, + ); } } - } else { // Object. + } else { + // Object. nativeObj = {}; cycles.native.push(nativeObj); var val; @@ -2072,7 +2422,7 @@ Interpreter.prototype.pseudoToNative = function(pseudoObj, opt_cycles) { * @param {!Array} nativeArray The JS array to be converted. * @return {!Interpreter.Object} The equivalent JS interpreter array. */ -Interpreter.prototype.arrayNativeToPseudo = function(nativeArray) { +Interpreter.prototype.arrayNativeToPseudo = function (nativeArray) { var pseudoArray = this.createObjectProto(this.ARRAY_PROTO); var props = Object.getOwnPropertyNames(nativeArray); for (var i = 0; i < props.length; i++) { @@ -2089,7 +2439,7 @@ Interpreter.prototype.arrayNativeToPseudo = function(nativeArray) { * or JS interpreter object pretending to be an array. * @return {!Array} The equivalent native JS array. */ -Interpreter.prototype.arrayPseudoToNative = function(pseudoArray) { +Interpreter.prototype.arrayPseudoToNative = function (pseudoArray) { var nativeArray = []; for (var key in pseudoArray.properties) { nativeArray[key] = this.getProperty(pseudoArray, key); @@ -2097,8 +2447,8 @@ Interpreter.prototype.arrayPseudoToNative = function(pseudoArray) { // pseudoArray might be an object pretending to be an array. In this case // it's possible that length is non-existent, invalid, or smaller than the // largest defined numeric property. Set length explicitly here. - nativeArray.length = Interpreter.legalArrayLength( - this.getProperty(pseudoArray, 'length')) || 0; + nativeArray.length = + Interpreter.legalArrayLength(this.getProperty(pseudoArray, "length")) || 0; return nativeArray; }; @@ -2107,14 +2457,14 @@ Interpreter.prototype.arrayPseudoToNative = function(pseudoArray) { * @param {Interpreter.Value} value Data object. * @return {Interpreter.Object} Prototype object, null if none. */ -Interpreter.prototype.getPrototype = function(value) { +Interpreter.prototype.getPrototype = function (value) { switch (typeof value) { - case 'number': - return this.NUMBER.properties['prototype']; - case 'boolean': - return this.BOOLEAN.properties['prototype']; - case 'string': - return this.STRING.properties['prototype']; + case "number": + return this.NUMBER.properties["prototype"]; + case "boolean": + return this.BOOLEAN.properties["prototype"]; + case "string": + return this.STRING.properties["prototype"]; } if (value) { return value.proto; @@ -2129,17 +2479,20 @@ Interpreter.prototype.getPrototype = function(value) { * @param {Acorn AST Node} node Node that triggered this function. Used by Bitburner for getting error line numbers * @return {Interpreter.Value} Property value (may be undefined). */ -Interpreter.prototype.getProperty = function(obj, name, node) { +Interpreter.prototype.getProperty = function (obj, name, node) { name = String(name); if (obj === undefined || obj === null) { let lineNum; if (node != null && node.loc != null && node.loc.start != null) { - lineNum = node.loc.start.line; + lineNum = node.loc.start.line; } - this.throwException(this.TYPE_ERROR, - "Cannot read property '" + name + "' of " + obj, lineNum); + this.throwException( + this.TYPE_ERROR, + "Cannot read property '" + name + "' of " + obj, + lineNum, + ); } - if (name === 'length') { + if (name === "length") { // Special cases for magic length property. if (this.isa(obj, this.STRING)) { return String(obj).length; @@ -2175,12 +2528,12 @@ Interpreter.prototype.getProperty = function(obj, name, node) { * @param {Interpreter.Value} name Name of property. * @return {boolean} True if property exists. */ -Interpreter.prototype.hasProperty = function(obj, name) { +Interpreter.prototype.hasProperty = function (obj, name) { if (!obj.isObject) { - throw TypeError('Primitive data type has no properties'); + throw TypeError("Primitive data type has no properties"); } name = String(name); - if (name === 'length' && this.isa(obj, this.STRING)) { + if (name === "length" && this.isa(obj, this.STRING)) { return true; } if (this.isa(obj, this.STRING)) { @@ -2208,51 +2561,73 @@ Interpreter.prototype.hasProperty = function(obj, name) { * @return {!Interpreter.Object|undefined} Returns a setter function if one * needs to be called, otherwise undefined. */ -Interpreter.prototype.setProperty = function(obj, name, value, opt_descriptor) { +Interpreter.prototype.setProperty = function ( + obj, + name, + value, + opt_descriptor, +) { name = String(name); if (obj === undefined || obj === null) { - this.throwException(this.TYPE_ERROR, - "Cannot set property '" + name + "' of " + obj); + this.throwException( + this.TYPE_ERROR, + "Cannot set property '" + name + "' of " + obj, + ); } - if (opt_descriptor && ('get' in opt_descriptor || 'set' in opt_descriptor) && - ('value' in opt_descriptor || 'writable' in opt_descriptor)) { - this.throwException(this.TYPE_ERROR, 'Invalid property descriptor. ' + - 'Cannot both specify accessors and a value or writable attribute'); + if ( + opt_descriptor && + ("get" in opt_descriptor || "set" in opt_descriptor) && + ("value" in opt_descriptor || "writable" in opt_descriptor) + ) { + this.throwException( + this.TYPE_ERROR, + "Invalid property descriptor. " + + "Cannot both specify accessors and a value or writable attribute", + ); } var strict = !this.stateStack || this.getScope().strict; if (!obj.isObject) { if (strict) { - this.throwException(this.TYPE_ERROR, "Can't create property '" + name + - "' on '" + obj + "'"); + this.throwException( + this.TYPE_ERROR, + "Can't create property '" + name + "' on '" + obj + "'", + ); } return; } if (this.isa(obj, this.STRING)) { var n = Interpreter.legalArrayIndex(name); - if (name === 'length' || (!isNaN(n) && n < String(obj).length)) { + if (name === "length" || (!isNaN(n) && n < String(obj).length)) { // Can't set length or letters on String objects. if (strict) { - this.throwException(this.TYPE_ERROR, "Cannot assign to read only " + - "property '" + name + "' of String '" + obj.data + "'"); + this.throwException( + this.TYPE_ERROR, + "Cannot assign to read only " + + "property '" + + name + + "' of String '" + + obj.data + + "'", + ); } return; } } - if (obj.class === 'Array') { + if (obj.class === "Array") { // Arrays have a magic length variable that is bound to the elements. var length = obj.properties.length; var i; - if (name === 'length') { + if (name === "length") { // Delete elements if length is smaller. if (opt_descriptor) { - if (!('value' in opt_descriptor)) { + if (!("value" in opt_descriptor)) { return; } value = opt_descriptor.value; } value = Interpreter.legalArrayLength(value); if (isNaN(value)) { - this.throwException(this.RANGE_ERROR, 'Invalid array length'); + this.throwException(this.RANGE_ERROR, "Invalid array length"); } if (value < length) { for (i in obj.properties) { @@ -2262,28 +2637,30 @@ Interpreter.prototype.setProperty = function(obj, name, value, opt_descriptor) { } } } - } else if (!isNaN(i = Interpreter.legalArrayIndex(name))) { + } else if (!isNaN((i = Interpreter.legalArrayIndex(name)))) { // Increase length if this index is larger. obj.properties.length = Math.max(length, i + 1); } } if (obj.preventExtensions && !(name in obj.properties)) { if (strict) { - this.throwException(this.TYPE_ERROR, "Can't add property '" + name + - "', object is not extensible"); + this.throwException( + this.TYPE_ERROR, + "Can't add property '" + name + "', object is not extensible", + ); } return; } if (opt_descriptor) { // Define the property. - if ('get' in opt_descriptor) { + if ("get" in opt_descriptor) { if (opt_descriptor.get) { obj.getter[name] = opt_descriptor.get; } else { delete obj.getter[name]; } } - if ('set' in opt_descriptor) { + if ("set" in opt_descriptor) { if (opt_descriptor.set) { obj.setter[name] = opt_descriptor.set; } else { @@ -2291,18 +2668,18 @@ Interpreter.prototype.setProperty = function(obj, name, value, opt_descriptor) { } } var descriptor = {}; - if ('configurable' in opt_descriptor) { + if ("configurable" in opt_descriptor) { descriptor.configurable = opt_descriptor.configurable; } - if ('enumerable' in opt_descriptor) { + if ("enumerable" in opt_descriptor) { descriptor.enumerable = opt_descriptor.enumerable; } - if ('writable' in opt_descriptor) { + if ("writable" in opt_descriptor) { descriptor.writable = opt_descriptor.writable; delete obj.getter[name]; delete obj.setter[name]; } - if ('value' in opt_descriptor) { + if ("value" in opt_descriptor) { descriptor.value = opt_descriptor.value; delete obj.getter[name]; delete obj.setter[name]; @@ -2314,12 +2691,12 @@ Interpreter.prototype.setProperty = function(obj, name, value, opt_descriptor) { try { Object.defineProperty(obj.properties, name, descriptor); } catch (e) { - this.throwException(this.TYPE_ERROR, 'Cannot redefine property: ' + name); + this.throwException(this.TYPE_ERROR, "Cannot redefine property: " + name); } } else { // Set the property. if (value === Interpreter.VALUE_IN_DESCRIPTOR) { - throw ReferenceError('Value not specified.'); + throw ReferenceError("Value not specified."); } // Determine the parent (possibly self) where the property is defined. var defObj = obj; @@ -2336,8 +2713,14 @@ Interpreter.prototype.setProperty = function(obj, name, value, opt_descriptor) { } if (defObj.getter && defObj.getter[name]) { if (strict) { - this.throwException(this.TYPE_ERROR, "Cannot set property '" + name + - "' of object '" + obj + "' which only has a getter"); + this.throwException( + this.TYPE_ERROR, + "Cannot set property '" + + name + + "' of object '" + + obj + + "' which only has a getter", + ); } } else { // No setter, simple assignment. @@ -2345,8 +2728,15 @@ Interpreter.prototype.setProperty = function(obj, name, value, opt_descriptor) { obj.properties[name] = value; } catch (e) { if (strict) { - this.throwException(this.TYPE_ERROR, "Cannot assign to read only " + - "property '" + name + "' of object '" + obj + "'"); + this.throwException( + this.TYPE_ERROR, + "Cannot assign to read only " + + "property '" + + name + + "' of object '" + + obj + + "'", + ); } } } @@ -2360,21 +2750,27 @@ Interpreter.prototype.setProperty = function(obj, name, value, opt_descriptor) { * @param {Interpreter.Value} name Name of property. * @param {!Function} wrapper Function object. */ -Interpreter.prototype.setNativeFunctionPrototype = - function(obj, name, wrapper) { - this.setProperty(obj.properties['prototype'], name, - this.createNativeFunction(wrapper, false), - Interpreter.NONENUMERABLE_DESCRIPTOR); +Interpreter.prototype.setNativeFunctionPrototype = function ( + obj, + name, + wrapper, +) { + this.setProperty( + obj.properties["prototype"], + name, + this.createNativeFunction(wrapper, false), + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); }; /** * Returns the current scope from the stateStack. * @return {!Interpreter.Object} Current scope dictionary. */ -Interpreter.prototype.getScope = function() { +Interpreter.prototype.getScope = function () { var scope = this.stateStack[this.stateStack.length - 1].scope; if (!scope) { - throw Error('No scope found.'); + throw Error("No scope found."); } return scope; }; @@ -2386,7 +2782,7 @@ Interpreter.prototype.getScope = function() { * @param {Interpreter.Object} parentScope Scope to link to. * @return {!Interpreter.Object} New scope. */ -Interpreter.prototype.createScope = function(node, parentScope) { +Interpreter.prototype.createScope = function (node, parentScope) { var scope = this.createObjectProto(null); scope.parentScope = parentScope; if (!parentScope) { @@ -2399,10 +2795,13 @@ Interpreter.prototype.createScope = function(node, parentScope) { if (parentScope && parentScope.strict) { scope.strict = true; } else { - var firstNode = node['body'] && node['body'][0]; - if (firstNode && firstNode.expression && - firstNode.expression['type'] === 'Literal' && - firstNode.expression.value === 'use strict') { + var firstNode = node["body"] && node["body"][0]; + if ( + firstNode && + firstNode.expression && + firstNode.expression["type"] === "Literal" && + firstNode.expression.value === "use strict" + ) { scope.strict = true; } } @@ -2418,9 +2817,9 @@ Interpreter.prototype.createScope = function(node, parentScope) { * scope. * @return {!Interpreter.Object} New scope. */ -Interpreter.prototype.createSpecialScope = function(parentScope, opt_scope) { +Interpreter.prototype.createSpecialScope = function (parentScope, opt_scope) { if (!parentScope) { - throw Error('parentScope required'); + throw Error("parentScope required"); } var scope = opt_scope || this.createObjectProto(null); scope.parentScope = parentScope; @@ -2436,7 +2835,7 @@ Interpreter.prototype.createSpecialScope = function(parentScope, opt_scope) { * May be flagged as being a getter and thus needing immediate execution * (rather than being the value of the property). */ -Interpreter.prototype.getValueFromScope = function(name, node) { +Interpreter.prototype.getValueFromScope = function (name, node) { var scope = this.getScope(); while (scope && scope !== this.global) { if (name in scope.properties) { @@ -2451,16 +2850,18 @@ Interpreter.prototype.getValueFromScope = function(name, node) { } // Typeof operator is unique: it can safely look at non-defined variables. var prevNode = this.stateStack[this.stateStack.length - 1].node; - if (prevNode['type'] === 'UnaryExpression' && - prevNode['operator'] === 'typeof') { + if ( + prevNode["type"] === "UnaryExpression" && + prevNode["operator"] === "typeof" + ) { return undefined; } var lineNum; if (node != null && node.loc != null && node.loc.start != null) { - lineNum = node.loc.start.line; + lineNum = node.loc.start.line; } - this.throwException(this.REFERENCE_ERROR, name + ' is not defined', lineNum); + this.throwException(this.REFERENCE_ERROR, name + " is not defined", lineNum); }; /** @@ -2470,7 +2871,7 @@ Interpreter.prototype.getValueFromScope = function(name, node) { * @return {!Interpreter.Object|undefined} Returns a setter function if one * needs to be called, otherwise undefined. */ -Interpreter.prototype.setValueToScope = function(name, value) { +Interpreter.prototype.setValueToScope = function (name, value) { var scope = this.getScope(); var strict = scope.strict; while (scope && scope !== this.global) { @@ -2485,7 +2886,7 @@ Interpreter.prototype.setValueToScope = function(name, value) { if (scope === this.global && (!strict || this.hasProperty(scope, name))) { return this.setProperty(scope, name, value); } - this.throwException(this.REFERENCE_ERROR, name + ' is not defined'); + this.throwException(this.REFERENCE_ERROR, name + " is not defined"); }; /** @@ -2494,25 +2895,33 @@ Interpreter.prototype.setValueToScope = function(name, value) { * @param {!Interpreter.Object} scope Scope dictionary to populate. * @private */ -Interpreter.prototype.populateScope_ = function(node, scope) { - if (node['type'] === 'VariableDeclaration') { - for (var i = 0; i < node['declarations'].length; i++) { - this.setProperty(scope, node['declarations'][i]['id']['name'], - undefined, Interpreter.VARIABLE_DESCRIPTOR); +Interpreter.prototype.populateScope_ = function (node, scope) { + if (node["type"] === "VariableDeclaration") { + for (var i = 0; i < node["declarations"].length; i++) { + this.setProperty( + scope, + node["declarations"][i]["id"]["name"], + undefined, + Interpreter.VARIABLE_DESCRIPTOR, + ); } - } else if (node['type'] === 'FunctionDeclaration') { - this.setProperty(scope, node['id']['name'], - this.createFunction(node, scope), Interpreter.VARIABLE_DESCRIPTOR); - return; // Do not recurse into function. - } else if (node['type'] === 'FunctionExpression') { - return; // Do not recurse into function. - } else if (node['type'] === 'ExpressionStatement') { - return; // Expressions can't contain variable/function declarations. + } else if (node["type"] === "FunctionDeclaration") { + this.setProperty( + scope, + node["id"]["name"], + this.createFunction(node, scope), + Interpreter.VARIABLE_DESCRIPTOR, + ); + return; // Do not recurse into function. + } else if (node["type"] === "FunctionExpression") { + return; // Do not recurse into function. + } else if (node["type"] === "ExpressionStatement") { + return; // Expressions can't contain variable/function declarations. } - var nodeClass = node['constructor']; + var nodeClass = node["constructor"]; for (var name in node) { var prop = node[name]; - if (prop && typeof prop === 'object') { + if (prop && typeof prop === "object") { if (Array.isArray(prop)) { for (var i = 0; i < prop.length; i++) { if (prop[i] && prop[i].constructor === nodeClass) { @@ -2537,21 +2946,21 @@ Interpreter.prototype.populateScope_ = function(node, scope) { * @param {number=} end Ending character of all nodes, or undefined. * @private */ -Interpreter.prototype.stripLocations_ = function(node, start, end) { +Interpreter.prototype.stripLocations_ = function (node, start, end) { if (start) { - node['start'] = start; + node["start"] = start; } else { - delete node['start']; + delete node["start"]; } if (end) { - node['end'] = end; + node["end"] = end; } else { - delete node['end']; + delete node["end"]; } for (var name in node) { if (node.hasOwnProperty(name)) { var prop = node[name]; - if (prop && typeof prop === 'object') { + if (prop && typeof prop === "object") { this.stripLocations_(prop, start, end); } } @@ -2562,7 +2971,7 @@ Interpreter.prototype.stripLocations_ = function(node, start, end) { * Is the current state directly being called with as a construction with 'new'. * @return {boolean} True if 'new foo()', false if 'foo()'. */ -Interpreter.prototype.calledWithNew = function() { +Interpreter.prototype.calledWithNew = function () { return this.stateStack[this.stateStack.length - 1].isConstructor; }; @@ -2574,7 +2983,7 @@ Interpreter.prototype.calledWithNew = function() { * May be flagged as being a getter and thus needing immediate execution * (rather than being the value of the property). */ -Interpreter.prototype.getValue = function(ref, node) { +Interpreter.prototype.getValue = function (ref, node) { if (ref[0] === Interpreter.SCOPE_REFERENCE) { // A null/varname variable lookup. return this.getValueFromScope(ref[1], node); @@ -2591,7 +3000,7 @@ Interpreter.prototype.getValue = function(ref, node) { * @return {!Interpreter.Object|undefined} Returns a setter function if one * needs to be called, otherwise undefined. */ -Interpreter.prototype.setValue = function(ref, value) { +Interpreter.prototype.setValue = function (ref, value) { if (ref[0] === Interpreter.SCOPE_REFERENCE) { // A null/varname variable lookup. return this.setValueToScope(ref[1], value); @@ -2602,16 +3011,16 @@ Interpreter.prototype.setValue = function(ref, value) { }; /** - * Completion Value Types. - * @enum {number} - */ - Interpreter.Completion = { - NORMAL: 0, - BREAK: 1, - CONTINUE: 2, - RETURN: 3, - THROW: 4, - }; + * Completion Value Types. + * @enum {number} + */ +Interpreter.Completion = { + NORMAL: 0, + BREAK: 1, + CONTINUE: 2, + RETURN: 3, + THROW: 4, +}; /** * Throw an exception in the interpreter that can be handled by an @@ -2622,17 +3031,25 @@ Interpreter.prototype.setValue = function(ref, value) { * provided) or the value to throw (if no message). * @param {string=} opt_message Message being thrown. */ -Interpreter.prototype.throwException = function(errorClass, opt_message, lineNumber) { +Interpreter.prototype.throwException = function ( + errorClass, + opt_message, + lineNumber, +) { if (opt_message === undefined) { - var error = errorClass; // This is a value to throw, not an error class. + var error = errorClass; // This is a value to throw, not an error class. } else { var error = this.createObject(errorClass); - this.setProperty(error, 'message', opt_message, - Interpreter.NONENUMERABLE_DESCRIPTOR); + this.setProperty( + error, + "message", + opt_message, + Interpreter.NONENUMERABLE_DESCRIPTOR, + ); } var lineNumErrorMsg; - if (lineNumber != null) { - lineNumErrorMsg = this.getErrorLineNumberMessage(lineNumber); + if (lineNumber != null) { + lineNumErrorMsg = this.getErrorLineNumberMessage(lineNumber); } this.unwind(Interpreter.Completion.THROW, error, undefined, lineNumErrorMsg); // Abort anything related to the current step. @@ -2648,35 +3065,46 @@ Interpreter.prototype.throwException = function(errorClass, opt_message, lineNum * @param {Interpreter.Value=} value Value computed, returned or thrown. * @param {string=} label Target label for break or return. */ -Interpreter.prototype.unwind = function(type, value, label, lineNumberMsg="") { +Interpreter.prototype.unwind = function ( + type, + value, + label, + lineNumberMsg = "", +) { if (type === Interpreter.Completion.NORMAL) { - throw TypeError('Should not unwind for NORMAL completions'); + throw TypeError("Should not unwind for NORMAL completions"); } for (var stack = this.stateStack; stack.length > 0; stack.pop()) { var state = stack[stack.length - 1]; - switch (state.node['type']) { - case 'TryStatement': - state.cv = {type: type, value: value, label: label}; + switch (state.node["type"]) { + case "TryStatement": + state.cv = { type: type, value: value, label: label }; return; - case 'CallExpression': - case 'NewExpression': + case "CallExpression": + case "NewExpression": if (type === Interpreter.Completion.RETURN) { state.value = value; return; } else if (type !== Interpreter.Completion.THROW) { - throw Error('Unsynatctic break/continue not rejected by Acorn'); + throw Error("Unsynatctic break/continue not rejected by Acorn"); } } if (type === Interpreter.Completion.BREAK) { - if (label ? (state.labels && state.labels.indexOf(label) !== -1) : - (state.isLoop || state.isSwitch)) { + if ( + label + ? state.labels && state.labels.indexOf(label) !== -1 + : state.isLoop || state.isSwitch + ) { stack.pop(); return; } } else if (type === Interpreter.Completion.CONTINUE) { - if (label ? (state.labels && state.labels.indexOf(label) !== -1) : - state.isLoop) { + if ( + label + ? state.labels && state.labels.indexOf(label) !== -1 + : state.isLoop + ) { return; } } @@ -2686,15 +3114,15 @@ Interpreter.prototype.unwind = function(type, value, label, lineNumberMsg="") { var realError; if (this.isa(value, this.ERROR)) { var errorTable = { - 'EvalError': EvalError, - 'RangeError': RangeError, - 'ReferenceError': ReferenceError, - 'SyntaxError': SyntaxError, - 'TypeError': TypeError, - 'URIError': URIError, + EvalError: EvalError, + RangeError: RangeError, + ReferenceError: ReferenceError, + SyntaxError: SyntaxError, + TypeError: TypeError, + URIError: URIError, }; - var name = this.getProperty(value, 'name').toString(); - var message = this.getProperty(value, 'message').valueOf(); + var name = this.getProperty(value, "name").toString(); + var message = this.getProperty(value, "message").valueOf(); var type = errorTable[name] || Error; realError = type(message + lineNumberMsg); } else { @@ -2710,14 +3138,16 @@ Interpreter.prototype.unwind = function(type, value, label, lineNumberMsg="") { * Name of variable or object/propname tuple. * @private */ -Interpreter.prototype.createGetter_ = function(func, left) { +Interpreter.prototype.createGetter_ = function (func, left) { // Normally 'this' will be specified as the object component (o.x). // Sometimes 'this' is explicitly provided (o). var funcThis = Array.isArray(left) ? left[0] : left; var node = new this.nodeConstructor(); - node['type'] = 'CallExpression'; - var state = new Interpreter.State(node, - this.stateStack[this.stateStack.length - 1].scope); + node["type"] = "CallExpression"; + var state = new Interpreter.State( + node, + this.stateStack[this.stateStack.length - 1].scope, + ); state.doneCallee_ = true; state.funcThis_ = funcThis; state.func_ = func; @@ -2734,14 +3164,16 @@ Interpreter.prototype.createGetter_ = function(func, left) { * @param {Interpreter.Value} value Value to set. * @private */ -Interpreter.prototype.createSetter_ = function(func, left, value) { +Interpreter.prototype.createSetter_ = function (func, left, value) { // Normally 'this' will be specified as the object component (o.x). // Sometimes 'this' is implicitly the global object (x). var funcThis = Array.isArray(left) ? left[0] : this.global; var node = new this.nodeConstructor(); - node['type'] = 'CallExpression'; - var state = new Interpreter.State(node, - this.stateStack[this.stateStack.length - 1].scope); + node["type"] = "CallExpression"; + var state = new Interpreter.State( + node, + this.stateStack[this.stateStack.length - 1].scope, + ); state.doneCallee_ = true; state.funcThis_ = funcThis; state.func_ = func; @@ -2756,18 +3188,17 @@ Interpreter.prototype.createSetter_ = function(func, left, value) { * @param {!Interpreter.Object} scope Scope object for the state. * @constructor */ -Interpreter.State = function(node, scope) { +Interpreter.State = function (node, scope) { this.node = node; this.scope = scope; }; - /////////////////////////////////////////////////////////////////////////////// // Functions to handle each node type. /////////////////////////////////////////////////////////////////////////////// -Interpreter.prototype['stepArrayExpression'] = function(stack, state, node) { - var elements = node['elements']; +Interpreter.prototype["stepArrayExpression"] = function (stack, state, node) { + var elements = node["elements"]; var n = state.n_ || 0; if (!state.array_) { state.array_ = this.createObjectProto(this.ARRAY_PROTO); @@ -2788,11 +3219,14 @@ Interpreter.prototype['stepArrayExpression'] = function(stack, state, node) { stack[stack.length - 1].value = state.array_; }; -Interpreter.prototype['stepAssignmentExpression'] = - function(stack, state, node) { +Interpreter.prototype["stepAssignmentExpression"] = function ( + stack, + state, + node, +) { if (!state.doneLeft_) { state.doneLeft_ = true; - var nextState = new Interpreter.State(node['left'], state.scope); + var nextState = new Interpreter.State(node["left"], state.scope); nextState.components = true; return nextState; } @@ -2803,10 +3237,10 @@ Interpreter.prototype['stepAssignmentExpression'] = if (state.doneGetter_) { state.leftValue_ = state.value; } - if (!state.doneGetter_ && node['operator'] !== '=') { + if (!state.doneGetter_ && node["operator"] !== "=") { var leftValue = this.getValue(state.leftReference_, node); state.leftValue_ = leftValue; - if (leftValue && typeof leftValue === 'object' && leftValue.isGetter) { + if (leftValue && typeof leftValue === "object" && leftValue.isGetter) { // Clear the getter flag and call the getter function. leftValue.isGetter = false; state.doneGetter_ = true; @@ -2815,7 +3249,7 @@ Interpreter.prototype['stepAssignmentExpression'] = } } state.doneRight_ = true; - return new Interpreter.State(node['right'], state.scope); + return new Interpreter.State(node["right"], state.scope); } if (state.doneSetter_) { // Return if setter function. @@ -2827,21 +3261,45 @@ Interpreter.prototype['stepAssignmentExpression'] = } var value = state.leftValue_; var rightValue = state.value; - switch (node['operator']) { - case '=': value = rightValue; break; - case '+=': value += rightValue; break; - case '-=': value -= rightValue; break; - case '*=': value *= rightValue; break; - case '/=': value /= rightValue; break; - case '%=': value %= rightValue; break; - case '<<=': value <<= rightValue; break; - case '>>=': value >>= rightValue; break; - case '>>>=': value >>>= rightValue; break; - case '&=': value &= rightValue; break; - case '^=': value ^= rightValue; break; - case '|=': value |= rightValue; break; + switch (node["operator"]) { + case "=": + value = rightValue; + break; + case "+=": + value += rightValue; + break; + case "-=": + value -= rightValue; + break; + case "*=": + value *= rightValue; + break; + case "/=": + value /= rightValue; + break; + case "%=": + value %= rightValue; + break; + case "<<=": + value <<= rightValue; + break; + case ">>=": + value >>= rightValue; + break; + case ">>>=": + value >>>= rightValue; + break; + case "&=": + value &= rightValue; + break; + case "^=": + value ^= rightValue; + break; + case "|=": + value |= rightValue; + break; default: - throw SyntaxError('Unknown assignment expression: ' + node['operator']); + throw SyntaxError("Unknown assignment expression: " + node["operator"]); } var setter = this.setValue(state.leftReference_, value); if (setter) { @@ -2854,65 +3312,109 @@ Interpreter.prototype['stepAssignmentExpression'] = stack[stack.length - 1].value = value; }; -Interpreter.prototype['stepBinaryExpression'] = function(stack, state, node) { +Interpreter.prototype["stepBinaryExpression"] = function (stack, state, node) { if (!state.doneLeft_) { state.doneLeft_ = true; - return new Interpreter.State(node['left'], state.scope); + return new Interpreter.State(node["left"], state.scope); } if (!state.doneRight_) { state.doneRight_ = true; state.leftValue_ = state.value; - return new Interpreter.State(node['right'], state.scope); + return new Interpreter.State(node["right"], state.scope); } stack.pop(); var leftValue = state.leftValue_; var rightValue = state.value; var value; - switch (node['operator']) { - case '==': value = leftValue == rightValue; break; - case '!=': value = leftValue != rightValue; break; - case '===': value = leftValue === rightValue; break; - case '!==': value = leftValue !== rightValue; break; - case '>': value = leftValue > rightValue; break; - case '>=': value = leftValue >= rightValue; break; - case '<': value = leftValue < rightValue; break; - case '<=': value = leftValue <= rightValue; break; - case '+': value = leftValue + rightValue; break; - case '-': value = leftValue - rightValue; break; - case '*': value = leftValue * rightValue; break; - case '/': value = leftValue / rightValue; break; - case '%': value = leftValue % rightValue; break; - case '&': value = leftValue & rightValue; break; - case '|': value = leftValue | rightValue; break; - case '^': value = leftValue ^ rightValue; break; - case '<<': value = leftValue << rightValue; break; - case '>>': value = leftValue >> rightValue; break; - case '>>>': value = leftValue >>> rightValue; break; - case 'in': + switch (node["operator"]) { + case "==": + value = leftValue == rightValue; + break; + case "!=": + value = leftValue != rightValue; + break; + case "===": + value = leftValue === rightValue; + break; + case "!==": + value = leftValue !== rightValue; + break; + case ">": + value = leftValue > rightValue; + break; + case ">=": + value = leftValue >= rightValue; + break; + case "<": + value = leftValue < rightValue; + break; + case "<=": + value = leftValue <= rightValue; + break; + case "+": + value = leftValue + rightValue; + break; + case "-": + value = leftValue - rightValue; + break; + case "*": + value = leftValue * rightValue; + break; + case "/": + value = leftValue / rightValue; + break; + case "%": + value = leftValue % rightValue; + break; + case "&": + value = leftValue & rightValue; + break; + case "|": + value = leftValue | rightValue; + break; + case "^": + value = leftValue ^ rightValue; + break; + case "<<": + value = leftValue << rightValue; + break; + case ">>": + value = leftValue >> rightValue; + break; + case ">>>": + value = leftValue >>> rightValue; + break; + case "in": if (!rightValue || !rightValue.isObject) { let lineNum = this.getErrorLineNumber(node); - this.throwException(this.TYPE_ERROR, - "'in' expects an object, not '" + rightValue + "'", lineNum); + this.throwException( + this.TYPE_ERROR, + "'in' expects an object, not '" + rightValue + "'", + lineNum, + ); } value = this.hasProperty(rightValue, leftValue); break; - case 'instanceof': + case "instanceof": if (!this.isa(rightValue, this.FUNCTION)) { let lineNum = this.getErrorLineNumber(node); - this.throwException(this.TYPE_ERROR, - 'Right-hand side of instanceof is not an object', lineNum); + this.throwException( + this.TYPE_ERROR, + "Right-hand side of instanceof is not an object", + lineNum, + ); } value = leftValue.isObject ? this.isa(leftValue, rightValue) : false; break; default: - throw SyntaxError('Unknown binary operator: ' + node['operator']); + throw SyntaxError("Unknown binary operator: " + node["operator"]); } stack[stack.length - 1].value = value; }; -Interpreter.prototype['stepBlockStatement'] = function(stack, state, node) { +Interpreter.prototype["stepBlockStatement"] = function (stack, state, node) { var n = state.n_ || 0; - var expression = node['body'][n]; + var expression = node["body"][n]; if (expression) { state.n_ = n + 1; return new Interpreter.State(expression, state.scope); @@ -2920,16 +3422,16 @@ Interpreter.prototype['stepBlockStatement'] = function(stack, state, node) { stack.pop(); }; -Interpreter.prototype['stepBreakStatement'] = function(stack, state, node) { - var label = node['label'] && node['label']['name']; +Interpreter.prototype["stepBreakStatement"] = function (stack, state, node) { + var label = node["label"] && node["label"]["name"]; this.unwind(Interpreter.Completion.BREAK, undefined, label); }; -Interpreter.prototype['stepCallExpression'] = function(stack, state, node) { +Interpreter.prototype["stepCallExpression"] = function (stack, state, node) { if (!state.doneCallee_) { state.doneCallee_ = 1; // Components needed to determine value of 'this'. - var nextState = new Interpreter.State(node['callee'], state.scope); + var nextState = new Interpreter.State(node["callee"], state.scope); nextState.components = true; return nextState; } @@ -2941,18 +3443,20 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) { state.func_ = this.getValue(func, node); if (func[0] === Interpreter.SCOPE_REFERENCE) { // (Globally or locally) named function. Is it named 'eval'? - state.directEval_ = (func[1] === 'eval'); + state.directEval_ = func[1] === "eval"; } else { // Method function, 'this' is object (ignored if invoked as 'new'). state.funcThis_ = func[0]; } func = state.func_; - if (func && typeof func === 'object' && func.isGetter) { + if (func && typeof func === "object" && func.isGetter) { // Clear the getter flag and call the getter function. func.isGetter = false; state.doneCallee_ = 1; - return this.createGetter_(/** @type {!Interpreter.Object} */ (func), - state.value); + return this.createGetter_( + /** @type {!Interpreter.Object} */ (func), + state.value, + ); } } else { // Already evaluated function: (function(){...})(); @@ -2966,19 +3470,23 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) { if (state.n_ !== 0) { state.arguments_.push(state.value); } - if (node['arguments'][state.n_]) { - return new Interpreter.State(node['arguments'][state.n_++], state.scope); + if (node["arguments"][state.n_]) { + return new Interpreter.State(node["arguments"][state.n_++], state.scope); } // Determine value of 'this' in function. - if (node['type'] === 'NewExpression') { + if (node["type"] === "NewExpression") { if (func.illegalConstructor) { // Illegal: new escape(); let lineNum = this.getErrorLineNumber(node); - this.throwException(this.TYPE_ERROR, func + ' is not a constructor', lineNum); + this.throwException( + this.TYPE_ERROR, + func + " is not a constructor", + lineNum, + ); } // Constructor, 'this' is new object. - var proto = func.properties['prototype']; - if (typeof proto !== 'object' || proto === null) { + var proto = func.properties["prototype"]; + if (typeof proto !== "object" || proto === null) { // Non-object prototypes default to Object.prototype. proto = this.OBJECT_PROTO; } @@ -2994,16 +3502,20 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) { state.doneExec_ = true; if (!func || !func.isObject) { let lineNum = this.getErrorLineNumber(node); - this.throwException(this.TYPE_ERROR, func + ' is not a function', lineNum); + this.throwException( + this.TYPE_ERROR, + func + " is not a function", + lineNum, + ); } var funcNode = func.node; if (funcNode) { - var scope = this.createScope(funcNode['body'], func.parentScope); + var scope = this.createScope(funcNode["body"], func.parentScope); // Add all arguments. - for (var i = 0; i < funcNode['params'].length; i++) { - var paramName = funcNode['params'][i]['name']; - var paramValue = state.arguments_.length > i ? state.arguments_[i] : - undefined; + for (var i = 0; i < funcNode["params"].length; i++) { + var paramName = funcNode["params"][i]["name"]; + var paramValue = + state.arguments_.length > i ? state.arguments_[i] : undefined; this.setProperty(scope, paramName, paramValue); } // Build arguments variable. @@ -3011,19 +3523,23 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) { for (var i = 0; i < state.arguments_.length; i++) { this.setProperty(argsList, i, state.arguments_[i]); } - this.setProperty(scope, 'arguments', argsList); + this.setProperty(scope, "arguments", argsList); // Add the function's name (var x = function foo(){};) - var name = funcNode['id'] && funcNode['id']['name']; + var name = funcNode["id"] && funcNode["id"]["name"]; if (name) { this.setProperty(scope, name, func); } - this.setProperty(scope, 'this', state.funcThis_, - Interpreter.READONLY_DESCRIPTOR); - state.value = undefined; // Default value if no explicit return. - return new Interpreter.State(funcNode['body'], scope); + this.setProperty( + scope, + "this", + state.funcThis_, + Interpreter.READONLY_DESCRIPTOR, + ); + state.value = undefined; // Default value if no explicit return. + return new Interpreter.State(funcNode["body"], scope); } else if (func.eval) { var code = state.arguments_[0]; - if (typeof code !== 'string') { + if (typeof code !== "string") { // JS does not parse String objects: // eval(new String('1 + 1')) -> '1 + 1' state.value = code; @@ -3033,12 +3549,16 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) { } catch (e) { // Acorn threw a SyntaxError. Rethrow as a trappable error. let lineNum = this.getErrorLineNumber(node); - this.throwException(this.SYNTAX_ERROR, 'Invalid code: ' + e.message, lineNum); + this.throwException( + this.SYNTAX_ERROR, + "Invalid code: " + e.message, + lineNum, + ); } var evalNode = new this.nodeConstructor(); - evalNode['type'] = 'EvalProgram_'; - evalNode['body'] = ast['body']; - this.stripLocations_(evalNode, node['start'], node['end']); + evalNode["type"] = "EvalProgram_"; + evalNode["body"] = ast["body"]; + this.stripLocations_(evalNode, node["start"], node["end"]); // Create new scope and update it with definitions in eval(). var scope = state.directEval_ ? state.scope : this.global; if (scope.strict) { @@ -3048,14 +3568,14 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) { // Non-strict mode pollutes the current scope. this.populateScope_(ast, scope); } - this.value = undefined; // Default value if no code. + this.value = undefined; // Default value if no code. return new Interpreter.State(evalNode, scope); } } else if (func.nativeFunc) { state.value = func.nativeFunc.apply(state.funcThis_, state.arguments_); } else if (func.asyncFunc) { var thisInterpreter = this; - var callback = function(value) { + var callback = function (value) { state.value = value; thisInterpreter.paused_ = false; }; @@ -3071,12 +3591,16 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) { f(); */ let lineNum = this.getErrorLineNumber(node); - this.throwException(this.TYPE_ERROR, func.class + ' is not a function', lineNum); + this.throwException( + this.TYPE_ERROR, + func.class + " is not a function", + lineNum, + ); } } else { // Execution complete. Put the return value on the stack. stack.pop(); - if (state.isConstructor && typeof state.value !== 'object') { + if (state.isConstructor && typeof state.value !== "object") { stack[stack.length - 1].value = state.funcThis_; } else { stack[stack.length - 1].value = state.value; @@ -3084,82 +3608,87 @@ Interpreter.prototype['stepCallExpression'] = function(stack, state, node) { } }; -Interpreter.prototype['stepCatchClause'] = function(stack, state, node) { +Interpreter.prototype["stepCatchClause"] = function (stack, state, node) { if (!state.done_) { state.done_ = true; // Create an empty scope. var scope = this.createSpecialScope(state.scope); // Add the argument. - this.setProperty(scope, node['param']['name'], state.throwValue); + this.setProperty(scope, node["param"]["name"], state.throwValue); // Execute catch clause. - return new Interpreter.State(node['body'], scope); + return new Interpreter.State(node["body"], scope); } else { stack.pop(); } }; -Interpreter.prototype['stepConditionalExpression'] = - function(stack, state, node) { +Interpreter.prototype["stepConditionalExpression"] = function ( + stack, + state, + node, +) { var mode = state.mode_ || 0; if (mode === 0) { state.mode_ = 1; - return new Interpreter.State(node['test'], state.scope); + return new Interpreter.State(node["test"], state.scope); } if (mode === 1) { state.mode_ = 2; var value = Boolean(state.value); - if (value && node['consequent']) { + if (value && node["consequent"]) { // Execute 'if' block. - return new Interpreter.State(node['consequent'], state.scope); - } else if (!value && node['alternate']) { + return new Interpreter.State(node["consequent"], state.scope); + } else if (!value && node["alternate"]) { // Execute 'else' block. - return new Interpreter.State(node['alternate'], state.scope); + return new Interpreter.State(node["alternate"], state.scope); } // eval('1;if(false){2}') -> undefined this.value = undefined; } stack.pop(); - if (node['type'] === 'ConditionalExpression') { + if (node["type"] === "ConditionalExpression") { stack[stack.length - 1].value = state.value; } }; -Interpreter.prototype['stepContinueStatement'] = function(stack, state, node) { - var label = node['label'] && node['label']['name']; +Interpreter.prototype["stepContinueStatement"] = function (stack, state, node) { + var label = node["label"] && node["label"]["name"]; this.unwind(Interpreter.Completion.CONTINUE, undefined, label); }; -Interpreter.prototype['stepDebuggerStatement'] = function(stack, state, node) { +Interpreter.prototype["stepDebuggerStatement"] = function (stack, state, node) { // Do nothing. May be overridden by developers. stack.pop(); }; -Interpreter.prototype['stepDoWhileStatement'] = function(stack, state, node) { - if (node['type'] === 'DoWhileStatement' && state.test_ === undefined) { +Interpreter.prototype["stepDoWhileStatement"] = function (stack, state, node) { + if (node["type"] === "DoWhileStatement" && state.test_ === undefined) { // First iteration of do/while executes without checking test. state.value = true; state.test_ = true; } if (!state.test_) { state.test_ = true; - return new Interpreter.State(node['test'], state.scope); + return new Interpreter.State(node["test"], state.scope); } - if (!state.value) { // Done, exit loop. + if (!state.value) { + // Done, exit loop. stack.pop(); - } else if (node['body']) { // Execute the body. + } else if (node["body"]) { + // Execute the body. state.test_ = false; state.isLoop = true; - return new Interpreter.State(node['body'], state.scope); + return new Interpreter.State(node["body"], state.scope); } }; -Interpreter.prototype['stepEmptyStatement'] = function(stack, state, node) { +Interpreter.prototype["stepEmptyStatement"] = function (stack, state, node) { stack.pop(); }; -Interpreter.prototype['stepEvalProgram_'] = function(stack, state, node) { +Interpreter.prototype["stepEvalProgram_"] = function (stack, state, node) { var n = state.n_ || 0; - var expression = node['body'][n]; + var expression = node["body"][n]; if (expression) { state.n_ = n + 1; return new Interpreter.State(expression, state.scope); @@ -3168,10 +3697,14 @@ Interpreter.prototype['stepEvalProgram_'] = function(stack, state, node) { stack[stack.length - 1].value = this.value; }; -Interpreter.prototype['stepExpressionStatement'] = function(stack, state, node) { +Interpreter.prototype["stepExpressionStatement"] = function ( + stack, + state, + node, +) { if (!state.done_) { state.done_ = true; - return new Interpreter.State(node['expression'], state.scope); + return new Interpreter.State(node["expression"], state.scope); } stack.pop(); // Save this value to interpreter.value for use as a return value if @@ -3179,19 +3712,24 @@ Interpreter.prototype['stepExpressionStatement'] = function(stack, state, node) this.value = state.value; }; -Interpreter.prototype['stepForInStatement'] = function(stack, state, node) { +Interpreter.prototype["stepForInStatement"] = function (stack, state, node) { // First, initialize a variable if exists. Only do so once, ever. if (!state.doneInit_) { state.doneInit_ = true; - if (node['left']['declarations'] && - node['left']['declarations'][0]['init']) { + if ( + node["left"]["declarations"] && + node["left"]["declarations"][0]["init"] + ) { if (state.scope.strict) { let lineNum = this.getErrorLineNumber(node); - this.throwException(this.SYNTAX_ERROR, - 'for-in loop variable declaration may not have an initializer.', lineNum); + this.throwException( + this.SYNTAX_ERROR, + "for-in loop variable declaration may not have an initializer.", + lineNum, + ); } // Variable initialization: for (var x = 4 in y) - return new Interpreter.State(node['left'], state.scope); + return new Interpreter.State(node["left"], state.scope); } } // Second, look up the object. Only do so once, ever. @@ -3200,7 +3738,7 @@ Interpreter.prototype['stepForInStatement'] = function(stack, state, node) { if (!state.variable_) { state.variable_ = state.value; } - return new Interpreter.State(node['right'], state.scope); + return new Interpreter.State(node["right"], state.scope); } if (!state.isLoop) { // First iteration. @@ -3218,19 +3756,27 @@ Interpreter.prototype['stepForInStatement'] = function(stack, state, node) { while (true) { var prop = state.props_.shift(); if (prop === undefined) { - break; // Reached end of this object's properties. + break; // Reached end of this object's properties. } - if (!Object.prototype.hasOwnProperty.call(state.object_.properties, - prop)) { - continue; // Property has been deleted in the loop. + if ( + !Object.prototype.hasOwnProperty.call( + state.object_.properties, + prop, + ) + ) { + continue; // Property has been deleted in the loop. } if (state.visited_[prop]) { - continue; // Already seen this property on a child. + continue; // Already seen this property on a child. } state.visited_[prop] = true; - if (!Object.prototype.propertyIsEnumerable.call( - state.object_.properties, prop)) { - continue; // Skip non-enumerable property. + if ( + !Object.prototype.propertyIsEnumerable.call( + state.object_.properties, + prop, + ) + ) { + continue; // Skip non-enumerable property. } state.name_ = prop; break gotPropName; @@ -3243,12 +3789,13 @@ Interpreter.prototype['stepForInStatement'] = function(stack, state, node) { while (true) { var prop = state.props_.shift(); if (prop === undefined) { - break; // Reached end of this value's properties. + break; // Reached end of this value's properties. } state.visited_[prop] = true; - if (!Object.prototype.propertyIsEnumerable.call( - state.object_, prop)) { - continue; // Skip non-enumerable property. + if ( + !Object.prototype.propertyIsEnumerable.call(state.object_, prop) + ) { + continue; // Skip non-enumerable property. } state.name_ = prop; break gotPropName; @@ -3266,11 +3813,13 @@ Interpreter.prototype['stepForInStatement'] = function(stack, state, node) { // Fourth, find the variable if (!state.doneVariable_) { state.doneVariable_ = true; - var left = node['left']; - if (left['type'] === 'VariableDeclaration') { + var left = node["left"]; + if (left["type"] === "VariableDeclaration") { // Inline variable declaration: for (var x in y) - state.variable_ = - [Interpreter.SCOPE_REFERENCE, left['declarations'][0]['id']['name']]; + state.variable_ = [ + Interpreter.SCOPE_REFERENCE, + left["declarations"][0]["id"]["name"], + ]; } else { // Arbitrary left side: for (foo().bar in y) state.variable_ = null; @@ -3297,64 +3846,72 @@ Interpreter.prototype['stepForInStatement'] = function(stack, state, node) { state.doneVariable_ = false; state.doneSetter_ = false; // Sixth and finally, execute the body if there was one. this. - if (node['body']) { - return new Interpreter.State(node['body'], state.scope); + if (node["body"]) { + return new Interpreter.State(node["body"], state.scope); } }; -Interpreter.prototype['stepForStatement'] = function(stack, state, node) { +Interpreter.prototype["stepForStatement"] = function (stack, state, node) { var mode = state.mode_ || 0; if (mode === 0) { state.mode_ = 1; - if (node['init']) { - return new Interpreter.State(node['init'], state.scope); + if (node["init"]) { + return new Interpreter.State(node["init"], state.scope); } } else if (mode === 1) { state.mode_ = 2; - if (node['test']) { - return new Interpreter.State(node['test'], state.scope); + if (node["test"]) { + return new Interpreter.State(node["test"], state.scope); } } else if (mode === 2) { state.mode_ = 3; - if (node['test'] && !state.value) { + if (node["test"] && !state.value) { // Done, exit loop. stack.pop(); - } else { // Execute the body. + } else { + // Execute the body. state.isLoop = true; - return new Interpreter.State(node['body'], state.scope); + return new Interpreter.State(node["body"], state.scope); } } else if (mode === 3) { state.mode_ = 1; - if (node['update']) { - return new Interpreter.State(node['update'], state.scope); + if (node["update"]) { + return new Interpreter.State(node["update"], state.scope); } } }; -Interpreter.prototype['stepFunctionDeclaration'] = - function(stack, state, node) { +Interpreter.prototype["stepFunctionDeclaration"] = function ( + stack, + state, + node, +) { // This was found and handled when the scope was populated. stack.pop(); }; -Interpreter.prototype['stepFunctionExpression'] = function(stack, state, node) { +Interpreter.prototype["stepFunctionExpression"] = function ( + stack, + state, + node, +) { stack.pop(); stack[stack.length - 1].value = this.createFunction(node, state.scope); }; -Interpreter.prototype['stepIdentifier'] = function(stack, state, node) { +Interpreter.prototype["stepIdentifier"] = function (stack, state, node) { stack.pop(); if (state.components) { - stack[stack.length - 1].value = [Interpreter.SCOPE_REFERENCE, node['name']]; + stack[stack.length - 1].value = [Interpreter.SCOPE_REFERENCE, node["name"]]; return; } - var value = this.getValueFromScope(node['name'], node); + var value = this.getValueFromScope(node["name"], node); // An identifier could be a getter if it's a property on the global object. - if (value && typeof value === 'object' && value.isGetter) { + if (value && typeof value === "object" && value.isGetter) { // Clear the getter flag and call the getter function. value.isGetter = false; var scope = state.scope; - while (!this.hasProperty(scope, node['name'])) { + while (!this.hasProperty(scope, node["name"])) { scope = scope.parentScope; } var func = /** @type {!Interpreter.Object} */ (value); @@ -3363,23 +3920,23 @@ Interpreter.prototype['stepIdentifier'] = function(stack, state, node) { stack[stack.length - 1].value = value; }; -Interpreter.prototype['stepIfStatement'] = - Interpreter.prototype['stepConditionalExpression']; +Interpreter.prototype["stepIfStatement"] = + Interpreter.prototype["stepConditionalExpression"]; -Interpreter.prototype['stepLabeledStatement'] = function(stack, state, node) { +Interpreter.prototype["stepLabeledStatement"] = function (stack, state, node) { // No need to hit this node again on the way back up the stack. stack.pop(); // Note that a statement might have multiple labels. var labels = state.labels || []; - labels.push(node['label']['name']); - var nextState = new Interpreter.State(node['body'], state.scope); + labels.push(node["label"]["name"]); + var nextState = new Interpreter.State(node["body"], state.scope); nextState.labels = labels; return nextState; }; -Interpreter.prototype['stepLiteral'] = function(stack, state, node) { +Interpreter.prototype["stepLiteral"] = function (stack, state, node) { stack.pop(); - var value = node['value']; + var value = node["value"]; if (value instanceof RegExp) { var pseudoRegexp = this.createObjectProto(this.REGEXP_PROTO); this.populateRegExp(pseudoRegexp, value); @@ -3388,23 +3945,25 @@ Interpreter.prototype['stepLiteral'] = function(stack, state, node) { stack[stack.length - 1].value = value; }; -Interpreter.prototype['stepLogicalExpression'] = function(stack, state, node) { - if (node['operator'] !== '&&' && node['operator'] !== '||') { - throw SyntaxError('Unknown logical operator: ' + node['operator']); +Interpreter.prototype["stepLogicalExpression"] = function (stack, state, node) { + if (node["operator"] !== "&&" && node["operator"] !== "||") { + throw SyntaxError("Unknown logical operator: " + node["operator"]); } if (!state.doneLeft_) { state.doneLeft_ = true; - return new Interpreter.State(node['left'], state.scope); + return new Interpreter.State(node["left"], state.scope); } if (!state.doneRight_) { - if ((node['operator'] === '&&' && !state.value) || - (node['operator'] === '||' && state.value)) { + if ( + (node["operator"] === "&&" && !state.value) || + (node["operator"] === "||" && state.value) + ) { // Shortcut evaluation. stack.pop(); stack[stack.length - 1].value = state.value; } else { state.doneRight_ = true; - return new Interpreter.State(node['right'], state.scope); + return new Interpreter.State(node["right"], state.scope); } } else { stack.pop(); @@ -3412,21 +3971,21 @@ Interpreter.prototype['stepLogicalExpression'] = function(stack, state, node) { } }; -Interpreter.prototype['stepMemberExpression'] = function(stack, state, node) { +Interpreter.prototype["stepMemberExpression"] = function (stack, state, node) { if (!state.doneObject_) { state.doneObject_ = true; - return new Interpreter.State(node['object'], state.scope); + return new Interpreter.State(node["object"], state.scope); } var propName; - if (!node['computed']) { + if (!node["computed"]) { state.object_ = state.value; // obj.foo -- Just access 'foo' directly. - propName = node['property']['name']; + propName = node["property"]["name"]; } else if (!state.doneProperty_) { state.object_ = state.value; // obj[foo] -- Compute value of 'foo'. state.doneProperty_ = true; - return new Interpreter.State(node['property'], state.scope); + return new Interpreter.State(node["property"], state.scope); } else { propName = state.value; } @@ -3435,7 +3994,7 @@ Interpreter.prototype['stepMemberExpression'] = function(stack, state, node) { stack[stack.length - 1].value = [state.object_, propName]; } else { var value = this.getProperty(state.object_, propName); - if (value && typeof value === 'object' && value.isGetter) { + if (value && typeof value === "object" && value.isGetter) { // Clear the getter flag and call the getter function. value.isGetter = false; var func = /** @type {!Interpreter.Object} */ (value); @@ -3445,60 +4004,60 @@ Interpreter.prototype['stepMemberExpression'] = function(stack, state, node) { } }; -Interpreter.prototype['stepNewExpression'] = - Interpreter.prototype['stepCallExpression']; +Interpreter.prototype["stepNewExpression"] = + Interpreter.prototype["stepCallExpression"]; -Interpreter.prototype['stepObjectExpression'] = function(stack, state, node) { +Interpreter.prototype["stepObjectExpression"] = function (stack, state, node) { var n = state.n_ || 0; - var property = node['properties'][n]; + var property = node["properties"][n]; if (!state.object_) { // First execution. state.object_ = this.createObjectProto(this.OBJECT_PROTO); state.properties_ = Object.create(null); } else { // Determine property name. - var key = property['key']; - if (key['type'] === 'Identifier') { - var propName = key['name']; - } else if (key['type'] === 'Literal') { - var propName = key['value']; + var key = property["key"]; + if (key["type"] === "Identifier") { + var propName = key["name"]; + } else if (key["type"] === "Literal") { + var propName = key["value"]; } else { - throw SyntaxError('Unknown object structure: ' + key['type']); + throw SyntaxError("Unknown object structure: " + key["type"]); } // Set the property computed in the previous execution. if (!state.properties_[propName]) { // Create temp object to collect value, getter, and/or setter. state.properties_[propName] = {}; } - state.properties_[propName][property['kind']] = state.value; + state.properties_[propName][property["kind"]] = state.value; state.n_ = ++n; - property = node['properties'][n]; + property = node["properties"][n]; } if (property) { - return new Interpreter.State(property['value'], state.scope); + return new Interpreter.State(property["value"], state.scope); } for (var key in state.properties_) { var kinds = state.properties_[key]; - if ('get' in kinds || 'set' in kinds) { + if ("get" in kinds || "set" in kinds) { // Set a property with a getter or setter. var descriptor = { configurable: true, enumerable: true, - get: kinds['get'], - set: kinds['set'], + get: kinds["get"], + set: kinds["set"], }; this.setProperty(state.object_, key, null, descriptor); } else { // Set a normal property with a value. - this.setProperty(state.object_, key, kinds['init']); + this.setProperty(state.object_, key, kinds["init"]); } } stack.pop(); stack[stack.length - 1].value = state.object_; }; -Interpreter.prototype['stepProgram'] = function(stack, state, node) { - var expression = node['body'].shift(); +Interpreter.prototype["stepProgram"] = function (stack, state, node) { + var expression = node["body"].shift(); if (expression) { state.done = false; return new Interpreter.State(expression, state.scope); @@ -3508,17 +4067,21 @@ Interpreter.prototype['stepProgram'] = function(stack, state, node) { // Leave the root scope on the tree in case the program is appended to. }; -Interpreter.prototype['stepReturnStatement'] = function(stack, state, node) { - if (node['argument'] && !state.done_) { +Interpreter.prototype["stepReturnStatement"] = function (stack, state, node) { + if (node["argument"] && !state.done_) { state.done_ = true; - return new Interpreter.State(node['argument'], state.scope); + return new Interpreter.State(node["argument"], state.scope); } this.unwind(Interpreter.Completion.RETURN, state.value, undefined); }; -Interpreter.prototype['stepSequenceExpression'] = function(stack, state, node) { +Interpreter.prototype["stepSequenceExpression"] = function ( + stack, + state, + node, +) { var n = state.n_ || 0; - var expression = node['expressions'][n]; + var expression = node["expressions"][n]; if (expression) { state.n_ = n + 1; return new Interpreter.State(expression, state.scope); @@ -3527,10 +4090,10 @@ Interpreter.prototype['stepSequenceExpression'] = function(stack, state, node) { stack[stack.length - 1].value = state.value; }; -Interpreter.prototype['stepSwitchStatement'] = function(stack, state, node) { +Interpreter.prototype["stepSwitchStatement"] = function (stack, state, node) { if (!state.test_) { state.test_ = 1; - return new Interpreter.State(node['discriminant'], state.scope); + return new Interpreter.State(node["discriminant"], state.scope); } if (state.test_ === 1) { state.test_ = 2; @@ -3541,8 +4104,8 @@ Interpreter.prototype['stepSwitchStatement'] = function(stack, state, node) { while (true) { var index = state.index_ || 0; - var switchCase = node['cases'][index]; - if (!state.matched_ && switchCase && !switchCase['test']) { + var switchCase = node["cases"][index]; + if (!state.matched_ && switchCase && !switchCase["test"]) { // Test on the default case is null. // Bypass (but store) the default case, and get back to it later. state.defaultCase_ = index; @@ -3556,18 +4119,20 @@ Interpreter.prototype['stepSwitchStatement'] = function(stack, state, node) { continue; } if (switchCase) { - if (!state.matched_ && !state.tested_ && switchCase['test']) { + if (!state.matched_ && !state.tested_ && switchCase["test"]) { state.tested_ = true; - return new Interpreter.State(switchCase['test'], state.scope); + return new Interpreter.State(switchCase["test"], state.scope); } if (state.matched_ || state.value === state.switchValue_) { state.matched_ = true; var n = state.n_ || 0; - if (switchCase['consequent'][n]) { + if (switchCase["consequent"][n]) { state.isSwitch = true; state.n_ = n + 1; - return new Interpreter.State(switchCase['consequent'][n], - state.scope); + return new Interpreter.State( + switchCase["consequent"][n], + state.scope, + ); } } // Move on to next case. @@ -3581,36 +4146,40 @@ Interpreter.prototype['stepSwitchStatement'] = function(stack, state, node) { } }; -Interpreter.prototype['stepThisExpression'] = function(stack, state, node) { +Interpreter.prototype["stepThisExpression"] = function (stack, state, node) { stack.pop(); - stack[stack.length - 1].value = this.getValueFromScope('this', node); + stack[stack.length - 1].value = this.getValueFromScope("this", node); }; -Interpreter.prototype['stepThrowStatement'] = function(stack, state, node) { +Interpreter.prototype["stepThrowStatement"] = function (stack, state, node) { if (!state.done_) { state.done_ = true; - return new Interpreter.State(node['argument'], state.scope); + return new Interpreter.State(node["argument"], state.scope); } else { this.throwException(state.value); } }; -Interpreter.prototype['stepTryStatement'] = function(stack, state, node) { +Interpreter.prototype["stepTryStatement"] = function (stack, state, node) { if (!state.doneBlock_) { state.doneBlock_ = true; - return new Interpreter.State(node['block'], state.scope); + return new Interpreter.State(node["block"], state.scope); } - if (state.cv && state.cv.type === Interpreter.Completion.THROW && - !state.doneHandler_ && node['handler']) { + if ( + state.cv && + state.cv.type === Interpreter.Completion.THROW && + !state.doneHandler_ && + node["handler"] + ) { state.doneHandler_ = true; - var nextState = new Interpreter.State(node['handler'], state.scope); + var nextState = new Interpreter.State(node["handler"], state.scope); nextState.throwValue = state.cv.value; - state.cv = undefined; // This error has been handled, don't rethrow. + state.cv = undefined; // This error has been handled, don't rethrow. return nextState; } - if (!state.doneFinalizer_ && node['finalizer']) { + if (!state.doneFinalizer_ && node["finalizer"]) { state.doneFinalizer_ = true; - return new Interpreter.State(node['finalizer'], state.scope); + return new Interpreter.State(node["finalizer"], state.scope); } stack.pop(); if (state.cv) { @@ -3620,24 +4189,24 @@ Interpreter.prototype['stepTryStatement'] = function(stack, state, node) { } }; -Interpreter.prototype['stepUnaryExpression'] = function(stack, state, node) { +Interpreter.prototype["stepUnaryExpression"] = function (stack, state, node) { if (!state.done_) { state.done_ = true; - var nextState = new Interpreter.State(node['argument'], state.scope); - nextState.components = node['operator'] === 'delete'; + var nextState = new Interpreter.State(node["argument"], state.scope); + nextState.components = node["operator"] === "delete"; return nextState; } stack.pop(); var value = state.value; - if (node['operator'] === '-') { + if (node["operator"] === "-") { value = -value; - } else if (node['operator'] === '+') { + } else if (node["operator"] === "+") { value = +value; - } else if (node['operator'] === '!') { + } else if (node["operator"] === "!") { value = !value; - } else if (node['operator'] === '~') { + } else if (node["operator"] === "~") { value = ~value; - } else if (node['operator'] === 'delete') { + } else if (node["operator"] === "delete") { var result = true; // If value is not an array, then it is a primitive, or some other value. // If so, skip the delete and return true. @@ -3652,28 +4221,30 @@ Interpreter.prototype['stepUnaryExpression'] = function(stack, state, node) { delete obj.properties[name]; } catch (e) { if (state.scope.strict) { - this.throwException(this.TYPE_ERROR, "Cannot delete property '" + - name + "' of '" + obj + "'"); + this.throwException( + this.TYPE_ERROR, + "Cannot delete property '" + name + "' of '" + obj + "'", + ); } else { result = false; } } } value = result; - } else if (node['operator'] === 'typeof') { - value = (value && value.class === 'Function') ? 'function' : typeof value; - } else if (node['operator'] === 'void') { + } else if (node["operator"] === "typeof") { + value = value && value.class === "Function" ? "function" : typeof value; + } else if (node["operator"] === "void") { value = undefined; } else { - throw SyntaxError('Unknown unary operator: ' + node['operator']); + throw SyntaxError("Unknown unary operator: " + node["operator"]); } stack[stack.length - 1].value = value; }; -Interpreter.prototype['stepUpdateExpression'] = function(stack, state, node) { +Interpreter.prototype["stepUpdateExpression"] = function (stack, state, node) { if (!state.doneLeft_) { state.doneLeft_ = true; - var nextState = new Interpreter.State(node['argument'], state.scope); + var nextState = new Interpreter.State(node["argument"], state.scope); nextState.components = true; return nextState; } @@ -3686,7 +4257,7 @@ Interpreter.prototype['stepUpdateExpression'] = function(stack, state, node) { if (!state.doneGetter_) { var leftValue = this.getValue(state.leftSide_, node); state.leftValue_ = leftValue; - if (leftValue && typeof leftValue === 'object' && leftValue.isGetter) { + if (leftValue && typeof leftValue === "object" && leftValue.isGetter) { // Clear the getter flag and call the getter function. leftValue.isGetter = false; state.doneGetter_ = true; @@ -3704,14 +4275,14 @@ Interpreter.prototype['stepUpdateExpression'] = function(stack, state, node) { } var leftValue = Number(state.leftValue_); var changeValue; - if (node['operator'] === '++') { + if (node["operator"] === "++") { changeValue = leftValue + 1; - } else if (node['operator'] === '--') { + } else if (node["operator"] === "--") { changeValue = leftValue - 1; } else { - throw SyntaxError('Unknown update expression: ' + node['operator']); + throw SyntaxError("Unknown update expression: " + node["operator"]); } - var returnValue = node['prefix'] ? changeValue : leftValue; + var returnValue = node["prefix"] ? changeValue : leftValue; var setter = this.setValue(state.leftSide_, changeValue); if (setter) { state.doneSetter_ = true; @@ -3723,66 +4294,72 @@ Interpreter.prototype['stepUpdateExpression'] = function(stack, state, node) { stack[stack.length - 1].value = returnValue; }; -Interpreter.prototype['stepVariableDeclaration'] = function(stack, state, node) { - var declarations = node['declarations']; +Interpreter.prototype["stepVariableDeclaration"] = function ( + stack, + state, + node, +) { + var declarations = node["declarations"]; var n = state.n_ || 0; var declarationNode = declarations[n]; if (state.init_ && declarationNode) { // This setValue call never needs to deal with calling a setter function. // Note that this is setting the init value, not defining the variable. // Variable definition is done when scope is populated. - this.setValueToScope(declarationNode['id']['name'], state.value); + this.setValueToScope(declarationNode["id"]["name"], state.value); state.init_ = false; declarationNode = declarations[++n]; } while (declarationNode) { // Skip any declarations that are not initialized. They have already // been defined as undefined in populateScope_. - if (declarationNode['init']) { + if (declarationNode["init"]) { state.n_ = n; state.init_ = true; - return new Interpreter.State(declarationNode['init'], state.scope); + return new Interpreter.State(declarationNode["init"], state.scope); } declarationNode = declarations[++n]; } stack.pop(); }; -Interpreter.prototype['stepWithStatement'] = function(stack, state, node) { +Interpreter.prototype["stepWithStatement"] = function (stack, state, node) { if (!state.doneObject_) { state.doneObject_ = true; - return new Interpreter.State(node['object'], state.scope); + return new Interpreter.State(node["object"], state.scope); } else if (!state.doneBody_) { state.doneBody_ = true; var scope = this.createSpecialScope(state.scope, state.value); - return new Interpreter.State(node['body'], scope); + return new Interpreter.State(node["body"], scope); } else { stack.pop(); } }; -Interpreter.prototype['stepWhileStatement'] = - Interpreter.prototype['stepDoWhileStatement']; +Interpreter.prototype["stepWhileStatement"] = + Interpreter.prototype["stepDoWhileStatement"]; // Preserve top-level API functions from being pruned/renamed by JS compilers. // Add others as needed. // The global object ('window' in a browser, 'global' in node.js) is 'this'. //this['Interpreter'] = Interpreter; -Interpreter.prototype['step'] = Interpreter.prototype.step; -Interpreter.prototype['run'] = Interpreter.prototype.run; -Interpreter.prototype['appendCode'] = Interpreter.prototype.appendCode; -Interpreter.prototype['createObject'] = Interpreter.prototype.createObject; -Interpreter.prototype['createObjectProto'] = - Interpreter.prototype.createObjectProto; -Interpreter.prototype['createAsyncFunction'] = - Interpreter.prototype.createAsyncFunction; -Interpreter.prototype['createNativeFunction'] = - Interpreter.prototype.createNativeFunction; -Interpreter.prototype['getProperty'] = Interpreter.prototype.getProperty; -Interpreter.prototype['setProperty'] = Interpreter.prototype.setProperty; -Interpreter.prototype['nativeToPseudo'] = Interpreter.prototype.nativeToPseudo; -Interpreter.prototype['pseudoToNative'] = Interpreter.prototype.pseudoToNative; +Interpreter.prototype["step"] = Interpreter.prototype.step; +Interpreter.prototype["run"] = Interpreter.prototype.run; +Interpreter.prototype["appendCode"] = Interpreter.prototype.appendCode; +Interpreter.prototype["createObject"] = Interpreter.prototype.createObject; +Interpreter.prototype["createObjectProto"] = + Interpreter.prototype.createObjectProto; +Interpreter.prototype["createAsyncFunction"] = + Interpreter.prototype.createAsyncFunction; +Interpreter.prototype["createNativeFunction"] = + Interpreter.prototype.createNativeFunction; +Interpreter.prototype["getProperty"] = Interpreter.prototype.getProperty; +Interpreter.prototype["setProperty"] = Interpreter.prototype.setProperty; +Interpreter.prototype["nativeToPseudo"] = Interpreter.prototype.nativeToPseudo; +Interpreter.prototype["pseudoToNative"] = Interpreter.prototype.pseudoToNative; // Obsolete. Do not use. -Interpreter.prototype['createPrimitive'] = function(x) {return x;}; +Interpreter.prototype["createPrimitive"] = function (x) { + return x; +}; -export {Interpreter}; +export { Interpreter }; diff --git a/src/Literature/Literature.ts b/src/Literature/Literature.ts index f86f96e75..3f71aace2 100644 --- a/src/Literature/Literature.ts +++ b/src/Literature/Literature.ts @@ -3,13 +3,13 @@ * These files can be read by the player */ export class Literature { - title: string; - fn: string; - txt: string; + title: string; + fn: string; + txt: string; - constructor(title: string, filename: string, txt: string) { - this.title = title; - this.fn = filename; - this.txt = txt; - } -} \ No newline at end of file + constructor(title: string, filename: string, txt: string) { + this.title = title; + this.fn = filename; + this.txt = txt; + } +} diff --git a/src/Literature/LiteratureHelpers.ts b/src/Literature/LiteratureHelpers.ts index 43cefdc72..f091ec763 100644 --- a/src/Literature/LiteratureHelpers.ts +++ b/src/Literature/LiteratureHelpers.ts @@ -2,8 +2,10 @@ import { Literatures } from "./Literatures"; import { dialogBoxCreate } from "../../utils/DialogBox"; export function showLiterature(fn: string): void { - const litObj = Literatures[fn]; - if (litObj == null) { return; } - const txt = `${litObj.title}

    ${litObj.txt}`; - dialogBoxCreate(txt); + const litObj = Literatures[fn]; + if (litObj == null) { + return; + } + const txt = `${litObj.title}

    ${litObj.txt}`; + dialogBoxCreate(txt); } diff --git a/src/Literature/Literatures.ts b/src/Literature/Literatures.ts index 81a27b554..3cc432c4c 100644 --- a/src/Literature/Literatures.ts +++ b/src/Literature/Literatures.ts @@ -5,414 +5,433 @@ import { IMap } from "../types"; export const Literatures: IMap = {}; (function () { - let title, fn, txt; - title = "The Beginner's Guide to Hacking"; - fn = LiteratureNames.HackersStartingHandbook; - txt = "Some resources:

    " + - "Learn to Program

    " + - "For Experienced JavaScript Developers: NetscriptJS

    " + - "Netscript Documentation

    " + - "When starting out, hacking is the most profitable way to earn money and progress. This " + - "is a brief collection of tips/pointers on how to make the most out of your hacking scripts.

    " + - "-hack() and grow() both work by percentages. hack() steals a certain percentage of the " + - "money on a server, and grow() increases the amount of money on a server by some percentage (multiplicatively)

    " + - "-Because hack() and grow() work by percentages, they are more effective if the target server has a high amount of money. " + - "Therefore, you should try to increase the amount of money on a server (using grow()) to a certain amount before hacking it. Two " + - "import Netscript functions for this are getServerMoneyAvailable() and getServerMaxMoney()

    " + - "-Keep security level low. Security level affects everything when hacking. Two important Netscript functions " + - "for this are getServerSecurityLevel() and getServerMinSecurityLevel()

    " + - "-Purchase additional servers by visiting 'Alpha Enterprises' in the city. They are relatively cheap " + - "and give you valuable RAM to run more scripts early in the game

    " + - "-Prioritize upgrading the RAM on your home computer. This can also be done at 'Alpha Enterprises'

    " + - "-Many low level servers have free RAM. You can use this RAM to run your scripts. Use the scp Terminal or " + - "Netscript command to copy your scripts onto these servers and then run them."; - Literatures[fn] = new Literature(title, fn, txt); + let title, fn, txt; + title = "The Beginner's Guide to Hacking"; + fn = LiteratureNames.HackersStartingHandbook; + txt = + "Some resources:

    " + + "Learn to Program

    " + + "For Experienced JavaScript Developers: NetscriptJS

    " + + "Netscript Documentation

    " + + "When starting out, hacking is the most profitable way to earn money and progress. This " + + "is a brief collection of tips/pointers on how to make the most out of your hacking scripts.

    " + + "-hack() and grow() both work by percentages. hack() steals a certain percentage of the " + + "money on a server, and grow() increases the amount of money on a server by some percentage (multiplicatively)

    " + + "-Because hack() and grow() work by percentages, they are more effective if the target server has a high amount of money. " + + "Therefore, you should try to increase the amount of money on a server (using grow()) to a certain amount before hacking it. Two " + + "import Netscript functions for this are getServerMoneyAvailable() and getServerMaxMoney()

    " + + "-Keep security level low. Security level affects everything when hacking. Two important Netscript functions " + + "for this are getServerSecurityLevel() and getServerMinSecurityLevel()

    " + + "-Purchase additional servers by visiting 'Alpha Enterprises' in the city. They are relatively cheap " + + "and give you valuable RAM to run more scripts early in the game

    " + + "-Prioritize upgrading the RAM on your home computer. This can also be done at 'Alpha Enterprises'

    " + + "-Many low level servers have free RAM. You can use this RAM to run your scripts. Use the scp Terminal or " + + "Netscript command to copy your scripts onto these servers and then run them."; + Literatures[fn] = new Literature(title, fn, txt); - title = "The Complete Handbook for Creating a Successful Corporation"; - fn = LiteratureNames.CorporationManagementHandbook; - txt = "Getting Started with Corporations
    " + - "To get started, visit the City Hall in Sector-12 in order to create a Corporation. This requires " + - "$150b of your own money, but this $150b will get put into your Corporation's funds. " + - "After creating your Corporation, you will see it listed as one of the locations in the city. Click on " + - "your Corporation in order to manage it.

    " + - "Your Corporation can have many different divisions, each in a different Industry. There are many different " + - "types of Industries, each with different properties. To create your first division, click the " + - "'Expand into new Industry' button at the top of the management UI. The Agriculture " + - "and Software industries are recommended for your first division.

    " + - "The first thing you'll need to do is hire some employees. Employees can be assigned to five different positions. " + - "Each position has a different effect on various aspects of your Corporation. It is recommended to have at least " + - "one employee at each position.

    " + - "Each industry uses some combination of Materials in order to produce other Materials and/or create Products. " + - "Specific information about this is displayed in each of your divisions' UI.

    " + - "Products are special, industry-specific objects. They are different than Materials because you " + - "must manually choose to develop them, and you can choose to develop any number of Products. Developing " + - "a Product takes time, but a Product typically generates significantly more revenue than any Material. " + - "Not all industries allow you to create Products. To create a Product, look for a button " + - "in the top-left panel of the division UI (e.g. For the Software Industry, the button says 'Develop Software').

    " + - "To get your supply chain system started, " + - "purchase the Materials that your industry needs to produce other Materials/Products. This can be done " + - "by clicking the 'Buy' button next to the corresponding Material(s). After you have the required Materials, " + - "you will immediately start production. The amount of Materials/Products you produce is based on a variety of factors, " + - "one of which is your employees and their productivity.

    " + - "Once you start producing Materials/Products, you can sell them in order to start earning revenue. This can be done " + - "by clicking the 'Sell' button next to the corresponding Material or Product. The amount of Material/Product you sell is dependent " + - "on a wide variety of different factors.

    " + - "These are the basics of getting your Corporation up and running! Now, you can start purchasing upgrades to improve " + - "your bottom line. If you need money, consider looking for seed investors, who will give you money in exchange for stock shares. " + - "Otherwise, once you feel you are ready, take your Corporation public! Once your Corporation goes public, you can no longer " + - "find investors. Instead, your Corporation will be publicly traded and its stock price will change based on how well " + - "it's performing financially. You can then sell your stock shares in order to make money.

    " + - "Tips/Pointers
    " + - "-The 'Smart Supply' upgrade is extremely useful. Consider purchasing it as soon as possible.

    " + - "-Purchasing Hardware, Robots, AI Cores, and Real Estate can potentially increase your production. " + - "The effects of these depend on what industry you are in.

    " + - "-In order to optimize your production, you will need a good balance of Operators, Managers, and Engineers

    " + - "-Different employees excel in different jobs. For example, the highly intelligent employees will probably do best " + - "if they are assigned to do Engineering work or Research & Development.

    " + - "-If your employees have low morale, energy, or happiness, their production will greatly suffer.

    " + - "-Tech is important, but don't neglect sales! Having several Businessmen can boost your sales and your bottom line.

    " + - "-Don't forget to advertise your company. You won't have any business if nobody knows you.

    " + - "-Having company awareness is great, but what's really important is your company's popularity. Try to keep " + - "your popularity as high as possible to see the biggest benefit for your sales

    " + - "-Remember, you need to spend money to make money!

    " + - "-Corporations do not reset when installing Augmentations, but they do reset when destroying a BitNode"; - Literatures[fn] = new Literature(title, fn, txt); + title = "The Complete Handbook for Creating a Successful Corporation"; + fn = LiteratureNames.CorporationManagementHandbook; + txt = + "Getting Started with Corporations
    " + + "To get started, visit the City Hall in Sector-12 in order to create a Corporation. This requires " + + "$150b of your own money, but this $150b will get put into your Corporation's funds. " + + "After creating your Corporation, you will see it listed as one of the locations in the city. Click on " + + "your Corporation in order to manage it.

    " + + "Your Corporation can have many different divisions, each in a different Industry. There are many different " + + "types of Industries, each with different properties. To create your first division, click the " + + "'Expand into new Industry' button at the top of the management UI. The Agriculture " + + "and Software industries are recommended for your first division.

    " + + "The first thing you'll need to do is hire some employees. Employees can be assigned to five different positions. " + + "Each position has a different effect on various aspects of your Corporation. It is recommended to have at least " + + "one employee at each position.

    " + + "Each industry uses some combination of Materials in order to produce other Materials and/or create Products. " + + "Specific information about this is displayed in each of your divisions' UI.

    " + + "Products are special, industry-specific objects. They are different than Materials because you " + + "must manually choose to develop them, and you can choose to develop any number of Products. Developing " + + "a Product takes time, but a Product typically generates significantly more revenue than any Material. " + + "Not all industries allow you to create Products. To create a Product, look for a button " + + "in the top-left panel of the division UI (e.g. For the Software Industry, the button says 'Develop Software').

    " + + "To get your supply chain system started, " + + "purchase the Materials that your industry needs to produce other Materials/Products. This can be done " + + "by clicking the 'Buy' button next to the corresponding Material(s). After you have the required Materials, " + + "you will immediately start production. The amount of Materials/Products you produce is based on a variety of factors, " + + "one of which is your employees and their productivity.

    " + + "Once you start producing Materials/Products, you can sell them in order to start earning revenue. This can be done " + + "by clicking the 'Sell' button next to the corresponding Material or Product. The amount of Material/Product you sell is dependent " + + "on a wide variety of different factors.

    " + + "These are the basics of getting your Corporation up and running! Now, you can start purchasing upgrades to improve " + + "your bottom line. If you need money, consider looking for seed investors, who will give you money in exchange for stock shares. " + + "Otherwise, once you feel you are ready, take your Corporation public! Once your Corporation goes public, you can no longer " + + "find investors. Instead, your Corporation will be publicly traded and its stock price will change based on how well " + + "it's performing financially. You can then sell your stock shares in order to make money.

    " + + "Tips/Pointers
    " + + "-The 'Smart Supply' upgrade is extremely useful. Consider purchasing it as soon as possible.

    " + + "-Purchasing Hardware, Robots, AI Cores, and Real Estate can potentially increase your production. " + + "The effects of these depend on what industry you are in.

    " + + "-In order to optimize your production, you will need a good balance of Operators, Managers, and Engineers

    " + + "-Different employees excel in different jobs. For example, the highly intelligent employees will probably do best " + + "if they are assigned to do Engineering work or Research & Development.

    " + + "-If your employees have low morale, energy, or happiness, their production will greatly suffer.

    " + + "-Tech is important, but don't neglect sales! Having several Businessmen can boost your sales and your bottom line.

    " + + "-Don't forget to advertise your company. You won't have any business if nobody knows you.

    " + + "-Having company awareness is great, but what's really important is your company's popularity. Try to keep " + + "your popularity as high as possible to see the biggest benefit for your sales

    " + + "-Remember, you need to spend money to make money!

    " + + "-Corporations do not reset when installing Augmentations, but they do reset when destroying a BitNode"; + Literatures[fn] = new Literature(title, fn, txt); - title = "A Brief History of Synthoids"; - fn = LiteratureNames.HistoryOfSynthoids; - txt = "Synthetic androids, or Synthoids for short, are genetically engineered robots and, short of Augmentations, " + - "are composed entirely of organic substances. For this reason, Synthoids are virtually identical to " + - "humans in form, composition, and appearance.

    " + - "Synthoids were first designed and manufactured by OmniTek Incorporated sometime around the middle of the century. " + - "Their original purpose was to be used for manual labor and as emergency responders for disasters. As such, they " + - "were initially programmed only for their specific tasks. Each iteration that followed improved upon the " + - "intelligence and capabilities of the Synthoids. By the 6th iteration, called MK-VI, the Synthoids were " + - "so smart and capable enough of making their own decisions that many argued OmniTek had created the first " + - "sentient AI. These MK-VI Synthoids were produced in mass quantities (estimates up to 50 billion) with the hopes of increasing society's " + - "productivity and bolstering the global economy. Stemming from humanity's desire for technological advancement, optimism " + - "and excitement about the future had never been higher.

    " + - "All of that excitement and optimism quickly turned to fear, panic, and dread in 2070, when a terrorist group " + - "called Ascendis Totalis hacked into OmniTek and uploaded a rogue AI into severeal of their Synthoid manufacturing facilities. " + - "This hack went undetected and for months OmniTek unknowingly churned out legions of Synthoids embedded with this " + - "rogue AI. Then, on December 24th, 2070, Omnica activated dormant protocols in the rogue AI, causing all of the " + - "infected Synthoids to immediately launch a military campaign to seek and destroy all of humanity.

    " + - "What ensued was the deadlist conflict in human history. This crisis, now commonly known as the Synthoid Uprising, " + - "resulted in almost ten billion deaths over the course of a year. Despite the nations of the world banding together " + - "to combat the threat, the MK-VI Synthoids were simply stronger, faster, more intelligent, and more adaptable than humans, " + - "outsmarting them at every turn.

    " + - "It wasn't until the sacrifice of an elite international military taskforce, called the Bladeburners, that humanity " + - "was finally able to defeat the Synthoids. The Bladeburners' final act was a suicide bombing mission that " + - "destroyed a large portion of the MK-VI Synthoids, including many of its leaders. In the following " + - "weeks militaries from around the world were able to round up and shut down the remaining rogue MK-VI Synthoids, ending " + - "the Synthoid Uprising.

    " + - "In the aftermath of the bloodshed, the Synthoid Accords were drawn up. These Accords banned OmniTek Incorporated " + - "from manufacturing any Synthoids beyond the MK-III series. They also banned any other corporation " + - "from constructing androids with advanced, near-sentient AI. MK-VI Synthoids that did not have the rogue Ascendis Totalis " + - "AI were allowed to continue their existence, but they were stripped of all rights and protections as they " + - "were not considered humans. They were also banned from doing anything that may pose a global security threat, such " + - "as working for any military/defense organization or conducting any bioengineering, computing, or robotics related research.

    " + - "Unfortunately, many believe that not all of the rogue MK-VI Synthoids from the Uprising were found and destroyed, " + - "and that many of them are blending in as normal humans in society today. In response, many nations have created " + - "Bladeburner divisions, special military branches that are tasked with investigating and dealing with any Synthoid threads.

    " + - "To this day, tensions still exist between the remaining Synthoids and humans as a result of the Uprising.

    " + - "Nobody knows what happened to the terrorist group Ascendis Totalis."; - Literatures[fn] = new Literature(title, fn, txt); + title = "A Brief History of Synthoids"; + fn = LiteratureNames.HistoryOfSynthoids; + txt = + "Synthetic androids, or Synthoids for short, are genetically engineered robots and, short of Augmentations, " + + "are composed entirely of organic substances. For this reason, Synthoids are virtually identical to " + + "humans in form, composition, and appearance.

    " + + "Synthoids were first designed and manufactured by OmniTek Incorporated sometime around the middle of the century. " + + "Their original purpose was to be used for manual labor and as emergency responders for disasters. As such, they " + + "were initially programmed only for their specific tasks. Each iteration that followed improved upon the " + + "intelligence and capabilities of the Synthoids. By the 6th iteration, called MK-VI, the Synthoids were " + + "so smart and capable enough of making their own decisions that many argued OmniTek had created the first " + + "sentient AI. These MK-VI Synthoids were produced in mass quantities (estimates up to 50 billion) with the hopes of increasing society's " + + "productivity and bolstering the global economy. Stemming from humanity's desire for technological advancement, optimism " + + "and excitement about the future had never been higher.

    " + + "All of that excitement and optimism quickly turned to fear, panic, and dread in 2070, when a terrorist group " + + "called Ascendis Totalis hacked into OmniTek and uploaded a rogue AI into severeal of their Synthoid manufacturing facilities. " + + "This hack went undetected and for months OmniTek unknowingly churned out legions of Synthoids embedded with this " + + "rogue AI. Then, on December 24th, 2070, Omnica activated dormant protocols in the rogue AI, causing all of the " + + "infected Synthoids to immediately launch a military campaign to seek and destroy all of humanity.

    " + + "What ensued was the deadlist conflict in human history. This crisis, now commonly known as the Synthoid Uprising, " + + "resulted in almost ten billion deaths over the course of a year. Despite the nations of the world banding together " + + "to combat the threat, the MK-VI Synthoids were simply stronger, faster, more intelligent, and more adaptable than humans, " + + "outsmarting them at every turn.

    " + + "It wasn't until the sacrifice of an elite international military taskforce, called the Bladeburners, that humanity " + + "was finally able to defeat the Synthoids. The Bladeburners' final act was a suicide bombing mission that " + + "destroyed a large portion of the MK-VI Synthoids, including many of its leaders. In the following " + + "weeks militaries from around the world were able to round up and shut down the remaining rogue MK-VI Synthoids, ending " + + "the Synthoid Uprising.

    " + + "In the aftermath of the bloodshed, the Synthoid Accords were drawn up. These Accords banned OmniTek Incorporated " + + "from manufacturing any Synthoids beyond the MK-III series. They also banned any other corporation " + + "from constructing androids with advanced, near-sentient AI. MK-VI Synthoids that did not have the rogue Ascendis Totalis " + + "AI were allowed to continue their existence, but they were stripped of all rights and protections as they " + + "were not considered humans. They were also banned from doing anything that may pose a global security threat, such " + + "as working for any military/defense organization or conducting any bioengineering, computing, or robotics related research.

    " + + "Unfortunately, many believe that not all of the rogue MK-VI Synthoids from the Uprising were found and destroyed, " + + "and that many of them are blending in as normal humans in society today. In response, many nations have created " + + "Bladeburner divisions, special military branches that are tasked with investigating and dealing with any Synthoid threads.

    " + + "To this day, tensions still exist between the remaining Synthoids and humans as a result of the Uprising.

    " + + "Nobody knows what happened to the terrorist group Ascendis Totalis."; + Literatures[fn] = new Literature(title, fn, txt); - title = "A Green Tomorrow"; - fn = LiteratureNames.AGreenTomorrow; - txt = "Starting a few decades ago, there was a massive global movement towards the generation of renewable energy in an effort to " + - "combat global warming and climate change. The shift towards renewable energy was a big success, or so it seemed. In 2045 " + - "a staggering 80% of the world's energy came from non-renewable fossil fuels. Now, about three decades later, that " + - "number is down to only 15%. Most of the world's energy now comes from nuclear power and renewable sources such as " + - "solar and geothermal energy. Unfortunately, these efforts were not the huge success that they seem to be.

    " + - "Since 2045 primary energy use has soared almost tenfold. This was mainly due to growing urban populations and " + - "the rise of increasingly advanced (and power-hungry) technology that has become ubiquitous in our lives. So, " + - "despite the fact that the percentage of our energy that comes from fossil fuels has drastically decreased, " + - "the total amount of energy we are producing from fossil fuels has actually increased.

    " + - "The grim effects of our species' irresponsible use of energy and neglect of our mother world have become increasingly apparent. " + - "Last year a temperature of 190F was recorded in the Death Valley desert, which is over 50% higher than the highest " + - "recorded temperature at the beginning of the century. In the last two decades numerous major cities such as Manhattan, Boston, and " + - "Los Angeles have been partially or fully submerged by rising sea levels. In the present day, over 75% of the world's agriculture is " + - "done in climate-controlled vertical farms, as most traditional farmland has become unusable due to severe climate conditions.

    " + - "Despite all of this, the greedy and corrupt corporations that rule the world have done nothing to address these problems that " + - "threaten our species. And so it's up to us, the common people. Each and every one of us can make a difference by doing what " + - "these corporations won't: taking responsibility. If we don't, pretty soon there won't be an Earth left to save. We are " + - "the last hope for a green tomorrow."; - Literatures[fn] = new Literature(title, fn, txt); + title = "A Green Tomorrow"; + fn = LiteratureNames.AGreenTomorrow; + txt = + "Starting a few decades ago, there was a massive global movement towards the generation of renewable energy in an effort to " + + "combat global warming and climate change. The shift towards renewable energy was a big success, or so it seemed. In 2045 " + + "a staggering 80% of the world's energy came from non-renewable fossil fuels. Now, about three decades later, that " + + "number is down to only 15%. Most of the world's energy now comes from nuclear power and renewable sources such as " + + "solar and geothermal energy. Unfortunately, these efforts were not the huge success that they seem to be.

    " + + "Since 2045 primary energy use has soared almost tenfold. This was mainly due to growing urban populations and " + + "the rise of increasingly advanced (and power-hungry) technology that has become ubiquitous in our lives. So, " + + "despite the fact that the percentage of our energy that comes from fossil fuels has drastically decreased, " + + "the total amount of energy we are producing from fossil fuels has actually increased.

    " + + "The grim effects of our species' irresponsible use of energy and neglect of our mother world have become increasingly apparent. " + + "Last year a temperature of 190F was recorded in the Death Valley desert, which is over 50% higher than the highest " + + "recorded temperature at the beginning of the century. In the last two decades numerous major cities such as Manhattan, Boston, and " + + "Los Angeles have been partially or fully submerged by rising sea levels. In the present day, over 75% of the world's agriculture is " + + "done in climate-controlled vertical farms, as most traditional farmland has become unusable due to severe climate conditions.

    " + + "Despite all of this, the greedy and corrupt corporations that rule the world have done nothing to address these problems that " + + "threaten our species. And so it's up to us, the common people. Each and every one of us can make a difference by doing what " + + "these corporations won't: taking responsibility. If we don't, pretty soon there won't be an Earth left to save. We are " + + "the last hope for a green tomorrow."; + Literatures[fn] = new Literature(title, fn, txt); - title = "Alpha and Omega"; - fn = LiteratureNames.AlphaOmega; - txt = "Then we saw a new Heaven and a new Earth, for our first Heaven and Earth had gone away, and our sea was no more. " + - "And we saw a new holy city, new Aeria, coming down out of this new Heaven, prepared as a bride adorned for her husband. " + - "And we heard a loud voice saying, 'Behold, the new dwelling place of the Gods. We will dwell with them, and they " + - "will be our people, and we will be with them as their Gods. We will wipe away every tear from their eyes, and death " + - "shall be no more, neither shall there be mourning, nor crying, nor pain anymore, for the former things " + - "have passed away.'

    " + - "And once we were seated on the throne we said 'Behold, I am making all things new.' " + - "Also we said, 'Write this down, for these words are trustworthy and true.' And we said to you, " + - "'It is done! I am the Alpha and the Omega, the beginning and the end. To the thirsty I will give from the spring " + - "of the water of life without payment. The one who conquers will have this heritage, and we will be his God and " + - "he will be our son. But as for the cowardly, the faithless, the detestable, as for murderers, " + - "the sexually immoral, sorcerers, idolaters, and all liars, their portion will be in the lake that " + - "burns with fire and sulfur, for it is the second true death.'"; - Literatures[fn] = new Literature(title, fn, txt); + title = "Alpha and Omega"; + fn = LiteratureNames.AlphaOmega; + txt = + "Then we saw a new Heaven and a new Earth, for our first Heaven and Earth had gone away, and our sea was no more. " + + "And we saw a new holy city, new Aeria, coming down out of this new Heaven, prepared as a bride adorned for her husband. " + + "And we heard a loud voice saying, 'Behold, the new dwelling place of the Gods. We will dwell with them, and they " + + "will be our people, and we will be with them as their Gods. We will wipe away every tear from their eyes, and death " + + "shall be no more, neither shall there be mourning, nor crying, nor pain anymore, for the former things " + + "have passed away.'

    " + + "And once we were seated on the throne we said 'Behold, I am making all things new.' " + + "Also we said, 'Write this down, for these words are trustworthy and true.' And we said to you, " + + "'It is done! I am the Alpha and the Omega, the beginning and the end. To the thirsty I will give from the spring " + + "of the water of life without payment. The one who conquers will have this heritage, and we will be his God and " + + "he will be our son. But as for the cowardly, the faithless, the detestable, as for murderers, " + + "the sexually immoral, sorcerers, idolaters, and all liars, their portion will be in the lake that " + + "burns with fire and sulfur, for it is the second true death.'"; + Literatures[fn] = new Literature(title, fn, txt); - title = "Are We Living in a Computer Simulation?"; - fn = LiteratureNames.SimulatedReality; - txt = "The idea that we are living in a virtual world is not new. It's a trope that has " + - "been explored constantly in literature and pop culture. However, it is also a legitimate " + - "scientific hypothesis that many notable physicists and philosophers have debated for years.

    " + - "Proponents for this simulated reality theory often point to how advanced our technology has become, " + - "as well as the incredibly fast pace at which it has advanced over the past decades. The amount of computing " + - "power available to us has increased over 100-fold since 2060 due to the development of nanoprocessors and " + - "quantum computers. Artifical Intelligence has advanced to the point where our entire lives are controlled " + - "by robots and machines that handle our day-to-day activities such as autonomous transportation and scheduling. " + - "If we consider the pace at which this technology has advanced and assume that these developments continue, it's " + - "reasonable to assume that at some point in the future our technology would be advanced enough that " + - "we could create simulations that are indistinguishable from reality. However, if continued technological advancement " + - "is a reasonable outcome, then it is very likely that such a scenario has already happened.

    " + - "Statistically speaking, somewhere out there in the infinite universe there is an advanced, intelligent species " + - "that already has such technology. Who's to say that they haven't already created such a virtual reality: our own?"; - Literatures[fn] = new Literature(title, fn, txt); + title = "Are We Living in a Computer Simulation?"; + fn = LiteratureNames.SimulatedReality; + txt = + "The idea that we are living in a virtual world is not new. It's a trope that has " + + "been explored constantly in literature and pop culture. However, it is also a legitimate " + + "scientific hypothesis that many notable physicists and philosophers have debated for years.

    " + + "Proponents for this simulated reality theory often point to how advanced our technology has become, " + + "as well as the incredibly fast pace at which it has advanced over the past decades. The amount of computing " + + "power available to us has increased over 100-fold since 2060 due to the development of nanoprocessors and " + + "quantum computers. Artifical Intelligence has advanced to the point where our entire lives are controlled " + + "by robots and machines that handle our day-to-day activities such as autonomous transportation and scheduling. " + + "If we consider the pace at which this technology has advanced and assume that these developments continue, it's " + + "reasonable to assume that at some point in the future our technology would be advanced enough that " + + "we could create simulations that are indistinguishable from reality. However, if continued technological advancement " + + "is a reasonable outcome, then it is very likely that such a scenario has already happened.

    " + + "Statistically speaking, somewhere out there in the infinite universe there is an advanced, intelligent species " + + "that already has such technology. Who's to say that they haven't already created such a virtual reality: our own?"; + Literatures[fn] = new Literature(title, fn, txt); - title = "Beyond Man"; - fn = LiteratureNames.BeyondMan; - txt = "Humanity entered a 'transhuman' era a long time ago. And despite the protests and criticisms of many who cried out against " + - "human augmentation at the time, the transhuman movement continued and prospered. Proponents of the movement ignored the critics, " + - "arguing that it was in our inherent nature to better ourselves. To improve. To be more than we were. They claimed that " + - "not doing so would be to go against every living organism's biological purpose: evolution and survival of the fittest.

    " + - "And here we are today, with technology that is advanced enough to augment humans to a state that " + - "can only be described as posthuman. But what do we have to show for it when this augmentation " + - "technology is only available to the so-called 'elite'? Are we really better off than before when only 5% of the " + - "world's population has access to this technology? When the powerful corporations and organizations of the world " + - "keep it all to themselves, have we really evolved?

    " + - "Augmentation technology has only further increased the divide between the rich and the poor, between the powerful and " + - "the oppressed. We have not become 'more than human'. We have not evolved from nature's original design. We are still the greedy, " + - "corrupted, and evil men that we always were."; - Literatures[fn] = new Literature(title, fn, txt); + title = "Beyond Man"; + fn = LiteratureNames.BeyondMan; + txt = + "Humanity entered a 'transhuman' era a long time ago. And despite the protests and criticisms of many who cried out against " + + "human augmentation at the time, the transhuman movement continued and prospered. Proponents of the movement ignored the critics, " + + "arguing that it was in our inherent nature to better ourselves. To improve. To be more than we were. They claimed that " + + "not doing so would be to go against every living organism's biological purpose: evolution and survival of the fittest.

    " + + "And here we are today, with technology that is advanced enough to augment humans to a state that " + + "can only be described as posthuman. But what do we have to show for it when this augmentation " + + "technology is only available to the so-called 'elite'? Are we really better off than before when only 5% of the " + + "world's population has access to this technology? When the powerful corporations and organizations of the world " + + "keep it all to themselves, have we really evolved?

    " + + "Augmentation technology has only further increased the divide between the rich and the poor, between the powerful and " + + "the oppressed. We have not become 'more than human'. We have not evolved from nature's original design. We are still the greedy, " + + "corrupted, and evil men that we always were."; + Literatures[fn] = new Literature(title, fn, txt); + title = "Brighter than the Sun"; + fn = LiteratureNames.BrighterThanTheSun; + txt = + "When people think about the corporations that dominate the East, they typically think of KuaiGong International, which " + + "holds a complete monopoly for manufacturing and commerce in Asia, or Global Pharmaceuticals, the world's largest " + + "drug company, or OmniTek Incorporated, the global leader in intelligent and autonomous robots. But there's one company " + + "that has seen a rapid rise in the last year and is poised to dominate not only the East, but the entire world: TaiYang Digital.

    " + + "TaiYang Digital is a Chinese internet-technology corporation that provides services such as " + + "online advertising, search engines, gaming, media, entertainment, and cloud computing/storage. Its name TaiYang comes from the Chinese word " + + "for 'sun'. In Chinese culture, the sun is a 'yang' symbol " + + "associated with life, heat, masculinity, and heaven.

    " + + "The company was founded " + + "less than 5 years ago and is already the third highest valued company in all of Asia. In 2076 it generated a total revenue of " + + "over 10 trillion yuan. It's services are used daily by over a billion people worldwide.

    " + + "TaiYang Digital's meteoric rise is extremely surprising in modern society. This sort of growth is " + + "something you'd commonly see in the first half of the century, especially for tech companies. However in " + + "the last two decades the number of corporations has significantly declined as the largest entities " + + "quickly took over the economy. Corporations such as ECorp, MegaCorp, and KuaiGong have established " + + "such strong monopolies in their market sectors that they have effectively killed off all " + + "of the smaller and new corporations that have tried to start up over the years. This is what makes " + + "the rise of TaiYang Digital so impressive. And if TaiYang continues down this path, then they have " + + "a bright future ahead of them."; + Literatures[fn] = new Literature(title, fn, txt); - title = "Brighter than the Sun"; - fn = LiteratureNames.BrighterThanTheSun; - txt = "When people think about the corporations that dominate the East, they typically think of KuaiGong International, which " + - "holds a complete monopoly for manufacturing and commerce in Asia, or Global Pharmaceuticals, the world's largest " + - "drug company, or OmniTek Incorporated, the global leader in intelligent and autonomous robots. But there's one company " + - "that has seen a rapid rise in the last year and is poised to dominate not only the East, but the entire world: TaiYang Digital.

    " + - "TaiYang Digital is a Chinese internet-technology corporation that provides services such as " + - "online advertising, search engines, gaming, media, entertainment, and cloud computing/storage. Its name TaiYang comes from the Chinese word " + - "for 'sun'. In Chinese culture, the sun is a 'yang' symbol " + - "associated with life, heat, masculinity, and heaven.

    " + - "The company was founded " + - "less than 5 years ago and is already the third highest valued company in all of Asia. In 2076 it generated a total revenue of " + - "over 10 trillion yuan. It's services are used daily by over a billion people worldwide.

    " + - "TaiYang Digital's meteoric rise is extremely surprising in modern society. This sort of growth is " + - "something you'd commonly see in the first half of the century, especially for tech companies. However in " + - "the last two decades the number of corporations has significantly declined as the largest entities " + - "quickly took over the economy. Corporations such as ECorp, MegaCorp, and KuaiGong have established " + - "such strong monopolies in their market sectors that they have effectively killed off all " + - "of the smaller and new corporations that have tried to start up over the years. This is what makes " + - "the rise of TaiYang Digital so impressive. And if TaiYang continues down this path, then they have " + - "a bright future ahead of them."; - Literatures[fn] = new Literature(title, fn, txt); + title = "Democracy is Dead: The Fall of an Empire"; + fn = LiteratureNames.DemocracyIsDead; + txt = + "They rose from the shadows in the street.
    From the places where the oppressed meet.
    " + + "Their cries echoed loudly through the air.
    As they once did in Tiananmen Square.
    " + + "Loudness in the silence, Darkness in the light.
    They came forth with power and might.
    " + + "Once the beacon of democracy, America was first.
    Its pillars of society destroyed and dispersed.
    " + + "Soon the cries rose everywhere, with revolt and riot.
    Until one day, finally, all was quiet.
    " + + "From the ashes rose a new order, corporatocracy was its name.
    " + + "Rome, Mongol, Byzantine, all of history is just the same.
    " + + "For man will never change in a fundamental way.
    " + + "And now democracy is dead, in the USA."; + Literatures[fn] = new Literature(title, fn, txt); - title = "Democracy is Dead: The Fall of an Empire"; - fn = LiteratureNames.DemocracyIsDead; - txt = "They rose from the shadows in the street.
    From the places where the oppressed meet.
    " + - "Their cries echoed loudly through the air.
    As they once did in Tiananmen Square.
    " + - "Loudness in the silence, Darkness in the light.
    They came forth with power and might.
    " + - "Once the beacon of democracy, America was first.
    Its pillars of society destroyed and dispersed.
    " + - "Soon the cries rose everywhere, with revolt and riot.
    Until one day, finally, all was quiet.
    " + - "From the ashes rose a new order, corporatocracy was its name.
    " + - "Rome, Mongol, Byzantine, all of history is just the same.
    " + - "For man will never change in a fundamental way.
    " + - "And now democracy is dead, in the USA."; - Literatures[fn] = new Literature(title, fn, txt); + title = "Figures Show Rising Crime Rates in Sector-12"; + fn = LiteratureNames.Sector12Crime; + txt = + "A recent study by analytics company Wilson Inc. shows a significant rise " + + "in criminal activity in Sector-12. Perhaps the most alarming part of the statistic " + + "is that most of the rise is in violent crime such as homicide and assault. According " + + "to the study, the city saw a total of 21,406 reported homicides in 2076, which is over " + + "a 20% increase compared to 2075.

    " + + "CIA director David Glarow says its too early to know " + + "whether these figures indicate the beginning of a sustained increase in crime rates, or whether " + + "the year was just an unfortunate outlier. He states that many intelligence and law enforcement " + + "agents have noticed an increase in organized crime activites, and believes that these figures may " + + "be the result of an uprising from criminal organizations such as The Syndicate or the Slum Snakes."; + Literatures[fn] = new Literature(title, fn, txt); - title = "Figures Show Rising Crime Rates in Sector-12"; - fn = LiteratureNames.Sector12Crime; - txt = "A recent study by analytics company Wilson Inc. shows a significant rise " + - "in criminal activity in Sector-12. Perhaps the most alarming part of the statistic " + - "is that most of the rise is in violent crime such as homicide and assault. According " + - "to the study, the city saw a total of 21,406 reported homicides in 2076, which is over " + - "a 20% increase compared to 2075.

    " + - "CIA director David Glarow says its too early to know " + - "whether these figures indicate the beginning of a sustained increase in crime rates, or whether " + - "the year was just an unfortunate outlier. He states that many intelligence and law enforcement " + - "agents have noticed an increase in organized crime activites, and believes that these figures may " + - "be the result of an uprising from criminal organizations such as The Syndicate or the Slum Snakes."; - Literatures[fn] = new Literature(title, fn, txt); + title = "Man and the Machine"; + fn = LiteratureNames.ManAndMachine; + txt = + "In 2005 Ray Kurzweil popularized his theory of the Singularity. He predicted that the rate " + + "of technological advancement would continue to accelerate faster and faster until one day " + + "machines would be become infinitely more intelligent than humans. This point, called the " + + "Singularity, would result in a drastic transformation of the world as we know it. He predicted " + + "that the Singularity would arrive by 2045. " + + "And yet here we are, more than three decades later, where most would agree that we have not " + + "yet reached a point where computers and machines are vastly more intelligent than we are. So what gives?

    " + + "The answer is that we have reached the Singularity, just not in the way we expected. The artifical superintelligence " + + "that was predicted by Kurzweil and others exists in the world today - in the form of Augmentations. " + + "Yes, those Augmentations that the rich and powerful keep to themselves enable humans " + + "to become superintelligent beings. The Singularity did not lead to a world where " + + "our machines are infinitely more intelligent than us, it led to a world " + + "where man and machine can merge to become something greater. Most of the world just doesn't " + + "know it yet."; + Literatures[fn] = new Literature(title, fn, txt); - title = "Man and the Machine"; - fn = LiteratureNames.ManAndMachine; - txt = "In 2005 Ray Kurzweil popularized his theory of the Singularity. He predicted that the rate " + - "of technological advancement would continue to accelerate faster and faster until one day " + - "machines would be become infinitely more intelligent than humans. This point, called the " + - "Singularity, would result in a drastic transformation of the world as we know it. He predicted " + - "that the Singularity would arrive by 2045. " + - "And yet here we are, more than three decades later, where most would agree that we have not " + - "yet reached a point where computers and machines are vastly more intelligent than we are. So what gives?

    " + - "The answer is that we have reached the Singularity, just not in the way we expected. The artifical superintelligence " + - "that was predicted by Kurzweil and others exists in the world today - in the form of Augmentations. " + - "Yes, those Augmentations that the rich and powerful keep to themselves enable humans " + - "to become superintelligent beings. The Singularity did not lead to a world where " + - "our machines are infinitely more intelligent than us, it led to a world " + - "where man and machine can merge to become something greater. Most of the world just doesn't " + - "know it yet." - Literatures[fn] = new Literature(title, fn, txt); + title = "Secret Societies"; + fn = LiteratureNames.SecretSocieties; + txt = + "The idea of secret societies has long intrigued the general public by inspiring curiosity, fascination, and " + + "distrust. People have long wondered about who these secret society members are and what they do, with the " + + "most radical of conspiracy theorists claiming that they control everything in the entire world. And while the world " + + "may never know for sure, it is likely that many secret societies do actually exist, even today.

    " + + "However, the secret societies of the modern world are nothing like those that (supposedly) existed " + + "decades and centuries ago. The Freemasons, Knights Templar, and Illuminati, while they may have been around " + + "at the turn of the 21st century, almost assuredly do not exist today. The dominance of the Web in " + + "our everyday lives and the fact that so much of the world is now digital has given rise to a new breed " + + "of secret societies: Internet-based ones.

    " + + "Commonly called 'hacker groups', Internet-based secret societies have become well-known in today's " + + "world. Some of these, such as The Black Hand, are black hat groups that claim they are trying to " + + "help the oppressed by attacking the elite and powerful. Others, such as NiteSec, are hacktivist groups " + + "that try to push political and social agendas. Perhaps the most intriguing hacker group " + + "is the mysterious Bitrunners, whose purpose still remains unknown."; + Literatures[fn] = new Literature(title, fn, txt); - title = "Secret Societies"; - fn = LiteratureNames.SecretSocieties; - txt = "The idea of secret societies has long intrigued the general public by inspiring curiosity, fascination, and " + - "distrust. People have long wondered about who these secret society members are and what they do, with the " + - "most radical of conspiracy theorists claiming that they control everything in the entire world. And while the world " + - "may never know for sure, it is likely that many secret societies do actually exist, even today.

    " + - "However, the secret societies of the modern world are nothing like those that (supposedly) existed " + - "decades and centuries ago. The Freemasons, Knights Templar, and Illuminati, while they may have been around " + - "at the turn of the 21st century, almost assuredly do not exist today. The dominance of the Web in " + - "our everyday lives and the fact that so much of the world is now digital has given rise to a new breed " + - "of secret societies: Internet-based ones.

    " + - "Commonly called 'hacker groups', Internet-based secret societies have become well-known in today's " + - "world. Some of these, such as The Black Hand, are black hat groups that claim they are trying to " + - "help the oppressed by attacking the elite and powerful. Others, such as NiteSec, are hacktivist groups " + - "that try to push political and social agendas. Perhaps the most intriguing hacker group " + - "is the mysterious Bitrunners, whose purpose still remains unknown."; - Literatures[fn] = new Literature(title, fn, txt); + title = "Space: The Failed Frontier"; + fn = LiteratureNames.TheFailedFrontier; + txt = + "Humans have long dreamed about spaceflight. With enduring interest, we were driven to explore " + + "the unknown and discover new worlds. We dreamed about conquering the stars. And in our quest, " + + "we pushed the boundaries of our scientific limits, and then pushed further. Space exploration " + + "lead to the development of many important technologies and new industries.

    " + + "But sometime in the middle of the 21st century, all of that changed. Humanity lost its ambitions and " + + "aspirations of exploring the cosmos. The once-large funding for agencies like NASA and the European " + + "Space Agency gradually whittled away until their eventual disbanding in the 2060's. Not even " + + "militaries are fielding flights into space nowadays. The only remnants of the once great mission for cosmic " + + "conquest are the countless satellites in near-earth orbit, used for communications, espionage, " + + "and other corporate interests.

    " + + "And as we continue to look at the state of space technology, it becomes more and " + + "more apparent that we will never return to that golden age of space exploration, that " + + "age where everyone dreamed of going beyond earth for the sake of discovery."; + Literatures[fn] = new Literature(title, fn, txt); - title = "Space: The Failed Frontier"; - fn = LiteratureNames.TheFailedFrontier; - txt = "Humans have long dreamed about spaceflight. With enduring interest, we were driven to explore " + - "the unknown and discover new worlds. We dreamed about conquering the stars. And in our quest, " + - "we pushed the boundaries of our scientific limits, and then pushed further. Space exploration " + - "lead to the development of many important technologies and new industries.

    " + - "But sometime in the middle of the 21st century, all of that changed. Humanity lost its ambitions and " + - "aspirations of exploring the cosmos. The once-large funding for agencies like NASA and the European " + - "Space Agency gradually whittled away until their eventual disbanding in the 2060's. Not even " + - "militaries are fielding flights into space nowadays. The only remnants of the once great mission for cosmic " + - "conquest are the countless satellites in near-earth orbit, used for communications, espionage, " + - "and other corporate interests.

    " + - "And as we continue to look at the state of space technology, it becomes more and " + - "more apparent that we will never return to that golden age of space exploration, that " + - "age where everyone dreamed of going beyond earth for the sake of discovery."; - Literatures[fn] = new Literature(title, fn, txt); + title = "Coded Intelligence: Myth or Reality?"; + fn = LiteratureNames.CodedIntelligence; + txt = + "Tremendous progress has been made in the field of Artificial Intelligence over the past few decades. " + + "Our autonomous vehicles and transporation systems. The electronic personal assistants that control our everyday lives. " + + "Medical, service, and manufacturing robots. All of these are examples of how far AI has come and how much it has " + + "improved our daily lives. However, the question still remains of whether AI will ever be advanced enough to re-create " + + "human intelligence.

    " + + "We've certainly come close to artificial intelligence that is similar to humans. For example OmniTek Incorporated's " + + "CompanionBot, a robot meant to act as a comforting friend for lonely and grieving people, is eerily human-like " + + "in its appearance, speech, mannerisms, and even movement. However its artificial intelligence isn't the same as " + + "that of humans. Not yet. It doesn't have sentience or self-awareness or consciousness.

    " + + "Many neuroscientists believe that we won't ever reach the point of creating artificial human intelligence. 'At the end of the " + + "the day, AI comes down to 1's and 0's, while the human brain does not. We'll never see AI that is identical to that of " + + "humans.'"; + Literatures[fn] = new Literature(title, fn, txt); - title = "Coded Intelligence: Myth or Reality?"; - fn = LiteratureNames.CodedIntelligence; - txt = "Tremendous progress has been made in the field of Artificial Intelligence over the past few decades. " + - "Our autonomous vehicles and transporation systems. The electronic personal assistants that control our everyday lives. " + - "Medical, service, and manufacturing robots. All of these are examples of how far AI has come and how much it has " + - "improved our daily lives. However, the question still remains of whether AI will ever be advanced enough to re-create " + - "human intelligence.

    " + - "We've certainly come close to artificial intelligence that is similar to humans. For example OmniTek Incorporated's " + - "CompanionBot, a robot meant to act as a comforting friend for lonely and grieving people, is eerily human-like " + - "in its appearance, speech, mannerisms, and even movement. However its artificial intelligence isn't the same as " + - "that of humans. Not yet. It doesn't have sentience or self-awareness or consciousness.

    " + - "Many neuroscientists believe that we won't ever reach the point of creating artificial human intelligence. 'At the end of the " + - "the day, AI comes down to 1's and 0's, while the human brain does not. We'll never see AI that is identical to that of " + - "humans.'"; - Literatures[fn] = new Literature(title, fn, txt); + title = "Synthetic Muscles"; + fn = LiteratureNames.SyntheticMuscles; + txt = + "Initial versions of synthetic muscles weren't made of anything organic but were actually " + + "crude devices made to mimic human muscle function. Some of the early iterations were actually made of " + + "common materials such as fishing lines and sewing threads due to their high strength for " + + "a cheap cost.

    " + + "As technology progressed, however, advances in biomedical engineering paved the way for a new method of " + + "creating synthetic muscles. Instead of creating something that closely imitated the functionality " + + "of human muscle, scientists discovered a way of forcing the human body itself to augment its own " + + "muscle tissue using both synthetic and organic materials. This is typically done using gene therapy " + + "or chemical injections."; + Literatures[fn] = new Literature(title, fn, txt); - title = "Synthetic Muscles"; - fn = LiteratureNames.SyntheticMuscles; - txt = "Initial versions of synthetic muscles weren't made of anything organic but were actually " + - "crude devices made to mimic human muscle function. Some of the early iterations were actually made of " + - "common materials such as fishing lines and sewing threads due to their high strength for " + - "a cheap cost.

    " + - "As technology progressed, however, advances in biomedical engineering paved the way for a new method of " + - "creating synthetic muscles. Instead of creating something that closely imitated the functionality " + - "of human muscle, scientists discovered a way of forcing the human body itself to augment its own " + - "muscle tissue using both synthetic and organic materials. This is typically done using gene therapy " + - "or chemical injections."; - Literatures[fn] = new Literature(title, fn, txt); + title = "Tensions rise in global tech race"; + fn = LiteratureNames.TensionsInTechRace; + txt = + "Have we entered a new Cold War? Is WWIII just beyond the horizon?

    " + + "After rumors came out that OmniTek Incorporated had begun developing advanced robotic supersoldiers, " + + "geopolitical tensions quickly flared between the USA, Russia, and several Asian superpowers. " + + "In a rare show of cooperation between corporations, MegaCorp and ECorp have " + + "reportedly launched hundreds of new surveillance and espionage satellites. " + + "Defense contractors such as " + + "DeltaOne and AeroCorp have been working with the CIA and NSA to prepare " + + "for conflict. Meanwhile, the rest of the world sits in earnest " + + "hoping that it never reaches full-scale war. With today's technology " + + "and firepower, a World War would assuredly mean the end of human civilization."; + Literatures[fn] = new Literature(title, fn, txt); - title = "Tensions rise in global tech race"; - fn = LiteratureNames.TensionsInTechRace; - txt = "Have we entered a new Cold War? Is WWIII just beyond the horizon?

    " + - "After rumors came out that OmniTek Incorporated had begun developing advanced robotic supersoldiers, " + - "geopolitical tensions quickly flared between the USA, Russia, and several Asian superpowers. " + - "In a rare show of cooperation between corporations, MegaCorp and ECorp have " + - "reportedly launched hundreds of new surveillance and espionage satellites. " + - "Defense contractors such as " + - "DeltaOne and AeroCorp have been working with the CIA and NSA to prepare " + - "for conflict. Meanwhile, the rest of the world sits in earnest " + - "hoping that it never reaches full-scale war. With today's technology " + - "and firepower, a World War would assuredly mean the end of human civilization."; - Literatures[fn] = new Literature(title, fn, txt); + title = "The Cost of Immortality"; + fn = LiteratureNames.CostOfImmortality; + txt = + "Evolution and advances in medical and augmentation technology has lead to drastic improvements " + + "in human mortality rates. Recent figures show that the life expectancy for humans " + + "that live in a first-world country is about 130 years of age, almost double of what it was " + + "at the turn of the century. However, this increase in average lifespan has had some " + + "significant effects on society and culture.

    " + + "Due to longer lifespans and a better quality of life, many adults are holding " + + "off on having kids until much later. As a result, the percentage of youth in " + + "first-world countries has been decreasing, while the number " + + "of senior citizens is significantly increasing.

    " + + "Perhaps the most alarming result of all of this is the rapidly shrinking workforce. " + + "Despite the increase in life expectancy, the typical retirement age for " + + "workers in America has remained about the same, meaning a larger and larger " + + "percentage of people in America are retirees. Furthermore, many " + + "young adults are holding off on joining the workforce because they feel that " + + "they have plenty of time left in their lives for employment, and want to " + + "'enjoy life while they're young.' For most industries, this shrinking workforce " + + "is not a major issue as most things are handled by robots anyways. However, " + + "there are still several key industries such as engineering and education " + + "that have not been automated, and these remain in danger to this cultural " + + "phenomenon."; + Literatures[fn] = new Literature(title, fn, txt); - title = "The Cost of Immortality"; - fn = LiteratureNames.CostOfImmortality; - txt = "Evolution and advances in medical and augmentation technology has lead to drastic improvements " + - "in human mortality rates. Recent figures show that the life expectancy for humans " + - "that live in a first-world country is about 130 years of age, almost double of what it was " + - "at the turn of the century. However, this increase in average lifespan has had some " + - "significant effects on society and culture.

    " + - "Due to longer lifespans and a better quality of life, many adults are holding " + - "off on having kids until much later. As a result, the percentage of youth in " + - "first-world countries has been decreasing, while the number " + - "of senior citizens is significantly increasing.

    " + - "Perhaps the most alarming result of all of this is the rapidly shrinking workforce. " + - "Despite the increase in life expectancy, the typical retirement age for " + - "workers in America has remained about the same, meaning a larger and larger " + - "percentage of people in America are retirees. Furthermore, many " + - "young adults are holding off on joining the workforce because they feel that " + - "they have plenty of time left in their lives for employment, and want to " + - "'enjoy life while they're young.' For most industries, this shrinking workforce " + - "is not a major issue as most things are handled by robots anyways. However, " + - "there are still several key industries such as engineering and education " + - "that have not been automated, and these remain in danger to this cultural " + - "phenomenon."; - Literatures[fn] = new Literature(title, fn, txt); + title = "The Hidden World"; + fn = LiteratureNames.TheHiddenWorld; + txt = + "WAKE UP SHEEPLE

    " + + "THE GOVERNMENT DOES NOT EXIST. CORPORATIONS DO NOT RUN SOCIETY

    " + + "THE ILLUMINATI ARE THE SECRET RULERS OF THE WORLD!

    " + + "Yes, the Illuminati of legends. The ancient secret society that controls the entire " + + "world from the shadows with their invisible hand. The group of the rich and wealthy " + + "that have penetrated every major government, financial agency, and corporation in the last " + + "three hundred years.

    " + + "OPEN YOUR EYES

    " + + "It was the Illuminati that brought an end to democracy in the world. They are the driving force " + + "behind everything that happens.

    " + + "THEY ARE ALL AROUND YOU

    " + + "After destabilizing the world's governments, they are now entering the final stage of their master plan. " + + "They will secretly initiate global crises. Terrorism. Pandemics. World War. And out of the chaos " + + "that ensues they will build their New World Order."; + Literatures[fn] = new Literature(title, fn, txt); - title = "The Hidden World"; - fn = LiteratureNames.TheHiddenWorld; - txt = "WAKE UP SHEEPLE

    " + - "THE GOVERNMENT DOES NOT EXIST. CORPORATIONS DO NOT RUN SOCIETY

    " + - "THE ILLUMINATI ARE THE SECRET RULERS OF THE WORLD!

    " + - "Yes, the Illuminati of legends. The ancient secret society that controls the entire " + - "world from the shadows with their invisible hand. The group of the rich and wealthy " + - "that have penetrated every major government, financial agency, and corporation in the last " + - "three hundred years.

    " + - "OPEN YOUR EYES

    " + - "It was the Illuminati that brought an end to democracy in the world. They are the driving force " + - "behind everything that happens.

    " + - "THEY ARE ALL AROUND YOU

    " + - "After destabilizing the world's governments, they are now entering the final stage of their master plan. " + - "They will secretly initiate global crises. Terrorism. Pandemics. World War. And out of the chaos " + - "that ensues they will build their New World Order."; - Literatures[fn] = new Literature(title, fn, txt); + title = "The New God"; + fn = LiteratureNames.TheNewGod; + txt = + "Everyone has a moment in their life when they wonder about the bigger questions.

    " + + "What's the point of all this? What is my purpose?

    " + + "Some people dare to think even bigger.

    " + + "What will the fate of the human race be?

    " + + "We live in an era vastly different from that of 15 or even 20 years ago. We have gone " + + "beyond the limits of humanity. We have stripped ourselves of the tyranny of flesh.

    " + + "The Singularity is here. The merging of man and machine. This is where humanity evolves into "; + "something greater. This is our future.

    " + + "Embrace it, and you will obey a new god. The God in the Machine."; + Literatures[fn] = new Literature(title, fn, txt); - title = "The New God"; - fn = LiteratureNames.TheNewGod; - txt = "Everyone has a moment in their life when they wonder about the bigger questions.

    " + - "What's the point of all this? What is my purpose?

    " + - "Some people dare to think even bigger.

    " + - "What will the fate of the human race be?

    " + - "We live in an era vastly different from that of 15 or even 20 years ago. We have gone " + - "beyond the limits of humanity. We have stripped ourselves of the tyranny of flesh.

    " + - "The Singularity is here. The merging of man and machine. This is where humanity evolves into " - "something greater. This is our future.

    " + - "Embrace it, and you will obey a new god. The God in the Machine."; - Literatures[fn] = new Literature(title, fn, txt); + title = "The New Triads"; + fn = LiteratureNames.NewTriads; + txt = + "The Triads were an ancient transnational crime syndicate based in China, Hong Kong, and other Asian " + + "territories. They were often considered one of the first and biggest criminal secret societies. " + + "While most of the branches of the Triads have been destroyed over the past few decades, the " + + "crime faction has spawned and inspired a number of other Asian crime organizations over the past few years. " + + "The most notable of these is the Tetrads.

    " + + "It is widely believed that the Tetrads are a rogue group that splintered off from the Triads sometime in the " + + "mid 21st century. The founders of the Tetrads, all of whom were ex-Triad members, believed that the " + + "Triads were losing their purpose and direction. The Tetrads started off as a small group that mainly engaged " + + "in fraud and extortion. They were largely unknown until just a few years ago when they took over the illegal " + + "drug trade in all of the major Asian cities. They quickly became the most powerful crime syndicate in the " + + "continent.

    " + + "Not much else is known about the Tetrads, or about the efforts the Asian governments and corporations are making " + + "to take down this large new crime organization. Many believe that the Tetrads have infiltrated the governments " + + "and powerful corporations in Asia, which has helped faciliate their recent rapid rise."; + Literatures[fn] = new Literature(title, fn, txt); - title = "The New Triads"; - fn = LiteratureNames.NewTriads; - txt = "The Triads were an ancient transnational crime syndicate based in China, Hong Kong, and other Asian " + - "territories. They were often considered one of the first and biggest criminal secret societies. " + - "While most of the branches of the Triads have been destroyed over the past few decades, the " + - "crime faction has spawned and inspired a number of other Asian crime organizations over the past few years. " + - "The most notable of these is the Tetrads.

    " + - "It is widely believed that the Tetrads are a rogue group that splintered off from the Triads sometime in the " + - "mid 21st century. The founders of the Tetrads, all of whom were ex-Triad members, believed that the " + - "Triads were losing their purpose and direction. The Tetrads started off as a small group that mainly engaged " + - "in fraud and extortion. They were largely unknown until just a few years ago when they took over the illegal " + - "drug trade in all of the major Asian cities. They quickly became the most powerful crime syndicate in the " + - "continent.

    " + - "Not much else is known about the Tetrads, or about the efforts the Asian governments and corporations are making " + - "to take down this large new crime organization. Many believe that the Tetrads have infiltrated the governments " + - "and powerful corporations in Asia, which has helped faciliate their recent rapid rise."; - Literatures[fn] = new Literature(title, fn, txt); - - title = "The Secret War"; - fn = LiteratureNames.TheSecretWar; - txt = "" - Literatures[fn] = new Literature(title, fn, txt); -})() + title = "The Secret War"; + fn = LiteratureNames.TheSecretWar; + txt = ""; + Literatures[fn] = new Literature(title, fn, txt); +})(); diff --git a/src/Literature/data/LiteratureNames.ts b/src/Literature/data/LiteratureNames.ts index 462afb4f1..4d1a2c49e 100644 --- a/src/Literature/data/LiteratureNames.ts +++ b/src/Literature/data/LiteratureNames.ts @@ -1,25 +1,25 @@ import { IMap } from "../../types"; export const LiteratureNames: IMap = { - HackersStartingHandbook: "hackers-starting-handbook.lit", - CorporationManagementHandbook: "corporation-management-handbook.lit", - HistoryOfSynthoids: "history-of-synthoids.lit", - AGreenTomorrow: "A-Green-Tomorrow.lit", - AlphaOmega: "alpha-omega.lit", - SimulatedReality: "simulated-reality.lit", - BeyondMan: "beyond-man.lit", - BrighterThanTheSun: "brighter-than-the-sun.lit", - DemocracyIsDead: "democracy-is-dead.lit", - Sector12Crime: "sector-12-crime.lit", - ManAndMachine: "man-and-machine.lit", - SecretSocieties: "secret-societies.lit", - TheFailedFrontier: "the-failed-frontier.lit", - CodedIntelligence: "coded-intelligence.lit", - SyntheticMuscles: "synthetic-muscles.lit", - TensionsInTechRace: "tensions-in-tech-race.lit", - CostOfImmortality: "cost-of-immortality.lit", - TheHiddenWorld: "the-hidden-world.lit", - TheNewGod: "the-new-god.lit", - NewTriads: "new-triads.lit", - TheSecretWar: "the-secret-war.lit", -}; \ No newline at end of file + HackersStartingHandbook: "hackers-starting-handbook.lit", + CorporationManagementHandbook: "corporation-management-handbook.lit", + HistoryOfSynthoids: "history-of-synthoids.lit", + AGreenTomorrow: "A-Green-Tomorrow.lit", + AlphaOmega: "alpha-omega.lit", + SimulatedReality: "simulated-reality.lit", + BeyondMan: "beyond-man.lit", + BrighterThanTheSun: "brighter-than-the-sun.lit", + DemocracyIsDead: "democracy-is-dead.lit", + Sector12Crime: "sector-12-crime.lit", + ManAndMachine: "man-and-machine.lit", + SecretSocieties: "secret-societies.lit", + TheFailedFrontier: "the-failed-frontier.lit", + CodedIntelligence: "coded-intelligence.lit", + SyntheticMuscles: "synthetic-muscles.lit", + TensionsInTechRace: "tensions-in-tech-race.lit", + CostOfImmortality: "cost-of-immortality.lit", + TheHiddenWorld: "the-hidden-world.lit", + TheNewGod: "the-new-god.lit", + NewTriads: "new-triads.lit", + TheSecretWar: "the-secret-war.lit", +}; diff --git a/src/Locations/City.ts b/src/Locations/City.ts index ed02e1933..089c0d869 100644 --- a/src/Locations/City.ts +++ b/src/Locations/City.ts @@ -5,28 +5,28 @@ import { CityName } from "./data/CityNames"; import { LocationName } from "./data/LocationNames"; export class City { - /** - * List of all locations in this city - */ - locations: LocationName[]; + /** + * List of all locations in this city + */ + locations: LocationName[]; - /** - * Name of this city - */ - name: CityName; + /** + * Name of this city + */ + name: CityName; - /** - * Metro map ascii art - */ - asciiArt: string; + /** + * Metro map ascii art + */ + asciiArt: string; - constructor(name: CityName, locations: LocationName[]=[], asciiArt='') { - this.name = name; - this.locations = locations; - this.asciiArt = asciiArt; - } + constructor(name: CityName, locations: LocationName[] = [], asciiArt = "") { + this.name = name; + this.locations = locations; + this.asciiArt = asciiArt; + } - addLocation(loc: LocationName): void { - this.locations.push(loc); - } + addLocation(loc: LocationName): void { + this.locations.push(loc); + } } diff --git a/src/Locations/Location.ts b/src/Locations/Location.ts index 291a335d8..eaf06fecc 100644 --- a/src/Locations/Location.ts +++ b/src/Locations/Location.ts @@ -6,75 +6,91 @@ import { LocationName } from "./data/LocationNames"; import { LocationType } from "./LocationTypeEnum"; interface IInfiltrationMetadata { - maxClearanceLevel: number; - startingSecurityLevel: number; + maxClearanceLevel: number; + startingSecurityLevel: number; } export interface IConstructorParams { - city?: CityName | null; - costMult?: number; - expMult?: number; - infiltrationData?: IInfiltrationMetadata; - name?: LocationName; - types?: LocationType[]; - techVendorMaxRam?: number; - techVendorMinRam?: number; + city?: CityName | null; + costMult?: number; + expMult?: number; + infiltrationData?: IInfiltrationMetadata; + name?: LocationName; + types?: LocationType[]; + techVendorMaxRam?: number; + techVendorMinRam?: number; } export class Location { - /** - * Name of city this location is in. If this property is null, it means this i - * is a generic location that is available in all cities - */ - city: CityName | null = null; + /** + * Name of city this location is in. If this property is null, it means this i + * is a generic location that is available in all cities + */ + city: CityName | null = null; - /** - * Cost multiplier that influences how expensive a gym/university is - */ - costMult = 0; + /** + * Cost multiplier that influences how expensive a gym/university is + */ + costMult = 0; - /** - * Exp multiplier that influences how effective a gym/university is - */ - expMult = 0; + /** + * Exp multiplier that influences how effective a gym/university is + */ + expMult = 0; - /** - * Companies can be infiltrated. This contains the data required for that - * infiltration event - */ - infiltrationData?: IInfiltrationMetadata; + /** + * Companies can be infiltrated. This contains the data required for that + * infiltration event + */ + infiltrationData?: IInfiltrationMetadata; - /** - * Identifier for location - */ - name: LocationName = LocationName.Void; + /** + * Identifier for location + */ + name: LocationName = LocationName.Void; - /** - * List of what type(s) this location is. A location can be multiple types - * (e.g. company and tech vendor) - */ - types: LocationType[] = []; + /** + * List of what type(s) this location is. A location can be multiple types + * (e.g. company and tech vendor) + */ + types: LocationType[] = []; - /** - * Tech vendors allow you to purchase servers. - * This property defines the max RAM server you can purchase from this vendor - */ - techVendorMaxRam = 0; + /** + * Tech vendors allow you to purchase servers. + * This property defines the max RAM server you can purchase from this vendor + */ + techVendorMaxRam = 0; - /** - * Tech vendors allow you to purchase servers. - * This property defines the max RAM server you can purchase from this vendor - */ - techVendorMinRam = 0; + /** + * Tech vendors allow you to purchase servers. + * This property defines the max RAM server you can purchase from this vendor + */ + techVendorMinRam = 0; - constructor(p: IConstructorParams) { - if (p.city) { this.city = p.city; } - if (p.costMult) { this.costMult = p.costMult; } - if (p.expMult) { this.expMult = p.expMult; } - if (p.infiltrationData) { this.infiltrationData = p.infiltrationData; } - if (p.name) { this.name = p.name; } - if (p.types) { this.types = p.types; } - if (p.techVendorMaxRam) { this.techVendorMaxRam = p.techVendorMaxRam; } - if (p.techVendorMinRam) { this.techVendorMinRam = p.techVendorMinRam; } + constructor(p: IConstructorParams) { + if (p.city) { + this.city = p.city; } + if (p.costMult) { + this.costMult = p.costMult; + } + if (p.expMult) { + this.expMult = p.expMult; + } + if (p.infiltrationData) { + this.infiltrationData = p.infiltrationData; + } + if (p.name) { + this.name = p.name; + } + if (p.types) { + this.types = p.types; + } + if (p.techVendorMaxRam) { + this.techVendorMaxRam = p.techVendorMaxRam; + } + if (p.techVendorMinRam) { + this.techVendorMinRam = p.techVendorMinRam; + } + } } diff --git a/src/Locations/LocationTypeEnum.ts b/src/Locations/LocationTypeEnum.ts index 56d06631c..c5cdf9bbc 100644 --- a/src/Locations/LocationTypeEnum.ts +++ b/src/Locations/LocationTypeEnum.ts @@ -2,14 +2,14 @@ * Enum defining the different types of possible locations */ export enum LocationType { - Company, - Gym, - Hospital, - Slums, - Special, // This location has special options/activities (e.g. Bladeburner, Re-sleeving) - StockMarket, - TechVendor, - TravelAgency, - University, - Casino, + Company, + Gym, + Hospital, + Slums, + Special, // This location has special options/activities (e.g. Bladeburner, Re-sleeving) + StockMarket, + TechVendor, + TravelAgency, + University, + Casino, } diff --git a/src/Locations/Locations.ts b/src/Locations/Locations.ts index ab09771e0..6416afc4b 100644 --- a/src/Locations/Locations.ts +++ b/src/Locations/Locations.ts @@ -2,13 +2,11 @@ * Map of all Locations in the game * Key = Location name, value = Location object */ -import { City } from "./City"; -import { Cities } from "./Cities"; -import { Location, - IConstructorParams } from "./Location"; -import { CityName } from "./data/CityNames"; -import { LocationsMetadata } from "./data/LocationsMetadata"; - +import { City } from "./City"; +import { Cities } from "./Cities"; +import { Location, IConstructorParams } from "./Location"; +import { CityName } from "./data/CityNames"; +import { LocationsMetadata } from "./data/LocationsMetadata"; import { IMap } from "../types"; @@ -19,26 +17,30 @@ export const Locations: IMap = {}; * be initialized from the `LocationsMetadata` */ function constructLocation(p: IConstructorParams): Location { - if (!p.name) { - throw new Error(`Invalid constructor parameters for Location. No 'name' property`); - } + if (!p.name) { + throw new Error( + `Invalid constructor parameters for Location. No 'name' property`, + ); + } - if (Locations[p.name] instanceof Location) { - console.warn(`Property with name ${p.name} already exists and is being overwritten`); - } + if (Locations[p.name] instanceof Location) { + console.warn( + `Property with name ${p.name} already exists and is being overwritten`, + ); + } - Locations[p.name] = new Location(p); + Locations[p.name] = new Location(p); - return Locations[p.name]; + return Locations[p.name]; } // First construct all cities -Cities[CityName.Aevum] = new City(CityName.Aevum); -Cities[CityName.Chongqing] = new City(CityName.Chongqing); -Cities[CityName.Ishima] = new City(CityName.Ishima); -Cities[CityName.NewTokyo] = new City(CityName.NewTokyo); -Cities[CityName.Sector12] = new City(CityName.Sector12); -Cities[CityName.Volhaven] = new City(CityName.Volhaven); +Cities[CityName.Aevum] = new City(CityName.Aevum); +Cities[CityName.Chongqing] = new City(CityName.Chongqing); +Cities[CityName.Ishima] = new City(CityName.Ishima); +Cities[CityName.NewTokyo] = new City(CityName.NewTokyo); +Cities[CityName.Sector12] = new City(CityName.Sector12); +Cities[CityName.Volhaven] = new City(CityName.Volhaven); Cities[CityName.Aevum].asciiArt = ` [aevum police headquarters] 26 @@ -75,7 +77,7 @@ Cities[CityName.Aevum].asciiArt = ` | 67 o - [the slums] P ` + [the slums] P `; Cities[CityName.Chongqing].asciiArt = ` | 75 o @@ -98,7 +100,7 @@ Cities[CityName.Chongqing].asciiArt = ` | | x 82 - [the slums] D ` + [the slums] D `; Cities[CityName.Ishima].asciiArt = ` o 59 o o | @@ -123,7 +125,7 @@ Cities[CityName.Ishima].asciiArt = ` / o----B------x-----o o 50 52 [omega soft.] - [the slums] E ` + [the slums] E `; Cities[CityName.NewTokyo].asciiArt = ` @@ -151,7 +153,7 @@ Cities[CityName.NewTokyo].asciiArt = ` F [the slums] - ` + `; Cities[CityName.Sector12].asciiArt = ` 78 o 97 o [icarus microsystems] / @@ -181,7 +183,7 @@ Cities[CityName.Sector12].asciiArt = ` | / 85 o--G--------K--------S-------o 88 [the slums] R - [foodnstuff] [travel agency] ` + [foodnstuff] [travel agency] `; Cities[CityName.Volhaven].asciiArt = ` [omnia cybersystems] 17 66 68 @@ -213,19 +215,19 @@ Cities[CityName.Volhaven].asciiArt = ` 57 - [the slums] K ` + [the slums] K `; // Then construct all locations, and add them to the cities as we go. for (const metadata of LocationsMetadata) { - const loc = constructLocation(metadata); + const loc = constructLocation(metadata); - const cityName = loc.city; - if (cityName === null) { - // Generic location, add to all cities - for (const city in Cities) { - Cities[city].addLocation(loc.name); - } - } else { - Cities[cityName].addLocation(loc.name); + const cityName = loc.city; + if (cityName === null) { + // Generic location, add to all cities + for (const city in Cities) { + Cities[city].addLocation(loc.name); } + } else { + Cities[cityName].addLocation(loc.name); + } } diff --git a/src/Locations/LocationsHelpers.tsx b/src/Locations/LocationsHelpers.tsx index 439bcefe9..6366aa9c7 100644 --- a/src/Locations/LocationsHelpers.tsx +++ b/src/Locations/LocationsHelpers.tsx @@ -7,15 +7,12 @@ import { CONSTANTS } from "../Constants"; import { CityName } from "./data/CityNames"; import { IPlayer } from "../PersonObjects/IPlayer"; -import { - AddToAllServers, - createUniqueRandomIp, -} from "../Server/AllServers"; +import { AddToAllServers, createUniqueRandomIp } from "../Server/AllServers"; import { safetlyCreateUniqueServer } from "../Server/ServerHelpers"; import { - getPurchaseServerCost, - purchaseRamForHomeComputer, - purchaseServer, + getPurchaseServerCost, + purchaseRamForHomeComputer, + purchaseServer, } from "../Server/ServerPurchases"; import { SpecialServerIps } from "../Server/SpecialServerIps"; import { Settings } from "../Settings/Settings"; @@ -25,14 +22,14 @@ import { Money } from "../ui/React/Money"; import { dialogBoxCreate } from "../../utils/DialogBox"; import { - yesNoBoxGetYesButton, - yesNoBoxGetNoButton, - yesNoBoxClose, - yesNoBoxCreate, - yesNoTxtInpBoxGetYesButton, - yesNoTxtInpBoxGetNoButton, - yesNoTxtInpBoxClose, - yesNoTxtInpBoxCreate, + yesNoBoxGetYesButton, + yesNoBoxGetNoButton, + yesNoBoxClose, + yesNoBoxCreate, + yesNoTxtInpBoxGetYesButton, + yesNoTxtInpBoxGetNoButton, + yesNoTxtInpBoxClose, + yesNoTxtInpBoxCreate, } from "../../utils/YesNoBox"; import { createElement } from "../../utils/uiHelpers/createElement"; @@ -50,36 +47,43 @@ import * as React from "react"; * @param {Function} travelFn - Function that changes the player's state for traveling */ type TravelFunction = (to: CityName) => void; -export function createTravelPopup(destination: CityName, travelFn: TravelFunction): void { - const cost: number = CONSTANTS.TravelCost; +export function createTravelPopup( + destination: CityName, + travelFn: TravelFunction, +): void { + const cost: number = CONSTANTS.TravelCost; - if (Settings.SuppressTravelConfirmation) { - travelFn(destination); - return; - } + if (Settings.SuppressTravelConfirmation) { + travelFn(destination); + return; + } - const yesBtn = yesNoBoxGetYesButton(); - const noBtn = yesNoBoxGetNoButton(); - if (yesBtn == null || noBtn == null) { - console.warn(`Could not find YesNo pop-up box buttons`); - return; - } + const yesBtn = yesNoBoxGetYesButton(); + const noBtn = yesNoBoxGetNoButton(); + if (yesBtn == null || noBtn == null) { + console.warn(`Could not find YesNo pop-up box buttons`); + return; + } - yesBtn.innerHTML = "Yes"; - yesBtn.addEventListener("click", () => { - yesNoBoxClose(); - travelFn(destination); - return false; - }); + yesBtn.innerHTML = "Yes"; + yesBtn.addEventListener("click", () => { + yesNoBoxClose(); + travelFn(destination); + return false; + }); - noBtn.innerHTML = "No"; - noBtn.addEventListener("click", () => { - yesNoBoxClose(); - return false; - }); + noBtn.innerHTML = "No"; + noBtn.addEventListener("click", () => { + yesNoBoxClose(); + return false; + }); - yesNoBoxCreate(Would you like to travel to {destination}? The trip will - cost .); + yesNoBoxCreate( + + Would you like to travel to {destination}? The trip will cost{" "} + . + , + ); } /** @@ -88,28 +92,40 @@ export function createTravelPopup(destination: CityName, travelFn: TravelFunctio * @param {IPlayer} p - Player object */ export function createPurchaseServerPopup(ram: number, p: IPlayer): void { - const cost = getPurchaseServerCost(ram); - if (cost === Infinity) { - dialogBoxCreate("Something went wrong when trying to purchase this server. Please contact developer."); - return; - } + const cost = getPurchaseServerCost(ram); + if (cost === Infinity) { + dialogBoxCreate( + "Something went wrong when trying to purchase this server. Please contact developer.", + ); + return; + } - const yesBtn = yesNoTxtInpBoxGetYesButton(); - const noBtn = yesNoTxtInpBoxGetNoButton(); - if (yesBtn == null || noBtn == null) { return; } - yesBtn.innerHTML = "Purchase Server"; - noBtn.innerHTML = "Cancel"; - yesBtn.addEventListener("click", function() { - purchaseServer(ram, p); - yesNoTxtInpBoxClose(); - }); - noBtn.addEventListener("click", function() { - yesNoTxtInpBoxClose(); - }); + const yesBtn = yesNoTxtInpBoxGetYesButton(); + const noBtn = yesNoTxtInpBoxGetNoButton(); + if (yesBtn == null || noBtn == null) { + return; + } + yesBtn.innerHTML = "Purchase Server"; + noBtn.innerHTML = "Cancel"; + yesBtn.addEventListener("click", function () { + purchaseServer(ram, p); + yesNoTxtInpBoxClose(); + }); + noBtn.addEventListener("click", function () { + yesNoTxtInpBoxClose(); + }); - yesNoTxtInpBoxCreate(<>Would you like to purchase a new server with {numeralWrapper.formatRAM(ram)} of RAM for ? -

    Please enter the server hostname below:
    - ); + yesNoTxtInpBoxCreate( + <> + Would you like to purchase a new server with{" "} + {numeralWrapper.formatRAM(ram)} of RAM for{" "} + ? +
    +
    + Please enter the server hostname below: +
    + , + ); } /** @@ -117,79 +133,96 @@ export function createPurchaseServerPopup(ram: number, p: IPlayer): void { * @param {IPlayer} p - Player object */ export function createStartCorporationPopup(p: IPlayer): void { - if (!p.canAccessCorporation() || p.hasCorporation()) { return; } + if (!p.canAccessCorporation() || p.hasCorporation()) { + return; + } - const popupId = "create-corporation-popup"; - const txt = createElement("p", { - innerHTML: "Would you like to start a corporation? This will require $150b for registration " + - "and initial funding. This $150b can either be self-funded, or you can obtain " + - "the seed money from the government in exchange for 500 million shares

    " + - "If you would like to start one, please enter a name for your corporation below:", - }); + const popupId = "create-corporation-popup"; + const txt = createElement("p", { + innerHTML: + "Would you like to start a corporation? This will require $150b for registration " + + "and initial funding. This $150b can either be self-funded, or you can obtain " + + "the seed money from the government in exchange for 500 million shares

    " + + "If you would like to start one, please enter a name for your corporation below:", + }); - const nameInput = createElement("input", { - class: 'text-input', - placeholder: "Corporation Name", - }) as HTMLInputElement; + const nameInput = createElement("input", { + class: "text-input", + placeholder: "Corporation Name", + }) as HTMLInputElement; - const selfFundedButton = createElement("button", { - class: "popup-box-button", - innerText: "Self-Fund", - clickListener: () => { - if (!p.canAfford(150e9)) { - dialogBoxCreate("You don't have enough money to create a corporation! You need $150b."); - return false; - } + const selfFundedButton = createElement("button", { + class: "popup-box-button", + innerText: "Self-Fund", + clickListener: () => { + if (!p.canAfford(150e9)) { + dialogBoxCreate( + "You don't have enough money to create a corporation! You need $150b.", + ); + return false; + } - const companyName = nameInput.value; - if (companyName == null || companyName == "") { - dialogBoxCreate("Invalid company name!"); - return false; - } + const companyName = nameInput.value; + if (companyName == null || companyName == "") { + dialogBoxCreate("Invalid company name!"); + return false; + } - p.startCorporation(companyName); - p.loseMoney(150e9); + p.startCorporation(companyName); + p.loseMoney(150e9); - const worldHeader = document.getElementById("world-menu-header"); - if (worldHeader instanceof HTMLElement) { - worldHeader.click(); worldHeader.click(); - } - dialogBoxCreate("Congratulations! You just self-funded your own corporation. You can visit " + - "and manage your company in the City."); - removeElementById(popupId); - return false; - }, - }); + const worldHeader = document.getElementById("world-menu-header"); + if (worldHeader instanceof HTMLElement) { + worldHeader.click(); + worldHeader.click(); + } + dialogBoxCreate( + "Congratulations! You just self-funded your own corporation. You can visit " + + "and manage your company in the City.", + ); + removeElementById(popupId); + return false; + }, + }); - const seedMoneyButton = createElement("button", { - class: "popup-box-button", - innerText: "Use Seed Money", - clickListener: () => { - const companyName = nameInput.value; - if (companyName == null || companyName == "") { - dialogBoxCreate("Invalid company name!"); - return false; - } + const seedMoneyButton = createElement("button", { + class: "popup-box-button", + innerText: "Use Seed Money", + clickListener: () => { + const companyName = nameInput.value; + if (companyName == null || companyName == "") { + dialogBoxCreate("Invalid company name!"); + return false; + } - p.startCorporation(companyName, 500e6); + p.startCorporation(companyName, 500e6); - const worldHeader = document.getElementById("world-menu-header"); - if (worldHeader instanceof HTMLElement) { - worldHeader.click(); worldHeader.click(); - } - dialogBoxCreate( - "Congratulations! You just started your own corporation with government seed money. " + - "You can visit and manage your company in the City.", - ); - removeElementById(popupId); - return false; - }, - }) + const worldHeader = document.getElementById("world-menu-header"); + if (worldHeader instanceof HTMLElement) { + worldHeader.click(); + worldHeader.click(); + } + dialogBoxCreate( + "Congratulations! You just started your own corporation with government seed money. " + + "You can visit and manage your company in the City.", + ); + removeElementById(popupId); + return false; + }, + }); - const cancelBtn = createPopupCloseButton(popupId, { class: "popup-box-button" }); + const cancelBtn = createPopupCloseButton(popupId, { + class: "popup-box-button", + }); - createPopup(popupId, [txt, nameInput, cancelBtn, selfFundedButton, seedMoneyButton]); - nameInput.focus(); + createPopup(popupId, [ + txt, + nameInput, + cancelBtn, + selfFundedButton, + seedMoneyButton, + ]); + nameInput.focus(); } /** @@ -197,55 +230,59 @@ export function createStartCorporationPopup(p: IPlayer): void { * @param {IPlayer} p - Player object */ export function createUpgradeHomeCoresPopup(p: IPlayer): void { - const currentCores = p.getHomeComputer().cpuCores; - if (currentCores >= 8) { - dialogBoxCreate(<> - You have the maximum amount of CPU cores on your home computer. - ); - return; + const currentCores = p.getHomeComputer().cpuCores; + if (currentCores >= 8) { + dialogBoxCreate( + <>You have the maximum amount of CPU cores on your home computer., + ); + return; + } + + // Cost of purchasing another cost is found by indexing this array with number of current cores + const allCosts = [0, 10e9, 250e9, 5e12, 100e12, 1e15, 20e15, 200e15]; + const cost: number = allCosts[currentCores]; + + const yesBtn = yesNoBoxGetYesButton(); + const noBtn = yesNoBoxGetNoButton(); + if (yesBtn == null || noBtn == null) { + return; + } + + yesBtn.innerHTML = "Purchase"; + yesBtn.addEventListener("click", () => { + if (!p.canAfford(cost)) { + dialogBoxCreate( + "You do not have enough money to purchase an additional CPU Core for your home computer!", + ); + } else { + p.loseMoney(cost); + p.getHomeComputer().cpuCores++; + dialogBoxCreate( + "You purchased an additional CPU Core for your home computer! It now has " + + p.getHomeComputer().cpuCores + + " cores.", + ); } + yesNoBoxClose(); + }); - // Cost of purchasing another cost is found by indexing this array with number of current cores - const allCosts = [ - 0, - 10e9, - 250e9, - 5e12, - 100e12, - 1e15, - 20e15, - 200e15, - ]; - const cost: number = allCosts[currentCores]; + noBtn.innerHTML = "Cancel"; + noBtn.addEventListener("click", () => { + yesNoBoxClose(); + }); - const yesBtn = yesNoBoxGetYesButton(); - const noBtn = yesNoBoxGetNoButton(); - if (yesBtn == null || noBtn == null) { return; } - - yesBtn.innerHTML = "Purchase"; - yesBtn.addEventListener("click", ()=>{ - if (!p.canAfford(cost)) { - dialogBoxCreate("You do not have enough money to purchase an additional CPU Core for your home computer!"); - } else { - p.loseMoney(cost); - p.getHomeComputer().cpuCores++; - dialogBoxCreate( - "You purchased an additional CPU Core for your home computer! It now has " + - p.getHomeComputer().cpuCores + " cores.", - ); - } - yesNoBoxClose(); - }); - - noBtn.innerHTML = "Cancel"; - noBtn.addEventListener("click", ()=>{ - yesNoBoxClose(); - }); - - yesNoBoxCreate(<>Would you like to purchase an additional CPU Core for your home computer? Each CPU Core -lets you start with an additional Core Node in Hacking Missions.

    -Purchasing an additional core (for a total of {p.getHomeComputer().cpuCores + 1}) will -cost ); + yesNoBoxCreate( + <> + Would you like to purchase an additional CPU Core for your home computer? + Each CPU Core lets you start with an additional Core Node in Hacking + Missions. +
    +
    + Purchasing an additional core (for a total of{" "} + {p.getHomeComputer().cpuCores + 1}) will cost{" "} + + , + ); } /** @@ -253,28 +290,33 @@ cost ); * @param {IPlayer} p - Player object */ export function purchaseTorRouter(p: IPlayer): void { - if (p.hasTorRouter()) { - dialogBoxCreate(`You already have a TOR Router!`); - return; - } - if (!p.canAfford(CONSTANTS.TorRouterCost)) { - dialogBoxCreate("You cannot afford to purchase the TOR router!"); - return; - } - p.loseMoney(CONSTANTS.TorRouterCost); + if (p.hasTorRouter()) { + dialogBoxCreate(`You already have a TOR Router!`); + return; + } + if (!p.canAfford(CONSTANTS.TorRouterCost)) { + dialogBoxCreate("You cannot afford to purchase the TOR router!"); + return; + } + p.loseMoney(CONSTANTS.TorRouterCost); - const darkweb = safetlyCreateUniqueServer({ - ip: createUniqueRandomIp(), hostname:"darkweb", organizationName:"", - isConnectedTo:false, adminRights:false, purchasedByPlayer:false, maxRam:1, - }); - AddToAllServers(darkweb); - SpecialServerIps.addIp("Darkweb Server", darkweb.ip); + const darkweb = safetlyCreateUniqueServer({ + ip: createUniqueRandomIp(), + hostname: "darkweb", + organizationName: "", + isConnectedTo: false, + adminRights: false, + purchasedByPlayer: false, + maxRam: 1, + }); + AddToAllServers(darkweb); + SpecialServerIps.addIp("Darkweb Server", darkweb.ip); - p.getHomeComputer().serversOnNetwork.push(darkweb.ip); - darkweb.serversOnNetwork.push(p.getHomeComputer().ip); - dialogBoxCreate( - "You have purchased a TOR router!
    " + - "You now have access to the dark web from your home computer.
    " + - "Use the scan/scan-analyze commands to search for the dark web connection.", - ); + p.getHomeComputer().serversOnNetwork.push(darkweb.ip); + darkweb.serversOnNetwork.push(p.getHomeComputer().ip); + dialogBoxCreate( + "You have purchased a TOR router!
    " + + "You now have access to the dark web from your home computer.
    " + + "Use the scan/scan-analyze commands to search for the dark web connection.", + ); } diff --git a/src/Locations/createCityMap.ts b/src/Locations/createCityMap.ts index ccab4f250..eb1037b71 100644 --- a/src/Locations/createCityMap.ts +++ b/src/Locations/createCityMap.ts @@ -8,11 +8,11 @@ import { Cities } from "./Cities"; import { IMap } from "../types"; export function createCityMap(initValue: T): IMap { - const map: IMap = {}; - const cities = Object.keys(Cities); - for (let i = 0; i < cities.length; ++i) { - map[cities[i]] = initValue; - } + const map: IMap = {}; + const cities = Object.keys(Cities); + for (let i = 0; i < cities.length; ++i) { + map[cities[i]] = initValue; + } - return map; + return map; } diff --git a/src/Locations/data/CityNames.ts b/src/Locations/data/CityNames.ts index c5a357880..59df78673 100644 --- a/src/Locations/data/CityNames.ts +++ b/src/Locations/data/CityNames.ts @@ -3,10 +3,10 @@ * Implemented as an enum for typing purposes */ export enum CityName { - Aevum = "Aevum", - Chongqing = "Chongqing", - Ishima = "Ishima", - NewTokyo = "New Tokyo", - Sector12 = "Sector-12", - Volhaven = "Volhaven", + Aevum = "Aevum", + Chongqing = "Chongqing", + Ishima = "Ishima", + NewTokyo = "New Tokyo", + Sector12 = "Sector-12", + Volhaven = "Volhaven", } diff --git a/src/Locations/data/LocationNames.ts b/src/Locations/data/LocationNames.ts index dfb48c353..3880fc018 100644 --- a/src/Locations/data/LocationNames.ts +++ b/src/Locations/data/LocationNames.ts @@ -2,80 +2,80 @@ * Names of all locations */ export enum LocationName { - // Cities - Aevum = "Aevum", - Chongqing = "Chongqing", - Ishima = "Ishima", - NewTokyo = "New Tokyo", - Sector12 = "Sector-12", - Volhaven = "Volhaven", + // Cities + Aevum = "Aevum", + Chongqing = "Chongqing", + Ishima = "Ishima", + NewTokyo = "New Tokyo", + Sector12 = "Sector-12", + Volhaven = "Volhaven", - // Aevum Locations - AevumAeroCorp = "AeroCorp", - AevumBachmanAndAssociates = "Bachman & Associates", - AevumClarkeIncorporated = "Clarke Incorporated", - AevumCrushFitnessGym = "Crush Fitness Gym", - AevumECorp = "ECorp", - AevumFulcrumTechnologies = "Fulcrum Technologies", - AevumGalacticCybersystems = "Galactic Cybersystems", - AevumNetLinkTechnologies = "NetLink Technologies", - AevumPolice = "Aevum Police Headquarters", - AevumRhoConstruction = "Rho Construction", - AevumSnapFitnessGym = "Snap Fitness Gym", - AevumSummitUniversity = "Summit University", - AevumWatchdogSecurity = "Watchdog Security", - AevumCasino = "Iker Molina Casino", + // Aevum Locations + AevumAeroCorp = "AeroCorp", + AevumBachmanAndAssociates = "Bachman & Associates", + AevumClarkeIncorporated = "Clarke Incorporated", + AevumCrushFitnessGym = "Crush Fitness Gym", + AevumECorp = "ECorp", + AevumFulcrumTechnologies = "Fulcrum Technologies", + AevumGalacticCybersystems = "Galactic Cybersystems", + AevumNetLinkTechnologies = "NetLink Technologies", + AevumPolice = "Aevum Police Headquarters", + AevumRhoConstruction = "Rho Construction", + AevumSnapFitnessGym = "Snap Fitness Gym", + AevumSummitUniversity = "Summit University", + AevumWatchdogSecurity = "Watchdog Security", + AevumCasino = "Iker Molina Casino", - // Chongqing locations - ChongqingKuaiGongInternational = "KuaiGong International", - ChongqingSolarisSpaceSystems = "Solaris Space Systems", + // Chongqing locations + ChongqingKuaiGongInternational = "KuaiGong International", + ChongqingSolarisSpaceSystems = "Solaris Space Systems", - // Sector 12 - Sector12AlphaEnterprises = "Alpha Enterprises", - Sector12BladeIndustries = "Blade Industries", - Sector12CIA = "Central Intelligence Agency", - Sector12CarmichaelSecurity = "Carmichael Security", - Sector12CityHall = "Sector-12 City Hall", - Sector12DeltaOne = "DeltaOne", - Sector12FoodNStuff = "FoodNStuff", - Sector12FourSigma = "Four Sigma", - Sector12IcarusMicrosystems = "Icarus Microsystems", - Sector12IronGym = "Iron Gym", - Sector12JoesGuns = "Joe's Guns", - Sector12MegaCorp = "MegaCorp", - Sector12NSA = "National Security Agency", - Sector12PowerhouseGym = "Powerhouse Gym", - Sector12RothmanUniversity = "Rothman University", - Sector12UniversalEnergy = "Universal Energy", + // Sector 12 + Sector12AlphaEnterprises = "Alpha Enterprises", + Sector12BladeIndustries = "Blade Industries", + Sector12CIA = "Central Intelligence Agency", + Sector12CarmichaelSecurity = "Carmichael Security", + Sector12CityHall = "Sector-12 City Hall", + Sector12DeltaOne = "DeltaOne", + Sector12FoodNStuff = "FoodNStuff", + Sector12FourSigma = "Four Sigma", + Sector12IcarusMicrosystems = "Icarus Microsystems", + Sector12IronGym = "Iron Gym", + Sector12JoesGuns = "Joe's Guns", + Sector12MegaCorp = "MegaCorp", + Sector12NSA = "National Security Agency", + Sector12PowerhouseGym = "Powerhouse Gym", + Sector12RothmanUniversity = "Rothman University", + Sector12UniversalEnergy = "Universal Energy", - // New Tokyo - NewTokyoDefComm = "DefComm", - NewTokyoGlobalPharmaceuticals = "Global Pharmaceuticals", - NewTokyoNoodleBar = "Noodle Bar", - NewTokyoVitaLife = "VitaLife", + // New Tokyo + NewTokyoDefComm = "DefComm", + NewTokyoGlobalPharmaceuticals = "Global Pharmaceuticals", + NewTokyoNoodleBar = "Noodle Bar", + NewTokyoVitaLife = "VitaLife", - // Ishima - IshimaNovaMedical = "Nova Medical", - IshimaOmegaSoftware = "Omega Software", - IshimaStormTechnologies = "Storm Technologies", + // Ishima + IshimaNovaMedical = "Nova Medical", + IshimaOmegaSoftware = "Omega Software", + IshimaStormTechnologies = "Storm Technologies", - // Volhaven - VolhavenCompuTek = "CompuTek", - VolhavenHeliosLabs = "Helios Labs", - VolhavenLexoCorp = "LexoCorp", - VolhavenMilleniumFitnessGym = "Millenium Fitness Gym", - VolhavenNWO = "NWO", - VolhavenOmniTekIncorporated = "OmniTek Incorporated", - VolhavenOmniaCybersystems = "Omnia Cybersystems", - VolhavenSysCoreSecurities = "SysCore Securities", - VolhavenZBInstituteOfTechnology = "ZB Institute of Technology", + // Volhaven + VolhavenCompuTek = "CompuTek", + VolhavenHeliosLabs = "Helios Labs", + VolhavenLexoCorp = "LexoCorp", + VolhavenMilleniumFitnessGym = "Millenium Fitness Gym", + VolhavenNWO = "NWO", + VolhavenOmniTekIncorporated = "OmniTek Incorporated", + VolhavenOmniaCybersystems = "Omnia Cybersystems", + VolhavenSysCoreSecurities = "SysCore Securities", + VolhavenZBInstituteOfTechnology = "ZB Institute of Technology", - // Generic locations - Hospital = "Hospital", - Slums = "The Slums", - TravelAgency = "Travel Agency", - WorldStockExchange = "World Stock Exchange", + // Generic locations + Hospital = "Hospital", + Slums = "The Slums", + TravelAgency = "Travel Agency", + WorldStockExchange = "World Stock Exchange", - // Default name for Location objects - Void = "The Void", + // Default name for Location objects + Void = "The Void", } diff --git a/src/Locations/data/LocationsMetadata.ts b/src/Locations/data/LocationsMetadata.ts index 165cbab17..1a1a7731d 100644 --- a/src/Locations/data/LocationsMetadata.ts +++ b/src/Locations/data/LocationsMetadata.ts @@ -8,436 +8,436 @@ import { IConstructorParams } from "../Location"; import { LocationType } from "../LocationTypeEnum"; export const LocationsMetadata: IConstructorParams[] = [ - { - city: CityName.Aevum, - infiltrationData: { - maxClearanceLevel: 12, - startingSecurityLevel: 8.18, - }, - name: LocationName.AevumAeroCorp, - types: [LocationType.Company], + { + city: CityName.Aevum, + infiltrationData: { + maxClearanceLevel: 12, + startingSecurityLevel: 8.18, }, - { - city: CityName.Aevum, - infiltrationData: { - maxClearanceLevel: 15, - startingSecurityLevel: 8.19, - }, - name: LocationName.AevumBachmanAndAssociates, - types: [LocationType.Company], + name: LocationName.AevumAeroCorp, + types: [LocationType.Company], + }, + { + city: CityName.Aevum, + infiltrationData: { + maxClearanceLevel: 15, + startingSecurityLevel: 8.19, }, - { - city: CityName.Aevum, - infiltrationData: { - maxClearanceLevel: 18, - startingSecurityLevel: 9.55, - }, - name: LocationName.AevumClarkeIncorporated, - types: [LocationType.Company], + name: LocationName.AevumBachmanAndAssociates, + types: [LocationType.Company], + }, + { + city: CityName.Aevum, + infiltrationData: { + maxClearanceLevel: 18, + startingSecurityLevel: 9.55, }, - { - city: CityName.Aevum, - costMult: 3, - expMult: 2, - name: LocationName.AevumCrushFitnessGym, - types: [LocationType.Gym], + name: LocationName.AevumClarkeIncorporated, + types: [LocationType.Company], + }, + { + city: CityName.Aevum, + costMult: 3, + expMult: 2, + name: LocationName.AevumCrushFitnessGym, + types: [LocationType.Gym], + }, + { + city: CityName.Aevum, + infiltrationData: { + maxClearanceLevel: 37, + startingSecurityLevel: 17.02, }, - { - city: CityName.Aevum, - infiltrationData: { - maxClearanceLevel: 37, - startingSecurityLevel: 17.02, - }, - name: LocationName.AevumECorp, - types: [LocationType.Company, LocationType.TechVendor], - techVendorMaxRam: 512, - techVendorMinRam: 128, + name: LocationName.AevumECorp, + types: [LocationType.Company, LocationType.TechVendor], + techVendorMaxRam: 512, + techVendorMinRam: 128, + }, + { + city: CityName.Aevum, + infiltrationData: { + maxClearanceLevel: 25, + startingSecurityLevel: 15.54, }, - { - city: CityName.Aevum, - infiltrationData: { - maxClearanceLevel: 25, - startingSecurityLevel: 15.54, - }, - name: LocationName.AevumFulcrumTechnologies, - types: [LocationType.Company, LocationType.TechVendor], - techVendorMaxRam: 1024, - techVendorMinRam: 256, + name: LocationName.AevumFulcrumTechnologies, + types: [LocationType.Company, LocationType.TechVendor], + techVendorMaxRam: 1024, + techVendorMinRam: 256, + }, + { + city: CityName.Aevum, + infiltrationData: { + maxClearanceLevel: 12, + startingSecurityLevel: 7.89, }, - { - city: CityName.Aevum, - infiltrationData: { - maxClearanceLevel: 12, - startingSecurityLevel: 7.89, - }, - name: LocationName.AevumGalacticCybersystems, - types: [LocationType.Company], + name: LocationName.AevumGalacticCybersystems, + types: [LocationType.Company], + }, + { + city: CityName.Aevum, + infiltrationData: { + maxClearanceLevel: 6, + startingSecurityLevel: 3.29, }, - { - city: CityName.Aevum, - infiltrationData: { - maxClearanceLevel: 6, - startingSecurityLevel: 3.29, - }, - name: LocationName.AevumNetLinkTechnologies, - types: [LocationType.Company, LocationType.TechVendor], - techVendorMaxRam: 64, - techVendorMinRam: 8, + name: LocationName.AevumNetLinkTechnologies, + types: [LocationType.Company, LocationType.TechVendor], + techVendorMaxRam: 64, + techVendorMinRam: 8, + }, + { + city: CityName.Aevum, + infiltrationData: { + maxClearanceLevel: 6, + startingSecurityLevel: 5.35, }, - { - city: CityName.Aevum, - infiltrationData: { - maxClearanceLevel: 6, - startingSecurityLevel: 5.35, - }, - name: LocationName.AevumPolice, - types: [LocationType.Company], + name: LocationName.AevumPolice, + types: [LocationType.Company], + }, + { + city: CityName.Aevum, + infiltrationData: { + maxClearanceLevel: 5, + startingSecurityLevel: 5.02, }, - { - city: CityName.Aevum, - infiltrationData: { - maxClearanceLevel: 5, - startingSecurityLevel: 5.02, - }, - name: LocationName.AevumRhoConstruction, - types: [LocationType.Company], + name: LocationName.AevumRhoConstruction, + types: [LocationType.Company], + }, + { + city: CityName.Aevum, + costMult: 10, + expMult: 5, + name: LocationName.AevumSnapFitnessGym, + types: [LocationType.Gym], + }, + { + city: CityName.Aevum, + costMult: 4, + expMult: 3, + name: LocationName.AevumSummitUniversity, + types: [LocationType.University], + }, + { + city: CityName.Aevum, + infiltrationData: { + maxClearanceLevel: 7, + startingSecurityLevel: 5.85, }, - { - city: CityName.Aevum, - costMult: 10, - expMult: 5, - name: LocationName.AevumSnapFitnessGym, - types: [LocationType.Gym], + name: LocationName.AevumWatchdogSecurity, + types: [LocationType.Company], + }, + { + city: CityName.Aevum, + name: LocationName.AevumCasino, + types: [LocationType.Casino], + }, + { + city: CityName.Chongqing, + infiltrationData: { + maxClearanceLevel: 25, + startingSecurityLevel: 16.25, }, - { - city: CityName.Aevum, - costMult: 4, - expMult: 3, - name: LocationName.AevumSummitUniversity, - types: [LocationType.University], + name: LocationName.ChongqingKuaiGongInternational, + types: [LocationType.Company], + }, + { + city: CityName.Chongqing, + infiltrationData: { + maxClearanceLevel: 18, + startingSecurityLevel: 12.59, }, - { - city: CityName.Aevum, - infiltrationData: { - maxClearanceLevel: 7, - startingSecurityLevel: 5.85, - }, - name: LocationName.AevumWatchdogSecurity, - types: [LocationType.Company], + name: LocationName.ChongqingSolarisSpaceSystems, + types: [LocationType.Company], + }, + { + city: CityName.Ishima, + infiltrationData: { + maxClearanceLevel: 12, + startingSecurityLevel: 5.02, }, - { - city: CityName.Aevum, - name: LocationName.AevumCasino, - types: [LocationType.Casino], + name: LocationName.IshimaNovaMedical, + types: [LocationType.Company], + }, + { + city: CityName.Ishima, + infiltrationData: { + maxClearanceLevel: 10, + startingSecurityLevel: 3.2, }, - { - city: CityName.Chongqing, - infiltrationData: { - maxClearanceLevel: 25, - startingSecurityLevel: 16.25, - }, - name: LocationName.ChongqingKuaiGongInternational, - types: [LocationType.Company], + name: LocationName.IshimaOmegaSoftware, + types: [LocationType.Company, LocationType.TechVendor], + techVendorMaxRam: 128, + techVendorMinRam: 4, + }, + { + city: CityName.Ishima, + infiltrationData: { + maxClearanceLevel: 25, + startingSecurityLevel: 5.38, }, - { - city: CityName.Chongqing, - infiltrationData: { - maxClearanceLevel: 18, - startingSecurityLevel: 12.59, - }, - name: LocationName.ChongqingSolarisSpaceSystems, - types: [LocationType.Company], + name: LocationName.IshimaStormTechnologies, + types: [LocationType.Company, LocationType.TechVendor], + techVendorMaxRam: 512, + techVendorMinRam: 32, + }, + { + city: CityName.NewTokyo, + infiltrationData: { + maxClearanceLevel: 17, + startingSecurityLevel: 7.18, }, - { - city: CityName.Ishima, - infiltrationData: { - maxClearanceLevel: 12, - startingSecurityLevel: 5.02, - }, - name: LocationName.IshimaNovaMedical, - types: [LocationType.Company], + name: LocationName.NewTokyoDefComm, + types: [LocationType.Company], + }, + { + city: CityName.NewTokyo, + infiltrationData: { + maxClearanceLevel: 20, + startingSecurityLevel: 5.9, }, - { - city: CityName.Ishima, - infiltrationData: { - maxClearanceLevel: 10, - startingSecurityLevel: 3.2, - }, - name: LocationName.IshimaOmegaSoftware, - types: [LocationType.Company, LocationType.TechVendor], - techVendorMaxRam: 128, - techVendorMinRam: 4, + name: LocationName.NewTokyoGlobalPharmaceuticals, + types: [LocationType.Company], + }, + { + city: CityName.NewTokyo, + infiltrationData: { + maxClearanceLevel: 5, + startingSecurityLevel: 2.5, }, - { - city: CityName.Ishima, - infiltrationData: { - maxClearanceLevel: 25, - startingSecurityLevel: 5.38, - }, - name: LocationName.IshimaStormTechnologies, - types: [LocationType.Company, LocationType.TechVendor], - techVendorMaxRam: 512, - techVendorMinRam: 32, + name: LocationName.NewTokyoNoodleBar, + types: [LocationType.Company, LocationType.Special], + }, + { + city: CityName.NewTokyo, + infiltrationData: { + maxClearanceLevel: 25, + startingSecurityLevel: 5.52, }, - { - city: CityName.NewTokyo, - infiltrationData: { - maxClearanceLevel: 17, - startingSecurityLevel: 7.18, - }, - name: LocationName.NewTokyoDefComm, - types: [LocationType.Company], + name: LocationName.NewTokyoVitaLife, + types: [LocationType.Company, LocationType.Special], + }, + { + city: CityName.Sector12, + infiltrationData: { + maxClearanceLevel: 10, + startingSecurityLevel: 3.62, }, - { - city: CityName.NewTokyo, - infiltrationData: { - maxClearanceLevel: 20, - startingSecurityLevel: 5.9, - }, - name: LocationName.NewTokyoGlobalPharmaceuticals, - types: [LocationType.Company], + name: LocationName.Sector12AlphaEnterprises, + types: [LocationType.Company, LocationType.TechVendor], + techVendorMaxRam: 8, + techVendorMinRam: 2, + }, + { + city: CityName.Sector12, + infiltrationData: { + maxClearanceLevel: 25, + startingSecurityLevel: 10.59, }, - { - city: CityName.NewTokyo, - infiltrationData: { - maxClearanceLevel: 5, - startingSecurityLevel: 2.5, - }, - name: LocationName.NewTokyoNoodleBar, - types: [LocationType.Company, LocationType.Special], + name: LocationName.Sector12BladeIndustries, + types: [LocationType.Company], + }, + { + city: CityName.Sector12, + name: LocationName.Sector12CIA, + types: [LocationType.Company], + }, + { + city: CityName.Sector12, + infiltrationData: { + maxClearanceLevel: 15, + startingSecurityLevel: 4.66, }, - { - city: CityName.NewTokyo, - infiltrationData: { - maxClearanceLevel: 25, - startingSecurityLevel: 5.52, - }, - name: LocationName.NewTokyoVitaLife, - types: [LocationType.Company, LocationType.Special], + name: LocationName.Sector12CarmichaelSecurity, + types: [LocationType.Company], + }, + { + city: CityName.Sector12, + name: LocationName.Sector12CityHall, + types: [LocationType.Special], + }, + { + city: CityName.Sector12, + infiltrationData: { + maxClearanceLevel: 12, + startingSecurityLevel: 5.9, }, - { - city: CityName.Sector12, - infiltrationData: { - maxClearanceLevel: 10, - startingSecurityLevel: 3.62, - }, - name: LocationName.Sector12AlphaEnterprises, - types: [LocationType.Company, LocationType.TechVendor], - techVendorMaxRam: 8, - techVendorMinRam: 2, + name: LocationName.Sector12DeltaOne, + types: [LocationType.Company], + }, + { + city: CityName.Sector12, + name: LocationName.Sector12FoodNStuff, + types: [LocationType.Company], + }, + { + city: CityName.Sector12, + infiltrationData: { + maxClearanceLevel: 25, + startingSecurityLevel: 8.18, }, - { - city: CityName.Sector12, - infiltrationData: { - maxClearanceLevel: 25, - startingSecurityLevel: 10.59, - }, - name: LocationName.Sector12BladeIndustries, - types: [LocationType.Company], + name: LocationName.Sector12FourSigma, + types: [LocationType.Company], + }, + { + city: CityName.Sector12, + infiltrationData: { + maxClearanceLevel: 17, + startingSecurityLevel: 6.02, }, - { - city: CityName.Sector12, - name: LocationName.Sector12CIA, - types: [LocationType.Company], + name: LocationName.Sector12IcarusMicrosystems, + types: [LocationType.Company], + }, + { + city: CityName.Sector12, + expMult: 1, + costMult: 1, + name: LocationName.Sector12IronGym, + types: [LocationType.Gym], + }, + { + city: CityName.Sector12, + infiltrationData: { + maxClearanceLevel: 5, + startingSecurityLevel: 3.13, }, - { - city: CityName.Sector12, - infiltrationData: { - maxClearanceLevel: 15, - startingSecurityLevel: 4.66, - }, - name: LocationName.Sector12CarmichaelSecurity, - types: [LocationType.Company], + name: LocationName.Sector12JoesGuns, + types: [LocationType.Company], + }, + { + city: CityName.Sector12, + infiltrationData: { + maxClearanceLevel: 31, + startingSecurityLevel: 16.36, }, - { - city: CityName.Sector12, - name: LocationName.Sector12CityHall, - types: [LocationType.Special], + name: LocationName.Sector12MegaCorp, + types: [LocationType.Company], + }, + { + city: CityName.Sector12, + name: LocationName.Sector12NSA, + types: [LocationType.Company, LocationType.Special], + }, + { + city: CityName.Sector12, + costMult: 20, + expMult: 10, + name: LocationName.Sector12PowerhouseGym, + types: [LocationType.Gym], + }, + { + city: CityName.Sector12, + costMult: 3, + expMult: 2, + name: LocationName.Sector12RothmanUniversity, + types: [LocationType.University], + }, + { + city: CityName.Sector12, + infiltrationData: { + maxClearanceLevel: 12, + startingSecurityLevel: 5.9, }, - { - city: CityName.Sector12, - infiltrationData: { - maxClearanceLevel: 12, - startingSecurityLevel: 5.9, - }, - name: LocationName.Sector12DeltaOne, - types: [LocationType.Company], + name: LocationName.Sector12UniversalEnergy, + types: [LocationType.Company], + }, + { + city: CityName.Volhaven, + infiltrationData: { + maxClearanceLevel: 15, + startingSecurityLevel: 3.59, }, - { - city: CityName.Sector12, - name: LocationName.Sector12FoodNStuff, - types: [LocationType.Company], + name: LocationName.VolhavenCompuTek, + types: [LocationType.Company, LocationType.TechVendor], + techVendorMaxRam: 256, + techVendorMinRam: 8, + }, + { + city: CityName.Volhaven, + infiltrationData: { + maxClearanceLevel: 18, + startingSecurityLevel: 7.28, }, - { - city: CityName.Sector12, - infiltrationData: { - maxClearanceLevel: 25, - startingSecurityLevel: 8.18, - }, - name: LocationName.Sector12FourSigma, - types: [LocationType.Company], + name: LocationName.VolhavenHeliosLabs, + types: [LocationType.Company], + }, + { + city: CityName.Volhaven, + infiltrationData: { + maxClearanceLevel: 15, + startingSecurityLevel: 4.35, }, - { - city: CityName.Sector12, - infiltrationData: { - maxClearanceLevel: 17, - startingSecurityLevel: 6.02, - }, - name: LocationName.Sector12IcarusMicrosystems, - types: [LocationType.Company], + name: LocationName.VolhavenLexoCorp, + types: [LocationType.Company], + }, + { + city: CityName.Volhaven, + costMult: 7, + expMult: 4, + name: LocationName.VolhavenMilleniumFitnessGym, + types: [LocationType.Gym], + }, + { + city: CityName.Volhaven, + infiltrationData: { + maxClearanceLevel: 50, + startingSecurityLevel: 8.53, }, - { - city: CityName.Sector12, - expMult: 1, - costMult: 1, - name: LocationName.Sector12IronGym, - types: [LocationType.Gym], + name: LocationName.VolhavenNWO, + types: [LocationType.Company], + }, + { + city: CityName.Volhaven, + infiltrationData: { + maxClearanceLevel: 25, + startingSecurityLevel: 7.74, }, - { - city: CityName.Sector12, - infiltrationData: { - maxClearanceLevel: 5, - startingSecurityLevel: 3.13, - }, - name: LocationName.Sector12JoesGuns, - types: [LocationType.Company], + name: LocationName.VolhavenOmniTekIncorporated, + types: [LocationType.Company, LocationType.TechVendor], + techVendorMaxRam: 1024, + techVendorMinRam: 128, + }, + { + city: CityName.Volhaven, + infiltrationData: { + maxClearanceLevel: 22, + startingSecurityLevel: 6, }, - { - city: CityName.Sector12, - infiltrationData: { - maxClearanceLevel: 31, - startingSecurityLevel: 16.36, - }, - name: LocationName.Sector12MegaCorp, - types: [LocationType.Company], + name: LocationName.VolhavenOmniaCybersystems, + types: [LocationType.Company], + }, + { + city: CityName.Volhaven, + infiltrationData: { + maxClearanceLevel: 18, + startingSecurityLevel: 4.77, }, - { - city: CityName.Sector12, - name: LocationName.Sector12NSA, - types: [LocationType.Company, LocationType.Special], - }, - { - city: CityName.Sector12, - costMult: 20, - expMult: 10, - name: LocationName.Sector12PowerhouseGym, - types: [LocationType.Gym], - }, - { - city: CityName.Sector12, - costMult: 3, - expMult: 2, - name: LocationName.Sector12RothmanUniversity, - types: [LocationType.University], - }, - { - city: CityName.Sector12, - infiltrationData: { - maxClearanceLevel: 12, - startingSecurityLevel: 5.9, - }, - name: LocationName.Sector12UniversalEnergy, - types: [LocationType.Company], - }, - { - city: CityName.Volhaven, - infiltrationData: { - maxClearanceLevel: 15, - startingSecurityLevel: 3.59, - }, - name: LocationName.VolhavenCompuTek, - types: [LocationType.Company, LocationType.TechVendor], - techVendorMaxRam: 256, - techVendorMinRam: 8, - }, - { - city: CityName.Volhaven, - infiltrationData: { - maxClearanceLevel: 18, - startingSecurityLevel: 7.28, - }, - name: LocationName.VolhavenHeliosLabs, - types: [LocationType.Company], - }, - { - city: CityName.Volhaven, - infiltrationData: { - maxClearanceLevel: 15, - startingSecurityLevel: 4.35, - }, - name: LocationName.VolhavenLexoCorp, - types: [LocationType.Company], - }, - { - city: CityName.Volhaven, - costMult: 7, - expMult: 4, - name: LocationName.VolhavenMilleniumFitnessGym, - types: [LocationType.Gym], - }, - { - city: CityName.Volhaven, - infiltrationData: { - maxClearanceLevel: 50, - startingSecurityLevel: 8.53, - }, - name: LocationName.VolhavenNWO, - types: [LocationType.Company], - }, - { - city: CityName.Volhaven, - infiltrationData: { - maxClearanceLevel: 25, - startingSecurityLevel: 7.74, - }, - name: LocationName.VolhavenOmniTekIncorporated, - types: [LocationType.Company, LocationType.TechVendor], - techVendorMaxRam: 1024, - techVendorMinRam: 128, - }, - { - city: CityName.Volhaven, - infiltrationData: { - maxClearanceLevel: 22, - startingSecurityLevel: 6, - }, - name: LocationName.VolhavenOmniaCybersystems, - types: [LocationType.Company], - }, - { - city: CityName.Volhaven, - infiltrationData: { - maxClearanceLevel: 18, - startingSecurityLevel: 4.77, - }, - name: LocationName.VolhavenSysCoreSecurities, - types: [LocationType.Company], - }, - { - city: CityName.Volhaven, - costMult: 5, - expMult: 4, - name: LocationName.VolhavenZBInstituteOfTechnology, - types: [LocationType.University], - }, - { - city: null, - name: LocationName.Hospital, - types: [LocationType.Hospital], - }, - { - city: null, - name: LocationName.Slums, - types: [LocationType.Slums], - }, - { - city: null, - name: LocationName.TravelAgency, - types: [LocationType.TravelAgency], - }, - { - city: null, - name: LocationName.WorldStockExchange, - types: [LocationType.StockMarket], - }, -]; \ No newline at end of file + name: LocationName.VolhavenSysCoreSecurities, + types: [LocationType.Company], + }, + { + city: CityName.Volhaven, + costMult: 5, + expMult: 4, + name: LocationName.VolhavenZBInstituteOfTechnology, + types: [LocationType.University], + }, + { + city: null, + name: LocationName.Hospital, + types: [LocationType.Hospital], + }, + { + city: null, + name: LocationName.Slums, + types: [LocationType.Slums], + }, + { + city: null, + name: LocationName.TravelAgency, + types: [LocationType.TravelAgency], + }, + { + city: null, + name: LocationName.WorldStockExchange, + types: [LocationType.StockMarket], + }, +]; diff --git a/src/Locations/ui/ApplyToJobButton.tsx b/src/Locations/ui/ApplyToJobButton.tsx index 1abea2ee3..7f22cf14a 100644 --- a/src/Locations/ui/ApplyToJobButton.tsx +++ b/src/Locations/ui/ApplyToJobButton.tsx @@ -3,46 +3,53 @@ */ import * as React from "react"; -import { Company } from "../../Company/Company"; -import { CompanyPosition } from "../../Company/CompanyPosition"; -import { getJobRequirementText } from "../../Company/GetJobRequirementText"; -import { IPlayer } from "../../PersonObjects/IPlayer"; +import { Company } from "../../Company/Company"; +import { CompanyPosition } from "../../Company/CompanyPosition"; +import { getJobRequirementText } from "../../Company/GetJobRequirementText"; +import { IPlayer } from "../../PersonObjects/IPlayer"; -import { StdButton } from "../../ui/React/StdButton"; +import { StdButton } from "../../ui/React/StdButton"; type IProps = { - company: Company; - entryPosType: CompanyPosition; - onClick: (e: React.MouseEvent) => void; - p: IPlayer; - style?: any; - text: string; -} + company: Company; + entryPosType: CompanyPosition; + onClick: (e: React.MouseEvent) => void; + p: IPlayer; + style?: any; + text: string; +}; export class ApplyToJobButton extends React.Component { - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.getJobRequirementTooltip = this.getJobRequirementTooltip.bind(this); + this.getJobRequirementTooltip = this.getJobRequirementTooltip.bind(this); + } + + getJobRequirementTooltip(): string { + const pos = this.props.p.getNextCompanyPosition( + this.props.company, + this.props.entryPosType, + ); + if (pos == null) { + return ""; } - getJobRequirementTooltip(): string { - const pos = this.props.p.getNextCompanyPosition(this.props.company, this.props.entryPosType); - if (pos == null) { return "" } - - if (!this.props.company.hasPosition(pos)) { return ""; } - - return getJobRequirementText(this.props.company, pos, true); + if (!this.props.company.hasPosition(pos)) { + return ""; } - render(): React.ReactNode { - return ( - - ) - } + return getJobRequirementText(this.props.company, pos, true); + } + + render(): React.ReactNode { + return ( + + ); + } } diff --git a/src/Locations/ui/CasinoLocation.tsx b/src/Locations/ui/CasinoLocation.tsx index ecc3fd48c..5aeb5bb05 100644 --- a/src/Locations/ui/CasinoLocation.tsx +++ b/src/Locations/ui/CasinoLocation.tsx @@ -11,95 +11,102 @@ import { SlotMachine } from "../../Casino/SlotMachine"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { StdButton } from "../../ui/React/StdButton"; - enum GameType { - None = 'none', - Coin = 'coin', - Slots = 'slots', - Roulette = 'roulette', - Blackjack = 'blackjack', + None = "none", + Coin = "coin", + Slots = "slots", + Roulette = "roulette", + Blackjack = "blackjack", } type IProps = { - p: IPlayer; -} + p: IPlayer; +}; type IState = { - game: GameType; -} + game: GameType; +}; export class CasinoLocation extends React.Component { - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.state = { - game: GameType.None, - } + this.state = { + game: GameType.None, + }; - this.updateGame = this.updateGame.bind(this); + this.updateGame = this.updateGame.bind(this); + } + + updateGame(game: GameType): void { + this.setState({ + game, + }); + } + + renderGames(): React.ReactNode { + return ( + <> + this.updateGame(GameType.Coin)} + text={"Play coin flip"} + /> +
    + this.updateGame(GameType.Slots)} + text={"Play slots"} + /> +
    + this.updateGame(GameType.Roulette)} + text={"Play roulette"} + /> +
    + this.updateGame(GameType.Blackjack)} + text={"Play blackjack"} + /> + + ); + } + + renderGame(): React.ReactNode { + let elem = null; + switch (this.state.game) { + case GameType.Coin: + elem = ; + break; + case GameType.Slots: + elem = ; + break; + case GameType.Roulette: + elem = ; + break; + case GameType.Blackjack: + elem = ; + break; + case GameType.None: + break; + default: + throw new Error(`MissingCaseException: ${this.state.game}`); } - updateGame(game: GameType): void { - this.setState({ - game, - }); - } + return ( + <> + this.updateGame(GameType.None)} + text={"Stop playing"} + /> + {elem} + + ); + } - renderGames(): React.ReactNode { - return (<> - this.updateGame(GameType.Coin)} - text={"Play coin flip"} - />
    - this.updateGame(GameType.Slots)} - text={"Play slots"} - />
    - this.updateGame(GameType.Roulette)} - text={"Play roulette"} - />
    - this.updateGame(GameType.Blackjack)} - text={"Play blackjack"} - /> - ) + render(): React.ReactNode { + if (this.state.game === GameType.None) { + return this.renderGames(); + } else { + return this.renderGame(); } - - renderGame(): React.ReactNode { - let elem = null; - switch(this.state.game) { - case GameType.Coin: - elem = - break; - case GameType.Slots: - elem = - break; - case GameType.Roulette: - elem = - break; - case GameType.Blackjack: - elem = - break; - case GameType.None: - break; - default: - throw new Error(`MissingCaseException: ${this.state.game}`); - } - - return ( - <> - this.updateGame(GameType.None)} text={"Stop playing"} /> - {elem} - - ) - } - - render(): React.ReactNode { - if(this.state.game === GameType.None) { - return this.renderGames(); - } else { - return this.renderGame(); - } - } -} \ No newline at end of file + } +} diff --git a/src/Locations/ui/City.tsx b/src/Locations/ui/City.tsx index 382cd0ef3..9b612133e 100644 --- a/src/Locations/ui/City.tsx +++ b/src/Locations/ui/City.tsx @@ -12,83 +12,111 @@ import { Settings } from "../../Settings/Settings"; import { StdButton } from "../../ui/React/StdButton"; type IProps = { - city: City; - enterLocation: (to: LocationName) => void; -} + city: City; + enterLocation: (to: LocationName) => void; +}; export class LocationCity extends React.Component { - asciiCity(): React.ReactNode { - const LocationLetter = (location: LocationName): JSX.Element => { - if (location) - return - X - - return * - } - - const locationLettersRegex = /[A-Z]/g; - const letterMap: any = {'A': 0,'B': 1,'C': 2,'D': 3,'E': 4,'F': 5,'G': 6, - 'H': 7,'I': 8,'J': 9,'K': 10,'L': 11,'M': 12,'N': 13,'O': 14, - 'P': 15,'Q': 16,'R': 17,'S': 18,'T': 19,'U': 20,'V': 21,'W': 22, - 'X': 23,'Y': 24,'Z': 25} - - const lineElems = (s: string): JSX.Element[] => { - const elems: any[] = []; - const matches: any[] = []; - let match: any; - while ((match = locationLettersRegex.exec(s)) !== null) { - matches.push(match); - } - if (matches.length === 0) { - elems.push(s); - return elems; - } - - for(let i = 0; i < matches.length; i++) { - const startI = i === 0 ? 0 : matches[i-1].index+1; - const endI = matches[i].index; - elems.push(s.slice(startI, endI)) - const locationI = letterMap[s[matches[i].index]]; - elems.push(LocationLetter(this.props.city.locations[locationI])) - } - elems.push(s.slice(matches[matches.length-1].index+1)) - return elems; - } - - const elems: JSX.Element[] = []; - const lines = this.props.city.asciiArt.split('\n'); - for(const i in lines) { - elems.push(
    {lineElems(lines[i])}
    ) - } - - return
    {elems}
    ; - } - - listCity(): React.ReactNode { - const locationButtons = this.props.city.locations.map((locName) => { - return ( -
  • - -
  • - ) - }); - + asciiCity(): React.ReactNode { + const LocationLetter = (location: LocationName): JSX.Element => { + if (location) return ( -
      - {locationButtons} -
    - ) + + X + + ); + return *; + }; + + const locationLettersRegex = /[A-Z]/g; + const letterMap: any = { + A: 0, + B: 1, + C: 2, + D: 3, + E: 4, + F: 5, + G: 6, + H: 7, + I: 8, + J: 9, + K: 10, + L: 11, + M: 12, + N: 13, + O: 14, + P: 15, + Q: 16, + R: 17, + S: 18, + T: 19, + U: 20, + V: 21, + W: 22, + X: 23, + Y: 24, + Z: 25, + }; + + const lineElems = (s: string): JSX.Element[] => { + const elems: any[] = []; + const matches: any[] = []; + let match: any; + while ((match = locationLettersRegex.exec(s)) !== null) { + matches.push(match); + } + if (matches.length === 0) { + elems.push(s); + return elems; + } + + for (let i = 0; i < matches.length; i++) { + const startI = i === 0 ? 0 : matches[i - 1].index + 1; + const endI = matches[i].index; + elems.push(s.slice(startI, endI)); + const locationI = letterMap[s[matches[i].index]]; + elems.push(LocationLetter(this.props.city.locations[locationI])); + } + elems.push(s.slice(matches[matches.length - 1].index + 1)); + return elems; + }; + + const elems: JSX.Element[] = []; + const lines = this.props.city.asciiArt.split("\n"); + for (const i in lines) { + elems.push(
    {lineElems(lines[i])}
    ); } - render(): React.ReactNode { - return ( - <> - {Settings.DisableASCIIArt ? this.listCity() : this.asciiCity()} - - ) - } -} \ No newline at end of file + return
    {elems}
    ; + } + + listCity(): React.ReactNode { + const locationButtons = this.props.city.locations.map((locName) => { + return ( +
  • + +
  • + ); + }); + + return
      {locationButtons}
    ; + } + + render(): React.ReactNode { + return <>{Settings.DisableASCIIArt ? this.listCity() : this.asciiCity()}; + } +} diff --git a/src/Locations/ui/CompanyLocation.tsx b/src/Locations/ui/CompanyLocation.tsx index 63777600e..0bf8d67af 100644 --- a/src/Locations/ui/CompanyLocation.tsx +++ b/src/Locations/ui/CompanyLocation.tsx @@ -5,407 +5,464 @@ */ import * as React from "react"; -import { ApplyToJobButton } from "./ApplyToJobButton"; +import { ApplyToJobButton } from "./ApplyToJobButton"; -import { Location } from "../Location"; -import { Locations } from "../Locations"; -import { LocationName } from "../data/LocationNames"; +import { Location } from "../Location"; +import { Locations } from "../Locations"; +import { LocationName } from "../data/LocationNames"; -import { IEngine } from "../../IEngine"; +import { IEngine } from "../../IEngine"; -import { Companies } from "../../Company/Companies"; -import { Company } from "../../Company/Company"; -import { CompanyPosition } from "../../Company/CompanyPosition"; -import { CompanyPositions } from "../../Company/CompanyPositions"; -import * as posNames from "../../Company/data/companypositionnames"; -import { IPlayer } from "../../PersonObjects/IPlayer"; +import { Companies } from "../../Company/Companies"; +import { Company } from "../../Company/Company"; +import { CompanyPosition } from "../../Company/CompanyPosition"; +import { CompanyPositions } from "../../Company/CompanyPositions"; +import * as posNames from "../../Company/data/companypositionnames"; +import { IPlayer } from "../../PersonObjects/IPlayer"; -import { StdButton } from "../../ui/React/StdButton"; -import { Reputation } from "../../ui/React/Reputation"; -import { Favor } from "../../ui/React/Favor"; +import { StdButton } from "../../ui/React/StdButton"; +import { Reputation } from "../../ui/React/Reputation"; +import { Favor } from "../../ui/React/Favor"; import { - yesNoBoxGetYesButton, - yesNoBoxGetNoButton, - yesNoBoxClose, - yesNoBoxCreate, + yesNoBoxGetYesButton, + yesNoBoxGetNoButton, + yesNoBoxClose, + yesNoBoxCreate, } from "../../../utils/YesNoBox"; type IProps = { - engine: IEngine; - locName: LocationName; - p: IPlayer; -} + engine: IEngine; + locName: LocationName; + p: IPlayer; +}; type IState = { - employedHere: boolean; -} + employedHere: boolean; +}; const blockStyleMarkup = { - display: "block", -} + display: "block", +}; export class CompanyLocation extends React.Component { - /** - * We'll keep a reference to the Company that this component is being rendered for, - * so we don't have to look it up every time - */ - company: Company; + /** + * We'll keep a reference to the Company that this component is being rendered for, + * so we don't have to look it up every time + */ + company: Company; - /** - * CompanyPosition object for the job that the player holds at this company - * (if he has one) - */ - companyPosition: CompanyPosition | null = null; + /** + * CompanyPosition object for the job that the player holds at this company + * (if he has one) + */ + companyPosition: CompanyPosition | null = null; - /** - * Stores button styling that sets them all to block display - */ - btnStyle: any; + /** + * Stores button styling that sets them all to block display + */ + btnStyle: any; - /** - * Reference to the Location that this component is being rendered for - */ - location: Location; + /** + * Reference to the Location that this component is being rendered for + */ + location: Location; - /** - * Name of company position that player holds, if applicable - */ - jobTitle: string | null = null; + /** + * Name of company position that player holds, if applicable + */ + jobTitle: string | null = null; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.btnStyle = { display: "block" }; + this.btnStyle = { display: "block" }; - this.quit = this.quit.bind(this); - this.applyForAgentJob = this.applyForAgentJob.bind(this); - this.applyForBusinessConsultantJob = this.applyForBusinessConsultantJob.bind(this); - this.applyForBusinessJob = this.applyForBusinessJob.bind(this); - this.applyForEmployeeJob = this.applyForEmployeeJob.bind(this); - this.applyForItJob = this.applyForItJob.bind(this); - this.applyForPartTimeEmployeeJob = this.applyForPartTimeEmployeeJob.bind(this); - this.applyForPartTimeWaiterJob = this.applyForPartTimeWaiterJob.bind(this); - this.applyForSecurityJob = this.applyForSecurityJob.bind(this); - this.applyForSoftwareConsultantJob = this.applyForSoftwareConsultantJob.bind(this); - this.applyForSoftwareJob = this.applyForSoftwareJob.bind(this); - this.applyForWaiterJob = this.applyForWaiterJob.bind(this); - this.startInfiltration = this.startInfiltration.bind(this); - this.work = this.work.bind(this); + this.quit = this.quit.bind(this); + this.applyForAgentJob = this.applyForAgentJob.bind(this); + this.applyForBusinessConsultantJob = + this.applyForBusinessConsultantJob.bind(this); + this.applyForBusinessJob = this.applyForBusinessJob.bind(this); + this.applyForEmployeeJob = this.applyForEmployeeJob.bind(this); + this.applyForItJob = this.applyForItJob.bind(this); + this.applyForPartTimeEmployeeJob = + this.applyForPartTimeEmployeeJob.bind(this); + this.applyForPartTimeWaiterJob = this.applyForPartTimeWaiterJob.bind(this); + this.applyForSecurityJob = this.applyForSecurityJob.bind(this); + this.applyForSoftwareConsultantJob = + this.applyForSoftwareConsultantJob.bind(this); + this.applyForSoftwareJob = this.applyForSoftwareJob.bind(this); + this.applyForWaiterJob = this.applyForWaiterJob.bind(this); + this.startInfiltration = this.startInfiltration.bind(this); + this.work = this.work.bind(this); - this.location = Locations[props.locName]; - if (this.location == null) { - throw new Error(`CompanyLocation component constructed with invalid location: ${props.locName}`); - } - - this.company = Companies[props.locName]; - if (this.company == null) { - throw new Error(`CompanyLocation component constructed with invalid company: ${props.locName}`); - } - - this.state = { - employedHere: false, - } - - this.props.p.location = props.locName; - - this.checkIfEmployedHere(false); + this.location = Locations[props.locName]; + if (this.location == null) { + throw new Error( + `CompanyLocation component constructed with invalid location: ${props.locName}`, + ); } - applyForAgentJob(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - this.props.p.applyForAgentJob(); - this.checkIfEmployedHere(true); + this.company = Companies[props.locName]; + if (this.company == null) { + throw new Error( + `CompanyLocation component constructed with invalid company: ${props.locName}`, + ); } - applyForBusinessConsultantJob(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - this.props.p.applyForBusinessConsultantJob(); - this.checkIfEmployedHere(true); + this.state = { + employedHere: false, + }; + + this.props.p.location = props.locName; + + this.checkIfEmployedHere(false); + } + + applyForAgentJob(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + this.props.p.applyForAgentJob(); + this.checkIfEmployedHere(true); + } + + applyForBusinessConsultantJob(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + this.props.p.applyForBusinessConsultantJob(); + this.checkIfEmployedHere(true); + } + + applyForBusinessJob(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + this.props.p.applyForBusinessJob(); + this.checkIfEmployedHere(true); + } + + applyForEmployeeJob(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + this.props.p.applyForEmployeeJob(); + this.checkIfEmployedHere(true); + } + + applyForItJob(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + this.props.p.applyForItJob(); + this.checkIfEmployedHere(true); + } + + applyForPartTimeEmployeeJob(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + this.props.p.applyForPartTimeEmployeeJob(); + this.checkIfEmployedHere(true); + } + + applyForPartTimeWaiterJob(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + this.props.p.applyForPartTimeWaiterJob(); + this.checkIfEmployedHere(true); + } + + applyForSecurityJob(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + this.props.p.applyForSecurityJob(); + this.checkIfEmployedHere(true); + } + + applyForSoftwareConsultantJob(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + this.props.p.applyForSoftwareConsultantJob(); + this.checkIfEmployedHere(true); + } + + applyForSoftwareJob(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + this.props.p.applyForSoftwareJob(); + this.checkIfEmployedHere(true); + } + + applyForWaiterJob(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + this.props.p.applyForWaiterJob(); + this.checkIfEmployedHere(true); + } + + checkIfEmployedHere(updateState = false): void { + this.jobTitle = this.props.p.jobs[this.props.locName]; + if (this.jobTitle != null) { + this.companyPosition = CompanyPositions[this.jobTitle]; } - applyForBusinessJob(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - this.props.p.applyForBusinessJob(); - this.checkIfEmployedHere(true); + if (updateState) { + this.setState({ + employedHere: this.jobTitle != null, + }); + } + } + + startInfiltration(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + const loc = this.location; + if (!loc.infiltrationData) { + console.error( + `trying to start infiltration at ${this.props.locName} but the infiltrationData is null`, + ); + return; } - applyForEmployeeJob(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - this.props.p.applyForEmployeeJob(); - this.checkIfEmployedHere(true); + this.props.engine.loadInfiltrationContent( + this.props.locName, + loc.infiltrationData.startingSecurityLevel, + loc.infiltrationData.maxClearanceLevel, + ); + + const data = loc.infiltrationData; + if (data == null) { + return; + } + } + + work(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } - applyForItJob(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - this.props.p.applyForItJob(); - this.checkIfEmployedHere(true); + const pos = this.companyPosition; + if (pos instanceof CompanyPosition) { + if ( + pos.isPartTimeJob() || + pos.isSoftwareConsultantJob() || + pos.isBusinessConsultantJob() + ) { + this.props.p.startWorkPartTime(this.props.locName); + } else { + this.props.p.startWork(this.props.locName); + } + } + } + + quit(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } - applyForPartTimeEmployeeJob(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - this.props.p.applyForPartTimeEmployeeJob(); - this.checkIfEmployedHere(true); + const yesBtn = yesNoBoxGetYesButton(); + const noBtn = yesNoBoxGetNoButton(); + if (yesBtn == null || noBtn == null) { + return; } + yesBtn.innerHTML = "Quit job"; + noBtn.innerHTML = "Cancel"; + yesBtn.addEventListener("click", () => { + this.props.p.quitJob(this.props.locName); + this.checkIfEmployedHere(true); + yesNoBoxClose(); + }); + noBtn.addEventListener("click", () => { + yesNoBoxClose(); + }); - applyForPartTimeWaiterJob(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - this.props.p.applyForPartTimeWaiterJob(); - this.checkIfEmployedHere(true); - } + yesNoBoxCreate( + <>Would you like to quit your job at {this.company.name}?, + ); + } - applyForSecurityJob(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - this.props.p.applyForSecurityJob(); - this.checkIfEmployedHere(true); - } + render(): React.ReactNode { + const isEmployedHere = this.jobTitle != null; + const favorGain = this.company.getFavorGain(); - applyForSoftwareConsultantJob(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - this.props.p.applyForSoftwareConsultantJob(); - this.checkIfEmployedHere(true); - } - - applyForSoftwareJob(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - this.props.p.applyForSoftwareJob(); - this.checkIfEmployedHere(true); - } - - applyForWaiterJob(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - this.props.p.applyForWaiterJob(); - this.checkIfEmployedHere(true); - } - - checkIfEmployedHere(updateState=false): void { - this.jobTitle = this.props.p.jobs[this.props.locName]; - if (this.jobTitle != null) { - this.companyPosition = CompanyPositions[this.jobTitle]; - } - - if (updateState) { - this.setState({ - employedHere: this.jobTitle != null, - }); - } - } - - startInfiltration(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - const loc = this.location; - if (!loc.infiltrationData) { - console.error(`trying to start infiltration at ${this.props.locName} but the infiltrationData is null`); - return; - } - - this.props.engine.loadInfiltrationContent(this.props.locName, loc.infiltrationData.startingSecurityLevel, loc.infiltrationData.maxClearanceLevel); - - const data = loc.infiltrationData; - if (data == null) { return; } - } - - work(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - - const pos = this.companyPosition; - if (pos instanceof CompanyPosition) { - if (pos.isPartTimeJob() || pos.isSoftwareConsultantJob() || pos.isBusinessConsultantJob()) { - this.props.p.startWorkPartTime(this.props.locName); - } else { - this.props.p.startWork(this.props.locName); + return ( +
    + {isEmployedHere && ( +
    +

    Job Title: {this.jobTitle}

    +
    +

    -------------------------

    +
    +

    + Company reputation: {Reputation(this.company.playerReputation)} + + You will earn {Favor(favorGain[0])} company favor upon resetting + after installing Augmentations + +

    +
    +
    +

    -------------------------

    +
    +

    + Company Favor: {Favor(this.company.favor)} + + Company favor increases the rate at which you earn reputation + for this company by 1% per favor. Company favor is gained + whenever you reset after installing Augmentations. The amount of + favor you gain depends on how much reputation you have with the + comapny. + +

    +
    +
    +

    -------------------------

    +
    + +      + +
    + )} + {this.company.hasAgentPositions() && ( + + )} + {this.company.hasBusinessConsultantPositions() && ( + ): void { - if (!e.isTrusted) { return; } - - const yesBtn = yesNoBoxGetYesButton(); - const noBtn = yesNoBoxGetNoButton(); - if (yesBtn == null || noBtn == null) { return; } - yesBtn.innerHTML = "Quit job"; - noBtn.innerHTML = "Cancel"; - yesBtn.addEventListener("click", () => { - this.props.p.quitJob(this.props.locName); - this.checkIfEmployedHere(true); - yesNoBoxClose(); - }); - noBtn.addEventListener("click", () => { - yesNoBoxClose(); - }); - - yesNoBoxCreate(<>Would you like to quit your job at {this.company.name}?); - } - - render(): React.ReactNode { - const isEmployedHere = this.jobTitle != null; - const favorGain = this.company.getFavorGain(); - - return ( -
    - { - isEmployedHere && -
    -

    Job Title: {this.jobTitle}

    -

    -------------------------


    -

    - Company reputation: {Reputation(this.company.playerReputation)} - - You will earn {Favor(favorGain[0])} company - favor upon resetting after installing Augmentations - -


    -

    -------------------------


    -

    - Company Favor: {Favor(this.company.favor)} - - Company favor increases the rate at which you earn reputation for this company by - 1% per favor. Company favor is gained whenever you reset after installing Augmentations. The amount - of favor you gain depends on how much reputation you have with the comapny. - -


    -

    -------------------------


    -      - -
    - } - { - this.company.hasAgentPositions() && - - } - { - this.company.hasBusinessConsultantPositions() && - - } - { - this.company.hasBusinessPositions() && - - } - { - this.company.hasEmployeePositions() && - - } - { - this.company.hasEmployeePositions() && - - } - { - this.company.hasITPositions() && - - } - { - this.company.hasSecurityPositions() && - - } - { - this.company.hasSoftwareConsultantPositions() && - - } - { - this.company.hasSoftwarePositions() && - - } - { - this.company.hasWaiterPositions() && - - } - { - this.company.hasWaiterPositions() && - - } - { - (this.location.infiltrationData != null) && - - } -
    - ) - } + onClick={this.applyForBusinessConsultantJob} + p={this.props.p} + style={this.btnStyle} + text={"Apply for Business Consultant Job"} + /> + )} + {this.company.hasBusinessPositions() && ( + + )} + {this.company.hasEmployeePositions() && ( + + )} + {this.company.hasEmployeePositions() && ( + + )} + {this.company.hasITPositions() && ( + + )} + {this.company.hasSecurityPositions() && ( + + )} + {this.company.hasSoftwareConsultantPositions() && ( + + )} + {this.company.hasSoftwarePositions() && ( + + )} + {this.company.hasWaiterPositions() && ( + + )} + {this.company.hasWaiterPositions() && ( + + )} + {this.location.infiltrationData != null && ( + + )} +
    + ); + } } diff --git a/src/Locations/ui/CoresButton.tsx b/src/Locations/ui/CoresButton.tsx index c2f489c31..609f8e644 100644 --- a/src/Locations/ui/CoresButton.tsx +++ b/src/Locations/ui/CoresButton.tsx @@ -1,72 +1,87 @@ import React, { useState } from "react"; -import { Location } from "../Location"; -import { createPurchaseServerPopup, - createUpgradeHomeCoresPopup, - purchaseTorRouter } from "../LocationsHelpers"; +import { Location } from "../Location"; +import { + createPurchaseServerPopup, + createUpgradeHomeCoresPopup, + purchaseTorRouter, +} from "../LocationsHelpers"; -import { CONSTANTS } from "../../Constants"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { purchaseRamForHomeComputer } from "../../Server/ServerPurchases"; +import { CONSTANTS } from "../../Constants"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { purchaseRamForHomeComputer } from "../../Server/ServerPurchases"; -import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased"; -import { StdButton } from "../../ui/React/StdButton"; -import { Money } from "../../ui/React/Money"; -import { MathComponent } from 'mathjax-react'; +import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased"; +import { StdButton } from "../../ui/React/StdButton"; +import { Money } from "../../ui/React/Money"; +import { MathComponent } from "mathjax-react"; type IProps = { - p: IPlayer; -} + p: IPlayer; +}; export function CoresButton(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender(old => !old); - } + const setRerender = useState(false)[1]; + function rerender(): void { + setRerender((old) => !old); + } - const btnStyle = { display: "block" }; + const btnStyle = { display: "block" }; - const homeComputer = props.p.getHomeComputer(); - const maxCores = homeComputer.cpuCores >= 8; - if(maxCores) { - return (); - } - - const cost = 1e9*Math.pow(7.5, homeComputer.cpuCores); - - function buy(): void { - if(maxCores) return; - if (!props.p.canAfford(cost)) return; - props.p.loseMoney(cost); - homeComputer.cpuCores++; - rerender(); - } - const settings = { - CommonHTML: { - scale: 90 - }, - "HTML-CSS": { - scale: 90 - }, - NativeMML: { - scale: 90 - }, - SVG: { - scale: 90 - }, - PreviewHTML: { - scale: 90 - } - } - - return (= 8; + if (maxCores) { + return ( + Upgrade 'home' cores ({homeComputer.cpuCores} -> {homeComputer.cpuCores+1}) - } - tooltip={} - />); + text={"Upgrade 'home' cores - MAX"} + /> + ); + } + + const cost = 1e9 * Math.pow(7.5, homeComputer.cpuCores); + + function buy(): void { + if (maxCores) return; + if (!props.p.canAfford(cost)) return; + props.p.loseMoney(cost); + homeComputer.cpuCores++; + rerender(); + } + const settings = { + CommonHTML: { + scale: 90, + }, + "HTML-CSS": { + scale: 90, + }, + NativeMML: { + scale: 90, + }, + SVG: { + scale: 90, + }, + PreviewHTML: { + scale: 90, + }, + }; + + return ( + + Upgrade 'home' cores ({homeComputer.cpuCores} ->{" "} + {homeComputer.cpuCores + 1}) - + + } + tooltip={ + + } + /> + ); } diff --git a/src/Locations/ui/GenericLocation.tsx b/src/Locations/ui/GenericLocation.tsx index 3cdd6b849..d82bd92fe 100644 --- a/src/Locations/ui/GenericLocation.tsx +++ b/src/Locations/ui/GenericLocation.tsx @@ -6,166 +6,158 @@ */ import * as React from "react"; -import { CompanyLocation } from "./CompanyLocation"; -import { GymLocation } from "./GymLocation"; -import { HospitalLocation } from "./HospitalLocation"; -import { SlumsLocation } from "./SlumsLocation"; -import { SpecialLocation } from "./SpecialLocation"; -import { TechVendorLocation } from "./TechVendorLocation"; -import { TravelAgencyLocation } from "./TravelAgencyLocation"; -import { UniversityLocation } from "./UniversityLocation"; -import { CasinoLocation } from "./CasinoLocation"; +import { CompanyLocation } from "./CompanyLocation"; +import { GymLocation } from "./GymLocation"; +import { HospitalLocation } from "./HospitalLocation"; +import { SlumsLocation } from "./SlumsLocation"; +import { SpecialLocation } from "./SpecialLocation"; +import { TechVendorLocation } from "./TechVendorLocation"; +import { TravelAgencyLocation } from "./TravelAgencyLocation"; +import { UniversityLocation } from "./UniversityLocation"; +import { CasinoLocation } from "./CasinoLocation"; -import { Location } from "../Location"; -import { LocationType } from "../LocationTypeEnum"; -import { CityName } from "../data/CityNames"; +import { Location } from "../Location"; +import { LocationType } from "../LocationTypeEnum"; +import { CityName } from "../data/CityNames"; -import { IEngine } from "../../IEngine"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { Settings } from "../../Settings/Settings"; +import { IEngine } from "../../IEngine"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { Settings } from "../../Settings/Settings"; -import { SpecialServerIps } from "../../Server/SpecialServerIps"; -import { getServer, isBackdoorInstalled } from "../../Server/ServerHelpers"; +import { SpecialServerIps } from "../../Server/SpecialServerIps"; +import { getServer, isBackdoorInstalled } from "../../Server/ServerHelpers"; -import { StdButton } from "../../ui/React/StdButton"; -import { CorruptableText } from "../../ui/React/CorruptableText"; +import { StdButton } from "../../ui/React/StdButton"; +import { CorruptableText } from "../../ui/React/CorruptableText"; type IProps = { - engine: IEngine; - loc: Location; - p: IPlayer; - returnToCity: () => void; - travel: (to: CityName) => void; -} + engine: IEngine; + loc: Location; + p: IPlayer; + returnToCity: () => void; + travel: (to: CityName) => void; +}; export class GenericLocation extends React.Component { - /** - * Stores button styling that sets them all to block display - */ - btnStyle: any; + /** + * Stores button styling that sets them all to block display + */ + btnStyle: any; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.btnStyle = { display: "block" }; + this.btnStyle = { display: "block" }; + } + + /** + * Determine what needs to be rendered for this location based on the locations + * type. Returns an array of React components that should be rendered + */ + getLocationSpecificContent(): React.ReactNode[] { + const content: React.ReactNode[] = []; + + if (this.props.loc.types.includes(LocationType.Company)) { + content.push( + , + ); } - /** - * Determine what needs to be rendered for this location based on the locations - * type. Returns an array of React components that should be rendered - */ - getLocationSpecificContent(): React.ReactNode[] { - const content: React.ReactNode[] = []; - - if (this.props.loc.types.includes(LocationType.Company)) { - content.push( - , - ) - } - - if (this.props.loc.types.includes(LocationType.Gym)) { - content.push( - , - ) - } - - if (this.props.loc.types.includes(LocationType.Hospital)) { - content.push( - , - ) - } - - if (this.props.loc.types.includes(LocationType.Slums)) { - content.push( - , - ) - } - - if (this.props.loc.types.includes(LocationType.Special)) { - content.push( - , - ) - } - - if (this.props.loc.types.includes(LocationType.TechVendor)) { - content.push( - , - ) - } - - if (this.props.loc.types.includes(LocationType.TravelAgency)) { - content.push( - , - ) - } - - if (this.props.loc.types.includes(LocationType.University)) { - content.push( - , - ) - } - - if (this.props.loc.types.includes(LocationType.Casino)) { - content.push( - , - ) - } - - return content; + if (this.props.loc.types.includes(LocationType.Gym)) { + content.push( + , + ); } - render(): React.ReactNode { - const locContent: React.ReactNode[] = this.getLocationSpecificContent(); - const ip = SpecialServerIps.getIp(this.props.loc.name); - const server = getServer(ip); - const backdoorInstalled = server !== null && isBackdoorInstalled(server); - - return ( -
    - -

    - {backdoorInstalled && !Settings.DisableTextEffects - ? - : this.props.loc.name - } -

    - {locContent} -
    - ) + if (this.props.loc.types.includes(LocationType.Hospital)) { + content.push( + , + ); } + + if (this.props.loc.types.includes(LocationType.Slums)) { + content.push(); + } + + if (this.props.loc.types.includes(LocationType.Special)) { + content.push( + , + ); + } + + if (this.props.loc.types.includes(LocationType.TechVendor)) { + content.push( + , + ); + } + + if (this.props.loc.types.includes(LocationType.TravelAgency)) { + content.push( + , + ); + } + + if (this.props.loc.types.includes(LocationType.University)) { + content.push( + , + ); + } + + if (this.props.loc.types.includes(LocationType.Casino)) { + content.push(); + } + + return content; + } + + render(): React.ReactNode { + const locContent: React.ReactNode[] = this.getLocationSpecificContent(); + const ip = SpecialServerIps.getIp(this.props.loc.name); + const server = getServer(ip); + const backdoorInstalled = server !== null && isBackdoorInstalled(server); + + return ( +
    + +

    + {backdoorInstalled && !Settings.DisableTextEffects ? ( + + ) : ( + this.props.loc.name + )} +

    + {locContent} +
    + ); + } } diff --git a/src/Locations/ui/GymLocation.tsx b/src/Locations/ui/GymLocation.tsx index fa4bbc0b9..cbf0e6d7f 100644 --- a/src/Locations/ui/GymLocation.tsx +++ b/src/Locations/ui/GymLocation.tsx @@ -5,96 +5,115 @@ */ import * as React from "react"; -import { Location } from "../Location"; +import { Location } from "../Location"; -import { CONSTANTS } from "../../Constants"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { getServer } from "../../Server/ServerHelpers"; -import { Server } from "../../Server/Server"; -import { SpecialServerIps } from "../../Server/SpecialServerIps"; +import { CONSTANTS } from "../../Constants"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { getServer } from "../../Server/ServerHelpers"; +import { Server } from "../../Server/Server"; +import { SpecialServerIps } from "../../Server/SpecialServerIps"; -import { StdButton } from "../../ui/React/StdButton"; -import { Money } from "../../ui/React/Money"; +import { StdButton } from "../../ui/React/StdButton"; +import { Money } from "../../ui/React/Money"; type IProps = { - loc: Location; - p: IPlayer; -} + loc: Location; + p: IPlayer; +}; export class GymLocation extends React.Component { - /** - * Stores button styling that sets them all to block display - */ - btnStyle: any; + /** + * Stores button styling that sets them all to block display + */ + btnStyle: any; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.btnStyle = { display: "block" }; + this.btnStyle = { display: "block" }; - this.trainStrength = this.trainStrength.bind(this); - this.trainDefense = this.trainDefense.bind(this); - this.trainDexterity = this.trainDexterity.bind(this); - this.trainAgility = this.trainAgility.bind(this); + this.trainStrength = this.trainStrength.bind(this); + this.trainDefense = this.trainDefense.bind(this); + this.trainDexterity = this.trainDexterity.bind(this); + this.trainAgility = this.trainAgility.bind(this); - this.calculateCost = this.calculateCost.bind(this); - } + this.calculateCost = this.calculateCost.bind(this); + } - calculateCost(): number { - const ip = SpecialServerIps.getIp(this.props.loc.name); - const server = getServer(ip); - if(server == null || !server.hasOwnProperty('backdoorInstalled')) return this.props.loc.costMult; - const discount = (server as Server).backdoorInstalled? 0.9 : 1; - return this.props.loc.costMult * discount; - } + calculateCost(): number { + const ip = SpecialServerIps.getIp(this.props.loc.name); + const server = getServer(ip); + if (server == null || !server.hasOwnProperty("backdoorInstalled")) + return this.props.loc.costMult; + const discount = (server as Server).backdoorInstalled ? 0.9 : 1; + return this.props.loc.costMult * discount; + } - train(stat: string): void { - const loc = this.props.loc; - this.props.p.startClass(this.calculateCost(), loc.expMult, stat); - } + train(stat: string): void { + const loc = this.props.loc; + this.props.p.startClass(this.calculateCost(), loc.expMult, stat); + } - trainStrength(): void { - this.train(CONSTANTS.ClassGymStrength); - } + trainStrength(): void { + this.train(CONSTANTS.ClassGymStrength); + } - trainDefense(): void { - this.train(CONSTANTS.ClassGymDefense); - } + trainDefense(): void { + this.train(CONSTANTS.ClassGymDefense); + } - trainDexterity(): void { - this.train(CONSTANTS.ClassGymDexterity); - } + trainDexterity(): void { + this.train(CONSTANTS.ClassGymDexterity); + } - trainAgility(): void { - this.train(CONSTANTS.ClassGymAgility); - } + trainAgility(): void { + this.train(CONSTANTS.ClassGymAgility); + } - render(): React.ReactNode { - const cost = CONSTANTS.ClassGymBaseCost * this.calculateCost(); + render(): React.ReactNode { + const cost = CONSTANTS.ClassGymBaseCost * this.calculateCost(); - return ( -
    - Train Strength ( / sec)} - /> - Train Defense ( / sec)} - /> - Train Dexterity ( / sec)} - /> - Train Agility ( / sec)} - /> -
    - ) - } + return ( +
    + + Train Strength ( / + sec) + + } + /> + + Train Defense ( / sec) + + } + /> + + Train Dexterity ( / + sec) + + } + /> + + Train Agility ( / sec) + + } + /> +
    + ); + } } diff --git a/src/Locations/ui/HospitalLocation.tsx b/src/Locations/ui/HospitalLocation.tsx index 2528cdc78..a5ecaa7b4 100644 --- a/src/Locations/ui/HospitalLocation.tsx +++ b/src/Locations/ui/HospitalLocation.tsx @@ -5,73 +5,89 @@ */ import * as React from "react"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { getHospitalizationCost } from "../../Hospital/Hospital"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { getHospitalizationCost } from "../../Hospital/Hospital"; -import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton"; -import { Money } from "../../ui/React/Money"; +import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton"; +import { Money } from "../../ui/React/Money"; -import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; type IProps = { - p: IPlayer; -} + p: IPlayer; +}; type IState = { - currHp: number; -} + currHp: number; +}; export class HospitalLocation extends React.Component { - /** - * Stores button styling that sets them all to block display - */ - btnStyle: any; + /** + * Stores button styling that sets them all to block display + */ + btnStyle: any; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.btnStyle = { display: "block" }; + this.btnStyle = { display: "block" }; - this.getCost = this.getCost.bind(this); - this.getHealed = this.getHealed.bind(this); + this.getCost = this.getCost.bind(this); + this.getHealed = this.getHealed.bind(this); - this.state = { - currHp: this.props.p.hp, + this.state = { + currHp: this.props.p.hp, + }; + } + + getCost(): number { + return getHospitalizationCost(this.props.p); + } + + getHealed(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; + } + + if (this.props.p.hp < 0) { + this.props.p.hp = 0; + } + if (this.props.p.hp >= this.props.p.max_hp) { + return; + } + + const cost = this.getCost(); + this.props.p.loseMoney(cost); + this.props.p.hp = this.props.p.max_hp; + this.props.p.recordMoneySource(-1 * cost, "hospitalization"); + + // This just forces a re-render to update the cost + this.setState({ + currHp: this.props.p.hp, + }); + + dialogBoxCreate( + <> + You were healed to full health! The hospital billed you for{" "} + + , + ); + } + + render(): React.ReactNode { + const cost = this.getCost(); + + return ( + + Get treatment for wounds -{" "} + + } - } - - getCost(): number { - return getHospitalizationCost(this.props.p); - } - - getHealed(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - - if (this.props.p.hp < 0) { this.props.p.hp = 0; } - if (this.props.p.hp >= this.props.p.max_hp) { return; } - - const cost = this.getCost(); - this.props.p.loseMoney(cost); - this.props.p.hp = this.props.p.max_hp; - this.props.p.recordMoneySource(-1 * cost, 'hospitalization'); - - // This just forces a re-render to update the cost - this.setState({ - currHp: this.props.p.hp, - }); - - dialogBoxCreate(<>You were healed to full health! The hospital billed you for ); - } - - render(): React.ReactNode { - const cost = this.getCost(); - - return ( - Get treatment for wounds - } - /> - ) - } + /> + ); + } } diff --git a/src/Locations/ui/RamButton.tsx b/src/Locations/ui/RamButton.tsx index 3aa5834b2..ed2103188 100644 --- a/src/Locations/ui/RamButton.tsx +++ b/src/Locations/ui/RamButton.tsx @@ -1,51 +1,63 @@ import React, { useState } from "react"; -import { Location } from "../Location"; -import { createPurchaseServerPopup, - createUpgradeHomeCoresPopup, - purchaseTorRouter } from "../LocationsHelpers"; +import { Location } from "../Location"; +import { + createPurchaseServerPopup, + createUpgradeHomeCoresPopup, + purchaseTorRouter, +} from "../LocationsHelpers"; -import { CONSTANTS } from "../../Constants"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { purchaseRamForHomeComputer } from "../../Server/ServerPurchases"; +import { CONSTANTS } from "../../Constants"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { purchaseRamForHomeComputer } from "../../Server/ServerPurchases"; -import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased"; -import { StdButton } from "../../ui/React/StdButton"; -import { Money } from "../../ui/React/Money"; -import { MathComponent } from 'mathjax-react'; +import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased"; +import { StdButton } from "../../ui/React/StdButton"; +import { Money } from "../../ui/React/Money"; +import { MathComponent } from "mathjax-react"; type IProps = { - p: IPlayer; -} + p: IPlayer; +}; export function RamButton(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender(old => !old); - } + const setRerender = useState(false)[1]; + function rerender(): void { + setRerender((old) => !old); + } - const btnStyle = { display: "block" }; + const btnStyle = { display: "block" }; - const homeComputer = props.p.getHomeComputer(); - if(homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) { - return (); - } + const homeComputer = props.p.getHomeComputer(); + if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) { + return ( + + ); + } - const cost = props.p.getUpgradeHomeRamCost(); + const cost = props.p.getUpgradeHomeRamCost(); - function buy(): void { - purchaseRamForHomeComputer(props.p); - rerender(); - } + function buy(): void { + purchaseRamForHomeComputer(props.p); + rerender(); + } - return (Upgrade 'home' RAM ({homeComputer.maxRam}GB -> {homeComputer.maxRam*2}GB) - } - tooltip={} - />); + return ( + + Upgrade 'home' RAM ({homeComputer.maxRam}GB ->{" "} + {homeComputer.maxRam * 2}GB) - + + } + tooltip={ + + } + /> + ); } diff --git a/src/Locations/ui/Root.tsx b/src/Locations/ui/Root.tsx index 62045e7bf..3f49dca46 100644 --- a/src/Locations/ui/Root.tsx +++ b/src/Locations/ui/Root.tsx @@ -3,149 +3,152 @@ */ import * as React from "react"; -import { LocationCity } from "./City"; -import { GenericLocation } from "./GenericLocation"; +import { LocationCity } from "./City"; +import { GenericLocation } from "./GenericLocation"; -import { Cities } from "../Cities"; -import { Locations } from "../Locations"; -import { LocationType } from "../LocationTypeEnum"; +import { Cities } from "../Cities"; +import { Locations } from "../Locations"; +import { LocationType } from "../LocationTypeEnum"; -import { CityName } from "../data/CityNames"; -import { LocationName } from "../data/LocationNames"; +import { CityName } from "../data/CityNames"; +import { LocationName } from "../data/LocationNames"; -import { CONSTANTS } from "../../Constants"; -import { IEngine } from "../../IEngine"; -import { IPlayer } from "../../PersonObjects/IPlayer"; +import { CONSTANTS } from "../../Constants"; +import { IEngine } from "../../IEngine"; +import { IPlayer } from "../../PersonObjects/IPlayer"; -import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; type IProps = { - initiallyInCity?: boolean; - engine: IEngine; - p: IPlayer; -} + initiallyInCity?: boolean; + engine: IEngine; + p: IPlayer; +}; type IState = { - city: CityName; - inCity: boolean; - location: LocationName; -} + city: CityName; + inCity: boolean; + location: LocationName; +}; export class LocationRoot extends React.Component { - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.state = { - city: props.p.city, - inCity: props.initiallyInCity == null ? true : props.initiallyInCity, - location: props.p.location, - } + this.state = { + city: props.p.city, + inCity: props.initiallyInCity == null ? true : props.initiallyInCity, + location: props.p.location, + }; - this.enterLocation = this.enterLocation.bind(this); - this.returnToCity = this.returnToCity.bind(this); - this.travel = this.travel.bind(this); + this.enterLocation = this.enterLocation.bind(this); + this.returnToCity = this.returnToCity.bind(this); + this.travel = this.travel.bind(this); + } + + enterLocation(to: LocationName): void { + this.props.p.gotoLocation(to); + this.setState({ + inCity: false, + location: to, + }); + } + + /** + * Click listener for a button that lets the player go from a specific location + * back to the city + */ + returnToCity(): void { + this.setState({ + inCity: true, + }); + } + + /** + * Render UI for a city + */ + renderCity(): React.ReactNode { + const city = Cities[this.state.city]; + if (city == null) { + throw new Error(`Invalid city when rendering UI: ${this.state.city}`); } - enterLocation(to: LocationName): void { - this.props.p.gotoLocation(to); - this.setState({ - inCity: false, - location: to, - }); + return ( +
    +

    {this.state.city}

    + +
    + ); + } + + /** + * Render UI for a specific location + */ + renderLocation(): React.ReactNode { + const loc = Locations[this.state.location]; + + if (loc == null) { + throw new Error( + `Invalid location when rendering UI: ${this.state.location}`, + ); } - /** - * Click listener for a button that lets the player go from a specific location - * back to the city - */ - returnToCity(): void { - this.setState({ - inCity: true, - }); + if (loc.types.includes(LocationType.StockMarket)) { + this.props.engine.loadStockMarketContent(); } - /** - * Render UI for a city - */ - renderCity(): React.ReactNode { - const city = Cities[this.state.city]; - if (city == null) { - throw new Error(`Invalid city when rendering UI: ${this.state.city}`); - } + return ( + + ); + } - return ( -
    -

    {this.state.city}

    - -
    - ) + /** + * Travel to a different city + * @param {CityName} to - Destination city + */ + travel(to: CityName): void { + const p = this.props.p; + const cost = CONSTANTS.TravelCost; + if (!p.canAfford(cost)) { + dialogBoxCreate(`You cannot afford to travel to ${to}`); + return; } - /** - * Render UI for a specific location - */ - renderLocation(): React.ReactNode { - const loc = Locations[this.state.location]; + p.loseMoney(cost); + p.travel(to); + dialogBoxCreate(You are now in {to}!); - if (loc == null) { - throw new Error(`Invalid location when rendering UI: ${this.state.location}`); - } - - if (loc.types.includes(LocationType.StockMarket)) { - this.props.engine.loadStockMarketContent(); - } - - return ( - - ) + // Dynamically update main menu + if (p.firstTimeTraveled === false) { + p.firstTimeTraveled = true; + const travelTab = document.getElementById("travel-tab"); + const worldHeader = document.getElementById("world-menu-header"); + if (travelTab != null && worldHeader !== null) { + travelTab.style.display = "list-item"; + worldHeader.click(); + worldHeader.click(); + } } - /** - * Travel to a different city - * @param {CityName} to - Destination city - */ - travel(to: CityName): void { - const p = this.props.p; - const cost = CONSTANTS.TravelCost; - if (!p.canAfford(cost)) { - dialogBoxCreate(`You cannot afford to travel to ${to}`); - return; - } - - p.loseMoney(cost); - p.travel(to); - dialogBoxCreate(You are now in {to}!); - - // Dynamically update main menu - if (p.firstTimeTraveled === false) { - p.firstTimeTraveled = true; - const travelTab = document.getElementById("travel-tab"); - const worldHeader = document.getElementById("world-menu-header"); - if (travelTab != null && worldHeader !== null) { - travelTab.style.display = "list-item"; - worldHeader.click(); worldHeader.click(); - } - } - - if (this.props.p.travel(to)) { - this.setState({ - inCity: true, - city: to, - }); - } + if (this.props.p.travel(to)) { + this.setState({ + inCity: true, + city: to, + }); } + } - render(): React.ReactNode { - if (this.state.inCity) { - return this.renderCity(); - } else { - return this.renderLocation(); - } + render(): React.ReactNode { + if (this.state.inCity) { + return this.renderCity(); + } else { + return this.renderLocation(); } + } } diff --git a/src/Locations/ui/SlumsLocation.tsx b/src/Locations/ui/SlumsLocation.tsx index 08d2afff1..ad761376f 100644 --- a/src/Locations/ui/SlumsLocation.tsx +++ b/src/Locations/ui/SlumsLocation.tsx @@ -5,202 +5,250 @@ */ import * as React from "react"; -import { Crimes } from "../../Crime/Crimes"; -import { IPlayer } from "../../PersonObjects/IPlayer"; +import { Crimes } from "../../Crime/Crimes"; +import { IPlayer } from "../../PersonObjects/IPlayer"; -import { numeralWrapper } from "../../ui/numeralFormat"; -import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton"; type IProps = { - p: IPlayer; -} + p: IPlayer; +}; export class SlumsLocation extends React.Component { - /** - * Stores button styling that sets them all to block display - */ - btnStyle: any; + /** + * Stores button styling that sets them all to block display + */ + btnStyle: any; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.btnStyle = { display: "block" }; + this.btnStyle = { display: "block" }; - this.shoplift = this.shoplift.bind(this); - this.robStore = this.robStore.bind(this); - this.mug = this.mug.bind(this); - this.larceny = this.larceny.bind(this); - this.dealDrugs = this.dealDrugs.bind(this); - this.bondForgery = this.bondForgery.bind(this); - this.traffickArms = this.traffickArms.bind(this); - this.homicide = this.homicide.bind(this); - this.grandTheftAuto = this.grandTheftAuto.bind(this); - this.kidnap = this.kidnap.bind(this); - this.assassinate = this.assassinate.bind(this); - this.heist = this.heist.bind(this); + this.shoplift = this.shoplift.bind(this); + this.robStore = this.robStore.bind(this); + this.mug = this.mug.bind(this); + this.larceny = this.larceny.bind(this); + this.dealDrugs = this.dealDrugs.bind(this); + this.bondForgery = this.bondForgery.bind(this); + this.traffickArms = this.traffickArms.bind(this); + this.homicide = this.homicide.bind(this); + this.grandTheftAuto = this.grandTheftAuto.bind(this); + this.kidnap = this.kidnap.bind(this); + this.assassinate = this.assassinate.bind(this); + this.heist = this.heist.bind(this); + } + + shoplift(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.Shoplift.commit(this.props.p); + } - shoplift(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.Shoplift.commit(this.props.p); + robStore(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.RobStore.commit(this.props.p); + } - robStore(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.RobStore.commit(this.props.p); + mug(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.Mug.commit(this.props.p); + } - mug(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.Mug.commit(this.props.p); + larceny(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.Larceny.commit(this.props.p); + } - larceny(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.Larceny.commit(this.props.p); + dealDrugs(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.DealDrugs.commit(this.props.p); + } - dealDrugs(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.DealDrugs.commit(this.props.p); + bondForgery(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.BondForgery.commit(this.props.p); + } - bondForgery(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.BondForgery.commit(this.props.p); + traffickArms(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.TraffickArms.commit(this.props.p); + } - traffickArms(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.TraffickArms.commit(this.props.p); + homicide(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.Homicide.commit(this.props.p); + } - homicide(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.Homicide.commit(this.props.p); + grandTheftAuto(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.GrandTheftAuto.commit(this.props.p); + } - grandTheftAuto(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.GrandTheftAuto.commit(this.props.p); + kidnap(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.Kidnap.commit(this.props.p); + } - kidnap(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.Kidnap.commit(this.props.p); + assassinate(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.Assassination.commit(this.props.p); + } - assassinate(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.Assassination.commit(this.props.p); + heist(e: React.MouseEvent): void { + if (!e.isTrusted) { + return; } + Crimes.Heist.commit(this.props.p); + } - heist(e: React.MouseEvent): void { - if (!e.isTrusted) { return; } - Crimes.Heist.commit(this.props.p); - } + render(): React.ReactNode { + const shopliftChance = Crimes.Shoplift.successRate(this.props.p); + const robStoreChance = Crimes.RobStore.successRate(this.props.p); + const mugChance = Crimes.Mug.successRate(this.props.p); + const larcenyChance = Crimes.Larceny.successRate(this.props.p); + const drugsChance = Crimes.DealDrugs.successRate(this.props.p); + const bondChance = Crimes.BondForgery.successRate(this.props.p); + const armsChance = Crimes.TraffickArms.successRate(this.props.p); + const homicideChance = Crimes.Homicide.successRate(this.props.p); + const gtaChance = Crimes.GrandTheftAuto.successRate(this.props.p); + const kidnapChance = Crimes.Kidnap.successRate(this.props.p); + const assassinateChance = Crimes.Assassination.successRate(this.props.p); + const heistChance = Crimes.Heist.successRate(this.props.p); - render(): React.ReactNode { - const shopliftChance = Crimes.Shoplift.successRate(this.props.p); - const robStoreChance = Crimes.RobStore.successRate(this.props.p); - const mugChance = Crimes.Mug.successRate(this.props.p); - const larcenyChance = Crimes.Larceny.successRate(this.props.p); - const drugsChance = Crimes.DealDrugs.successRate(this.props.p); - const bondChance = Crimes.BondForgery.successRate(this.props.p); - const armsChance = Crimes.TraffickArms.successRate(this.props.p); - const homicideChance = Crimes.Homicide.successRate(this.props.p); - const gtaChance = Crimes.GrandTheftAuto.successRate(this.props.p); - const kidnapChance = Crimes.Kidnap.successRate(this.props.p); - const assassinateChance = Crimes.Assassination.successRate(this.props.p); - const heistChance = Crimes.Heist.successRate(this.props.p); - - return ( -
    - - - - - - - - - - - - -
    - ) - } + return ( +
    + + + + + + + + + + + + +
    + ); + } } diff --git a/src/Locations/ui/SpecialLocation.tsx b/src/Locations/ui/SpecialLocation.tsx index e39b25172..81c2a30b5 100644 --- a/src/Locations/ui/SpecialLocation.tsx +++ b/src/Locations/ui/SpecialLocation.tsx @@ -12,158 +12,186 @@ */ import * as React from "react"; -import { Location } from "../Location"; -import { createStartCorporationPopup } from "../LocationsHelpers"; -import { LocationName } from "../data/LocationNames"; +import { Location } from "../Location"; +import { createStartCorporationPopup } from "../LocationsHelpers"; +import { LocationName } from "../data/LocationNames"; -import { IEngine } from "../../IEngine"; -import { IPlayer } from "../../PersonObjects/IPlayer"; +import { IEngine } from "../../IEngine"; +import { IPlayer } from "../../PersonObjects/IPlayer"; -import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton"; -import { StdButton } from "../../ui/React/StdButton"; +import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton"; +import { StdButton } from "../../ui/React/StdButton"; -import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; type IProps = { - engine: IEngine; - loc: Location; - p: IPlayer; -} + engine: IEngine; + loc: Location; + p: IPlayer; +}; type IState = { - inBladeburner: boolean; -} + inBladeburner: boolean; +}; export class SpecialLocation extends React.Component { - /** - * Stores button styling that sets them all to block display - */ - btnStyle: any; + /** + * Stores button styling that sets them all to block display + */ + btnStyle: any; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.btnStyle = { display: "block" }; + this.btnStyle = { display: "block" }; - this.renderNoodleBar = this.renderNoodleBar.bind(this); - this.createCorporationPopup = this.createCorporationPopup.bind(this); - this.handleBladeburner = this.handleBladeburner.bind(this); - this.handleResleeving = this.handleResleeving.bind(this); + this.renderNoodleBar = this.renderNoodleBar.bind(this); + this.createCorporationPopup = this.createCorporationPopup.bind(this); + this.handleBladeburner = this.handleBladeburner.bind(this); + this.handleResleeving = this.handleResleeving.bind(this); - this.state = { - inBladeburner: this.props.p.inBladeburner(), + this.state = { + inBladeburner: this.props.p.inBladeburner(), + }; + } + + /** + * Click handler for "Create Corporation" button at Sector-12 City Hall + */ + createCorporationPopup(): void { + createStartCorporationPopup(this.props.p); + } + + /** + * Click handler for Bladeburner button at Sector-12 NSA + */ + handleBladeburner(): void { + const p = this.props.p; + if (p.inBladeburner()) { + // Enter Bladeburner division + this.props.engine.loadBladeburnerContent(); + } else { + // Apply for Bladeburner division + if ( + p.strength >= 100 && + p.defense >= 100 && + p.dexterity >= 100 && + p.agility >= 100 + ) { + p.startBladeburner({ new: true }); + dialogBoxCreate( + "You have been accepted into the Bladeburner division!", + ); + this.setState({ + inBladeburner: true, + }); + + const worldHeader = document.getElementById("world-menu-header"); + if (worldHeader instanceof HTMLElement) { + worldHeader.click(); + worldHeader.click(); } + } else { + dialogBoxCreate( + "Rejected! Please apply again when you have 100 of each combat stat (str, def, dex, agi)", + ); + } + } + } + + /** + * Click handler for Resleeving button at New Tokyo VitaLife + */ + handleResleeving(): void { + this.props.engine.loadResleevingContent(); + } + + renderBladeburner(): React.ReactNode { + if (!this.props.p.canAccessBladeburner()) { + return null; + } + const text = this.state.inBladeburner + ? "Enter Bladeburner Headquarters" + : "Apply to Bladeburner Division"; + return ( + + ); + } + + renderNoodleBar(): React.ReactNode { + function EatNoodles(): void { + dialogBoxCreate(<>You ate some delicious noodles and feel refreshed.); } - /** - * Click handler for "Create Corporation" button at Sector-12 City Hall - */ - createCorporationPopup(): void { - createStartCorporationPopup(this.props.p); + return ( + + ); + } + + renderCreateCorporation(): React.ReactNode { + if (!this.props.p.canAccessCorporation()) { + return ( + <> +

    + + A business man is yelling at a clerk. You should come back later. + +

    + + ); } - - /** - * Click handler for Bladeburner button at Sector-12 NSA - */ - handleBladeburner(): void { - const p = this.props.p; - if (p.inBladeburner()) { - // Enter Bladeburner division - this.props.engine.loadBladeburnerContent(); - } else { - // Apply for Bladeburner division - if (p.strength >= 100 && p.defense >= 100 && p.dexterity >= 100 && p.agility >= 100) { - p.startBladeburner({ new: true }); - dialogBoxCreate("You have been accepted into the Bladeburner division!"); - this.setState({ - inBladeburner: true, - }); - - const worldHeader = document.getElementById("world-menu-header"); - if (worldHeader instanceof HTMLElement) { - worldHeader.click(); worldHeader.click(); - } - } else { - dialogBoxCreate("Rejected! Please apply again when you have 100 of each combat stat (str, def, dex, agi)"); - } + return ( + + ); + } - /** - * Click handler for Resleeving button at New Tokyo VitaLife - */ - handleResleeving(): void { - this.props.engine.loadResleevingContent(); + renderResleeving(): React.ReactNode { + if (!this.props.p.canAccessResleeving()) { + return null; } + return ( + + ); + } - renderBladeburner(): React.ReactNode { - if (!this.props.p.canAccessBladeburner()) { return null; } - const text = this.state.inBladeburner ? "Enter Bladeburner Headquarters" : "Apply to Bladeburner Division"; - return ( - - ) - } - - renderNoodleBar(): React.ReactNode { - function EatNoodles(): void { - dialogBoxCreate(<>You ate some delicious noodles and feel refreshed.) - } - - return () - } - - renderCreateCorporation(): React.ReactNode { - if (!this.props.p.canAccessCorporation()) { - return <> -

    A business man is yelling at a clerk. You should come back later.

    - ; - } - return ( - - ) - } - - renderResleeving(): React.ReactNode { - if (!this.props.p.canAccessResleeving()) { return null; } - return ( - - ) - } - - render(): React.ReactNode { - switch (this.props.loc.name) { - case LocationName.NewTokyoVitaLife: { - return this.renderResleeving(); - } - case LocationName.Sector12CityHall: { - return this.renderCreateCorporation(); - } - case LocationName.Sector12NSA: { - return this.renderBladeburner(); - } - case LocationName.NewTokyoNoodleBar: { - return this.renderNoodleBar(); - } - default: - console.error(`Location ${this.props.loc.name} doesn't have any special properties`); - break; - } + render(): React.ReactNode { + switch (this.props.loc.name) { + case LocationName.NewTokyoVitaLife: { + return this.renderResleeving(); + } + case LocationName.Sector12CityHall: { + return this.renderCreateCorporation(); + } + case LocationName.Sector12NSA: { + return this.renderBladeburner(); + } + case LocationName.NewTokyoNoodleBar: { + return this.renderNoodleBar(); + } + default: + console.error( + `Location ${this.props.loc.name} doesn't have any special properties`, + ); + break; } + } } diff --git a/src/Locations/ui/TechVendorLocation.tsx b/src/Locations/ui/TechVendorLocation.tsx index 922ac9830..af943a427 100644 --- a/src/Locations/ui/TechVendorLocation.tsx +++ b/src/Locations/ui/TechVendorLocation.tsx @@ -5,51 +5,68 @@ */ import * as React from "react"; -import { Location } from "../Location"; -import { createPurchaseServerPopup, - createUpgradeHomeCoresPopup, - purchaseTorRouter } from "../LocationsHelpers"; -import { RamButton } from "./RamButton"; -import { TorButton } from "./TorButton"; -import { CoresButton } from "./CoresButton"; +import { Location } from "../Location"; +import { + createPurchaseServerPopup, + createUpgradeHomeCoresPopup, + purchaseTorRouter, +} from "../LocationsHelpers"; +import { RamButton } from "./RamButton"; +import { TorButton } from "./TorButton"; +import { CoresButton } from "./CoresButton"; -import { CONSTANTS } from "../../Constants"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { getPurchaseServerCost } from "../../Server/ServerPurchases"; +import { CONSTANTS } from "../../Constants"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { getPurchaseServerCost } from "../../Server/ServerPurchases"; -import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased"; -import { StdButton } from "../../ui/React/StdButton"; -import { Money } from "../../ui/React/Money"; +import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased"; +import { StdButton } from "../../ui/React/StdButton"; +import { Money } from "../../ui/React/Money"; type IProps = { - loc: Location; - p: IPlayer; -} + loc: Location; + p: IPlayer; +}; export function TechVendorLocation(props: IProps): React.ReactElement { - const btnStyle = { display: "block" }; + const btnStyle = { display: "block" }; - const purchaseServerButtons: React.ReactNode[] = []; - for (let i = props.loc.techVendorMinRam; i <= props.loc.techVendorMaxRam; i *= 2) { - const cost = getPurchaseServerCost(i); - purchaseServerButtons.push( - createPurchaseServerPopup(i, props.p)} - style={btnStyle} - text={<>Purchase {i}GB Server - } - disabled={!props.p.canAfford(cost)} - />, - ) - } + const purchaseServerButtons: React.ReactNode[] = []; + for ( + let i = props.loc.techVendorMinRam; + i <= props.loc.techVendorMaxRam; + i *= 2 + ) { + const cost = getPurchaseServerCost(i); + purchaseServerButtons.push( + createPurchaseServerPopup(i, props.p)} + style={btnStyle} + text={ + <> + Purchase {i}GB Server - + + } + disabled={!props.p.canAfford(cost)} + />, + ); + } - return (
    - {purchaseServerButtons} -
    -

    "You can order bigger servers via scripts. We don't take custom order in person."

    -
    - - - -
    ); + return ( +
    + {purchaseServerButtons} +
    +

    + + "You can order bigger servers via scripts. We don't take custom order + in person." + +

    +
    + + + +
    + ); } diff --git a/src/Locations/ui/TorButton.tsx b/src/Locations/ui/TorButton.tsx index db6ee2132..a92bd6eb9 100644 --- a/src/Locations/ui/TorButton.tsx +++ b/src/Locations/ui/TorButton.tsx @@ -1,46 +1,54 @@ import React, { useState } from "react"; -import { Location } from "../Location"; -import { createPurchaseServerPopup, - createUpgradeHomeCoresPopup, - purchaseTorRouter } from "../LocationsHelpers"; +import { Location } from "../Location"; +import { + createPurchaseServerPopup, + createUpgradeHomeCoresPopup, + purchaseTorRouter, +} from "../LocationsHelpers"; -import { CONSTANTS } from "../../Constants"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { purchaseRamForHomeComputer } from "../../Server/ServerPurchases"; +import { CONSTANTS } from "../../Constants"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { purchaseRamForHomeComputer } from "../../Server/ServerPurchases"; -import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased"; -import { StdButton } from "../../ui/React/StdButton"; -import { Money } from "../../ui/React/Money"; +import { StdButtonPurchased } from "../../ui/React/StdButtonPurchased"; +import { StdButton } from "../../ui/React/StdButton"; +import { Money } from "../../ui/React/Money"; type IProps = { - p: IPlayer; -} + p: IPlayer; +}; export function TorButton(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - function rerender(): void { - setRerender(old => !old); - } + const setRerender = useState(false)[1]; + function rerender(): void { + setRerender((old) => !old); + } - const btnStyle = { display: "block" }; + const btnStyle = { display: "block" }; - function buy(): void { - purchaseTorRouter(props.p); - rerender(); - } + function buy(): void { + purchaseTorRouter(props.p); + rerender(); + } - if(props.p.hasTorRouter()) { - return (); - } + if (props.p.hasTorRouter()) { + return ( + + ); + } - return (Purchase TOR router - } - />); + return ( + + Purchase TOR router -{" "} + + + } + /> + ); } diff --git a/src/Locations/ui/TravelAgencyLocation.tsx b/src/Locations/ui/TravelAgencyLocation.tsx index da5518985..5136efd32 100644 --- a/src/Locations/ui/TravelAgencyLocation.tsx +++ b/src/Locations/ui/TravelAgencyLocation.tsx @@ -5,111 +5,145 @@ */ import * as React from "react"; -import { CityName } from "../data/CityNames"; -import { createTravelPopup } from "../LocationsHelpers"; +import { CityName } from "../data/CityNames"; +import { createTravelPopup } from "../LocationsHelpers"; -import { CONSTANTS } from "../../Constants"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { Settings } from "../../Settings/Settings"; +import { CONSTANTS } from "../../Constants"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { Settings } from "../../Settings/Settings"; -import { StdButton } from "../../ui/React/StdButton"; -import { Money } from "../../ui/React/Money"; +import { StdButton } from "../../ui/React/StdButton"; +import { Money } from "../../ui/React/Money"; type IProps = { - p: IPlayer; - travel: (to: CityName) => void; -} + p: IPlayer; + travel: (to: CityName) => void; +}; export class TravelAgencyLocation extends React.Component { - /** - * Stores button styling that sets them all to block display - */ - btnStyle: any; + /** + * Stores button styling that sets them all to block display + */ + btnStyle: any; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.btnStyle = { display: "block" }; - } - - asciiWorldMap(): React.ReactNode { - const LocationLetter = (props: any): JSX.Element => { - if(props.city !== this.props.p.city) { - return - {props.city} - {props.city[0]} - - } - return {props.city[0]} - } + this.btnStyle = { display: "block" }; + } + asciiWorldMap(): React.ReactNode { + const LocationLetter = (props: any): JSX.Element => { + if (props.city !== this.props.p.city) { return ( -
    -

    - From here, you can travel to any other city! A ticket - costs . -

    -
                   ,_   .  ._. _.  .
    -
               , _-\','|~\~      ~/      ;-'_   _-'     ,;_;_,    ~~-
    -
      /~~-\_/-'~'--' \~~| ',    ,'      /  / ~|-_\_/~/~      ~~--~~~~'--_
    -
      /              ,/'-/~ '\ ,' _  , ','|~                   ._/-, /~
    -
      ~/-'~\_,       '-,| '|. '   ~  ,\ /'~                /    /_  /~
    -
    .-~      '|        '',\~|\       _\~     ,_  ,              /,
    -
              '\       /'~          |_/~\\,-,~  \ "         ,_,/ |
    -
               |       /            ._-~'\_ _~|              \ ) 
    -
                \   __-\           '/      ~ |\  \_          /  ~
    -
      .,         '\ |,  ~-_      - |          \\_' ~|  /\  \~ ,
    -
                   ~-_'  _;       '\           '-,   \,' /\/  |
    -
                     '\_,~'\_       \_ _,       /'    '  |, /|'
    -
                       /     \_       ~ |      /         \  ~'; -,_.
    -
                       |       ~\        |    |  ,        '-_, ,; ~ ~\
    -
                        \,     /        \    / /|            ,-, ,   -,
    -
                         |    ,/          |  |' |/          ,-   ~ \   '.
    -
                        ,|   ,/           \ ,/              \      |
    -
                        /    |             ~                 -~~-, /   _
    -
                        | ,-'                                    ~    /
    -
                        / ,'                                      ~
    -
                        ',|  ~
    -
                          ~'
    -
    - ) + + {props.city} + {props.city[0]} + + ); + } + return {props.city[0]}; + }; + + return ( +
    +

    + From here, you can travel to any other city! A ticket costs{" "} + . +

    +
     ,_ . ._. _. .
    +
     , _-\','|~\~ ~/ ;-'_ _-' ,;_;_, ~~-
    +
     /~~-\_/-'~'--' \~~| ', ,' / / ~|-_\_/~/~ ~~--~~~~'--_
    +
    +          {" "}
    +          / ,/'-/~ '\ ,' _ , '
    +          ,'|~ ._/-, /~
    +        
    +
     ~/-'~\_, '-,| '|. ' ~ ,\ /'~ / /_ /~
    +
    +          .-~ '| '',\~|\ _\~ ,_ ,  /,
    +        
    +
    +          {" "}
    +          '\  /'~ |_/~\\,-,~ \ " ,_,/ |
    +        
    +
    +          {" "}
    +          | / ._-~'\_ _~| \ ) 
    +        
    +
     \ __-\ '/ ~ |\ \_ / ~
    +
     ., '\ |, ~-_ - | \\_' ~| /\ \~ ,
    +
     ~-_' _; '\ '-, \,' /\/ |
    +
     '\_,~'\_ \_ _, /' ' |, /|'
    +
     / \_ ~ | / \ ~'; -,_.
    +
     | ~\ | | , '-_, ,; ~ ~\
    +
    +          {" "}
    +          \,  / \ / /| ,-, , -,
    +        
    +
     | ,/ | |' |/ ,- ~ \ '.
    +
    +          {" "}
    +          ,| ,/ \ ,/ \  |
    +        
    +
     / | ~ -~~-, / _
    +
     | ,-' ~ /
    +
     / ,' ~
    +
     ',| ~
    +
     ~'
    +
    + ); + } + + listWorldMap(): React.ReactNode { + const travelBtns: React.ReactNode[] = []; + for (const key in CityName) { + const city: CityName = (CityName as any)[key]; + + // Skip current city + if (city === this.props.p.city) { + continue; + } + + travelBtns.push( + , + ); } + return ( +
    +

    + From here, you can travel to any other city! A ticket costs{" "} + . +

    + {travelBtns} +
    + ); + } - listWorldMap(): React.ReactNode { - const travelBtns: React.ReactNode[] = []; - for (const key in CityName) { - const city: CityName = (CityName as any)[key]; - - // Skip current city - if (city === this.props.p.city) { continue; } - - travelBtns.push( - , - ) - } - - return ( -
    -

    - From here, you can travel to any other city! A ticket - costs . -

    - {travelBtns} -
    - ) - } - - render(): React.ReactNode { - if (Settings.DisableASCIIArt) { - return this.listWorldMap(); - } else { - return this.asciiWorldMap(); - } + render(): React.ReactNode { + if (Settings.DisableASCIIArt) { + return this.listWorldMap(); + } else { + return this.asciiWorldMap(); } + } } diff --git a/src/Locations/ui/UniversityLocation.tsx b/src/Locations/ui/UniversityLocation.tsx index fd0e1fe73..a8fd3981b 100644 --- a/src/Locations/ui/UniversityLocation.tsx +++ b/src/Locations/ui/UniversityLocation.tsx @@ -5,132 +5,158 @@ */ import * as React from "react"; -import { Location } from "../Location"; +import { Location } from "../Location"; -import { CONSTANTS } from "../../Constants"; -import { IPlayer } from "../../PersonObjects/IPlayer"; -import { getServer } from "../../Server/ServerHelpers"; -import { Server } from "../../Server/Server"; -import { SpecialServerIps } from "../../Server/SpecialServerIps"; +import { CONSTANTS } from "../../Constants"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { getServer } from "../../Server/ServerHelpers"; +import { Server } from "../../Server/Server"; +import { SpecialServerIps } from "../../Server/SpecialServerIps"; -import { StdButton } from "../../ui/React/StdButton"; -import { Money } from "../../ui/React/Money"; +import { StdButton } from "../../ui/React/StdButton"; +import { Money } from "../../ui/React/Money"; type IProps = { - loc: Location; - p: IPlayer; -} + loc: Location; + p: IPlayer; +}; export class UniversityLocation extends React.Component { - /** - * Stores button styling that sets them all to block display - */ - btnStyle: any; + /** + * Stores button styling that sets them all to block display + */ + btnStyle: any; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.btnStyle = { display: "block" }; + this.btnStyle = { display: "block" }; - this.take = this.take.bind(this); - this.study = this.study.bind(this); - this.dataStructures = this.dataStructures.bind(this); - this.networks = this.networks.bind(this); - this.algorithms = this.algorithms.bind(this); - this.management = this.management.bind(this); - this.leadership = this.leadership.bind(this); + this.take = this.take.bind(this); + this.study = this.study.bind(this); + this.dataStructures = this.dataStructures.bind(this); + this.networks = this.networks.bind(this); + this.algorithms = this.algorithms.bind(this); + this.management = this.management.bind(this); + this.leadership = this.leadership.bind(this); - this.calculateCost = this.calculateCost.bind(this); - } + this.calculateCost = this.calculateCost.bind(this); + } - calculateCost(): number { - const ip = SpecialServerIps.getIp(this.props.loc.name); - const server = getServer(ip); - if(server == null || !server.hasOwnProperty('backdoorInstalled')) return this.props.loc.costMult; - const discount = (server as Server).backdoorInstalled? 0.9 : 1; - return this.props.loc.costMult * discount; - } + calculateCost(): number { + const ip = SpecialServerIps.getIp(this.props.loc.name); + const server = getServer(ip); + if (server == null || !server.hasOwnProperty("backdoorInstalled")) + return this.props.loc.costMult; + const discount = (server as Server).backdoorInstalled ? 0.9 : 1; + return this.props.loc.costMult * discount; + } - take(stat: string): void { - const loc = this.props.loc; - this.props.p.startClass(this.calculateCost(), loc.expMult, stat); - } + take(stat: string): void { + const loc = this.props.loc; + this.props.p.startClass(this.calculateCost(), loc.expMult, stat); + } - study(): void { - this.take(CONSTANTS.ClassStudyComputerScience); - } + study(): void { + this.take(CONSTANTS.ClassStudyComputerScience); + } - dataStructures(): void { - this.take(CONSTANTS.ClassDataStructures); - } + dataStructures(): void { + this.take(CONSTANTS.ClassDataStructures); + } - networks(): void { - this.take(CONSTANTS.ClassNetworks); - } + networks(): void { + this.take(CONSTANTS.ClassNetworks); + } - algorithms(): void { - this.take(CONSTANTS.ClassAlgorithms); - } + algorithms(): void { + this.take(CONSTANTS.ClassAlgorithms); + } - management(): void { - this.take(CONSTANTS.ClassManagement); - } + management(): void { + this.take(CONSTANTS.ClassManagement); + } - leadership(): void { - this.take(CONSTANTS.ClassLeadership); - } + leadership(): void { + this.take(CONSTANTS.ClassLeadership); + } - render(): React.ReactNode { - const costMult: number = this.calculateCost(); + render(): React.ReactNode { + const costMult: number = this.calculateCost(); - const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult; - const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult; - const algorithmsCost = CONSTANTS.ClassAlgorithmsBaseCost * costMult; - const managementCost = CONSTANTS.ClassManagementBaseCost * costMult; - const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult; + const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult; + const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult; + const algorithmsCost = CONSTANTS.ClassAlgorithmsBaseCost * costMult; + const managementCost = CONSTANTS.ClassManagementBaseCost * costMult; + const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult; - const earnHackingExpTooltip = `Gain hacking experience!` - const earnCharismaExpTooltip = `Gain charisma experience!`; + const earnHackingExpTooltip = `Gain hacking experience!`; + const earnCharismaExpTooltip = `Gain charisma experience!`; - return ( -
    - - Take Data Structures course ( / sec)} - tooltip={earnHackingExpTooltip} - /> - Take Networks course ( / sec)} - tooltip={earnHackingExpTooltip} - /> - Take Algorithms course ( / sec)} - tooltip={earnHackingExpTooltip} - /> - Take Management course ( / sec)} - tooltip={earnCharismaExpTooltip} - /> - Take Leadership course ( / sec)} - tooltip={earnCharismaExpTooltip} - /> -
    - ) - } + return ( +
    + + + Take Data Structures course ( + / sec) + + } + tooltip={earnHackingExpTooltip} + /> + + Take Networks course ( + / sec) + + } + tooltip={earnHackingExpTooltip} + /> + + Take Algorithms course ( + / sec) + + } + tooltip={earnHackingExpTooltip} + /> + + Take Management course ( + / sec) + + } + tooltip={earnCharismaExpTooltip} + /> + + Take Leadership course ( + / sec) + + } + tooltip={earnCharismaExpTooltip} + /> +
    + ); + } } diff --git a/src/Message/Message.ts b/src/Message/Message.ts index 737409219..0c475623c 100644 --- a/src/Message/Message.ts +++ b/src/Message/Message.ts @@ -1,34 +1,35 @@ -import { Reviver, - Generic_toJSON, - Generic_fromJSON } from "../../utils/JSONReviver"; +import { + Reviver, + Generic_toJSON, + Generic_fromJSON, +} from "../../utils/JSONReviver"; export class Message { + // Name of Message file + filename = ""; - // Name of Message file - filename = ""; + // The text contains in the Message + msg = ""; - // The text contains in the Message - msg = ""; + // Flag indicating whether this Message has been received by the player + recvd = false; - // Flag indicating whether this Message has been received by the player - recvd = false; + constructor(filename = "", msg = "") { + this.filename = filename; + this.msg = msg; + this.recvd = false; + } - constructor(filename="", msg="") { - this.filename = filename; - this.msg = msg; - this.recvd = false; - } + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("Message", this); + } - // Serialize the current object to a JSON save state - toJSON(): any { - return Generic_toJSON("Message", this); - } - - // Initializes a Message Object from a JSON save state - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Message { - return Generic_fromJSON(Message, value.data); - } + // Initializes a Message Object from a JSON save state + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Message { + return Generic_fromJSON(Message, value.data); + } } Reviver.constructors.Message = Message; diff --git a/src/Message/MessageHelpers.js b/src/Message/MessageHelpers.js index 79bda4adc..a50af2118 100644 --- a/src/Message/MessageHelpers.js +++ b/src/Message/MessageHelpers.js @@ -7,171 +7,233 @@ import { Player } from "../Player"; import { redPillFlag } from "../RedPill"; import { GetServerByHostname } from "../Server/ServerHelpers"; import { Settings } from "../Settings/Settings"; -import { dialogBoxCreate} from "../../utils/DialogBox"; +import { dialogBoxCreate } from "../../utils/DialogBox"; import { Reviver } from "../../utils/JSONReviver"; //Sends message to player, including a pop up -function sendMessage(msg, forced=false) { - msg.recvd = true; - if (forced || !Settings.SuppressMessages) { - showMessage(msg); - } - addMessageToServer(msg, "home"); +function sendMessage(msg, forced = false) { + msg.recvd = true; + if (forced || !Settings.SuppressMessages) { + showMessage(msg); + } + addMessageToServer(msg, "home"); } function showMessage(msg) { - var txt = "Message received from unknown sender:

    " + - "" + msg.msg + "

    " + - "This message was saved as " + msg.filename + " onto your home computer."; - dialogBoxCreate(txt); + var txt = + "Message received from unknown sender:

    " + + "" + + msg.msg + + "

    " + + "This message was saved as " + + msg.filename + + " onto your home computer."; + dialogBoxCreate(txt); } //Adds a message to a server function addMessageToServer(msg, serverHostname) { - var server = GetServerByHostname(serverHostname); - if (server == null) { - console.warn(`Could not find server ${serverHostname}`); - return; + var server = GetServerByHostname(serverHostname); + if (server == null) { + console.warn(`Could not find server ${serverHostname}`); + return; + } + for (var i = 0; i < server.messages.length; ++i) { + if (server.messages[i].filename === msg.filename) { + return; //Already exists } - for (var i = 0; i < server.messages.length; ++i) { - if (server.messages[i].filename === msg.filename) { - return; //Already exists - } - } - server.messages.push(msg); + } + server.messages.push(msg); } //Checks if any of the 'timed' messages should be sent function checkForMessagesToSend() { - var jumper0 = Messages[MessageFilenames.Jumper0]; - var jumper1 = Messages[MessageFilenames.Jumper1]; - var jumper2 = Messages[MessageFilenames.Jumper2]; - var jumper3 = Messages[MessageFilenames.Jumper3]; - var jumper4 = Messages[MessageFilenames.Jumper4]; - var cybersecTest = Messages[MessageFilenames.CyberSecTest]; - var nitesecTest = Messages[MessageFilenames.NiteSecTest]; - var bitrunnersTest = Messages[MessageFilenames.BitRunnersTest]; - var redpill = Messages[MessageFilenames.RedPill]; + var jumper0 = Messages[MessageFilenames.Jumper0]; + var jumper1 = Messages[MessageFilenames.Jumper1]; + var jumper2 = Messages[MessageFilenames.Jumper2]; + var jumper3 = Messages[MessageFilenames.Jumper3]; + var jumper4 = Messages[MessageFilenames.Jumper4]; + var cybersecTest = Messages[MessageFilenames.CyberSecTest]; + var nitesecTest = Messages[MessageFilenames.NiteSecTest]; + var bitrunnersTest = Messages[MessageFilenames.BitRunnersTest]; + var redpill = Messages[MessageFilenames.RedPill]; - var redpillOwned = false; - if (Augmentations[AugmentationNames.TheRedPill].owned) { - redpillOwned = true; - } + var redpillOwned = false; + if (Augmentations[AugmentationNames.TheRedPill].owned) { + redpillOwned = true; + } - if (redpill && redpillOwned && Player.sourceFiles.length === 0 && !redPillFlag && !inMission) { - sendMessage(redpill, true); - } else if (redpill && redpillOwned) { - //If player has already destroyed a BitNode, message is not forced - if (!redPillFlag && !inMission) { - sendMessage(redpill); - } - } else if (jumper0 && !jumper0.recvd && Player.hacking_skill >= 25) { - sendMessage(jumper0); - const flightName = Programs.Flight.name; - const homeComp = Player.getHomeComputer(); - if (!homeComp.programs.includes(flightName)) { - homeComp.programs.push(flightName); - } - } else if (jumper1 && !jumper1.recvd && Player.hacking_skill >= 40) { - sendMessage(jumper1); - } else if (cybersecTest && !cybersecTest.recvd && Player.hacking_skill >= 50) { - sendMessage(cybersecTest); - } else if (jumper2 && !jumper2.recvd && Player.hacking_skill >= 175) { - sendMessage(jumper2); - } else if (nitesecTest && !nitesecTest.recvd && Player.hacking_skill >= 200) { - sendMessage(nitesecTest); - } else if (jumper3 && !jumper3.recvd && Player.hacking_skill >= 350) { - sendMessage(jumper3); - } else if (jumper4 && !jumper4.recvd && Player.hacking_skill >= 490) { - sendMessage(jumper4); - } else if (bitrunnersTest && !bitrunnersTest.recvd && Player.hacking_skill >= 500) { - sendMessage(bitrunnersTest); + if ( + redpill && + redpillOwned && + Player.sourceFiles.length === 0 && + !redPillFlag && + !inMission + ) { + sendMessage(redpill, true); + } else if (redpill && redpillOwned) { + //If player has already destroyed a BitNode, message is not forced + if (!redPillFlag && !inMission) { + sendMessage(redpill); } + } else if (jumper0 && !jumper0.recvd && Player.hacking_skill >= 25) { + sendMessage(jumper0); + const flightName = Programs.Flight.name; + const homeComp = Player.getHomeComputer(); + if (!homeComp.programs.includes(flightName)) { + homeComp.programs.push(flightName); + } + } else if (jumper1 && !jumper1.recvd && Player.hacking_skill >= 40) { + sendMessage(jumper1); + } else if ( + cybersecTest && + !cybersecTest.recvd && + Player.hacking_skill >= 50 + ) { + sendMessage(cybersecTest); + } else if (jumper2 && !jumper2.recvd && Player.hacking_skill >= 175) { + sendMessage(jumper2); + } else if (nitesecTest && !nitesecTest.recvd && Player.hacking_skill >= 200) { + sendMessage(nitesecTest); + } else if (jumper3 && !jumper3.recvd && Player.hacking_skill >= 350) { + sendMessage(jumper3); + } else if (jumper4 && !jumper4.recvd && Player.hacking_skill >= 490) { + sendMessage(jumper4); + } else if ( + bitrunnersTest && + !bitrunnersTest.recvd && + Player.hacking_skill >= 500 + ) { + sendMessage(bitrunnersTest); + } } function AddToAllMessages(msg) { - Messages[msg.filename] = msg; + Messages[msg.filename] = msg; } -let Messages = {} +let Messages = {}; function loadMessages(saveString) { - Messages = JSON.parse(saveString, Reviver); + Messages = JSON.parse(saveString, Reviver); } let MessageFilenames = { - Jumper0: "j0.msg", - Jumper1: "j1.msg", - Jumper2: "j2.msg", - Jumper3: "j3.msg", - Jumper4: "j4.msg", - CyberSecTest: "csec-test.msg", - NiteSecTest: "nitesec-test.msg", - BitRunnersTest: "19dfj3l1nd.msg", - RedPill: "icarus.msg", + Jumper0: "j0.msg", + Jumper1: "j1.msg", + Jumper2: "j2.msg", + Jumper3: "j3.msg", + Jumper4: "j4.msg", + CyberSecTest: "csec-test.msg", + NiteSecTest: "nitesec-test.msg", + BitRunnersTest: "19dfj3l1nd.msg", + RedPill: "icarus.msg", +}; + +function initMessages() { + //Reset + Messages = {}; + + //jump3R Messages + AddToAllMessages( + new Message( + MessageFilenames.Jumper0, + "I know you can sense it. I know you're searching for it. " + + "It's why you spend night after " + + "night at your computer.

    It's real, I've seen it. And I can " + + "help you find it. But not right now. You're not ready yet.

    " + + "Use this program to track your progress

    " + + "The fl1ght.exe program was added to your home computer

    " + + "-jump3R", + ), + ); + AddToAllMessages( + new Message( + MessageFilenames.Jumper1, + "Soon you will be contacted by a hacking group known as CyberSec. " + + "They can help you with your search.

    " + + "You should join them, garner their favor, and " + + "exploit them for their Augmentations. But do not trust them. " + + "They are not what they seem. No one is.

    " + + "-jump3R", + ), + ); + AddToAllMessages( + new Message( + MessageFilenames.Jumper2, + "Do not try to save the world. There is no world to save. If " + + "you want to find the truth, worry only about yourself. Ethics and " + + "morals will get you killed.

    Watch out for a hacking group known as NiteSec." + + "

    -jump3R", + ), + ); + AddToAllMessages( + new Message( + MessageFilenames.Jumper3, + "You must learn to walk before you can run. And you must " + + "run before you can fly. Look for the black hand.

    " + + "I.I.I.I

    -jump3R", + ), + ); + AddToAllMessages( + new Message( + MessageFilenames.Jumper4, + "To find what you are searching for, you must understand the bits. " + + "The bits are all around us. The runners will help you.

    " + + "-jump3R", + ), + ); + + //Messages from hacking factions + AddToAllMessages( + new Message( + MessageFilenames.CyberSecTest, + "We've been watching you. Your skills are very impressive. But you're wasting " + + "your talents. If you join us, you can put your skills to good use and change " + + "the world for the better. If you join us, we can unlock your full potential.

    " + + "But first, you must pass our test. Find and install the backdoor on our server.

    " + + "-CyberSec", + ), + ); + AddToAllMessages( + new Message( + MessageFilenames.NiteSecTest, + "People say that the corrupted governments and corporations rule the world. " + + "Yes, maybe they do. But do you know who everyone really fears? People " + + "like us. Because they can't hide from us. Because they can't fight shadows " + + "and ideas with bullets.

    " + + "Join us, and people will fear you, too.

    " + + "Find and install the backdoor on our server. Then, we will contact you again." + + "

    -NiteSec", + ), + ); + AddToAllMessages( + new Message( + MessageFilenames.BitRunnersTest, + "We know what you are doing. We know what drives you. We know " + + "what you are looking for.

    " + + "We can help you find the answers.

    " + + "run4theh111z", + ), + ); + + AddToAllMessages( + new Message( + MessageFilenames.RedPill, + "@)(#V%*N)@(#*)*C)@#%*)*V)@#(*%V@)(#VN%*)@#(*%
    " + + ")@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)
    " + + "@_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB
    " + + "DFLSMFVMV)#@($*)@#*$MV)@#(*$V)M#(*$)M@(#*VM$)", + ), + ); } -function initMessages() { - //Reset - Messages = {}; - - //jump3R Messages - AddToAllMessages(new Message(MessageFilenames.Jumper0, - "I know you can sense it. I know you're searching for it. " + - "It's why you spend night after " + - "night at your computer.

    It's real, I've seen it. And I can " + - "help you find it. But not right now. You're not ready yet.

    " + - "Use this program to track your progress

    " + - "The fl1ght.exe program was added to your home computer

    " + - "-jump3R")); - AddToAllMessages(new Message(MessageFilenames.Jumper1, - "Soon you will be contacted by a hacking group known as CyberSec. " + - "They can help you with your search.

    " + - "You should join them, garner their favor, and " + - "exploit them for their Augmentations. But do not trust them. " + - "They are not what they seem. No one is.

    " + - "-jump3R")); - AddToAllMessages(new Message(MessageFilenames.Jumper2, - "Do not try to save the world. There is no world to save. If " + - "you want to find the truth, worry only about yourself. Ethics and " + - "morals will get you killed.

    Watch out for a hacking group known as NiteSec." + - "

    -jump3R")); - AddToAllMessages(new Message(MessageFilenames.Jumper3, - "You must learn to walk before you can run. And you must " + - "run before you can fly. Look for the black hand.

    " + - "I.I.I.I

    -jump3R")); - AddToAllMessages(new Message(MessageFilenames.Jumper4, - "To find what you are searching for, you must understand the bits. " + - "The bits are all around us. The runners will help you.

    " + - "-jump3R")); - - //Messages from hacking factions - AddToAllMessages(new Message(MessageFilenames.CyberSecTest, - "We've been watching you. Your skills are very impressive. But you're wasting " + - "your talents. If you join us, you can put your skills to good use and change " + - "the world for the better. If you join us, we can unlock your full potential.

    " + - "But first, you must pass our test. Find and install the backdoor on our server.

    " + - "-CyberSec")); - AddToAllMessages(new Message(MessageFilenames.NiteSecTest, - "People say that the corrupted governments and corporations rule the world. " + - "Yes, maybe they do. But do you know who everyone really fears? People " + - "like us. Because they can't hide from us. Because they can't fight shadows " + - "and ideas with bullets.

    " + - "Join us, and people will fear you, too.

    " + - "Find and install the backdoor on our server. Then, we will contact you again." + - "

    -NiteSec")); - AddToAllMessages(new Message(MessageFilenames.BitRunnersTest, - "We know what you are doing. We know what drives you. We know " + - "what you are looking for.

    " + - "We can help you find the answers.

    " + - "run4theh111z")); - - AddToAllMessages(new Message(MessageFilenames.RedPill, - "@)(#V%*N)@(#*)*C)@#%*)*V)@#(*%V@)(#VN%*)@#(*%
    " + - ")@B(*#%)@)M#B*%V)____FIND___#$@)#%(B*)@#(*%B)
    " + - "@_#(%_@#M(BDSPOMB__THE-CAVE_#)$(*@#$)@#BNBEGB
    " + - "DFLSMFVMV)#@($*)@#*$MV)@#(*$V)M#(*$)M@(#*VM$)")); -} - -export {Messages, checkForMessagesToSend, sendMessage, showMessage, loadMessages, - initMessages, Message}; +export { + Messages, + checkForMessagesToSend, + sendMessage, + showMessage, + loadMessages, + initMessages, + Message, +}; diff --git a/src/Milestones/Milestone.ts b/src/Milestones/Milestone.ts index 3f9dec424..dd5c0b7e9 100644 --- a/src/Milestones/Milestone.ts +++ b/src/Milestones/Milestone.ts @@ -1,6 +1,6 @@ import { IPlayer } from "../PersonObjects/IPlayer"; export type Milestone = { - title: string; - fulfilled: (p: IPlayer) => boolean; -} \ No newline at end of file + title: string; + fulfilled: (p: IPlayer) => boolean; +}; diff --git a/src/Milestones/MilestoneHelpers.tsx b/src/Milestones/MilestoneHelpers.tsx index e9ddac327..7ea791d77 100644 --- a/src/Milestones/MilestoneHelpers.tsx +++ b/src/Milestones/MilestoneHelpers.tsx @@ -6,24 +6,21 @@ import * as ReactDOM from "react-dom"; let milestonesContainer: HTMLElement | null = null; -(function(){ - function setContainer(): void { - milestonesContainer = document.getElementById("milestones-container"); - document.removeEventListener("DOMContentLoaded", setContainer); - } +(function () { + function setContainer(): void { + milestonesContainer = document.getElementById("milestones-container"); + document.removeEventListener("DOMContentLoaded", setContainer); + } - document.addEventListener("DOMContentLoaded", setContainer); + document.addEventListener("DOMContentLoaded", setContainer); })(); export function displayMilestonesContent(): void { - if (!routing.isOn(Page.Milestones)) { - return; - } + if (!routing.isOn(Page.Milestones)) { + return; + } - if (milestonesContainer instanceof HTMLElement) { - ReactDOM.render( - , - milestonesContainer, - ); - } -} \ No newline at end of file + if (milestonesContainer instanceof HTMLElement) { + ReactDOM.render(, milestonesContainer); + } +} diff --git a/src/Milestones/Milestones.ts b/src/Milestones/Milestones.ts index 97a6d6b3a..96230e73a 100644 --- a/src/Milestones/Milestones.ts +++ b/src/Milestones/Milestones.ts @@ -5,95 +5,102 @@ import { Faction } from "../Faction/Faction"; import { GetServerByHostname } from "../Server/ServerHelpers"; function allFactionAugs(p: IPlayer, f: Faction): boolean { - const factionAugs = f.augmentations.slice().filter((aug)=> aug !== "NeuroFlux Governor"); - for(const factionAug of factionAugs) { - if(!p.augmentations.some(aug => {return aug.name == factionAug})) return false; - } - return true; + const factionAugs = f.augmentations + .slice() + .filter((aug) => aug !== "NeuroFlux Governor"); + for (const factionAug of factionAugs) { + if ( + !p.augmentations.some((aug) => { + return aug.name == factionAug; + }) + ) + return false; + } + return true; } export const Milestones: Milestone[] = [ - { - title: "Gain root access on CSEC", - fulfilled: (): boolean => { - const server = GetServerByHostname("CSEC"); - if(!server || !server.hasOwnProperty('hasAdminRights')) return false; - return (server as any).hasAdminRights; - }, + { + title: "Gain root access on CSEC", + fulfilled: (): boolean => { + const server = GetServerByHostname("CSEC"); + if (!server || !server.hasOwnProperty("hasAdminRights")) return false; + return (server as any).hasAdminRights; }, - { - title: "Install the backdoor on CSEC", - fulfilled: (): boolean => { - const server = GetServerByHostname("CSEC"); - if(!server || !server.hasOwnProperty('backdoorInstalled')) return false; - return (server as any).backdoorInstalled; - }, + }, + { + title: "Install the backdoor on CSEC", + fulfilled: (): boolean => { + const server = GetServerByHostname("CSEC"); + if (!server || !server.hasOwnProperty("backdoorInstalled")) return false; + return (server as any).backdoorInstalled; }, - { - title: "Join the faction hinted at in csec-test.msg", - fulfilled: (p: IPlayer): boolean => { - return p.factions.includes("CyberSec"); - }, + }, + { + title: "Join the faction hinted at in csec-test.msg", + fulfilled: (p: IPlayer): boolean => { + return p.factions.includes("CyberSec"); }, - { - title: "Install all the Augmentations from CyberSec", - fulfilled: (p: IPlayer): boolean => { - return allFactionAugs(p, Factions["CyberSec"]); - }, + }, + { + title: "Install all the Augmentations from CyberSec", + fulfilled: (p: IPlayer): boolean => { + return allFactionAugs(p, Factions["CyberSec"]); }, - { - title: "Join the faction hinted at in nitesec-test.msg", - fulfilled: (p: IPlayer): boolean => { - return p.factions.includes("NiteSec"); - }, + }, + { + title: "Join the faction hinted at in nitesec-test.msg", + fulfilled: (p: IPlayer): boolean => { + return p.factions.includes("NiteSec"); }, - { - title: "Install all the Augmentations from NiteSec", - fulfilled: (p: IPlayer): boolean => { - return allFactionAugs(p, Factions["NiteSec"]); - }, + }, + { + title: "Install all the Augmentations from NiteSec", + fulfilled: (p: IPlayer): boolean => { + return allFactionAugs(p, Factions["NiteSec"]); }, - { - title: "Join the faction hinted at in j3.msg", - fulfilled: (p: IPlayer): boolean => { - return p.factions.includes("The Black Hand"); - }, + }, + { + title: "Join the faction hinted at in j3.msg", + fulfilled: (p: IPlayer): boolean => { + return p.factions.includes("The Black Hand"); }, - { - title: "Install all the Augmentations from The Black Hand", - fulfilled: (p: IPlayer): boolean => { - return allFactionAugs(p, Factions["The Black Hand"]); - }, + }, + { + title: "Install all the Augmentations from The Black Hand", + fulfilled: (p: IPlayer): boolean => { + return allFactionAugs(p, Factions["The Black Hand"]); }, - { - title: "Join the faction hinted at in 19dfj3l1nd.msg", - fulfilled: (p: IPlayer): boolean => { - return p.factions.includes("BitRunners"); - }, + }, + { + title: "Join the faction hinted at in 19dfj3l1nd.msg", + fulfilled: (p: IPlayer): boolean => { + return p.factions.includes("BitRunners"); }, - { - title: "Install all the Augmentations from BitRunners", - fulfilled: (p: IPlayer): boolean => { - return allFactionAugs(p, Factions["BitRunners"]); - }, + }, + { + title: "Install all the Augmentations from BitRunners", + fulfilled: (p: IPlayer): boolean => { + return allFactionAugs(p, Factions["BitRunners"]); }, - { - title: "Complete fl1ght.exe", - fulfilled: (p: IPlayer): boolean => { - // technically wrong but whatever - return p.factions.includes("Daedalus"); - }, + }, + { + title: "Complete fl1ght.exe", + fulfilled: (p: IPlayer): boolean => { + // technically wrong but whatever + return p.factions.includes("Daedalus"); }, - { - title: "Install the special Augmentation from Daedalus", - fulfilled: (p: IPlayer): boolean => { - return p.augmentations.some(aug => aug.name == "The Red Pill") - }, + }, + { + title: "Install the special Augmentation from Daedalus", + fulfilled: (p: IPlayer): boolean => { + return p.augmentations.some((aug) => aug.name == "The Red Pill"); }, - { - title: "Install the final backdoor and free yourself.", - fulfilled: (): boolean => { - return false; - }, + }, + { + title: "Install the final backdoor and free yourself.", + fulfilled: (): boolean => { + return false; }, -]; \ No newline at end of file + }, +]; diff --git a/src/Milestones/Quest.ts b/src/Milestones/Quest.ts index 98d89e0d3..80e69b931 100644 --- a/src/Milestones/Quest.ts +++ b/src/Milestones/Quest.ts @@ -1,11 +1,11 @@ import { Milestone } from "./Milestone"; export class Quest { - title: string; - milestones: Milestone[]; - - constructor(title: string, milestones: Milestone[]) { - this.title = title; - this.milestones = milestones; - } -} \ No newline at end of file + title: string; + milestones: Milestone[]; + + constructor(title: string, milestones: Milestone[]) { + this.title = title; + this.milestones = milestones; + } +} diff --git a/src/Milestones/ui/Root.tsx b/src/Milestones/ui/Root.tsx index bbf191588..f278da7d2 100644 --- a/src/Milestones/ui/Root.tsx +++ b/src/Milestones/ui/Root.tsx @@ -4,34 +4,42 @@ import { Milestone } from "../Milestone"; import * as React from "react"; interface IProps { - player: IPlayer; + player: IPlayer; } function highestMilestone(p: IPlayer, milestones: Milestone[]): number { - let n = -1; - for(let i = 0; i < milestones.length; i++) { - if(milestones[i].fulfilled(p)) n = i; - } + let n = -1; + for (let i = 0; i < milestones.length; i++) { + if (milestones[i].fulfilled(p)) n = i; + } - return n; + return n; } export function Root(props: IProps): JSX.Element { - const n = highestMilestone(props.player, Milestones); - const milestones = Milestones.map((milestone: Milestone, i: number) => { - if (i<=n+1) { - return (
      -

      [{milestone.fulfilled(props.player)?"x":" "}] {milestone.title}

      -
    ) - } - }) - return (<> -

    Milestones

    -

    Milestones don't reward you for completing them. They are here to guide you if you're lost. They will reset when you install Augmentations.


    - -

    Completing fl1ght.exe

    -
  • - {milestones} -
  • - ); -} \ No newline at end of file + const n = highestMilestone(props.player, Milestones); + const milestones = Milestones.map((milestone: Milestone, i: number) => { + if (i <= n + 1) { + return ( +
      +

      + [{milestone.fulfilled(props.player) ? "x" : " "}] {milestone.title} +

      +
    + ); + } + }); + return ( + <> +

    Milestones

    +

    + Milestones don't reward you for completing them. They are here to guide + you if you're lost. They will reset when you install Augmentations. +

    +
    + +

    Completing fl1ght.exe

    +
  • {milestones}
  • + + ); +} diff --git a/src/Missions.jsx b/src/Missions.jsx index fb4ccc0f0..6847e6cbe 100644 --- a/src/Missions.jsx +++ b/src/Missions.jsx @@ -21,162 +21,164 @@ import jsplumb from "jsplumb"; import React from "react"; import ReactDOM from "react-dom"; - let inMission = false; // Flag to denote whether a mission is running let currMission = null; function setInMission(bool, mission) { - inMission = bool; - if (bool) { - currMission = mission; - } else { - currMission = null; - } + inMission = bool; + if (bool) { + currMission = mission; + } else { + currMission = null; + } } // Keyboard shortcuts -$(document).keydown(function(e) { - if (inMission && currMission && currMission.selectedNode.length != 0) { - switch (e.keyCode) { - case 65: // a for Attack - currMission.actionButtons[0].click(); - break; - case 83: // s for Scan - currMission.actionButtons[1].click(); - break; - case 87: // w for Weaken - currMission.actionButtons[2].click(); - break; - case 70: // f for Fortify - currMission.actionButtons[3].click(); - break; - case 82: // r for Overflow - currMission.actionButtons[4].click(); - break; - case 68: // d for Detach connection - currMission.actionButtons[5].click(); - break; - default: - break; - } +$(document).keydown(function (e) { + if (inMission && currMission && currMission.selectedNode.length != 0) { + switch (e.keyCode) { + case 65: // a for Attack + currMission.actionButtons[0].click(); + break; + case 83: // s for Scan + currMission.actionButtons[1].click(); + break; + case 87: // w for Weaken + currMission.actionButtons[2].click(); + break; + case 70: // f for Fortify + currMission.actionButtons[3].click(); + break; + case 82: // r for Overflow + currMission.actionButtons[4].click(); + break; + case 68: // d for Detach connection + currMission.actionButtons[5].click(); + break; + default: + break; } + } }); let NodeTypes = { - Core: "CPU Core Node", // All actions available - Firewall: "Firewall Node", // No actions available - Database: "Database Node", // No actions available - Spam: "Spam Node", // No actions Available - Transfer: "Transfer Node", // Can Weaken, Scan, Fortify and Overflow - Shield: "Shield Node", // Can Fortify -} + Core: "CPU Core Node", // All actions available + Firewall: "Firewall Node", // No actions available + Database: "Database Node", // No actions available + Spam: "Spam Node", // No actions Available + Transfer: "Transfer Node", // Can Weaken, Scan, Fortify and Overflow + Shield: "Shield Node", // Can Fortify +}; let NodeActions = { - Attack: "Attacking", // Damaged based on attack stat + hacking level + opp def - Scan: "Scanning", // -Def for target, affected by attack and hacking level - Weaken: "Weakening", // -Attack for target, affected by attack and hacking level - Fortify: "Fortifying", // +Defense for Node, affected by hacking level - Overflow: "Overflowing", // +Attack but -Defense for Node, affected by hacking level -} + Attack: "Attacking", // Damaged based on attack stat + hacking level + opp def + Scan: "Scanning", // -Def for target, affected by attack and hacking level + Weaken: "Weakening", // -Attack for target, affected by attack and hacking level + Fortify: "Fortifying", // +Defense for Node, affected by hacking level + Overflow: "Overflowing", // +Attack but -Defense for Node, affected by hacking level +}; function Node(type, stats) { - this.type = type; - this.atk = stats.atk ? stats.atk : 0; - this.def = stats.def ? stats.def : 0; - this.hp = stats.hp ? stats.hp : 0; - this.maxhp = this.hp; - this.plyrCtrl = false; - this.enmyCtrl = false; - this.pos = [0, 0]; // x, y - this.el = null; // Holds the Node's DOM element - this.action = null; - this.targetedCount = 0; // Count of how many connections this node is the target of + this.type = type; + this.atk = stats.atk ? stats.atk : 0; + this.def = stats.def ? stats.def : 0; + this.hp = stats.hp ? stats.hp : 0; + this.maxhp = this.hp; + this.plyrCtrl = false; + this.enmyCtrl = false; + this.pos = [0, 0]; // x, y + this.el = null; // Holds the Node's DOM element + this.action = null; + this.targetedCount = 0; // Count of how many connections this node is the target of - /** - * Holds the JsPlumb Connection object for this Node, where this Node is the Source (since each Node - * can only have 1 outgoing Connection) - */ - this.conn = null; + /** + * Holds the JsPlumb Connection object for this Node, where this Node is the Source (since each Node + * can only have 1 outgoing Connection) + */ + this.conn = null; } -Node.prototype.setPosition = function(x, y) { - this.pos = [x, y]; -} +Node.prototype.setPosition = function (x, y) { + this.pos = [x, y]; +}; -Node.prototype.setControlledByPlayer = function() { - this.plyrCtrl = true; - this.enmyCtrl = false; - if (this.el) { - this.el.classList.remove("hack-mission-enemy-node"); - this.el.classList.add("hack-mission-player-node"); - } -} +Node.prototype.setControlledByPlayer = function () { + this.plyrCtrl = true; + this.enmyCtrl = false; + if (this.el) { + this.el.classList.remove("hack-mission-enemy-node"); + this.el.classList.add("hack-mission-player-node"); + } +}; -Node.prototype.setControlledByEnemy = function() { - this.plyrCtrl = false; - this.enmyCtrl = true; - if (this.el) { - this.el.classList.remove("hack-mission-player-node"); - this.el.classList.add("hack-mission-enemy-node"); - } -} +Node.prototype.setControlledByEnemy = function () { + this.plyrCtrl = false; + this.enmyCtrl = true; + if (this.el) { + this.el.classList.remove("hack-mission-player-node"); + this.el.classList.add("hack-mission-enemy-node"); + } +}; // Sets this node to be the active node -Node.prototype.select = function(actionButtons) { - if (this.enmyCtrl) {return;} - this.el.classList.add("hack-mission-player-node-active"); +Node.prototype.select = function (actionButtons) { + if (this.enmyCtrl) { + return; + } + this.el.classList.add("hack-mission-player-node-active"); - // Make all buttons inactive - for (var i = 0; i < actionButtons.length; ++i) { - actionButtons[i].classList.remove("a-link-button"); - actionButtons[i].classList.add("a-link-button-inactive"); - } + // Make all buttons inactive + for (var i = 0; i < actionButtons.length; ++i) { + actionButtons[i].classList.remove("a-link-button"); + actionButtons[i].classList.add("a-link-button-inactive"); + } - switch(this.type) { - case NodeTypes.Core: - // All buttons active - for (var i = 0; i < actionButtons.length; ++i) { - actionButtons[i].classList.remove("a-link-button-inactive"); - actionButtons[i].classList.add("a-link-button"); - } - break; - case NodeTypes.Transfer: - actionButtons[1].classList.remove("a-link-button-inactive"); - actionButtons[1].classList.add("a-link-button"); - actionButtons[2].classList.remove("a-link-button-inactive"); - actionButtons[2].classList.add("a-link-button"); - actionButtons[3].classList.remove("a-link-button-inactive"); - actionButtons[3].classList.add("a-link-button"); - actionButtons[4].classList.remove("a-link-button-inactive"); - actionButtons[4].classList.add("a-link-button"); - actionButtons[5].classList.remove("a-link-button-inactive"); - actionButtons[5].classList.add("a-link-button"); - break; - case NodeTypes.Shield: - case NodeTypes.Firewall: - actionButtons[3].classList.remove("a-link-button-inactive"); - actionButtons[3].classList.add("a-link-button"); - break; - default: - break; - } -} + switch (this.type) { + case NodeTypes.Core: + // All buttons active + for (var i = 0; i < actionButtons.length; ++i) { + actionButtons[i].classList.remove("a-link-button-inactive"); + actionButtons[i].classList.add("a-link-button"); + } + break; + case NodeTypes.Transfer: + actionButtons[1].classList.remove("a-link-button-inactive"); + actionButtons[1].classList.add("a-link-button"); + actionButtons[2].classList.remove("a-link-button-inactive"); + actionButtons[2].classList.add("a-link-button"); + actionButtons[3].classList.remove("a-link-button-inactive"); + actionButtons[3].classList.add("a-link-button"); + actionButtons[4].classList.remove("a-link-button-inactive"); + actionButtons[4].classList.add("a-link-button"); + actionButtons[5].classList.remove("a-link-button-inactive"); + actionButtons[5].classList.add("a-link-button"); + break; + case NodeTypes.Shield: + case NodeTypes.Firewall: + actionButtons[3].classList.remove("a-link-button-inactive"); + actionButtons[3].classList.add("a-link-button"); + break; + default: + break; + } +}; -Node.prototype.deselect = function(actionButtons) { - this.el.classList.remove("hack-mission-player-node-active"); - for (var i = 0; i < actionButtons.length; ++i) { - actionButtons[i].classList.remove("a-link-button"); - actionButtons[i].classList.add("a-link-button-inactive"); - } -} +Node.prototype.deselect = function (actionButtons) { + this.el.classList.remove("hack-mission-player-node-active"); + for (var i = 0; i < actionButtons.length; ++i) { + actionButtons[i].classList.remove("a-link-button"); + actionButtons[i].classList.add("a-link-button-inactive"); + } +}; - -Node.prototype.untarget = function() { - if (this.targetedCount === 0) { - console.warn(`Node ${this.el.id} is being 'untargeted' when it has no target count`); - return; - } - --this.targetedCount; -} +Node.prototype.untarget = function () { + if (this.targetedCount === 0) { + console.warn( + `Node ${this.el.id} is being 'untargeted' when it has no target count`, + ); + return; + } + --this.targetedCount; +}; /** * Hacking mission instance @@ -184,713 +186,767 @@ Node.prototype.untarget = function() { * @param fac {Faction} Faction for which this mission is being conducted */ function HackingMission(rep, fac) { - this.faction = fac; + this.faction = fac; - this.started = false; - this.time = 180000; // 5 minutes to start, milliseconds + this.started = false; + this.time = 180000; // 5 minutes to start, milliseconds - this.playerCores = []; - this.playerNodes = []; // Non-core nodes - this.playerAtk = 0; - this.playerDef = 0; + this.playerCores = []; + this.playerNodes = []; // Non-core nodes + this.playerAtk = 0; + this.playerDef = 0; - this.enemyCores = []; - this.enemyDatabases = []; - this.enemyNodes = []; // Non-core nodes - this.enemyAtk = 0; - this.enemyDef = 0; + this.enemyCores = []; + this.enemyDatabases = []; + this.enemyNodes = []; // Non-core nodes + this.enemyAtk = 0; + this.enemyDef = 0; - this.miscNodes = []; + this.miscNodes = []; - this.selectedNode = []; // Which of the player's nodes are currently selected + this.selectedNode = []; // Which of the player's nodes are currently selected - this.actionButtons = []; // DOM buttons for actions + this.actionButtons = []; // DOM buttons for actions - this.availablePositions = []; - for (var r = 0; r < 8; ++r) { - for (var c = 0; c < 8; ++c) { - this.availablePositions.push([r, c]); - } + this.availablePositions = []; + for (var r = 0; r < 8; ++r) { + for (var c = 0; c < 8; ++c) { + this.availablePositions.push([r, c]); } + } - this.map = []; - for (var i = 0; i < 8; ++i) { - this.map.push([null, null, null, null, null, null, null, null]); - } + this.map = []; + for (var i = 0; i < 8; ++i) { + this.map.push([null, null, null, null, null, null, null, null]); + } - this.jsplumbinstance = null; + this.jsplumbinstance = null; - this.difficulty = rep / CONSTANTS.HackingMissionRepToDiffConversion + 1; - this.reward = 250 + (rep / CONSTANTS.HackingMissionRepToRewardConversion); + this.difficulty = rep / CONSTANTS.HackingMissionRepToDiffConversion + 1; + this.reward = 250 + rep / CONSTANTS.HackingMissionRepToRewardConversion; } -HackingMission.prototype.init = function() { - // Create Header DOM - this.createPageDom(); +HackingMission.prototype.init = function () { + // Create Header DOM + this.createPageDom(); - // Create player starting nodes - var home = Player.getHomeComputer() - for (var i = 0; i < home.cpuCores; ++i) { - var stats = { - atk: (Player.hacking_skill / 7.5) + 30, - def: (Player.hacking_skill / 20), - hp: (Player.hacking_skill / 4), - }; - this.playerCores.push(new Node(NodeTypes.Core, stats)); - this.playerCores[i].setControlledByPlayer(); - this.setNodePosition(this.playerCores[i], i, 0); - this.removeAvailablePosition(i, 0); + // Create player starting nodes + var home = Player.getHomeComputer(); + for (var i = 0; i < home.cpuCores; ++i) { + var stats = { + atk: Player.hacking_skill / 7.5 + 30, + def: Player.hacking_skill / 20, + hp: Player.hacking_skill / 4, + }; + this.playerCores.push(new Node(NodeTypes.Core, stats)); + this.playerCores[i].setControlledByPlayer(); + this.setNodePosition(this.playerCores[i], i, 0); + this.removeAvailablePosition(i, 0); + } + + // Randomly generate enemy nodes (CPU and Firewall) based on difficulty + var numNodes = Math.min(8, Math.max(1, Math.round(this.difficulty / 4))); + var numFirewalls = Math.min( + 20, + getRandomInt( + Math.round(this.difficulty / 3), + Math.round(this.difficulty / 3) + 1, + ), + ); + var numDatabases = Math.min( + 10, + getRandomInt(1, Math.round(this.difficulty / 3) + 1), + ); + var totalNodes = numNodes + numFirewalls + numDatabases; + var xlimit = 7 - Math.floor(totalNodes / 8); + var randMult = addOffset(0.8 + this.difficulty / 5, 10); + for (var i = 0; i < numNodes; ++i) { + var stats = { + atk: randMult * getRandomInt(80, 86), + def: randMult * getRandomInt(5, 10), + hp: randMult * getRandomInt(210, 230), + }; + this.enemyCores.push(new Node(NodeTypes.Core, stats)); + this.enemyCores[i].setControlledByEnemy(); + this.setNodeRandomPosition(this.enemyCores[i], xlimit); + } + for (var i = 0; i < numFirewalls; ++i) { + var stats = { + atk: 0, + def: randMult * getRandomInt(10, 20), + hp: randMult * getRandomInt(275, 300), + }; + this.enemyNodes.push(new Node(NodeTypes.Firewall, stats)); + this.enemyNodes[i].setControlledByEnemy(); + this.setNodeRandomPosition(this.enemyNodes[i], xlimit); + } + for (var i = 0; i < numDatabases; ++i) { + var stats = { + atk: 0, + def: randMult * getRandomInt(30, 55), + hp: randMult * getRandomInt(210, 275), + }; + var node = new Node(NodeTypes.Database, stats); + node.setControlledByEnemy(); + this.setNodeRandomPosition(node, xlimit); + this.enemyDatabases.push(node); + } + this.calculateDefenses(); + this.calculateAttacks(); + this.createMap(); +}; + +HackingMission.prototype.createPageDom = function () { + var container = document.getElementById("mission-container"); + + var favorMult = 1 + this.faction.favor / 100; + var gain = this.reward * Player.faction_rep_mult * favorMult; + var headerText = document.createElement("p"); + ReactDOM.render( + <> + You are about to start a hacking mission! You will gain {Reputation(gain)}{" "} + faction reputation with {this.faction.name} if you win. Click the 'Start' + button to begin. + , + headerText, + ); + headerText.style.display = "block"; + headerText.classList.add("hack-mission-header-element"); + headerText.style.width = "80%"; + + var inGameGuideBtn = document.createElement("a"); + inGameGuideBtn.innerText = "How to Play"; + inGameGuideBtn.classList.add("a-link-button"); + inGameGuideBtn.style.display = "inline-block"; + inGameGuideBtn.classList.add("hack-mission-header-element"); + inGameGuideBtn.addEventListener("click", function () { + dialogBoxCreate(CONSTANTS.HackingMissionHowToPlay); + return false; + }); + + // Start button will get replaced with forfeit when game is started + var startBtn = document.createElement("a"); + startBtn.innerHTML = "Start"; + startBtn.setAttribute("id", "hack-mission-start-btn"); + startBtn.classList.add("a-link-button"); + startBtn.classList.add("hack-mission-header-element"); + startBtn.style.display = "inline-block"; + startBtn.addEventListener("click", () => { + this.start(); + return false; + }); + + var forfeitMission = document.createElement("a"); + forfeitMission.innerHTML = "Forfeit Mission (Exit)"; + forfeitMission.classList.add("a-link-button"); + forfeitMission.classList.add("hack-mission-header-element"); + forfeitMission.style.display = "inline-block"; + forfeitMission.addEventListener("click", () => { + this.finishMission(false); + return false; + }); + + var timer = document.createElement("p"); + timer.setAttribute("id", "hacking-mission-timer"); + timer.style.display = "inline-block"; + timer.style.margin = "6px"; + + // Create Action Buttons (Attack/Scan/Weaken/ etc...) + var actionsContainer = document.createElement("span"); + actionsContainer.style.display = "block"; + actionsContainer.classList.add("hack-mission-action-buttons-container"); + for (var i = 0; i < 6; ++i) { + this.actionButtons.push(document.createElement("a")); + this.actionButtons[i].style.display = "inline-block"; + this.actionButtons[i].classList.add("a-link-button-inactive"); // Disabled at start + this.actionButtons[i].classList.add("tooltip"); // Disabled at start + this.actionButtons[i].classList.add("hack-mission-header-element"); + actionsContainer.appendChild(this.actionButtons[i]); + } + this.actionButtons[0].innerText = "Attack(a)"; + var atkTooltip = document.createElement("span"); + atkTooltip.classList.add("tooltiptexthigh"); + atkTooltip.innerText = + "Lowers the targeted node's HP. The effectiveness of this depends on " + + "this node's Attack level, your hacking level, and the opponent's defense level."; + this.actionButtons[0].appendChild(atkTooltip); + this.actionButtons[1].innerText = "Scan(s)"; + var scanTooltip = document.createElement("span"); + scanTooltip.classList.add("tooltiptexthigh"); + scanTooltip.innerText = + "Lowers the targeted node's defense. The effectiveness of this depends on " + + "this node's Attack level, your hacking level, and the opponent's defense level."; + this.actionButtons[1].appendChild(scanTooltip); + this.actionButtons[2].innerText = "Weaken(w)"; + var WeakenTooltip = document.createElement("span"); + WeakenTooltip.classList.add("tooltiptexthigh"); + WeakenTooltip.innerText = + "Lowers the targeted node's attack. The effectiveness of this depends on " + + "this node's Attack level, your hacking level, and the opponent's defense level."; + this.actionButtons[2].appendChild(WeakenTooltip); + this.actionButtons[3].innerText = "Fortify(f)"; + var fortifyTooltip = document.createElement("span"); + fortifyTooltip.classList.add("tooltiptexthigh"); + fortifyTooltip.innerText = + "Raises this node's Defense level. The effectiveness of this depends on " + + "your hacking level"; + this.actionButtons[3].appendChild(fortifyTooltip); + this.actionButtons[4].innerText = "Overflow(r)"; + var overflowTooltip = document.createElement("span"); + overflowTooltip.classList.add("tooltiptexthigh"); + overflowTooltip.innerText = + "Raises this node's Attack level but lowers its Defense level. The effectiveness " + + "of this depends on your hacking level."; + this.actionButtons[4].appendChild(overflowTooltip); + this.actionButtons[5].innerText = "Drop Connection(d)"; + var dropconnTooltip = document.createElement("span"); + dropconnTooltip.classList.add("tooltiptexthigh"); + dropconnTooltip.innerText = + "Removes this Node's current connection to some target Node, if it has one. This can " + + "also be done by simply clicking the white connection line."; + this.actionButtons[5].appendChild(dropconnTooltip); + + // Player/enemy defense displays will be in action container + var playerStats = document.createElement("p"); + var enemyStats = document.createElement("p"); + playerStats.style.display = "inline-block"; + enemyStats.style.display = "inline-block"; + playerStats.style.color = "#00ccff"; + enemyStats.style.color = "red"; + playerStats.style.margin = "4px"; + enemyStats.style.margin = "4px"; + playerStats.setAttribute("id", "hacking-mission-player-stats"); + enemyStats.setAttribute("id", "hacking-mission-enemy-stats"); + actionsContainer.appendChild(playerStats); + actionsContainer.appendChild(enemyStats); + + // Set Action Button event listeners + this.actionButtons[0].addEventListener("click", () => { + if (!(this.selectedNode.length > 0)) { + console.error("Pressing Action button without selected node"); + return; } - - // Randomly generate enemy nodes (CPU and Firewall) based on difficulty - var numNodes = Math.min(8, Math.max(1, Math.round(this.difficulty / 4))); - var numFirewalls = Math.min(20, - getRandomInt(Math.round(this.difficulty/3), Math.round(this.difficulty/3) + 1)); - var numDatabases = Math.min(10, getRandomInt(1, Math.round(this.difficulty / 3) + 1)); - var totalNodes = numNodes + numFirewalls + numDatabases; - var xlimit = 7 - Math.floor(totalNodes / 8); - var randMult = addOffset(0.8 + (this.difficulty / 5), 10); - for (var i = 0; i < numNodes; ++i) { - var stats = { - atk: randMult * getRandomInt(80, 86), - def: randMult * getRandomInt(5, 10), - hp: randMult * getRandomInt(210, 230), - } - this.enemyCores.push(new Node(NodeTypes.Core, stats)); - this.enemyCores[i].setControlledByEnemy(); - this.setNodeRandomPosition(this.enemyCores[i], xlimit); + if (this.selectedNode[0].type !== NodeTypes.Core) { + return; } - for (var i = 0; i < numFirewalls; ++i) { - var stats = { - atk: 0, - def: randMult * getRandomInt(10, 20), - hp: randMult * getRandomInt(275, 300), - } - this.enemyNodes.push(new Node(NodeTypes.Firewall, stats)); - this.enemyNodes[i].setControlledByEnemy(); - this.setNodeRandomPosition(this.enemyNodes[i], xlimit); + this.setActionButtonsActive(this.selectedNode[0].type); + this.setActionButton(NodeActions.Attack, false); // Set attack button inactive + this.selectedNode.forEach(function (node) { + node.action = NodeActions.Attack; + }); + }); + + this.actionButtons[1].addEventListener("click", () => { + if (!(this.selectedNode.length > 0)) { + console.error("Pressing Action button without selected node"); + return; } - for (var i = 0; i < numDatabases; ++i) { - var stats = { - atk: 0, - def: randMult * getRandomInt(30, 55), - hp: randMult * getRandomInt(210, 275), - } - var node = new Node(NodeTypes.Database, stats); - node.setControlledByEnemy(); - this.setNodeRandomPosition(node, xlimit); - this.enemyDatabases.push(node); + var nodeType = this.selectedNode[0].type; // In a multiselect, every Node will have the same type + if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) { + return; } - this.calculateDefenses(); - this.calculateAttacks(); - this.createMap(); -} - -HackingMission.prototype.createPageDom = function() { - var container = document.getElementById("mission-container"); - - var favorMult = 1 + (this.faction.favor / 100); - var gain = this.reward * Player.faction_rep_mult * favorMult; - var headerText = document.createElement("p"); - ReactDOM.render(<>You are about to start a hacking mission! You will gain {Reputation(gain)} faction reputation with {this.faction.name} if you win. Click the 'Start' button to begin., headerText); - headerText.style.display = "block"; - headerText.classList.add("hack-mission-header-element"); - headerText.style.width = "80%"; - - var inGameGuideBtn = document.createElement("a"); - inGameGuideBtn.innerText = "How to Play"; - inGameGuideBtn.classList.add("a-link-button"); - inGameGuideBtn.style.display = "inline-block"; - inGameGuideBtn.classList.add("hack-mission-header-element"); - inGameGuideBtn.addEventListener("click", function() { - dialogBoxCreate(CONSTANTS.HackingMissionHowToPlay); - return false; + this.setActionButtonsActive(nodeType); + this.setActionButton(NodeActions.Scan, false); // Set scan button inactive + this.selectedNode.forEach(function (node) { + node.action = NodeActions.Scan; }); + }); - // Start button will get replaced with forfeit when game is started - var startBtn = document.createElement("a"); - startBtn.innerHTML = "Start"; - startBtn.setAttribute("id", "hack-mission-start-btn"); - startBtn.classList.add("a-link-button"); - startBtn.classList.add("hack-mission-header-element"); - startBtn.style.display = "inline-block"; - startBtn.addEventListener("click", () => { - this.start(); - return false; - }); - - var forfeitMission = document.createElement("a"); - forfeitMission.innerHTML = "Forfeit Mission (Exit)"; - forfeitMission.classList.add("a-link-button"); - forfeitMission.classList.add("hack-mission-header-element"); - forfeitMission.style.display = "inline-block"; - forfeitMission.addEventListener("click", () => { - this.finishMission(false); - return false; - }); - - var timer = document.createElement("p"); - timer.setAttribute("id", "hacking-mission-timer"); - timer.style.display = "inline-block"; - timer.style.margin = "6px"; - - // Create Action Buttons (Attack/Scan/Weaken/ etc...) - var actionsContainer = document.createElement("span"); - actionsContainer.style.display = "block"; - actionsContainer.classList.add("hack-mission-action-buttons-container"); - for (var i = 0; i < 6; ++i) { - this.actionButtons.push(document.createElement("a")); - this.actionButtons[i].style.display = "inline-block"; - this.actionButtons[i].classList.add("a-link-button-inactive"); // Disabled at start - this.actionButtons[i].classList.add("tooltip"); // Disabled at start - this.actionButtons[i].classList.add("hack-mission-header-element"); - actionsContainer.appendChild(this.actionButtons[i]); + this.actionButtons[2].addEventListener("click", () => { + if (!(this.selectedNode.length > 0)) { + console.error("Pressing Action button without selected node"); + return; } - this.actionButtons[0].innerText = "Attack(a)"; - var atkTooltip = document.createElement("span"); - atkTooltip.classList.add("tooltiptexthigh"); - atkTooltip.innerText = "Lowers the targeted node's HP. The effectiveness of this depends on " + - "this node's Attack level, your hacking level, and the opponent's defense level."; - this.actionButtons[0].appendChild(atkTooltip); - this.actionButtons[1].innerText = "Scan(s)"; - var scanTooltip = document.createElement("span"); - scanTooltip.classList.add("tooltiptexthigh"); - scanTooltip.innerText = "Lowers the targeted node's defense. The effectiveness of this depends on " + - "this node's Attack level, your hacking level, and the opponent's defense level."; - this.actionButtons[1].appendChild(scanTooltip); - this.actionButtons[2].innerText = "Weaken(w)"; - var WeakenTooltip = document.createElement("span"); - WeakenTooltip.classList.add("tooltiptexthigh"); - WeakenTooltip.innerText = "Lowers the targeted node's attack. The effectiveness of this depends on " + - "this node's Attack level, your hacking level, and the opponent's defense level."; - this.actionButtons[2].appendChild(WeakenTooltip); - this.actionButtons[3].innerText = "Fortify(f)"; - var fortifyTooltip = document.createElement("span"); - fortifyTooltip.classList.add("tooltiptexthigh"); - fortifyTooltip.innerText = "Raises this node's Defense level. The effectiveness of this depends on " + - "your hacking level"; - this.actionButtons[3].appendChild(fortifyTooltip); - this.actionButtons[4].innerText = "Overflow(r)"; - var overflowTooltip = document.createElement("span"); - overflowTooltip.classList.add("tooltiptexthigh"); - overflowTooltip.innerText = "Raises this node's Attack level but lowers its Defense level. The effectiveness " + - "of this depends on your hacking level."; - this.actionButtons[4].appendChild(overflowTooltip); - this.actionButtons[5].innerText = "Drop Connection(d)"; - var dropconnTooltip = document.createElement("span"); - dropconnTooltip.classList.add("tooltiptexthigh"); - dropconnTooltip.innerText = "Removes this Node's current connection to some target Node, if it has one. This can " + - "also be done by simply clicking the white connection line."; - this.actionButtons[5].appendChild(dropconnTooltip); - - // Player/enemy defense displays will be in action container - var playerStats = document.createElement("p"); - var enemyStats = document.createElement("p"); - playerStats.style.display = "inline-block"; - enemyStats.style.display = "inline-block"; - playerStats.style.color = "#00ccff"; - enemyStats.style.color = "red"; - playerStats.style.margin = "4px"; - enemyStats.style.margin = "4px"; - playerStats.setAttribute("id", "hacking-mission-player-stats"); - enemyStats.setAttribute("id", "hacking-mission-enemy-stats"); - actionsContainer.appendChild(playerStats); - actionsContainer.appendChild(enemyStats); - - // Set Action Button event listeners - this.actionButtons[0].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - if (this.selectedNode[0].type !== NodeTypes.Core) {return;} - this.setActionButtonsActive(this.selectedNode[0].type); - this.setActionButton(NodeActions.Attack, false); // Set attack button inactive - this.selectedNode.forEach(function(node){ - node.action = NodeActions.Attack; - }); - }); - - this.actionButtons[1].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - var nodeType = this.selectedNode[0].type; // In a multiselect, every Node will have the same type - if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) {return;} - this.setActionButtonsActive(nodeType); - this.setActionButton(NodeActions.Scan, false); // Set scan button inactive - this.selectedNode.forEach(function(node){ - node.action = NodeActions.Scan; - }); - }); - - this.actionButtons[2].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - var nodeType = this.selectedNode[0].type; // In a multiselect, every Node will have the same type - if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) {return;} - this.setActionButtonsActive(nodeType); - this.setActionButton(NodeActions.Weaken, false); // Set Weaken button inactive - this.selectedNode.forEach(function(node){ - node.action = NodeActions.Weaken; - }); - }); - - this.actionButtons[3].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - this.setActionButtonsActive(this.selectedNode[0].type); - this.setActionButton(NodeActions.Fortify, false); // Set Fortify button inactive - this.selectedNode.forEach(function(node){ - node.action = NodeActions.Fortify; - }); - }); - - this.actionButtons[4].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - var nodeType = this.selectedNode[0].type; - if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) {return;} - this.setActionButtonsActive(nodeType); - this.setActionButton(NodeActions.Overflow, false); // Set Overflow button inactive - this.selectedNode.forEach(function(node){ - node.action = NodeActions.Overflow; - }); - }); - - this.actionButtons[5].addEventListener("click", () => { - if (!(this.selectedNode.length > 0)) { - console.error("Pressing Action button without selected node"); - return; - } - this.selectedNode.forEach(function(node){ - if (node.conn) { - var endpoints = node.conn.endpoints; - endpoints[0].detachFrom(endpoints[1]); - } - node.action = NodeActions.Fortify; - }); - }); - - var timeDisplay = document.createElement("p"); - - container.appendChild(headerText); - container.appendChild(inGameGuideBtn); - container.appendChild(startBtn); - container.appendChild(forfeitMission); - container.appendChild(timer); - container.appendChild(actionsContainer); - container.appendChild(timeDisplay); -} - -HackingMission.prototype.setActionButtonsInactive = function() { - for (var i = 0; i < this.actionButtons.length; ++i) { - this.actionButtons[i].classList.remove("a-link-button"); - this.actionButtons[i].classList.add("a-link-button-inactive"); + var nodeType = this.selectedNode[0].type; // In a multiselect, every Node will have the same type + if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) { + return; } -} + this.setActionButtonsActive(nodeType); + this.setActionButton(NodeActions.Weaken, false); // Set Weaken button inactive + this.selectedNode.forEach(function (node) { + node.action = NodeActions.Weaken; + }); + }); -HackingMission.prototype.setActionButtonsActive = function(nodeType=null) { - for (var i = 0; i < this.actionButtons.length; ++i) { - this.actionButtons[i].classList.add("a-link-button"); - this.actionButtons[i].classList.remove("a-link-button-inactive"); + this.actionButtons[3].addEventListener("click", () => { + if (!(this.selectedNode.length > 0)) { + console.error("Pressing Action button without selected node"); + return; } + this.setActionButtonsActive(this.selectedNode[0].type); + this.setActionButton(NodeActions.Fortify, false); // Set Fortify button inactive + this.selectedNode.forEach(function (node) { + node.action = NodeActions.Fortify; + }); + }); - /** - * For Transfer, FireWall and Shield Nodes, certain buttons should always be disabled - * 0 = Attack, 1 = Scan, 2 = Weaken, 3 = Fortify, 4 = overflow, 5 = Drop conn - */ - if (nodeType) { - switch (nodeType) { - case NodeTypes.Firewall: - case NodeTypes.Shield: - this.actionButtons[0].classList.remove("a-link-button"); - this.actionButtons[0].classList.add("a-link-button-inactive"); - this.actionButtons[1].classList.remove("a-link-button"); - this.actionButtons[1].classList.add("a-link-button-inactive"); - this.actionButtons[2].classList.remove("a-link-button"); - this.actionButtons[2].classList.add("a-link-button-inactive"); - this.actionButtons[4].classList.remove("a-link-button"); - this.actionButtons[4].classList.add("a-link-button-inactive"); - this.actionButtons[5].classList.remove("a-link-button"); - this.actionButtons[5].classList.add("a-link-button-inactive"); - break; - case NodeTypes.Transfer: - this.actionButtons[0].classList.remove("a-link-button"); - this.actionButtons[0].classList.add("a-link-button-inactive"); - break; - default: - break; - } + this.actionButtons[4].addEventListener("click", () => { + if (!(this.selectedNode.length > 0)) { + console.error("Pressing Action button without selected node"); + return; } -} + var nodeType = this.selectedNode[0].type; + if (nodeType !== NodeTypes.Core && nodeType !== NodeTypes.Transfer) { + return; + } + this.setActionButtonsActive(nodeType); + this.setActionButton(NodeActions.Overflow, false); // Set Overflow button inactive + this.selectedNode.forEach(function (node) { + node.action = NodeActions.Overflow; + }); + }); + + this.actionButtons[5].addEventListener("click", () => { + if (!(this.selectedNode.length > 0)) { + console.error("Pressing Action button without selected node"); + return; + } + this.selectedNode.forEach(function (node) { + if (node.conn) { + var endpoints = node.conn.endpoints; + endpoints[0].detachFrom(endpoints[1]); + } + node.action = NodeActions.Fortify; + }); + }); + + var timeDisplay = document.createElement("p"); + + container.appendChild(headerText); + container.appendChild(inGameGuideBtn); + container.appendChild(startBtn); + container.appendChild(forfeitMission); + container.appendChild(timer); + container.appendChild(actionsContainer); + container.appendChild(timeDisplay); +}; + +HackingMission.prototype.setActionButtonsInactive = function () { + for (var i = 0; i < this.actionButtons.length; ++i) { + this.actionButtons[i].classList.remove("a-link-button"); + this.actionButtons[i].classList.add("a-link-button-inactive"); + } +}; + +HackingMission.prototype.setActionButtonsActive = function (nodeType = null) { + for (var i = 0; i < this.actionButtons.length; ++i) { + this.actionButtons[i].classList.add("a-link-button"); + this.actionButtons[i].classList.remove("a-link-button-inactive"); + } + + /** + * For Transfer, FireWall and Shield Nodes, certain buttons should always be disabled + * 0 = Attack, 1 = Scan, 2 = Weaken, 3 = Fortify, 4 = overflow, 5 = Drop conn + */ + if (nodeType) { + switch (nodeType) { + case NodeTypes.Firewall: + case NodeTypes.Shield: + this.actionButtons[0].classList.remove("a-link-button"); + this.actionButtons[0].classList.add("a-link-button-inactive"); + this.actionButtons[1].classList.remove("a-link-button"); + this.actionButtons[1].classList.add("a-link-button-inactive"); + this.actionButtons[2].classList.remove("a-link-button"); + this.actionButtons[2].classList.add("a-link-button-inactive"); + this.actionButtons[4].classList.remove("a-link-button"); + this.actionButtons[4].classList.add("a-link-button-inactive"); + this.actionButtons[5].classList.remove("a-link-button"); + this.actionButtons[5].classList.add("a-link-button-inactive"); + break; + case NodeTypes.Transfer: + this.actionButtons[0].classList.remove("a-link-button"); + this.actionButtons[0].classList.add("a-link-button-inactive"); + break; + default: + break; + } + } +}; // True for active, false for inactive -HackingMission.prototype.setActionButton = function(i, active=true) { - if (isString(i)) { - switch (i) { - case NodeActions.Attack: - i = 0; - break; - case NodeActions.Scan: - i = 1; - break; - case NodeActions.Weaken: - i = 2; - break; - case NodeActions.Fortify: - i = 3; - break; - case NodeActions.Overflow: - default: - i = 4; - break; +HackingMission.prototype.setActionButton = function (i, active = true) { + if (isString(i)) { + switch (i) { + case NodeActions.Attack: + i = 0; + break; + case NodeActions.Scan: + i = 1; + break; + case NodeActions.Weaken: + i = 2; + break; + case NodeActions.Fortify: + i = 3; + break; + case NodeActions.Overflow: + default: + i = 4; + break; + } + } + if (active) { + this.actionButtons[i].classList.remove("a-link-button-inactive"); + this.actionButtons[i].classList.add("a-link-button"); + } else { + this.actionButtons[i].classList.remove("a-link-button"); + this.actionButtons[i].classList.add("a-link-button-inactive"); + } +}; + +HackingMission.prototype.calculateAttacks = function () { + var total = 0; + for (var i = 0; i < this.playerCores.length; ++i) { + total += this.playerCores[i].atk; + } + for (var i = 0; i < this.playerNodes.length; ++i) { + total += this.playerNodes[i].atk; + } + this.playerAtk = total; + document.getElementById("hacking-mission-player-stats").innerHTML = + "Player Attack: " + + formatNumber(this.playerAtk, 1) + + "
    " + + "Player Defense: " + + formatNumber(this.playerDef, 1); + total = 0; + for (var i = 0; i < this.enemyCores.length; ++i) { + total += this.enemyCores[i].atk; + } + for (var i = 0; i < this.enemyDatabases.length; ++i) { + total += this.enemyDatabases[i].atk; + } + for (var i = 0; i < this.enemyNodes.length; ++i) { + total += this.enemyNodes[i].atk; + } + this.enemyAtk = total; + document.getElementById("hacking-mission-enemy-stats").innerHTML = + "Enemy Attack: " + + formatNumber(this.enemyAtk, 1) + + "
    " + + "Enemy Defense: " + + formatNumber(this.enemyDef, 1); +}; + +HackingMission.prototype.calculateDefenses = function () { + var total = 0; + for (var i = 0; i < this.playerCores.length; ++i) { + total += this.playerCores[i].def; + } + for (var i = 0; i < this.playerNodes.length; ++i) { + total += this.playerNodes[i].def; + } + this.playerDef = total; + document.getElementById("hacking-mission-player-stats").innerHTML = + "Player Attack: " + + formatNumber(this.playerAtk, 1) + + "
    " + + "Player Defense: " + + formatNumber(this.playerDef, 1); + total = 0; + for (var i = 0; i < this.enemyCores.length; ++i) { + total += this.enemyCores[i].def; + } + for (var i = 0; i < this.enemyDatabases.length; ++i) { + total += this.enemyDatabases[i].def; + } + for (var i = 0; i < this.enemyNodes.length; ++i) { + total += this.enemyNodes[i].def; + } + this.enemyDef = total; + document.getElementById("hacking-mission-enemy-stats").innerHTML = + "Enemy Attack: " + + formatNumber(this.enemyAtk, 1) + + "
    " + + "Enemy Defense: " + + formatNumber(this.enemyDef, 1); +}; + +HackingMission.prototype.removeAvailablePosition = function (x, y) { + for (var i = 0; i < this.availablePositions.length; ++i) { + if ( + this.availablePositions[i][0] === x && + this.availablePositions[i][1] === y + ) { + this.availablePositions.splice(i, 1); + return; + } + } + console.warn(`removeAvailablePosition() did not remove ${x}, ${y}`); +}; + +HackingMission.prototype.setNodePosition = function (nodeObj, x, y) { + if (!(nodeObj instanceof Node)) { + console.warn("Non-Node object passed into setNodePOsition"); + return; + } + if (isNaN(x) || isNaN(y)) { + console.error( + `Invalid values (${x}, ${y}) passed as (x, y) for setNodePosition`, + ); + return; + } + nodeObj.pos = [x, y]; + this.map[x][y] = nodeObj; +}; + +HackingMission.prototype.setNodeRandomPosition = function ( + nodeObj, + xlimit = 0, +) { + var i = getRandomInt(0, this.availablePositions.length - 1); + if (this.availablePositions[i][1] < xlimit) { + // Recurse if not within limit + return this.setNodeRandomPosition(nodeObj, xlimit); + } + var pos = this.availablePositions.splice(i, 1); + pos = pos[0]; + this.setNodePosition(nodeObj, pos[0], pos[1]); +}; + +HackingMission.prototype.createMap = function () { + // Use a grid + var map = document.createElement("div"); + map.classList.add("hack-mission-grid"); + map.setAttribute("id", "hacking-mission-map"); + document.getElementById("mission-container").appendChild(map); + + // Create random Nodes for every space in the map that + // hasn't been filled yet. The stats of each Node will be based on + // the player/enemy attack + var averageAttack = (this.playerAtk + this.enemyAtk) / 2; + for (var x = 0; x < 8; ++x) { + for (var y = 0; y < 8; ++y) { + if (!(this.map[x][y] instanceof Node)) { + var node, + type = getRandomInt(0, 2); + var randMult = addOffset(0.85 + this.difficulty / 2, 15); + switch (type) { + case 0: // Spam + var stats = { + atk: 0, + def: averageAttack * 1.1 + getRandomInt(15, 45), + hp: randMult * getRandomInt(200, 225), + }; + node = new Node(NodeTypes.Spam, stats); + break; + case 1: // Transfer + var stats = { + atk: 0, + def: averageAttack * 1.1 + getRandomInt(15, 45), + hp: randMult * getRandomInt(250, 275), + }; + node = new Node(NodeTypes.Transfer, stats); + break; + case 2: // Shield + default: + var stats = { + atk: 0, + def: averageAttack * 1.1 + getRandomInt(30, 70), + hp: randMult * getRandomInt(300, 320), + }; + node = new Node(NodeTypes.Shield, stats); + break; } + this.setNodePosition(node, x, y); + this.removeAvailablePosition(x, y); + this.miscNodes.push(node); + } } - if (active) { - this.actionButtons[i].classList.remove("a-link-button-inactive"); - this.actionButtons[i].classList.add("a-link-button"); - } else { - this.actionButtons[i].classList.remove("a-link-button"); - this.actionButtons[i].classList.add("a-link-button-inactive"); - } + } -} + // Create DOM elements in order + for (var r = 0; r < 8; ++r) { + for (var c = 0; c < 8; ++c) { + this.createNodeDomElement(this.map[r][c]); + } + } -HackingMission.prototype.calculateAttacks = function() { - var total = 0; - for (var i = 0; i < this.playerCores.length; ++i) { - total += this.playerCores[i].atk; - } - for (var i = 0; i < this.playerNodes.length; ++i) { - total += this.playerNodes[i].atk; - } - this.playerAtk = total; - document.getElementById("hacking-mission-player-stats").innerHTML = - "Player Attack: " + formatNumber(this.playerAtk, 1) + "
    " + - "Player Defense: " + formatNumber(this.playerDef, 1); - total = 0; - for (var i = 0; i < this.enemyCores.length; ++i) { - total += this.enemyCores[i].atk; - } - for (var i = 0; i < this.enemyDatabases.length; ++i) { - total += this.enemyDatabases[i].atk; - } - for (var i = 0; i < this.enemyNodes.length; ++i) { - total += this.enemyNodes[i].atk; - } - this.enemyAtk = total; - document.getElementById("hacking-mission-enemy-stats").innerHTML = - "Enemy Attack: " + formatNumber(this.enemyAtk, 1) + "
    " + - "Enemy Defense: " + formatNumber(this.enemyDef, 1); -} + // Configure all Player CPUS + for (var i = 0; i < this.playerCores.length; ++i) { + this.configurePlayerNodeElement(this.playerCores[i].el); + } +}; -HackingMission.prototype.calculateDefenses = function() { - var total = 0; - for (var i = 0; i < this.playerCores.length; ++i) { - total += this.playerCores[i].def; - } - for (var i = 0; i < this.playerNodes.length; ++i) { - total += this.playerNodes[i].def; - } - this.playerDef = total; - document.getElementById("hacking-mission-player-stats").innerHTML = - "Player Attack: " + formatNumber(this.playerAtk, 1) + "
    " + - "Player Defense: " + formatNumber(this.playerDef, 1); - total = 0; - for (var i = 0; i < this.enemyCores.length; ++i) { - total += this.enemyCores[i].def; - } - for (var i = 0; i < this.enemyDatabases.length; ++i) { - total += this.enemyDatabases[i].def; - } - for (var i = 0; i < this.enemyNodes.length; ++i) { - total += this.enemyNodes[i].def; - } - this.enemyDef = total; - document.getElementById("hacking-mission-enemy-stats").innerHTML = - "Enemy Attack: " + formatNumber(this.enemyAtk, 1) + "
    " + - "Enemy Defense: " + formatNumber(this.enemyDef, 1); -} +HackingMission.prototype.createNodeDomElement = function (nodeObj) { + var nodeDiv = document.createElement("a"), + txtEl = document.createElement("p"); + nodeObj.el = nodeDiv; -HackingMission.prototype.removeAvailablePosition = function(x, y) { - for (var i = 0; i < this.availablePositions.length; ++i) { - if (this.availablePositions[i][0] === x && - this.availablePositions[i][1] === y) { - this.availablePositions.splice(i, 1); - return; - } - } - console.warn(`removeAvailablePosition() did not remove ${x}, ${y}`); -} + // Set the node element's id based on its coordinates + var id = "hacking-mission-node-" + nodeObj.pos[0] + "-" + nodeObj.pos[1]; + nodeDiv.setAttribute("id", id); + txtEl.setAttribute("id", id + "-txt"); -HackingMission.prototype.setNodePosition = function(nodeObj, x, y) { - if (!(nodeObj instanceof Node)) { - console.warn("Non-Node object passed into setNodePOsition"); - return; - } - if (isNaN(x) || isNaN(y)) { - console.error(`Invalid values (${x}, ${y}) passed as (x, y) for setNodePosition`); - return; - } - nodeObj.pos = [x, y]; - this.map[x][y] = nodeObj; -} + // Set node classes for owner + nodeDiv.classList.add("hack-mission-node"); + if (nodeObj.plyrCtrl) { + nodeDiv.classList.add("hack-mission-player-node"); + } else if (nodeObj.enmyCtrl) { + nodeDiv.classList.add("hack-mission-enemy-node"); + } -HackingMission.prototype.setNodeRandomPosition = function(nodeObj, xlimit=0) { - var i = getRandomInt(0, this.availablePositions.length - 1); - if (this.availablePositions[i][1] < xlimit) { - // Recurse if not within limit - return this.setNodeRandomPosition(nodeObj, xlimit); - } - var pos = this.availablePositions.splice(i, 1); - pos = pos[0]; - this.setNodePosition(nodeObj, pos[0], pos[1]); -} + // Set node classes based on type + var txt; + switch (nodeObj.type) { + case NodeTypes.Core: + txt = "CPU Core
    " + "HP: " + formatNumber(nodeObj.hp, 1); + nodeDiv.classList.add("hack-mission-cpu-node"); + break; + case NodeTypes.Firewall: + txt = "Firewall
    " + "HP: " + formatNumber(nodeObj.hp, 1); + nodeDiv.classList.add("hack-mission-firewall-node"); + break; + case NodeTypes.Database: + txt = "Database
    " + "HP: " + formatNumber(nodeObj.hp, 1); + nodeDiv.classList.add("hack-mission-database-node"); + break; + case NodeTypes.Spam: + txt = "Spam
    " + "HP: " + formatNumber(nodeObj.hp, 1); + nodeDiv.classList.add("hack-mission-spam-node"); + break; + case NodeTypes.Transfer: + txt = "Transfer
    " + "HP: " + formatNumber(nodeObj.hp, 1); + nodeDiv.classList.add("hack-mission-transfer-node"); + break; + case NodeTypes.Shield: + default: + txt = "Shield
    " + "HP: " + formatNumber(nodeObj.hp, 1); + nodeDiv.classList.add("hack-mission-shield-node"); + break; + } -HackingMission.prototype.createMap = function() { - // Use a grid - var map = document.createElement("div"); - map.classList.add("hack-mission-grid"); - map.setAttribute("id", "hacking-mission-map"); - document.getElementById("mission-container").appendChild(map); + txt += + "
    Atk: " + + formatNumber(nodeObj.atk, 1) + + "
    Def: " + + formatNumber(nodeObj.def, 1); + txtEl.innerHTML = txt; - // Create random Nodes for every space in the map that - // hasn't been filled yet. The stats of each Node will be based on - // the player/enemy attack - var averageAttack = (this.playerAtk + this.enemyAtk) / 2; - for (var x = 0; x < 8; ++x) { - for (var y = 0; y < 8; ++y) { - if (!(this.map[x][y] instanceof Node)) { - var node, type = getRandomInt(0, 2); - var randMult = addOffset(0.85 + (this.difficulty / 2), 15); - switch (type) { - case 0: // Spam - var stats = { - atk: 0, - def: averageAttack * 1.1 + getRandomInt(15, 45), - hp: randMult * getRandomInt(200, 225), - } - node = new Node(NodeTypes.Spam, stats); - break; - case 1: // Transfer - var stats = { - atk: 0, - def: averageAttack * 1.1 + getRandomInt(15, 45), - hp: randMult * getRandomInt(250, 275), - } - node = new Node(NodeTypes.Transfer, stats); - break; - case 2: // Shield - default: - var stats = { - atk: 0, - def: averageAttack * 1.1 + getRandomInt(30, 70), - hp: randMult * getRandomInt(300, 320), - } - node = new Node(NodeTypes.Shield, stats); - break; - } - this.setNodePosition(node, x, y); - this.removeAvailablePosition(x, y); - this.miscNodes.push(node); - } - } - } + nodeDiv.appendChild(txtEl); + document.getElementById("hacking-mission-map").appendChild(nodeDiv); +}; - // Create DOM elements in order - for (var r = 0; r < 8; ++r) { - for (var c = 0; c < 8; ++c) { - this.createNodeDomElement(this.map[r][c]); - } - } +HackingMission.prototype.updateNodeDomElement = function (nodeObj) { + if (nodeObj.el == null) { + console.error("Calling updateNodeDomElement on a Node without an element"); + return; + } - // Configure all Player CPUS - for (var i = 0; i < this.playerCores.length; ++i) { - this.configurePlayerNodeElement(this.playerCores[i].el); - } -} + let id = "hacking-mission-node-" + nodeObj.pos[0] + "-" + nodeObj.pos[1]; + let txtEl = document.getElementById(id + "-txt"); -HackingMission.prototype.createNodeDomElement = function(nodeObj) { - var nodeDiv = document.createElement("a"), txtEl = document.createElement('p'); - nodeObj.el = nodeDiv; + // Set node classes based on type + let txt; + switch (nodeObj.type) { + case NodeTypes.Core: + txt = "CPU Core
    " + "HP: " + formatNumber(nodeObj.hp, 1); + break; + case NodeTypes.Firewall: + txt = "Firewall
    " + "HP: " + formatNumber(nodeObj.hp, 1); + break; + case NodeTypes.Database: + txt = "Database
    " + "HP: " + formatNumber(nodeObj.hp, 1); + break; + case NodeTypes.Spam: + txt = "Spam
    " + "HP: " + formatNumber(nodeObj.hp, 1); + break; + case NodeTypes.Transfer: + txt = "Transfer
    " + "HP: " + formatNumber(nodeObj.hp, 1); + break; + case NodeTypes.Shield: + default: + txt = "Shield
    " + "HP: " + formatNumber(nodeObj.hp, 1); + break; + } - // Set the node element's id based on its coordinates - var id = "hacking-mission-node-" + nodeObj.pos[0] + "-" + nodeObj.pos[1]; - nodeDiv.setAttribute("id", id); - txtEl.setAttribute("id", id + "-txt"); - - // Set node classes for owner - nodeDiv.classList.add("hack-mission-node"); - if (nodeObj.plyrCtrl) { - nodeDiv.classList.add("hack-mission-player-node"); - } else if (nodeObj.enmyCtrl) { - nodeDiv.classList.add("hack-mission-enemy-node"); - } - - // Set node classes based on type - var txt; - switch (nodeObj.type) { - case NodeTypes.Core: - txt = "CPU Core
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-cpu-node"); - break; - case NodeTypes.Firewall: - txt = "Firewall
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-firewall-node"); - break; - case NodeTypes.Database: - txt = "Database
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-database-node"); - break; - case NodeTypes.Spam: - txt = "Spam
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-spam-node"); - break; - case NodeTypes.Transfer: - txt = "Transfer
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-transfer-node"); - break; - case NodeTypes.Shield: - default: - txt = "Shield
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - nodeDiv.classList.add("hack-mission-shield-node"); - break; - } - - txt += "
    Atk: " + formatNumber(nodeObj.atk, 1) + - "
    Def: " + formatNumber(nodeObj.def, 1); - txtEl.innerHTML = txt; - - nodeDiv.appendChild(txtEl); - document.getElementById("hacking-mission-map").appendChild(nodeDiv); -} - -HackingMission.prototype.updateNodeDomElement = function(nodeObj) { - if (nodeObj.el == null) { - console.error("Calling updateNodeDomElement on a Node without an element"); - return; - } - - let id = "hacking-mission-node-" + nodeObj.pos[0] + "-" + nodeObj.pos[1]; - let txtEl = document.getElementById(id + "-txt"); - - // Set node classes based on type - let txt; - switch (nodeObj.type) { - case NodeTypes.Core: - txt = "CPU Core
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - break; - case NodeTypes.Firewall: - txt = "Firewall
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - break; - case NodeTypes.Database: - txt = "Database
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - break; - case NodeTypes.Spam: - txt = "Spam
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - break; - case NodeTypes.Transfer: - txt = "Transfer
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - break; - case NodeTypes.Shield: - default: - txt = "Shield
    " + "HP: " + - formatNumber(nodeObj.hp, 1); - break; - } - - txt += "
    Atk: " + formatNumber(nodeObj.atk, 1) + - "
    Def: " + formatNumber(nodeObj.def, 1); - if (nodeObj.action) { - txt += "
    " + nodeObj.action; - } - txtEl.innerHTML = txt; -} + txt += + "
    Atk: " + + formatNumber(nodeObj.atk, 1) + + "
    Def: " + + formatNumber(nodeObj.def, 1); + if (nodeObj.action) { + txt += "
    " + nodeObj.action; + } + txtEl.innerHTML = txt; +}; /** * Gets a Node DOM elements corresponding Node object using its * element id. Function accepts either the DOM element object or the ID as * an argument */ -HackingMission.prototype.getNodeFromElement = function(el) { - var id; - if (isString(el)) { - id = el; - } else { - id = el.id; - } - id = id.replace("hacking-mission-node-", ""); - var res = id.split('-'); - if (res.length != 2) { - console.error("Parsing hacking mission node id. could not find coordinates"); - return null; - } - var x = res[0], y = res[1]; - if (isNaN(x) || isNaN(y) || x >= 8 || y >= 8 || x < 0 || y < 0) { - console.error(`Unexpected values (${x}, ${y}) for (x, y)`); - return null; - } - return this.map[x][y]; -} +HackingMission.prototype.getNodeFromElement = function (el) { + var id; + if (isString(el)) { + id = el; + } else { + id = el.id; + } + id = id.replace("hacking-mission-node-", ""); + var res = id.split("-"); + if (res.length != 2) { + console.error( + "Parsing hacking mission node id. could not find coordinates", + ); + return null; + } + var x = res[0], + y = res[1]; + if (isNaN(x) || isNaN(y) || x >= 8 || y >= 8 || x < 0 || y < 0) { + console.error(`Unexpected values (${x}, ${y}) for (x, y)`); + return null; + } + return this.map[x][y]; +}; function selectNode(hackMissionInst, el) { - var nodeObj = hackMissionInst.getNodeFromElement(el); - if (nodeObj == null) {console.error("Failed getting Node object");} - if (!nodeObj.plyrCtrl) {return;} + var nodeObj = hackMissionInst.getNodeFromElement(el); + if (nodeObj == null) { + console.error("Failed getting Node object"); + } + if (!nodeObj.plyrCtrl) { + return; + } - clearAllSelectedNodes(hackMissionInst); - nodeObj.select(hackMissionInst.actionButtons); - hackMissionInst.selectedNode.push(nodeObj); + clearAllSelectedNodes(hackMissionInst); + nodeObj.select(hackMissionInst.actionButtons); + hackMissionInst.selectedNode.push(nodeObj); } function multiselectNode(hackMissionInst, el) { - var nodeObj = hackMissionInst.getNodeFromElement(el); - if (nodeObj == null) {console.error("Failed getting Node Object in multiselectNode()");} - if (!nodeObj.plyrCtrl) {return;} + var nodeObj = hackMissionInst.getNodeFromElement(el); + if (nodeObj == null) { + console.error("Failed getting Node Object in multiselectNode()"); + } + if (!nodeObj.plyrCtrl) { + return; + } - clearAllSelectedNodes(hackMissionInst); - var type = nodeObj.type; - if (type === NodeTypes.Core) { - hackMissionInst.playerCores.forEach(function(node) { - node.select(hackMissionInst.actionButtons); - hackMissionInst.selectedNode.push(node); - }); - } else { - hackMissionInst.playerNodes.forEach(function(node) { - if (node.type === type) { - node.select(hackMissionInst.actionButtons); - hackMissionInst.selectedNode.push(node); - } - }); - } + clearAllSelectedNodes(hackMissionInst); + var type = nodeObj.type; + if (type === NodeTypes.Core) { + hackMissionInst.playerCores.forEach(function (node) { + node.select(hackMissionInst.actionButtons); + hackMissionInst.selectedNode.push(node); + }); + } else { + hackMissionInst.playerNodes.forEach(function (node) { + if (node.type === type) { + node.select(hackMissionInst.actionButtons); + hackMissionInst.selectedNode.push(node); + } + }); + } } function clearAllSelectedNodes(hackMissionInst) { - if (hackMissionInst.selectedNode.length > 0) { - hackMissionInst.selectedNode.forEach(function(node){ - node.deselect(hackMissionInst.actionButtons); - }); - hackMissionInst.selectedNode.length = 0; - } + if (hackMissionInst.selectedNode.length > 0) { + hackMissionInst.selectedNode.forEach(function (node) { + node.deselect(hackMissionInst.actionButtons); + }); + hackMissionInst.selectedNode.length = 0; + } } /** @@ -898,643 +954,771 @@ function clearAllSelectedNodes(hackMissionInst) { * be selectable and actionable. * Note: Does NOT change its css class. This is handled by Node.setControlledBy... */ -HackingMission.prototype.configurePlayerNodeElement = function(el) { - var nodeObj = this.getNodeFromElement(el); - if (nodeObj == null) {console.error("Failed getting Node object");} +HackingMission.prototype.configurePlayerNodeElement = function (el) { + var nodeObj = this.getNodeFromElement(el); + if (nodeObj == null) { + console.error("Failed getting Node object"); + } - // Add event listener - const selectNodeWrapper = () => { - selectNode(this, el); - } - el.addEventListener("click", selectNodeWrapper); + // Add event listener + const selectNodeWrapper = () => { + selectNode(this, el); + }; + el.addEventListener("click", selectNodeWrapper); - const multiselectNodeWrapper = () => { - multiselectNode(this, el); - } - el.addEventListener("dblclick", multiselectNodeWrapper); + const multiselectNodeWrapper = () => { + multiselectNode(this, el); + }; + el.addEventListener("dblclick", multiselectNodeWrapper); - - if (el.firstChild) { - el.firstChild.addEventListener("click", selectNodeWrapper); - } -} + if (el.firstChild) { + el.firstChild.addEventListener("click", selectNodeWrapper); + } +}; /** * Configures a DOM element representing an enemy-node by removing * any event listeners */ -HackingMission.prototype.configureEnemyNodeElement = function(el) { - // Deselect node if it was the selected node - var nodeObj = this.getNodeFromElement(el); - for (var i = 0; i < this.selectedNode.length; ++i) { - if (this.selectedNode[i] == nodeObj) { - nodeObj.deselect(this.actionButtons); - this.selectedNode.splice(i, 1); - break; - } +HackingMission.prototype.configureEnemyNodeElement = function (el) { + // Deselect node if it was the selected node + var nodeObj = this.getNodeFromElement(el); + for (var i = 0; i < this.selectedNode.length; ++i) { + if (this.selectedNode[i] == nodeObj) { + nodeObj.deselect(this.actionButtons); + this.selectedNode.splice(i, 1); + break; } -} + } +}; /** * Returns bool indicating whether a node is reachable by player * by checking if any of the adjacent nodes are owned by the player */ -HackingMission.prototype.nodeReachable = function(node) { - var x = node.pos[0], y = node.pos[1]; - if (x > 0 && this.map[x-1][y].plyrCtrl) {return true;} - if (x < 7 && this.map[x+1][y].plyrCtrl) {return true;} - if (y > 0 && this.map[x][y-1].plyrCtrl) {return true;} - if (y < 7 && this.map[x][y+1].plyrCtrl) {return true;} +HackingMission.prototype.nodeReachable = function (node) { + var x = node.pos[0], + y = node.pos[1]; + if (x > 0 && this.map[x - 1][y].plyrCtrl) { + return true; + } + if (x < 7 && this.map[x + 1][y].plyrCtrl) { + return true; + } + if (y > 0 && this.map[x][y - 1].plyrCtrl) { + return true; + } + if (y < 7 && this.map[x][y + 1].plyrCtrl) { + return true; + } + return false; +}; + +HackingMission.prototype.nodeReachableByEnemy = function (node) { + if (node == null) { return false; -} + } + var x = node.pos[0], + y = node.pos[1]; + if (x > 0 && this.map[x - 1][y].enmyCtrl) { + return true; + } + if (x < 7 && this.map[x + 1][y].enmyCtrl) { + return true; + } + if (y > 0 && this.map[x][y - 1].enmyCtrl) { + return true; + } + if (y < 7 && this.map[x][y + 1].enmyCtrl) { + return true; + } + return false; +}; -HackingMission.prototype.nodeReachableByEnemy = function(node) { - if (node == null) {return false;} - var x = node.pos[0], y = node.pos[1]; - if (x > 0 && this.map[x-1][y].enmyCtrl) {return true;} - if (x < 7 && this.map[x+1][y].enmyCtrl) {return true;} - if (y > 0 && this.map[x][y-1].enmyCtrl) {return true;} - if (y < 7 && this.map[x][y+1].enmyCtrl) {return true;} - return false; -} +HackingMission.prototype.start = function () { + this.started = true; + this.initJsPlumb(); + var startBtn = clearEventListeners("hack-mission-start-btn"); + startBtn.classList.remove("a-link-button"); + startBtn.classList.add("a-link-button-inactive"); +}; -HackingMission.prototype.start = function() { - this.started = true; - this.initJsPlumb(); - var startBtn = clearEventListeners("hack-mission-start-btn"); - startBtn.classList.remove("a-link-button"); - startBtn.classList.add("a-link-button-inactive"); -} +HackingMission.prototype.initJsPlumb = function () { + var instance = jsPlumb.getInstance({ + DragOptions: { cursor: "pointer", zIndex: 2000 }, + PaintStyle: { + gradient: { + stops: [ + [0, "#FFFFFF"], + [1, "#FFFFFF"], + ], + }, + stroke: "#FFFFFF", + strokeWidth: 8, + }, + }); -HackingMission.prototype.initJsPlumb = function() { - var instance = jsPlumb.getInstance({ - DragOptions:{cursor:"pointer", zIndex:2000}, - PaintStyle: { - gradient: { stops: [ - [ 0, "#FFFFFF" ], - [ 1, "#FFFFFF" ], - ] }, - stroke: "#FFFFFF", - strokeWidth: 8, - }, + this.jsplumbinstance = instance; + + // All player cores are sources + for (var i = 0; i < this.playerCores.length; ++i) { + instance.makeSource(this.playerCores[i].el, { + deleteEndpointsOnEmpty: true, + maxConnections: 1, + anchor: "Continuous", + connector: "Flowchart", }); + } - this.jsplumbinstance = instance; - - // All player cores are sources - for (var i = 0; i < this.playerCores.length; ++i) { - instance.makeSource(this.playerCores[i].el, { - deleteEndpointsOnEmpty:true, - maxConnections:1, - anchor:"Continuous", - connector:"Flowchart", - }); - } - - // Everything else is a target - for (var i = 0; i < this.enemyCores.length; ++i) { - instance.makeTarget(this.enemyCores[i].el, { - maxConnections:-1, - anchor:"Continuous", - connector:"Flowchart", - }); - } - for (var i = 0; i < this.enemyDatabases.length; ++i) { - instance.makeTarget(this.enemyDatabases[i].el, { - maxConnections:-1, - anchor:"Continuous", - connector:["Flowchart"], - }); - } - for (var i = 0; i < this.enemyNodes.length; ++i) { - instance.makeTarget(this.enemyNodes[i].el, { - maxConnections:-1, - anchor:"Continuous", - connector:"Flowchart", - }); - } - for (var i = 0; i < this.miscNodes.length; ++i) { - instance.makeTarget(this.miscNodes[i].el, { - maxConnections:-1, - anchor:"Continuous", - connector:"Flowchart", - }); - } - - // Clicking a connection drops it - instance.bind("click", (conn) => { - // Cannot drop enemy's connections - const sourceNode = this.getNodeFromElement(conn.source); - if (sourceNode.enmyCtrl) { return; } - - var endpoints = conn.endpoints; - endpoints[0].detachFrom(endpoints[1]); + // Everything else is a target + for (var i = 0; i < this.enemyCores.length; ++i) { + instance.makeTarget(this.enemyCores[i].el, { + maxConnections: -1, + anchor: "Continuous", + connector: "Flowchart", }); - - // Connection events - instance.bind("connection", (info) => { - var targetNode = this.getNodeFromElement(info.target); - - // Do not detach for enemy nodes - var thisNode = this.getNodeFromElement(info.source); - if (thisNode.enmyCtrl) {return;} - - // If the node is not reachable, drop the connection - if (!this.nodeReachable(targetNode)) { - info.sourceEndpoint.detachFrom(info.targetEndpoint); - return; - } - - var sourceNode = this.getNodeFromElement(info.source); - sourceNode.conn = info.connection; - var targetNode = this.getNodeFromElement(info.target); - ++targetNode.targetedCount; + } + for (var i = 0; i < this.enemyDatabases.length; ++i) { + instance.makeTarget(this.enemyDatabases[i].el, { + maxConnections: -1, + anchor: "Continuous", + connector: ["Flowchart"], }); - - // Detach Connection events - instance.bind("connectionDetached", (info) => { - var sourceNode = this.getNodeFromElement(info.source); - sourceNode.conn = null; - var targetNode = this.getNodeFromElement(info.target); - targetNode.untarget(); + } + for (var i = 0; i < this.enemyNodes.length; ++i) { + instance.makeTarget(this.enemyNodes[i].el, { + maxConnections: -1, + anchor: "Continuous", + connector: "Flowchart", }); + } + for (var i = 0; i < this.miscNodes.length; ++i) { + instance.makeTarget(this.miscNodes[i].el, { + maxConnections: -1, + anchor: "Continuous", + connector: "Flowchart", + }); + } -} + // Clicking a connection drops it + instance.bind("click", (conn) => { + // Cannot drop enemy's connections + const sourceNode = this.getNodeFromElement(conn.source); + if (sourceNode.enmyCtrl) { + return; + } + + var endpoints = conn.endpoints; + endpoints[0].detachFrom(endpoints[1]); + }); + + // Connection events + instance.bind("connection", (info) => { + var targetNode = this.getNodeFromElement(info.target); + + // Do not detach for enemy nodes + var thisNode = this.getNodeFromElement(info.source); + if (thisNode.enmyCtrl) { + return; + } + + // If the node is not reachable, drop the connection + if (!this.nodeReachable(targetNode)) { + info.sourceEndpoint.detachFrom(info.targetEndpoint); + return; + } + + var sourceNode = this.getNodeFromElement(info.source); + sourceNode.conn = info.connection; + var targetNode = this.getNodeFromElement(info.target); + ++targetNode.targetedCount; + }); + + // Detach Connection events + instance.bind("connectionDetached", (info) => { + var sourceNode = this.getNodeFromElement(info.source); + sourceNode.conn = null; + var targetNode = this.getNodeFromElement(info.target); + targetNode.untarget(); + }); +}; // Drops all connections where the specified node is the source -HackingMission.prototype.dropAllConnectionsFromNode = function(node) { - var allConns = this.jsplumbinstance.getAllConnections(); - for (var i = allConns.length-1; i >= 0; --i) { - if (allConns[i].source == node.el) { - allConns[i].endpoints[0].detachFrom(allConns[i].endpoints[1]); - } +HackingMission.prototype.dropAllConnectionsFromNode = function (node) { + var allConns = this.jsplumbinstance.getAllConnections(); + for (var i = allConns.length - 1; i >= 0; --i) { + if (allConns[i].source == node.el) { + allConns[i].endpoints[0].detachFrom(allConns[i].endpoints[1]); } -} + } +}; // Drops all connections where the specified node is the target -HackingMission.prototype.dropAllConnectionsToNode = function(node) { - var allConns = this.jsplumbinstance.getAllConnections(); - for (var i = allConns.length-1; i >= 0; --i) { - if (allConns[i].target == node.el) { - allConns[i].endpoints[0].detachFrom(allConns[i].endpoints[1]); - } +HackingMission.prototype.dropAllConnectionsToNode = function (node) { + var allConns = this.jsplumbinstance.getAllConnections(); + for (var i = allConns.length - 1; i >= 0; --i) { + if (allConns[i].target == node.el) { + allConns[i].endpoints[0].detachFrom(allConns[i].endpoints[1]); } - node.beingTargeted = false; -} + } + node.beingTargeted = false; +}; var storedCycles = 0; -HackingMission.prototype.process = function(numCycles=1) { - if (!this.started) {return;} - storedCycles += numCycles; - if (storedCycles < 2) {return;} // Only process every 3 cycles minimum +HackingMission.prototype.process = function (numCycles = 1) { + if (!this.started) { + return; + } + storedCycles += numCycles; + if (storedCycles < 2) { + return; + } // Only process every 3 cycles minimum - var res = false; - // Process actions of all player nodes - this.playerCores.forEach((node) => { - res |= this.processNode(node, storedCycles); - }); + var res = false; + // Process actions of all player nodes + this.playerCores.forEach((node) => { + res |= this.processNode(node, storedCycles); + }); - this.playerNodes.forEach((node) => { - if (node.type === NodeTypes.Transfer || - node.type === NodeTypes.Shield || - node.type === NodeTypes.Firewall) { - res |= this.processNode(node, storedCycles); - } - }); - - // Process actions of all enemy nodes - this.enemyCores.forEach((node) => { - this.enemyAISelectAction(node); - res |= this.processNode(node, storedCycles); - }); - - this.enemyNodes.forEach((node) => { - if (node.type === NodeTypes.Transfer || - node.type === NodeTypes.Shield || - node.type === NodeTypes.Firewall) { - this.enemyAISelectAction(node); - res |= this.processNode(node, storedCycles); - } - }); - - // The hp of enemy databases increases slowly - this.enemyDatabases.forEach((node) => { - node.maxhp += (0.1 * storedCycles); - node.hp += (0.1 * storedCycles); - }); - - if (res) { - this.calculateAttacks(); - this.calculateDefenses(); + this.playerNodes.forEach((node) => { + if ( + node.type === NodeTypes.Transfer || + node.type === NodeTypes.Shield || + node.type === NodeTypes.Firewall + ) { + res |= this.processNode(node, storedCycles); } + }); - // Win if all enemy databases are conquered - if (this.enemyDatabases.length === 0) { - this.finishMission(true); - return; + // Process actions of all enemy nodes + this.enemyCores.forEach((node) => { + this.enemyAISelectAction(node); + res |= this.processNode(node, storedCycles); + }); + + this.enemyNodes.forEach((node) => { + if ( + node.type === NodeTypes.Transfer || + node.type === NodeTypes.Shield || + node.type === NodeTypes.Firewall + ) { + this.enemyAISelectAction(node); + res |= this.processNode(node, storedCycles); } + }); - // Lose if all your cores are gone - if (this.playerCores.length === 0) { - this.finishMission(false); - return; + // The hp of enemy databases increases slowly + this.enemyDatabases.forEach((node) => { + node.maxhp += 0.1 * storedCycles; + node.hp += 0.1 * storedCycles; + }); + + if (res) { + this.calculateAttacks(); + this.calculateDefenses(); + } + + // Win if all enemy databases are conquered + if (this.enemyDatabases.length === 0) { + this.finishMission(true); + return; + } + + // Lose if all your cores are gone + if (this.playerCores.length === 0) { + this.finishMission(false); + return; + } + + // Defense/hp of misc nodes increases slowly over time + this.miscNodes.forEach((node) => { + node.def += 0.1 * storedCycles; + node.maxhp += 0.05 * storedCycles; + node.hp += 0.1 * storedCycles; + if (node.hp > node.maxhp) { + node.hp = node.maxhp; } + this.updateNodeDomElement(node); + }); - // Defense/hp of misc nodes increases slowly over time - this.miscNodes.forEach((node) => { - node.def += (0.1 * storedCycles); - node.maxhp += (0.05 * storedCycles); - node.hp += (0.1 * storedCycles); - if (node.hp > node.maxhp) {node.hp = node.maxhp;} - this.updateNodeDomElement(node); - }); + // Update timer and check if player lost + this.time -= storedCycles * Engine._idleSpeed; + if (this.time <= 0) { + this.finishMission(false); + return; + } + this.updateTimer(); - // Update timer and check if player lost - this.time -= (storedCycles * Engine._idleSpeed); - if (this.time <= 0) { - this.finishMission(false); - return; - } - this.updateTimer(); - - storedCycles = 0; -} + storedCycles = 0; +}; // Returns a bool representing whether defenses need to be re-calculated -HackingMission.prototype.processNode = function(nodeObj, numCycles=1) { - if (nodeObj.action == null) { - return; +HackingMission.prototype.processNode = function (nodeObj, numCycles = 1) { + if (nodeObj.action == null) { + return; + } + + var targetNode = null, + def, + atk; + if (nodeObj.conn) { + if (nodeObj.conn.target != null) { + targetNode = this.getNodeFromElement(nodeObj.conn.target); + } else { + targetNode = this.getNodeFromElement(nodeObj.conn.targetId); } - var targetNode = null, def, atk; - if (nodeObj.conn) { - if (nodeObj.conn.target != null) { - targetNode = this.getNodeFromElement(nodeObj.conn.target); - } else { - targetNode = this.getNodeFromElement(nodeObj.conn.targetId); - } + if (targetNode == null) { + // Player is in the middle of dragging the connection, + // so the target node is null. Do nothing here + } else if (targetNode.plyrCtrl) { + def = this.playerDef; + atk = this.enemyAtk; + } else if (targetNode.enmyCtrl) { + def = this.enemyDef; + atk = this.playerAtk; + } else { + // Misc Node + def = targetNode.def; + nodeObj.plyrCtrl ? (atk = this.playerAtk) : (atk = this.enemyAtk); + } + } - if (targetNode == null) { - // Player is in the middle of dragging the connection, - // so the target node is null. Do nothing here - } else if (targetNode.plyrCtrl) { - def = this.playerDef; - atk = this.enemyAtk; - } else if (targetNode.enmyCtrl) { - def = this.enemyDef; - atk = this.playerAtk; - } else { // Misc Node - def = targetNode.def; - nodeObj.plyrCtrl ? atk = this.playerAtk : atk = this.enemyAtk; - } + // Calculations are per second, so divide everything by 5 + var calcStats = false, + plyr = nodeObj.plyrCtrl; + var enmyHacking = + this.difficulty * CONSTANTS.HackingMissionDifficultyToHacking; + switch (nodeObj.action) { + case NodeActions.Attack: + if (targetNode == null) { + break; + } + if (nodeObj.conn == null) { + break; + } + var dmg = this.calculateAttackDamage( + atk, + def, + plyr ? Player.hacking_skill : enmyHacking, + ); + targetNode.hp -= (dmg / 5) * numCycles; + break; + case NodeActions.Scan: + if (targetNode == null) { + break; + } + if (nodeObj.conn == null) { + break; + } + var eff = this.calculateScanEffect( + atk, + def, + plyr ? Player.hacking_skill : enmyHacking, + ); + targetNode.def -= (eff / 5) * numCycles; + calcStats = true; + break; + case NodeActions.Weaken: + if (targetNode == null) { + break; + } + if (nodeObj.conn == null) { + break; + } + var eff = this.calculateWeakenEffect( + atk, + def, + plyr ? Player.hacking_skill : enmyHacking, + ); + targetNode.atk -= (eff / 5) * numCycles; + calcStats = true; + break; + case NodeActions.Fortify: + var eff = this.calculateFortifyEffect(Player.hacking_skill); + nodeObj.def += (eff / 5) * numCycles; + calcStats = true; + break; + case NodeActions.Overflow: + var eff = this.calculateOverflowEffect(Player.hacking_skill); + if (nodeObj.def < eff) { + break; + } + nodeObj.def -= (eff / 5) * numCycles; + nodeObj.atk += (eff / 5) * numCycles; + calcStats = true; + break; + default: + console.error(`Invalid Node Action: ${nodeObj.action}`); + break; + } + + // Stats can't go below 0 + if (nodeObj.atk < 0) { + nodeObj.atk = 0; + } + if (nodeObj.def < 0) { + nodeObj.def = 0; + } + if (targetNode && targetNode.atk < 0) { + targetNode.atk = 0; + } + if (targetNode && targetNode.def < 0) { + targetNode.def = 0; + } + + // Conquering a node + if (targetNode && targetNode.hp <= 0) { + var conqueredByPlayer = nodeObj.plyrCtrl; + targetNode.hp = targetNode.maxhp; + targetNode.action = null; + targetNode.conn = null; + if (this.selectedNode == targetNode) { + targetNode.deselect(this.actionButtons); } - // Calculations are per second, so divide everything by 5 - var calcStats = false, plyr = nodeObj.plyrCtrl; - var enmyHacking = this.difficulty * CONSTANTS.HackingMissionDifficultyToHacking; - switch(nodeObj.action) { - case NodeActions.Attack: - if (targetNode == null) {break;} - if (nodeObj.conn == null) {break;} - var dmg = this.calculateAttackDamage(atk, def, plyr ? Player.hacking_skill : enmyHacking); - targetNode.hp -= (dmg/5 * numCycles); - break; - case NodeActions.Scan: - if (targetNode == null) {break;} - if (nodeObj.conn == null) {break;} - var eff = this.calculateScanEffect(atk, def, plyr ? Player.hacking_skill : enmyHacking); - targetNode.def -= (eff/5 * numCycles); - calcStats = true; - break; - case NodeActions.Weaken: - if (targetNode == null) {break;} - if (nodeObj.conn == null) {break;} - var eff = this.calculateWeakenEffect(atk, def, plyr ? Player.hacking_skill : enmyHacking); - targetNode.atk -= (eff/5 * numCycles); - calcStats = true; - break; - case NodeActions.Fortify: - var eff = this.calculateFortifyEffect(Player.hacking_skill); - nodeObj.def += (eff/5 * numCycles); - calcStats = true; - break; - case NodeActions.Overflow: - var eff = this.calculateOverflowEffect(Player.hacking_skill); - if (nodeObj.def < eff) {break;} - nodeObj.def -= (eff/5 * numCycles); - nodeObj.atk += (eff/5 * numCycles); - calcStats = true; - break; - default: - console.error(`Invalid Node Action: ${nodeObj.action}`); - break; + // The conquered node has its stats reduced + targetNode.atk /= 2; + targetNode.def /= 3.5; + + // Flag for whether the target node was a misc node + var isMiscNode = !targetNode.plyrCtrl && !targetNode.enmyCtrl; + + // Remove all connections from Node + this.dropAllConnectionsToNode(targetNode); + this.dropAllConnectionsFromNode(targetNode); + + // Changes the css class and turn the node into a JsPlumb Source/Target + if (conqueredByPlayer) { + targetNode.setControlledByPlayer(); + this.jsplumbinstance.unmakeTarget(targetNode.el); + this.jsplumbinstance.makeSource(targetNode.el, { + deleteEndpointsOnEmpty: true, + maxConnections: 1, + anchor: "Continuous", + connector: "Flowchart", + }); + } else { + targetNode.setControlledByEnemy(); + nodeObj.conn = null; // Clear connection + this.jsplumbinstance.unmakeSource(targetNode.el); + this.jsplumbinstance.makeTarget(targetNode.el, { + maxConnections: -1, + anchor: "Continuous", + connector: ["Flowchart"], + }); } - // Stats can't go below 0 - if (nodeObj.atk < 0) {nodeObj.atk = 0;} - if (nodeObj.def < 0) {nodeObj.def = 0;} - if (targetNode && targetNode.atk < 0) {targetNode.atk = 0;} - if (targetNode && targetNode.def < 0) {targetNode.def = 0;} + calcStats = true; - // Conquering a node - if (targetNode && targetNode.hp <= 0) { - var conqueredByPlayer = nodeObj.plyrCtrl; - targetNode.hp = targetNode.maxhp; - targetNode.action = null; - targetNode.conn = null; - if (this.selectedNode == targetNode) { - targetNode.deselect(this.actionButtons); + // Helper function to swap nodes between the respective enemyNodes/playerNodes arrays + function swapNodes(orig, dest, targetNode) { + for (var i = 0; i < orig.length; ++i) { + if (orig[i] == targetNode) { + var node = orig.splice(i, 1); + node = node[0]; + dest.push(node); + break; } + } + } - // The conquered node has its stats reduced - targetNode.atk /= 2; - targetNode.def /= 3.5; - - // Flag for whether the target node was a misc node - var isMiscNode = !targetNode.plyrCtrl && !targetNode.enmyCtrl; - - // Remove all connections from Node - this.dropAllConnectionsToNode(targetNode); - this.dropAllConnectionsFromNode(targetNode); - - // Changes the css class and turn the node into a JsPlumb Source/Target + switch (targetNode.type) { + case NodeTypes.Core: if (conqueredByPlayer) { - targetNode.setControlledByPlayer(); - this.jsplumbinstance.unmakeTarget(targetNode.el); - this.jsplumbinstance.makeSource(targetNode.el, { - deleteEndpointsOnEmpty:true, - maxConnections:1, - anchor:"Continuous", - connector:"Flowchart", - }); + swapNodes(this.enemyCores, this.playerCores, targetNode); + this.configurePlayerNodeElement(targetNode.el); } else { - targetNode.setControlledByEnemy(); - nodeObj.conn = null; // Clear connection - this.jsplumbinstance.unmakeSource(targetNode.el); - this.jsplumbinstance.makeTarget(targetNode.el, { - maxConnections:-1, - anchor:"Continuous", - connector:["Flowchart"], - }); + swapNodes(this.playerCores, this.enemyCores, targetNode); + this.configureEnemyNodeElement(targetNode.el); + } + break; + case NodeTypes.Firewall: + if (conqueredByPlayer) { + swapNodes(this.enemyNodes, this.playerNodes, targetNode); + } else { + swapNodes(this.playerNodes, this.enemyNodes, targetNode); + this.configureEnemyNodeElement(targetNode.el); + } + break; + case NodeTypes.Database: + if (conqueredByPlayer) { + swapNodes(this.enemyDatabases, this.playerNodes, targetNode); + } else { + swapNodes(this.playerNodes, this.enemyDatabases, targetNode); + } + break; + case NodeTypes.Spam: + if (conqueredByPlayer) { + swapNodes( + isMiscNode ? this.miscNodes : this.enemyNodes, + this.playerNodes, + targetNode, + ); + // Conquering spam node increases time limit + this.time += CONSTANTS.HackingMissionSpamTimeIncrease; + } else { + swapNodes( + isMiscNode ? this.miscNodes : this.playerNodes, + this.enemyNodes, + targetNode, + ); } - calcStats = true; - - // Helper function to swap nodes between the respective enemyNodes/playerNodes arrays - function swapNodes(orig, dest, targetNode) { - for (var i = 0; i < orig.length; ++i) { - if (orig[i] == targetNode) { - var node = orig.splice(i, 1); - node = node[0]; - dest.push(node); - break; - } - } + break; + case NodeTypes.Transfer: + // Conquering a Transfer node increases the attack of all cores by some percentages + if (conqueredByPlayer) { + swapNodes( + isMiscNode ? this.miscNodes : this.enemyNodes, + this.playerNodes, + targetNode, + ); + this.playerCores.forEach(function (node) { + node.atk *= CONSTANTS.HackingMissionTransferAttackIncrease; + }); + this.configurePlayerNodeElement(targetNode.el); + } else { + swapNodes( + isMiscNode ? this.miscNodes : this.playerNodes, + this.enemyNodes, + targetNode, + ); + this.enemyCores.forEach(function (node) { + node.atk *= CONSTANTS.HackingMissionTransferAttackIncrease; + }); + this.configureEnemyNodeElement(targetNode.el); } - - switch(targetNode.type) { - case NodeTypes.Core: - if (conqueredByPlayer) { - swapNodes(this.enemyCores, this.playerCores, targetNode); - this.configurePlayerNodeElement(targetNode.el); - } else { - swapNodes(this.playerCores, this.enemyCores, targetNode); - this.configureEnemyNodeElement(targetNode.el); - } - break; - case NodeTypes.Firewall: - if (conqueredByPlayer) { - swapNodes(this.enemyNodes, this.playerNodes, targetNode); - } else { - swapNodes(this.playerNodes, this.enemyNodes, targetNode); - this.configureEnemyNodeElement(targetNode.el); - } - break; - case NodeTypes.Database: - if (conqueredByPlayer) { - swapNodes(this.enemyDatabases, this.playerNodes, targetNode); - } else { - swapNodes(this.playerNodes, this.enemyDatabases, targetNode); - } - break; - case NodeTypes.Spam: - if (conqueredByPlayer) { - swapNodes(isMiscNode ? this.miscNodes : this.enemyNodes, this.playerNodes, targetNode); - // Conquering spam node increases time limit - this.time += CONSTANTS.HackingMissionSpamTimeIncrease; - } else { - swapNodes(isMiscNode ? this.miscNodes : this.playerNodes, this.enemyNodes, targetNode); - } - - break; - case NodeTypes.Transfer: - // Conquering a Transfer node increases the attack of all cores by some percentages - if (conqueredByPlayer) { - swapNodes(isMiscNode ? this.miscNodes : this.enemyNodes, this.playerNodes, targetNode); - this.playerCores.forEach(function(node) { - node.atk *= CONSTANTS.HackingMissionTransferAttackIncrease; - }); - this.configurePlayerNodeElement(targetNode.el); - } else { - swapNodes(isMiscNode ? this.miscNodes : this.playerNodes, this.enemyNodes, targetNode); - this.enemyCores.forEach(function(node) { - node.atk *= CONSTANTS.HackingMissionTransferAttackIncrease; - }); - this.configureEnemyNodeElement(targetNode.el); - } - break; - case NodeTypes.Shield: - if (conqueredByPlayer) { - swapNodes(isMiscNode ? this.miscNodes : this.enemyNodes, this.playerNodes, targetNode); - this.configurePlayerNodeElement(targetNode.el); - } else { - swapNodes(isMiscNode ? this.miscNodes : this.playerNodes, this.enemyNodes, targetNode); - this.configureEnemyNodeElement(targetNode.el); - } - break; - } - - // If a misc node was conquered, the defense for all misc nodes increases by some fixed amount - if (isMiscNode) { //&& conqueredByPlayer) { - this.miscNodes.forEach((node) => { - if (node.targetedCount === 0) { - node.def *= CONSTANTS.HackingMissionMiscDefenseIncrease; - } - }); + break; + case NodeTypes.Shield: + if (conqueredByPlayer) { + swapNodes( + isMiscNode ? this.miscNodes : this.enemyNodes, + this.playerNodes, + targetNode, + ); + this.configurePlayerNodeElement(targetNode.el); + } else { + swapNodes( + isMiscNode ? this.miscNodes : this.playerNodes, + this.enemyNodes, + targetNode, + ); + this.configureEnemyNodeElement(targetNode.el); } + break; } - // Update node DOMs - this.updateNodeDomElement(nodeObj); - if (targetNode) {this.updateNodeDomElement(targetNode);} - return calcStats; -} + // If a misc node was conquered, the defense for all misc nodes increases by some fixed amount + if (isMiscNode) { + //&& conqueredByPlayer) { + this.miscNodes.forEach((node) => { + if (node.targetedCount === 0) { + node.def *= CONSTANTS.HackingMissionMiscDefenseIncrease; + } + }); + } + } + + // Update node DOMs + this.updateNodeDomElement(nodeObj); + if (targetNode) { + this.updateNodeDomElement(targetNode); + } + return calcStats; +}; // Enemy "AI" for CPU Core and Transfer Nodes -HackingMission.prototype.enemyAISelectAction = function(nodeObj) { - if (nodeObj == null) {return;} - switch(nodeObj.type) { - case NodeTypes.Core: - /** - * Select a single RANDOM target from miscNodes and player's Nodes - * If it is reachable, it will target it. If not, no target will - * be selected for now, and the next time process() gets called this will repeat - */ - if (nodeObj.conn == null) { - if (this.miscNodes.length === 0) { - // Randomly pick a player node and attack it if its reachable - var rand = getRandomInt(0, this.playerNodes.length-1); - var node; - if (this.playerNodes.length === 0) { - node = null; - } else { - node = this.playerNodes[rand]; - } - if (this.nodeReachableByEnemy(node)) { - // Create connection - nodeObj.conn = this.jsplumbinstance.connect({ - source:nodeObj.el, - target:node.el, - }); - ++node.targetedCount; - } else { - // Randomly pick a player core and attack it if its reachable - rand = getRandomInt(0, this.playerCores.length-1); - if (this.playerCores.length === 0) { - return; // No Misc Nodes, no player Nodes, no Player cores. Player lost - } else { - node = this.playerCores[rand]; - } - - if (this.nodeReachableByEnemy(node)) { - // Create connection - nodeObj.conn = this.jsplumbinstance.connect({ - source:nodeObj.el, - target:node.el, - }); - ++node.targetedCount; - } - } - } else { - // Randomly pick a misc node and attack it if its reachable - var rand = getRandomInt(0, this.miscNodes.length-1); - var node = this.miscNodes[rand]; - if (this.nodeReachableByEnemy(node)) { - nodeObj.conn = this.jsplumbinstance.connect({ - source:nodeObj.el, - target:node.el, - }); - ++node.targetedCount; - } - } - - // If no connection was made, set the Core to Fortify - nodeObj.action = NodeActions.Fortify; +HackingMission.prototype.enemyAISelectAction = function (nodeObj) { + if (nodeObj == null) { + return; + } + switch (nodeObj.type) { + case NodeTypes.Core: + /** + * Select a single RANDOM target from miscNodes and player's Nodes + * If it is reachable, it will target it. If not, no target will + * be selected for now, and the next time process() gets called this will repeat + */ + if (nodeObj.conn == null) { + if (this.miscNodes.length === 0) { + // Randomly pick a player node and attack it if its reachable + var rand = getRandomInt(0, this.playerNodes.length - 1); + var node; + if (this.playerNodes.length === 0) { + node = null; + } else { + node = this.playerNodes[rand]; + } + if (this.nodeReachableByEnemy(node)) { + // Create connection + nodeObj.conn = this.jsplumbinstance.connect({ + source: nodeObj.el, + target: node.el, + }); + ++node.targetedCount; + } else { + // Randomly pick a player core and attack it if its reachable + rand = getRandomInt(0, this.playerCores.length - 1); + if (this.playerCores.length === 0) { + return; // No Misc Nodes, no player Nodes, no Player cores. Player lost } else { - // If this node has a selected target - var targetNode; - if (nodeObj.conn.target) { - targetNode = this.getNodeFromElement(nodeObj.conn.target); - } else { - targetNode = this.getNodeFromElement(nodeObj.conn.targetId); - } - if (targetNode == null) { - console.error("Error getting Target node Object in enemyAISelectAction()"); - } + node = this.playerCores[rand]; + } - if (targetNode.def > this.enemyAtk + 15) { - if (nodeObj.def < 50) { - nodeObj.action = NodeActions.Fortify; - } else { - nodeObj.action = NodeActions.Overflow; - } - } else if (Math.abs(targetNode.def - this.enemyAtk) <= 15) { - nodeObj.action = NodeActions.Scan; - } else { - nodeObj.action = NodeActions.Attack; - } + if (this.nodeReachableByEnemy(node)) { + // Create connection + nodeObj.conn = this.jsplumbinstance.connect({ + source: nodeObj.el, + target: node.el, + }); + ++node.targetedCount; } - break; - case NodeTypes.Transfer: - // Switch between fortifying and overflowing as necessary - if (nodeObj.def < 125) { - nodeObj.action = NodeActions.Fortify; - } else { - nodeObj.action = NodeActions.Overflow; - } - break; - case NodeTypes.Firewall: - case NodeTypes.Shield: + } + } else { + // Randomly pick a misc node and attack it if its reachable + var rand = getRandomInt(0, this.miscNodes.length - 1); + var node = this.miscNodes[rand]; + if (this.nodeReachableByEnemy(node)) { + nodeObj.conn = this.jsplumbinstance.connect({ + source: nodeObj.el, + target: node.el, + }); + ++node.targetedCount; + } + } + + // If no connection was made, set the Core to Fortify + nodeObj.action = NodeActions.Fortify; + } else { + // If this node has a selected target + var targetNode; + if (nodeObj.conn.target) { + targetNode = this.getNodeFromElement(nodeObj.conn.target); + } else { + targetNode = this.getNodeFromElement(nodeObj.conn.targetId); + } + if (targetNode == null) { + console.error( + "Error getting Target node Object in enemyAISelectAction()", + ); + } + + if (targetNode.def > this.enemyAtk + 15) { + if (nodeObj.def < 50) { nodeObj.action = NodeActions.Fortify; - break; - default: - break; - } -} + } else { + nodeObj.action = NodeActions.Overflow; + } + } else if (Math.abs(targetNode.def - this.enemyAtk) <= 15) { + nodeObj.action = NodeActions.Scan; + } else { + nodeObj.action = NodeActions.Attack; + } + } + break; + case NodeTypes.Transfer: + // Switch between fortifying and overflowing as necessary + if (nodeObj.def < 125) { + nodeObj.action = NodeActions.Fortify; + } else { + nodeObj.action = NodeActions.Overflow; + } + break; + case NodeTypes.Firewall: + case NodeTypes.Shield: + nodeObj.action = NodeActions.Fortify; + break; + default: + break; + } +}; var hackEffWeightSelf = 130; // Weight for Node actions on self var hackEffWeightTarget = 25; // Weight for Node Actions against Target var hackEffWeightAttack = 80; // Weight for Attack action // Returns damage per cycle based on stats -HackingMission.prototype.calculateAttackDamage = function(atk, def, hacking = 0) { - return Math.max(0.55 * (atk + (hacking / hackEffWeightAttack) - def), 1); -} +HackingMission.prototype.calculateAttackDamage = function ( + atk, + def, + hacking = 0, +) { + return Math.max(0.55 * (atk + hacking / hackEffWeightAttack - def), 1); +}; -HackingMission.prototype.calculateScanEffect = function(atk, def, hacking=0) { - return Math.max(0.6 * ((atk) + hacking / hackEffWeightTarget - (def * 0.95)), 2); -} +HackingMission.prototype.calculateScanEffect = function ( + atk, + def, + hacking = 0, +) { + return Math.max(0.6 * (atk + hacking / hackEffWeightTarget - def * 0.95), 2); +}; -HackingMission.prototype.calculateWeakenEffect = function(atk, def, hacking=0) { - return Math.max((atk) + hacking / hackEffWeightTarget - (def * 0.95), 2); -} +HackingMission.prototype.calculateWeakenEffect = function ( + atk, + def, + hacking = 0, +) { + return Math.max(atk + hacking / hackEffWeightTarget - def * 0.95, 2); +}; -HackingMission.prototype.calculateFortifyEffect = function(hacking=0) { - return 0.9 * hacking / hackEffWeightSelf; -} +HackingMission.prototype.calculateFortifyEffect = function (hacking = 0) { + return (0.9 * hacking) / hackEffWeightSelf; +}; -HackingMission.prototype.calculateOverflowEffect = function(hacking=0) { - return 0.95 * hacking / hackEffWeightSelf; -} +HackingMission.prototype.calculateOverflowEffect = function (hacking = 0) { + return (0.95 * hacking) / hackEffWeightSelf; +}; // Updates timer display -HackingMission.prototype.updateTimer = function() { - var timer = document.getElementById("hacking-mission-timer"); +HackingMission.prototype.updateTimer = function () { + var timer = document.getElementById("hacking-mission-timer"); - // Convert time remaining to a string of the form mm:ss - var seconds = Math.round(this.time / 1000); - var minutes = Math.trunc(seconds / 60); - seconds %= 60; - var str = ("0" + minutes).slice(-2) + ":" + ("0" + seconds).slice(-2); - timer.innerText = "Time left: " + str; -} + // Convert time remaining to a string of the form mm:ss + var seconds = Math.round(this.time / 1000); + var minutes = Math.trunc(seconds / 60); + seconds %= 60; + var str = ("0" + minutes).slice(-2) + ":" + ("0" + seconds).slice(-2); + timer.innerText = "Time left: " + str; +}; // The 'win' argument is a bool for whether or not the player won -HackingMission.prototype.finishMission = function(win) { - inMission = false; - currMission = null; +HackingMission.prototype.finishMission = function (win) { + inMission = false; + currMission = null; - if (win) { - var favorMult = 1 + (this.faction.favor / 100); - var gain = this.reward * Player.faction_rep_mult * favorMult; - dialogBoxCreate(<>Mission won! You earned {Reputation(gain)} reputation with {this.faction.name}); - Player.gainIntelligenceExp(this.difficulty * CONSTANTS.IntelligenceHackingMissionBaseExpGain); - this.faction.playerReputation += gain; - } else { - dialogBoxCreate("Mission lost/forfeited! You did not gain any faction reputation."); - } + if (win) { + var favorMult = 1 + this.faction.favor / 100; + var gain = this.reward * Player.faction_rep_mult * favorMult; + dialogBoxCreate( + <> + Mission won! You earned {Reputation(gain)} reputation with{" "} + {this.faction.name} + , + ); + Player.gainIntelligenceExp( + this.difficulty * CONSTANTS.IntelligenceHackingMissionBaseExpGain, + ); + this.faction.playerReputation += gain; + } else { + dialogBoxCreate( + "Mission lost/forfeited! You did not gain any faction reputation.", + ); + } - // Clear mission container - var container = document.getElementById("mission-container"); - while(container.firstChild) { - container.removeChild(container.firstChild); - } + // Clear mission container + var container = document.getElementById("mission-container"); + while (container.firstChild) { + container.removeChild(container.firstChild); + } - // Return to Faction page - document.getElementById("mainmenu-container").style.visibility = "visible"; - document.getElementById("character-overview-wrapper").style.visibility = "visible"; - Engine.loadFactionContent(); - displayFactionContent(this.faction.name); -} + // Return to Faction page + document.getElementById("mainmenu-container").style.visibility = "visible"; + document.getElementById("character-overview-wrapper").style.visibility = + "visible"; + Engine.loadFactionContent(); + displayFactionContent(this.faction.name); +}; -export {HackingMission, inMission, setInMission, currMission}; +export { HackingMission, inMission, setInMission, currMission }; diff --git a/src/Netscript/Environment.ts b/src/Netscript/Environment.ts index 0f3a95219..3c4da20ed 100644 --- a/src/Netscript/Environment.ts +++ b/src/Netscript/Environment.ts @@ -5,71 +5,71 @@ import { IMap } from "../types"; export class Environment { - /** - * Parent environment. Used to implement "scope" - */ - parent: Environment | null = null; + /** + * Parent environment. Used to implement "scope" + */ + parent: Environment | null = null; - /** - * Whether or not the script that uses this Environment should stop running - */ - stopFlag = false; + /** + * Whether or not the script that uses this Environment should stop running + */ + stopFlag = false; - /** - * Environment variables (currently only Netscript functions) - */ - vars: IMap = {}; + /** + * Environment variables (currently only Netscript functions) + */ + vars: IMap = {}; - constructor(parent: Environment | null) { - if (parent instanceof Environment) { - this.vars = Object.assign({}, parent.vars); - } - - this.parent = parent; + constructor(parent: Environment | null) { + if (parent instanceof Environment) { + this.vars = Object.assign({}, parent.vars); } - /** - * Finds the scope where the variable with the given name is defined - */ - lookup(name: string): Environment | null { - // eslint-disable-next-line @typescript-eslint/no-this-alias - let scope: Environment | null = this; - while (scope) { - if (Object.prototype.hasOwnProperty.call(scope.vars, name)) { - return scope; - } - scope = scope.parent; - } + this.parent = parent; + } - return null; + /** + * Finds the scope where the variable with the given name is defined + */ + lookup(name: string): Environment | null { + // eslint-disable-next-line @typescript-eslint/no-this-alias + let scope: Environment | null = this; + while (scope) { + if (Object.prototype.hasOwnProperty.call(scope.vars, name)) { + return scope; + } + scope = scope.parent; } - //Get the current value of a variable - get(name: string): any { - if (name in this.vars) { - return this.vars[name]; - } + return null; + } - throw new Error(`Undefined variable ${name}`); + //Get the current value of a variable + get(name: string): any { + if (name in this.vars) { + return this.vars[name]; } - //Sets the value of a variable in any scope - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - set(name: string, value: any): any { - const scope = this.lookup(name); + throw new Error(`Undefined variable ${name}`); + } - //If scope has a value, then this variable is already set in a higher scope, so - //set is there. Otherwise, create a new variable in the local scope - if (scope !== null) { - return scope.vars[name] = value; - } else { - return this.vars[name] = value; - } - } + //Sets the value of a variable in any scope + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + set(name: string, value: any): any { + const scope = this.lookup(name); - //Creates (or overwrites) a variable in the current scope - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - def(name: string, value: any): any { - return this.vars[name] = value; + //If scope has a value, then this variable is already set in a higher scope, so + //set is there. Otherwise, create a new variable in the local scope + if (scope !== null) { + return (scope.vars[name] = value); + } else { + return (this.vars[name] = value); } + } + + //Creates (or overwrites) a variable in the current scope + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + def(name: string, value: any): any { + return (this.vars[name] = value); + } } diff --git a/src/Netscript/Pid.ts b/src/Netscript/Pid.ts index 06883adf1..77c5db17c 100644 --- a/src/Netscript/Pid.ts +++ b/src/Netscript/Pid.ts @@ -6,37 +6,37 @@ let pidCounter = 1; * Find and return the next availble PID for a script */ export function generateNextPid(): number { - let tempCounter = pidCounter; + let tempCounter = pidCounter; - // Cap the number of search iterations at some arbitrary value to avoid - // infinite loops. We'll assume that players wont have 1mil+ running scripts - let found = false; - for (let i = 0; i < 1e6;) { - if (!workerScripts.has(tempCounter + i)) { - found = true; - tempCounter = tempCounter + i; - break; - } - - if (i === Number.MAX_SAFE_INTEGER - 1) { - i = 1; - } else { - ++i; - } + // Cap the number of search iterations at some arbitrary value to avoid + // infinite loops. We'll assume that players wont have 1mil+ running scripts + let found = false; + for (let i = 0; i < 1e6; ) { + if (!workerScripts.has(tempCounter + i)) { + found = true; + tempCounter = tempCounter + i; + break; } - if (found) { - pidCounter = tempCounter + 1; - if (pidCounter >= Number.MAX_SAFE_INTEGER) { - pidCounter = 1; - } - - return tempCounter; + if (i === Number.MAX_SAFE_INTEGER - 1) { + i = 1; } else { - return -1; + ++i; } + } + + if (found) { + pidCounter = tempCounter + 1; + if (pidCounter >= Number.MAX_SAFE_INTEGER) { + pidCounter = 1; + } + + return tempCounter; + } else { + return -1; + } } export function resetPidCounter(): void { - pidCounter = 1; -} \ No newline at end of file + pidCounter = 1; +} diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index ff3ccdcac..62719246c 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -4,349 +4,367 @@ import { IMap } from "../types"; // RAM costs for Netscript functions export const RamCostConstants: IMap = { - ScriptBaseRamCost: 1.6, - ScriptDomRamCost: 25, - ScriptHackRamCost: 0.1, - ScriptHackAnalyzeRamCost: 1, - ScriptGrowRamCost: 0.15, - ScriptGrowthAnalyzeRamCost: 1, - ScriptWeakenRamCost: 0.15, - ScriptScanRamCost: 0.2, - ScriptPortProgramRamCost: 0.05, - ScriptRunRamCost: 1.0, - ScriptExecRamCost: 1.3, - ScriptSpawnRamCost: 2.0, - ScriptScpRamCost: 0.6, - ScriptKillRamCost: 0.5, - ScriptHasRootAccessRamCost: 0.05, - ScriptGetHostnameRamCost: 0.05, - ScriptGetHackingLevelRamCost: 0.05, - ScriptGetMultipliersRamCost: 4.0, - ScriptGetServerRamCost: 0.1, - ScriptGetServerMaxRam: 0.05, - ScriptGetServerUsedRam: 0.05, - ScriptFileExistsRamCost: 0.1, - ScriptIsRunningRamCost: 0.1, - ScriptHacknetNodesRamCost: 4.0, - ScriptHNUpgLevelRamCost: 0.4, - ScriptHNUpgRamRamCost: 0.6, - ScriptHNUpgCoreRamCost: 0.8, - ScriptGetStockRamCost: 2.0, - ScriptBuySellStockRamCost: 2.5, - ScriptGetPurchaseServerRamCost: 0.25, - ScriptPurchaseServerRamCost: 2.25, - ScriptGetPurchasedServerLimit: 0.05, - ScriptGetPurchasedServerMaxRam: 0.05, - ScriptRoundRamCost: 0.05, - ScriptReadWriteRamCost: 1.0, - ScriptArbScriptRamCost: 1.0, - ScriptGetScriptRamCost: 0.1, - ScriptGetRunningScriptRamCost: 0.3, - ScriptGetHackTimeRamCost: 0.05, - ScriptGetFavorToDonate: 0.10, - ScriptCodingContractBaseRamCost: 10, - ScriptSleeveBaseRamCost: 4, + ScriptBaseRamCost: 1.6, + ScriptDomRamCost: 25, + ScriptHackRamCost: 0.1, + ScriptHackAnalyzeRamCost: 1, + ScriptGrowRamCost: 0.15, + ScriptGrowthAnalyzeRamCost: 1, + ScriptWeakenRamCost: 0.15, + ScriptScanRamCost: 0.2, + ScriptPortProgramRamCost: 0.05, + ScriptRunRamCost: 1.0, + ScriptExecRamCost: 1.3, + ScriptSpawnRamCost: 2.0, + ScriptScpRamCost: 0.6, + ScriptKillRamCost: 0.5, + ScriptHasRootAccessRamCost: 0.05, + ScriptGetHostnameRamCost: 0.05, + ScriptGetHackingLevelRamCost: 0.05, + ScriptGetMultipliersRamCost: 4.0, + ScriptGetServerRamCost: 0.1, + ScriptGetServerMaxRam: 0.05, + ScriptGetServerUsedRam: 0.05, + ScriptFileExistsRamCost: 0.1, + ScriptIsRunningRamCost: 0.1, + ScriptHacknetNodesRamCost: 4.0, + ScriptHNUpgLevelRamCost: 0.4, + ScriptHNUpgRamRamCost: 0.6, + ScriptHNUpgCoreRamCost: 0.8, + ScriptGetStockRamCost: 2.0, + ScriptBuySellStockRamCost: 2.5, + ScriptGetPurchaseServerRamCost: 0.25, + ScriptPurchaseServerRamCost: 2.25, + ScriptGetPurchasedServerLimit: 0.05, + ScriptGetPurchasedServerMaxRam: 0.05, + ScriptRoundRamCost: 0.05, + ScriptReadWriteRamCost: 1.0, + ScriptArbScriptRamCost: 1.0, + ScriptGetScriptRamCost: 0.1, + ScriptGetRunningScriptRamCost: 0.3, + ScriptGetHackTimeRamCost: 0.05, + ScriptGetFavorToDonate: 0.1, + ScriptCodingContractBaseRamCost: 10, + ScriptSleeveBaseRamCost: 4, - ScriptSingularityFn1RamCost: 2, - ScriptSingularityFn2RamCost: 3, - ScriptSingularityFn3RamCost: 5, + ScriptSingularityFn1RamCost: 2, + ScriptSingularityFn2RamCost: 3, + ScriptSingularityFn3RamCost: 5, - ScriptGangApiBaseRamCost: 4, + ScriptGangApiBaseRamCost: 4, - ScriptBladeburnerApiBaseRamCost: 4, -} + ScriptBladeburnerApiBaseRamCost: 4, +}; export const RamCosts: IMap = { - hacknet: { - numNodes: () => 0, - purchaseNode: () => 0, - getPurchaseNodeCost: () => 0, - getNodeStats: () => 0, - upgradeLevel: () => 0, - upgradeRam: () => 0, - upgradeCore: () => 0, - upgradeCache: () => 0, - getLevelUpgradeCost: () => 0, - getRamUpgradeCost: () => 0, - getCoreUpgradeCost: () => 0, - getCacheUpgradeCost: () => 0, - numHashes: () => 0, - hashCost: () => 0, - spendHashes: () => 0, - }, - sprintf: () => 0, - vsprintf: () => 0, - scan: () => RamCostConstants.ScriptScanRamCost, - hack: () => RamCostConstants.ScriptHackRamCost, - hackAnalyzeThreads: () => RamCostConstants.ScriptHackAnalyzeRamCost, - hackAnalyzePercent: () => RamCostConstants.ScriptHackAnalyzeRamCost, - hackChance: () => RamCostConstants.ScriptHackAnalyzeRamCost, - sleep: () => 0, - grow: () => RamCostConstants.ScriptGrowRamCost, - growthAnalyze: () => RamCostConstants.ScriptGrowthAnalyzeRamCost, - weaken: () => RamCostConstants.ScriptWeakenRamCost, - print: () => 0, - tprint: () => 0, - clearLog: () => 0, - disableLog: () => 0, - enableLog: () => 0, - isLogEnabled: () => 0, - getScriptLogs: () => 0, - nuke: () => RamCostConstants.ScriptPortProgramRamCost, - brutessh: () => RamCostConstants.ScriptPortProgramRamCost, - ftpcrack: () => RamCostConstants.ScriptPortProgramRamCost, - relaysmtp: () => RamCostConstants.ScriptPortProgramRamCost, - httpworm: () => RamCostConstants.ScriptPortProgramRamCost, - sqlinject: () => RamCostConstants.ScriptPortProgramRamCost, - run: () => RamCostConstants.ScriptRunRamCost, - exec: () => RamCostConstants.ScriptExecRamCost, - spawn: () => RamCostConstants.ScriptSpawnRamCost, - kill: () => RamCostConstants.ScriptKillRamCost, - killall: () => RamCostConstants.ScriptKillRamCost, - exit: () => 0, - scp: () => RamCostConstants.ScriptScpRamCost, - ls: () => RamCostConstants.ScriptScanRamCost, - ps: () => RamCostConstants.ScriptScanRamCost, - hasRootAccess: () => RamCostConstants.ScriptHasRootAccessRamCost, - getIp: () => RamCostConstants.ScriptGetHostnameRamCost, - getHostname: () => RamCostConstants.ScriptGetHostnameRamCost, - getHackingLevel: () => RamCostConstants.ScriptGetHackingLevelRamCost, - getHackingMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost, - getHacknetMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost, - getBitNodeMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost, - getServer: () => RamCostConstants.ScriptGetMultipliersRamCost / 2, - getServerMoneyAvailable: () => RamCostConstants.ScriptGetServerRamCost, - getServerSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost, - getServerBaseSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost, - getServerMinSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost, - getServerRequiredHackingLevel: () => RamCostConstants.ScriptGetServerRamCost, - getServerMaxMoney: () => RamCostConstants.ScriptGetServerRamCost, - getServerGrowth: () => RamCostConstants.ScriptGetServerRamCost, - getServerNumPortsRequired: () => RamCostConstants.ScriptGetServerRamCost, - getServerRam: () => RamCostConstants.ScriptGetServerRamCost, - getServerMaxRam: () => RamCostConstants.ScriptGetServerMaxRam, - getServerUsedRam: () => RamCostConstants.ScriptGetServerUsedRam, - serverExists: () => RamCostConstants.ScriptGetServerRamCost, - fileExists: () => RamCostConstants.ScriptFileExistsRamCost, - isRunning: () => RamCostConstants.ScriptIsRunningRamCost, - getStockSymbols: () => RamCostConstants.ScriptGetStockRamCost, - getStockPrice: () => RamCostConstants.ScriptGetStockRamCost, - getStockAskPrice: () => RamCostConstants.ScriptGetStockRamCost, - getStockBidPrice: () => RamCostConstants.ScriptGetStockRamCost, - getStockPosition: () => RamCostConstants.ScriptGetStockRamCost, - getStockMaxShares: () => RamCostConstants.ScriptGetStockRamCost, - getStockPurchaseCost: () => RamCostConstants.ScriptGetStockRamCost, - getStockSaleGain: () => RamCostConstants.ScriptGetStockRamCost, - buyStock: () => RamCostConstants.ScriptBuySellStockRamCost, - sellStock: () => RamCostConstants.ScriptBuySellStockRamCost, - shortStock: () => RamCostConstants.ScriptBuySellStockRamCost, - sellShort: () => RamCostConstants.ScriptBuySellStockRamCost, - placeOrder: () => RamCostConstants.ScriptBuySellStockRamCost, - cancelOrder: () => RamCostConstants.ScriptBuySellStockRamCost, - getOrders: () => RamCostConstants.ScriptBuySellStockRamCost, - getStockVolatility: () => RamCostConstants.ScriptBuySellStockRamCost, - getStockForecast: () => RamCostConstants.ScriptBuySellStockRamCost, - purchase4SMarketData: () => RamCostConstants.ScriptBuySellStockRamCost, - purchase4SMarketDataTixApi: () => RamCostConstants.ScriptBuySellStockRamCost, - getPurchasedServerLimit: () => RamCostConstants.ScriptGetPurchasedServerLimit, - getPurchasedServerMaxRam: () => RamCostConstants.ScriptGetPurchasedServerMaxRam, - getPurchasedServerCost: () => RamCostConstants.ScriptGetPurchaseServerRamCost, - purchaseServer: () => RamCostConstants.ScriptPurchaseServerRamCost, - deleteServer: () => RamCostConstants.ScriptPurchaseServerRamCost, - getPurchasedServers: () => RamCostConstants.ScriptPurchaseServerRamCost, - write: () => RamCostConstants.ScriptReadWriteRamCost, - tryWrite: () => RamCostConstants.ScriptReadWriteRamCost, - read: () => RamCostConstants.ScriptReadWriteRamCost, - peek: () => RamCostConstants.ScriptReadWriteRamCost, - clear: () => RamCostConstants.ScriptReadWriteRamCost, - getPortHandle: () => RamCostConstants.ScriptReadWriteRamCost * 10, - rm: () => RamCostConstants.ScriptReadWriteRamCost, - scriptRunning: () => RamCostConstants.ScriptArbScriptRamCost, - scriptKill: () => RamCostConstants.ScriptArbScriptRamCost, - getScriptName: () => 0, - getScriptRam: () => RamCostConstants.ScriptGetScriptRamCost, - getHackTime: () => RamCostConstants.ScriptGetHackTimeRamCost, - getGrowTime: () => RamCostConstants.ScriptGetHackTimeRamCost, - getWeakenTime: () => RamCostConstants.ScriptGetHackTimeRamCost, - getScriptIncome: () => RamCostConstants.ScriptGetScriptRamCost, - getScriptExpGain: () => RamCostConstants.ScriptGetScriptRamCost, - getRunningScript: () => RamCostConstants.ScriptGetRunningScriptRamCost, - nFormat: () => 0, - getTimeSinceLastAug: () => RamCostConstants.ScriptGetHackTimeRamCost, - prompt: () => 0, - wget: () => 0, - getFavorToDonate: () => RamCostConstants.ScriptGetFavorToDonate, + hacknet: { + numNodes: () => 0, + purchaseNode: () => 0, + getPurchaseNodeCost: () => 0, + getNodeStats: () => 0, + upgradeLevel: () => 0, + upgradeRam: () => 0, + upgradeCore: () => 0, + upgradeCache: () => 0, + getLevelUpgradeCost: () => 0, + getRamUpgradeCost: () => 0, + getCoreUpgradeCost: () => 0, + getCacheUpgradeCost: () => 0, + numHashes: () => 0, + hashCost: () => 0, + spendHashes: () => 0, + }, + sprintf: () => 0, + vsprintf: () => 0, + scan: () => RamCostConstants.ScriptScanRamCost, + hack: () => RamCostConstants.ScriptHackRamCost, + hackAnalyzeThreads: () => RamCostConstants.ScriptHackAnalyzeRamCost, + hackAnalyzePercent: () => RamCostConstants.ScriptHackAnalyzeRamCost, + hackChance: () => RamCostConstants.ScriptHackAnalyzeRamCost, + sleep: () => 0, + grow: () => RamCostConstants.ScriptGrowRamCost, + growthAnalyze: () => RamCostConstants.ScriptGrowthAnalyzeRamCost, + weaken: () => RamCostConstants.ScriptWeakenRamCost, + print: () => 0, + tprint: () => 0, + clearLog: () => 0, + disableLog: () => 0, + enableLog: () => 0, + isLogEnabled: () => 0, + getScriptLogs: () => 0, + nuke: () => RamCostConstants.ScriptPortProgramRamCost, + brutessh: () => RamCostConstants.ScriptPortProgramRamCost, + ftpcrack: () => RamCostConstants.ScriptPortProgramRamCost, + relaysmtp: () => RamCostConstants.ScriptPortProgramRamCost, + httpworm: () => RamCostConstants.ScriptPortProgramRamCost, + sqlinject: () => RamCostConstants.ScriptPortProgramRamCost, + run: () => RamCostConstants.ScriptRunRamCost, + exec: () => RamCostConstants.ScriptExecRamCost, + spawn: () => RamCostConstants.ScriptSpawnRamCost, + kill: () => RamCostConstants.ScriptKillRamCost, + killall: () => RamCostConstants.ScriptKillRamCost, + exit: () => 0, + scp: () => RamCostConstants.ScriptScpRamCost, + ls: () => RamCostConstants.ScriptScanRamCost, + ps: () => RamCostConstants.ScriptScanRamCost, + hasRootAccess: () => RamCostConstants.ScriptHasRootAccessRamCost, + getIp: () => RamCostConstants.ScriptGetHostnameRamCost, + getHostname: () => RamCostConstants.ScriptGetHostnameRamCost, + getHackingLevel: () => RamCostConstants.ScriptGetHackingLevelRamCost, + getHackingMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost, + getHacknetMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost, + getBitNodeMultipliers: () => RamCostConstants.ScriptGetMultipliersRamCost, + getServer: () => RamCostConstants.ScriptGetMultipliersRamCost / 2, + getServerMoneyAvailable: () => RamCostConstants.ScriptGetServerRamCost, + getServerSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost, + getServerBaseSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost, + getServerMinSecurityLevel: () => RamCostConstants.ScriptGetServerRamCost, + getServerRequiredHackingLevel: () => RamCostConstants.ScriptGetServerRamCost, + getServerMaxMoney: () => RamCostConstants.ScriptGetServerRamCost, + getServerGrowth: () => RamCostConstants.ScriptGetServerRamCost, + getServerNumPortsRequired: () => RamCostConstants.ScriptGetServerRamCost, + getServerRam: () => RamCostConstants.ScriptGetServerRamCost, + getServerMaxRam: () => RamCostConstants.ScriptGetServerMaxRam, + getServerUsedRam: () => RamCostConstants.ScriptGetServerUsedRam, + serverExists: () => RamCostConstants.ScriptGetServerRamCost, + fileExists: () => RamCostConstants.ScriptFileExistsRamCost, + isRunning: () => RamCostConstants.ScriptIsRunningRamCost, + getStockSymbols: () => RamCostConstants.ScriptGetStockRamCost, + getStockPrice: () => RamCostConstants.ScriptGetStockRamCost, + getStockAskPrice: () => RamCostConstants.ScriptGetStockRamCost, + getStockBidPrice: () => RamCostConstants.ScriptGetStockRamCost, + getStockPosition: () => RamCostConstants.ScriptGetStockRamCost, + getStockMaxShares: () => RamCostConstants.ScriptGetStockRamCost, + getStockPurchaseCost: () => RamCostConstants.ScriptGetStockRamCost, + getStockSaleGain: () => RamCostConstants.ScriptGetStockRamCost, + buyStock: () => RamCostConstants.ScriptBuySellStockRamCost, + sellStock: () => RamCostConstants.ScriptBuySellStockRamCost, + shortStock: () => RamCostConstants.ScriptBuySellStockRamCost, + sellShort: () => RamCostConstants.ScriptBuySellStockRamCost, + placeOrder: () => RamCostConstants.ScriptBuySellStockRamCost, + cancelOrder: () => RamCostConstants.ScriptBuySellStockRamCost, + getOrders: () => RamCostConstants.ScriptBuySellStockRamCost, + getStockVolatility: () => RamCostConstants.ScriptBuySellStockRamCost, + getStockForecast: () => RamCostConstants.ScriptBuySellStockRamCost, + purchase4SMarketData: () => RamCostConstants.ScriptBuySellStockRamCost, + purchase4SMarketDataTixApi: () => RamCostConstants.ScriptBuySellStockRamCost, + getPurchasedServerLimit: () => RamCostConstants.ScriptGetPurchasedServerLimit, + getPurchasedServerMaxRam: () => + RamCostConstants.ScriptGetPurchasedServerMaxRam, + getPurchasedServerCost: () => RamCostConstants.ScriptGetPurchaseServerRamCost, + purchaseServer: () => RamCostConstants.ScriptPurchaseServerRamCost, + deleteServer: () => RamCostConstants.ScriptPurchaseServerRamCost, + getPurchasedServers: () => RamCostConstants.ScriptPurchaseServerRamCost, + write: () => RamCostConstants.ScriptReadWriteRamCost, + tryWrite: () => RamCostConstants.ScriptReadWriteRamCost, + read: () => RamCostConstants.ScriptReadWriteRamCost, + peek: () => RamCostConstants.ScriptReadWriteRamCost, + clear: () => RamCostConstants.ScriptReadWriteRamCost, + getPortHandle: () => RamCostConstants.ScriptReadWriteRamCost * 10, + rm: () => RamCostConstants.ScriptReadWriteRamCost, + scriptRunning: () => RamCostConstants.ScriptArbScriptRamCost, + scriptKill: () => RamCostConstants.ScriptArbScriptRamCost, + getScriptName: () => 0, + getScriptRam: () => RamCostConstants.ScriptGetScriptRamCost, + getHackTime: () => RamCostConstants.ScriptGetHackTimeRamCost, + getGrowTime: () => RamCostConstants.ScriptGetHackTimeRamCost, + getWeakenTime: () => RamCostConstants.ScriptGetHackTimeRamCost, + getScriptIncome: () => RamCostConstants.ScriptGetScriptRamCost, + getScriptExpGain: () => RamCostConstants.ScriptGetScriptRamCost, + getRunningScript: () => RamCostConstants.ScriptGetRunningScriptRamCost, + nFormat: () => 0, + getTimeSinceLastAug: () => RamCostConstants.ScriptGetHackTimeRamCost, + prompt: () => 0, + wget: () => 0, + getFavorToDonate: () => RamCostConstants.ScriptGetFavorToDonate, - // Singularity Functions - universityCourse: () => RamCostConstants.ScriptSingularityFn1RamCost, - gymWorkout: () => RamCostConstants.ScriptSingularityFn1RamCost, - travelToCity: () => RamCostConstants.ScriptSingularityFn1RamCost, - purchaseTor: () => RamCostConstants.ScriptSingularityFn1RamCost, - purchaseProgram: () => RamCostConstants.ScriptSingularityFn1RamCost, - getCurrentServer: () => RamCostConstants.ScriptSingularityFn1RamCost, - connect: () => RamCostConstants.ScriptSingularityFn1RamCost, - manualHack: () => RamCostConstants.ScriptSingularityFn1RamCost, - installBackdoor: () => RamCostConstants.ScriptSingularityFn1RamCost, - getStats: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, - getCharacterInformation: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, - getPlayer: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, - hospitalize: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, - isBusy: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, - stopAction: () => RamCostConstants.ScriptSingularityFn1RamCost / 2, - upgradeHomeRam: () => RamCostConstants.ScriptSingularityFn2RamCost, - getUpgradeHomeRamCost: () => RamCostConstants.ScriptSingularityFn2RamCost / 2, - workForCompany: () => RamCostConstants.ScriptSingularityFn2RamCost, - applyToCompany: () => RamCostConstants.ScriptSingularityFn2RamCost, - getCompanyRep: () => RamCostConstants.ScriptSingularityFn2RamCost / 3, - getCompanyFavor: () => RamCostConstants.ScriptSingularityFn2RamCost / 3, - getCompanyFavorGain: () => RamCostConstants.ScriptSingularityFn2RamCost / 4, - checkFactionInvitations: () => RamCostConstants.ScriptSingularityFn2RamCost, - joinFaction: () => RamCostConstants.ScriptSingularityFn2RamCost, - workForFaction: () => RamCostConstants.ScriptSingularityFn2RamCost, - getFactionRep: () => RamCostConstants.ScriptSingularityFn2RamCost / 3, - getFactionFavor: () => RamCostConstants.ScriptSingularityFn2RamCost / 3, - getFactionFavorGain: () => RamCostConstants.ScriptSingularityFn2RamCost / 4, - donateToFaction: () => RamCostConstants.ScriptSingularityFn3RamCost, - createProgram: () => RamCostConstants.ScriptSingularityFn3RamCost, - commitCrime: () => RamCostConstants.ScriptSingularityFn3RamCost, - getCrimeChance: () => RamCostConstants.ScriptSingularityFn3RamCost, - getCrimeStats: () => RamCostConstants.ScriptSingularityFn3RamCost, - getOwnedAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost, - getOwnedSourceFiles: () => RamCostConstants.ScriptSingularityFn3RamCost, - getAugmentationsFromFaction: () => RamCostConstants.ScriptSingularityFn3RamCost, - getAugmentationPrereq: () => RamCostConstants.ScriptSingularityFn3RamCost, - getAugmentationCost: () => RamCostConstants.ScriptSingularityFn3RamCost, - getAugmentationStats: () => RamCostConstants.ScriptSingularityFn3RamCost, - purchaseAugmentation: () => RamCostConstants.ScriptSingularityFn3RamCost, - softReset: () => RamCostConstants.ScriptSingularityFn3RamCost, - installAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost, + // Singularity Functions + universityCourse: () => RamCostConstants.ScriptSingularityFn1RamCost, + gymWorkout: () => RamCostConstants.ScriptSingularityFn1RamCost, + travelToCity: () => RamCostConstants.ScriptSingularityFn1RamCost, + purchaseTor: () => RamCostConstants.ScriptSingularityFn1RamCost, + purchaseProgram: () => RamCostConstants.ScriptSingularityFn1RamCost, + getCurrentServer: () => RamCostConstants.ScriptSingularityFn1RamCost, + connect: () => RamCostConstants.ScriptSingularityFn1RamCost, + manualHack: () => RamCostConstants.ScriptSingularityFn1RamCost, + installBackdoor: () => RamCostConstants.ScriptSingularityFn1RamCost, + getStats: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, + getCharacterInformation: () => + RamCostConstants.ScriptSingularityFn1RamCost / 4, + getPlayer: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, + hospitalize: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, + isBusy: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, + stopAction: () => RamCostConstants.ScriptSingularityFn1RamCost / 2, + upgradeHomeRam: () => RamCostConstants.ScriptSingularityFn2RamCost, + getUpgradeHomeRamCost: () => RamCostConstants.ScriptSingularityFn2RamCost / 2, + workForCompany: () => RamCostConstants.ScriptSingularityFn2RamCost, + applyToCompany: () => RamCostConstants.ScriptSingularityFn2RamCost, + getCompanyRep: () => RamCostConstants.ScriptSingularityFn2RamCost / 3, + getCompanyFavor: () => RamCostConstants.ScriptSingularityFn2RamCost / 3, + getCompanyFavorGain: () => RamCostConstants.ScriptSingularityFn2RamCost / 4, + checkFactionInvitations: () => RamCostConstants.ScriptSingularityFn2RamCost, + joinFaction: () => RamCostConstants.ScriptSingularityFn2RamCost, + workForFaction: () => RamCostConstants.ScriptSingularityFn2RamCost, + getFactionRep: () => RamCostConstants.ScriptSingularityFn2RamCost / 3, + getFactionFavor: () => RamCostConstants.ScriptSingularityFn2RamCost / 3, + getFactionFavorGain: () => RamCostConstants.ScriptSingularityFn2RamCost / 4, + donateToFaction: () => RamCostConstants.ScriptSingularityFn3RamCost, + createProgram: () => RamCostConstants.ScriptSingularityFn3RamCost, + commitCrime: () => RamCostConstants.ScriptSingularityFn3RamCost, + getCrimeChance: () => RamCostConstants.ScriptSingularityFn3RamCost, + getCrimeStats: () => RamCostConstants.ScriptSingularityFn3RamCost, + getOwnedAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost, + getOwnedSourceFiles: () => RamCostConstants.ScriptSingularityFn3RamCost, + getAugmentationsFromFaction: () => + RamCostConstants.ScriptSingularityFn3RamCost, + getAugmentationPrereq: () => RamCostConstants.ScriptSingularityFn3RamCost, + getAugmentationCost: () => RamCostConstants.ScriptSingularityFn3RamCost, + getAugmentationStats: () => RamCostConstants.ScriptSingularityFn3RamCost, + purchaseAugmentation: () => RamCostConstants.ScriptSingularityFn3RamCost, + softReset: () => RamCostConstants.ScriptSingularityFn3RamCost, + installAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost, - // Gang API - gang : { - createGang: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, - inGang: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, - getMemberNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, - getGangInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, - getOtherGangInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, - getMemberInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, - canRecruitMember: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, - recruitMember: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, - getTaskNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, - getTaskStats: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, - setMemberTask: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, - getEquipmentNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, - getEquipmentCost: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, - getEquipmentType: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, - getEquipmentStats: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, - purchaseEquipment: () => RamCostConstants.ScriptGangApiBaseRamCost, - ascendMember: () => RamCostConstants.ScriptGangApiBaseRamCost, - setTerritoryWarfare: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, - getChanceToWinClash: () => RamCostConstants.ScriptGangApiBaseRamCost, - getBonusTime: () => 0, - }, + // Gang API + gang: { + createGang: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, + inGang: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, + getMemberNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, + getGangInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, + getOtherGangInformation: () => + RamCostConstants.ScriptGangApiBaseRamCost / 2, + getMemberInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, + canRecruitMember: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, + recruitMember: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, + getTaskNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, + getTaskStats: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, + setMemberTask: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, + getEquipmentNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4, + getEquipmentCost: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, + getEquipmentType: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, + getEquipmentStats: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, + purchaseEquipment: () => RamCostConstants.ScriptGangApiBaseRamCost, + ascendMember: () => RamCostConstants.ScriptGangApiBaseRamCost, + setTerritoryWarfare: () => RamCostConstants.ScriptGangApiBaseRamCost / 2, + getChanceToWinClash: () => RamCostConstants.ScriptGangApiBaseRamCost, + getBonusTime: () => 0, + }, - // Bladeburner API - bladeburner : { - getContractNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10, - getOperationNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10, - getBlackOpNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10, - getBlackOpRank: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2, - getGeneralActionNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10, - getSkillNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10, - startAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - stopBladeburnerAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2, - getCurrentAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 4, - getActionTime: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getActionEstimatedSuccessChance: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getActionRepGain: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getActionCountRemaining: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getActionMaxLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getActionCurrentLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getActionAutolevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - setActionAutolevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - setActionLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getRank: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getSkillPoints: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getSkillLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getSkillUpgradeCost: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - upgradeSkill: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getTeamSize: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - setTeamSize: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getCityEstimatedPopulation: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getCityEstimatedCommunities: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getCityChaos: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getCity: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - switchCity: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getStamina: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - joinBladeburnerFaction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - joinBladeburnerDivision: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, - getBonusTime: () => 0, - }, + // Bladeburner API + bladeburner: { + getContractNames: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10, + getOperationNames: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10, + getBlackOpNames: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10, + getBlackOpRank: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2, + getGeneralActionNames: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10, + getSkillNames: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost / 10, + startAction: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + stopBladeburnerAction: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost / 2, + getCurrentAction: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost / 4, + getActionTime: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getActionEstimatedSuccessChance: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getActionRepGain: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getActionCountRemaining: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getActionMaxLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getActionCurrentLevel: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getActionAutolevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + setActionAutolevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + setActionLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getRank: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getSkillPoints: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getSkillLevel: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getSkillUpgradeCost: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + upgradeSkill: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getTeamSize: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + setTeamSize: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getCityEstimatedPopulation: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getCityEstimatedCommunities: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getCityChaos: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getCity: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + switchCity: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getStamina: () => RamCostConstants.ScriptBladeburnerApiBaseRamCost, + joinBladeburnerFaction: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost, + joinBladeburnerDivision: () => + RamCostConstants.ScriptBladeburnerApiBaseRamCost, + getBonusTime: () => 0, + }, - // Coding Contract API - codingcontract : { - attempt: () => RamCostConstants.ScriptCodingContractBaseRamCost, - getContractType: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2, - getData: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2, - getDescription: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2, - getNumTriesRemaining: () => RamCostConstants.ScriptCodingContractBaseRamCost / 5, - }, + // Coding Contract API + codingcontract: { + attempt: () => RamCostConstants.ScriptCodingContractBaseRamCost, + getContractType: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2, + getData: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2, + getDescription: () => RamCostConstants.ScriptCodingContractBaseRamCost / 2, + getNumTriesRemaining: () => + RamCostConstants.ScriptCodingContractBaseRamCost / 5, + }, - // Duplicate Sleeve API - sleeve : { - getNumSleeves: () => RamCostConstants.ScriptSleeveBaseRamCost, - setToShockRecovery: () => RamCostConstants.ScriptSleeveBaseRamCost, - setToSynchronize: () => RamCostConstants.ScriptSleeveBaseRamCost, - setToCommitCrime: () => RamCostConstants.ScriptSleeveBaseRamCost, - setToUniversityCourse: () => RamCostConstants.ScriptSleeveBaseRamCost, - travel: () => RamCostConstants.ScriptSleeveBaseRamCost, - setToCompanyWork: () => RamCostConstants.ScriptSleeveBaseRamCost, - setToFactionWork: () => RamCostConstants.ScriptSleeveBaseRamCost, - setToGymWorkout: () => RamCostConstants.ScriptSleeveBaseRamCost, - getSleeveStats: () => RamCostConstants.ScriptSleeveBaseRamCost, - getTask: () => RamCostConstants.ScriptSleeveBaseRamCost, - getInformation: () => RamCostConstants.ScriptSleeveBaseRamCost, - getSleeveAugmentations: () => RamCostConstants.ScriptSleeveBaseRamCost, - getSleevePurchasableAugs: () => RamCostConstants.ScriptSleeveBaseRamCost, - purchaseSleeveAug: () => RamCostConstants.ScriptSleeveBaseRamCost, - }, + // Duplicate Sleeve API + sleeve: { + getNumSleeves: () => RamCostConstants.ScriptSleeveBaseRamCost, + setToShockRecovery: () => RamCostConstants.ScriptSleeveBaseRamCost, + setToSynchronize: () => RamCostConstants.ScriptSleeveBaseRamCost, + setToCommitCrime: () => RamCostConstants.ScriptSleeveBaseRamCost, + setToUniversityCourse: () => RamCostConstants.ScriptSleeveBaseRamCost, + travel: () => RamCostConstants.ScriptSleeveBaseRamCost, + setToCompanyWork: () => RamCostConstants.ScriptSleeveBaseRamCost, + setToFactionWork: () => RamCostConstants.ScriptSleeveBaseRamCost, + setToGymWorkout: () => RamCostConstants.ScriptSleeveBaseRamCost, + getSleeveStats: () => RamCostConstants.ScriptSleeveBaseRamCost, + getTask: () => RamCostConstants.ScriptSleeveBaseRamCost, + getInformation: () => RamCostConstants.ScriptSleeveBaseRamCost, + getSleeveAugmentations: () => RamCostConstants.ScriptSleeveBaseRamCost, + getSleevePurchasableAugs: () => RamCostConstants.ScriptSleeveBaseRamCost, + purchaseSleeveAug: () => RamCostConstants.ScriptSleeveBaseRamCost, + }, - heart: { - // Easter egg function - break : () => 0, - }, -} + heart: { + // Easter egg function + break: () => 0, + }, +}; export function getRamCost(...args: string[]): number { - if (args.length === 0) { - console.warn(`No arguments passed to getRamCost()`); - return 0; - } + if (args.length === 0) { + console.warn(`No arguments passed to getRamCost()`); + return 0; + } - let curr = RamCosts[args[0]]; - for (let i = 1; i < args.length; ++i) { - if (curr == null) { - console.warn(`Invalid function passed to getRamCost: ${args}`); - return 0; - } - - const currType = typeof curr; - if (currType === "function" || currType === "number") { - break; - } - - curr = curr[args[i]]; + let curr = RamCosts[args[0]]; + for (let i = 1; i < args.length; ++i) { + if (curr == null) { + console.warn(`Invalid function passed to getRamCost: ${args}`); + return 0; } const currType = typeof curr; - if (currType === "function") { - return curr(); + if (currType === "function" || currType === "number") { + break; } - if (currType === "number") { - return curr; - } + curr = curr[args[i]]; + } - console.warn(`Unexpected type (${currType}) for value [${args}]`); - return 0; + const currType = typeof curr; + if (currType === "function") { + return curr(); + } + + if (currType === "number") { + return curr; + } + + console.warn(`Unexpected type (${currType}) for value [${args}]`); + return 0; } diff --git a/src/Netscript/WorkerScript.ts b/src/Netscript/WorkerScript.ts index 9500f70d6..3a55f829e 100644 --- a/src/Netscript/WorkerScript.ts +++ b/src/Netscript/WorkerScript.ts @@ -16,193 +16,206 @@ import { BaseServer } from "../Server/BaseServer"; import { IMap } from "../types"; export class WorkerScript { - /** - * Script's arguments - */ - args: any[]; + /** + * Script's arguments + */ + args: any[]; - /** - * Copy of the script's code - */ - code = ""; + /** + * Copy of the script's code + */ + code = ""; - /** - * Holds the timeoutID (numeric value) for whenever this script is blocked by a - * timed Netscript function. i.e. Holds the return value of setTimeout() - */ - delay: number | null = null; + /** + * Holds the timeoutID (numeric value) for whenever this script is blocked by a + * timed Netscript function. i.e. Holds the return value of setTimeout() + */ + delay: number | null = null; - /** - * Holds the Promise resolve() function for when the script is "blocked" by an async op - */ - delayResolve?: () => void; + /** + * Holds the Promise resolve() function for when the script is "blocked" by an async op + */ + delayResolve?: () => void; - /** - * Stores names of all functions that have logging disabled - */ - disableLogs: IMap = {}; + /** + * Stores names of all functions that have logging disabled + */ + disableLogs: IMap = {}; - /** - * Used for dynamic RAM calculation. Stores names of all functions that have - * already been checked by this script. - * TODO: Could probably just combine this with loadedFns? - */ - dynamicLoadedFns: IMap = {}; + /** + * Used for dynamic RAM calculation. Stores names of all functions that have + * already been checked by this script. + * TODO: Could probably just combine this with loadedFns? + */ + dynamicLoadedFns: IMap = {}; - /** - * Tracks dynamic RAM usage - */ - dynamicRamUsage: number = RamCostConstants.ScriptBaseRamCost; + /** + * Tracks dynamic RAM usage + */ + dynamicRamUsage: number = RamCostConstants.ScriptBaseRamCost; - /** - * Netscript Environment for this script - */ - env: Environment; + /** + * Netscript Environment for this script + */ + env: Environment; - /** - * Status message in case of script error. Currently unused I think - */ - errorMessage = ""; + /** + * Status message in case of script error. Currently unused I think + */ + errorMessage = ""; - /** - * Used for static RAM calculation. Stores names of all functions that have - * already been checked by this script - */ - loadedFns: IMap = {}; + /** + * Used for static RAM calculation. Stores names of all functions that have + * already been checked by this script + */ + loadedFns: IMap = {}; - /** - * Filename of script - */ - name: string; + /** + * Filename of script + */ + name: string; - /** - * Script's output/return value. Currently not used or implemented - */ - output = ""; + /** + * Script's output/return value. Currently not used or implemented + */ + output = ""; - /** - * Process ID. Must be an integer. Used for efficient script - * killing and removal. - */ - pid: number; + /** + * Process ID. Must be an integer. Used for efficient script + * killing and removal. + */ + pid: number; - /** - * Script's Static RAM usage. Equivalent to underlying script's RAM usage - */ - ramUsage = 0; + /** + * Script's Static RAM usage. Equivalent to underlying script's RAM usage + */ + ramUsage = 0; - /** - * Whether or not this workerScript is currently running - */ - running = false; + /** + * Whether or not this workerScript is currently running + */ + running = false; - /** - * Reference to underlying RunningScript object - */ - scriptRef: RunningScript; + /** + * Reference to underlying RunningScript object + */ + scriptRef: RunningScript; - /** - * IP Address on which this script is running - */ - serverIp: string; + /** + * IP Address on which this script is running + */ + serverIp: string; - constructor(runningScriptObj: RunningScript, pid: number, nsFuncsGenerator?: (ws: WorkerScript) => any) { - this.name = runningScriptObj.filename; - this.serverIp = runningScriptObj.server; + constructor( + runningScriptObj: RunningScript, + pid: number, + nsFuncsGenerator?: (ws: WorkerScript) => any, + ) { + this.name = runningScriptObj.filename; + this.serverIp = runningScriptObj.server; - const sanitizedPid = Math.round(pid); - if (typeof sanitizedPid !== "number" || isNaN(sanitizedPid)) { - throw new Error(`Invalid PID when constructing WorkerScript: ${pid}`); - } - this.pid = sanitizedPid; - runningScriptObj.pid = sanitizedPid; + const sanitizedPid = Math.round(pid); + if (typeof sanitizedPid !== "number" || isNaN(sanitizedPid)) { + throw new Error(`Invalid PID when constructing WorkerScript: ${pid}`); + } + this.pid = sanitizedPid; + runningScriptObj.pid = sanitizedPid; - // Get the underlying script's code - const server = AllServers[this.serverIp]; - if (server == null) { - throw new Error(`WorkerScript constructed with invalid server ip: ${this.serverIp}`); - } - let found = false; - for (let i = 0; i < server.scripts.length; ++i) { - if (server.scripts[i].filename === this.name) { - found = true; - this.code = server.scripts[i].code; - } - } - if (!found) { - throw new Error(`WorkerScript constructed with invalid script filename: ${this.name}`); - } - - this.env = new Environment(null); - if (typeof nsFuncsGenerator === "function") { - this.env.vars = nsFuncsGenerator(this); - } - this.env.set("args", runningScriptObj.args.slice()); - - this.scriptRef = runningScriptObj; - this.args = runningScriptObj.args.slice(); + // Get the underlying script's code + const server = AllServers[this.serverIp]; + if (server == null) { + throw new Error( + `WorkerScript constructed with invalid server ip: ${this.serverIp}`, + ); + } + let found = false; + for (let i = 0; i < server.scripts.length; ++i) { + if (server.scripts[i].filename === this.name) { + found = true; + this.code = server.scripts[i].code; + } + } + if (!found) { + throw new Error( + `WorkerScript constructed with invalid script filename: ${this.name}`, + ); } - /** - * Returns the Server on which this script is running - */ - getServer(): BaseServer { - const server = AllServers[this.serverIp]; - if(server == null) throw new Error(`Script ${this.name} pid ${this.pid} is running on non-existent server?`); - return server; + this.env = new Environment(null); + if (typeof nsFuncsGenerator === "function") { + this.env.vars = nsFuncsGenerator(this); + } + this.env.set("args", runningScriptObj.args.slice()); + + this.scriptRef = runningScriptObj; + this.args = runningScriptObj.args.slice(); + } + + /** + * Returns the Server on which this script is running + */ + getServer(): BaseServer { + const server = AllServers[this.serverIp]; + if (server == null) + throw new Error( + `Script ${this.name} pid ${this.pid} is running on non-existent server?`, + ); + return server; + } + + /** + * Returns the Script object for the underlying script. + * Returns null if it cannot be found (which would be a bug) + */ + getScript(): Script | null { + const server = this.getServer(); + for (let i = 0; i < server.scripts.length; ++i) { + if (server.scripts[i].filename === this.name) { + return server.scripts[i]; + } } - /** - * Returns the Script object for the underlying script. - * Returns null if it cannot be found (which would be a bug) - */ - getScript(): Script | null { - const server = this.getServer(); - for (let i = 0; i < server.scripts.length; ++i) { - if (server.scripts[i].filename === this.name) { - return server.scripts[i]; - } - } + console.error( + "Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong", + ); + return null; + } - console.error("Failed to find underlying Script object in WorkerScript.getScript(). This probably means somethings wrong"); - return null; + /** + * Returns the script with the specified filename on the specified server, + * or null if it cannot be found + */ + getScriptOnServer(fn: string, server: BaseServer): Script | null { + if (server == null) { + server = this.getServer(); } - /** - * Returns the script with the specified filename on the specified server, - * or null if it cannot be found - */ - getScriptOnServer(fn: string, server: BaseServer): Script | null { - if (server == null) { - server = this.getServer(); - } - - for (let i = 0; i < server.scripts.length; ++i) { - if (server.scripts[i].filename === fn) { - return server.scripts[i]; - } - } - - return null; + for (let i = 0; i < server.scripts.length; ++i) { + if (server.scripts[i].filename === fn) { + return server.scripts[i]; + } } - shouldLog(fn: string): boolean { - return (this.disableLogs[fn] == null); - } + return null; + } - log(func: string, txt: string): void { - if(this.shouldLog(func)) { - if(func && txt){ - this.scriptRef.log(`${func}: ${txt}`); - } else if(func) { - this.scriptRef.log(func); - } else { - this.scriptRef.log(txt); - } - } - } + shouldLog(fn: string): boolean { + return this.disableLogs[fn] == null; + } - print(txt: string): void { + log(func: string, txt: string): void { + if (this.shouldLog(func)) { + if (func && txt) { + this.scriptRef.log(`${func}: ${txt}`); + } else if (func) { + this.scriptRef.log(func); + } else { this.scriptRef.log(txt); + } } + } + + print(txt: string): void { + this.scriptRef.log(txt); + } } diff --git a/src/Netscript/killWorkerScript.ts b/src/Netscript/killWorkerScript.ts index 33c91b963..2bdddfb5e 100644 --- a/src/Netscript/killWorkerScript.ts +++ b/src/Netscript/killWorkerScript.ts @@ -12,59 +12,74 @@ import { AllServers } from "../Server/AllServers"; import { compareArrays } from "../../utils/helpers/compareArrays"; import { roundToTwo } from "../../utils/helpers/roundToTwo"; -export function killWorkerScript(runningScriptObj: RunningScript, serverIp: string, rerenderUi: boolean): boolean; +export function killWorkerScript( + runningScriptObj: RunningScript, + serverIp: string, + rerenderUi: boolean, +): boolean; export function killWorkerScript(workerScript: WorkerScript): boolean; export function killWorkerScript(pid: number): boolean; -export function killWorkerScript(script: RunningScript | WorkerScript | number, serverIp?: string, rerenderUi?: boolean): boolean { - if (rerenderUi == null || typeof rerenderUi !== "boolean") { - rerenderUi = true; +export function killWorkerScript( + script: RunningScript | WorkerScript | number, + serverIp?: string, + rerenderUi?: boolean, +): boolean { + if (rerenderUi == null || typeof rerenderUi !== "boolean") { + rerenderUi = true; + } + + if (script instanceof WorkerScript) { + stopAndCleanUpWorkerScript(script); + + return true; + } else if (script instanceof RunningScript && typeof serverIp === "string") { + // Try to kill by PID + const res = killWorkerScriptByPid(script.pid, rerenderUi); + if (res) { + return res; } - if (script instanceof WorkerScript) { - stopAndCleanUpWorkerScript(script); - - return true; - } else if (script instanceof RunningScript && typeof serverIp === "string") { - // Try to kill by PID - const res = killWorkerScriptByPid(script.pid, rerenderUi); - if (res) { return res; } - - // If for some reason that doesn't work, we'll try the old way - for (const ws of workerScripts.values()) { - if (ws.name == script.filename && ws.serverIp == serverIp && - compareArrays(ws.args, script.args)) { - - stopAndCleanUpWorkerScript(ws, rerenderUi); - - return true; - } - } - - return false; - } else if (typeof script === "number") { - return killWorkerScriptByPid(script, rerenderUi); - } else { - console.error(`killWorkerScript() called with invalid argument:`); - console.error(script); - return false; - } -} - -function killWorkerScriptByPid(pid: number, rerenderUi=true): boolean { - const ws = workerScripts.get(pid); - if (ws instanceof WorkerScript) { + // If for some reason that doesn't work, we'll try the old way + for (const ws of workerScripts.values()) { + if ( + ws.name == script.filename && + ws.serverIp == serverIp && + compareArrays(ws.args, script.args) + ) { stopAndCleanUpWorkerScript(ws, rerenderUi); return true; + } } return false; + } else if (typeof script === "number") { + return killWorkerScriptByPid(script, rerenderUi); + } else { + console.error(`killWorkerScript() called with invalid argument:`); + console.error(script); + return false; + } } -function stopAndCleanUpWorkerScript(workerScript: WorkerScript, rerenderUi=true): void { - workerScript.env.stopFlag = true; - killNetscriptDelay(workerScript); - removeWorkerScript(workerScript, rerenderUi); +function killWorkerScriptByPid(pid: number, rerenderUi = true): boolean { + const ws = workerScripts.get(pid); + if (ws instanceof WorkerScript) { + stopAndCleanUpWorkerScript(ws, rerenderUi); + + return true; + } + + return false; +} + +function stopAndCleanUpWorkerScript( + workerScript: WorkerScript, + rerenderUi = true, +): void { + workerScript.env.stopFlag = true; + killNetscriptDelay(workerScript); + removeWorkerScript(workerScript, rerenderUi); } /** @@ -74,49 +89,61 @@ function stopAndCleanUpWorkerScript(workerScript: WorkerScript, rerenderUi=true) * @param {WorkerScript | number} - Identifier for WorkerScript. Either the object itself, or * its index in the global workerScripts array */ -function removeWorkerScript(workerScript: WorkerScript, rerenderUi=true): void { - if (workerScript instanceof WorkerScript) { - const ip = workerScript.serverIp; - const name = workerScript.name; +function removeWorkerScript( + workerScript: WorkerScript, + rerenderUi = true, +): void { + if (workerScript instanceof WorkerScript) { + const ip = workerScript.serverIp; + const name = workerScript.name; - // Get the server on which the script runs - const server = AllServers[ip]; - if (server == null) { - console.error(`Could not find server on which this script is running: ${ip}`); - return; - } - - // Recalculate ram used on that server - server.ramUsed = roundToTwo(server.ramUsed - workerScript.ramUsage); - if (server.ramUsed < 0) { - console.warn(`Server (${server.hostname}) RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${server.ramUsed}`); - server.ramUsed = 0; - } - - // Delete the RunningScript object from that server - for (let i = 0; i < server.runningScripts.length; ++i) { - const runningScript = server.runningScripts[i]; - if (runningScript.filename === name && compareArrays(runningScript.args, workerScript.args)) { - server.runningScripts.splice(i, 1); - break; - } - } - - // Delete script from global pool (workerScripts) - const res = workerScripts.delete(workerScript.pid); - if (!res) { - console.warn(`removeWorkerScript() called with WorkerScript that wasn't in the global map:`); - console.warn(workerScript); - } - - if (rerenderUi) { - WorkerScriptStartStopEventEmitter.emitEvent(); - } - } else { - console.error(`Invalid argument passed into removeWorkerScript():`); - console.error(workerScript); - return; + // Get the server on which the script runs + const server = AllServers[ip]; + if (server == null) { + console.error( + `Could not find server on which this script is running: ${ip}`, + ); + return; } + + // Recalculate ram used on that server + server.ramUsed = roundToTwo(server.ramUsed - workerScript.ramUsage); + if (server.ramUsed < 0) { + console.warn( + `Server (${server.hostname}) RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${server.ramUsed}`, + ); + server.ramUsed = 0; + } + + // Delete the RunningScript object from that server + for (let i = 0; i < server.runningScripts.length; ++i) { + const runningScript = server.runningScripts[i]; + if ( + runningScript.filename === name && + compareArrays(runningScript.args, workerScript.args) + ) { + server.runningScripts.splice(i, 1); + break; + } + } + + // Delete script from global pool (workerScripts) + const res = workerScripts.delete(workerScript.pid); + if (!res) { + console.warn( + `removeWorkerScript() called with WorkerScript that wasn't in the global map:`, + ); + console.warn(workerScript); + } + + if (rerenderUi) { + WorkerScriptStartStopEventEmitter.emitEvent(); + } + } else { + console.error(`Invalid argument passed into removeWorkerScript():`); + console.error(workerScript); + return; + } } /** @@ -125,12 +152,12 @@ function removeWorkerScript(workerScript: WorkerScript, rerenderUi=true): void { * be killed immediately even if they're in the middle of one of those long operations */ function killNetscriptDelay(workerScript: WorkerScript): void { - if (workerScript instanceof WorkerScript) { - if (workerScript.delay) { - clearTimeout(workerScript.delay); - if (workerScript.delayResolve) { - workerScript.delayResolve(); - } - } + if (workerScript instanceof WorkerScript) { + if (workerScript.delay) { + clearTimeout(workerScript.delay); + if (workerScript.delayResolve) { + workerScript.delayResolve(); + } } + } } diff --git a/src/NetscriptBladeburner.js b/src/NetscriptBladeburner.js index c5509fd96..29e2b8994 100644 --- a/src/NetscriptBladeburner.js +++ b/src/NetscriptBladeburner.js @@ -1,9 +1,15 @@ -export function unknownBladeburnerActionErrorMessage(functionName, actionType, actionName) { - return `ERROR: bladeburner.${functionName}() failed due to an invalid action specified. ` + - `Type: ${actionType}, Name: ${actionName}. Note that for contracts and operations, the ` + - `name of the operation is case-sensitive.`; +export function unknownBladeburnerActionErrorMessage( + functionName, + actionType, + actionName, +) { + return ( + `ERROR: bladeburner.${functionName}() failed due to an invalid action specified. ` + + `Type: ${actionType}, Name: ${actionName}. Note that for contracts and operations, the ` + + `name of the operation is case-sensitive.` + ); } export function unknownBladeburnerExceptionMessage(functionName, err) { - return `bladeburner.${functionName}() failed with exception: ` + err; -} \ No newline at end of file + return `bladeburner.${functionName}() failed with exception: ` + err; +} diff --git a/src/NetscriptEvaluator.js b/src/NetscriptEvaluator.js index 8e765591c..e18d494b7 100644 --- a/src/NetscriptEvaluator.js +++ b/src/NetscriptEvaluator.js @@ -4,62 +4,75 @@ import { isString } from "../utils/helpers/isString"; import { AllServers } from "./Server/AllServers"; export function netscriptDelay(time, workerScript) { - return new Promise(function(resolve) { - workerScript.delay = setTimeoutRef(() => { - workerScript.delay = null; - resolve(); - }, time); - workerScript.delayResolve = resolve; - }); + return new Promise(function (resolve) { + workerScript.delay = setTimeoutRef(() => { + workerScript.delay = null; + resolve(); + }, time); + workerScript.delayResolve = resolve; + }); } -export function makeRuntimeRejectMsg(workerScript, msg, exp=null) { - var lineNum = ""; - if (exp != null) { - var num = getErrorLineNumber(exp, workerScript); - lineNum = " (Line " + num + ")" - } - const server = AllServers[workerScript.serverIp]; - if (server == null) { - throw new Error(`WorkerScript constructed with invalid server ip: ${this.serverIp}`); - } +export function makeRuntimeRejectMsg(workerScript, msg, exp = null) { + var lineNum = ""; + if (exp != null) { + var num = getErrorLineNumber(exp, workerScript); + lineNum = " (Line " + num + ")"; + } + const server = AllServers[workerScript.serverIp]; + if (server == null) { + throw new Error( + `WorkerScript constructed with invalid server ip: ${this.serverIp}`, + ); + } - return "|"+server.hostname+"|"+workerScript.name+"|" + msg + lineNum; + return "|" + server.hostname + "|" + workerScript.name + "|" + msg + lineNum; } -export function resolveNetscriptRequestedThreads(workerScript, functionName, requestedThreads) { - const threads = workerScript.scriptRef.threads; - if (!requestedThreads) { - return (isNaN(threads) || threads < 1) ? 1 : threads; - } - const requestedThreadsAsInt = requestedThreads|0; - if (isNaN(requestedThreads) || requestedThreadsAsInt < 1) { - throw makeRuntimeRejectMsg(workerScript, `Invalid thread count passed to ${functionName}: ${requestedThreads}. Threads must be a positive number.`); - } - if (requestedThreads > threads) { - throw makeRuntimeRejectMsg(workerScript, `Too many threads requested by ${functionName}. Requested: ${requestedThreads}. Has: ${threads}.`); - } - return requestedThreadsAsInt; +export function resolveNetscriptRequestedThreads( + workerScript, + functionName, + requestedThreads, +) { + const threads = workerScript.scriptRef.threads; + if (!requestedThreads) { + return isNaN(threads) || threads < 1 ? 1 : threads; + } + const requestedThreadsAsInt = requestedThreads | 0; + if (isNaN(requestedThreads) || requestedThreadsAsInt < 1) { + throw makeRuntimeRejectMsg( + workerScript, + `Invalid thread count passed to ${functionName}: ${requestedThreads}. Threads must be a positive number.`, + ); + } + if (requestedThreads > threads) { + throw makeRuntimeRejectMsg( + workerScript, + `Too many threads requested by ${functionName}. Requested: ${requestedThreads}. Has: ${threads}.`, + ); + } + return requestedThreadsAsInt; } - export function getErrorLineNumber(exp, workerScript) { - var code = workerScript.scriptRef.codeCode(); + var code = workerScript.scriptRef.codeCode(); - //Split code up to the start of the node - try { - code = code.substring(0, exp.start); - return (code.match(/\n/g) || []).length + 1; - } catch(e) { - return -1; - } + //Split code up to the start of the node + try { + code = code.substring(0, exp.start); + return (code.match(/\n/g) || []).length + 1; + } catch (e) { + return -1; + } } export function isScriptErrorMessage(msg) { - if (!isString(msg)) {return false;} - let splitMsg = msg.split("|"); - if (splitMsg.length != 4){ - return false; - } - return true; + if (!isString(msg)) { + return false; + } + let splitMsg = msg.split("|"); + if (splitMsg.length != 4) { + return false; + } + return true; } diff --git a/src/NetscriptFunctions.d.ts b/src/NetscriptFunctions.d.ts index c6af1ed19..93626649d 100644 --- a/src/NetscriptFunctions.d.ts +++ b/src/NetscriptFunctions.d.ts @@ -1,2 +1,2 @@ import { WorkerScript } from "./Netscript/WorkerScript"; -export declare function NetscriptFunctions(workerScript: WorkerScript): any; \ No newline at end of file +export declare function NetscriptFunctions(workerScript: WorkerScript): any; diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 9ebf85617..0f291769d 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -1,13 +1,13 @@ -import { vsprintf, sprintf } from 'sprintf-js'; -import * as libarg from 'arg'; +import { vsprintf, sprintf } from "sprintf-js"; +import * as libarg from "arg"; import { getRamCost } from "./Netscript/RamCostGenerator"; import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter"; import { Augmentations } from "./Augmentation/Augmentations"; import { - augmentationExists, - installAugmentations, + augmentationExists, + installAugmentations, } from "./Augmentation/AugmentationHelpers"; import { prestigeAugmentation } from "./Prestige"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; @@ -21,24 +21,25 @@ import { CompanyPositions } from "./Company/CompanyPositions"; import { CONSTANTS } from "./Constants"; import { DarkWebItems } from "./DarkWeb/DarkWebItems"; import { - NewIndustry, - NewCity, - UnlockUpgrade, - LevelUpgrade, - IssueDividends, - SellMaterial, - SellProduct, - SetSmartSupply, - BuyMaterial } from "./Corporation/Actions"; + NewIndustry, + NewCity, + UnlockUpgrade, + LevelUpgrade, + IssueDividends, + SellMaterial, + SellProduct, + SetSmartSupply, + BuyMaterial, +} from "./Corporation/Actions"; import { CorporationUnlockUpgrades } from "./Corporation/data/CorporationUnlockUpgrades"; import { CorporationUpgrades } from "./Corporation/data/CorporationUpgrades"; import { - calculateHackingChance, - calculateHackingExpGain, - calculatePercentMoneyHacked, - calculateHackingTime, - calculateGrowTime, - calculateWeakenTime, + calculateHackingChance, + calculateHackingExpGain, + calculatePercentMoneyHacked, + calculateHackingTime, + calculateGrowTime, + calculateWeakenTime, } from "./Hacking"; import { calculateServerGrowth } from "./Server/formulas/grow"; import { Gang } from "./Gang/Gang"; @@ -49,47 +50,47 @@ import { Factions, factionExists } from "./Faction/Factions"; import { joinFaction, purchaseAugmentation } from "./Faction/FactionHelpers"; import { FactionWorkType } from "./Faction/FactionWorkTypeEnum"; import { - netscriptCanGrow, - netscriptCanHack, - netscriptCanWeaken, + netscriptCanGrow, + netscriptCanHack, + netscriptCanWeaken, } from "./Hacking/netscriptCanHack"; import { - getCostOfNextHacknetNode, - getCostOfNextHacknetServer, - hasHacknetServers, - purchaseHacknet, - purchaseLevelUpgrade, - purchaseRamUpgrade, - purchaseCoreUpgrade, - purchaseCacheUpgrade, - purchaseHashUpgrade, - updateHashManagerCapacity, + getCostOfNextHacknetNode, + getCostOfNextHacknetServer, + hasHacknetServers, + purchaseHacknet, + purchaseLevelUpgrade, + purchaseRamUpgrade, + purchaseCoreUpgrade, + purchaseCacheUpgrade, + purchaseHashUpgrade, + updateHashManagerCapacity, } from "./Hacknet/HacknetHelpers"; import { - calculateMoneyGainRate, - calculateLevelUpgradeCost, - calculateRamUpgradeCost, - calculateCoreUpgradeCost, - calculateNodeCost, + calculateMoneyGainRate, + calculateLevelUpgradeCost, + calculateRamUpgradeCost, + calculateCoreUpgradeCost, + calculateNodeCost, } from "./Hacknet/formulas/HacknetNodes"; import { - calculateHashGainRate as HScalculateHashGainRate, - calculateLevelUpgradeCost as HScalculateLevelUpgradeCost, - calculateRamUpgradeCost as HScalculateRamUpgradeCost, - calculateCoreUpgradeCost as HScalculateCoreUpgradeCost, - calculateCacheUpgradeCost as HScalculateCacheUpgradeCost, - calculateServerCost as HScalculateServerCost, + calculateHashGainRate as HScalculateHashGainRate, + calculateLevelUpgradeCost as HScalculateLevelUpgradeCost, + calculateRamUpgradeCost as HScalculateRamUpgradeCost, + calculateCoreUpgradeCost as HScalculateCoreUpgradeCost, + calculateCacheUpgradeCost as HScalculateCacheUpgradeCost, + calculateServerCost as HScalculateServerCost, } from "./Hacknet/formulas/HacknetServers"; -import { HacknetNodeConstants, HacknetServerConstants } from "./Hacknet/data/Constants"; +import { + HacknetNodeConstants, + HacknetServerConstants, +} from "./Hacknet/data/Constants"; import { HacknetServer } from "./Hacknet/HacknetServer"; import { CityName } from "./Locations/data/CityNames"; import { LocationName } from "./Locations/data/LocationNames"; import { Terminal } from "./Terminal"; -import { - calculateSkill, - calculateExp, -} from "./PersonObjects/formulas/skill"; +import { calculateSkill, calculateExp } from "./PersonObjects/formulas/skill"; import { Message } from "./Message/Message"; import { inMission } from "./Missions"; @@ -97,79 +98,82 @@ import { Player } from "./Player"; import { Programs } from "./Programs/Programs"; import { Script } from "./Script/Script"; import { - findRunningScript, - findRunningScriptByPid, + findRunningScript, + findRunningScriptByPid, } from "./Script/ScriptHelpers"; import { isScriptFilename } from "./Script/ScriptHelpersTS"; import { - AllServers, - AddToAllServers, - createUniqueRandomIp, + AllServers, + AddToAllServers, + createUniqueRandomIp, } from "./Server/AllServers"; import { RunningScript } from "./Script/RunningScript"; import { - GetServerByHostname, - getServer, - getServerOnNetwork, - numCycleForGrowth, - processSingleServerGrowth, - safetlyCreateUniqueServer, + GetServerByHostname, + getServer, + getServerOnNetwork, + numCycleForGrowth, + processSingleServerGrowth, + safetlyCreateUniqueServer, } from "./Server/ServerHelpers"; import { - getPurchaseServerCost, - getPurchaseServerLimit, - getPurchaseServerMaxRam, + getPurchaseServerCost, + getPurchaseServerLimit, + getPurchaseServerMaxRam, } from "./Server/ServerPurchases"; import { SpecialServerIps } from "./Server/SpecialServerIps"; import { SourceFileFlags } from "./SourceFile/SourceFileFlags"; import { - buyStock, - sellStock, - shortStock, - sellShort, + buyStock, + sellStock, + shortStock, + sellShort, } from "./StockMarket/BuyingAndSelling"; import { - influenceStockThroughServerHack, - influenceStockThroughServerGrow, + influenceStockThroughServerHack, + influenceStockThroughServerGrow, } from "./StockMarket/PlayerInfluencing"; import { - StockMarket, - SymbolToStockMap, - placeOrder, - cancelOrder, - displayStockMarketContent, + StockMarket, + SymbolToStockMap, + placeOrder, + cancelOrder, + displayStockMarketContent, } from "./StockMarket/StockMarket"; import { - getBuyTransactionCost, - getSellTransactionGain, + getBuyTransactionCost, + getSellTransactionGain, } from "./StockMarket/StockMarketHelpers"; import { OrderTypes } from "./StockMarket/data/OrderTypes"; import { PositionTypes } from "./StockMarket/data/PositionTypes"; import { StockSymbols } from "./StockMarket/data/StockSymbols"; import { - getStockMarket4SDataCost, - getStockMarket4STixApiCost, + getStockMarket4SDataCost, + getStockMarket4STixApiCost, } from "./StockMarket/StockMarketCosts"; -import { isValidFilePath, removeLeadingSlash } from "./Terminal/DirectoryHelpers"; +import { + isValidFilePath, + removeLeadingSlash, +} from "./Terminal/DirectoryHelpers"; import { TextFile, getTextFile, createTextFile } from "./TextFile"; import { - NetscriptPorts, - runScriptFromScript, - startWorkerScript, + NetscriptPorts, + runScriptFromScript, + startWorkerScript, } from "./NetscriptWorker"; import { killWorkerScript } from "./Netscript/killWorkerScript"; import { workerScripts } from "./Netscript/WorkerScripts"; import { - makeRuntimeRejectMsg, - netscriptDelay, - resolveNetscriptRequestedThreads, + makeRuntimeRejectMsg, + netscriptDelay, + resolveNetscriptRequestedThreads, } from "./NetscriptEvaluator"; import { Interpreter } from "./JSInterpreter"; import { NetscriptPort } from "./NetscriptPort"; import { SleeveTaskType } from "./PersonObjects/Sleeve/SleeveTaskTypesEnum"; import { findSleevePurchasableAugs } from "./PersonObjects/Sleeve/SleeveHelpers"; -import { Exploit } from './Exploits/Exploit.ts'; +import { Exploit } from "./Exploits/Exploit.ts"; import { numeralWrapper } from "./ui/numeralFormat"; import { post } from "./ui/postToTerminal"; @@ -185,4461 +189,5904 @@ import { createElement } from "../utils/uiHelpers/createElement"; import { createPopup } from "../utils/uiHelpers/createPopup"; import { removeElementById } from "../utils/uiHelpers/removeElementById"; -const defaultInterpreter = new Interpreter('', () => undefined); +const defaultInterpreter = new Interpreter("", () => undefined); // the acorn interpreter has a bug where it doesn't convert arrays correctly. // so we have to more or less copy it here. function toNative(pseudoObj) { - if(pseudoObj == null) return null; - if(!pseudoObj.hasOwnProperty('properties') || - !pseudoObj.hasOwnProperty('getter') || - !pseudoObj.hasOwnProperty('setter') || - !pseudoObj.hasOwnProperty('proto')) { - return pseudoObj; // it wasn't a pseudo object anyway. - } + if (pseudoObj == null) return null; + if ( + !pseudoObj.hasOwnProperty("properties") || + !pseudoObj.hasOwnProperty("getter") || + !pseudoObj.hasOwnProperty("setter") || + !pseudoObj.hasOwnProperty("proto") + ) { + return pseudoObj; // it wasn't a pseudo object anyway. + } - let nativeObj; - if (pseudoObj.hasOwnProperty('class') && pseudoObj.class === 'Array') { - nativeObj = []; - const length = defaultInterpreter.getProperty(pseudoObj, 'length'); - for (let i = 0; i < length; i++) { - if (defaultInterpreter.hasProperty(pseudoObj, i)) { - nativeObj[i] = - toNative(defaultInterpreter.getProperty(pseudoObj, i)); - } - } - } else { // Object. - nativeObj = {}; - for (var key in pseudoObj.properties) { - const val = pseudoObj.properties[key]; - nativeObj[key] = toNative(val); - } + let nativeObj; + if (pseudoObj.hasOwnProperty("class") && pseudoObj.class === "Array") { + nativeObj = []; + const length = defaultInterpreter.getProperty(pseudoObj, "length"); + for (let i = 0; i < length; i++) { + if (defaultInterpreter.hasProperty(pseudoObj, i)) { + nativeObj[i] = toNative(defaultInterpreter.getProperty(pseudoObj, i)); } - return nativeObj; + } + } else { + // Object. + nativeObj = {}; + for (var key in pseudoObj.properties) { + const val = pseudoObj.properties[key]; + nativeObj[key] = toNative(val); + } + } + return nativeObj; } function NetscriptFunctions(workerScript) { - const updateDynamicRam = function(fnName, ramCost) { - if (workerScript.dynamicLoadedFns[fnName]) { return; } - workerScript.dynamicLoadedFns[fnName] = true; + const updateDynamicRam = function (fnName, ramCost) { + if (workerScript.dynamicLoadedFns[fnName]) { + return; + } + workerScript.dynamicLoadedFns[fnName] = true; - let threads = workerScript.scriptRef.threads; - if (typeof threads !== 'number') { - console.warn(`WorkerScript detected NaN for threadcount for ${workerScript.name} on ${workerScript.serverIp}`); - threads = 1; - } - - workerScript.dynamicRamUsage += (ramCost * threads); - if (workerScript.dynamicRamUsage > 1.01 * workerScript.ramUsage) { - throw makeRuntimeRejectMsg(workerScript, - "Dynamic RAM usage calculated to be greater than initial RAM usage on fn: " + fnName + - ". This is probably because you somehow circumvented the static RAM " + - "calculation.

    Please don't do that :(

    " + - "Dynamic RAM Usage: " + numeralWrapper.formatRAM(workerScript.dynamicRamUsage) + "
    " + - "Static RAM Usage: " + numeralWrapper.formatRAM(workerScript.ramUsage)); - } - }; - - /** - * Gets the Server for a specific hostname/ip, throwing an error - * if the server doesn't exist. - * @param {string} ip - Hostname or IP of the server - * @param {string} callingFnName - Name of calling function. For logging purposes - * @returns {Server} The specified Server - */ - const safeGetServer = function(ip, callingFnName="") { - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg(callingFnName, `Invalid IP/hostname: ${ip}`); - } - return server; + let threads = workerScript.scriptRef.threads; + if (typeof threads !== "number") { + console.warn( + `WorkerScript detected NaN for threadcount for ${workerScript.name} on ${workerScript.serverIp}`, + ); + threads = 1; } - /** - * Searches for and returns the RunningScript object for the specified script. - * If the 'fn' argument is not specified, this returns the current RunningScript. - * @param {string} fn - Filename of script - * @param {string} ip - Hostname/ip of the server on which the script resides - * @param {any[]} scriptArgs - Running script's arguments - * @returns {RunningScript} - * Running script identified by the parameters, or null if no such script - * exists, or the current running script if the first argument 'fn' - * is not specified. - */ - const getRunningScript = function(fn, ip, callingFnName, scriptArgs) { - if (typeof callingFnName !== "string" || callingFnName === "") { - callingFnName = "getRunningScript"; - } + workerScript.dynamicRamUsage += ramCost * threads; + if (workerScript.dynamicRamUsage > 1.01 * workerScript.ramUsage) { + throw makeRuntimeRejectMsg( + workerScript, + "Dynamic RAM usage calculated to be greater than initial RAM usage on fn: " + + fnName + + ". This is probably because you somehow circumvented the static RAM " + + "calculation.

    Please don't do that :(

    " + + "Dynamic RAM Usage: " + + numeralWrapper.formatRAM(workerScript.dynamicRamUsage) + + "
    " + + "Static RAM Usage: " + + numeralWrapper.formatRAM(workerScript.ramUsage), + ); + } + }; - if (!Array.isArray(scriptArgs)) { - throw makeRuntimeRejectMsg( - workerScript, - `Invalid scriptArgs argument passed into getRunningScript() from ${callingFnName}(). ` + - `This is probably a bug. Please report to game developer`, - ); - } + /** + * Gets the Server for a specific hostname/ip, throwing an error + * if the server doesn't exist. + * @param {string} ip - Hostname or IP of the server + * @param {string} callingFnName - Name of calling function. For logging purposes + * @returns {Server} The specified Server + */ + const safeGetServer = function (ip, callingFnName = "") { + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg(callingFnName, `Invalid IP/hostname: ${ip}`); + } + return server; + }; - if (fn != null && typeof fn === "string") { - // Get Logs of another script - if (ip == null) { ip = workerScript.serverIp; } - const server = safeGetServer(ip, callingFnName); - - return findRunningScript(fn, scriptArgs, server); - } - - // If no arguments are specified, return the current RunningScript - return workerScript.scriptRef; + /** + * Searches for and returns the RunningScript object for the specified script. + * If the 'fn' argument is not specified, this returns the current RunningScript. + * @param {string} fn - Filename of script + * @param {string} ip - Hostname/ip of the server on which the script resides + * @param {any[]} scriptArgs - Running script's arguments + * @returns {RunningScript} + * Running script identified by the parameters, or null if no such script + * exists, or the current running script if the first argument 'fn' + * is not specified. + */ + const getRunningScript = function (fn, ip, callingFnName, scriptArgs) { + if (typeof callingFnName !== "string" || callingFnName === "") { + callingFnName = "getRunningScript"; } - const getRunningScriptByPid = function(pid, callingFnName) { - if (typeof callingFnName !== "string" || callingFnName === "") { - callingFnName = "getRunningScriptgetRunningScriptByPid"; + if (!Array.isArray(scriptArgs)) { + throw makeRuntimeRejectMsg( + workerScript, + `Invalid scriptArgs argument passed into getRunningScript() from ${callingFnName}(). ` + + `This is probably a bug. Please report to game developer`, + ); + } + + if (fn != null && typeof fn === "string") { + // Get Logs of another script + if (ip == null) { + ip = workerScript.serverIp; + } + const server = safeGetServer(ip, callingFnName); + + return findRunningScript(fn, scriptArgs, server); + } + + // If no arguments are specified, return the current RunningScript + return workerScript.scriptRef; + }; + + const getRunningScriptByPid = function (pid, callingFnName) { + if (typeof callingFnName !== "string" || callingFnName === "") { + callingFnName = "getRunningScriptgetRunningScriptByPid"; + } + + for (const name of Object.keys(AllServers)) { + const server = AllServers[name]; + const runningScript = findRunningScriptByPid(pid, server); + if (runningScript) return runningScript; + } + return null; + }; + + /** + * Helper function for getting the error log message when the user specifies + * a nonexistent running script + * @param {string} fn - Filename of script + * @param {string} ip - Hostname/ip of the server on which the script resides + * @param {any[]} scriptArgs - Running script's arguments + * @returns {string} Error message to print to logs + */ + const getCannotFindRunningScriptErrorMessage = function (fn, ip, scriptArgs) { + if (!Array.isArray(scriptArgs)) { + scriptArgs = []; + } + + return `Cannot find running script ${fn} on server ${ip} with args: ${arrayToString( + scriptArgs, + )}`; + }; + + /** + * Checks if the player has TIX API access. Throws an error if the player does not + */ + const checkTixApiAccess = function (callingFn = "") { + if (!Player.hasWseAccount) { + throw makeRuntimeErrorMsg( + callingFn, + `You don't have WSE Access! Cannot use ${callingFn}()`, + ); + } + if (!Player.hasTixApiAccess) { + throw makeRuntimeErrorMsg( + callingFn, + `You don't have TIX API Access! Cannot use ${callingFn}()`, + ); + } + }; + + /** + * Gets a stock, given its symbol. Throws an error if the symbol is invalid + * @param {string} symbol - Stock's symbol + * @returns {Stock} stock object + */ + const getStockFromSymbol = function (symbol, callingFn = "") { + const stock = SymbolToStockMap[symbol]; + if (stock == null) { + throw makeRuntimeErrorMsg(callingFn, `Invalid stock symbol: '${symbol}'`); + } + + return stock; + }; + + /** + * Used to fail a function if the function's target is a Hacknet Server. + * This is used for functions that should run on normal Servers, but not Hacknet Servers + * @param {Server} server - Target server + * @param {string} callingFn - Name of calling function. For logging purposes + * @returns {boolean} True if the server is a Hacknet Server, false otherwise + */ + const failOnHacknetServer = function (server, callingFn = "") { + if (server instanceof HacknetServer) { + workerScript.log(callingFn, `Does not work on Hacknet Servers`); + return true; + } else { + return false; + } + }; + + // Utility function to get Hacknet Node object + const getHacknetNode = function (i, callingFn = "") { + if (isNaN(i)) { + throw makeRuntimeErrorMsg( + callingFn, + "Invalid index specified for Hacknet Node: " + i, + ); + } + if (i < 0 || i >= Player.hacknetNodes.length) { + throw makeRuntimeErrorMsg( + callingFn, + "Index specified for Hacknet Node is out-of-bounds: " + i, + ); + } + + if (hasHacknetServers()) { + const hserver = AllServers[Player.hacknetNodes[i]]; + if (hserver == null) { + throw makeRuntimeErrorMsg( + callingFn, + `Could not get Hacknet Server for index ${i}. This is probably a bug, please report to game dev`, + ); + } + + return hserver; + } else { + return Player.hacknetNodes[i]; + } + }; + + const makeRuntimeErrorMsg = function (caller, msg) { + const stack = new Error().stack.split("\n").slice(1); + const scripts = workerScript.getServer().scripts; + const userstack = []; + for (const stackline of stack) { + let filename; + for (const script of scripts) { + if (script.url && stackline.includes(script.url)) { + filename = script.filename; } - - for(const name of Object.keys(AllServers)) { - const server = AllServers[name]; - const runningScript = findRunningScriptByPid(pid, server); - if (runningScript) return runningScript; + for (const dependency of script.dependencies) { + if (stackline.includes(dependency.url)) { + filename = dependency.filename; + } + } + } + if (!filename) continue; + + function parseChromeStackline(line) { + const lineRe = /.*:(\d+):\d+.*/; + const funcRe = /.*at (.+) \(.*/; + + const lineMatch = line.match(lineRe); + const funcMatch = line.match(funcRe); + if (lineMatch && funcMatch) { + return { line: lineMatch[1], func: funcMatch[1] }; } return null; + } + let call = { line: "-1", func: "unknown" }; + const chromeCall = parseChromeStackline(stackline); + if (chromeCall) { + call = chromeCall; + } + + function parseFirefoxStackline(line) { + const lineRe = /.*:(\d+):\d+$/; + const lineMatch = line.match(lineRe); + + const lio = line.lastIndexOf("@"); + + if (lineMatch && lio !== -1) { + return { line: lineMatch[1], func: line.slice(0, lio) }; + } + return null; + } + + let firefoxCall = parseFirefoxStackline(stackline); + if (firefoxCall) { + call = firefoxCall; + } + + userstack.push(`${filename}:L${call.line}@${call.func}`); } - /** - * Helper function for getting the error log message when the user specifies - * a nonexistent running script - * @param {string} fn - Filename of script - * @param {string} ip - Hostname/ip of the server on which the script resides - * @param {any[]} scriptArgs - Running script's arguments - * @returns {string} Error message to print to logs - */ - const getCannotFindRunningScriptErrorMessage = function(fn, ip, scriptArgs) { - if (!Array.isArray(scriptArgs)) { - scriptArgs = []; - } + workerScript.log(caller, msg); + let rejectMsg = `${caller}: ${msg}`; + if (userstack.length !== 0) + rejectMsg += `

    Stack:
    ${userstack.join("
    ")}`; + return makeRuntimeRejectMsg(workerScript, rejectMsg); + }; - return `Cannot find running script ${fn} on server ${ip} with args: ${arrayToString(scriptArgs)}`; + const checkFormulasAccess = function (func, n) { + if ( + (SourceFileFlags[5] < 1 && Player.bitNodeN !== 5) || + (SourceFileFlags[n] < 1 && Player.bitNodeN !== n) + ) { + let extra = ""; + if (n !== 5) { + extra = ` and Source-File ${n}-1`; + } + throw makeRuntimeErrorMsg( + `formulas.${func}`, + `Requires Source-File 5-1${extra} to run.`, + ); + } + }; + + const checkSingularityAccess = function (func, n) { + if (Player.bitNodeN !== 4) { + if (SourceFileFlags[4] < n) { + throw makeRuntimeErrorMsg( + func, + `This singularity function requires Source-File 4-${n} to run.`, + ); + } + } + }; + + const checkBladeburnerAccess = function (func, skipjoined = false) { + const apiAccess = + Player.bitNodeN === 7 || + Player.sourceFiles.some((a) => { + return a.n === 7; + }); + if (!apiAccess) { + const apiDenied = `You do not currently have access to the Bladeburner API. You must either be in BitNode-7 or have Source-File 7.`; + throw makeRuntimeErrorMsg(`bladeburner.${func}`, apiDenied); + } + if (!skipjoined) { + const bladeburnerAccess = Player.bladeburner instanceof Bladeburner; + if (!bladeburnerAccess) { + const bladeburnerDenied = `You must be a member of the Bladeburner division to use this API.`; + throw makeRuntimeErrorMsg(`bladeburner.${func}`, bladeburnerDenied); + } + } + }; + + const checkBladeburnerCity = function (func, city) { + if (!Player.bladeburner.cities.hasOwnProperty(city)) { + throw makeRuntimeErrorMsg(`bladeburner.${func}`, `Invalid city: ${city}`); + } + }; + + const checkSleeveAPIAccess = function (func) { + if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { + throw makeRuntimeErrorMsg( + `sleeve.${func}`, + "You do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10", + ); + } + }; + + const checkSleeveNumber = function (func, sleeveNumber) { + if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { + const msg = `Invalid sleeve number: ${sleeveNumber}`; + workerScript.log(func, msg); + throw makeRuntimeErrorMsg(`sleeve.${func}`, msg); + } + }; + + const getCodingContract = function (func, ip, fn) { + const server = safeGetServer(ip, func); + const contract = server.getContract(fn); + if (contract == null) { + throw makeRuntimeErrorMsg( + `codingcontract.${func}`, + `Cannot find contract '${fn}' on server '${ip}'`, + ); } - /** - * Checks if the player has TIX API access. Throws an error if the player does not - */ - const checkTixApiAccess = function(callingFn="") { - if (!Player.hasWseAccount) { - throw makeRuntimeErrorMsg(callingFn, `You don't have WSE Access! Cannot use ${callingFn}()`); - } - if (!Player.hasTixApiAccess) { - throw makeRuntimeErrorMsg(callingFn, `You don't have TIX API Access! Cannot use ${callingFn}()`); - } + return contract; + }; + + const checkGangApiAccess = function (func) { + const hasAccess = Player.gang instanceof Gang; + if (!hasAccess) { + throw makeRuntimeErrorMsg( + `gang.${func}`, + `You do not currently have a Gang`, + ); + } + }; + + const getGangMember = function (func, name) { + for (const member of Player.gang.members) + if (member.name === name) return member; + throw makeRuntimeErrorMsg(`gang.${func}`, `Invalid gang member: '${name}'`); + }; + + const getGangTask = function (func, name) { + const task = GangMemberTasks[name]; + if (!task) { + throw makeRuntimeErrorMsg(`gang.${func}`, `Invalid task: '${name}'`); } - /** - * Gets a stock, given its symbol. Throws an error if the symbol is invalid - * @param {string} symbol - Stock's symbol - * @returns {Stock} stock object - */ - const getStockFromSymbol = function(symbol, callingFn="") { - const stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeErrorMsg(callingFn, `Invalid stock symbol: '${symbol}'`); - } + return task; + }; - return stock; + const getBladeburnerActionObject = function (func, type, name) { + const actionId = Player.bladeburner.getActionIdFromTypeAndName(type, name); + if (!actionId) { + throw makeRuntimeErrorMsg( + `bladeburner.${func}`, + `Invalid action type='${type}', name='${name}'`, + ); + } + const actionObj = Player.bladeburner.getActionObject(actionId); + if (!actionObj) { + throw makeRuntimeErrorMsg( + `bladeburner.${func}`, + `Invalid action type='${type}', name='${name}'`, + ); } - /** - * Used to fail a function if the function's target is a Hacknet Server. - * This is used for functions that should run on normal Servers, but not Hacknet Servers - * @param {Server} server - Target server - * @param {string} callingFn - Name of calling function. For logging purposes - * @returns {boolean} True if the server is a Hacknet Server, false otherwise - */ - const failOnHacknetServer = function(server, callingFn="") { - if (server instanceof HacknetServer) { - workerScript.log(callingFn, `Does not work on Hacknet Servers`); - return true; - } else { - return false; - } + return actionObj; + }; + + const getCompany = function (func, name) { + const company = Companies[name]; + if (company == null || !(company instanceof Company)) { + throw makeRuntimeErrorMsg(func, `Invalid company name: '${name}'`); + } + return company; + }; + + const getFaction = function (func, name) { + if (!factionExists(name)) { + throw makeRuntimeErrorMsg(func, `Invalid faction name: '${name}`); } - // Utility function to get Hacknet Node object - const getHacknetNode = function(i, callingFn="") { - if (isNaN(i)) { - throw makeRuntimeErrorMsg(callingFn, "Invalid index specified for Hacknet Node: " + i); + return Factions[name]; + }; + + const getAugmentation = function (func, name) { + if (!augmentationExists(name)) { + throw makeRuntimeErrorMsg(func, `Invalid augmentation: '${name}'`); + } + + return Augmentations[name]; + }; + + function getDivision(divisionName) { + const division = Player.corporation.divisions.find( + (div) => div.name === divisionName, + ); + if (division === undefined) + throw new Error(`No division named '${divisionName}'`); + return division; + } + + function getWarehouse(divisionName, cityName) { + const division = getDivision(divisionName); + if (!(cityName in division.warehouses)) + throw new Error(`Invalid city name '${cityName}'`); + const warehouse = division.warehouses[cityName]; + if (warehouse === 0) + throw new Error(`${division.name} has not expanded to '${cityName}'`); + return warehouse; + } + + function getMaterial(divisionName, cityName, materialName) { + const warehouse = getWarehouse(divisionName, cityName); + const material = warehouse.materials[materialName]; + if (material === undefined) + throw new Error(`Invalid material name: '${materialName}'`); + return material; + } + + function getProduct(divisionName, productName) { + const division = getDivision(divisionName); + const product = division.products[productName]; + if (product === undefined) + throw new Error(`Invalid product name: '${productName}'`); + return product; + } + + const runAfterReset = function (cbScript = null) { + //Run a script after reset + if (cbScript && isString(cbScript)) { + const home = Player.getHomeComputer(); + for (const script of home.scripts) { + if (script.filename === cbScript) { + const ramUsage = script.ramUsage; + const ramAvailable = home.maxRam - home.ramUsed; + if (ramUsage > ramAvailable) { + return; // Not enough RAM + } + const runningScriptObj = new RunningScript(script, []); // No args + runningScriptObj.threads = 1; // Only 1 thread + startWorkerScript(runningScriptObj, home); } - if (i < 0 || i >= Player.hacknetNodes.length) { - throw makeRuntimeErrorMsg(callingFn, "Index specified for Hacknet Node is out-of-bounds: " + i); + } + } + }; + + const hack = function ( + ip, + manual, + { threads: requestedThreads, stock } = {}, + ) { + if (ip === undefined) { + throw makeRuntimeErrorMsg("hack", "Takes 1 argument."); + } + const threads = resolveNetscriptRequestedThreads( + workerScript, + "hack", + requestedThreads, + ); + const server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("hack", `Invalid IP/hostname: ${ip}.`); + } + + // Calculate the hacking time + var hackingTime = calculateHackingTime(server, Player); // This is in seconds + + // No root access or skill level too low + const canHack = netscriptCanHack(server, Player); + if (!canHack.res) { + throw makeRuntimeErrorMsg("hack", canHack.msg); + } + + workerScript.log( + "hack", + `Executing ${ip} in ${convertTimeMsToTimeElapsedString( + hackingTime * 1000, + true, + )} (t=${numeralWrapper.formatThreads(threads)})`, + ); + + return netscriptDelay(hackingTime * 1000, workerScript).then(function () { + if (workerScript.env.stopFlag) { + return Promise.reject(workerScript); + } + var hackChance = calculateHackingChance(server, Player); + var rand = Math.random(); + var expGainedOnSuccess = + calculateHackingExpGain(server, Player) * threads; + var expGainedOnFailure = expGainedOnSuccess / 4; + if (rand < hackChance) { + // Success! + const percentHacked = calculatePercentMoneyHacked(server, Player); + let maxThreadNeeded = Math.ceil( + (1 / percentHacked) * (server.moneyAvailable / server.moneyMax), + ); + if (isNaN(maxThreadNeeded)) { + // Server has a 'max money' of 0 (probably). We'll set this to an arbitrarily large value + maxThreadNeeded = 1e6; } + let moneyDrained = + Math.floor(server.moneyAvailable * percentHacked) * threads; + + // Over-the-top safety checks + if (moneyDrained <= 0) { + moneyDrained = 0; + expGainedOnSuccess = expGainedOnFailure; + } + if (moneyDrained > server.moneyAvailable) { + moneyDrained = server.moneyAvailable; + } + server.moneyAvailable -= moneyDrained; + if (server.moneyAvailable < 0) { + server.moneyAvailable = 0; + } + + const moneyGained = + moneyDrained * BitNodeMultipliers.ScriptHackMoneyGain; + + Player.gainMoney(moneyGained); + workerScript.scriptRef.onlineMoneyMade += moneyGained; + Player.scriptProdSinceLastAug += moneyGained; + Player.recordMoneySource(moneyGained, "hacking"); + workerScript.scriptRef.recordHack(server.ip, moneyGained, threads); + Player.gainHackingExp(expGainedOnSuccess); + workerScript.scriptRef.onlineExpGained += expGainedOnSuccess; + workerScript.log( + "hack", + `Successfully hacked '${ + server.hostname + }' for ${numeralWrapper.formatMoney( + moneyGained, + )} and ${numeralWrapper.formatExp( + expGainedOnSuccess, + )} exp (t=${numeralWrapper.formatThreads(threads)})`, + ); + server.fortify( + CONSTANTS.ServerFortifyAmount * Math.min(threads, maxThreadNeeded), + ); + if (stock) { + influenceStockThroughServerHack(server, moneyGained); + } + if (manual) { + server.backdoorInstalled = true; + } + return Promise.resolve(moneyGained); + } else { + // Player only gains 25% exp for failure? + Player.gainHackingExp(expGainedOnFailure); + workerScript.scriptRef.onlineExpGained += expGainedOnFailure; + workerScript.log( + "hack", + `Failed to hack '${ + server.hostname + }'. Gained ${numeralWrapper.formatExp( + expGainedOnFailure, + )} exp (t=${numeralWrapper.formatThreads(threads)})`, + ); + return Promise.resolve(0); + } + }); + }; + + const argsToString = function (args) { + let out = ""; + for (let arg of args) { + arg = toNative(arg); + if (typeof arg === "object") { + out += JSON.stringify(arg); + continue; + } + out += `${arg}`; + } + + return out; + }; + + const functions = { + hacknet: { + numNodes: function () { + return Player.hacknetNodes.length; + }, + maxNumNodes: function () { if (hasHacknetServers()) { - const hserver = AllServers[Player.hacknetNodes[i]]; - if (hserver == null) { - throw makeRuntimeErrorMsg(callingFn, `Could not get Hacknet Server for index ${i}. This is probably a bug, please report to game dev`); - } - - return hserver; + return HacknetServerConstants.MaxServers; + } + return Infinity; + }, + purchaseNode: function () { + return purchaseHacknet(); + }, + getPurchaseNodeCost: function () { + if (hasHacknetServers()) { + return getCostOfNextHacknetServer(); } else { - return Player.hacknetNodes[i]; + return getCostOfNextHacknetNode(); } - }; + }, + getNodeStats: function (i) { + const node = getHacknetNode(i, "getNodeStats"); + const hasUpgraded = hasHacknetServers(); + const res = { + name: hasUpgraded ? node.hostname : node.name, + level: node.level, + ram: hasUpgraded ? node.maxRam : node.ram, + cores: node.cores, + production: hasUpgraded ? node.hashRate : node.moneyGainRatePerSecond, + timeOnline: node.onlineTimeSeconds, + totalProduction: hasUpgraded + ? node.totalHashesGenerated + : node.totalMoneyGenerated, + }; - const makeRuntimeErrorMsg = function(caller, msg) { - const stack = (new Error()).stack.split('\n').slice(1); - const scripts = workerScript.getServer().scripts; - const userstack = []; - for(const stackline of stack) { - let filename; - for(const script of scripts) { - if (script.url && stackline.includes(script.url)) { - filename = script.filename; - } - for (const dependency of script.dependencies) { - if (stackline.includes(dependency.url)) { - filename = dependency.filename; - } - } - } - if(!filename) continue - - function parseChromeStackline(line) { - const lineRe = /.*:(\d+):\d+.*/; - const funcRe = /.*at (.+) \(.*/; - - const lineMatch = line.match(lineRe); - const funcMatch = line.match(funcRe); - if(lineMatch && funcMatch) { - return {line: lineMatch[1], func: funcMatch[1]}; - } - return null; - } - let call = {line: "-1", func: "unknown"}; - const chromeCall = parseChromeStackline(stackline); - if (chromeCall) { - call = chromeCall; - } - - function parseFirefoxStackline(line) { - const lineRe = /.*:(\d+):\d+$/; - const lineMatch = line.match(lineRe); - - const lio = line.lastIndexOf("@"); - - if(lineMatch && lio !== -1) { - return {line: lineMatch[1], func: line.slice(0, lio)}; - } - return null; - } - - let firefoxCall = parseFirefoxStackline(stackline); - if (firefoxCall) { - call = firefoxCall; - } - - userstack.push(`${filename}:L${call.line}@${call.func}`); + if (hasUpgraded) { + res.cache = node.cache; + res.hashCapacity = node.hashCapacity; } - workerScript.log(caller, msg); - let rejectMsg = `${caller}: ${msg}` - if(userstack.length !== 0) rejectMsg += `

    Stack:
    ${userstack.join('
    ')}`; - return makeRuntimeRejectMsg(workerScript, rejectMsg); - } - - const checkFormulasAccess = function(func, n) { - if ((SourceFileFlags[5] < 1 && Player.bitNodeN !== 5) || (SourceFileFlags[n] < 1 && Player.bitNodeN !== n)) { - let extra = ''; - if (n !== 5) { - extra = ` and Source-File ${n}-1`; - } - throw makeRuntimeErrorMsg(`formulas.${func}`, `Requires Source-File 5-1${extra} to run.`); + return res; + }, + upgradeLevel: function (i, n) { + const node = getHacknetNode(i, "upgradeLevel"); + return purchaseLevelUpgrade(node, n); + }, + upgradeRam: function (i, n) { + const node = getHacknetNode(i, "upgradeRam"); + return purchaseRamUpgrade(node, n); + }, + upgradeCore: function (i, n) { + const node = getHacknetNode(i, "upgradeCore"); + return purchaseCoreUpgrade(node, n); + }, + upgradeCache: function (i, n) { + if (!hasHacknetServers()) { + return false; } - } - - const checkSingularityAccess = function(func, n) { - if (Player.bitNodeN !== 4) { - if (SourceFileFlags[4] < n) { - throw makeRuntimeErrorMsg(func, `This singularity function requires Source-File 4-${n} to run.`); - } + const node = getHacknetNode(i, "upgradeCache"); + const res = purchaseCacheUpgrade(node, n); + if (res) { + updateHashManagerCapacity(); } - } - - const checkBladeburnerAccess = function(func, skipjoined=false) { - const apiAccess = (Player.bitNodeN === 7 || Player.sourceFiles.some(a=>{return a.n === 7})); - if (!apiAccess) { - const apiDenied = `You do not currently have access to the Bladeburner API. You must either be in BitNode-7 or have Source-File 7.`; - throw makeRuntimeErrorMsg(`bladeburner.${func}`, apiDenied); + return res; + }, + getLevelUpgradeCost: function (i, n) { + const node = getHacknetNode(i, "upgradeLevel"); + return node.calculateLevelUpgradeCost( + n, + Player.hacknet_node_level_cost_mult, + ); + }, + getRamUpgradeCost: function (i, n) { + const node = getHacknetNode(i, "upgradeRam"); + return node.calculateRamUpgradeCost( + n, + Player.hacknet_node_ram_cost_mult, + ); + }, + getCoreUpgradeCost: function (i, n) { + const node = getHacknetNode(i, "upgradeCore"); + return node.calculateCoreUpgradeCost( + n, + Player.hacknet_node_core_cost_mult, + ); + }, + getCacheUpgradeCost: function (i, n) { + if (!hasHacknetServers()) { + return Infinity; } - if (!skipjoined) { - const bladeburnerAccess = Player.bladeburner instanceof Bladeburner; - if(!bladeburnerAccess) { - const bladeburnerDenied = `You must be a member of the Bladeburner division to use this API.`; - throw makeRuntimeErrorMsg(`bladeburner.${func}`, bladeburnerDenied); - } + const node = getHacknetNode(i, "upgradeCache"); + return node.calculateCacheUpgradeCost(n); + }, + numHashes: function () { + if (!hasHacknetServers()) { + return 0; } - } - - const checkBladeburnerCity = function(func, city) { - if (!Player.bladeburner.cities.hasOwnProperty(city)) { - throw makeRuntimeErrorMsg(`bladeburner.${func}`, `Invalid city: ${city}`); + return Player.hashManager.hashes; + }, + hashCapacity: function () { + if (!hasHacknetServers()) { + return 0; } - } - - const checkSleeveAPIAccess = function(func) { - if (Player.bitNodeN !== 10 && !SourceFileFlags[10]) { - throw makeRuntimeErrorMsg(`sleeve.${func}`, "You do not currently have access to the Sleeve API. This is either because you are not in BitNode-10 or because you do not have Source-File 10"); - } - } - - const checkSleeveNumber = function(func, sleeveNumber) { - if (sleeveNumber >= Player.sleeves.length || sleeveNumber < 0) { - const msg = `Invalid sleeve number: ${sleeveNumber}`; - workerScript.log(func, msg); - throw makeRuntimeErrorMsg(`sleeve.${func}`, msg); - } - } - - const getCodingContract = function(func, ip, fn) { - const server = safeGetServer(ip, func); - const contract = server.getContract(fn); - if (contract == null) { - throw makeRuntimeErrorMsg(`codingcontract.${func}`, `Cannot find contract '${fn}' on server '${ip}'`) + return Player.hashManager.capacity; + }, + hashCost: function (upgName) { + if (!hasHacknetServers()) { + return Infinity; } - return contract; - } - - const checkGangApiAccess = function(func) { - const hasAccess = Player.gang instanceof Gang; - if (!hasAccess) { - throw makeRuntimeErrorMsg(`gang.${func}`, `You do not currently have a Gang`); + return Player.hashManager.getUpgradeCost(upgName); + }, + spendHashes: function (upgName, upgTarget) { + if (!hasHacknetServers()) { + return false; } - } + return purchaseHashUpgrade(upgName, upgTarget); + }, + getHashUpgradeLevel: function (upgName) { + const level = Player.hashManager.upgrades[upgName]; + if (level === undefined) { + throw makeRuntimeErrorMsg( + "hacknet.hashUpgradeLevel", + `Invalid Hash Upgrade: ${upgName}`, + ); + } + return level; + }, + getStudyMult: function () { + if (!hasHacknetServers()) { + return false; + } + return Player.hashManager.getStudyMult(); + }, + getTrainingMult: function () { + if (!hasHacknetServers()) { + return false; + } + return Player.hashManager.getTrainingMult(); + }, + }, + sprintf: sprintf, + vsprintf: vsprintf, + scan: function (ip = workerScript.serverIp, hostnames = true) { + updateDynamicRam("scan", getRamCost("scan")); + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("scan", `Invalid IP/hostname: ${ip}.`); + } + var out = []; + for (var i = 0; i < server.serversOnNetwork.length; i++) { + var entry; + if (hostnames) { + entry = getServerOnNetwork(server, i).hostname; + } else { + entry = getServerOnNetwork(server, i).ip; + } + if (entry == null) { + continue; + } + out.push(entry); + } + workerScript.log( + "scan", + `returned ${server.serversOnNetwork.length} connections for ${server.hostname}`, + ); + return out; + }, + hack: function (ip, { threads: requestedThreads, stock } = {}) { + updateDynamicRam("hack", getRamCost("hack")); + return hack(ip, false, { threads: requestedThreads, stock: stock }); + }, + hackAnalyzeThreads: function (ip, hackAmount) { + updateDynamicRam("hackAnalyzeThreads", getRamCost("hackAnalyzeThreads")); - const getGangMember = function(func, name) { - for (const member of Player.gang.members) - if (member.name === name) - return member; - throw makeRuntimeErrorMsg(`gang.${func}`, `Invalid gang member: '${name}'`) - } + // Check argument validity + const server = safeGetServer(ip, "hackAnalyzeThreads"); + if (isNaN(hackAmount)) { + throw makeRuntimeErrorMsg( + workerScript, + `Invalid growth argument passed into hackAnalyzeThreads: ${hackAmount}. Must be numeric.`, + ); + } - const getGangTask = function(func, name) { - const task = GangMemberTasks[name]; - if (!task) { - throw makeRuntimeErrorMsg(`gang.${func}`, `Invalid task: '${name}'`); + if (hackAmount < 0 || hackAmount > server.moneyAvailable) { + return -1; + } + + const percentHacked = calculatePercentMoneyHacked(server, Player); + + return hackAmount / Math.floor(server.moneyAvailable * percentHacked); + }, + hackAnalyzePercent: function (ip) { + updateDynamicRam("hackAnalyzePercent", getRamCost("hackAnalyzePercent")); + + const server = safeGetServer(ip, "hackAnalyzePercent"); + + return calculatePercentMoneyHacked(server, Player) * 100; + }, + hackChance: function (ip) { + updateDynamicRam("hackChance", getRamCost("hackChance")); + + const server = safeGetServer(ip, "hackChance"); + + return calculateHackingChance(server, Player); + }, + sleep: function (time) { + if (time === undefined) { + throw makeRuntimeErrorMsg("sleep", "Takes 1 argument."); + } + workerScript.log("sleep", `Sleeping for ${time} milliseconds`); + return netscriptDelay(time, workerScript).then(function () { + return Promise.resolve(true); + }); + }, + grow: function (ip, { threads: requestedThreads, stock } = {}) { + updateDynamicRam("grow", getRamCost("grow")); + const threads = resolveNetscriptRequestedThreads( + workerScript, + "grow", + requestedThreads, + ); + if (ip === undefined) { + throw makeRuntimeErrorMsg("grow", "Takes 1 argument."); + } + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("grow", `Invalid IP/hostname: ${ip}.`); + } + + const host = getServer(workerScript.serverIp); + + // No root access or skill level too low + const canHack = netscriptCanGrow(server); + if (!canHack.res) { + throw makeRuntimeErrorMsg("grow", canHack.msg); + } + + var growTime = calculateGrowTime(server, Player); + workerScript.log( + "grow", + `Executing on '${ + server.hostname + }' in ${convertTimeMsToTimeElapsedString( + growTime * 1000, + true, + )} (t=${numeralWrapper.formatThreads(threads)}).`, + ); + return netscriptDelay(growTime * 1000, workerScript).then(function () { + if (workerScript.env.stopFlag) { + return Promise.reject(workerScript); + } + const moneyBefore = + server.moneyAvailable <= 0 ? 1 : server.moneyAvailable; + server.moneyAvailable += 1 * threads; // It can be grown even if it has no money + processSingleServerGrowth(server, threads, Player, host.cpuCores); + const moneyAfter = server.moneyAvailable; + workerScript.scriptRef.recordGrow(server.ip, threads); + var expGain = calculateHackingExpGain(server, Player) * threads; + const logGrowPercent = moneyAfter / moneyBefore - 1; + workerScript.log( + "grow", + `Available money on '${ + server.hostname + }' grown by ${numeralWrapper.formatPercentage( + logGrowPercent, + 6, + )}. Gained ${numeralWrapper.formatExp( + expGain, + )} hacking exp (t=${numeralWrapper.formatThreads(threads)}).`, + ); + workerScript.scriptRef.onlineExpGained += expGain; + Player.gainHackingExp(expGain); + if (stock) { + influenceStockThroughServerGrow(server, moneyAfter - moneyBefore); + } + return Promise.resolve(moneyAfter / moneyBefore); + }); + }, + growthAnalyze: function (ip, growth) { + updateDynamicRam("growthAnalyze", getRamCost("growthAnalyze")); + + // Check argument validity + const server = safeGetServer(ip, "growthAnalyze"); + if ( + typeof growth !== "number" || + isNaN(growth) || + growth < 1 || + !isFinite(growth) + ) { + throw makeRuntimeErrorMsg( + "growthAnalyze", + `Invalid argument: growth must be numeric and >= 1, is ${growth}.`, + ); + } + + return numCycleForGrowth(server, Number(growth), Player); + }, + weaken: function (ip, { threads: requestedThreads } = {}) { + updateDynamicRam("weaken", getRamCost("weaken")); + var threads = resolveNetscriptRequestedThreads( + workerScript, + "weaken", + requestedThreads, + ); + if (ip === undefined) { + throw makeRuntimeErrorMsg("weaken", "Takes 1 argument."); + } + const server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("weaken", `Invalid IP/hostname: ${ip}`); + } + + // No root access or skill level too low + const canHack = netscriptCanWeaken(server); + if (!canHack.res) { + throw makeRuntimeErrorMsg("weaken", canHack.msg); + } + + const weakenTime = calculateWeakenTime(server, Player); + workerScript.log( + "weaken", + `Executing on '${ + server.hostname + }' in ${convertTimeMsToTimeElapsedString( + weakenTime * 1000, + true, + )} (t=${numeralWrapper.formatThreads(threads)})`, + ); + return netscriptDelay(weakenTime * 1000, workerScript).then(function () { + if (workerScript.env.stopFlag) return Promise.reject(workerScript); + const host = getServer(workerScript.serverIp); + const coreBonus = 1 + (host.cpuCores - 1) / 16; + server.weaken(CONSTANTS.ServerWeakenAmount * threads * coreBonus); + workerScript.scriptRef.recordWeaken(server.ip, threads); + const expGain = calculateHackingExpGain(server, Player) * threads; + workerScript.log( + "weaken", + `'${server.hostname}' security level weakened to ${ + server.hackDifficulty + }. Gained ${numeralWrapper.formatExp( + expGain, + )} hacking exp (t=${numeralWrapper.formatThreads(threads)})`, + ); + workerScript.scriptRef.onlineExpGained += expGain; + Player.gainHackingExp(expGain); + return Promise.resolve(CONSTANTS.ServerWeakenAmount * threads); + }); + }, + print: function () { + if (arguments.length === 0) { + throw makeRuntimeErrorMsg("print", "Takes at least 1 argument."); + } + workerScript.print(argsToString(arguments)); + }, + tprint: function () { + if (arguments.length === 0) { + throw makeRuntimeErrorMsg("tprint", "Takes at least 1 argument."); + } + post(`${workerScript.scriptRef.filename}: ${argsToString(arguments)}`); + }, + tprintf: function (format, ...args) { + post(vsprintf(format, args)); + }, + clearLog: function () { + workerScript.scriptRef.clearLog(); + }, + disableLog: function (fn) { + if (fn === "ALL") { + for (fn in possibleLogs) { + workerScript.disableLogs[fn] = true; + } + workerScript.log("disableLog", `Disabled logging for all functions`); + } else if (possibleLogs[fn] === undefined) { + throw makeRuntimeErrorMsg("disableLog", `Invalid argument: ${fn}.`); + } else { + workerScript.disableLogs[fn] = true; + workerScript.log("disableLog", `Disabled logging for ${fn}`); + } + }, + enableLog: function (fn) { + if (possibleLogs[fn] === undefined) { + throw makeRuntimeErrorMsg("enableLog", `Invalid argument: ${fn}.`); + } + delete workerScript.disableLogs[fn]; + workerScript.log("enableLog", `Enabled logging for ${fn}`); + }, + isLogEnabled: function (fn) { + if (possibleLogs[fn] === undefined) { + throw makeRuntimeErrorMsg("isLogEnabled", `Invalid argument: ${fn}.`); + } + return workerScript.disableLogs[fn] ? false : true; + }, + getScriptLogs: function (fn, ip, ...scriptArgs) { + const runningScriptObj = getRunningScript( + fn, + ip, + "getScriptLogs", + scriptArgs, + ); + if (runningScriptObj == null) { + workerScript.log( + "getScriptLogs", + getCannotFindRunningScriptErrorMessage(fn, ip, scriptArgs), + ); + return ""; + } + + return runningScriptObj.logs.slice(); + }, + tail: function (fn, ip = workerScript.serverIp, ...scriptArgs) { + let runningScriptObj; + if (arguments.length === 0) { + runningScriptObj = workerScript.scriptRef; + } else if (typeof fn === "number") { + runningScriptObj = getRunningScriptByPid(fn, "tail"); + } else { + runningScriptObj = getRunningScript(fn, ip, "tail", scriptArgs); + } + if (runningScriptObj == null) { + workerScript.log( + "tail", + getCannotFindRunningScriptErrorMessage(fn, ip, scriptArgs), + ); + return; + } + + logBoxCreate(runningScriptObj); + }, + nuke: function (ip) { + updateDynamicRam("nuke", getRamCost("nuke")); + if (ip === undefined) { + throw makeRuntimeErrorMsg("nuke", "Takes 1 argument."); + } + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("nuke", `Invalid IP/hostname: ${ip}.`); + } + if (!Player.hasProgram(Programs.NukeProgram.name)) { + throw makeRuntimeErrorMsg( + "nuke", + "You do not have the NUKE.exe virus!", + ); + } + if (server.openPortCount < server.numOpenPortsRequired) { + throw makeRuntimeErrorMsg( + "nuke", + "Not enough ports opened to use NUKE.exe virus.", + ); + } + if (server.hasAdminRights) { + workerScript.log( + "nuke", + `Already have root access to '${server.hostname}'.`, + ); + } else { + server.hasAdminRights = true; + workerScript.log( + "nuke", + `Executed NUKE.exe virus on '${server.hostname}' to gain root access.`, + ); + } + return true; + }, + brutessh: function (ip) { + updateDynamicRam("brutessh", getRamCost("brutessh")); + if (ip === undefined) { + throw makeRuntimeErrorMsg("brutessh", "Takes 1 argument."); + } + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("brutessh", `Invalid IP/hostname: ${ip}.`); + } + if (!Player.hasProgram(Programs.BruteSSHProgram.name)) { + throw makeRuntimeErrorMsg( + "brutessh", + "You do not have the BruteSSH.exe program!", + ); + } + if (!server.sshPortOpen) { + workerScript.log( + "brutessh", + `Executed BruteSSH.exe on '${server.hostname}' to open SSH port (22).`, + ); + server.sshPortOpen = true; + ++server.openPortCount; + } else { + workerScript.log( + "brutessh", + `SSH Port (22) already opened on '${server.hostname}'.`, + ); + } + return true; + }, + ftpcrack: function (ip) { + updateDynamicRam("ftpcrack", getRamCost("ftpcrack")); + if (ip === undefined) { + throw makeRuntimeErrorMsg("ftpcrack", "Takes 1 argument."); + } + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("ftpcrack", `Invalid IP/hostname: ${ip}.`); + } + if (!Player.hasProgram(Programs.FTPCrackProgram.name)) { + throw makeRuntimeErrorMsg( + "ftpcrack", + "You do not have the FTPCrack.exe program!", + ); + } + if (!server.ftpPortOpen) { + workerScript.log( + "ftpcrack", + `Executed FTPCrack.exe on '${server.hostname}' to open FTP port (21).`, + ); + server.ftpPortOpen = true; + ++server.openPortCount; + } else { + workerScript.log( + "ftpcrack", + `FTP Port (21) already opened on '${server.hostname}'.`, + ); + } + return true; + }, + relaysmtp: function (ip) { + updateDynamicRam("relaysmtp", getRamCost("relaysmtp")); + if (ip === undefined) { + throw makeRuntimeErrorMsg("relaysmtp", "Takes 1 argument."); + } + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("relaysmtp", `Invalid IP/hostname: ${ip}.`); + } + if (!Player.hasProgram(Programs.RelaySMTPProgram.name)) { + throw makeRuntimeErrorMsg( + "relaysmtp", + "You do not have the relaySMTP.exe program!", + ); + } + if (!server.smtpPortOpen) { + workerScript.log( + "relaysmtp", + `Executed relaySMTP.exe on '${server.hostname}' to open SMTP port (25).`, + ); + server.smtpPortOpen = true; + ++server.openPortCount; + } else { + workerScript.log( + "relaysmtp", + `SMTP Port (25) already opened on '${server.hostname}'.`, + ); + } + return true; + }, + httpworm: function (ip) { + updateDynamicRam("httpworm", getRamCost("httpworm")); + if (ip === undefined) { + throw makeRuntimeErrorMsg("httpworm", "Takes 1 argument"); + } + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("httpworm", `Invalid IP/hostname: ${ip}`); + } + if (!Player.hasProgram(Programs.HTTPWormProgram.name)) { + throw makeRuntimeErrorMsg( + "httpworm", + "You do not have the HTTPWorm.exe program!", + ); + } + if (!server.httpPortOpen) { + workerScript.log( + "httpworm", + `Executed HTTPWorm.exe on '${server.hostname}' to open HTTP port (80).`, + ); + server.httpPortOpen = true; + ++server.openPortCount; + } else { + workerScript.log( + "httpworm", + `HTTP Port (80) already opened on '${server.hostname}'.`, + ); + } + return true; + }, + sqlinject: function (ip) { + updateDynamicRam("sqlinject", getRamCost("sqlinject")); + if (ip === undefined) { + throw makeRuntimeErrorMsg("sqlinject", "Takes 1 argument."); + } + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("sqlinject", `Invalid IP/hostname: ${ip}`); + } + if (!Player.hasProgram(Programs.SQLInjectProgram.name)) { + throw makeRuntimeErrorMsg( + "sqlinject", + "You do not have the SQLInject.exe program!", + ); + } + if (!server.sqlPortOpen) { + workerScript.log( + "sqlinject", + `Executed SQLInject.exe on '${server.hostname}' to open SQL port (1433).`, + ); + server.sqlPortOpen = true; + ++server.openPortCount; + } else { + workerScript.log( + "sqlinject", + `SQL Port (1433) already opened on '${server.hostname}'.`, + ); + } + return true; + }, + run: function (scriptname, threads = 1) { + updateDynamicRam("run", getRamCost("run")); + if (scriptname === undefined) { + throw makeRuntimeErrorMsg( + "run", + "Usage: run(scriptname, [numThreads], [arg1], [arg2]...)", + ); + } + if (isNaN(threads) || threads <= 0) { + throw makeRuntimeErrorMsg( + "run", + `Invalid thread count. Must be numeric and > 0, is ${threads}`, + ); + } + var argsForNewScript = []; + for (var i = 2; i < arguments.length; ++i) { + argsForNewScript.push(arguments[i]); + } + var scriptServer = getServer(workerScript.serverIp); + if (scriptServer == null) { + throw makeRuntimeErrorMsg( + "run", + "Could not find server. This is a bug. Report to dev.", + ); + } + + return runScriptFromScript( + "run", + scriptServer, + scriptname, + argsForNewScript, + workerScript, + threads, + ); + }, + exec: function (scriptname, ip, threads = 1) { + updateDynamicRam("exec", getRamCost("exec")); + if (scriptname === undefined || ip === undefined) { + throw makeRuntimeErrorMsg( + "exec", + "Usage: exec(scriptname, server, [numThreads], [arg1], [arg2]...)", + ); + } + if (isNaN(threads) || threads <= 0) { + throw makeRuntimeErrorMsg( + "exec", + `Invalid thread count. Must be numeric and > 0, is ${threads}`, + ); + } + var argsForNewScript = []; + for (var i = 3; i < arguments.length; ++i) { + argsForNewScript.push(arguments[i]); + } + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("exec", `Invalid IP/hostname: ${ip}`); + } + return runScriptFromScript( + "exec", + server, + scriptname, + argsForNewScript, + workerScript, + threads, + ); + }, + spawn: function (scriptname, threads) { + updateDynamicRam("spawn", getRamCost("spawn")); + if (!scriptname || !threads) { + throw makeRuntimeErrorMsg("spawn", "Usage: spawn(scriptname, threads)"); + } + + const spawnDelay = 10; + setTimeoutRef(() => { + if (isNaN(threads) || threads <= 0) { + throw makeRuntimeErrorMsg( + "spawn", + `Invalid thread count. Must be numeric and > 0, is ${threads}`, + ); + } + var argsForNewScript = []; + for (var i = 2; i < arguments.length; ++i) { + argsForNewScript.push(arguments[i]); + } + var scriptServer = getServer(workerScript.serverIp); + if (scriptServer == null) { + throw makeRuntimeErrorMsg( + "spawn", + "Could not find server. This is a bug. Report to dev", + ); } - return task; - } + return runScriptFromScript( + "spawn", + scriptServer, + scriptname, + argsForNewScript, + workerScript, + threads, + ); + }, spawnDelay * 1e3); - const getBladeburnerActionObject = function(func, type, name) { - const actionId = Player.bladeburner.getActionIdFromTypeAndName(type, name) - if (!actionId) { - throw makeRuntimeErrorMsg(`bladeburner.${func}`, `Invalid action type='${type}', name='${name}'`); - } - const actionObj = Player.bladeburner.getActionObject(actionId); - if (!actionObj) { - throw makeRuntimeErrorMsg(`bladeburner.${func}`, `Invalid action type='${type}', name='${name}'`); + workerScript.log( + "spawn", + `Will execute '${scriptname}' in ${spawnDelay} seconds`, + ); + + workerScript.running = false; // Prevent workerScript from "finishing execution naturally" + if (killWorkerScript(workerScript)) { + workerScript.log("spawn", "Exiting..."); + } + }, + kill: function (filename, ip, ...scriptArgs) { + updateDynamicRam("kill", getRamCost("kill")); + + let res; + const killByPid = typeof filename === "number"; + if (killByPid) { + // Kill by pid + res = killWorkerScript(filename); + } else { + // Kill by filename/ip + if (filename === undefined || ip === undefined) { + throw makeRuntimeErrorMsg( + "kill", + "Usage: kill(scriptname, server, [arg1], [arg2]...)", + ); } - return actionObj; - } - - const getCompany = function(func, name) { - const company = Companies[name]; - if (company == null || !(company instanceof Company)) { - throw makeRuntimeErrorMsg(func, `Invalid company name: '${name}'`) - } - return company; - } - - const getFaction = function(func, name) { - if (!factionExists(name)) { - throw makeRuntimeErrorMsg(func, `Invalid faction name: '${name}`) + const server = safeGetServer(ip); + const runningScriptObj = getRunningScript( + filename, + ip, + "kill", + scriptArgs, + ); + if (runningScriptObj == null) { + workerScript.log( + "kill", + getCannotFindRunningScriptErrorMessage(filename, ip, scriptArgs), + ); + return false; } - return Factions[name]; - } + res = killWorkerScript(runningScriptObj, server.ip); + } - const getAugmentation = function(func, name) { - if (!augmentationExists(name)) { - throw makeRuntimeErrorMsg(func, `Invalid augmentation: '${name}'`); + if (res) { + if (killByPid) { + workerScript.log("kill", `Killing script with PID ${filename}`); + } else { + workerScript.log( + "kill", + `Killing '${filename}' on '${ip}' with args: ${arrayToString( + scriptArgs, + )}.`, + ); } - - return Augmentations[name]; - } - - function getDivision(divisionName) { - const division = Player.corporation.divisions.find(div => div.name === divisionName); - if(division === undefined) - throw new Error(`No division named '${divisionName}'`); - return division; - } - - function getWarehouse(divisionName, cityName) { - const division = getDivision(divisionName); - if(!(cityName in division.warehouses)) - throw new Error(`Invalid city name '${cityName}'`); - const warehouse = division.warehouses[cityName]; - if(warehouse === 0) - throw new Error(`${division.name} has not expanded to '${cityName}'`); - return warehouse; - } - - function getMaterial(divisionName, cityName, materialName) { - const warehouse = getWarehouse(divisionName, cityName); - const material = warehouse.materials[materialName]; - if(material === undefined) - throw new Error(`Invalid material name: '${materialName}'`); - return material; - } - - function getProduct(divisionName, productName) { - const division = getDivision(divisionName); - const product = division.products[productName]; - if(product === undefined) - throw new Error(`Invalid product name: '${productName}'`); - return product; - } - - const runAfterReset = function(cbScript=null) { - //Run a script after reset - if (cbScript && isString(cbScript)) { - const home = Player.getHomeComputer(); - for (const script of home.scripts) { - if (script.filename === cbScript) { - const ramUsage = script.ramUsage; - const ramAvailable = home.maxRam - home.ramUsed; - if (ramUsage > ramAvailable) { - return; // Not enough RAM - } - const runningScriptObj = new RunningScript(script, []); // No args - runningScriptObj.threads = 1; // Only 1 thread - startWorkerScript(runningScriptObj, home); - } - } + return true; + } else { + if (killByPid) { + workerScript.log("kill", `No script with PID ${filename}`); + } else { + workerScript.log( + "kill", + `No such script '${filename}' on '${ip}' with args: ${arrayToString( + scriptArgs, + )}`, + ); } - } + return false; + } + }, + killall: function (ip = workerScript.serverIp) { + updateDynamicRam("killall", getRamCost("killall")); + if (ip === undefined) { + throw makeRuntimeErrorMsg("killall", "Takes 1 argument"); + } + const server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("killall", `Invalid IP/hostname: ${ip}`); + } + const scriptsRunning = server.runningScripts.length > 0; + for (let i = server.runningScripts.length - 1; i >= 0; --i) { + killWorkerScript(server.runningScripts[i], server.ip, false); + } + WorkerScriptStartStopEventEmitter.emitEvent(); + workerScript.log( + "killall", + `Killing all scripts on '${server.hostname}'. May take a few minutes for the scripts to die.`, + ); - const hack = function(ip, manual, { threads: requestedThreads, stock } = {}) { - if (ip === undefined) { - throw makeRuntimeErrorMsg("hack", "Takes 1 argument."); - } - const threads = resolveNetscriptRequestedThreads(workerScript, "hack", requestedThreads); - const server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("hack", `Invalid IP/hostname: ${ip}.`); - } - - // Calculate the hacking time - var hackingTime = calculateHackingTime(server, Player); // This is in seconds - - // No root access or skill level too low - const canHack = netscriptCanHack(server, Player); - if (!canHack.res) { - throw makeRuntimeErrorMsg('hack', canHack.msg); - } - - workerScript.log("hack", `Executing ${ip} in ${convertTimeMsToTimeElapsedString(hackingTime*1000, true)} (t=${numeralWrapper.formatThreads(threads)})`); - - return netscriptDelay(hackingTime * 1000, workerScript).then(function() { - if (workerScript.env.stopFlag) {return Promise.reject(workerScript);} - var hackChance = calculateHackingChance(server, Player); - var rand = Math.random(); - var expGainedOnSuccess = calculateHackingExpGain(server, Player) * threads; - var expGainedOnFailure = (expGainedOnSuccess / 4); - if (rand < hackChance) { // Success! - const percentHacked = calculatePercentMoneyHacked(server, Player); - let maxThreadNeeded = Math.ceil(1/percentHacked*(server.moneyAvailable/server.moneyMax)); - if (isNaN(maxThreadNeeded)) { - // Server has a 'max money' of 0 (probably). We'll set this to an arbitrarily large value - maxThreadNeeded = 1e6; - } - - let moneyDrained = Math.floor(server.moneyAvailable * percentHacked) * threads; - - // Over-the-top safety checks - if (moneyDrained <= 0) { - moneyDrained = 0; - expGainedOnSuccess = expGainedOnFailure; - } - if (moneyDrained > server.moneyAvailable) {moneyDrained = server.moneyAvailable;} - server.moneyAvailable -= moneyDrained; - if (server.moneyAvailable < 0) {server.moneyAvailable = 0;} - - const moneyGained = moneyDrained * BitNodeMultipliers.ScriptHackMoneyGain; - - Player.gainMoney(moneyGained); - workerScript.scriptRef.onlineMoneyMade += moneyGained; - Player.scriptProdSinceLastAug += moneyGained; - Player.recordMoneySource(moneyGained, "hacking"); - workerScript.scriptRef.recordHack(server.ip, moneyGained, threads); - Player.gainHackingExp(expGainedOnSuccess); - workerScript.scriptRef.onlineExpGained += expGainedOnSuccess; - workerScript.log("hack", `Successfully hacked '${server.hostname}' for ${numeralWrapper.formatMoney(moneyGained)} and ${numeralWrapper.formatExp(expGainedOnSuccess)} exp (t=${numeralWrapper.formatThreads(threads)})`); - server.fortify(CONSTANTS.ServerFortifyAmount * Math.min(threads, maxThreadNeeded)); - if (stock) { - influenceStockThroughServerHack(server, moneyGained); - } - if(manual) { - server.backdoorInstalled = true; - } - return Promise.resolve(moneyGained); - } else { - // Player only gains 25% exp for failure? - Player.gainHackingExp(expGainedOnFailure); - workerScript.scriptRef.onlineExpGained += expGainedOnFailure; - workerScript.log("hack", `Failed to hack '${server.hostname}'. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} exp (t=${numeralWrapper.formatThreads(threads)})`); - return Promise.resolve(0); - } + return scriptsRunning; + }, + exit: function () { + workerScript.running = false; // Prevent workerScript from "finishing execution naturally" + if (killWorkerScript(workerScript)) { + workerScript.log("exit", "Exiting..."); + } else { + workerScript.log("exit", "Failed. This is a bug. Report to dev."); + } + }, + scp: function (scriptname, ip1, ip2) { + updateDynamicRam("scp", getRamCost("scp")); + if (arguments.length !== 2 && arguments.length !== 3) { + throw makeRuntimeErrorMsg("scp", "Takes 2 or 3 arguments"); + } + if (scriptname && scriptname.constructor === Array) { + // Recursively call scp on all elements of array + var res = false; + scriptname.forEach(function (script) { + if (NetscriptFunctions(workerScript).scp(script, ip1, ip2)) { + res = true; + } }); - } + return res; + } - const argsToString = function(args) { - let out = ''; - for(let arg of args) { - arg = toNative(arg); - if(typeof arg === 'object') { - out += JSON.stringify(arg); - continue - } - out += `${arg}`; + // Invalid file type + if (!isValidFilePath(scriptname)) { + throw makeRuntimeErrorMsg("scp", `Invalid filename: '${scriptname}'`); + } + + // Invalid file name + if ( + !scriptname.endsWith(".lit") && + !isScriptFilename(scriptname) && + !scriptname.endsWith("txt") + ) { + throw makeRuntimeErrorMsg( + "scp", + "Only works for .script, .lit, and .txt files", + ); + } + + var destServer, currServ; + + if (ip2 != null) { + // 3 Argument version: scriptname, source, destination + if ( + scriptname === undefined || + ip1 === undefined || + ip2 === undefined + ) { + throw makeRuntimeErrorMsg("scp", "Takes 2 or 3 arguments"); + } + destServer = getServer(ip2); + if (destServer == null) { + throw makeRuntimeErrorMsg("scp", `Invalid IP/hostname: ${ip2}`); } - return out; - } + currServ = getServer(ip1); + if (currServ == null) { + throw makeRuntimeErrorMsg("scp", `Invalid IP/hostname: ${ip1}`); + } + } else if (ip1 != null) { + // 2 Argument version: scriptname, destination + if (scriptname === undefined || ip1 === undefined) { + throw makeRuntimeErrorMsg("scp", "Takes 2 or 3 arguments"); + } + destServer = getServer(ip1); + if (destServer == null) { + throw makeRuntimeErrorMsg("scp", `Invalid IP/hostname: ${ip1}`); + } - const functions = { - hacknet : { - numNodes : function() { - return Player.hacknetNodes.length; - }, - maxNumNodes : function() { - if (hasHacknetServers()) { - return HacknetServerConstants.MaxServers; - } - return Infinity; - }, - purchaseNode : function() { - return purchaseHacknet(); - }, - getPurchaseNodeCost : function() { - if (hasHacknetServers()) { - return getCostOfNextHacknetServer(); - } else { - return getCostOfNextHacknetNode(); - } - }, - getNodeStats : function(i) { - const node = getHacknetNode(i, "getNodeStats"); - const hasUpgraded = hasHacknetServers(); - const res = { - name: hasUpgraded ? node.hostname : node.name, - level: node.level, - ram: hasUpgraded ? node.maxRam : node.ram, - cores: node.cores, - production: hasUpgraded ? node.hashRate : node.moneyGainRatePerSecond, - timeOnline: node.onlineTimeSeconds, - totalProduction: hasUpgraded ? node.totalHashesGenerated : node.totalMoneyGenerated, - }; + currServ = getServer(workerScript.serverIp); + if (currServ == null) { + throw makeRuntimeErrorMsg( + "scp", + "Could not find server ip for this script. This is a bug. Report to dev.", + ); + } + } else { + throw makeRuntimeErrorMsg("scp", "Takes 2 or 3 arguments"); + } - if (hasUpgraded) { - res.cache = node.cache; - res.hashCapacity = node.hashCapacity; - } + // Scp for lit files + if (scriptname.endsWith(".lit")) { + var found = false; + for (var i = 0; i < currServ.messages.length; ++i) { + if ( + !(currServ.messages[i] instanceof Message) && + currServ.messages[i] == scriptname + ) { + found = true; + break; + } + } - return res; - }, - upgradeLevel : function(i, n) { - const node = getHacknetNode(i, "upgradeLevel"); - return purchaseLevelUpgrade(node, n); - }, - upgradeRam : function(i, n) { - const node = getHacknetNode(i, "upgradeRam"); - return purchaseRamUpgrade(node, n); - }, - upgradeCore : function(i, n) { - const node = getHacknetNode(i, "upgradeCore"); - return purchaseCoreUpgrade(node, n); - }, - upgradeCache : function(i, n) { - if (!hasHacknetServers()) { return false; } - const node = getHacknetNode(i, "upgradeCache"); - const res = purchaseCacheUpgrade(node, n); - if (res) { - updateHashManagerCapacity(); - } - return res; - }, - getLevelUpgradeCost : function(i, n) { - const node = getHacknetNode(i, "upgradeLevel"); - return node.calculateLevelUpgradeCost(n, Player.hacknet_node_level_cost_mult); - }, - getRamUpgradeCost : function(i, n) { - const node = getHacknetNode(i, "upgradeRam"); - return node.calculateRamUpgradeCost(n, Player.hacknet_node_ram_cost_mult); - }, - getCoreUpgradeCost : function(i, n) { - const node = getHacknetNode(i, "upgradeCore"); - return node.calculateCoreUpgradeCost(n, Player.hacknet_node_core_cost_mult); - }, - getCacheUpgradeCost : function(i, n) { - if (!hasHacknetServers()) { return Infinity; } - const node = getHacknetNode(i, "upgradeCache"); - return node.calculateCacheUpgradeCost(n); - }, - numHashes : function() { - if (!hasHacknetServers()) { return 0; } - return Player.hashManager.hashes; - }, - hashCapacity: function() { - if (!hasHacknetServers()) { return 0; } - return Player.hashManager.capacity; - }, - hashCost : function(upgName) { - if (!hasHacknetServers()) { return Infinity; } + if (!found) { + workerScript.log("scp", `File '${scriptname}' does not exist.`); + return false; + } - return Player.hashManager.getUpgradeCost(upgName); - }, - spendHashes : function(upgName, upgTarget) { - if (!hasHacknetServers()) { return false; } - return purchaseHashUpgrade(upgName, upgTarget); - }, - getHashUpgradeLevel : function(upgName) { - const level = Player.hashManager.upgrades[upgName]; - if(level === undefined) { - throw makeRuntimeErrorMsg("hacknet.hashUpgradeLevel", `Invalid Hash Upgrade: ${upgName}`); - } - return level; - }, - getStudyMult : function() { - if (!hasHacknetServers()) { return false; } - return Player.hashManager.getStudyMult(); - }, - getTrainingMult : function() { - if (!hasHacknetServers()) { return false; } - return Player.hashManager.getTrainingMult(); - }, - }, - sprintf : sprintf, - vsprintf: vsprintf, - scan : function(ip=workerScript.serverIp, hostnames=true) { - updateDynamicRam("scan", getRamCost("scan")); - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg('scan', `Invalid IP/hostname: ${ip}.`); + for (var i = 0; i < destServer.messages.length; ++i) { + if (destServer.messages[i] === scriptname) { + workerScript.log( + "scp", + `File '${scriptname}' copied over to '${destServer.hostname}'.`, + ); + return true; // Already exists + } + } + destServer.messages.push(scriptname); + workerScript.log( + "scp", + `File '${scriptname}' copied over to '${destServer.hostname}'.`, + ); + return true; + } + + // Scp for text files + if (scriptname.endsWith(".txt")) { + var found = false, + txtFile; + for (var i = 0; i < currServ.textFiles.length; ++i) { + if (currServ.textFiles[i].fn === scriptname) { + found = true; + txtFile = currServ.textFiles[i]; + break; + } + } + + if (!found) { + workerScript.log("scp", `File '${scriptname}' does not exist.`); + return false; + } + + for (var i = 0; i < destServer.textFiles.length; ++i) { + if (destServer.textFiles[i].fn === scriptname) { + // Overwrite + destServer.textFiles[i].text = txtFile.text; + workerScript.log( + "scp", + `File '${scriptname}' copied over to '${destServer.hostname}'.`, + ); + return true; + } + } + var newFile = new TextFile(txtFile.fn, txtFile.text); + destServer.textFiles.push(newFile); + workerScript.log( + "scp", + `File '${scriptname}' copied over to '${destServer.hostname}'.`, + ); + return true; + } + + // Scp for script files + let sourceScript = null; + for (let i = 0; i < currServ.scripts.length; ++i) { + if (scriptname == currServ.scripts[i].filename) { + sourceScript = currServ.scripts[i]; + break; + } + } + if (sourceScript == null) { + workerScript.log("scp", `File '${scriptname}' does not exist.`); + return false; + } + + // Overwrite script if it already exists + for (let i = 0; i < destServer.scripts.length; ++i) { + if (scriptname == destServer.scripts[i].filename) { + workerScript.log( + "scp", + `WARNING: File '${scriptname}' overwritten on '${destServer.hostname}'`, + ); + const oldScript = destServer.scripts[i]; + // If it's the exact same file don't actually perform the + // copy to avoid recompiling uselessly. Players tend to scp + // liberally. + if (oldScript.code === sourceScript.code) return true; + oldScript.code = sourceScript.code; + oldScript.ramUsage = sourceScript.ramUsage; + oldScript.markUpdated(); + return true; + } + } + + // Create new script if it does not already exist + const newScript = new Script(scriptname); + newScript.code = sourceScript.code; + newScript.ramUsage = sourceScript.ramUsage; + newScript.server = destServer.ip; + destServer.scripts.push(newScript); + workerScript.log( + "scp", + `File '${scriptname}' copied over to '${destServer.hostname}'.`, + ); + return true; + }, + ls: function (ip, grep) { + updateDynamicRam("ls", getRamCost("ls")); + if (ip === undefined) { + throw makeRuntimeErrorMsg( + "ls", + "Usage: ls(ip/hostname, [grep filter])", + ); + } + const server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("ls", `Invalid IP/hostname: ${ip}`); + } + + // Get the grep filter, if one exists + let filter = false; + if (arguments.length >= 2) { + filter = grep.toString(); + } + + const allFiles = []; + for (let i = 0; i < server.programs.length; i++) { + if (filter) { + if (server.programs[i].includes(filter)) { + allFiles.push(server.programs[i]); + } + } else { + allFiles.push(server.programs[i]); + } + } + for (let i = 0; i < server.scripts.length; i++) { + if (filter) { + if (server.scripts[i].filename.includes(filter)) { + allFiles.push(server.scripts[i].filename); + } + } else { + allFiles.push(server.scripts[i].filename); + } + } + for (let i = 0; i < server.messages.length; i++) { + if (filter) { + if (server.messages[i] instanceof Message) { + if (server.messages[i].filename.includes(filter)) { + allFiles.push(server.messages[i].filename); } - var out = []; - for (var i = 0; i < server.serversOnNetwork.length; i++) { - var entry; - if (hostnames) { - entry = getServerOnNetwork(server, i).hostname; - } else { - entry = getServerOnNetwork(server, i).ip; - } - if (entry == null) { - continue; - } - out.push(entry); - } - workerScript.log("scan", `returned ${server.serversOnNetwork.length} connections for ${server.hostname}`); - return out; - }, - hack : function(ip, { threads: requestedThreads, stock } = {}){ - updateDynamicRam("hack", getRamCost("hack")); - return hack(ip, false, {threads: requestedThreads, stock: stock}); - }, - hackAnalyzeThreads : function(ip, hackAmount) { - updateDynamicRam("hackAnalyzeThreads", getRamCost("hackAnalyzeThreads")); + } else if (server.messages[i].includes(filter)) { + allFiles.push(server.messages[i]); + } + } else { + if (server.messages[i] instanceof Message) { + allFiles.push(server.messages[i].filename); + } else { + allFiles.push(server.messages[i]); + } + } + } - // Check argument validity - const server = safeGetServer(ip, 'hackAnalyzeThreads'); - if (isNaN(hackAmount)) { - throw makeRuntimeErrorMsg(workerScript, `Invalid growth argument passed into hackAnalyzeThreads: ${hackAmount}. Must be numeric.`); - } + for (var i = 0; i < server.textFiles.length; i++) { + if (filter) { + if (server.textFiles[i].fn.includes(filter)) { + allFiles.push(server.textFiles[i].fn); + } + } else { + allFiles.push(server.textFiles[i].fn); + } + } - if (hackAmount < 0 || hackAmount > server.moneyAvailable) { - return -1; - } + for (var i = 0; i < server.contracts.length; ++i) { + if (filter) { + if (server.contracts[i].fn.includes(filter)) { + allFiles.push(server.contracts[i].fn); + } + } else { + allFiles.push(server.contracts[i].fn); + } + } - const percentHacked = calculatePercentMoneyHacked(server, Player); + // Sort the files alphabetically then print each + allFiles.sort(); + return allFiles; + }, + ps: function (ip = workerScript.serverIp) { + updateDynamicRam("ps", getRamCost("ps")); + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("ps", `Invalid IP/hostname: ${ip}`); + } + const processes = []; + for (const i in server.runningScripts) { + const script = server.runningScripts[i]; + processes.push({ + filename: script.filename, + threads: script.threads, + args: script.args.slice(), + pid: script.pid, + }); + } + return processes; + }, + hasRootAccess: function (ip) { + updateDynamicRam("hasRootAccess", getRamCost("hasRootAccess")); + if (ip === undefined) { + throw makeRuntimeErrorMsg("hasRootAccess", "Takes 1 argument"); + } + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg( + "hasRootAccess", + `Invalid IP/hostname: ${ip}`, + ); + } + return server.hasAdminRights; + }, + getIp: function () { + updateDynamicRam("getIp", getRamCost("getIp")); + var scriptServer = getServer(workerScript.serverIp); + if (scriptServer == null) { + throw makeRuntimeErrorMsg( + "getIp", + "Could not find server. This is a bug. Report to dev.", + ); + } + return scriptServer.ip; + }, + getHostname: function () { + updateDynamicRam("getHostname", getRamCost("getHostname")); + var scriptServer = getServer(workerScript.serverIp); + if (scriptServer == null) { + throw makeRuntimeErrorMsg( + workerScript, + "Could not find server. This is a bug. Report to dev.", + ); + } + return scriptServer.hostname; + }, + getHackingLevel: function () { + updateDynamicRam("getHackingLevel", getRamCost("getHackingLevel")); + Player.updateSkillLevels(); + workerScript.log("getHackingLevel", `returned ${Player.hacking_skill}`); + return Player.hacking_skill; + }, + getHackingMultipliers: function () { + updateDynamicRam( + "getHackingMultipliers", + getRamCost("getHackingMultipliers"), + ); + return { + chance: Player.hacking_chance_mult, + speed: Player.hacking_speed_mult, + money: Player.hacking_money_mult, + growth: Player.hacking_grow_mult, + }; + }, + getHacknetMultipliers: function () { + updateDynamicRam( + "getHacknetMultipliers", + getRamCost("getHacknetMultipliers"), + ); + return { + production: Player.hacknet_node_money_mult, + purchaseCost: Player.hacknet_node_purchase_cost_mult, + ramCost: Player.hacknet_node_ram_cost_mult, + coreCost: Player.hacknet_node_core_cost_mult, + levelCost: Player.hacknet_node_level_cost_mult, + }; + }, + getBitNodeMultipliers: function () { + updateDynamicRam( + "getBitNodeMultipliers", + getRamCost("getBitNodeMultipliers"), + ); + if (SourceFileFlags[5] <= 0 && Player.bitNodeN !== 5) { + throw makeRuntimeErrorMsg( + "getBitNodeMultipliers", + "Requires Source-File 5 to run.", + ); + } + let copy = Object.assign({}, BitNodeMultipliers); + return copy; + }, + getServer: function (ip) { + updateDynamicRam("getServer", getRamCost("getServer")); + if (SourceFileFlags[5] <= 0 && Player.bitNodeN !== 5) { + throw makeRuntimeErrorMsg( + "getServer", + "Requires Source-File 5 to run.", + ); + } + const server = safeGetServer(ip, "getServer"); + const copy = Object.assign({}, server); + // These fields should be hidden. + copy.contracts = undefined; + copy.messages = undefined; + copy.runningScripts = undefined; + copy.scripts = undefined; + copy.textFiles = undefined; + copy.programs = undefined; + copy.serversOnNetwork = undefined; + return copy; + }, + getServerMoneyAvailable: function (ip) { + updateDynamicRam( + "getServerMoneyAvailable", + getRamCost("getServerMoneyAvailable"), + ); + const server = safeGetServer(ip, "getServerMoneyAvailable"); + if (failOnHacknetServer(server, "getServerMoneyAvailable")) { + return 0; + } + if (server.hostname == "home") { + // Return player's money + workerScript.log( + "getServerMoneyAvailable", + `returned player's money: ${numeralWrapper.formatMoney( + Player.money.toNumber(), + )}`, + ); + return Player.money.toNumber(); + } + workerScript.log( + "getServerMoneyAvailable", + `returned ${numeralWrapper.formatMoney(server.moneyAvailable)} for '${ + server.hostname + }'`, + ); + return server.moneyAvailable; + }, + getServerSecurityLevel: function (ip) { + updateDynamicRam( + "getServerSecurityLevel", + getRamCost("getServerSecurityLevel"), + ); + const server = safeGetServer(ip, "getServerSecurityLevel"); + if (failOnHacknetServer(server, "getServerSecurityLevel")) { + return 1; + } + workerScript.log( + "getServerSecurityLevel", + `returned ${numeralWrapper.formatServerSecurity( + server.hackDifficulty, + 3, + )} for '${server.hostname}'`, + ); + return server.hackDifficulty; + }, + getServerBaseSecurityLevel: function (ip) { + updateDynamicRam( + "getServerBaseSecurityLevel", + getRamCost("getServerBaseSecurityLevel"), + ); + const server = safeGetServer(ip, "getServerBaseSecurityLevel"); + if (failOnHacknetServer(server, "getServerBaseSecurityLevel")) { + return 1; + } + workerScript.log( + "getServerBaseSecurityLevel", + `returned ${numeralWrapper.formatServerSecurity( + server.baseDifficulty, + 3, + )} for '${server.hostname}'`, + ); + return server.baseDifficulty; + }, + getServerMinSecurityLevel: function (ip) { + updateDynamicRam( + "getServerMinSecurityLevel", + getRamCost("getServerMinSecurityLevel"), + ); + const server = safeGetServer(ip, "getServerMinSecurityLevel"); + if (failOnHacknetServer(server, "getServerMinSecurityLevel")) { + return 1; + } + workerScript.log( + "getServerMinSecurityLevel", + `returned ${numeralWrapper.formatServerSecurity( + server.minDifficulty, + 3, + )} for ${server.hostname}`, + ); + return server.minDifficulty; + }, + getServerRequiredHackingLevel: function (ip) { + updateDynamicRam( + "getServerRequiredHackingLevel", + getRamCost("getServerRequiredHackingLevel"), + ); + const server = safeGetServer(ip, "getServerRequiredHackingLevel"); + if (failOnHacknetServer(server, "getServerRequiredHackingLevel")) { + return 1; + } + workerScript.log( + "getServerRequiredHackingLevel", + `returned ${numeralWrapper.formatSkill( + server.requiredHackingSkill, + 0, + )} for '${server.hostname}'`, + ); + return server.requiredHackingSkill; + }, + getServerMaxMoney: function (ip) { + updateDynamicRam("getServerMaxMoney", getRamCost("getServerMaxMoney")); + const server = safeGetServer(ip, "getServerMaxMoney"); + if (failOnHacknetServer(server, "getServerMaxMoney")) { + return 0; + } + workerScript.log( + "getServerMaxMoney", + `returned ${numeralWrapper.formatMoney(server.moneyMax)} for '${ + server.hostname + }'`, + ); + return server.moneyMax; + }, + getServerGrowth: function (ip) { + updateDynamicRam("getServerGrowth", getRamCost("getServerGrowth")); + const server = safeGetServer(ip, "getServerGrowth"); + if (failOnHacknetServer(server, "getServerGrowth")) { + return 1; + } + workerScript.log( + "getServerGrowth", + `returned ${server.serverGrowth} for '${server.hostname}'`, + ); + return server.serverGrowth; + }, + getServerNumPortsRequired: function (ip) { + updateDynamicRam( + "getServerNumPortsRequired", + getRamCost("getServerNumPortsRequired"), + ); + const server = safeGetServer(ip, "getServerNumPortsRequired"); + if (failOnHacknetServer(server, "getServerNumPortsRequired")) { + return 5; + } + workerScript.log( + "getServerNumPortsRequired", + `returned ${server.numOpenPortsRequired} for '${server.hostname}'`, + ); + return server.numOpenPortsRequired; + }, + getServerRam: function (ip) { + updateDynamicRam("getServerRam", getRamCost("getServerRam")); + const server = safeGetServer(ip, "getServerRam"); + workerScript.log( + "getServerRam", + `returned [${numeralWrapper.formatRAM( + server.maxRam, + 2, + )}, ${numeralWrapper.formatRAM(server.ramUsed, 2)}]`, + ); + return [server.maxRam, server.ramUsed]; + }, + getServerMaxRam: function (ip) { + updateDynamicRam("getServerMaxRam", getRamCost("getServerMaxRam")); + const server = safeGetServer(ip, "getServerMaxRam"); + workerScript.log( + "getServerMaxRam", + `returned ${numeralWrapper.formatRAM(server.maxRam, 2)}`, + ); + return server.maxRam; + }, + getServerUsedRam: function (ip) { + updateDynamicRam("getServerUsedRam", getRamCost("getServerUsedRam")); + const server = safeGetServer(ip, "getServerUsedRam"); + workerScript.log( + "getServerUsedRam", + `returned ${numeralWrapper.formatRAM(server.ramUsed, 2)}`, + ); + return server.ramUsed; + }, + serverExists: function (ip) { + updateDynamicRam("serverExists", getRamCost("serverExists")); + return getServer(ip) !== null; + }, + fileExists: function (filename, ip = workerScript.serverIp) { + updateDynamicRam("fileExists", getRamCost("fileExists")); + if (filename === undefined) { + throw makeRuntimeErrorMsg( + "fileExists", + "Usage: fileExists(scriptname, [server])", + ); + } + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("fileExists", `Invalid IP/hostname: ${ip}`); + } + for (var i = 0; i < server.scripts.length; ++i) { + if (filename == server.scripts[i].filename) { + return true; + } + } + for (var i = 0; i < server.programs.length; ++i) { + if (filename.toLowerCase() == server.programs[i].toLowerCase()) { + return true; + } + } + for (var i = 0; i < server.messages.length; ++i) { + if ( + !(server.messages[i] instanceof Message) && + filename.toLowerCase() === server.messages[i] + ) { + return true; + } + } + var txtFile = getTextFile(filename, server); + if (txtFile != null) { + return true; + } + return false; + }, + isRunning: function (fn, ip = workerScript.serverIp, ...scriptArgs) { + updateDynamicRam("isRunning", getRamCost("isRunning")); + if (fn === undefined || ip === undefined) { + throw makeRuntimeErrorMsg( + "isRunning", + "Usage: isRunning(scriptname, server, [arg1], [arg2]...)", + ); + } + if (typeof fn === "number") { + return getRunningScriptByPid(fn, "isRunning") != null; + } else { + return getRunningScript(fn, ip, "isRunning", scriptArgs) != null; + } + }, + getStockSymbols: function () { + updateDynamicRam("getStockSymbols", getRamCost("getStockSymbols")); + checkTixApiAccess("getStockSymbols"); + return Object.values(StockSymbols); + }, + getStockPrice: function (symbol) { + updateDynamicRam("getStockPrice", getRamCost("getStockPrice")); + checkTixApiAccess("getStockPrice"); + const stock = getStockFromSymbol(symbol, "getStockPrice"); - return hackAmount / Math.floor(server.moneyAvailable * percentHacked); - }, - hackAnalyzePercent : function(ip) { - updateDynamicRam("hackAnalyzePercent", getRamCost("hackAnalyzePercent")); + return stock.price; + }, + getStockAskPrice: function (symbol) { + updateDynamicRam("getStockAskPrice", getRamCost("getStockAskPrice")); + checkTixApiAccess("getStockAskPrice"); + const stock = getStockFromSymbol(symbol, "getStockAskPrice"); - const server = safeGetServer(ip, 'hackAnalyzePercent'); + return stock.getAskPrice(); + }, + getStockBidPrice: function (symbol) { + updateDynamicRam("getStockBidPrice", getRamCost("getStockBidPrice")); + checkTixApiAccess("getStockBidPrice"); + const stock = getStockFromSymbol(symbol, "getStockBidPrice"); - return calculatePercentMoneyHacked(server, Player) * 100; - }, - hackChance : function(ip) { - updateDynamicRam("hackChance", getRamCost("hackChance")); + return stock.getBidPrice(); + }, + getStockPosition: function (symbol) { + updateDynamicRam("getStockPosition", getRamCost("getStockPosition")); + checkTixApiAccess("getStockPosition"); + var stock = SymbolToStockMap[symbol]; + if (stock == null) { + throw makeRuntimeErrorMsg( + "getStockPosition", + `Invalid stock symbol: ${symbol}`, + ); + } + return [ + stock.playerShares, + stock.playerAvgPx, + stock.playerShortShares, + stock.playerAvgShortPx, + ]; + }, + getStockMaxShares: function (symbol) { + updateDynamicRam("getStockMaxShares", getRamCost("getStockMaxShares")); + checkTixApiAccess("getStockMaxShares"); + const stock = getStockFromSymbol(symbol, "getStockMaxShares"); - const server = safeGetServer(ip, 'hackChance'); + return stock.maxShares; + }, + getStockPurchaseCost: function (symbol, shares, posType) { + updateDynamicRam( + "getStockPurchaseCost", + getRamCost("getStockPurchaseCost"), + ); + checkTixApiAccess("getStockPurchaseCost"); + const stock = getStockFromSymbol(symbol, "getStockPurchaseCost"); + shares = Math.round(shares); - return calculateHackingChance(server, Player); - }, - sleep : function(time){ - if (time === undefined) { - throw makeRuntimeErrorMsg("sleep", "Takes 1 argument."); - } - workerScript.log("sleep", `Sleeping for ${time} milliseconds`); - return netscriptDelay(time, workerScript).then(function() { - return Promise.resolve(true); + let pos; + const sanitizedPosType = posType.toLowerCase(); + if (sanitizedPosType.includes("l")) { + pos = PositionTypes.Long; + } else if (sanitizedPosType.includes("s")) { + pos = PositionTypes.Short; + } else { + return Infinity; + } + + const res = getBuyTransactionCost(stock, shares, pos); + if (res == null) { + return Infinity; + } + + return res; + }, + getStockSaleGain: function (symbol, shares, posType) { + updateDynamicRam("getStockSaleGain", getRamCost("getStockSaleGain")); + checkTixApiAccess("getStockSaleGain"); + const stock = getStockFromSymbol(symbol, "getStockSaleGain"); + shares = Math.round(shares); + + let pos; + const sanitizedPosType = posType.toLowerCase(); + if (sanitizedPosType.includes("l")) { + pos = PositionTypes.Long; + } else if (sanitizedPosType.includes("s")) { + pos = PositionTypes.Short; + } else { + return 0; + } + + const res = getSellTransactionGain(stock, shares, pos); + if (res == null) { + return 0; + } + + return res; + }, + buyStock: function (symbol, shares) { + updateDynamicRam("buyStock", getRamCost("buyStock")); + checkTixApiAccess("buyStock"); + const stock = getStockFromSymbol(symbol, "buyStock"); + const res = buyStock(stock, shares, workerScript, { + rerenderFn: displayStockMarketContent, + }); + return res ? stock.price : 0; + }, + sellStock: function (symbol, shares) { + updateDynamicRam("sellStock", getRamCost("sellStock")); + checkTixApiAccess("sellStock"); + const stock = getStockFromSymbol(symbol, "sellStock"); + const res = sellStock(stock, shares, workerScript, { + rerenderFn: displayStockMarketContent, + }); + + return res ? stock.price : 0; + }, + shortStock: function (symbol, shares) { + updateDynamicRam("shortStock", getRamCost("shortStock")); + checkTixApiAccess("shortStock"); + if (Player.bitNodeN !== 8) { + if (SourceFileFlags[8] <= 1) { + throw makeRuntimeErrorMsg( + shortStock, + "You must either be in BitNode-8 or you must have Source-File 8 Level 2.", + ); + } + } + const stock = getStockFromSymbol(symbol, "shortStock"); + const res = shortStock(stock, shares, workerScript, { + rerenderFn: displayStockMarketContent, + }); + + return res ? stock.price : 0; + }, + sellShort: function (symbol, shares) { + updateDynamicRam("sellShort", getRamCost("sellShort")); + checkTixApiAccess("sellShort"); + if (Player.bitNodeN !== 8) { + if (SourceFileFlags[8] <= 1) { + throw makeRuntimeErrorMsg( + "sellShort", + "You must either be in BitNode-8 or you must have Source-File 8 Level 2.", + ); + } + } + const stock = getStockFromSymbol(symbol, "sellShort"); + const res = sellShort(stock, shares, workerScript, { + rerenderFn: displayStockMarketContent, + }); + + return res ? stock.price : 0; + }, + placeOrder: function (symbol, shares, price, type, pos) { + updateDynamicRam("placeOrder", getRamCost("placeOrder")); + checkTixApiAccess("placeOrder"); + if (Player.bitNodeN !== 8) { + if (SourceFileFlags[8] <= 2) { + throw makeRuntimeErrorMsg( + "placeOrder", + "You must either be in BitNode-8 or you must have Source-File 8 Level 3.", + ); + } + } + const stock = getStockFromSymbol(symbol, "placeOrder"); + + let orderType, orderPos; + ltype = type.toLowerCase(); + if (ltype.includes("limit") && ltype.includes("buy")) { + orderType = OrderTypes.LimitBuy; + } else if (ltype.includes("limit") && ltype.includes("sell")) { + orderType = OrderTypes.LimitSell; + } else if (ltype.includes("stop") && ltype.includes("buy")) { + orderType = OrderTypes.StopBuy; + } else if (ltype.includes("stop") && ltype.includes("sell")) { + orderType = OrderTypes.StopSell; + } else { + throw makeRuntimeErrorMsg("placeOrder", `Invalid order type: ${type}`); + } + + lpos = pos.toLowerCase(); + if (lpos.includes("l")) { + orderPos = PositionTypes.Long; + } else if (lpos.includes("s")) { + orderPos = PositionTypes.Short; + } else { + throw makeRuntimeErrorMsg( + "placeOrder", + `Invalid position type: ${pos}`, + ); + } + + return placeOrder( + stock, + shares, + price, + orderType, + orderPos, + workerScript, + ); + }, + cancelOrder: function (symbol, shares, price, type, pos) { + updateDynamicRam("cancelOrder", getRamCost("cancelOrder")); + checkTixApiAccess("cancelOrder"); + if (Player.bitNodeN !== 8) { + if (SourceFileFlags[8] <= 2) { + throw makeRuntimeErrorMsg( + "cancelOrder", + "You must either be in BitNode-8 or you must have Source-File 8 Level 3.", + ); + } + } + const stock = getStockFrom(symbol, "cancelOrder"); + if (isNaN(shares) || isNaN(price)) { + throw makeRuntimeErrorMsg( + "cancelOrder", + `Invalid shares or price. Must be numeric. shares=${shares}, price=${price}`, + ); + } + var orderType, orderPos; + ltype = type.toLowerCase(); + if (ltype.includes("limit") && ltype.includes("buy")) { + orderType = OrderTypes.LimitBuy; + } else if (ltype.includes("limit") && ltype.includes("sell")) { + orderType = OrderTypes.LimitSell; + } else if (ltype.includes("stop") && ltype.includes("buy")) { + orderType = OrderTypes.StopBuy; + } else if (ltype.includes("stop") && ltype.includes("sell")) { + orderType = OrderTypes.StopSell; + } else { + throw makeRuntimeErrorMsg("cancelOrder", `Invalid order type: ${type}`); + } + + lpos = pos.toLowerCase(); + if (lpos.includes("l")) { + orderPos = PositionTypes.Long; + } else if (lpos.includes("s")) { + orderPos = PositionTypes.Short; + } else { + throw makeRuntimeErrorMsg( + "cancelOrder", + `Invalid position type: ${pos}`, + ); + } + var params = { + stock: stock, + shares: shares, + price: price, + type: orderType, + pos: orderPos, + }; + return cancelOrder(params, workerScript); + }, + getOrders: function () { + updateDynamicRam("getOrders", getRamCost("getOrders")); + checkTixApiAccess("getOrders"); + if (Player.bitNodeN !== 8) { + if (SourceFileFlags[8] <= 2) { + throw makeRuntimeErrorMsg( + workerScript, + "You must either be in BitNode-8 or have Source-File 8 Level 3.", + ); + } + } + + const orders = {}; + + const stockMarketOrders = StockMarket["Orders"]; + for (let symbol in stockMarketOrders) { + const orderBook = stockMarketOrders[symbol]; + if (orderBook.constructor === Array && orderBook.length > 0) { + orders[symbol] = []; + for (let i = 0; i < orderBook.length; ++i) { + orders[symbol].push({ + shares: orderBook[i].shares, + price: orderBook[i].price, + type: orderBook[i].type, + position: orderBook[i].pos, }); - }, - grow : function(ip, { threads: requestedThreads, stock } = {}){ - updateDynamicRam("grow", getRamCost("grow")); - const threads = resolveNetscriptRequestedThreads(workerScript, "grow", requestedThreads); - if (ip === undefined) { - throw makeRuntimeErrorMsg("grow", "Takes 1 argument."); - } - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("grow", `Invalid IP/hostname: ${ip}.`); - } + } + } + } - const host = getServer(workerScript.serverIp); + return orders; + }, + getStockVolatility: function (symbol) { + updateDynamicRam("getStockVolatility", getRamCost("getStockVolatility")); + if (!Player.has4SDataTixApi) { + throw makeRuntimeErrorMsg( + "getStockVolatility", + "You don't have 4S Market Data TIX API Access!", + ); + } + const stock = getStockFromSymbol(symbol, "getStockVolatility"); - // No root access or skill level too low - const canHack = netscriptCanGrow(server); - if (!canHack.res) { - throw makeRuntimeErrorMsg("grow", canHack.msg); - } + return stock.mv / 100; // Convert from percentage to decimal + }, + getStockForecast: function (symbol) { + updateDynamicRam("getStockForecast", getRamCost("getStockForecast")); + if (!Player.has4SDataTixApi) { + throw makeRuntimeErrorMsg( + "getStockForecast", + "You don't have 4S Market Data TIX API Access!", + ); + } + const stock = getStockFromSymbol(symbol, "getStockForecast"); - var growTime = calculateGrowTime(server, Player); - workerScript.log("grow", `Executing on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(growTime*1000, true)} (t=${numeralWrapper.formatThreads(threads)}).`); - return netscriptDelay(growTime * 1000, workerScript).then(function() { - if (workerScript.env.stopFlag) {return Promise.reject(workerScript);} - const moneyBefore = server.moneyAvailable <= 0 ? 1 : server.moneyAvailable; - server.moneyAvailable += (1 * threads); // It can be grown even if it has no money - processSingleServerGrowth(server, threads, Player, host.cpuCores); - const moneyAfter = server.moneyAvailable; - workerScript.scriptRef.recordGrow(server.ip, threads); - var expGain = calculateHackingExpGain(server, Player) * threads; - const logGrowPercent = (moneyAfter/moneyBefore) - 1; - workerScript.log("grow", `Available money on '${server.hostname}' grown by ${numeralWrapper.formatPercentage(logGrowPercent, 6)}. Gained ${numeralWrapper.formatExp(expGain)} hacking exp (t=${numeralWrapper.formatThreads(threads)}).`); - workerScript.scriptRef.onlineExpGained += expGain; - Player.gainHackingExp(expGain); - if (stock) { - influenceStockThroughServerGrow(server, moneyAfter - moneyBefore); - } - return Promise.resolve(moneyAfter/moneyBefore); - }); - }, - growthAnalyze : function(ip, growth) { - updateDynamicRam("growthAnalyze", getRamCost("growthAnalyze")); + var forecast = 50; + stock.b ? (forecast += stock.otlkMag) : (forecast -= stock.otlkMag); + return forecast / 100; // Convert from percentage to decimal + }, + purchase4SMarketData: function () { + updateDynamicRam( + "purchase4SMarketData", + getRamCost("purchase4SMarketData"), + ); + checkTixApiAccess("purchase4SMarketData"); - // Check argument validity - const server = safeGetServer(ip, 'growthAnalyze'); - if (typeof growth !== "number" || isNaN(growth) || growth < 1 || !isFinite(growth)) { - throw makeRuntimeErrorMsg("growthAnalyze", `Invalid argument: growth must be numeric and >= 1, is ${growth}.`); - } + if (Player.has4SData) { + workerScript.log( + "purchase4SMarketData", + "Already purchased 4S Market Data.", + ); + return true; + } - return numCycleForGrowth(server, Number(growth), Player); - }, - weaken : function(ip, { threads: requestedThreads } = {}) { - updateDynamicRam("weaken", getRamCost("weaken")); - var threads = resolveNetscriptRequestedThreads(workerScript, "weaken", requestedThreads) - if (ip === undefined) { - throw makeRuntimeErrorMsg("weaken", "Takes 1 argument."); - } - const server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("weaken", `Invalid IP/hostname: ${ip}`); - } + if (Player.money.lt(getStockMarket4SDataCost())) { + workerScript.log( + "purchase4SMarketData", + "Not enough money to purchase 4S Market Data.", + ); + return false; + } - // No root access or skill level too low - const canHack = netscriptCanWeaken(server); - if (!canHack.res) { - throw makeRuntimeErrorMsg("weaken", canHack.msg); - } + Player.has4SData = true; + Player.loseMoney(getStockMarket4SDataCost()); + workerScript.log("purchase4SMarketData", "Purchased 4S Market Data"); + displayStockMarketContent(); + return true; + }, + purchase4SMarketDataTixApi: function () { + updateDynamicRam( + "purchase4SMarketDataTixApi", + getRamCost("purchase4SMarketDataTixApi"), + ); + checkTixApiAccess("purchase4SMarketDataTixApi"); - const weakenTime = calculateWeakenTime(server, Player); - workerScript.log("weaken", `Executing on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(weakenTime*1000, true)} (t=${numeralWrapper.formatThreads(threads)})`); - return netscriptDelay(weakenTime * 1000, workerScript).then(function() { - if (workerScript.env.stopFlag) return Promise.reject(workerScript); - const host = getServer(workerScript.serverIp); - const coreBonus = 1+(host.cpuCores-1)/16; - server.weaken(CONSTANTS.ServerWeakenAmount * threads * coreBonus); - workerScript.scriptRef.recordWeaken(server.ip, threads); - const expGain = calculateHackingExpGain(server, Player) * threads; - workerScript.log("weaken", `'${server.hostname}' security level weakened to ${server.hackDifficulty}. Gained ${numeralWrapper.formatExp(expGain)} hacking exp (t=${numeralWrapper.formatThreads(threads)})`); - workerScript.scriptRef.onlineExpGained += expGain; - Player.gainHackingExp(expGain); - return Promise.resolve(CONSTANTS.ServerWeakenAmount * threads); - }); - }, - print: function(){ - if (arguments.length === 0) { - throw makeRuntimeErrorMsg("print", "Takes at least 1 argument."); - } - workerScript.print(argsToString(arguments)); - }, - tprint: function() { - if (arguments.length === 0) { - throw makeRuntimeErrorMsg("tprint", "Takes at least 1 argument."); - } - post(`${workerScript.scriptRef.filename}: ${argsToString(arguments)}`); - }, - tprintf: function(format, ...args) { - post(vsprintf(format, args)); - }, - clearLog: function() { - workerScript.scriptRef.clearLog(); - }, - disableLog: function(fn) { - if (fn === "ALL") { - for (fn in possibleLogs) { - workerScript.disableLogs[fn] = true; - } - workerScript.log("disableLog", `Disabled logging for all functions`); - } else if (possibleLogs[fn] === undefined) { - throw makeRuntimeErrorMsg("disableLog", `Invalid argument: ${fn}.`); - } else { - workerScript.disableLogs[fn] = true; - workerScript.log("disableLog", `Disabled logging for ${fn}`); - } - }, - enableLog: function(fn) { - if (possibleLogs[fn]===undefined) { - throw makeRuntimeErrorMsg("enableLog", `Invalid argument: ${fn}.`); - } - delete workerScript.disableLogs[fn]; - workerScript.log("enableLog", `Enabled logging for ${fn}`); - }, - isLogEnabled : function(fn) { - if (possibleLogs[fn] === undefined) { - throw makeRuntimeErrorMsg("isLogEnabled", `Invalid argument: ${fn}.`); - } - return workerScript.disableLogs[fn] ? false : true; - }, - getScriptLogs: function(fn, ip, ...scriptArgs) { - const runningScriptObj = getRunningScript(fn, ip, "getScriptLogs", scriptArgs); - if (runningScriptObj == null) { - workerScript.log("getScriptLogs", getCannotFindRunningScriptErrorMessage(fn, ip, scriptArgs)); - return ""; - } + if (Player.has4SDataTixApi) { + workerScript.log( + "purchase4SMarketDataTixApi", + "Already purchased 4S Market Data TIX API", + ); + return true; + } - return runningScriptObj.logs.slice(); - }, - tail: function(fn, ip=workerScript.serverIp, ...scriptArgs) { - let runningScriptObj; - if(arguments.length === 0) { - runningScriptObj = workerScript.scriptRef; - } else if(typeof fn === 'number') { - runningScriptObj = getRunningScriptByPid(fn, 'tail'); - } else { - runningScriptObj = getRunningScript(fn, ip, "tail", scriptArgs); - } - if (runningScriptObj == null) { - workerScript.log("tail", getCannotFindRunningScriptErrorMessage(fn, ip, scriptArgs)); - return; - } + if (Player.money.lt(getStockMarket4STixApiCost())) { + workerScript.log( + "purchase4SMarketDataTixApi", + "Not enough money to purchase 4S Market Data TIX API", + ); + return false; + } - logBoxCreate(runningScriptObj); - }, - nuke: function(ip){ - updateDynamicRam("nuke", getRamCost("nuke")); - if (ip === undefined) { - throw makeRuntimeErrorMsg("nuke", "Takes 1 argument."); - } - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("nuke", `Invalid IP/hostname: ${ip}.`); - } - if (!Player.hasProgram(Programs.NukeProgram.name)) { - throw makeRuntimeErrorMsg("nuke", "You do not have the NUKE.exe virus!"); - } - if (server.openPortCount < server.numOpenPortsRequired) { - throw makeRuntimeErrorMsg("nuke", "Not enough ports opened to use NUKE.exe virus."); - } - if (server.hasAdminRights) { - workerScript.log("nuke", `Already have root access to '${server.hostname}'.`); - } else { - server.hasAdminRights = true; - workerScript.log("nuke", `Executed NUKE.exe virus on '${server.hostname}' to gain root access.`); - } + Player.has4SDataTixApi = true; + Player.loseMoney(getStockMarket4STixApiCost()); + workerScript.log( + "purchase4SMarketDataTixApi", + "Purchased 4S Market Data TIX API", + ); + displayStockMarketContent(); + return true; + }, + getPurchasedServerLimit: function () { + updateDynamicRam( + "getPurchasedServerLimit", + getRamCost("getPurchasedServerLimit"), + ); + + return getPurchaseServerLimit(); + }, + getPurchasedServerMaxRam: function () { + updateDynamicRam( + "getPurchasedServerMaxRam", + getRamCost("getPurchasedServerMaxRam"), + ); + + return getPurchaseServerMaxRam(); + }, + getPurchasedServerCost: function (ram) { + updateDynamicRam( + "getPurchasedServerCost", + getRamCost("getPurchasedServerCost"), + ); + + const cost = getPurchaseServerCost(ram); + if (cost === Infinity) { + workerScript.log( + "getPurchasedServerCost", + `Invalid argument: ram='${ram}'`, + ); + return Infinity; + } + + return cost; + }, + purchaseServer: function (hostname, ram) { + updateDynamicRam("purchaseServer", getRamCost("purchaseServer")); + var hostnameStr = String(hostname); + hostnameStr = hostnameStr.replace(/\s+/g, ""); + if (hostnameStr == "") { + workerScript.log( + "purchaseServer", + `Invalid argument: hostname='${hostnameStr}'`, + ); + return ""; + } + + if (Player.purchasedServers.length >= getPurchaseServerLimit()) { + workerScript.log( + "purchaseServer", + `You have reached the maximum limit of ${getPurchaseServerLimit()} servers. You cannot purchase any more.`, + ); + return ""; + } + + const cost = getPurchaseServerCost(ram); + if (cost === Infinity) { + workerScript.log("purchaseServer", `Invalid argument: ram='${ram}'`); + return ""; + } + + if (Player.money.lt(cost)) { + workerScript.log( + "purchaseServer", + `Not enough money to purchase server. Need ${numeralWrapper.formatMoney( + cost, + )}`, + ); + return ""; + } + var newServ = safetlyCreateUniqueServer({ + ip: createUniqueRandomIp(), + hostname: hostnameStr, + organizationName: "", + isConnectedTo: false, + adminRights: true, + purchasedByPlayer: true, + maxRam: ram, + }); + AddToAllServers(newServ); + + Player.purchasedServers.push(newServ.ip); + var homeComputer = Player.getHomeComputer(); + homeComputer.serversOnNetwork.push(newServ.ip); + newServ.serversOnNetwork.push(homeComputer.ip); + Player.loseMoney(cost); + workerScript.log( + "purchaseServer", + `Purchased new server with hostname '${ + newServ.hostname + }' for ${numeralWrapper.formatMoney(cost)}`, + ); + return newServ.hostname; + }, + deleteServer: function (hostname) { + updateDynamicRam("deleteServer", getRamCost("deleteServer")); + var hostnameStr = String(hostname); + hostnameStr = hostnameStr.replace(/\s\s+/g, ""); + var server = GetServerByHostname(hostnameStr); + if (server == null) { + workerScript.log( + "deleteServer", + `Invalid argument: hostname='${hostnameStr}'`, + ); + return false; + } + + if (!server.purchasedByPlayer || server.hostname === "home") { + workerScript.log("deleteServer", "Cannot delete non-purchased server."); + return false; + } + + var ip = server.ip; + + // Can't delete server you're currently connected to + if (server.isConnectedTo) { + workerScript.log( + "deleteServer", + "You are currently connected to the server you are trying to delete.", + ); + return false; + } + + // A server cannot delete itself + if (ip === workerScript.serverIp) { + workerScript.log( + "deleteServer", + "Cannot delete the server this script is running on.", + ); + return false; + } + + // Delete all scripts running on server + if (server.runningScripts.length > 0) { + workerScript.log( + "deleteServer", + `Cannot delete server '${server.hostname}' because it still has scripts running.`, + ); + return false; + } + + // Delete from player's purchasedServers array + var found = false; + for (var i = 0; i < Player.purchasedServers.length; ++i) { + if (ip == Player.purchasedServers[i]) { + found = true; + Player.purchasedServers.splice(i, 1); + break; + } + } + + if (!found) { + workerScript.log( + "deleteServer", + `Could not identify server ${server.hostname} as a purchased server. This is a bug. Report to dev.`, + ); + return false; + } + + // Delete from all servers + delete AllServers[ip]; + + // Delete from home computer + found = false; + var homeComputer = Player.getHomeComputer(); + for (var i = 0; i < homeComputer.serversOnNetwork.length; ++i) { + if (ip == homeComputer.serversOnNetwork[i]) { + homeComputer.serversOnNetwork.splice(i, 1); + workerScript.log("deleteServer", `Deleted server '${hostnameStr}`); + return true; + } + } + // Wasn't found on home computer + workerScript.log( + "deleteServer", + `Could not find server ${server.hostname} as a purchased server. This is a bug. Report to dev.`, + ); + return false; + }, + getPurchasedServers: function (hostname = true) { + updateDynamicRam( + "getPurchasedServers", + getRamCost("getPurchasedServers"), + ); + var res = []; + Player.purchasedServers.forEach(function (ip) { + if (hostname) { + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg( + "getPurchasedServers", + "Could not find server. This is a bug. Report to dev.", + ); + } + res.push(server.hostname); + } else { + res.push(ip); + } + }); + return res; + }, + write: function (port, data = "", mode = "a") { + updateDynamicRam("write", getRamCost("write")); + if (!isNaN(port)) { + // Write to port + // Port 1-10 + port = Math.round(port); + if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { + throw makeRuntimeErrorMsg( + "write", + `Trying to write to invalid port: ${port}. Only ports 1-${CONSTANTS.NumNetscriptPorts} are valid.`, + ); + } + var port = NetscriptPorts[port - 1]; + if (port == null || !(port instanceof NetscriptPort)) { + throw makeRuntimeErrorMsg( + "write", + `Could not find port: ${port}. This is a bug. Report to dev.`, + ); + } + return port.write(data); + } else if (isString(port)) { + // Write to script or text file + let fn = port; + if (!isValidFilePath(fn)) { + throw makeRuntimeErrorMsg("write", `Invalid filepath: ${fn}`); + } + + if (fn.lastIndexOf("/") === 0) { + fn = removeLeadingSlash(fn); + } + + // Coerce 'data' to be a string + try { + data = String(data); + } catch (e) { + throw makeRuntimeErrorMsg( + "write", + `Invalid data (${e}). Data being written must be convertible to a string`, + ); + } + + const server = workerScript.getServer(); + if (server == null) { + throw makeRuntimeErrorMsg( + "write", + "Error getting Server. This is a bug. Report to dev.", + ); + } + if (isScriptFilename(fn)) { + // Write to script + let script = workerScript.getScriptOnServer(fn); + if (script == null) { + // Create a new script + script = new Script(fn, data, server.ip, server.scripts); + server.scripts.push(script); return true; - }, - brutessh: function(ip){ - updateDynamicRam("brutessh", getRamCost("brutessh")); - if (ip === undefined) { - throw makeRuntimeErrorMsg("brutessh", "Takes 1 argument."); - } - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("brutessh", `Invalid IP/hostname: ${ip}.`); - } - if (!Player.hasProgram(Programs.BruteSSHProgram.name)) { - throw makeRuntimeErrorMsg("brutessh", "You do not have the BruteSSH.exe program!"); - } - if (!server.sshPortOpen) { - workerScript.log("brutessh", `Executed BruteSSH.exe on '${server.hostname}' to open SSH port (22).`); - server.sshPortOpen = true; - ++server.openPortCount; - } else { - workerScript.log("brutessh", `SSH Port (22) already opened on '${server.hostname}'.`); - } + } + mode === "w" ? (script.code = data) : (script.code += data); + script.updateRamUsage(server.scripts); + script.markUpdated(); + } else { + // Write to text file + let txtFile = getTextFile(fn, server); + if (txtFile == null) { + txtFile = createTextFile(fn, data, server); return true; - }, - ftpcrack: function(ip) { - updateDynamicRam("ftpcrack", getRamCost("ftpcrack")); - if (ip === undefined) { - throw makeRuntimeErrorMsg("ftpcrack", "Takes 1 argument."); - } - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("ftpcrack", `Invalid IP/hostname: ${ip}.`); - } - if (!Player.hasProgram(Programs.FTPCrackProgram.name)) { - throw makeRuntimeErrorMsg("ftpcrack", "You do not have the FTPCrack.exe program!"); - } - if (!server.ftpPortOpen) { - workerScript.log("ftpcrack", `Executed FTPCrack.exe on '${server.hostname}' to open FTP port (21).`); - server.ftpPortOpen = true; - ++server.openPortCount; - } else { - workerScript.log("ftpcrack", `FTP Port (21) already opened on '${server.hostname}'.`); - } - return true; - }, - relaysmtp: function(ip) { - updateDynamicRam("relaysmtp", getRamCost("relaysmtp")); - if (ip === undefined) { - throw makeRuntimeErrorMsg("relaysmtp", "Takes 1 argument."); - } - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("relaysmtp", `Invalid IP/hostname: ${ip}.`); - } - if (!Player.hasProgram(Programs.RelaySMTPProgram.name)) { - throw makeRuntimeErrorMsg("relaysmtp", "You do not have the relaySMTP.exe program!"); - } - if (!server.smtpPortOpen) { - workerScript.log("relaysmtp", `Executed relaySMTP.exe on '${server.hostname}' to open SMTP port (25).`); - server.smtpPortOpen = true; - ++server.openPortCount; - } else { - workerScript.log("relaysmtp", `SMTP Port (25) already opened on '${server.hostname}'.`); - } - return true; - }, - httpworm: function(ip) { - updateDynamicRam("httpworm", getRamCost("httpworm")); - if (ip === undefined) { - throw makeRuntimeErrorMsg("httpworm", "Takes 1 argument"); - } - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("httpworm", `Invalid IP/hostname: ${ip}`); - } - if (!Player.hasProgram(Programs.HTTPWormProgram.name)) { - throw makeRuntimeErrorMsg("httpworm", "You do not have the HTTPWorm.exe program!"); - } - if (!server.httpPortOpen) { - workerScript.log("httpworm", `Executed HTTPWorm.exe on '${server.hostname}' to open HTTP port (80).`); - server.httpPortOpen = true; - ++server.openPortCount; - } else { - workerScript.log("httpworm", `HTTP Port (80) already opened on '${server.hostname}'.`); - } - return true; - }, - sqlinject: function(ip) { - updateDynamicRam("sqlinject", getRamCost("sqlinject")); - if (ip === undefined) { - throw makeRuntimeErrorMsg("sqlinject", "Takes 1 argument."); - } - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("sqlinject", `Invalid IP/hostname: ${ip}`); - } - if (!Player.hasProgram(Programs.SQLInjectProgram.name)) { - throw makeRuntimeErrorMsg("sqlinject", "You do not have the SQLInject.exe program!"); - } - if (!server.sqlPortOpen) { - workerScript.log("sqlinject", `Executed SQLInject.exe on '${server.hostname}' to open SQL port (1433).`); - server.sqlPortOpen = true; - ++server.openPortCount; - } else { - workerScript.log("sqlinject", `SQL Port (1433) already opened on '${server.hostname}'.`); - } - return true; - }, - run: function(scriptname, threads=1) { - updateDynamicRam("run", getRamCost("run")); - if (scriptname === undefined) { - throw makeRuntimeErrorMsg("run", "Usage: run(scriptname, [numThreads], [arg1], [arg2]...)"); - } - if (isNaN(threads) || threads <= 0) { - throw makeRuntimeErrorMsg("run", `Invalid thread count. Must be numeric and > 0, is ${threads}`); - } - var argsForNewScript = []; - for (var i = 2; i < arguments.length; ++i) { - argsForNewScript.push(arguments[i]); - } - var scriptServer = getServer(workerScript.serverIp); - if (scriptServer == null) { - throw makeRuntimeErrorMsg("run", "Could not find server. This is a bug. Report to dev."); - } + } + if (mode === "w") { + txtFile.write(data); + } else { + txtFile.append(data); + } + } + return true; + } else { + throw makeRuntimeErrorMsg("write", `Invalid argument: ${port}`); + } + }, + tryWrite: function (port, data = "") { + updateDynamicRam("tryWrite", getRamCost("tryWrite")); + if (!isNaN(port)) { + port = Math.round(port); + if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { + throw makeRuntimeErrorMsg( + "tryWrite", + `Invalid port: ${port}. Only ports 1-${CONSTANTS.NumNetscriptPorts} are valid.`, + ); + } + var port = NetscriptPorts[port - 1]; + if (port == null || !(port instanceof NetscriptPort)) { + throw makeRuntimeErrorMsg( + "tryWrite", + `Could not find port: ${port}. This is a bug. Report to dev.`, + ); + } + return port.tryWrite(data); + } else { + throw makeRuntimeErrorMsg("tryWrite", `Invalid argument: ${port}`); + } + }, + read: function (port) { + updateDynamicRam("read", getRamCost("read")); + if (!isNaN(port)) { + // Read from port + // Port 1-10 + port = Math.round(port); + if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { + throw makeRuntimeErrorMsg( + "read", + `Invalid port: ${port}. Only ports 1-${CONSTANTS.NumNetscriptPorts} are valid.`, + ); + } + var port = NetscriptPorts[port - 1]; + if (port == null || !(port instanceof NetscriptPort)) { + throw makeRuntimeErrorMsg( + "read", + `Could not find port: ${port}. This is a bug. Report to dev.`, + ); + } + return port.read(); + } else if (isString(port)) { + // Read from script or text file + let fn = port; + let server = getServer(workerScript.serverIp); + if (server == null) { + throw makeRuntimeErrorMsg( + "read", + "Error getting Server. This is a bug. Report to dev.", + ); + } + if (isScriptFilename(fn)) { + // Read from script + let script = workerScript.getScriptOnServer(fn); + if (script == null) { + return ""; + } + return script.code; + } else { + // Read from text file + let txtFile = getTextFile(fn, server); + if (txtFile !== null) { + return txtFile.text; + } else { + return ""; + } + } + } else { + throw makeRuntimeErrorMsg("read", `Invalid argument: ${port}`); + } + }, + peek: function (port) { + updateDynamicRam("peek", getRamCost("peek")); + if (isNaN(port)) { + throw makeRuntimeErrorMsg( + "peek", + `Invalid argument. Must be a port number between 1 and ${CONSTANTS.NumNetscriptPorts}, is ${port}`, + ); + } + port = Math.round(port); + if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { + throw makeRuntimeErrorMsg( + "peek", + `Invalid argument. Must be a port number between 1 and ${CONSTANTS.NumNetscriptPorts}, is ${port}`, + ); + } + var port = NetscriptPorts[port - 1]; + if (port == null || !(port instanceof NetscriptPort)) { + throw makeRuntimeErrorMsg( + "peek", + `Could not find port: ${port}. This is a bug. Report to dev.`, + ); + } + return port.peek(); + }, + clear: function (port) { + updateDynamicRam("clear", getRamCost("clear")); + if (!isNaN(port)) { + // Clear port + port = Math.round(port); + if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { + throw makeRuntimeErrorMsg( + "clear", + `Trying to clear invalid port: ${port}. Only ports 1-${CONSTANTS.NumNetscriptPorts} are valid`, + ); + } + var port = NetscriptPorts[port - 1]; + if (port == null || !(port instanceof NetscriptPort)) { + throw makeRuntimeErrorMsg( + "clear", + `Could not find port: ${port}. This is a bug. Report to dev.`, + ); + } + return port.clear(); + } else if (isString(port)) { + // Clear text file + var fn = port; + var server = getServer(workerScript.serverIp); + if (server == null) { + throw makeRuntimeErrorMsg( + "clear", + "Error getting Server. This is a bug. Report to dev.", + ); + } + var txtFile = getTextFile(fn, server); + if (txtFile != null) { + txtFile.write(""); + } + } else { + throw makeRuntimeErrorMsg("clear", `Invalid argument: ${port}`); + } + return 0; + }, + getPortHandle: function (port) { + updateDynamicRam("getPortHandle", getRamCost("getPortHandle")); + if (isNaN(port)) { + throw makeRuntimeErrorMsg( + "getPortHandle", + `Invalid port: ${port} Must be an integer between 1 and ${CONSTANTS.NumNetscriptPorts}.`, + ); + } + port = Math.round(port); + if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { + throw makeRuntimeErrorMsg( + "getPortHandle", + `Invalid port: ${port}. Only ports 1-${CONSTANTS.NumNetscriptPorts} are valid.`, + ); + } + var port = NetscriptPorts[port - 1]; + if (port == null || !(port instanceof NetscriptPort)) { + throw makeRuntimeErrorMsg( + "getPortHandle", + `Could not find port: ${port}. This is a bug. Report to dev.`, + ); + } + return port; + }, + rm: function (fn, ip) { + updateDynamicRam("rm", getRamCost("rm")); - return runScriptFromScript("run", scriptServer, scriptname, argsForNewScript, workerScript, threads); - }, - exec: function(scriptname, ip, threads = 1) { - updateDynamicRam("exec", getRamCost("exec")); - if (scriptname === undefined || ip === undefined) { - throw makeRuntimeErrorMsg("exec", "Usage: exec(scriptname, server, [numThreads], [arg1], [arg2]...)"); - } - if (isNaN(threads) || threads <= 0) { - throw makeRuntimeErrorMsg("exec", `Invalid thread count. Must be numeric and > 0, is ${threads}`); - } - var argsForNewScript = []; - for (var i = 3; i < arguments.length; ++i) { - argsForNewScript.push(arguments[i]); - } - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("exec", `Invalid IP/hostname: ${ip}`); - } - return runScriptFromScript("exec", server, scriptname, argsForNewScript, workerScript, threads); - }, - spawn: function(scriptname, threads) { - updateDynamicRam("spawn", getRamCost("spawn")); - if (!scriptname || !threads) { - throw makeRuntimeErrorMsg("spawn", "Usage: spawn(scriptname, threads)"); - } + if (ip == null || ip === "") { + ip = workerScript.serverIp; + } + const s = safeGetServer(ip, "rm"); - const spawnDelay = 10; - setTimeoutRef(() => { - if (isNaN(threads) || threads <= 0) { - throw makeRuntimeErrorMsg("spawn", `Invalid thread count. Must be numeric and > 0, is ${threads}`); - } - var argsForNewScript = []; - for (var i = 2; i < arguments.length; ++i) { - argsForNewScript.push(arguments[i]); - } - var scriptServer = getServer(workerScript.serverIp); - if (scriptServer == null) { - throw makeRuntimeErrorMsg("spawn", "Could not find server. This is a bug. Report to dev"); - } + const status = s.removeFile(fn); + if (!status.res) { + workerScript.log("rm", status.msg); + } - return runScriptFromScript("spawn", scriptServer, scriptname, argsForNewScript, workerScript, threads); - }, spawnDelay * 1e3); + return status.res; + }, + scriptRunning: function (scriptname, ip) { + updateDynamicRam("scriptRunning", getRamCost("scriptRunning")); + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg( + "scriptRunning", + `Invalid IP/hostname: ${ip}`, + ); + } + for (var i = 0; i < server.runningScripts.length; ++i) { + if (server.runningScripts[i].filename == scriptname) { + return true; + } + } + return false; + }, + scriptKill: function (scriptname, ip) { + updateDynamicRam("scriptKill", getRamCost("scriptKill")); + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("scriptKill", `Invalid IP/hostname: ${ip}`); + } + var suc = false; + for (var i = 0; i < server.runningScripts.length; ++i) { + if (server.runningScripts[i].filename == scriptname) { + killWorkerScript(server.runningScripts[i], server.ip); + suc = true; + } + } + return suc; + }, + getScriptName: function () { + return workerScript.name; + }, + getScriptRam: function (scriptname, ip = workerScript.serverIp) { + updateDynamicRam("getScriptRam", getRamCost("getScriptRam")); + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg("getScriptRam", `Invalid IP/hostname: ${ip}`); + } + for (var i = 0; i < server.scripts.length; ++i) { + if (server.scripts[i].filename == scriptname) { + return server.scripts[i].ramUsage; + } + } + return 0; + }, + getRunningScript: function (fn, ip) { + updateDynamicRam("getRunningScript", getRamCost("getRunningScript")); - workerScript.log("spawn", `Will execute '${scriptname}' in ${spawnDelay} seconds`); + let runningScript; + if (arguments.length === 0) { + runningScript = workerScript.scriptRef; + } else if (typeof fn === "number") { + runningScript = getRunningScriptByPid(fn, "getRunningScript"); + } else { + const scriptArgs = []; + for (var i = 2; i < arguments.length; ++i) { + scriptArgs.push(arguments[i]); + } + runningScript = getRunningScript( + fn, + ip, + "getRunningScript", + scriptArgs, + ); + } + if (runningScript === null) return null; + return { + args: runningScript.args.slice(), + filename: runningScript.filename, + logs: runningScript.logs.slice(), + offlineExpGained: runningScript.offlineExpGained, + offlineMoneyMade: runningScript.offlineMoneyMade, + offlineRunningTime: runningScript.offlineRunningTime, + onlineExpGained: runningScript.onlineExpGained, + onlineMoneyMade: runningScript.onlineMoneyMade, + onlineRunningTime: runningScript.onlineRunningTime, + pid: runningScript.pid, + ramUsage: runningScript.ramUsage, + server: runningScript.server, + threads: runningScript.threads, + }; + }, + getHackTime: function (ip) { + updateDynamicRam("getHackTime", getRamCost("getHackTime")); + const server = safeGetServer(ip, "getHackTime"); + if (failOnHacknetServer(server, "getHackTime")) { + return Infinity; + } - workerScript.running = false; // Prevent workerScript from "finishing execution naturally" - if (killWorkerScript(workerScript)) { - workerScript.log("spawn", "Exiting..."); - } - }, - kill: function(filename, ip, ...scriptArgs) { - updateDynamicRam("kill", getRamCost("kill")); + return calculateHackingTime(server, Player); // Returns seconds + }, + getGrowTime: function (ip) { + updateDynamicRam("getGrowTime", getRamCost("getGrowTime")); + const server = safeGetServer(ip, "getGrowTime"); + if (failOnHacknetServer(server, "getGrowTime")) { + return Infinity; + } + return calculateGrowTime(server, Player); // Returns seconds + }, + getWeakenTime: function (ip) { + updateDynamicRam("getWeakenTime", getRamCost("getWeakenTime")); + const server = safeGetServer(ip, "getWeakenTime"); + if (failOnHacknetServer(server, "getWeakenTime")) { + return Infinity; + } + + return calculateWeakenTime(server, Player); // Returns seconds + }, + getScriptIncome: function (scriptname, ip) { + updateDynamicRam("getScriptIncome", getRamCost("getScriptIncome")); + if (arguments.length === 0) { + var res = []; + + // First element is total income of all currently running scripts + let total = 0; + for (const script of workerScripts.values()) { + total += + script.scriptRef.onlineMoneyMade / + script.scriptRef.onlineRunningTime; + } + res.push(total); + + // Second element is total income you've earned from scripts since you installed Augs + res.push( + Player.scriptProdSinceLastAug / (Player.playtimeSinceLastAug / 1000), + ); + return res; + } else { + // Get income for a particular script + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg( + "getScriptIncome", + `Invalid IP/hostnamed: ${ip}`, + ); + } + var argsForScript = []; + for (var i = 2; i < arguments.length; ++i) { + argsForScript.push(arguments[i]); + } + var runningScriptObj = findRunningScript( + scriptname, + argsForScript, + server, + ); + if (runningScriptObj == null) { + workerScript.log( + "getScriptIncome", + `No such script '${scriptname}' on '${ + server.hostname + }' with args: ${arrayToString(argsForScript)}`, + ); + return -1; + } + return ( + runningScriptObj.onlineMoneyMade / runningScriptObj.onlineRunningTime + ); + } + }, + getScriptExpGain: function (scriptname, ip) { + updateDynamicRam("getScriptExpGain", getRamCost("getScriptExpGain")); + if (arguments.length === 0) { + var total = 0; + for (const ws of workerScripts.values()) { + total += + ws.scriptRef.onlineExpGained / ws.scriptRef.onlineRunningTime; + } + return total; + } else { + // Get income for a particular script + var server = getServer(ip); + if (server == null) { + throw makeRuntimeErrorMsg( + "getScriptExpGain", + `Invalid IP/hostnamed: ${ip}`, + ); + } + var argsForScript = []; + for (var i = 2; i < arguments.length; ++i) { + argsForScript.push(arguments[i]); + } + var runningScriptObj = findRunningScript( + scriptname, + argsForScript, + server, + ); + if (runningScriptObj == null) { + workerScript.log( + "getScriptExpGain", + `No such script '${scriptname}' on '${ + server.hostname + }' with args: ${arrayToString(argsForScript)}`, + ); + return -1; + } + return ( + runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime + ); + } + }, + nFormat: function (n, format) { + if (isNaN(n) || isNaN(parseFloat(n)) || typeof format !== "string") { + return ""; + } + + return numeralWrapper.format(parseFloat(n), format); + }, + tFormat: function (milliseconds, milliPrecision = false) { + return convertTimeMsToTimeElapsedString(milliseconds, milliPrecision); + }, + getTimeSinceLastAug: function () { + updateDynamicRam( + "getTimeSinceLastAug", + getRamCost("getTimeSinceLastAug"), + ); + return Player.playtimeSinceLastAug; + }, + prompt: function (txt) { + if (!isString(txt)) { + txt = JSON.stringify(txt); + } + + // The id for this popup will consist of the first 20 characters of the prompt string.. + // Thats hopefully good enough to be unique + const popupId = `prompt-popup-${txt.slice(0, 20)}`; + const textElement = createElement("p", { innerHTML: txt }); + + return new Promise(function (resolve) { + const yesBtn = createElement("button", { + class: "popup-box-button", + innerText: "Yes", + clickListener: () => { + removeElementById(popupId); + resolve(true); + }, + }); + + const noBtn = createElement("button", { + class: "popup-box-button", + innerText: "No", + clickListener: () => { + removeElementById(popupId); + resolve(false); + }, + }); + + createPopup(popupId, [textElement, yesBtn, noBtn]); + }); + }, + wget: async function (url, target, ip = workerScript.serverIp) { + if (!isScriptFilename(target) && !target.endsWith(".txt")) { + workerScript.log( + "wget", + `Invalid target file: '${target}'. Must be a script or text file.`, + ); + return Promise.resolve(false); + } + var s = safeGetServer(ip, "wget"); + return new Promise(function (resolve) { + $.get( + url, + function (data) { let res; - const killByPid = (typeof filename === "number"); - if (killByPid) { - // Kill by pid - res = killWorkerScript(filename); + if (isScriptFilename(target)) { + res = s.writeToScriptFile(target, data); } else { - // Kill by filename/ip - if (filename === undefined || ip === undefined) { - throw makeRuntimeErrorMsg("kill", "Usage: kill(scriptname, server, [arg1], [arg2]...)"); - } + res = s.writeToTextFile(target, data); + } + if (!res.success) { + workerScript.log("wget", "Failed."); + return resolve(false); + } + if (res.overwritten) { + workerScript.log( + "wget", + `Successfully retrieved content and overwrote '${target}' on '${ip}'`, + ); + return resolve(true); + } + workerScript.log( + "wget", + `Successfully retrieved content to new file '${target}' on '${ip}'`, + ); + return resolve(true); + }, + "text", + ).fail(function (e) { + workerScript.log("wget", JSON.stringify(e)); + return resolve(false); + }); + }); + }, + getFavorToDonate: function () { + updateDynamicRam("getFavorToDonate", getRamCost("getFavorToDonate")); + return Math.floor( + CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction, + ); + }, - const server = safeGetServer(ip); - const runningScriptObj = getRunningScript(filename, ip, "kill", scriptArgs); - if (runningScriptObj == null) { - workerScript.log("kill", getCannotFindRunningScriptErrorMessage(filename, ip, scriptArgs)); - return false; - } + /* Singularity Functions */ + universityCourse: function (universityName, className) { + updateDynamicRam("universityCourse", getRamCost("universityCourse")); + checkSingularityAccess("universityCourse", 1); + if (inMission) { + workerScript.log( + "universityCourse", + "You are in the middle of a mission.", + ); + return; + } + if (Player.isWorking) { + var txt = Player.singularityStopWork(); + workerScript.log("universityCourse", txt); + } - res = killWorkerScript(runningScriptObj, server.ip); - } - - if (res) { - if (killByPid) { - workerScript.log("kill", `Killing script with PID ${filename}`); - } else { - workerScript.log("kill", `Killing '${filename}' on '${ip}' with args: ${arrayToString(scriptArgs)}.`); - } - return true; - } else { - if (killByPid) { - workerScript.log("kill", `No script with PID ${filename}`); - } else { - workerScript.log("kill", `No such script '${filename}' on '${ip}' with args: ${arrayToString(scriptArgs)}`); - } - return false; - } - }, - killall: function(ip=workerScript.serverIp) { - updateDynamicRam("killall", getRamCost("killall")); - if (ip === undefined) { - throw makeRuntimeErrorMsg("killall", "Takes 1 argument"); - } - const server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("killall", `Invalid IP/hostname: ${ip}`); - } - const scriptsRunning = (server.runningScripts.length > 0); - for (let i = server.runningScripts.length-1; i >= 0; --i) { - killWorkerScript(server.runningScripts[i], server.ip, false); - } - WorkerScriptStartStopEventEmitter.emitEvent(); - workerScript.log("killall", `Killing all scripts on '${server.hostname}'. May take a few minutes for the scripts to die.`); - - return scriptsRunning; - }, - exit : function() { - workerScript.running = false; // Prevent workerScript from "finishing execution naturally" - if (killWorkerScript(workerScript)) { - workerScript.log("exit", "Exiting..."); - } else { - workerScript.log("exit", "Failed. This is a bug. Report to dev."); - } - }, - scp: function(scriptname, ip1, ip2) { - updateDynamicRam("scp", getRamCost("scp")); - if (arguments.length !== 2 && arguments.length !== 3) { - throw makeRuntimeErrorMsg("scp", "Takes 2 or 3 arguments"); - } - if (scriptname && scriptname.constructor === Array) { - // Recursively call scp on all elements of array - var res = false; - scriptname.forEach(function(script) { - if (NetscriptFunctions(workerScript).scp(script, ip1, ip2)) { - res = true; - } - }); - return res; - } - - // Invalid file type - if (!isValidFilePath(scriptname)) { - throw makeRuntimeErrorMsg("scp", `Invalid filename: '${scriptname}'`); - } - - // Invalid file name - if (!scriptname.endsWith(".lit") && !isScriptFilename(scriptname) && !scriptname.endsWith("txt")) { - throw makeRuntimeErrorMsg("scp", "Only works for .script, .lit, and .txt files"); - } - - var destServer, currServ; - - if (ip2 != null) { // 3 Argument version: scriptname, source, destination - if (scriptname === undefined || ip1 === undefined || ip2 === undefined) { - throw makeRuntimeErrorMsg("scp", "Takes 2 or 3 arguments"); - } - destServer = getServer(ip2); - if (destServer == null) { - throw makeRuntimeErrorMsg("scp", `Invalid IP/hostname: ${ip2}`); - } - - currServ = getServer(ip1); - if (currServ == null) { - throw makeRuntimeErrorMsg("scp", `Invalid IP/hostname: ${ip1}`); - } - } else if (ip1 != null) { // 2 Argument version: scriptname, destination - if (scriptname === undefined || ip1 === undefined) { - throw makeRuntimeErrorMsg("scp", "Takes 2 or 3 arguments"); - } - destServer = getServer(ip1); - if (destServer == null) { - throw makeRuntimeErrorMsg("scp", `Invalid IP/hostname: ${ip1}`); - } - - currServ = getServer(workerScript.serverIp); - if (currServ == null) { - throw makeRuntimeErrorMsg("scp", "Could not find server ip for this script. This is a bug. Report to dev."); - } - } else { - throw makeRuntimeErrorMsg("scp", "Takes 2 or 3 arguments"); - } - - // Scp for lit files - if (scriptname.endsWith(".lit")) { - var found = false; - for (var i = 0; i < currServ.messages.length; ++i) { - if (!(currServ.messages[i] instanceof Message) && currServ.messages[i] == scriptname) { - found = true; - break; - } - } - - if (!found) { - workerScript.log("scp", `File '${scriptname}' does not exist.`); - return false; - } - - for (var i = 0; i < destServer.messages.length; ++i) { - if (destServer.messages[i] === scriptname) { - workerScript.log("scp", `File '${scriptname}' copied over to '${destServer.hostname}'.`); - return true; // Already exists - } - } - destServer.messages.push(scriptname); - workerScript.log("scp", `File '${scriptname}' copied over to '${destServer.hostname}'.`); - return true; - } - - // Scp for text files - if (scriptname.endsWith(".txt")) { - var found = false, txtFile; - for (var i = 0; i < currServ.textFiles.length; ++i) { - if (currServ.textFiles[i].fn === scriptname) { - found = true; - txtFile = currServ.textFiles[i]; - break; - } - } - - if (!found) { - workerScript.log("scp", `File '${scriptname}' does not exist.`); - return false; - } - - for (var i = 0; i < destServer.textFiles.length; ++i) { - if (destServer.textFiles[i].fn === scriptname) { - // Overwrite - destServer.textFiles[i].text = txtFile.text; - workerScript.log("scp", `File '${scriptname}' copied over to '${destServer.hostname}'.`); - return true; - } - } - var newFile = new TextFile(txtFile.fn, txtFile.text); - destServer.textFiles.push(newFile); - workerScript.log("scp", `File '${scriptname}' copied over to '${destServer.hostname}'.`); - return true; - } - - // Scp for script files - let sourceScript = null; - for (let i = 0; i < currServ.scripts.length; ++i) { - if (scriptname == currServ.scripts[i].filename) { - sourceScript = currServ.scripts[i]; - break; - } - } - if (sourceScript == null) { - workerScript.log("scp", `File '${scriptname}' does not exist.`); - return false; - } - - // Overwrite script if it already exists - for (let i = 0; i < destServer.scripts.length; ++i) { - if (scriptname == destServer.scripts[i].filename) { - workerScript.log("scp", `WARNING: File '${scriptname}' overwritten on '${destServer.hostname}'`); - const oldScript = destServer.scripts[i]; - // If it's the exact same file don't actually perform the - // copy to avoid recompiling uselessly. Players tend to scp - // liberally. - if(oldScript.code === sourceScript.code) - return true; - oldScript.code = sourceScript.code; - oldScript.ramUsage = sourceScript.ramUsage; - oldScript.markUpdated(); - return true; - } - } - - // Create new script if it does not already exist - const newScript = new Script(scriptname); - newScript.code = sourceScript.code; - newScript.ramUsage = sourceScript.ramUsage; - newScript.server = destServer.ip; - destServer.scripts.push(newScript); - workerScript.log("scp", `File '${scriptname}' copied over to '${destServer.hostname}'.`); - return true; - }, - ls: function(ip, grep) { - updateDynamicRam("ls", getRamCost("ls")); - if (ip === undefined) { - throw makeRuntimeErrorMsg("ls", "Usage: ls(ip/hostname, [grep filter])"); - } - const server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("ls", `Invalid IP/hostname: ${ip}`); - } - - // Get the grep filter, if one exists - let filter = false; - if (arguments.length >= 2) { - filter = grep.toString(); - } - - const allFiles = []; - for (let i = 0; i < server.programs.length; i++) { - if (filter) { - if (server.programs[i].includes(filter)) { - allFiles.push(server.programs[i]); - } - } else { - allFiles.push(server.programs[i]); - } - } - for (let i = 0; i < server.scripts.length; i++) { - if (filter) { - if (server.scripts[i].filename.includes(filter)) { - allFiles.push(server.scripts[i].filename); - } - } else { - allFiles.push(server.scripts[i].filename); - } - - } - for (let i = 0; i < server.messages.length; i++) { - if (filter) { - if (server.messages[i] instanceof Message) { - if (server.messages[i].filename.includes(filter)) { - allFiles.push(server.messages[i].filename); - } - } else if (server.messages[i].includes(filter)) { - allFiles.push(server.messages[i]); - } - } else { - if (server.messages[i] instanceof Message) { - allFiles.push(server.messages[i].filename); - } else { - allFiles.push(server.messages[i]); - } - } - } - - for (var i = 0; i < server.textFiles.length; i++) { - if (filter) { - if (server.textFiles[i].fn.includes(filter)) { - allFiles.push(server.textFiles[i].fn); - } - } else { - allFiles.push(server.textFiles[i].fn); - } - } - - for (var i = 0; i < server.contracts.length; ++i) { - if (filter) { - if (server.contracts[i].fn.includes(filter)) { - allFiles.push(server.contracts[i].fn); - } - } else { - allFiles.push(server.contracts[i].fn); - } - } - - // Sort the files alphabetically then print each - allFiles.sort(); - return allFiles; - }, - ps: function(ip=workerScript.serverIp) { - updateDynamicRam("ps", getRamCost("ps")); - var server = getServer(ip); - if (server == null){ - throw makeRuntimeErrorMsg("ps", `Invalid IP/hostname: ${ip}`); - } - const processes = []; - for (const i in server.runningScripts) { - const script = server.runningScripts[i]; - processes.push({ - filename:script.filename, - threads: script.threads, - args: script.args.slice(), - pid: script.pid, - }) - } - return processes; - }, - hasRootAccess: function(ip) { - updateDynamicRam("hasRootAccess", getRamCost("hasRootAccess")); - if (ip===undefined){ - throw makeRuntimeErrorMsg("hasRootAccess", "Takes 1 argument"); - } - var server = getServer(ip); - if (server == null){ - throw makeRuntimeErrorMsg("hasRootAccess", `Invalid IP/hostname: ${ip}`); - } - return server.hasAdminRights; - }, - getIp: function() { - updateDynamicRam("getIp", getRamCost("getIp")); - var scriptServer = getServer(workerScript.serverIp); - if (scriptServer == null) { - throw makeRuntimeErrorMsg("getIp", "Could not find server. This is a bug. Report to dev."); - } - return scriptServer.ip; - }, - getHostname: function() { - updateDynamicRam("getHostname", getRamCost("getHostname")); - var scriptServer = getServer(workerScript.serverIp); - if (scriptServer == null) { - throw makeRuntimeErrorMsg(workerScript, "Could not find server. This is a bug. Report to dev."); - } - return scriptServer.hostname; - }, - getHackingLevel: function() { - updateDynamicRam("getHackingLevel", getRamCost("getHackingLevel")); - Player.updateSkillLevels(); - workerScript.log("getHackingLevel", `returned ${Player.hacking_skill}`); - return Player.hacking_skill; - }, - getHackingMultipliers: function() { - updateDynamicRam("getHackingMultipliers", getRamCost("getHackingMultipliers")); - return { - chance: Player.hacking_chance_mult, - speed: Player.hacking_speed_mult, - money: Player.hacking_money_mult, - growth: Player.hacking_grow_mult, - }; - }, - getHacknetMultipliers: function() { - updateDynamicRam("getHacknetMultipliers", getRamCost("getHacknetMultipliers")); - return { - production: Player.hacknet_node_money_mult, - purchaseCost: Player.hacknet_node_purchase_cost_mult, - ramCost: Player.hacknet_node_ram_cost_mult, - coreCost: Player.hacknet_node_core_cost_mult, - levelCost: Player.hacknet_node_level_cost_mult, - }; - }, - getBitNodeMultipliers: function() { - updateDynamicRam("getBitNodeMultipliers", getRamCost("getBitNodeMultipliers")); - if (SourceFileFlags[5] <= 0 && Player.bitNodeN !== 5) { - throw makeRuntimeErrorMsg("getBitNodeMultipliers", "Requires Source-File 5 to run."); - } - let copy = Object.assign({}, BitNodeMultipliers); - return copy; - }, - getServer: function(ip) { - updateDynamicRam("getServer", getRamCost("getServer")); - if (SourceFileFlags[5] <= 0 && Player.bitNodeN !== 5) { - throw makeRuntimeErrorMsg("getServer", "Requires Source-File 5 to run."); - } - const server = safeGetServer(ip, "getServer"); - const copy = Object.assign({}, server); - // These fields should be hidden. - copy.contracts = undefined; - copy.messages = undefined; - copy.runningScripts = undefined; - copy.scripts = undefined; - copy.textFiles = undefined; - copy.programs = undefined; - copy.serversOnNetwork = undefined; - return copy; - }, - getServerMoneyAvailable: function(ip) { - updateDynamicRam("getServerMoneyAvailable", getRamCost("getServerMoneyAvailable")); - const server = safeGetServer(ip, "getServerMoneyAvailable"); - if (failOnHacknetServer(server, "getServerMoneyAvailable")) { return 0; } - if (server.hostname == "home") { - // Return player's money - workerScript.log("getServerMoneyAvailable", `returned player's money: ${numeralWrapper.formatMoney(Player.money.toNumber())}`); - return Player.money.toNumber(); - } - workerScript.log("getServerMoneyAvailable", `returned ${numeralWrapper.formatMoney(server.moneyAvailable)} for '${server.hostname}'`); - return server.moneyAvailable; - }, - getServerSecurityLevel: function(ip) { - updateDynamicRam("getServerSecurityLevel", getRamCost("getServerSecurityLevel")); - const server = safeGetServer(ip, "getServerSecurityLevel"); - if (failOnHacknetServer(server, "getServerSecurityLevel")) { return 1; } - workerScript.log("getServerSecurityLevel", `returned ${numeralWrapper.formatServerSecurity(server.hackDifficulty, 3)} for '${server.hostname}'`); - return server.hackDifficulty; - }, - getServerBaseSecurityLevel: function(ip) { - updateDynamicRam("getServerBaseSecurityLevel", getRamCost("getServerBaseSecurityLevel")); - const server = safeGetServer(ip, "getServerBaseSecurityLevel"); - if (failOnHacknetServer(server, "getServerBaseSecurityLevel")) { return 1; } - workerScript.log("getServerBaseSecurityLevel", `returned ${numeralWrapper.formatServerSecurity(server.baseDifficulty, 3)} for '${server.hostname}'`); - return server.baseDifficulty; - }, - getServerMinSecurityLevel: function(ip) { - updateDynamicRam("getServerMinSecurityLevel", getRamCost("getServerMinSecurityLevel")); - const server = safeGetServer(ip, "getServerMinSecurityLevel"); - if (failOnHacknetServer(server, "getServerMinSecurityLevel")) { return 1; } - workerScript.log("getServerMinSecurityLevel", `returned ${numeralWrapper.formatServerSecurity(server.minDifficulty, 3)} for ${server.hostname}`); - return server.minDifficulty; - }, - getServerRequiredHackingLevel: function(ip) { - updateDynamicRam("getServerRequiredHackingLevel", getRamCost("getServerRequiredHackingLevel")); - const server = safeGetServer(ip, "getServerRequiredHackingLevel"); - if (failOnHacknetServer(server, "getServerRequiredHackingLevel")) { return 1; } - workerScript.log("getServerRequiredHackingLevel", `returned ${numeralWrapper.formatSkill(server.requiredHackingSkill, 0)} for '${server.hostname}'`); - return server.requiredHackingSkill; - }, - getServerMaxMoney: function(ip) { - updateDynamicRam("getServerMaxMoney", getRamCost("getServerMaxMoney")); - const server = safeGetServer(ip, "getServerMaxMoney"); - if (failOnHacknetServer(server, "getServerMaxMoney")) { return 0; } - workerScript.log("getServerMaxMoney", `returned ${numeralWrapper.formatMoney(server.moneyMax)} for '${server.hostname}'`); - return server.moneyMax; - }, - getServerGrowth: function(ip) { - updateDynamicRam("getServerGrowth", getRamCost("getServerGrowth")); - const server = safeGetServer(ip, "getServerGrowth"); - if (failOnHacknetServer(server, "getServerGrowth")) { return 1; } - workerScript.log("getServerGrowth", `returned ${server.serverGrowth} for '${server.hostname}'`); - return server.serverGrowth; - }, - getServerNumPortsRequired: function(ip) { - updateDynamicRam("getServerNumPortsRequired", getRamCost("getServerNumPortsRequired")); - const server = safeGetServer(ip, "getServerNumPortsRequired"); - if (failOnHacknetServer(server, "getServerNumPortsRequired")) { return 5; } - workerScript.log("getServerNumPortsRequired", `returned ${server.numOpenPortsRequired} for '${server.hostname}'`); - return server.numOpenPortsRequired; - }, - getServerRam: function(ip) { - updateDynamicRam("getServerRam", getRamCost("getServerRam")); - const server = safeGetServer(ip, "getServerRam"); - workerScript.log("getServerRam", `returned [${numeralWrapper.formatRAM(server.maxRam, 2)}, ${numeralWrapper.formatRAM(server.ramUsed, 2)}]`); - return [server.maxRam, server.ramUsed]; - }, - getServerMaxRam: function(ip) { - updateDynamicRam("getServerMaxRam", getRamCost("getServerMaxRam")); - const server = safeGetServer(ip, "getServerMaxRam"); - workerScript.log("getServerMaxRam", `returned ${numeralWrapper.formatRAM(server.maxRam, 2)}`); - return server.maxRam; - }, - getServerUsedRam: function(ip) { - updateDynamicRam("getServerUsedRam", getRamCost("getServerUsedRam")); - const server = safeGetServer(ip, "getServerUsedRam"); - workerScript.log("getServerUsedRam", `returned ${numeralWrapper.formatRAM(server.ramUsed, 2)}`); - return server.ramUsed; - }, - serverExists: function(ip) { - updateDynamicRam("serverExists", getRamCost("serverExists")); - return (getServer(ip) !== null); - }, - fileExists: function(filename,ip=workerScript.serverIp) { - updateDynamicRam("fileExists", getRamCost("fileExists")); - if (filename === undefined) { - throw makeRuntimeErrorMsg("fileExists", "Usage: fileExists(scriptname, [server])"); - } - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("fileExists", `Invalid IP/hostname: ${ip}`); - } - for (var i = 0; i < server.scripts.length; ++i) { - if (filename == server.scripts[i].filename) { - return true; - } - } - for (var i = 0; i < server.programs.length; ++i) { - if (filename.toLowerCase() == server.programs[i].toLowerCase()) { - return true; - } - } - for (var i = 0; i < server.messages.length; ++i) { - if (!(server.messages[i] instanceof Message) && - filename.toLowerCase() === server.messages[i]) { - return true; - } - } - var txtFile = getTextFile(filename, server); - if (txtFile != null) { - return true; - } + var costMult, expMult; + switch (universityName.toLowerCase()) { + case LocationName.AevumSummitUniversity.toLowerCase(): + if (Player.city != CityName.Aevum) { + workerScript.log( + "universityCourse", + "You cannot study at 'Summit University' because you are not in 'Aevum'.", + ); return false; - }, - isRunning: function(fn, ip=workerScript.serverIp, ...scriptArgs) { - updateDynamicRam("isRunning", getRamCost("isRunning")); - if (fn === undefined || ip === undefined) { - throw makeRuntimeErrorMsg("isRunning", "Usage: isRunning(scriptname, server, [arg1], [arg2]...)"); - } - if(typeof fn === 'number') { - return getRunningScriptByPid(fn, 'isRunning') != null; - } else { - return getRunningScript(fn, ip, "isRunning", scriptArgs) != null; - } - }, - getStockSymbols: function() { - updateDynamicRam("getStockSymbols", getRamCost("getStockSymbols")); - checkTixApiAccess("getStockSymbols"); - return Object.values(StockSymbols); - }, - getStockPrice: function(symbol) { - updateDynamicRam("getStockPrice", getRamCost("getStockPrice")); - checkTixApiAccess("getStockPrice"); - const stock = getStockFromSymbol(symbol, "getStockPrice"); - - return stock.price; - }, - getStockAskPrice: function(symbol) { - updateDynamicRam("getStockAskPrice", getRamCost("getStockAskPrice")); - checkTixApiAccess("getStockAskPrice"); - const stock = getStockFromSymbol(symbol, "getStockAskPrice"); - - return stock.getAskPrice(); - }, - getStockBidPrice: function(symbol) { - updateDynamicRam("getStockBidPrice", getRamCost("getStockBidPrice")); - checkTixApiAccess("getStockBidPrice"); - const stock = getStockFromSymbol(symbol, "getStockBidPrice"); - - return stock.getBidPrice(); - }, - getStockPosition: function(symbol) { - updateDynamicRam("getStockPosition", getRamCost("getStockPosition")); - checkTixApiAccess("getStockPosition"); - var stock = SymbolToStockMap[symbol]; - if (stock == null) { - throw makeRuntimeErrorMsg("getStockPosition", `Invalid stock symbol: ${symbol}`); - } - return [stock.playerShares, stock.playerAvgPx, stock.playerShortShares, stock.playerAvgShortPx]; - }, - getStockMaxShares: function(symbol) { - updateDynamicRam("getStockMaxShares", getRamCost("getStockMaxShares")); - checkTixApiAccess("getStockMaxShares"); - const stock = getStockFromSymbol(symbol, "getStockMaxShares"); - - return stock.maxShares; - }, - getStockPurchaseCost: function(symbol, shares, posType) { - updateDynamicRam("getStockPurchaseCost", getRamCost("getStockPurchaseCost")); - checkTixApiAccess("getStockPurchaseCost"); - const stock = getStockFromSymbol(symbol, "getStockPurchaseCost"); - shares = Math.round(shares); - - let pos; - const sanitizedPosType = posType.toLowerCase(); - if (sanitizedPosType.includes("l")) { - pos = PositionTypes.Long; - } else if (sanitizedPosType.includes("s")) { - pos = PositionTypes.Short; - } else { - return Infinity; - } - - const res = getBuyTransactionCost(stock, shares, pos); - if (res == null) { return Infinity; } - - return res; - }, - getStockSaleGain: function(symbol, shares, posType) { - updateDynamicRam("getStockSaleGain", getRamCost("getStockSaleGain")); - checkTixApiAccess("getStockSaleGain"); - const stock = getStockFromSymbol(symbol, "getStockSaleGain"); - shares = Math.round(shares); - - let pos; - const sanitizedPosType = posType.toLowerCase(); - if (sanitizedPosType.includes("l")) { - pos = PositionTypes.Long; - } else if (sanitizedPosType.includes("s")) { - pos = PositionTypes.Short; - } else { - return 0; - } - - const res = getSellTransactionGain(stock, shares, pos); - if (res == null) { return 0; } - - return res; - }, - buyStock: function(symbol, shares) { - updateDynamicRam("buyStock", getRamCost("buyStock")); - checkTixApiAccess("buyStock"); - const stock = getStockFromSymbol(symbol, "buyStock"); - const res = buyStock(stock, shares, workerScript, { rerenderFn: displayStockMarketContent }); - return res ? stock.price : 0; - }, - sellStock: function(symbol, shares) { - updateDynamicRam("sellStock", getRamCost("sellStock")); - checkTixApiAccess("sellStock"); - const stock = getStockFromSymbol(symbol, "sellStock"); - const res = sellStock(stock, shares, workerScript, { rerenderFn: displayStockMarketContent }); - - return res ? stock.price : 0; - }, - shortStock: function(symbol, shares) { - updateDynamicRam("shortStock", getRamCost("shortStock")); - checkTixApiAccess("shortStock"); - if (Player.bitNodeN !== 8) { - if (SourceFileFlags[8] <= 1) { - throw makeRuntimeErrorMsg(shortStock, "You must either be in BitNode-8 or you must have Source-File 8 Level 2."); - } - } - const stock = getStockFromSymbol(symbol, "shortStock"); - const res = shortStock(stock, shares, workerScript, { rerenderFn: displayStockMarketContent }); - - return res ? stock.price : 0; - }, - sellShort: function(symbol, shares) { - updateDynamicRam("sellShort", getRamCost("sellShort")); - checkTixApiAccess("sellShort"); - if (Player.bitNodeN !== 8) { - if (SourceFileFlags[8] <= 1) { - throw makeRuntimeErrorMsg("sellShort", "You must either be in BitNode-8 or you must have Source-File 8 Level 2."); - } - } - const stock = getStockFromSymbol(symbol, "sellShort"); - const res = sellShort(stock, shares, workerScript, { rerenderFn: displayStockMarketContent }); - - return res ? stock.price : 0; - }, - placeOrder: function(symbol, shares, price, type, pos) { - updateDynamicRam("placeOrder", getRamCost("placeOrder")); - checkTixApiAccess("placeOrder"); - if (Player.bitNodeN !== 8) { - if (SourceFileFlags[8] <= 2) { - throw makeRuntimeErrorMsg("placeOrder", "You must either be in BitNode-8 or you must have Source-File 8 Level 3."); - } - } - const stock = getStockFromSymbol(symbol, "placeOrder"); - - let orderType, orderPos; - ltype = type.toLowerCase(); - if (ltype.includes("limit") && ltype.includes("buy")) { - orderType = OrderTypes.LimitBuy; - } else if (ltype.includes("limit") && ltype.includes("sell")) { - orderType = OrderTypes.LimitSell; - } else if (ltype.includes("stop") && ltype.includes("buy")) { - orderType = OrderTypes.StopBuy; - } else if (ltype.includes("stop") && ltype.includes("sell")) { - orderType = OrderTypes.StopSell; - } else { - throw makeRuntimeErrorMsg("placeOrder", `Invalid order type: ${type}`); - } - - lpos = pos.toLowerCase(); - if (lpos.includes("l")) { - orderPos = PositionTypes.Long; - } else if (lpos.includes('s')) { - orderPos = PositionTypes.Short; - } else { - throw makeRuntimeErrorMsg("placeOrder", `Invalid position type: ${pos}`); - } - - return placeOrder(stock, shares, price, orderType, orderPos, workerScript); - }, - cancelOrder: function(symbol, shares, price, type, pos) { - updateDynamicRam("cancelOrder", getRamCost("cancelOrder")); - checkTixApiAccess("cancelOrder"); - if (Player.bitNodeN !== 8) { - if (SourceFileFlags[8] <= 2) { - throw makeRuntimeErrorMsg("cancelOrder", "You must either be in BitNode-8 or you must have Source-File 8 Level 3."); - } - } - const stock = getStockFrom(symbol, "cancelOrder"); - if (isNaN(shares) || isNaN(price)) { - throw makeRuntimeErrorMsg("cancelOrder", `Invalid shares or price. Must be numeric. shares=${shares}, price=${price}`); - } - var orderType, orderPos; - ltype = type.toLowerCase(); - if (ltype.includes("limit") && ltype.includes("buy")) { - orderType = OrderTypes.LimitBuy; - } else if (ltype.includes("limit") && ltype.includes("sell")) { - orderType = OrderTypes.LimitSell; - } else if (ltype.includes("stop") && ltype.includes("buy")) { - orderType = OrderTypes.StopBuy; - } else if (ltype.includes("stop") && ltype.includes("sell")) { - orderType = OrderTypes.StopSell; - } else { - throw makeRuntimeErrorMsg("cancelOrder", `Invalid order type: ${type}`); - } - - lpos = pos.toLowerCase(); - if (lpos.includes("l")) { - orderPos = PositionTypes.Long; - } else if (lpos.includes('s')) { - orderPos = PositionTypes.Short; - } else { - throw makeRuntimeErrorMsg("cancelOrder", `Invalid position type: ${pos}`); - } - var params = { - stock: stock, - shares: shares, - price: price, - type: orderType, - pos: orderPos, - }; - return cancelOrder(params, workerScript); - }, - getOrders: function() { - updateDynamicRam("getOrders", getRamCost("getOrders")); - checkTixApiAccess("getOrders"); - if (Player.bitNodeN !== 8) { - if (SourceFileFlags[8] <= 2) { - throw makeRuntimeErrorMsg(workerScript, "You must either be in BitNode-8 or have Source-File 8 Level 3."); - } - } - - const orders = {}; - - const stockMarketOrders = StockMarket["Orders"]; - for (let symbol in stockMarketOrders) { - const orderBook = stockMarketOrders[symbol]; - if (orderBook.constructor === Array && orderBook.length > 0) { - orders[symbol] = []; - for (let i = 0; i < orderBook.length; ++i) { - orders[symbol].push({ - shares: orderBook[i].shares, - price: orderBook[i].price, - type: orderBook[i].type, - position: orderBook[i].pos, - }); - } - } - } - - return orders; - }, - getStockVolatility: function(symbol) { - updateDynamicRam("getStockVolatility", getRamCost("getStockVolatility")); - if (!Player.has4SDataTixApi) { - throw makeRuntimeErrorMsg("getStockVolatility", "You don't have 4S Market Data TIX API Access!"); - } - const stock = getStockFromSymbol(symbol, "getStockVolatility"); - - return stock.mv / 100; // Convert from percentage to decimal - }, - getStockForecast: function(symbol) { - updateDynamicRam("getStockForecast", getRamCost("getStockForecast")); - if (!Player.has4SDataTixApi) { - throw makeRuntimeErrorMsg("getStockForecast", "You don't have 4S Market Data TIX API Access!"); - } - const stock = getStockFromSymbol(symbol, "getStockForecast"); - - var forecast = 50; - stock.b ? forecast += stock.otlkMag : forecast -= stock.otlkMag; - return forecast / 100; // Convert from percentage to decimal - }, - purchase4SMarketData: function() { - updateDynamicRam("purchase4SMarketData", getRamCost("purchase4SMarketData")); - checkTixApiAccess("purchase4SMarketData"); - - if (Player.has4SData) { - workerScript.log("purchase4SMarketData", "Already purchased 4S Market Data."); - return true; - } - - if (Player.money.lt(getStockMarket4SDataCost())) { - workerScript.log("purchase4SMarketData", "Not enough money to purchase 4S Market Data."); - return false; - } - - Player.has4SData = true; - Player.loseMoney(getStockMarket4SDataCost()); - workerScript.log("purchase4SMarketData", "Purchased 4S Market Data"); - displayStockMarketContent(); - return true; - }, - purchase4SMarketDataTixApi : function() { - updateDynamicRam("purchase4SMarketDataTixApi", getRamCost("purchase4SMarketDataTixApi")); - checkTixApiAccess("purchase4SMarketDataTixApi"); - - if (Player.has4SDataTixApi) { - workerScript.log("purchase4SMarketDataTixApi", "Already purchased 4S Market Data TIX API"); - return true; - } - - if (Player.money.lt(getStockMarket4STixApiCost())) { - workerScript.log("purchase4SMarketDataTixApi", "Not enough money to purchase 4S Market Data TIX API"); - return false; - } - - Player.has4SDataTixApi = true; - Player.loseMoney(getStockMarket4STixApiCost()); - workerScript.log("purchase4SMarketDataTixApi", "Purchased 4S Market Data TIX API"); - displayStockMarketContent(); - return true; - }, - getPurchasedServerLimit : function() { - updateDynamicRam("getPurchasedServerLimit", getRamCost("getPurchasedServerLimit")); - - return getPurchaseServerLimit(); - }, - getPurchasedServerMaxRam: function() { - updateDynamicRam("getPurchasedServerMaxRam", getRamCost("getPurchasedServerMaxRam")); - - return getPurchaseServerMaxRam(); - }, - getPurchasedServerCost: function(ram) { - updateDynamicRam("getPurchasedServerCost", getRamCost("getPurchasedServerCost")); - - const cost = getPurchaseServerCost(ram); - if (cost === Infinity) { - workerScript.log("getPurchasedServerCost", `Invalid argument: ram='${ram}'`); - return Infinity; - } - - return cost; - }, - purchaseServer: function(hostname, ram) { - updateDynamicRam("purchaseServer", getRamCost("purchaseServer")); - var hostnameStr = String(hostname); - hostnameStr = hostnameStr.replace(/\s+/g, ''); - if (hostnameStr == "") { - workerScript.log("purchaseServer", `Invalid argument: hostname='${hostnameStr}'`); - return ""; - } - - if (Player.purchasedServers.length >= getPurchaseServerLimit()) { - workerScript.log("purchaseServer", `You have reached the maximum limit of ${getPurchaseServerLimit()} servers. You cannot purchase any more.`); - return ""; - } - - const cost = getPurchaseServerCost(ram); - if (cost === Infinity) { - workerScript.log("purchaseServer", `Invalid argument: ram='${ram}'`); - return ""; - } - - if (Player.money.lt(cost)) { - workerScript.log("purchaseServer", `Not enough money to purchase server. Need ${numeralWrapper.formatMoney(cost)}`); - return ""; - } - var newServ = safetlyCreateUniqueServer({ - ip: createUniqueRandomIp(), - hostname: hostnameStr, - organizationName: "", - isConnectedTo: false, - adminRights: true, - purchasedByPlayer: true, - maxRam: ram, - }); - AddToAllServers(newServ); - - Player.purchasedServers.push(newServ.ip); - var homeComputer = Player.getHomeComputer(); - homeComputer.serversOnNetwork.push(newServ.ip); - newServ.serversOnNetwork.push(homeComputer.ip); - Player.loseMoney(cost); - workerScript.log("purchaseServer", `Purchased new server with hostname '${newServ.hostname}' for ${numeralWrapper.formatMoney(cost)}`); - return newServ.hostname; - }, - deleteServer: function(hostname) { - updateDynamicRam("deleteServer", getRamCost("deleteServer")); - var hostnameStr = String(hostname); - hostnameStr = hostnameStr.replace(/\s\s+/g, ''); - var server = GetServerByHostname(hostnameStr); - if (server == null) { - workerScript.log("deleteServer", `Invalid argument: hostname='${hostnameStr}'`); - return false; - } - - if (!server.purchasedByPlayer || server.hostname === "home") { - workerScript.log("deleteServer", "Cannot delete non-purchased server."); - return false; - } - - var ip = server.ip; - - // Can't delete server you're currently connected to - if (server.isConnectedTo) { - workerScript.log("deleteServer", "You are currently connected to the server you are trying to delete."); - return false; - } - - // A server cannot delete itself - if (ip === workerScript.serverIp) { - workerScript.log("deleteServer", "Cannot delete the server this script is running on."); - return false; - } - - // Delete all scripts running on server - if (server.runningScripts.length > 0) { - workerScript.log("deleteServer", `Cannot delete server '${server.hostname}' because it still has scripts running.`); - return false; - } - - // Delete from player's purchasedServers array - var found = false; - for (var i = 0; i < Player.purchasedServers.length; ++i) { - if (ip == Player.purchasedServers[i]) { - found = true; - Player.purchasedServers.splice(i, 1); - break; - } - } - - if (!found) { - workerScript.log("deleteServer", `Could not identify server ${server.hostname} as a purchased server. This is a bug. Report to dev.`); - return false; - } - - // Delete from all servers - delete AllServers[ip]; - - // Delete from home computer - found = false; - var homeComputer = Player.getHomeComputer(); - for (var i = 0; i < homeComputer.serversOnNetwork.length; ++i) { - if (ip == homeComputer.serversOnNetwork[i]) { - homeComputer.serversOnNetwork.splice(i, 1); - workerScript.log("deleteServer", `Deleted server '${hostnameStr}`); - return true; - } - } - // Wasn't found on home computer - workerScript.log("deleteServer", `Could not find server ${server.hostname} as a purchased server. This is a bug. Report to dev.`); + } + Player.gotoLocation(LocationName.AevumSummitUniversity); + costMult = 4; + expMult = 3; + break; + case LocationName.Sector12RothmanUniversity.toLowerCase(): + if (Player.city != CityName.Sector12) { + workerScript.log( + "universityCourse", + "You cannot study at 'Rothman University' because you are not in 'Sector-12'.", + ); return false; - }, - getPurchasedServers: function(hostname=true) { - updateDynamicRam("getPurchasedServers", getRamCost("getPurchasedServers")); - var res = []; - Player.purchasedServers.forEach(function(ip) { - if (hostname) { - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("getPurchasedServers", "Could not find server. This is a bug. Report to dev."); - } - res.push(server.hostname); - } else { - res.push(ip); - } - }); - return res; - }, - write: function(port, data="", mode="a") { - updateDynamicRam("write", getRamCost("write")); - if (!isNaN(port)) { // Write to port - // Port 1-10 - port = Math.round(port); - if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { - throw makeRuntimeErrorMsg("write", `Trying to write to invalid port: ${port}. Only ports 1-${CONSTANTS.NumNetscriptPorts} are valid.`); - } - var port = NetscriptPorts[port-1]; - if (port == null || !(port instanceof NetscriptPort)) { - throw makeRuntimeErrorMsg("write", `Could not find port: ${port}. This is a bug. Report to dev.`); - } - return port.write(data); - } else if (isString(port)) { // Write to script or text file - let fn = port; - if (!isValidFilePath(fn)) { - throw makeRuntimeErrorMsg("write", `Invalid filepath: ${fn}`); - } - - if(fn.lastIndexOf("/") === 0) { - fn = removeLeadingSlash(fn); - } - - // Coerce 'data' to be a string - try { - data = String(data); - } catch (e) { - throw makeRuntimeErrorMsg("write", `Invalid data (${e}). Data being written must be convertible to a string`); - } - - const server = workerScript.getServer(); - if (server == null) { - throw makeRuntimeErrorMsg("write", "Error getting Server. This is a bug. Report to dev."); - } - if (isScriptFilename(fn)) { - // Write to script - let script = workerScript.getScriptOnServer(fn); - if (script == null) { - // Create a new script - script = new Script(fn, data, server.ip, server.scripts); - server.scripts.push(script); - return true; - } - mode === "w" ? script.code = data : script.code += data; - script.updateRamUsage(server.scripts); - script.markUpdated(); - } else { - // Write to text file - let txtFile = getTextFile(fn, server); - if (txtFile == null) { - txtFile = createTextFile(fn, data, server); - return true; - } - if (mode === "w") { - txtFile.write(data); - } else { - txtFile.append(data); - } - } - return true; - } else { - throw makeRuntimeErrorMsg("write", `Invalid argument: ${port}`); - } - }, - tryWrite: function(port, data="") { - updateDynamicRam("tryWrite", getRamCost("tryWrite")); - if (!isNaN(port)) { - port = Math.round(port); - if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { - throw makeRuntimeErrorMsg("tryWrite", `Invalid port: ${port}. Only ports 1-${CONSTANTS.NumNetscriptPorts} are valid.`); - } - var port = NetscriptPorts[port-1]; - if (port == null || !(port instanceof NetscriptPort)) { - throw makeRuntimeErrorMsg("tryWrite", `Could not find port: ${port}. This is a bug. Report to dev.`); - } - return port.tryWrite(data); - } else { - throw makeRuntimeErrorMsg("tryWrite", `Invalid argument: ${port}`); - } - }, - read: function(port) { - updateDynamicRam("read", getRamCost("read")); - if (!isNaN(port)) { // Read from port - // Port 1-10 - port = Math.round(port); - if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { - throw makeRuntimeErrorMsg("read", `Invalid port: ${port}. Only ports 1-${CONSTANTS.NumNetscriptPorts} are valid.`); - } - var port = NetscriptPorts[port-1]; - if (port == null || !(port instanceof NetscriptPort)) { - throw makeRuntimeErrorMsg("read", `Could not find port: ${port}. This is a bug. Report to dev.`); - } - return port.read(); - } else if (isString(port)) { // Read from script or text file - let fn = port; - let server = getServer(workerScript.serverIp); - if (server == null) { - throw makeRuntimeErrorMsg("read", "Error getting Server. This is a bug. Report to dev."); - } - if (isScriptFilename(fn)) { - // Read from script - let script = workerScript.getScriptOnServer(fn); - if (script == null) { - return ""; - } - return script.code; - } else { - // Read from text file - let txtFile = getTextFile(fn, server); - if (txtFile !== null) { - return txtFile.text; - } else { - return ""; - } - } - } else { - throw makeRuntimeErrorMsg("read", `Invalid argument: ${port}`); - } - }, - peek: function(port) { - updateDynamicRam("peek", getRamCost("peek")); - if (isNaN(port)) { - throw makeRuntimeErrorMsg("peek", `Invalid argument. Must be a port number between 1 and ${CONSTANTS.NumNetscriptPorts}, is ${port}`); - } - port = Math.round(port); - if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { - throw makeRuntimeErrorMsg("peek", `Invalid argument. Must be a port number between 1 and ${CONSTANTS.NumNetscriptPorts}, is ${port}`); - } - var port = NetscriptPorts[port-1]; - if (port == null || !(port instanceof NetscriptPort)) { - throw makeRuntimeErrorMsg("peek", `Could not find port: ${port}. This is a bug. Report to dev.`); - } - return port.peek(); - }, - clear: function(port) { - updateDynamicRam("clear", getRamCost("clear")); - if (!isNaN(port)) { // Clear port - port = Math.round(port); - if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { - throw makeRuntimeErrorMsg("clear", `Trying to clear invalid port: ${port}. Only ports 1-${CONSTANTS.NumNetscriptPorts} are valid`); - } - var port = NetscriptPorts[port-1]; - if (port == null || !(port instanceof NetscriptPort)) { - throw makeRuntimeErrorMsg("clear", `Could not find port: ${port}. This is a bug. Report to dev.`); - } - return port.clear(); - } else if (isString(port)) { // Clear text file - var fn = port; - var server = getServer(workerScript.serverIp); - if (server == null) { - throw makeRuntimeErrorMsg("clear", "Error getting Server. This is a bug. Report to dev."); - } - var txtFile = getTextFile(fn, server); - if (txtFile != null) { - txtFile.write(""); - } - } else { - throw makeRuntimeErrorMsg("clear", `Invalid argument: ${port}`); - } - return 0; - }, - getPortHandle: function(port) { - updateDynamicRam("getPortHandle", getRamCost("getPortHandle")); - if (isNaN(port)) { - throw makeRuntimeErrorMsg("getPortHandle", `Invalid port: ${port} Must be an integer between 1 and ${CONSTANTS.NumNetscriptPorts}.`); - } - port = Math.round(port); - if (port < 1 || port > CONSTANTS.NumNetscriptPorts) { - throw makeRuntimeErrorMsg("getPortHandle", `Invalid port: ${port}. Only ports 1-${CONSTANTS.NumNetscriptPorts} are valid.`); - } - var port = NetscriptPorts[port-1]; - if (port == null || !(port instanceof NetscriptPort)) { - throw makeRuntimeErrorMsg("getPortHandle", `Could not find port: ${port}. This is a bug. Report to dev.`); - } - return port; - }, - rm: function(fn, ip) { - updateDynamicRam("rm", getRamCost("rm")); - - if (ip == null || ip === "") { - ip = workerScript.serverIp; - } - const s = safeGetServer(ip, "rm"); - - const status = s.removeFile(fn); - if (!status.res) { - workerScript.log("rm", status.msg); - } - - return status.res; - }, - scriptRunning: function(scriptname, ip) { - updateDynamicRam("scriptRunning", getRamCost("scriptRunning")); - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("scriptRunning", `Invalid IP/hostname: ${ip}`); - } - for (var i = 0; i < server.runningScripts.length; ++i) { - if (server.runningScripts[i].filename == scriptname) { - return true; - } - } + } + Player.location = LocationName.Sector12RothmanUniversity; + costMult = 3; + expMult = 2; + break; + case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase(): + if (Player.city != CityName.Volhaven) { + workerScript.log( + "universityCourse", + "You cannot study at 'ZB Institute of Technology' because you are not in 'Volhaven'.", + ); return false; - }, - scriptKill: function(scriptname, ip) { - updateDynamicRam("scriptKill", getRamCost("scriptKill")); - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("scriptKill", `Invalid IP/hostname: ${ip}`); - } - var suc = false; - for (var i = 0; i < server.runningScripts.length; ++i) { - if (server.runningScripts[i].filename == scriptname) { - killWorkerScript(server.runningScripts[i], server.ip); - suc = true; - } - } - return suc; - }, - getScriptName: function() { - return workerScript.name; - }, - getScriptRam: function(scriptname, ip=workerScript.serverIp) { - updateDynamicRam("getScriptRam", getRamCost("getScriptRam")); - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("getScriptRam", `Invalid IP/hostname: ${ip}`); - } - for (var i = 0; i < server.scripts.length; ++i) { - if (server.scripts[i].filename == scriptname) { - return server.scripts[i].ramUsage; - } - } - return 0; - }, - getRunningScript: function(fn, ip) { - updateDynamicRam("getRunningScript", getRamCost("getRunningScript")); + } + Player.location = LocationName.VolhavenZBInstituteOfTechnology; + costMult = 5; + expMult = 4; + break; + default: + workerScript.log( + "universityCourse", + `Invalid university name: '${universityName}'.`, + ); + return false; + } - let runningScript; - if(arguments.length === 0) { - runningScript = workerScript.scriptRef; - } else if(typeof fn === 'number') { - runningScript = getRunningScriptByPid(fn, 'getRunningScript'); - } else { - const scriptArgs = []; - for (var i = 2; i < arguments.length; ++i) { - scriptArgs.push(arguments[i]); - } - runningScript = getRunningScript(fn, ip, 'getRunningScript', scriptArgs); - } - if (runningScript === null) return null; - return { - args: runningScript.args.slice(), - filename: runningScript.filename, - logs: runningScript.logs.slice(), - offlineExpGained: runningScript.offlineExpGained, - offlineMoneyMade: runningScript.offlineMoneyMade, - offlineRunningTime: runningScript.offlineRunningTime, - onlineExpGained: runningScript.onlineExpGained, - onlineMoneyMade: runningScript.onlineMoneyMade, - onlineRunningTime: runningScript.onlineRunningTime, - pid: runningScript.pid, - ramUsage: runningScript.ramUsage, - server: runningScript.server, - threads: runningScript.threads, - }; - }, - getHackTime: function(ip) { - updateDynamicRam("getHackTime", getRamCost("getHackTime")); - const server = safeGetServer(ip, "getHackTime"); - if (failOnHacknetServer(server, "getHackTime")) { return Infinity; } - - return calculateHackingTime(server, Player); // Returns seconds - }, - getGrowTime: function(ip) { - updateDynamicRam("getGrowTime", getRamCost("getGrowTime")); - const server = safeGetServer(ip, "getGrowTime"); - if (failOnHacknetServer(server, "getGrowTime")) { return Infinity; } - - return calculateGrowTime(server, Player); // Returns seconds - }, - getWeakenTime: function(ip) { - updateDynamicRam("getWeakenTime", getRamCost("getWeakenTime")); - const server = safeGetServer(ip, "getWeakenTime"); - if (failOnHacknetServer(server, "getWeakenTime")) { return Infinity; } - - return calculateWeakenTime(server, Player); // Returns seconds - }, - getScriptIncome: function(scriptname, ip) { - updateDynamicRam("getScriptIncome", getRamCost("getScriptIncome")); - if (arguments.length === 0) { - var res = []; - - // First element is total income of all currently running scripts - let total = 0; - for (const script of workerScripts.values()) { - total += (script.scriptRef.onlineMoneyMade / script.scriptRef.onlineRunningTime); - } - res.push(total); - - // Second element is total income you've earned from scripts since you installed Augs - res.push(Player.scriptProdSinceLastAug / (Player.playtimeSinceLastAug / 1000)); - return res; - } else { - // Get income for a particular script - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("getScriptIncome", `Invalid IP/hostnamed: ${ip}`); - } - var argsForScript = []; - for (var i = 2; i < arguments.length; ++i) { - argsForScript.push(arguments[i]); - } - var runningScriptObj = findRunningScript(scriptname, argsForScript, server); - if (runningScriptObj == null) { - workerScript.log("getScriptIncome", `No such script '${scriptname}' on '${server.hostname}' with args: ${arrayToString(argsForScript)}`); - return -1; - } - return runningScriptObj.onlineMoneyMade / runningScriptObj.onlineRunningTime; - } - }, - getScriptExpGain: function(scriptname, ip) { - updateDynamicRam("getScriptExpGain", getRamCost("getScriptExpGain")); - if (arguments.length === 0) { - var total = 0; - for (const ws of workerScripts.values()) { - total += (ws.scriptRef.onlineExpGained / ws.scriptRef.onlineRunningTime); - } - return total; - } else { - // Get income for a particular script - var server = getServer(ip); - if (server == null) { - throw makeRuntimeErrorMsg("getScriptExpGain", `Invalid IP/hostnamed: ${ip}`); - } - var argsForScript = []; - for (var i = 2; i < arguments.length; ++i) { - argsForScript.push(arguments[i]); - } - var runningScriptObj = findRunningScript(scriptname, argsForScript, server); - if (runningScriptObj == null) { - workerScript.log("getScriptExpGain", `No such script '${scriptname}' on '${server.hostname}' with args: ${arrayToString(argsForScript)}`); - return -1; - } - return runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime; - } - }, - nFormat: function(n, format) { - if (isNaN(n) || isNaN(parseFloat(n)) || typeof format !== "string") { - return ""; - } - - return numeralWrapper.format(parseFloat(n), format); - }, - tFormat: function(milliseconds, milliPrecision=false) { - return convertTimeMsToTimeElapsedString(milliseconds, milliPrecision); - }, - getTimeSinceLastAug: function() { - updateDynamicRam("getTimeSinceLastAug", getRamCost("getTimeSinceLastAug")); - return Player.playtimeSinceLastAug; - }, - prompt : function(txt) { - if (!isString(txt)) {txt = JSON.stringify(txt);} - - // The id for this popup will consist of the first 20 characters of the prompt string.. - // Thats hopefully good enough to be unique - const popupId = `prompt-popup-${txt.slice(0, 20)}`; - const textElement = createElement("p", { innerHTML: txt }); - - return new Promise(function(resolve) { - const yesBtn = createElement("button", { - class: "popup-box-button", - innerText: "Yes", - clickListener: () => { - removeElementById(popupId); - resolve(true); - }, - }); - - const noBtn = createElement("button", { - class: "popup-box-button", - innerText: "No", - clickListener: () => { - removeElementById(popupId); - resolve(false); - }, - }); - - createPopup(popupId, [textElement, yesBtn, noBtn]); - }); - }, - wget: async function(url, target, ip=workerScript.serverIp) { - if (!isScriptFilename(target) && !target.endsWith(".txt")) { - workerScript.log("wget", `Invalid target file: '${target}'. Must be a script or text file.`); - return Promise.resolve(false); - } - var s = safeGetServer(ip, "wget"); - return new Promise(function(resolve) { - $.get(url, function(data) { - let res; - if (isScriptFilename(target)) { - res = s.writeToScriptFile(target, data); - } else { - res = s.writeToTextFile(target, data); - } - if (!res.success) { - workerScript.log("wget", "Failed."); - return resolve(false); - } - if (res.overwritten) { - workerScript.log("wget", `Successfully retrieved content and overwrote '${target}' on '${ip}'`); - return resolve(true); - } - workerScript.log("wget", `Successfully retrieved content to new file '${target}' on '${ip}'`); - return resolve(true); - }, 'text').fail(function(e) { - workerScript.log("wget", JSON.stringify(e)); - return resolve(false) - }); - }); - }, - getFavorToDonate: function() { - updateDynamicRam("getFavorToDonate", getRamCost("getFavorToDonate")); - return Math.floor(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction); - }, - - /* Singularity Functions */ - universityCourse: function(universityName, className) { - updateDynamicRam("universityCourse", getRamCost("universityCourse")); - checkSingularityAccess("universityCourse", 1); - if (inMission) { - workerScript.log("universityCourse", "You are in the middle of a mission."); - return; - } - if (Player.isWorking) { - var txt = Player.singularityStopWork(); - workerScript.log("universityCourse", txt); - } - - var costMult, expMult; - switch(universityName.toLowerCase()) { - case LocationName.AevumSummitUniversity.toLowerCase(): - if (Player.city != CityName.Aevum) { - workerScript.log("universityCourse", "You cannot study at 'Summit University' because you are not in 'Aevum'."); - return false; - } - Player.gotoLocation(LocationName.AevumSummitUniversity); - costMult = 4; - expMult = 3; - break; - case LocationName.Sector12RothmanUniversity.toLowerCase(): - if (Player.city != CityName.Sector12) { - workerScript.log("universityCourse", "You cannot study at 'Rothman University' because you are not in 'Sector-12'."); - return false; - } - Player.location = LocationName.Sector12RothmanUniversity; - costMult = 3; - expMult = 2; - break; - case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase(): - if (Player.city != CityName.Volhaven) { - workerScript.log("universityCourse", "You cannot study at 'ZB Institute of Technology' because you are not in 'Volhaven'."); - return false; - } - Player.location = LocationName.VolhavenZBInstituteOfTechnology; - costMult = 5; - expMult = 4; - break; - default: - workerScript.log("universityCourse", `Invalid university name: '${universityName}'.`); - return false; - } - - var task; - switch(className.toLowerCase()) { - case "Study Computer Science".toLowerCase(): - task = CONSTANTS.ClassStudyComputerScience; - break; - case "Data Structures".toLowerCase(): - task = CONSTANTS.ClassDataStructures; - break; - case "Networks".toLowerCase(): - task = CONSTANTS.ClassNetworks; - break; - case "Algorithms".toLowerCase(): - task = CONSTANTS.ClassAlgorithms; - break; - case "Management".toLowerCase(): - task = CONSTANTS.ClassManagement; - break; - case "Leadership".toLowerCase(): - task = CONSTANTS.ClassLeadership; - break; - default: - workerScript.log("universityCourse", `Invalid class name: ${className}.`); - return false; - } - Player.startClass(costMult, expMult, task); - workerScript.log("universityCourse", `Started ${task} at ${universityName}`); - return true; - }, - - gymWorkout: function(gymName, stat) { - updateDynamicRam("gymWorkout", getRamCost("gymWorkout")); - checkSingularityAccess("gymWorkout", 1); - if (inMission) { - workerScript.log("gymWorkout", "You are in the middle of a mission."); - return; - } - if (Player.isWorking) { - var txt = Player.singularityStopWork(); - workerScript.log("gymWorkout", txt); - } - var costMult, expMult; - switch(gymName.toLowerCase()) { - case LocationName.AevumCrushFitnessGym.toLowerCase(): - if (Player.city != CityName.Aevum) { - workerScript.log("gymWorkout", "You cannot workout at 'Crush Fitness' because you are not in 'Aevum'."); - return false; - } - Player.location = LocationName.AevumCrushFitnessGym; - costMult = 3; - expMult = 2; - break; - case LocationName.AevumSnapFitnessGym.toLowerCase(): - if (Player.city != CityName.Aevum) { - workerScript.log("gymWorkout", "You cannot workout at 'Snap Fitness' because you are not in 'Aevum'."); - return false; - } - Player.location = LocationName.AevumSnapFitnessGym; - costMult = 10; - expMult = 5; - break; - case LocationName.Sector12IronGym.toLowerCase(): - if (Player.city != CityName.Sector12) { - workerScript.log("gymWorkout", "You cannot workout at 'Iron Gym' because you are not in 'Sector-12'."); - return false; - } - Player.location = LocationName.Sector12IronGym; - costMult = 1; - expMult = 1; - break; - case LocationName.Sector12PowerhouseGym.toLowerCase(): - if (Player.city != CityName.Sector12) { - workerScript.log("gymWorkout", "You cannot workout at 'Powerhouse Gym' because you are not in 'Sector-12'."); - return false; - } - Player.location = LocationName.Sector12PowerhouseGym; - costMult = 20; - expMult = 10; - break; - case LocationName.VolhavenMilleniumFitnessGym.toLowerCase(): - if (Player.city != CityName.Volhaven) { - workerScript.log("gymWorkout", "You cannot workout at 'Millenium Fitness Gym' because you are not in 'Volhaven'."); - return false; - } - Player.location = LocationName.VolhavenMilleniumFitnessGym; - costMult = 7; - expMult = 4; - break; - default: - workerScript.log("gymWorkout", `Invalid gym name: ${gymName}. gymWorkout() failed`); - return false; - } - - switch(stat.toLowerCase()) { - case "strength".toLowerCase(): - case "str".toLowerCase(): - Player.startClass(costMult, expMult, CONSTANTS.ClassGymStrength); - break; - case "defense".toLowerCase(): - case "def".toLowerCase(): - Player.startClass(costMult, expMult, CONSTANTS.ClassGymDefense); - break; - case "dexterity".toLowerCase(): - case "dex".toLowerCase(): - Player.startClass(costMult, expMult, CONSTANTS.ClassGymDexterity); - break; - case "agility".toLowerCase(): - case "agi".toLowerCase(): - Player.startClass(costMult, expMult, CONSTANTS.ClassGymAgility); - break; - default: - workerScript.log("gymWorkout", `Invalid stat: ${stat}.`); - return false; - } - workerScript.log("gymWorkout", `Started training ${stat} at ${gymName}`); - return true; - }, - - travelToCity: function(cityname) { - updateDynamicRam("travelToCity", getRamCost("travelToCity")); - checkSingularityAccess("travelToCity", 1); - - switch(cityname) { - case CityName.Aevum: - case CityName.Chongqing: - case CityName.Sector12: - case CityName.NewTokyo: - case CityName.Ishima: - case CityName.Volhaven: - if(Player.money.lt(CONSTANTS.TravelCost)) { - throw makeRuntimeErrorMsg("travelToCity", "Not enough money to travel."); - } - Player.loseMoney(CONSTANTS.TravelCost); - Player.city = cityname; - workerScript.log("travelToCity", `Traveled to ${cityname}`); - return true; - default: - workerScript.log("travelToCity", `Invalid city name: '${cityname}'.`); - return false; - } - }, - - purchaseTor: function() { - updateDynamicRam("purchaseTor", getRamCost("purchaseTor")); - checkSingularityAccess("purchaseTor", 1); - - if (SpecialServerIps["Darkweb Server"] != null) { - workerScript.log("purchaseTor", "You already have a TOR router!"); - return false; - } - - if (Player.money.lt(CONSTANTS.TorRouterCost)) { - workerScript.log("purchaseTor", "You cannot afford to purchase a Tor router."); - return false; - } - Player.loseMoney(CONSTANTS.TorRouterCost); - - var darkweb = safetlyCreateUniqueServer({ - ip: createUniqueRandomIp(), hostname:"darkweb", organizationName:"", - isConnectedTo:false, adminRights:false, purchasedByPlayer:false, maxRam:1, - }); - AddToAllServers(darkweb); - SpecialServerIps.addIp("Darkweb Server", darkweb.ip); - - Player.getHomeComputer().serversOnNetwork.push(darkweb.ip); - darkweb.serversOnNetwork.push(Player.getHomeComputer().ip); - Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); - workerScript.log("purchaseTor", "You have purchased a Tor router!"); - return true; - }, - purchaseProgram: function(programName) { - updateDynamicRam("purchaseProgram", getRamCost("purchaseProgram")); - checkSingularityAccess("purchaseProgram", 1); - - if (SpecialServerIps["Darkweb Server"] == null) { - workerScript.log("purchaseProgram", "You do not have the TOR router."); - return false; - } - - programName = programName.toLowerCase(); - - let item = null; - for(const key in DarkWebItems) { - const i = DarkWebItems[key]; - if(i.program.toLowerCase() == programName) { - item = i; - } - } - - if(item == null) { - workerScript.log("purchaseProgram", `Invalid program name: '${programName}.`); - return false; - } - - if(Player.money.lt(item.price)) { - workerScript.log("purchaseProgram", `Not enough money to purchase '${item.program}'. Need ${numeralWrapper.formatMoney(item.price)}`); - return false; - } - - - if(Player.hasProgram(item.program)) { - workerScript.log("purchaseProgram", `You already have the '${item.program}' program`); - return true; - } - - Player.loseMoney(item.price); - Player.getHomeComputer().programs.push(item.program); - workerScript.log("purchaseProgram", `You have purchased the '${item.program}' program. The new program can be found on your home computer.`); - return true; - }, - getCurrentServer: function() { - updateDynamicRam("getCurrentServer", getRamCost("getCurrentServer")); - checkSingularityAccess("getCurrentServer", 1); - return Player.getCurrentServer().hostname; - }, - connect: function(hostname) { - updateDynamicRam("connect", getRamCost("connect")); - checkSingularityAccess("connect", 1); - if (!hostname) { - throw makeRuntimeErrorMsg("connect", `Invalid hostname: '${hostname}'`); - } - - let target = getServer(hostname); - if (target == null) { - throw makeRuntimeErrorMsg("connect", `Invalid hostname: '${hostname}'`); - return; - } - - if(hostname === 'home') { - Player.getCurrentServer().isConnectedTo = false; - Player.currentServer = Player.getHomeComputer().ip; - Player.getCurrentServer().isConnectedTo = true; - Terminal.currDir = "/"; - Terminal.resetTerminalInput(true); - return true; - } - - const server = Player.getCurrentServer(); - for (let i = 0; i < server.serversOnNetwork.length; i++) { - const other = getServerOnNetwork(server, i); - if (other.ip == hostname || other.hostname == hostname) { - Player.getCurrentServer().isConnectedTo = false; - Player.currentServer = target.ip; - Player.getCurrentServer().isConnectedTo = true; - Terminal.currDir = "/"; - Terminal.resetTerminalInput(true); - return true; - } - } + var task; + switch (className.toLowerCase()) { + case "Study Computer Science".toLowerCase(): + task = CONSTANTS.ClassStudyComputerScience; + break; + case "Data Structures".toLowerCase(): + task = CONSTANTS.ClassDataStructures; + break; + case "Networks".toLowerCase(): + task = CONSTANTS.ClassNetworks; + break; + case "Algorithms".toLowerCase(): + task = CONSTANTS.ClassAlgorithms; + break; + case "Management".toLowerCase(): + task = CONSTANTS.ClassManagement; + break; + case "Leadership".toLowerCase(): + task = CONSTANTS.ClassLeadership; + break; + default: + workerScript.log( + "universityCourse", + `Invalid class name: ${className}.`, + ); + return false; + } + Player.startClass(costMult, expMult, task); + workerScript.log( + "universityCourse", + `Started ${task} at ${universityName}`, + ); + return true; + }, + gymWorkout: function (gymName, stat) { + updateDynamicRam("gymWorkout", getRamCost("gymWorkout")); + checkSingularityAccess("gymWorkout", 1); + if (inMission) { + workerScript.log("gymWorkout", "You are in the middle of a mission."); + return; + } + if (Player.isWorking) { + var txt = Player.singularityStopWork(); + workerScript.log("gymWorkout", txt); + } + var costMult, expMult; + switch (gymName.toLowerCase()) { + case LocationName.AevumCrushFitnessGym.toLowerCase(): + if (Player.city != CityName.Aevum) { + workerScript.log( + "gymWorkout", + "You cannot workout at 'Crush Fitness' because you are not in 'Aevum'.", + ); return false; - }, - manualHack: function() { - updateDynamicRam("manualHack", getRamCost("manualHack")); - checkSingularityAccess("manualHack", 1); - const server = Player.getCurrentServer(); - return hack(server.hostname, true); - }, - installBackdoor: function() { - updateDynamicRam("installBackdoor", getRamCost("installBackdoor")); - checkSingularityAccess("installBackdoor", 1); - const server = Player.getCurrentServer(); - const installTime = calculateHackingTime(server, Player) / 4 * 1000; - - // No root access or skill level too low - const canHack = netscriptCanHack(server, Player); - if (!canHack.res) { - throw makeRuntimeErrorMsg('installBackdoor', canHack.msg); - } - - workerScript.log("installBackdoor", `Installing backdoor on '${server.hostname}' in ${convertTimeMsToTimeElapsedString(installTime, true)}`); - - return netscriptDelay(installTime, workerScript).then(function() { - if (workerScript.env.stopFlag) {return Promise.reject(workerScript);} - workerScript.log("installBackdoor", `Successfully installed backdoor on '${server.hostname}'`); - - server.backdoorInstalled = true; - return Promise.resolve(); - }); - }, - getStats: function() { - updateDynamicRam("getStats", getRamCost("getStats")); - checkSingularityAccess("getStats", 1); - workerScript.log("getStats", `getStats is deprecated, please use getPlayer`); - - return { - hacking: Player.hacking_skill, - strength: Player.strength, - defense: Player.defense, - dexterity: Player.dexterity, - agility: Player.agility, - charisma: Player.charisma, - intelligence: Player.intelligence, - } - }, - getCharacterInformation: function() { - updateDynamicRam("getCharacterInformation", getRamCost("getCharacterInformation")); - checkSingularityAccess("getCharacterInformation", 1); - workerScript.log("getCharacterInformation", `getCharacterInformation is deprecated, please use getPlayer`); - - return { - bitnode: Player.bitNodeN, - city: Player.city, - factions: Player.factions.slice(), - hp: Player.hp, - jobs: Object.keys(Player.jobs), - jobTitles: Object.values(Player.jobs), - maxHp: Player.max_hp, - mult: { - agility: Player.agility_mult, - agilityExp: Player.agility_exp_mult, - companyRep: Player.company_rep_mult, - crimeMoney: Player.crime_money_mult, - crimeSuccess: Player.crime_success_mult, - defense: Player.defense_mult, - defenseExp: Player.defense_exp_mult, - dexterity: Player.dexterity_mult, - dexterityExp: Player.dexterity_exp_mult, - factionRep: Player.faction_rep_mult, - hacking: Player.hacking_mult, - hackingExp: Player.hacking_exp_mult, - strength: Player.strength_mult, - strengthExp: Player.strength_exp_mult, - workMoney: Player.work_money_mult, - }, - timeWorked: Player.timeWorked, - tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), - workHackExpGain: Player.workHackExpGained, - workStrExpGain: Player.workStrExpGained, - workDefExpGain: Player.workDefExpGained, - workDexExpGain: Player.workDexExpGained, - workAgiExpGain: Player.workAgiExpGained, - workChaExpGain: Player.workChaExpGained, - workRepGain: Player.workRepGained, - workMoneyGain: Player.workMoneyGained, - hackingExp: Player.hacking_exp, - strengthExp: Player.strength_exp, - defenseExp: Player.defense_exp, - dexterityExp: Player.dexterity_exp, - agilityExp: Player.agility_exp, - charismaExp: Player.charisma_exp, - }; - }, - getPlayer: function() { - updateDynamicRam("getPlayer", getRamCost("getPlayer")); - - const data = { - hacking_skill: Player.hacking_skill, - hp: Player.hp, - max_hp: Player.max_hp, - strength: Player.strength, - defense: Player.defense, - dexterity: Player.dexterity, - agility: Player.agility, - charisma: Player.charisma, - intelligence: Player.intelligence, - hacking_chance_mult: Player.hacking_chance_mult, - hacking_speed_mult: Player.hacking_speed_mult, - hacking_money_mult: Player.hacking_money_mult, - hacking_grow_mult: Player.hacking_grow_mult, - hacking_exp: Player.hacking_exp, - strength_exp: Player.strength_exp, - defense_exp: Player.defense_exp, - dexterity_exp: Player.dexterity_exp, - agility_exp: Player.agility_exp, - charisma_exp: Player.charisma_exp, - hacking_mult: Player.hacking_mult, - strength_mult: Player.strength_mult, - defense_mult: Player.defense_mult, - dexterity_mult: Player.dexterity_mult, - agility_mult: Player.agility_mult, - charisma_mult: Player.charisma_mult, - hacking_exp_mult: Player.hacking_exp_mult, - strength_exp_mult: Player.strength_exp_mult, - defense_exp_mult: Player.defense_exp_mult, - dexterity_exp_mult: Player.dexterity_exp_mult, - agility_exp_mult: Player.agility_exp_mult, - charisma_exp_mult: Player.charisma_exp_mult, - company_rep_mult: Player.company_rep_mult, - faction_rep_mult: Player.faction_rep_mult, - numPeopleKilled: Player.numPeopleKilled, - money: Player.money.toNumber(), - city: Player.city, - location: Player.location, - crime_money_mult: Player.crime_money_mult, - crime_success_mult: Player.crime_success_mult, - isWorking: Player.isWorking, - workType: Player.workType, - currentWorkFactionName: Player.currentWorkFactionName, - currentWorkFactionDescription: Player.currentWorkFactionDescription, - workHackExpGainRate: Player.workHackExpGainRate, - workStrExpGainRate: Player.workStrExpGainRate, - workDefExpGainRate: Player.workDefExpGainRate, - workDexExpGainRate: Player.workDexExpGainRate, - workAgiExpGainRate: Player.workAgiExpGainRate, - workChaExpGainRate: Player.workChaExpGainRate, - workRepGainRate: Player.workRepGainRate, - workMoneyGainRate: Player.workMoneyGainRate, - workMoneyLossRate: Player.workMoneyLossRate, - workHackExpGained: Player.workHackExpGained, - workStrExpGained: Player.workStrExpGained, - workDefExpGained: Player.workDefExpGained, - workDexExpGained: Player.workDexExpGained, - workAgiExpGained: Player.workAgiExpGained, - workChaExpGained: Player.workChaExpGained, - workRepGained: Player.workRepGained, - workMoneyGained: Player.workMoneyGained, - createProgramName: Player.createProgramName, - createProgramReqLvl: Player.createProgramReqLvl, - className: Player.className, - crimeType: Player.crimeType, - work_money_mult: Player.work_money_mult, - hacknet_node_money_mult: Player.hacknet_node_money_mult, - hacknet_node_purchase_cost_mult: Player.hacknet_node_purchase_cost_mult, - hacknet_node_ram_cost_mult: Player.hacknet_node_ram_cost_mult, - hacknet_node_core_cost_mult: Player.hacknet_node_core_cost_mult, - hacknet_node_level_cost_mult: Player.hacknet_node_level_cost_mult, - hasWseAccount: Player.hasWseAccount, - hasTixApiAccess: Player.hasTixApiAccess, - has4SData: Player.has4SData, - has4SDataTixApi: Player.has4SDataTixApi, - bladeburner_max_stamina_mult: Player.bladeburner_max_stamina_mult, - bladeburner_stamina_gain_mult: Player.bladeburner_stamina_gain_mult, - bladeburner_analysis_mult: Player.bladeburner_analysis_mult, - bladeburner_success_chance_mult: Player.bladeburner_success_chance_mult, - bitNodeN: Player.bitNodeN, - totalPlaytime: Player.totalPlaytime, - playtimeSinceLastAug: Player.playtimeSinceLastAug, - playtimeSinceLastBitnode: Player.playtimeSinceLastBitnode, - jobs: {}, - factions: Player.factions.slice(), - tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), - }; - Object.assign(data.jobs, Player.jobs); - return data; - }, - hospitalize: function() { - updateDynamicRam("hospitalize", getRamCost("hospitalize")); - checkSingularityAccess("hospitalize", 1); - return Player.hospitalize(); - }, - isBusy: function() { - updateDynamicRam("isBusy", getRamCost("isBusy")); - checkSingularityAccess("isBusy", 1); - return Player.isWorking || inMission; - }, - stopAction: function() { - updateDynamicRam("stopAction", getRamCost("stopAction")); - checkSingularityAccess("stopAction", 1); - if (Player.isWorking) { - var txt = Player.singularityStopWork(); - workerScript.log("stopAction", txt); - return true; - } + } + Player.location = LocationName.AevumCrushFitnessGym; + costMult = 3; + expMult = 2; + break; + case LocationName.AevumSnapFitnessGym.toLowerCase(): + if (Player.city != CityName.Aevum) { + workerScript.log( + "gymWorkout", + "You cannot workout at 'Snap Fitness' because you are not in 'Aevum'.", + ); return false; - }, - upgradeHomeRam: function() { - updateDynamicRam("upgradeHomeRam", getRamCost("upgradeHomeRam")); - checkSingularityAccess("upgradeHomeRam", 2); - - // Check if we're at max RAM - const homeComputer = Player.getHomeComputer(); - if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) { - workerScript.log("upgradeHomeRam", `Your home computer is at max RAM.`); - return false; - } - - const cost = Player.getUpgradeHomeRamCost(); - if (Player.money.lt(cost)) { - workerScript.log("upgradeHomeRam", `You don't have enough money. Need ${numeralWrapper.formatMoney(cost)}`); - return false; - } - - homeComputer.maxRam *= 2; - Player.loseMoney(cost); - - Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); - workerScript.log("upgradeHomeRam", `Purchased additional RAM for home computer! It now has ${homeComputer.maxRam}GB of RAM.`); - return true; - }, - getUpgradeHomeRamCost: function() { - updateDynamicRam("getUpgradeHomeRamCost", getRamCost("getUpgradeHomeRamCost")); - checkSingularityAccess("getUpgradeHomeRamCost", 2); - - return Player.getUpgradeHomeRamCost(); - }, - workForCompany: function(companyName) { - updateDynamicRam("workForCompany", getRamCost("workForCompany")); - checkSingularityAccess("workForCompany", 2); - - // Sanitize input - if (companyName == null) { - companyName = Player.companyName; - } - - // Make sure its a valid company - if (companyName == null || companyName === "" || !(Companies[companyName] instanceof Company)) { - workerScript.log("workForCompany", `Invalid company: '${companyName}'`); - return false; - } - - // Make sure player is actually employed at the comapny - if (!Object.keys(Player.jobs).includes(companyName)) { - workerScript.log("workForCompany", `You do not have a job at '${companyName}'`); - return false; - } - - // Cant work while in a mission - if (inMission) { - workerScript.log("workForCompany", "You are in the middle of a mission."); - return false; - } - - // Check to make sure company position data is valid - const companyPositionName = Player.jobs[companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (companyPositionName === "" || !(companyPosition instanceof CompanyPosition)) { - workerScript.log("workForCompany", "You do not have a job"); - return false; - } - - if (Player.isWorking) { - var txt = Player.singularityStopWork(); - workerScript.log("workForCompany", txt); - } - - if (companyPosition.isPartTimeJob()) { - Player.startWorkPartTime(companyName); - } else { - Player.startWork(companyName); - } - workerScript.log("workForCompany", `Began working at '${Player.companyName}' as a '${companyPositionName}'`); - return true; - }, - applyToCompany: function(companyName, field) { - updateDynamicRam("applyToCompany", getRamCost("applyToCompany")); - checkSingularityAccess("applyToCompany", 2); - getCompany("applyToCompany", companyName); - - Player.location = companyName; - var res; - switch (field.toLowerCase()) { - case "software": - res = Player.applyForSoftwareJob(true); - break; - case "software consultant": - res = Player.applyForSoftwareConsultantJob(true); - break; - case "it": - res = Player.applyForItJob(true); - break; - case "security engineer": - res = Player.applyForSecurityEngineerJob(true); - break; - case "network engineer": - res = Player.applyForNetworkEngineerJob(true); - break; - case "business": - res = Player.applyForBusinessJob(true); - break; - case "business consultant": - res = Player.applyForBusinessConsultantJob(true); - break; - case "security": - res = Player.applyForSecurityJob(true); - break; - case "agent": - res = Player.applyForAgentJob(true); - break; - case "employee": - res = Player.applyForEmployeeJob(true); - break; - case "part-time employee": - res = Player.applyForPartTimeEmployeeJob(true); - break; - case "waiter": - res = Player.applyForWaiterJob(true); - break; - case "part-time waiter": - res = Player.applyForPartTimeWaiterJob(true); - break; - default: - workerScript.log("applyToCompany", `Invalid job: '${field}'.`); - return false; - } - // The Player object's applyForJob function can return string with special error messages - if (isString(res)) { - workerScript.log("applyToCompany", res); - return false; - } - if (res) { - workerScript.log("applyToCompany", `You were offered a new job at '${companyName}' as a '${Player.jobs[companyName]}'`); - } else { - workerScript.log("applyToCompany", `You failed to get a new job/promotion at '${companyName}' in the '${field}' field.`); - } - return res; - }, - getCompanyRep: function(companyName) { - updateDynamicRam("getCompanyRep", getRamCost("getCompanyRep")); - checkSingularityAccess("getCompanyRep", 2); - const company = getCompany("getCompanyRep", companyName); - return company.playerReputation; - }, - getCompanyFavor: function(companyName) { - updateDynamicRam("getCompanyFavor", getRamCost("getCompanyFavor")); - checkSingularityAccess("getCompanyFavor", 2); - const company = getCompany("getCompanyFavor", companyName); - return company.favor; - }, - getCompanyFavorGain: function(companyName) { - updateDynamicRam("getCompanyFavorGain", getRamCost("getCompanyFavorGain")); - checkSingularityAccess("getCompanyFavorGain", 2); - const company = getCompany("getCompanyFavorGain", companyName); - return company.getFavorGain()[0]; - }, - checkFactionInvitations: function() { - updateDynamicRam("checkFactionInvitations", getRamCost("checkFactionInvitations")); - checkSingularityAccess("checkFactionInvitations", 2); - // Make a copy of Player.factionInvitations - return Player.factionInvitations.slice(); - }, - joinFaction: function(name) { - updateDynamicRam("joinFaction", getRamCost("joinFaction")); - checkSingularityAccess("joinFaction", 2); - getFaction("joinFaction", name); - - if (!Player.factionInvitations.includes(name)) { - workerScript.log("joinFaction", `You have not been invited by faction '${name}'`); - return false; - } - const fac = Factions[name]; - joinFaction(fac); - - // Update Faction Invitation list to account for joined + banned factions - for (let i = 0; i < Player.factionInvitations.length; ++i) { - if (Player.factionInvitations[i] == name || Factions[Player.factionInvitations[i]].isBanned) { - Player.factionInvitations.splice(i, 1); - i--; - } - } - Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); - workerScript.log("joinFaction", `Joined the '${name}' faction.`); - return true; - }, - workForFaction: function(name, type) { - updateDynamicRam("workForFaction", getRamCost("workForFaction")); - checkSingularityAccess("workForFaction", 2); - getFaction("workForFaction", name); - - // if the player is in a gang and the target faction is any of the gang faction, fail - if(Player.inGang() && AllGangs[name] !== undefined) { - workerScript.log("workForFaction", `Faction '${name}' does not offer work at the moment.`); - return; - } - - if (inMission) { - workerScript.log("workForFaction", "You are in the middle of a mission."); - return; - } - - if (!Player.factions.includes(name)) { - workerScript.log("workForFaction", `You are not a member of '${name}'`); - return false; - } - - if (Player.isWorking) { - const txt = Player.singularityStopWork(); - workerScript.log("workForFaction", txt); - } - - var fac = Factions[name]; - // Arrays listing factions that allow each time of work - var hackAvailable = ["Illuminati", "Daedalus", "The Covenant", "ECorp", "MegaCorp", - "Bachman & Associates", "Blade Industries", "NWO", "Clarke Incorporated", - "OmniTek Incorporated", "Four Sigma", "KuaiGong International", - "Fulcrum Secret Technologies", "BitRunners", "The Black Hand", - "NiteSec", "Chongqing", "Sector-12", "New Tokyo", "Aevum", - "Ishima", "Volhaven", "Speakers for the Dead", "The Dark Army", - "The Syndicate", "Silhouette", "Netburners", "Tian Di Hui", "CyberSec"]; - var fdWkAvailable = ["Illuminati", "Daedalus", "The Covenant", "ECorp", "MegaCorp", - "Bachman & Associates", "Blade Industries", "NWO", "Clarke Incorporated", - "OmniTek Incorporated", "Four Sigma", "KuaiGong International", - "The Black Hand", "Chongqing", "Sector-12", "New Tokyo", "Aevum", - "Ishima", "Volhaven", "Speakers for the Dead", "The Dark Army", - "The Syndicate", "Silhouette", "Tetrads", "Slum Snakes"]; - var scWkAvailable = ["ECorp", "MegaCorp", - "Bachman & Associates", "Blade Industries", "NWO", "Clarke Incorporated", - "OmniTek Incorporated", "Four Sigma", "KuaiGong International", - "Fulcrum Secret Technologies", "Chongqing", "Sector-12", "New Tokyo", "Aevum", - "Ishima", "Volhaven", "Speakers for the Dead", - "The Syndicate", "Tetrads", "Slum Snakes", "Tian Di Hui"]; - - switch (type.toLowerCase()) { - case "hacking": - case "hacking contracts": - case "hackingcontracts": - if (!hackAvailable.includes(fac.name)) { - workerScript.log("workForFaction", `Faction '${fac.name}' do not need help with hacking contracts.`); - return false; - } - Player.startFactionHackWork(fac); - workerScript.log("workForFaction", `Started carrying out hacking contracts for '${fac.name}'`); - return true; - case "field": - case "fieldwork": - case "field work": - if (!fdWkAvailable.includes(fac.name)) { - workerScript.log("workForFaction", `Faction '${fac.name}' do not need help with field missions.`); - return false; - } - Player.startFactionFieldWork(fac); - workerScript.log("workForFaction", `Started carrying out field missions for '${fac.name}'`); - return true; - case "security": - case "securitywork": - case "security work": - if (!scWkAvailable.includes(fac.name)) { - workerScript.log("workForFaction", `Faction '${fac.name}' do not need help with security work.`); - return false; - } - Player.startFactionSecurityWork(fac); - workerScript.log("workForFaction", `Started carrying out security work for '${fac.name}'`); - return true; - default: - workerScript.log("workForFaction", `Invalid work type: '${type}`); - } - return true; - }, - getFactionRep: function(name) { - updateDynamicRam("getFactionRep", getRamCost("getFactionRep")); - checkSingularityAccess("getFactionRep", 2); - const faction = getFaction("getFactionRep", name); - return faction.playerReputation; - }, - getFactionFavor: function(name) { - updateDynamicRam("getFactionFavor", getRamCost("getFactionFavor")); - checkSingularityAccess("getFactionFavor", 2); - const faction = getFaction("getFactionFavor", name); - return faction.favor; - }, - getFactionFavorGain: function(name) { - updateDynamicRam("getFactionFavorGain", getRamCost("getFactionFavorGain")); - checkSingularityAccess("getFactionFavorGain", 2); - const faction = getFaction("getFactionFavorGain", name); - return faction.getFavorGain()[0]; - }, - donateToFaction: function(name, amt) { - updateDynamicRam("donateToFaction", getRamCost("donateToFaction")); - checkSingularityAccess("donateToFaction", 3); - const faction = getFaction("donateToFaction", name); - - if (typeof amt !== 'number' || amt <= 0) { - workerScript.log("donateToFaction", `Invalid donation amount: '${amt}'.`); - return false; - } - if (Player.money.lt(amt)) { - workerScript.log("donateToFaction", `You do not have enough money to donate ${numeralWrapper.formatMoney(amt)} to '${name}'`); - return false; - } - const repNeededToDonate = Math.round(CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction); - if (faction.favor < repNeededToDonate) { - workerScript.log("donateToFaction", `You do not have enough favor to donate to this faction. Have ${faction.favor}, need ${repNeededToDonate}`); - return false; - } - const repGain = amt / CONSTANTS.DonateMoneyToRepDivisor * Player.faction_rep_mult; - faction.playerReputation += repGain; - Player.loseMoney(amt); - workerScript.log("donateToFaction", `${numeralWrapper.formatMoney(amt)} donated to '${name}' for ${numeralWrapper.formatReputation(repGain)} reputation`); - return true; - }, - createProgram: function(name) { - updateDynamicRam("createProgram", getRamCost("createProgram")); - checkSingularityAccess("createProgram", 3); - - if (inMission) { - workerScript.log("createProgram", "You are in the middle of a mission."); - return; - } - if (Player.isWorking) { - var txt = Player.singularityStopWork(); - workerScript.log("createProgram", txt); - } - - name = name.toLowerCase(); - - let p = null; - for (const key in Programs) { - if(Programs[key].name.toLowerCase() == name) { - p = Programs[key]; - } - } - - if (p == null) { - workerScript.log("createProgram", `The specified program does not exist: '${name}`); - return false; - } - - if (Player.hasProgram(p.name)) { - workerScript.log("createProgram", `You already have the '${p.name}' program`); - return false; - } - - if (!p.create.req(Player)) { - workerScript.log("createProgram", `Hacking level is too low to create '${p.name}' (level ${p.create.level} req)`); - return false - } - - Player.startCreateProgramWork(p.name, p.create.time, p.create.level); - workerScript.log("createProgram", `Began creating program: '${name}'`); - return true; - }, - commitCrime: function(crimeRoughName) { - updateDynamicRam("commitCrime", getRamCost("commitCrime")); - checkSingularityAccess("commitCrime", 3); - if (inMission) { - workerScript.log("commitCrime", "You are in the middle of a mission."); - return; - } - if (Player.isWorking) { - const txt = Player.singularityStopWork(); - workerScript.log("commitCrime", txt); - } - - // Set Location to slums - Player.gotoLocation(LocationName.Slums); - - const crime = findCrime(crimeRoughName.toLowerCase()); - if(crime == null) { // couldn't find crime - throw makeRuntimeErrorMsg("commitCrime", `Invalid crime: '${crimeRoughName}'`); - } - workerScript.log("commitCrime", `Attempting to commit ${crime.name}...`); - return crime.commit(Player, 1, {workerscript: workerScript}); - }, - getCrimeChance: function(crimeRoughName) { - updateDynamicRam("getCrimeChance", getRamCost("getCrimeChance")); - checkSingularityAccess("getCrimeChance", 3); - - const crime = findCrime(crimeRoughName.toLowerCase()); - if(crime == null) { - throw makeRuntimeErrorMsg("getCrimeChance", `Invalid crime: ${crimeRoughName}`); - } - - return crime.successRate(Player); - }, - getCrimeStats: function(crimeRoughName) { - updateDynamicRam("getCrimeStats", getRamCost("getCrimeStats")); - checkSingularityAccess("getCrimeStats", 3); - - const crime = findCrime(crimeRoughName.toLowerCase()); - if(crime == null) { - throw makeRuntimeErrorMsg("getCrimeStats", `Invalid crime: ${crimeRoughName}`); - } - - return Object.assign({}, crime); - }, - getOwnedAugmentations: function(purchased=false) { - updateDynamicRam("getOwnedAugmentations", getRamCost("getOwnedAugmentations")); - checkSingularityAccess("getOwnedAugmentations", 3); - var res = []; - for (var i = 0; i < Player.augmentations.length; ++i) { - res.push(Player.augmentations[i].name); - } - if (purchased) { - for (var i = 0; i < Player.queuedAugmentations.length; ++i) { - res.push(Player.queuedAugmentations[i].name); - } - } - return res; - }, - getOwnedSourceFiles: function() { - updateDynamicRam("getOwnedSourceFiles", getRamCost("getOwnedSourceFiles")); - checkSingularityAccess("getOwnedSourceFiles", 3); - let res = []; - for (let i = 0; i < Player.sourceFiles.length; ++i) { - res.push({n: Player.sourceFiles[i].n, lvl: Player.sourceFiles[i].lvl}); - } - return res; - }, - getAugmentationsFromFaction: function(facname) { - updateDynamicRam("getAugmentationsFromFaction", getRamCost("getAugmentationsFromFaction")); - checkSingularityAccess("getAugmentationsFromFaction", 3); - const faction = getFaction("getAugmentationsFromFaction", facname); - - // If player has a gang with this faction, return all augmentations. - if (Player.hasGangWith(facname)) { - const res = []; - for (const augName in Augmentations) { - const aug = Augmentations[augName]; - if (!aug.isSpecial) { - res.push(augName); - } - } - - return res; - } - - return faction.augmentations.slice(); - }, - getAugmentationPrereq: function(name) { - updateDynamicRam("getAugmentationPrereq", getRamCost("getAugmentationPrereq")); - checkSingularityAccess("getAugmentationPrereq", 3); - const aug = getAugmentation("getAugmentationPrereq", name); - return aug.prereqs.slice(); - }, - getAugmentationCost: function(name) { - updateDynamicRam("getAugmentationCost", getRamCost("getAugmentationCost")); - checkSingularityAccess("getAugmentationCost", 3); - const aug = getAugmentation("getAugmentationCost", name); - return [aug.baseRepRequirement, aug.baseCost]; - }, - getAugmentationStats: function(name) { - updateDynamicRam("getAugmentationStats", getRamCost("getAugmentationStats")); - checkSingularityAccess("getAugmentationStats", 3); - const aug = getAugmentation("getAugmentationStats", name); - return Object.assign({}, aug.mults); - }, - purchaseAugmentation: function(faction, name) { - updateDynamicRam("purchaseAugmentation", getRamCost("purchaseAugmentation")); - checkSingularityAccess("purchaseAugmentation", 3); - const fac = getFaction("purchaseAugmentation", faction); - const aug = getAugmentation("purchaseAugmentation", name); - - let augs = []; - if (Player.hasGangWith(faction)) { - for (const augName in Augmentations) { - const tempAug = Augmentations[augName]; - if (!tempAug.isSpecial) { - augs.push(augName); - } - } - } else { - augs = fac.augmentations; - } - - if (!augs.includes(name)) { - workerScript.log("purchaseAugmentation", `Faction '${faction}' does not have the '${name}' augmentation.`); - return false; - } - - const isNeuroflux = (aug.name === AugmentationNames.NeuroFluxGovernor); - if (!isNeuroflux) { - for (let j = 0; j < Player.queuedAugmentations.length; ++j) { - if (Player.queuedAugmentations[j].name === aug.name) { - workerScript.log("purchaseAugmentation", `You already have the '${name}' augmentation.`); - return false; - } - } - for (let j = 0; j < Player.augmentations.length; ++j) { - if (Player.augmentations[j].name === aug.name) { - workerScript.log("purchaseAugmentation", `You already have the '${name}' augmentation.`); - return false; - } - } - } - - if (fac.playerReputation < aug.baseRepRequirement) { - workerScript.log("purchaseAugmentation", `You do not have enough reputation with '${fac.name}'.`); - return false; - } - - const res = purchaseAugmentation(aug, fac, true); - workerScript.log("purchaseAugmentation", res); - if (isString(res) && res.startsWith("You purchased")) { - Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); - return true; - } else { - return false; - } - }, - softReset: function(cbScript) { - updateDynamicRam("softReset", getRamCost("softReset")); - checkSingularityAccess("softReset", 3); - - workerScript.log("softReset", "Soft resetting. This will cause this script to be killed"); - setTimeoutRef(() => { - prestigeAugmentation(); - runAfterReset(cbScript); - }, 0); - - // Prevent workerScript from "finishing execution naturally" - workerScript.running = false; - killWorkerScript(workerScript); - }, - installAugmentations: function(cbScript) { - updateDynamicRam("installAugmentations", getRamCost("installAugmentations")); - checkSingularityAccess("installAugmentations", 3); - - if (Player.queuedAugmentations.length === 0) { - workerScript.log("installAugmentations", "You do not have any Augmentations to be installed."); - return false; - } - Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); - workerScript.log("installAugmentations", "Installing Augmentations. This will cause this script to be killed"); - setTimeoutRef(() => { - installAugmentations(); - runAfterReset(cbScript); - }, 0); - - workerScript.running = false; // Prevent workerScript from "finishing execution naturally" - killWorkerScript(workerScript); - }, - - // Gang API - gang: { - createGang: function(faction) { - updateDynamicRam("createGang", getRamCost("gang", "createGang")); - // this list is copied from Faction/ui/Root.tsx - const GangNames = [ - "Slum Snakes", - "Tetrads", - "The Syndicate", - "The Dark Army", - "Speakers for the Dead", - "NiteSec", - "The Black Hand", - ]; - if(!Player.canAccessGang() || !GangNames.includes(faction)) return false; - if (Player.inGang()) return false; - if(!Player.factions.includes(faction)) return false; - - const isHacking = (faction === "NiteSec" || faction === "The Black Hand"); - Player.startGang(faction, isHacking); - return true; - }, - inGang: function() { - updateDynamicRam("inGang", getRamCost("gang", "inGang")); - return Player.inGang(); - }, - getMemberNames: function() { - updateDynamicRam("getMemberNames", getRamCost("gang", "getMemberNames")); - checkGangApiAccess("getMemberNames"); - return Player.gang.members.map(member => member.name); - }, - getGangInformation: function() { - updateDynamicRam("getGangInformation", getRamCost("gang", "getGangInformation")); - checkGangApiAccess("getGangInformation"); - return { - faction: Player.gang.facName, - isHacking: Player.gang.isHackingGang, - moneyGainRate: Player.gang.moneyGainRate, - power: Player.gang.getPower(), - respect: Player.gang.respect, - respectGainRate: Player.gang.respectGainRate, - territory: Player.gang.getTerritory(), - territoryClashChance: Player.gang.territoryClashChance, - territoryWarfareEngaged: Player.gang.territoryWarfareEngaged, - wantedLevel: Player.gang.wanted, - wantedLevelGainRate: Player.gang.wantedGainRate, - }; - }, - getOtherGangInformation: function() { - updateDynamicRam("getOtherGangInformation", getRamCost("gang", "getOtherGangInformation")); - checkGangApiAccess("getOtherGangInformation"); - const cpy = {}; - for (const gang in AllGangs) { - cpy[gang] = Object.assign({}, AllGangs[gang]); - } - - return cpy; - }, - getMemberInformation: function(name) { - updateDynamicRam("getMemberInformation", getRamCost("gang", "getMemberInformation")); - checkGangApiAccess("getMemberInformation"); - const member = getGangMember("getMemberInformation", name); - return { - name: member.name, - task: member.task, - earnedRespect: member.earnedRespect, - hack: member.hack, - str: member.str, - def: member.def, - dex: member.dex, - agi: member.agi, - cha: member.cha, - - hack_exp: member.hack_exp, - str_exp: member.str_exp, - def_exp: member.def_exp, - dex_exp: member.dex_exp, - agi_exp: member.agi_exp, - cha_exp: member.cha_exp, - - hack_mult: member.hack_mult, - str_mult: member.str_mult, - def_mult: member.def_mult, - dex_mult: member.dex_mult, - agi_mult: member.agi_mult, - cha_mult: member.cha_mult, - - hack_asc_mult: member.calculateAscensionMult(member.hack_asc_points), - str_asc_mult: member.calculateAscensionMult(member.str_asc_points), - def_asc_mult: member.calculateAscensionMult(member.def_asc_points), - dex_asc_mult: member.calculateAscensionMult(member.dex_asc_points), - agi_asc_mult: member.calculateAscensionMult(member.agi_asc_points), - cha_asc_mult: member.calculateAscensionMult(member.cha_asc_points), - - hack_asc_points: member.hack_asc_points, - str_asc_points: member.str_asc_points, - def_asc_points: member.def_asc_points, - dex_asc_points: member.dex_asc_points, - agi_asc_points: member.agi_asc_points, - cha_asc_points: member.cha_asc_points, - - upgrades: member.upgrades.slice(), - augmentations: member.augmentations.slice(), - } - }, - canRecruitMember: function() { - updateDynamicRam("canRecruitMember", getRamCost("gang", "canRecruitMember")); - checkGangApiAccess("canRecruitMember"); - return Player.gang.canRecruitMember(); - }, - recruitMember: function(name) { - updateDynamicRam("recruitMember", getRamCost("gang", "recruitMember")); - checkGangApiAccess("recruitMember"); - const recruited = Player.gang.recruitMember(name); - if (recruited) { - workerScript.log("recruitMember", `Successfully recruited Gang Member '${name}'`); - } else { - workerScript.log("recruitMember", `Failed to recruit Gang Member '${name}'`); - } - - return recruited; - }, - getTaskNames: function() { - updateDynamicRam("getTaskNames", getRamCost("gang", "getTaskNames")); - checkGangApiAccess("getTaskNames"); - const tasks = Player.gang.getAllTaskNames(); - tasks.unshift("Unassigned"); - return tasks; - }, - setMemberTask: function(memberName, taskName) { - updateDynamicRam("setMemberTask", getRamCost("gang", "setMemberTask")); - checkGangApiAccess("setMemberTask"); - const member = getGangMember("setMemberTask", memberName); - const success = member.assignToTask(taskName); - if (success) { - workerScript.log("setMemberTask", `Successfully assigned Gang Member '${memberName}' to '${taskName}' task`); - } else { - workerScript.log("setMemberTask", `Failed to assign Gang Member '${memberName}' to '${taskName}' task. '${memberName}' is now Unassigned`); - } - - return success; - }, - getTaskStats: function(taskName) { - updateDynamicRam("getTaskStats", getRamCost("gang", "getTaskStats")); - checkGangApiAccess("getTaskStats"); - const task = getGangTask("getTaskStats", taskName); - const copy = Object.assign({}, task); - copy.territory = Object.assign({}, task.territory) - return copy; - }, - getEquipmentNames: function() { - updateDynamicRam("getEquipmentNames", getRamCost("gang", "getEquipmentNames")); - checkGangApiAccess("getEquipmentNames"); - return Object.keys(GangMemberUpgrades); - }, - getEquipmentCost: function(equipName) { - updateDynamicRam("getEquipmentCost", getRamCost("gang", "getEquipmentCost")); - checkGangApiAccess("getEquipmentCost"); - const upg = GangMemberUpgrades[equipName]; - if(upg === null) return Infinity; - return Player.gang.getUpgradeCost(upg); - }, - getEquipmentType: function(equipName) { - updateDynamicRam("getEquipmentType", getRamCost("gang", "getEquipmentType")); - checkGangApiAccess("getEquipmentType"); - const upg = GangMemberUpgrades[equipName]; - if (upg == null) return ""; - return upg.getType(); - }, - getEquipmentStats: function(equipName) { - updateDynamicRam("getEquipmentStats", getRamCost("gang", "getEquipmentStats")); - checkGangApiAccess("getEquipmentStats"); - const equipment = GangMemberUpgrades[equipName]; - if (!equipment) { - throw makeRuntimeErrorMsg("getEquipmentStats", `Invalid equipment: ${equipName}`); - } - return Object.assign({}, equipment.mults); - }, - purchaseEquipment: function(memberName, equipName) { - updateDynamicRam("purchaseEquipment", getRamCost("gang", "purchaseEquipment")); - checkGangApiAccess("purchaseEquipment"); - const member = getGangMember("purchaseEquipment", memberName); - const equipment = GangMemberUpgrades[equipName]; - if(!equipment) return false; - const res = member.buyUpgrade(equipment, Player, Player.gang); - if (res) { - workerScript.log("purchaseEquipment", `Purchased '${equipName}' for Gang member '${memberName}'`); - } else { - workerScript.log("purchaseEquipment", `Failed to purchase '${equipName}' for Gang member '${memberName}'`); - } - - return res; - }, - ascendMember: function(name) { - updateDynamicRam("ascendMember", getRamCost("gang", "ascendMember")); - checkGangApiAccess("ascendMember"); - const member = getGangMember("ascendMember", name); - if(!member.canAscend()) return; - return Player.gang.ascendMember(member, workerScript); - }, - setTerritoryWarfare: function(engage) { - updateDynamicRam("setTerritoryWarfare", getRamCost("gang", "setTerritoryWarfare")); - checkGangApiAccess("setTerritoryWarfare"); - if (engage) { - Player.gang.territoryWarfareEngaged = true; - workerScript.log("setTerritoryWarfare", "Engaging in Gang Territory Warfare"); - } else { - Player.gang.territoryWarfareEngaged = false; - workerScript.log("setTerritoryWarfare", "Disengaging in Gang Territory Warfare"); - } - }, - getChanceToWinClash: function(otherGang) { - updateDynamicRam("getChanceToWinClash", getRamCost("gang", "getChanceToWinClash")); - checkGangApiAccess("getChanceToWinClash"); - if (AllGangs[otherGang] == null) { - throw makeRuntimeErrorMsg(`gang.${getChanceToWinClash}`, `Invalid gang: ${otherGang}`); - } - - const playerPower = AllGangs[Player.gang.facName].power; - const otherPower = AllGangs[otherGang].power; - - return playerPower / (otherPower + playerPower); - }, - getBonusTime: function() { - updateDynamicRam("getBonusTime", getRamCost("gang", "getBonusTime")); - checkGangApiAccess("getBonusTime"); - return Math.round(Player.gang.storedCycles / 5); - }, - }, // end gang namespace - - // Bladeburner API - bladeburner: { - getContractNames: function() { - updateDynamicRam("getContractNames", getRamCost("bladeburner", "getContractNames")); - checkBladeburnerAccess("getContractNames"); - return Player.bladeburner.getContractNamesNetscriptFn(); - }, - getOperationNames: function() { - updateDynamicRam("getOperationNames", getRamCost("bladeburner", "getOperationNames")); - checkBladeburnerAccess("getOperationNames"); - return Player.bladeburner.getOperationNamesNetscriptFn(); - }, - getBlackOpNames: function() { - updateDynamicRam("getBlackOpNames", getRamCost("bladeburner", "getBlackOpNames")); - checkBladeburnerAccess("getBlackOpNames"); - return Player.bladeburner.getBlackOpNamesNetscriptFn(); - }, - getBlackOpRank: function(name="") { - updateDynamicRam("getBlackOpRank", getRamCost("bladeburner", "getBlackOpRank")); - checkBladeburnerAccess("getBlackOpRank"); - const action = getBladeburnerActionObject("getBlackOpRank", "blackops", name); - return action.reqdRank; - }, - getGeneralActionNames: function() { - updateDynamicRam("getGeneralActionNames", getRamCost("bladeburner", "getGeneralActionNames")); - checkBladeburnerAccess("getGeneralActionNames"); - return Player.bladeburner.getGeneralActionNamesNetscriptFn(); - }, - getSkillNames: function() { - updateDynamicRam("getSkillNames", getRamCost("bladeburner", "getSkillNames")); - checkBladeburnerAccess("getSkillNames"); - return Player.bladeburner.getSkillNamesNetscriptFn(); - }, - startAction: function(type="", name="") { - updateDynamicRam("startAction", getRamCost("bladeburner", "startAction")); - checkBladeburnerAccess("startAction"); - try { - return Player.bladeburner.startActionNetscriptFn(Player, type, name, workerScript); - } catch(e) { - throw makeRuntimeErrorMsg("bladeburner.startAction", e); - } - }, - stopBladeburnerAction: function() { - updateDynamicRam("stopBladeburnerAction", getRamCost("bladeburner", "stopBladeburnerAction")); - checkBladeburnerAccess("stopBladeburnerAction"); - return Player.bladeburner.resetAction(); - }, - getCurrentAction: function() { - updateDynamicRam("getCurrentAction", getRamCost("bladeburner", "getCurrentAction")); - checkBladeburnerAccess("getCurrentAction"); - return Player.bladeburner.getTypeAndNameFromActionId(Player.bladeburner.action); - }, - getActionTime: function(type="", name="") { - updateDynamicRam("getActionTime", getRamCost("bladeburner", "getActionTime")); - checkBladeburnerAccess("getActionTime"); - try { - return Player.bladeburner.getActionTimeNetscriptFn(Player, type, name, workerScript); - } catch(e) { - throw makeRuntimeErrorMsg("bladeburner.getActionTime", e); - } - }, - getActionEstimatedSuccessChance: function(type="", name="") { - updateDynamicRam("getActionEstimatedSuccessChance", getRamCost("bladeburner", "getActionEstimatedSuccessChance")); - checkBladeburnerAccess("getActionEstimatedSuccessChance"); - try { - return Player.bladeburner.getActionEstimatedSuccessChanceNetscriptFn(Player, type, name, workerScript); - } catch(e) { - throw makeRuntimeErrorMsg("bladeburner.getActionEstimatedSuccessChance", e); - } - }, - getActionRepGain: function(type="", name="", level) { - updateDynamicRam("getActionRepGain", getRamCost("bladeburner", "getActionRepGain")); - checkBladeburnerAccess("getActionRepGain"); - const action = getBladeburnerActionObject("getActionRepGain", type, name); - let rewardMultiplier; - if (level == null || isNaN(level)) { - rewardMultiplier = Math.pow(action.rewardFac, action.level - 1); - } else { - rewardMultiplier = Math.pow(action.rewardFac, level - 1); - } - - return action.rankGain * rewardMultiplier * BitNodeMultipliers.BladeburnerRank; - }, - getActionCountRemaining: function(type="", name="") { - updateDynamicRam("getActionCountRemaining", getRamCost("bladeburner", "getActionCountRemaining")); - checkBladeburnerAccess("getActionCountRemaining"); - try { - return Player.bladeburner.getActionCountRemainingNetscriptFn(type, name, workerScript); - } catch(e) { - throw makeRuntimeErrorMsg("bladeburner.getActionCountRemaining", e); - } - }, - getActionMaxLevel: function(type="", name="") { - updateDynamicRam("getActionMaxLevel", getRamCost("bladeburner", "getActionMaxLevel")); - checkBladeburnerAccess("getActionMaxLevel"); - const action = getBladeburnerActionObject("getActionMaxLevel", type, name); - return action.maxLevel; - }, - getActionCurrentLevel: function(type="", name="") { - updateDynamicRam("getActionCurrentLevel", getRamCost("bladeburner", "getActionCurrentLevel")); - checkBladeburnerAccess("getActionCurrentLevel"); - const action = getBladeburnerActionObject("getActionCurrentLevel", type, name); - return action.level; - }, - getActionAutolevel: function(type="", name="") { - updateDynamicRam("getActionAutolevel", getRamCost("bladeburner", "getActionAutolevel")); - checkBladeburnerAccess("getActionAutolevel"); - const action = getBladeburnerActionObject("getActionCurrentLevel", type, name); - return action.autoLevel; - }, - setActionAutolevel: function(type="", name="", autoLevel=true) { - updateDynamicRam("setActionAutolevel", getRamCost("bladeburner", "setActionAutolevel")); - checkBladeburnerAccess("setActionAutolevel"); - const action = getBladeburnerActionObject("setActionAutolevel", type, name); - action.autoLevel = autoLevel; - }, - setActionLevel: function(type="", name="", level=1) { - updateDynamicRam("setActionLevel", getRamCost("bladeburner", "setActionLevel")); - checkBladeburnerAccess("setActionLevel"); - const action = getBladeburnerActionObject("setActionLevel", type, name); - if(level < 1 || level > action.maxLevel) { - throw makeRuntimeErrorMsg("bladeburner.setActionLevel", `Level must be between 1 and ${action.maxLevel}, is ${level}`) - } - action.level = level; - }, - getRank: function() { - updateDynamicRam("getRank", getRamCost("bladeburner", "getRank")); - checkBladeburnerAccess("getRank"); - return Player.bladeburner.rank; - }, - getSkillPoints: function() { - updateDynamicRam("getSkillPoints", getRamCost("bladeburner", "getSkillPoints")); - checkBladeburnerAccess("getSkillPoints"); - return Player.bladeburner.skillPoints; - }, - getSkillLevel: function(skillName="") { - updateDynamicRam("getSkillLevel", getRamCost("bladeburner", "getSkillLevel")); - checkBladeburnerAccess("getSkillLevel"); - try { - return Player.bladeburner.getSkillLevelNetscriptFn(skillName, workerScript); - } catch(e) { - throw makeRuntimeErrorMsg("bladeburner.getSkillLevel", e); - } - }, - getSkillUpgradeCost: function(skillName="") { - updateDynamicRam("getSkillUpgradeCost", getRamCost("bladeburner", "getSkillUpgradeCost")); - checkBladeburnerAccess("getSkillUpgradeCost"); - try { - return Player.bladeburner.getSkillUpgradeCostNetscriptFn(skillName, workerScript); - } catch(e) { - throw makeRuntimeErrorMsg("bladeburner.getSkillUpgradeCost", e); - } - }, - upgradeSkill: function(skillName) { - updateDynamicRam("upgradeSkill", getRamCost("bladeburner", "upgradeSkill")); - checkBladeburnerAccess("upgradeSkill"); - try { - return Player.bladeburner.upgradeSkillNetscriptFn(skillName, workerScript); - } catch(e) { - throw makeRuntimeErrorMsg("bladeburner.upgradeSkill", e); - } - }, - getTeamSize: function(type="", name="") { - updateDynamicRam("getTeamSize", getRamCost("bladeburner", "getTeamSize")); - checkBladeburnerAccess("getTeamSize"); - try { - return Player.bladeburner.getTeamSizeNetscriptFn(type, name, workerScript); - } catch(e) { - throw makeRuntimeErrorMsg("bladeburner.getTeamSize", e); - } - }, - setTeamSize: function(type="", name="", size) { - updateDynamicRam("setTeamSize",getRamCost("bladeburner", "setTeamSize")); - checkBladeburnerAccess("setTeamSize"); - try { - return Player.bladeburner.setTeamSizeNetscriptFn(type, name, size, workerScript); - } catch(e) { - throw makeRuntimeErrorMsg("bladeburner.setTeamSize", e); - } - }, - getCityEstimatedPopulation: function(cityName) { - updateDynamicRam("getCityEstimatedPopulation", getRamCost("bladeburner", "getCityEstimatedPopulation")); - checkBladeburnerAccess("getCityEstimatedPopulation"); - checkBladeburnerCity("getCityEstimatedPopulation", cityName); - return Player.bladeburner.cities[cityName].popEst; - }, - getCityEstimatedCommunities: function(cityName) { - updateDynamicRam("getCityEstimatedCommunities", getRamCost("bladeburner", "getCityEstimatedCommunities")); - checkBladeburnerAccess("getCityEstimatedCommunities"); - checkBladeburnerCity("getCityEstimatedCommunities", cityName); - return Player.bladeburner.cities[cityName].commsEst; - }, - getCityChaos: function(cityName) { - updateDynamicRam("getCityChaos", getRamCost("bladeburner", "getCityChaos")); - checkBladeburnerAccess("getCityChaos"); - checkBladeburnerCity("getCityChaos", cityName); - return Player.bladeburner.cities[cityName].chaos; - }, - getCity: function() { - updateDynamicRam("getCity", getRamCost("bladeburner", "getCity")); - checkBladeburnerAccess("getCityChaos"); - return Player.bladeburner.city; - }, - switchCity: function(cityName) { - updateDynamicRam("switchCity", getRamCost("bladeburner", "switchCity")); - checkBladeburnerAccess("switchCity"); - checkBladeburnerCity("switchCity", cityName); - return Player.bladeburner.city = cityName; - }, - getStamina: function() { - updateDynamicRam("getStamina", getRamCost("bladeburner", "getStamina")); - checkBladeburnerAccess("getStamina"); - return [Player.bladeburner.stamina, Player.bladeburner.maxStamina]; - }, - joinBladeburnerFaction: function() { - updateDynamicRam("joinBladeburnerFaction", getRamCost("bladeburner", "joinBladeburnerFaction")); - checkBladeburnerAccess("joinBladeburnerFaction", true); - return Player.bladeburner.joinBladeburnerFactionNetscriptFn(workerScript); - }, - joinBladeburnerDivision: function() { - updateDynamicRam("joinBladeburnerDivision", getRamCost("bladeburner", "joinBladeburnerDivision")); - checkBladeburnerAccess("joinBladeburnerDivision", true); - if ((Player.bitNodeN === 7 || SourceFileFlags[7] > 0)) { - if (Player.bitNodeN === 8) { return false; } - if (Player.bladeburner instanceof Bladeburner) { - return true; // Already member - } else if (Player.strength >= 100 && Player.defense >= 100 && - Player.dexterity >= 100 && Player.agility >= 100) { - Player.bladeburner = new Bladeburner(Player); - workerScript.log("joinBladeburnerDivision", "You have been accepted into the Bladeburner division"); - - const worldHeader = document.getElementById("world-menu-header"); - if (worldHeader instanceof HTMLElement) { - worldHeader.click(); worldHeader.click(); - } - - return true; - } else { - workerScript.log("joinBladeburnerDivision", "You do not meet the requirements for joining the Bladeburner division"); - return false; - } - } - }, - getBonusTime: function() { - updateDynamicRam("getBonusTime", getRamCost("bladeburner", "getBonusTime")); - checkBladeburnerAccess("getBonusTime"); - return Math.round(Player.bladeburner.storedCycles / 5); - }, - }, // End Bladeburner - - // corporation: { - // expandIndustry: function(industryName, divisionName) { - // NewIndustry(Player.corporation, industryName, divisionName); - // }, - // expandCity: function(divisionName, cityName) { - // const division = getDivision(divisionName); - // NewCity(Player.corporation, division, cityName); - // }, - // unlockUpgrade: function(upgradeName) { - // const upgrade = Object.values(CorporationUnlockUpgrades). - // find(upgrade => upgrade[2] === upgradeName); - // if(upgrade === undefined) throw new Error("No upgrade named '${upgradeName}'") - // UnlockUpgrade(Player.corporation, upgrade); - // }, - // levelUpgrade: function(upgradeName) { - // const upgrade = Object.values(CorporationUpgrades). - // find(upgrade => upgrade[4] === upgradeName); - // if(upgrade === undefined) throw new Error("No upgrade named '${upgradeName}'") - // LevelUpgrade(Player.corporation, upgrade); - // }, - // issueDividends: function(percent) { - // IssueDividends(Player.corporation, percent); - // }, - // sellMaterial: function(divisionName, cityName, materialName, amt, price) { - // const material = getMaterial(divisionName, cityName, materialName); - // SellMaterial(material, amt, price); - // }, - // sellProduct: function(divisionName, cityName, productName, amt, price, all) { - // const product = getProduct(divisionName, productName); - // SellProduct(product, cityName, amt, price, all); - // }, - // setSmartSupply: function(divisionName, cityName, enabled) { - // const warehouse = getWarehouse(divisionName, cityName); - // SetSmartSupply(warehouse, enabled); - // }, - // BuyMaterial: function(divisionName, cityName, materialName, amt) { - - // }, - // }, // End Corporation API - - // Coding Contract API - codingcontract: { - attempt: function(answer, fn, ip=workerScript.serverIp, { returnReward } = {}) { - updateDynamicRam("attempt", getRamCost("codingcontract", "attempt")); - const contract = getCodingContract("attempt", ip, fn); - - // Convert answer to string. If the answer is a 2D array, then we have to - // manually add brackets for the inner arrays - if (is2DArray(answer)) { - let answerComponents = []; - for (let i = 0; i < answer.length; ++i) { - answerComponents.push(["[", answer[i].toString(), "]"].join("")); - } - - answer = answerComponents.join(","); - } else { - answer = String(answer); - } - - const serv = safeGetServer(ip, "codingcontract.attempt"); - if (contract.isSolution(answer)) { - const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty()); - workerScript.log("attempt", `Successfully completed Coding Contract '${fn}'. Reward: ${reward}`); - serv.removeContract(fn); - return returnReward ? reward : true; - } else { - ++contract.tries; - if (contract.tries >= contract.getMaxNumTries()) { - workerScript.log("attempt", `Coding Contract attempt '${fn}' failed. Contract is now self-destructing`); - serv.removeContract(fn); - } else { - workerScript.log("attempt", `Coding Contract attempt '${fn}' failed. ${contract.getMaxNumTries() - contract.tries} attempts remaining.`); - } - - return returnReward ? "" : false; - } - }, - getContractType: function(fn, ip=workerScript.serverIp) { - updateDynamicRam("getContractType", getRamCost("codingcontract", "getContractType")); - const contract = getCodingContract("getContractType", ip, fn); - return contract.getType(); - }, - getData: function(fn, ip=workerScript.serverIp) { - updateDynamicRam("getData", getRamCost("codingcontract", "getData")); - const contract = getCodingContract("getData", ip, fn); - const data = contract.getData(); - if (data.constructor === Array) { - // For two dimensional arrays, we have to copy the internal arrays using - // slice() as well. As of right now, no contract has arrays that have - // more than two dimensions - const copy = data.slice(); - for (let i = 0; i < copy.length; ++i) { - if (data[i].constructor === Array) { - copy[i] = data[i].slice(); - } - } - - return copy; - } else { - return data; - } - }, - getDescription: function(fn, ip=workerScript.serverIp) { - updateDynamicRam("getDescription", getRamCost("codingcontract", "getDescription")); - const contract = getCodingContract("getDescription", ip, fn); - return contract.getDescription(); - }, - getNumTriesRemaining: function(fn, ip=workerScript.serverIp) { - updateDynamicRam("getNumTriesRemaining", getRamCost("codingcontract", "getNumTriesRemaining")); - const contract = getCodingContract("getNumTriesRemaining", ip, fn); - return contract.getMaxNumTries() - contract.tries; - }, - }, // End coding contracts - - // Duplicate Sleeve API - sleeve: { - getNumSleeves: function() { - updateDynamicRam("getNumSleeves", getRamCost("sleeve", "getNumSleeves")); - checkSleeveAPIAccess("getNumSleeves"); - return Player.sleeves.length; - }, - setToShockRecovery: function(sleeveNumber=0) { - updateDynamicRam("setToShockRecovery", getRamCost("sleeve", "setToShockRecovery")); - checkSleeveAPIAccess("setToShockRecovery"); - checkSleeveNumber("setToShockRecovery", sleeveNumber); - return Player.sleeves[sleeveNumber].shockRecovery(Player); - }, - setToSynchronize: function(sleeveNumber=0) { - updateDynamicRam("setToSynchronize", getRamCost("sleeve", "setToSynchronize")); - checkSleeveAPIAccess("setToSynchronize"); - checkSleeveNumber("setToSynchronize", sleeveNumber); - return Player.sleeves[sleeveNumber].synchronize(Player); - }, - setToCommitCrime: function(sleeveNumber=0, crimeName="") { - updateDynamicRam("setToCommitCrime", getRamCost("sleeve", "setToCommitCrime")); - checkSleeveAPIAccess("setToCommitCrime"); - checkSleeveNumber("setToCommitCrime", sleeveNumber); - return Player.sleeves[sleeveNumber].commitCrime(Player, crimeName); - }, - setToUniversityCourse: function(sleeveNumber=0, universityName="", className="") { - updateDynamicRam("setToUniversityCourse", getRamCost("sleeve", "setToUniversityCourse")); - checkSleeveAPIAccess("setToUniversityCourse"); - checkSleeveNumber("setToUniversityCourse", sleeveNumber); - return Player.sleeves[sleeveNumber].takeUniversityCourse(Player, universityName, className); - }, - travel: function(sleeveNumber=0, cityName="") { - updateDynamicRam("travel", getRamCost("sleeve", "travel")); - checkSleeveAPIAccess("travel"); - checkSleeveNumber("travel", sleeveNumber); - return Player.sleeves[sleeveNumber].travel(Player, cityName); - }, - setToCompanyWork: function(sleeveNumber=0, companyName="") { - updateDynamicRam("setToCompanyWork", getRamCost("sleeve", "setToCompanyWork")); - checkSleeveAPIAccess("setToCompanyWork"); - checkSleeveNumber("setToCompanyWork", sleeveNumber); - - // Cannot work at the same company that another sleeve is working at - for (let i = 0; i < Player.sleeves.length; ++i) { - if (i === sleeveNumber) { continue; } - const other = Player.sleeves[i]; - if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) { - throw makeRuntimeErrorMsg("sleeve.setToFactionWork", `Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`) - } - } - - return Player.sleeves[sleeveNumber].workForCompany(Player, companyName); - }, - setToFactionWork: function(sleeveNumber=0, factionName="", workType="") { - updateDynamicRam("setToFactionWork", getRamCost("sleeve", "setToFactionWork")); - checkSleeveAPIAccess("setToFactionWork"); - checkSleeveNumber("setToFactionWork", sleeveNumber); - - // Cannot work at the same faction that another sleeve is working at - for (let i = 0; i < Player.sleeves.length; ++i) { - if (i === sleeveNumber) { continue; } - const other = Player.sleeves[i]; - if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) { - throw makeRuntimeErrorMsg("sleeve.setToFactionWork", `Sleeve ${sleeveNumber} cannot work for faction ${factionName} because Sleeve ${i} is already working for them.`) - } - } - - return Player.sleeves[sleeveNumber].workForFaction(Player, factionName, workType); - }, - setToGymWorkout: function(sleeveNumber=0, gymName="", stat="") { - updateDynamicRam("setToGymWorkout", getRamCost("sleeve", "setToGymWorkout")); - checkSleeveAPIAccess("setToGymWorkout"); - checkSleeveNumber("setToGymWorkout", sleeveNumber); - - return Player.sleeves[sleeveNumber].workoutAtGym(Player, gymName, stat); - }, - getSleeveStats: function(sleeveNumber=0) { - updateDynamicRam("getSleeveStats", getRamCost("sleeve", "getSleeveStats")); - checkSleeveAPIAccess("getSleeveStats"); - checkSleeveNumber("getSleeveStats", sleeveNumber); - - const sl = Player.sleeves[sleeveNumber]; - return { - shock: 100 - sl.shock, - sync: sl.sync, - hacking_skill: sl.hacking_skill, - strength: sl.strength, - defense: sl.defense, - dexterity: sl.dexterity, - agility: sl.agility, - charisma: sl.charisma, - }; - }, - getTask: function(sleeveNumber=0) { - updateDynamicRam("getTask", getRamCost("sleeve", "getTask")); - checkSleeveAPIAccess("getTask"); - checkSleeveNumber("getTask", sleeveNumber); - - const sl = Player.sleeves[sleeveNumber]; - return { - task: SleeveTaskType[sl.currentTask], - crime: sl.crimeType, - location: sl.currentTaskLocation, - gymStatType: sl.gymStatType, - factionWorkType: FactionWorkType[sl.factionWorkType], - }; - }, - getInformation: function(sleeveNumber=0) { - updateDynamicRam("getInformation", getRamCost("sleeve", "getInformation")); - checkSleeveAPIAccess("getInformation"); - checkSleeveNumber("getInformation", sleeveNumber); - - const sl = Player.sleeves[sleeveNumber]; - return { - city: sl.city, - hp: sl.hp, - jobs: Object.keys(Player.jobs), // technically sleeves have the same jobs as the player. - jobTitle: Object.values(Player.jobs), - maxHp: sl.max_hp, - tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), // There's no reason not to give that infomation here as well. Worst case scenario it isn't used. - - mult: { - agility: sl.agility_mult, - agilityExp: sl.agility_exp_mult, - companyRep: sl.company_rep_mult, - crimeMoney: sl.crime_money_mult, - crimeSuccess: sl.crime_success_mult, - defense: sl.defense_mult, - defenseExp: sl.defense_exp_mult, - dexterity: sl.dexterity_mult, - dexterityExp: sl.dexterity_exp_mult, - factionRep: sl.faction_rep_mult, - hacking: sl.hacking_mult, - hackingExp: sl.hacking_exp_mult, - strength: sl.strength_mult, - strengthExp: sl.strength_exp_mult, - workMoney: sl.work_money_mult, - }, - - timeWorked: sl.currentTaskTime, - earningsForSleeves : { - workHackExpGain: sl.earningsForSleeves.hack, - workStrExpGain: sl.earningsForSleeves.str, - workDefExpGain: sl.earningsForSleeves.def, - workDexExpGain: sl.earningsForSleeves.dex, - workAgiExpGain: sl.earningsForSleeves.agi, - workChaExpGain: sl.earningsForSleeves.cha, - workMoneyGain: sl.earningsForSleeves.money, - }, - earningsForPlayer : { - workHackExpGain: sl.earningsForPlayer.hack, - workStrExpGain: sl.earningsForPlayer.str, - workDefExpGain: sl.earningsForPlayer.def, - workDexExpGain: sl.earningsForPlayer.dex, - workAgiExpGain: sl.earningsForPlayer.agi, - workChaExpGain: sl.earningsForPlayer.cha, - workMoneyGain: sl.earningsForPlayer.money, - }, - earningsForTask : { - workHackExpGain: sl.earningsForTask.hack, - workStrExpGain: sl.earningsForTask.str, - workDefExpGain: sl.earningsForTask.def, - workDexExpGain: sl.earningsForTask.dex, - workAgiExpGain: sl.earningsForTask.agi, - workChaExpGain: sl.earningsForTask.cha, - workMoneyGain: sl.earningsForTask.money, - }, - workRepGain: sl.getRepGain(Player), - } - }, - getSleeveAugmentations: function(sleeveNumber=0) { - updateDynamicRam("getSleeveAugmentations", getRamCost("sleeve", "getSleeveAugmentations")); - checkSleeveAPIAccess("getSleeveAugmentations"); - checkSleeveNumber("getSleeveAugmentations", sleeveNumber); - - const augs = []; - for (let i = 0; i < Player.sleeves[sleeveNumber].augmentations.length; i++) { - augs.push(Player.sleeves[sleeveNumber].augmentations[i].name); - } - return augs; - }, - getSleevePurchasableAugs: function(sleeveNumber=0) { - updateDynamicRam("getSleevePurchasableAugs", getRamCost("sleeve", "getSleevePurchasableAugs")); - checkSleeveAPIAccess("getSleevePurchasableAugs"); - checkSleeveNumber("getSleevePurchasableAugs", sleeveNumber); - - const purchasableAugs = findSleevePurchasableAugs(Player.sleeves[sleeveNumber], Player); - const augs = []; - for (let i = 0; i < purchasableAugs.length; i++) { - const aug = purchasableAugs[i]; - augs.push({ - name: aug.name, - cost: aug.startingCost, - }); - } - - return augs; - }, - purchaseSleeveAug: function(sleeveNumber=0, augName="") { - updateDynamicRam("purchaseSleeveAug", getRamCost("sleeve", "purchaseSleeveAug")); - checkSleeveAPIAccess("purchaseSleeveAug"); - checkSleeveNumber("purchaseSleeveAug", sleeveNumber); - - const aug = Augmentations[augName]; - if (!aug) { - throw makeRuntimeErrorMsg("sleeve.purchaseSleeveAug", `Invalid aug: ${augName}`) - } - - return Player.sleeves[sleeveNumber].tryBuyAugmentation(Player, aug); - }, - }, // End sleeve - formulas: { - basic: { - calculateSkill: function(exp, mult = 1) { - checkFormulasAccess("basic.calculateSkill", 5); - return calculateSkill(exp, mult); - }, - calculateExp: function(skill, mult = 1) { - checkFormulasAccess("basic.calculateExp", 5); - return calculateExp(skill, mult); - }, - hackChance: function(server, player) { - checkFormulasAccess("basic.hackChance", 5); - return calculateHackingChance(server, player); - }, - hackExp: function(server, player) { - checkFormulasAccess("basic.hackExp", 5); - return calculateHackingExpGain(server, player); - }, - hackPercent: function(server, player) { - checkFormulasAccess("basic.hackPercent", 5); - return calculatePercentMoneyHacked(server, player); - }, - growPercent: function(server, threads, player, cores = 1) { - checkFormulasAccess("basic.growPercent", 5); - return calculateServerGrowth(server, threads, player, cores); - }, - hackTime: function(server, player) { - checkFormulasAccess("basic.hackTime", 5); - return calculateHackingTime(server, player); - }, - growTime: function(server, player) { - checkFormulasAccess("basic.growTime", 5); - return calculateGrowTime(server, player); - }, - weakenTime: function(server, player) { - checkFormulasAccess("basic.weakenTime", 5); - return calculateWeakenTime(server, player); - }, - }, - hacknetNodes: { - moneyGainRate: function(level, ram, cores, mult=1) { - checkFormulasAccess("hacknetNodes.moneyGainRate", 5); - return calculateMoneyGainRate(level, ram, cores, mult); - }, - levelUpgradeCost: function(startingLevel, extraLevels=1, costMult=1) { - checkFormulasAccess("hacknetNodes.levelUpgradeCost", 5); - return calculateLevelUpgradeCost(startingLevel, extraLevels, costMult); - }, - ramUpgradeCost: function(startingRam, extraLevels=1, costMult=1) { - checkFormulasAccess("hacknetNodes.ramUpgradeCost", 5); - return calculateRamUpgradeCost(startingRam, extraLevels, costMult); - }, - coreUpgradeCost: function(startingCore, extraCores=1, costMult=1) { - checkFormulasAccess("hacknetNodes.coreUpgradeCost", 5); - return calculateCoreUpgradeCost(startingCore, extraCores, costMult); - }, - hacknetNodeCost: function(n, mult) { - checkFormulasAccess("hacknetNodes.hacknetNodeCost", 5); - return calculateNodeCost(n, mult); - }, - constants: function() { - checkFormulasAccess("hacknetNodes.constants", 5); - return Object.assign({}, HacknetNodeConstants); - }, - }, - hacknetServers: { - hashGainRate: function(level, ramUsed, maxRam, cores, mult=1) { - checkFormulasAccess("hacknetServers.hashGainRate", 9); - return HScalculateHashGainRate(level, ramUsed, maxRam, cores, mult); - }, - levelUpgradeCost: function(startingLevel, extraLevels=1, costMult=1) { - checkFormulasAccess("hacknetServers.levelUpgradeCost", 9); - return HScalculateLevelUpgradeCost(startingLevel, extraLevels, costMult); - }, - ramUpgradeCost: function(startingRam, extraLevels=1, costMult=1) { - checkFormulasAccess("hacknetServers.ramUpgradeCost", 9); - return HScalculateRamUpgradeCost(startingRam, extraLevels, costMult); - }, - coreUpgradeCost: function(startingCore, extraCores=1, costMult=1) { - checkFormulasAccess("hacknetServers.coreUpgradeCost", 9); - return HScalculateCoreUpgradeCost(startingCore, extraCores, costMult); - }, - cacheUpgradeCost: function(startingCache, extraCache=1, costMult=1) { - checkFormulasAccess("hacknetServers.cacheUpgradeCost", 9); - return HScalculateCacheUpgradeCost(startingCache, extraCache, costMult); - }, - hashUpgradeCost: function(upgName, level) { - checkFormulasAccess("hacknetServers.hashUpgradeCost", 9); - const upg = Player.hashManager.getUpgrade(upgName); - if(!upg) { - throw makeRuntimeErrorMsg("formulas.hacknetServers.calculateHashUpgradeCost", `Invalid Hash Upgrade: ${upgName}`); - } - return upg.getCost(level); - }, - hacknetServerCost: function(n, mult) { - checkFormulasAccess("hacknetServers.hacknetServerCost", 9); - return HScalculateServerCost(n, mult); - }, - constants: function() { - checkFormulasAccess("hacknetServers.constants", 9); - return Object.assign({}, HacknetServerConstants); - }, - }, - }, // end formulas - heart: { - // Easter egg function - break: function() { - return Player.karma; - }, - }, - exploit: function() { - Player.giveExploit(Exploit.UndocumentedFunctionCall); - }, - bypass: function(doc) { - // reset both fields first - doc.completely_unused_field = undefined; - document.completely_unused_field = undefined; - // set one to true and check that it affected the other. - document.completely_unused_field = true; - if(doc.completely_unused_field && workerScript.ramUsage === 1.6) { - Player.giveExploit(Exploit.Bypass); - } - doc.completely_unused_field = undefined; - document.completely_unused_field = undefined; - }, - flags: function(data) { - data = toNative(data); - // We always want the help flag. - const args = {}; - - for(const d of data) { - let t = String; - if(typeof d[1] === 'number') { - t = Number; - } else if(typeof d[1] === 'boolean') { - t = Boolean; - } else if(Array.isArray(d[1])) { - t = [String]; - } - const numDashes = d[0].length > 1 ? 2 : 1; - args['-'.repeat(numDashes)+d[0]] = t - } - const ret = libarg(args, {argv: workerScript.args}); - for(const d of data) { - if(!ret.hasOwnProperty('--'+d[0]) || !ret.hasOwnProperty('-'+d[0])) ret[d[0]] = d[1]; - } - for(const key of Object.keys(ret)) { - if(!key.startsWith('-')) continue; - const value = ret[key]; - delete ret[key]; - const numDashes = key.length === 2 ? 1 : 2; - ret[key.slice(numDashes)] = value; - } - return ret; - }, - } - - function getFunctionNames(obj) { - const functionNames = []; - for(const [key, value] of Object.entries(obj)){ - if(typeof(value)=="function"){ - functionNames.push(key); - }else if(typeof(value)=="object"){ - functionNames.push(...getFunctionNames(value)); - } + } + Player.location = LocationName.AevumSnapFitnessGym; + costMult = 10; + expMult = 5; + break; + case LocationName.Sector12IronGym.toLowerCase(): + if (Player.city != CityName.Sector12) { + workerScript.log( + "gymWorkout", + "You cannot workout at 'Iron Gym' because you are not in 'Sector-12'.", + ); + return false; + } + Player.location = LocationName.Sector12IronGym; + costMult = 1; + expMult = 1; + break; + case LocationName.Sector12PowerhouseGym.toLowerCase(): + if (Player.city != CityName.Sector12) { + workerScript.log( + "gymWorkout", + "You cannot workout at 'Powerhouse Gym' because you are not in 'Sector-12'.", + ); + return false; + } + Player.location = LocationName.Sector12PowerhouseGym; + costMult = 20; + expMult = 10; + break; + case LocationName.VolhavenMilleniumFitnessGym.toLowerCase(): + if (Player.city != CityName.Volhaven) { + workerScript.log( + "gymWorkout", + "You cannot workout at 'Millenium Fitness Gym' because you are not in 'Volhaven'.", + ); + return false; + } + Player.location = LocationName.VolhavenMilleniumFitnessGym; + costMult = 7; + expMult = 4; + break; + default: + workerScript.log( + "gymWorkout", + `Invalid gym name: ${gymName}. gymWorkout() failed`, + ); + return false; + } + + switch (stat.toLowerCase()) { + case "strength".toLowerCase(): + case "str".toLowerCase(): + Player.startClass(costMult, expMult, CONSTANTS.ClassGymStrength); + break; + case "defense".toLowerCase(): + case "def".toLowerCase(): + Player.startClass(costMult, expMult, CONSTANTS.ClassGymDefense); + break; + case "dexterity".toLowerCase(): + case "dex".toLowerCase(): + Player.startClass(costMult, expMult, CONSTANTS.ClassGymDexterity); + break; + case "agility".toLowerCase(): + case "agi".toLowerCase(): + Player.startClass(costMult, expMult, CONSTANTS.ClassGymAgility); + break; + default: + workerScript.log("gymWorkout", `Invalid stat: ${stat}.`); + return false; + } + workerScript.log("gymWorkout", `Started training ${stat} at ${gymName}`); + return true; + }, + + travelToCity: function (cityname) { + updateDynamicRam("travelToCity", getRamCost("travelToCity")); + checkSingularityAccess("travelToCity", 1); + + switch (cityname) { + case CityName.Aevum: + case CityName.Chongqing: + case CityName.Sector12: + case CityName.NewTokyo: + case CityName.Ishima: + case CityName.Volhaven: + if (Player.money.lt(CONSTANTS.TravelCost)) { + throw makeRuntimeErrorMsg( + "travelToCity", + "Not enough money to travel.", + ); + } + Player.loseMoney(CONSTANTS.TravelCost); + Player.city = cityname; + workerScript.log("travelToCity", `Traveled to ${cityname}`); + return true; + default: + workerScript.log("travelToCity", `Invalid city name: '${cityname}'.`); + return false; + } + }, + + purchaseTor: function () { + updateDynamicRam("purchaseTor", getRamCost("purchaseTor")); + checkSingularityAccess("purchaseTor", 1); + + if (SpecialServerIps["Darkweb Server"] != null) { + workerScript.log("purchaseTor", "You already have a TOR router!"); + return false; + } + + if (Player.money.lt(CONSTANTS.TorRouterCost)) { + workerScript.log( + "purchaseTor", + "You cannot afford to purchase a Tor router.", + ); + return false; + } + Player.loseMoney(CONSTANTS.TorRouterCost); + + var darkweb = safetlyCreateUniqueServer({ + ip: createUniqueRandomIp(), + hostname: "darkweb", + organizationName: "", + isConnectedTo: false, + adminRights: false, + purchasedByPlayer: false, + maxRam: 1, + }); + AddToAllServers(darkweb); + SpecialServerIps.addIp("Darkweb Server", darkweb.ip); + + Player.getHomeComputer().serversOnNetwork.push(darkweb.ip); + darkweb.serversOnNetwork.push(Player.getHomeComputer().ip); + Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); + workerScript.log("purchaseTor", "You have purchased a Tor router!"); + return true; + }, + purchaseProgram: function (programName) { + updateDynamicRam("purchaseProgram", getRamCost("purchaseProgram")); + checkSingularityAccess("purchaseProgram", 1); + + if (SpecialServerIps["Darkweb Server"] == null) { + workerScript.log("purchaseProgram", "You do not have the TOR router."); + return false; + } + + programName = programName.toLowerCase(); + + let item = null; + for (const key in DarkWebItems) { + const i = DarkWebItems[key]; + if (i.program.toLowerCase() == programName) { + item = i; } - return functionNames; + } + + if (item == null) { + workerScript.log( + "purchaseProgram", + `Invalid program name: '${programName}.`, + ); + return false; + } + + if (Player.money.lt(item.price)) { + workerScript.log( + "purchaseProgram", + `Not enough money to purchase '${ + item.program + }'. Need ${numeralWrapper.formatMoney(item.price)}`, + ); + return false; + } + + if (Player.hasProgram(item.program)) { + workerScript.log( + "purchaseProgram", + `You already have the '${item.program}' program`, + ); + return true; + } + + Player.loseMoney(item.price); + Player.getHomeComputer().programs.push(item.program); + workerScript.log( + "purchaseProgram", + `You have purchased the '${item.program}' program. The new program can be found on your home computer.`, + ); + return true; + }, + getCurrentServer: function () { + updateDynamicRam("getCurrentServer", getRamCost("getCurrentServer")); + checkSingularityAccess("getCurrentServer", 1); + return Player.getCurrentServer().hostname; + }, + connect: function (hostname) { + updateDynamicRam("connect", getRamCost("connect")); + checkSingularityAccess("connect", 1); + if (!hostname) { + throw makeRuntimeErrorMsg("connect", `Invalid hostname: '${hostname}'`); + } + + let target = getServer(hostname); + if (target == null) { + throw makeRuntimeErrorMsg("connect", `Invalid hostname: '${hostname}'`); + return; + } + + if (hostname === "home") { + Player.getCurrentServer().isConnectedTo = false; + Player.currentServer = Player.getHomeComputer().ip; + Player.getCurrentServer().isConnectedTo = true; + Terminal.currDir = "/"; + Terminal.resetTerminalInput(true); + return true; + } + + const server = Player.getCurrentServer(); + for (let i = 0; i < server.serversOnNetwork.length; i++) { + const other = getServerOnNetwork(server, i); + if (other.ip == hostname || other.hostname == hostname) { + Player.getCurrentServer().isConnectedTo = false; + Player.currentServer = target.ip; + Player.getCurrentServer().isConnectedTo = true; + Terminal.currDir = "/"; + Terminal.resetTerminalInput(true); + return true; + } + } + + return false; + }, + manualHack: function () { + updateDynamicRam("manualHack", getRamCost("manualHack")); + checkSingularityAccess("manualHack", 1); + const server = Player.getCurrentServer(); + return hack(server.hostname, true); + }, + installBackdoor: function () { + updateDynamicRam("installBackdoor", getRamCost("installBackdoor")); + checkSingularityAccess("installBackdoor", 1); + const server = Player.getCurrentServer(); + const installTime = (calculateHackingTime(server, Player) / 4) * 1000; + + // No root access or skill level too low + const canHack = netscriptCanHack(server, Player); + if (!canHack.res) { + throw makeRuntimeErrorMsg("installBackdoor", canHack.msg); + } + + workerScript.log( + "installBackdoor", + `Installing backdoor on '${ + server.hostname + }' in ${convertTimeMsToTimeElapsedString(installTime, true)}`, + ); + + return netscriptDelay(installTime, workerScript).then(function () { + if (workerScript.env.stopFlag) { + return Promise.reject(workerScript); + } + workerScript.log( + "installBackdoor", + `Successfully installed backdoor on '${server.hostname}'`, + ); + + server.backdoorInstalled = true; + return Promise.resolve(); + }); + }, + getStats: function () { + updateDynamicRam("getStats", getRamCost("getStats")); + checkSingularityAccess("getStats", 1); + workerScript.log( + "getStats", + `getStats is deprecated, please use getPlayer`, + ); + + return { + hacking: Player.hacking_skill, + strength: Player.strength, + defense: Player.defense, + dexterity: Player.dexterity, + agility: Player.agility, + charisma: Player.charisma, + intelligence: Player.intelligence, + }; + }, + getCharacterInformation: function () { + updateDynamicRam( + "getCharacterInformation", + getRamCost("getCharacterInformation"), + ); + checkSingularityAccess("getCharacterInformation", 1); + workerScript.log( + "getCharacterInformation", + `getCharacterInformation is deprecated, please use getPlayer`, + ); + + return { + bitnode: Player.bitNodeN, + city: Player.city, + factions: Player.factions.slice(), + hp: Player.hp, + jobs: Object.keys(Player.jobs), + jobTitles: Object.values(Player.jobs), + maxHp: Player.max_hp, + mult: { + agility: Player.agility_mult, + agilityExp: Player.agility_exp_mult, + companyRep: Player.company_rep_mult, + crimeMoney: Player.crime_money_mult, + crimeSuccess: Player.crime_success_mult, + defense: Player.defense_mult, + defenseExp: Player.defense_exp_mult, + dexterity: Player.dexterity_mult, + dexterityExp: Player.dexterity_exp_mult, + factionRep: Player.faction_rep_mult, + hacking: Player.hacking_mult, + hackingExp: Player.hacking_exp_mult, + strength: Player.strength_mult, + strengthExp: Player.strength_exp_mult, + workMoney: Player.work_money_mult, + }, + timeWorked: Player.timeWorked, + tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), + workHackExpGain: Player.workHackExpGained, + workStrExpGain: Player.workStrExpGained, + workDefExpGain: Player.workDefExpGained, + workDexExpGain: Player.workDexExpGained, + workAgiExpGain: Player.workAgiExpGained, + workChaExpGain: Player.workChaExpGained, + workRepGain: Player.workRepGained, + workMoneyGain: Player.workMoneyGained, + hackingExp: Player.hacking_exp, + strengthExp: Player.strength_exp, + defenseExp: Player.defense_exp, + dexterityExp: Player.dexterity_exp, + agilityExp: Player.agility_exp, + charismaExp: Player.charisma_exp, + }; + }, + getPlayer: function () { + updateDynamicRam("getPlayer", getRamCost("getPlayer")); + + const data = { + hacking_skill: Player.hacking_skill, + hp: Player.hp, + max_hp: Player.max_hp, + strength: Player.strength, + defense: Player.defense, + dexterity: Player.dexterity, + agility: Player.agility, + charisma: Player.charisma, + intelligence: Player.intelligence, + hacking_chance_mult: Player.hacking_chance_mult, + hacking_speed_mult: Player.hacking_speed_mult, + hacking_money_mult: Player.hacking_money_mult, + hacking_grow_mult: Player.hacking_grow_mult, + hacking_exp: Player.hacking_exp, + strength_exp: Player.strength_exp, + defense_exp: Player.defense_exp, + dexterity_exp: Player.dexterity_exp, + agility_exp: Player.agility_exp, + charisma_exp: Player.charisma_exp, + hacking_mult: Player.hacking_mult, + strength_mult: Player.strength_mult, + defense_mult: Player.defense_mult, + dexterity_mult: Player.dexterity_mult, + agility_mult: Player.agility_mult, + charisma_mult: Player.charisma_mult, + hacking_exp_mult: Player.hacking_exp_mult, + strength_exp_mult: Player.strength_exp_mult, + defense_exp_mult: Player.defense_exp_mult, + dexterity_exp_mult: Player.dexterity_exp_mult, + agility_exp_mult: Player.agility_exp_mult, + charisma_exp_mult: Player.charisma_exp_mult, + company_rep_mult: Player.company_rep_mult, + faction_rep_mult: Player.faction_rep_mult, + numPeopleKilled: Player.numPeopleKilled, + money: Player.money.toNumber(), + city: Player.city, + location: Player.location, + crime_money_mult: Player.crime_money_mult, + crime_success_mult: Player.crime_success_mult, + isWorking: Player.isWorking, + workType: Player.workType, + currentWorkFactionName: Player.currentWorkFactionName, + currentWorkFactionDescription: Player.currentWorkFactionDescription, + workHackExpGainRate: Player.workHackExpGainRate, + workStrExpGainRate: Player.workStrExpGainRate, + workDefExpGainRate: Player.workDefExpGainRate, + workDexExpGainRate: Player.workDexExpGainRate, + workAgiExpGainRate: Player.workAgiExpGainRate, + workChaExpGainRate: Player.workChaExpGainRate, + workRepGainRate: Player.workRepGainRate, + workMoneyGainRate: Player.workMoneyGainRate, + workMoneyLossRate: Player.workMoneyLossRate, + workHackExpGained: Player.workHackExpGained, + workStrExpGained: Player.workStrExpGained, + workDefExpGained: Player.workDefExpGained, + workDexExpGained: Player.workDexExpGained, + workAgiExpGained: Player.workAgiExpGained, + workChaExpGained: Player.workChaExpGained, + workRepGained: Player.workRepGained, + workMoneyGained: Player.workMoneyGained, + createProgramName: Player.createProgramName, + createProgramReqLvl: Player.createProgramReqLvl, + className: Player.className, + crimeType: Player.crimeType, + work_money_mult: Player.work_money_mult, + hacknet_node_money_mult: Player.hacknet_node_money_mult, + hacknet_node_purchase_cost_mult: Player.hacknet_node_purchase_cost_mult, + hacknet_node_ram_cost_mult: Player.hacknet_node_ram_cost_mult, + hacknet_node_core_cost_mult: Player.hacknet_node_core_cost_mult, + hacknet_node_level_cost_mult: Player.hacknet_node_level_cost_mult, + hasWseAccount: Player.hasWseAccount, + hasTixApiAccess: Player.hasTixApiAccess, + has4SData: Player.has4SData, + has4SDataTixApi: Player.has4SDataTixApi, + bladeburner_max_stamina_mult: Player.bladeburner_max_stamina_mult, + bladeburner_stamina_gain_mult: Player.bladeburner_stamina_gain_mult, + bladeburner_analysis_mult: Player.bladeburner_analysis_mult, + bladeburner_success_chance_mult: Player.bladeburner_success_chance_mult, + bitNodeN: Player.bitNodeN, + totalPlaytime: Player.totalPlaytime, + playtimeSinceLastAug: Player.playtimeSinceLastAug, + playtimeSinceLastBitnode: Player.playtimeSinceLastBitnode, + jobs: {}, + factions: Player.factions.slice(), + tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), + }; + Object.assign(data.jobs, Player.jobs); + return data; + }, + hospitalize: function () { + updateDynamicRam("hospitalize", getRamCost("hospitalize")); + checkSingularityAccess("hospitalize", 1); + return Player.hospitalize(); + }, + isBusy: function () { + updateDynamicRam("isBusy", getRamCost("isBusy")); + checkSingularityAccess("isBusy", 1); + return Player.isWorking || inMission; + }, + stopAction: function () { + updateDynamicRam("stopAction", getRamCost("stopAction")); + checkSingularityAccess("stopAction", 1); + if (Player.isWorking) { + var txt = Player.singularityStopWork(); + workerScript.log("stopAction", txt); + return true; + } + return false; + }, + upgradeHomeRam: function () { + updateDynamicRam("upgradeHomeRam", getRamCost("upgradeHomeRam")); + checkSingularityAccess("upgradeHomeRam", 2); + + // Check if we're at max RAM + const homeComputer = Player.getHomeComputer(); + if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) { + workerScript.log("upgradeHomeRam", `Your home computer is at max RAM.`); + return false; + } + + const cost = Player.getUpgradeHomeRamCost(); + if (Player.money.lt(cost)) { + workerScript.log( + "upgradeHomeRam", + `You don't have enough money. Need ${numeralWrapper.formatMoney( + cost, + )}`, + ); + return false; + } + + homeComputer.maxRam *= 2; + Player.loseMoney(cost); + + Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); + workerScript.log( + "upgradeHomeRam", + `Purchased additional RAM for home computer! It now has ${homeComputer.maxRam}GB of RAM.`, + ); + return true; + }, + getUpgradeHomeRamCost: function () { + updateDynamicRam( + "getUpgradeHomeRamCost", + getRamCost("getUpgradeHomeRamCost"), + ); + checkSingularityAccess("getUpgradeHomeRamCost", 2); + + return Player.getUpgradeHomeRamCost(); + }, + workForCompany: function (companyName) { + updateDynamicRam("workForCompany", getRamCost("workForCompany")); + checkSingularityAccess("workForCompany", 2); + + // Sanitize input + if (companyName == null) { + companyName = Player.companyName; + } + + // Make sure its a valid company + if ( + companyName == null || + companyName === "" || + !(Companies[companyName] instanceof Company) + ) { + workerScript.log("workForCompany", `Invalid company: '${companyName}'`); + return false; + } + + // Make sure player is actually employed at the comapny + if (!Object.keys(Player.jobs).includes(companyName)) { + workerScript.log( + "workForCompany", + `You do not have a job at '${companyName}'`, + ); + return false; + } + + // Cant work while in a mission + if (inMission) { + workerScript.log( + "workForCompany", + "You are in the middle of a mission.", + ); + return false; + } + + // Check to make sure company position data is valid + const companyPositionName = Player.jobs[companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if ( + companyPositionName === "" || + !(companyPosition instanceof CompanyPosition) + ) { + workerScript.log("workForCompany", "You do not have a job"); + return false; + } + + if (Player.isWorking) { + var txt = Player.singularityStopWork(); + workerScript.log("workForCompany", txt); + } + + if (companyPosition.isPartTimeJob()) { + Player.startWorkPartTime(companyName); + } else { + Player.startWork(companyName); + } + workerScript.log( + "workForCompany", + `Began working at '${Player.companyName}' as a '${companyPositionName}'`, + ); + return true; + }, + applyToCompany: function (companyName, field) { + updateDynamicRam("applyToCompany", getRamCost("applyToCompany")); + checkSingularityAccess("applyToCompany", 2); + getCompany("applyToCompany", companyName); + + Player.location = companyName; + var res; + switch (field.toLowerCase()) { + case "software": + res = Player.applyForSoftwareJob(true); + break; + case "software consultant": + res = Player.applyForSoftwareConsultantJob(true); + break; + case "it": + res = Player.applyForItJob(true); + break; + case "security engineer": + res = Player.applyForSecurityEngineerJob(true); + break; + case "network engineer": + res = Player.applyForNetworkEngineerJob(true); + break; + case "business": + res = Player.applyForBusinessJob(true); + break; + case "business consultant": + res = Player.applyForBusinessConsultantJob(true); + break; + case "security": + res = Player.applyForSecurityJob(true); + break; + case "agent": + res = Player.applyForAgentJob(true); + break; + case "employee": + res = Player.applyForEmployeeJob(true); + break; + case "part-time employee": + res = Player.applyForPartTimeEmployeeJob(true); + break; + case "waiter": + res = Player.applyForWaiterJob(true); + break; + case "part-time waiter": + res = Player.applyForPartTimeWaiterJob(true); + break; + default: + workerScript.log("applyToCompany", `Invalid job: '${field}'.`); + return false; + } + // The Player object's applyForJob function can return string with special error messages + if (isString(res)) { + workerScript.log("applyToCompany", res); + return false; + } + if (res) { + workerScript.log( + "applyToCompany", + `You were offered a new job at '${companyName}' as a '${Player.jobs[companyName]}'`, + ); + } else { + workerScript.log( + "applyToCompany", + `You failed to get a new job/promotion at '${companyName}' in the '${field}' field.`, + ); + } + return res; + }, + getCompanyRep: function (companyName) { + updateDynamicRam("getCompanyRep", getRamCost("getCompanyRep")); + checkSingularityAccess("getCompanyRep", 2); + const company = getCompany("getCompanyRep", companyName); + return company.playerReputation; + }, + getCompanyFavor: function (companyName) { + updateDynamicRam("getCompanyFavor", getRamCost("getCompanyFavor")); + checkSingularityAccess("getCompanyFavor", 2); + const company = getCompany("getCompanyFavor", companyName); + return company.favor; + }, + getCompanyFavorGain: function (companyName) { + updateDynamicRam( + "getCompanyFavorGain", + getRamCost("getCompanyFavorGain"), + ); + checkSingularityAccess("getCompanyFavorGain", 2); + const company = getCompany("getCompanyFavorGain", companyName); + return company.getFavorGain()[0]; + }, + checkFactionInvitations: function () { + updateDynamicRam( + "checkFactionInvitations", + getRamCost("checkFactionInvitations"), + ); + checkSingularityAccess("checkFactionInvitations", 2); + // Make a copy of Player.factionInvitations + return Player.factionInvitations.slice(); + }, + joinFaction: function (name) { + updateDynamicRam("joinFaction", getRamCost("joinFaction")); + checkSingularityAccess("joinFaction", 2); + getFaction("joinFaction", name); + + if (!Player.factionInvitations.includes(name)) { + workerScript.log( + "joinFaction", + `You have not been invited by faction '${name}'`, + ); + return false; + } + const fac = Factions[name]; + joinFaction(fac); + + // Update Faction Invitation list to account for joined + banned factions + for (let i = 0; i < Player.factionInvitations.length; ++i) { + if ( + Player.factionInvitations[i] == name || + Factions[Player.factionInvitations[i]].isBanned + ) { + Player.factionInvitations.splice(i, 1); + i--; + } + } + Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); + workerScript.log("joinFaction", `Joined the '${name}' faction.`); + return true; + }, + workForFaction: function (name, type) { + updateDynamicRam("workForFaction", getRamCost("workForFaction")); + checkSingularityAccess("workForFaction", 2); + getFaction("workForFaction", name); + + // if the player is in a gang and the target faction is any of the gang faction, fail + if (Player.inGang() && AllGangs[name] !== undefined) { + workerScript.log( + "workForFaction", + `Faction '${name}' does not offer work at the moment.`, + ); + return; + } + + if (inMission) { + workerScript.log( + "workForFaction", + "You are in the middle of a mission.", + ); + return; + } + + if (!Player.factions.includes(name)) { + workerScript.log("workForFaction", `You are not a member of '${name}'`); + return false; + } + + if (Player.isWorking) { + const txt = Player.singularityStopWork(); + workerScript.log("workForFaction", txt); + } + + var fac = Factions[name]; + // Arrays listing factions that allow each time of work + var hackAvailable = [ + "Illuminati", + "Daedalus", + "The Covenant", + "ECorp", + "MegaCorp", + "Bachman & Associates", + "Blade Industries", + "NWO", + "Clarke Incorporated", + "OmniTek Incorporated", + "Four Sigma", + "KuaiGong International", + "Fulcrum Secret Technologies", + "BitRunners", + "The Black Hand", + "NiteSec", + "Chongqing", + "Sector-12", + "New Tokyo", + "Aevum", + "Ishima", + "Volhaven", + "Speakers for the Dead", + "The Dark Army", + "The Syndicate", + "Silhouette", + "Netburners", + "Tian Di Hui", + "CyberSec", + ]; + var fdWkAvailable = [ + "Illuminati", + "Daedalus", + "The Covenant", + "ECorp", + "MegaCorp", + "Bachman & Associates", + "Blade Industries", + "NWO", + "Clarke Incorporated", + "OmniTek Incorporated", + "Four Sigma", + "KuaiGong International", + "The Black Hand", + "Chongqing", + "Sector-12", + "New Tokyo", + "Aevum", + "Ishima", + "Volhaven", + "Speakers for the Dead", + "The Dark Army", + "The Syndicate", + "Silhouette", + "Tetrads", + "Slum Snakes", + ]; + var scWkAvailable = [ + "ECorp", + "MegaCorp", + "Bachman & Associates", + "Blade Industries", + "NWO", + "Clarke Incorporated", + "OmniTek Incorporated", + "Four Sigma", + "KuaiGong International", + "Fulcrum Secret Technologies", + "Chongqing", + "Sector-12", + "New Tokyo", + "Aevum", + "Ishima", + "Volhaven", + "Speakers for the Dead", + "The Syndicate", + "Tetrads", + "Slum Snakes", + "Tian Di Hui", + ]; + + switch (type.toLowerCase()) { + case "hacking": + case "hacking contracts": + case "hackingcontracts": + if (!hackAvailable.includes(fac.name)) { + workerScript.log( + "workForFaction", + `Faction '${fac.name}' do not need help with hacking contracts.`, + ); + return false; + } + Player.startFactionHackWork(fac); + workerScript.log( + "workForFaction", + `Started carrying out hacking contracts for '${fac.name}'`, + ); + return true; + case "field": + case "fieldwork": + case "field work": + if (!fdWkAvailable.includes(fac.name)) { + workerScript.log( + "workForFaction", + `Faction '${fac.name}' do not need help with field missions.`, + ); + return false; + } + Player.startFactionFieldWork(fac); + workerScript.log( + "workForFaction", + `Started carrying out field missions for '${fac.name}'`, + ); + return true; + case "security": + case "securitywork": + case "security work": + if (!scWkAvailable.includes(fac.name)) { + workerScript.log( + "workForFaction", + `Faction '${fac.name}' do not need help with security work.`, + ); + return false; + } + Player.startFactionSecurityWork(fac); + workerScript.log( + "workForFaction", + `Started carrying out security work for '${fac.name}'`, + ); + return true; + default: + workerScript.log("workForFaction", `Invalid work type: '${type}`); + } + return true; + }, + getFactionRep: function (name) { + updateDynamicRam("getFactionRep", getRamCost("getFactionRep")); + checkSingularityAccess("getFactionRep", 2); + const faction = getFaction("getFactionRep", name); + return faction.playerReputation; + }, + getFactionFavor: function (name) { + updateDynamicRam("getFactionFavor", getRamCost("getFactionFavor")); + checkSingularityAccess("getFactionFavor", 2); + const faction = getFaction("getFactionFavor", name); + return faction.favor; + }, + getFactionFavorGain: function (name) { + updateDynamicRam( + "getFactionFavorGain", + getRamCost("getFactionFavorGain"), + ); + checkSingularityAccess("getFactionFavorGain", 2); + const faction = getFaction("getFactionFavorGain", name); + return faction.getFavorGain()[0]; + }, + donateToFaction: function (name, amt) { + updateDynamicRam("donateToFaction", getRamCost("donateToFaction")); + checkSingularityAccess("donateToFaction", 3); + const faction = getFaction("donateToFaction", name); + + if (typeof amt !== "number" || amt <= 0) { + workerScript.log( + "donateToFaction", + `Invalid donation amount: '${amt}'.`, + ); + return false; + } + if (Player.money.lt(amt)) { + workerScript.log( + "donateToFaction", + `You do not have enough money to donate ${numeralWrapper.formatMoney( + amt, + )} to '${name}'`, + ); + return false; + } + const repNeededToDonate = Math.round( + CONSTANTS.BaseFavorToDonate * BitNodeMultipliers.RepToDonateToFaction, + ); + if (faction.favor < repNeededToDonate) { + workerScript.log( + "donateToFaction", + `You do not have enough favor to donate to this faction. Have ${faction.favor}, need ${repNeededToDonate}`, + ); + return false; + } + const repGain = + (amt / CONSTANTS.DonateMoneyToRepDivisor) * Player.faction_rep_mult; + faction.playerReputation += repGain; + Player.loseMoney(amt); + workerScript.log( + "donateToFaction", + `${numeralWrapper.formatMoney( + amt, + )} donated to '${name}' for ${numeralWrapper.formatReputation( + repGain, + )} reputation`, + ); + return true; + }, + createProgram: function (name) { + updateDynamicRam("createProgram", getRamCost("createProgram")); + checkSingularityAccess("createProgram", 3); + + if (inMission) { + workerScript.log( + "createProgram", + "You are in the middle of a mission.", + ); + return; + } + if (Player.isWorking) { + var txt = Player.singularityStopWork(); + workerScript.log("createProgram", txt); + } + + name = name.toLowerCase(); + + let p = null; + for (const key in Programs) { + if (Programs[key].name.toLowerCase() == name) { + p = Programs[key]; + } + } + + if (p == null) { + workerScript.log( + "createProgram", + `The specified program does not exist: '${name}`, + ); + return false; + } + + if (Player.hasProgram(p.name)) { + workerScript.log( + "createProgram", + `You already have the '${p.name}' program`, + ); + return false; + } + + if (!p.create.req(Player)) { + workerScript.log( + "createProgram", + `Hacking level is too low to create '${p.name}' (level ${p.create.level} req)`, + ); + return false; + } + + Player.startCreateProgramWork(p.name, p.create.time, p.create.level); + workerScript.log("createProgram", `Began creating program: '${name}'`); + return true; + }, + commitCrime: function (crimeRoughName) { + updateDynamicRam("commitCrime", getRamCost("commitCrime")); + checkSingularityAccess("commitCrime", 3); + if (inMission) { + workerScript.log("commitCrime", "You are in the middle of a mission."); + return; + } + if (Player.isWorking) { + const txt = Player.singularityStopWork(); + workerScript.log("commitCrime", txt); + } + + // Set Location to slums + Player.gotoLocation(LocationName.Slums); + + const crime = findCrime(crimeRoughName.toLowerCase()); + if (crime == null) { + // couldn't find crime + throw makeRuntimeErrorMsg( + "commitCrime", + `Invalid crime: '${crimeRoughName}'`, + ); + } + workerScript.log("commitCrime", `Attempting to commit ${crime.name}...`); + return crime.commit(Player, 1, { workerscript: workerScript }); + }, + getCrimeChance: function (crimeRoughName) { + updateDynamicRam("getCrimeChance", getRamCost("getCrimeChance")); + checkSingularityAccess("getCrimeChance", 3); + + const crime = findCrime(crimeRoughName.toLowerCase()); + if (crime == null) { + throw makeRuntimeErrorMsg( + "getCrimeChance", + `Invalid crime: ${crimeRoughName}`, + ); + } + + return crime.successRate(Player); + }, + getCrimeStats: function (crimeRoughName) { + updateDynamicRam("getCrimeStats", getRamCost("getCrimeStats")); + checkSingularityAccess("getCrimeStats", 3); + + const crime = findCrime(crimeRoughName.toLowerCase()); + if (crime == null) { + throw makeRuntimeErrorMsg( + "getCrimeStats", + `Invalid crime: ${crimeRoughName}`, + ); + } + + return Object.assign({}, crime); + }, + getOwnedAugmentations: function (purchased = false) { + updateDynamicRam( + "getOwnedAugmentations", + getRamCost("getOwnedAugmentations"), + ); + checkSingularityAccess("getOwnedAugmentations", 3); + var res = []; + for (var i = 0; i < Player.augmentations.length; ++i) { + res.push(Player.augmentations[i].name); + } + if (purchased) { + for (var i = 0; i < Player.queuedAugmentations.length; ++i) { + res.push(Player.queuedAugmentations[i].name); + } + } + return res; + }, + getOwnedSourceFiles: function () { + updateDynamicRam( + "getOwnedSourceFiles", + getRamCost("getOwnedSourceFiles"), + ); + checkSingularityAccess("getOwnedSourceFiles", 3); + let res = []; + for (let i = 0; i < Player.sourceFiles.length; ++i) { + res.push({ + n: Player.sourceFiles[i].n, + lvl: Player.sourceFiles[i].lvl, + }); + } + return res; + }, + getAugmentationsFromFaction: function (facname) { + updateDynamicRam( + "getAugmentationsFromFaction", + getRamCost("getAugmentationsFromFaction"), + ); + checkSingularityAccess("getAugmentationsFromFaction", 3); + const faction = getFaction("getAugmentationsFromFaction", facname); + + // If player has a gang with this faction, return all augmentations. + if (Player.hasGangWith(facname)) { + const res = []; + for (const augName in Augmentations) { + const aug = Augmentations[augName]; + if (!aug.isSpecial) { + res.push(augName); + } + } + + return res; + } + + return faction.augmentations.slice(); + }, + getAugmentationPrereq: function (name) { + updateDynamicRam( + "getAugmentationPrereq", + getRamCost("getAugmentationPrereq"), + ); + checkSingularityAccess("getAugmentationPrereq", 3); + const aug = getAugmentation("getAugmentationPrereq", name); + return aug.prereqs.slice(); + }, + getAugmentationCost: function (name) { + updateDynamicRam( + "getAugmentationCost", + getRamCost("getAugmentationCost"), + ); + checkSingularityAccess("getAugmentationCost", 3); + const aug = getAugmentation("getAugmentationCost", name); + return [aug.baseRepRequirement, aug.baseCost]; + }, + getAugmentationStats: function (name) { + updateDynamicRam( + "getAugmentationStats", + getRamCost("getAugmentationStats"), + ); + checkSingularityAccess("getAugmentationStats", 3); + const aug = getAugmentation("getAugmentationStats", name); + return Object.assign({}, aug.mults); + }, + purchaseAugmentation: function (faction, name) { + updateDynamicRam( + "purchaseAugmentation", + getRamCost("purchaseAugmentation"), + ); + checkSingularityAccess("purchaseAugmentation", 3); + const fac = getFaction("purchaseAugmentation", faction); + const aug = getAugmentation("purchaseAugmentation", name); + + let augs = []; + if (Player.hasGangWith(faction)) { + for (const augName in Augmentations) { + const tempAug = Augmentations[augName]; + if (!tempAug.isSpecial) { + augs.push(augName); + } + } + } else { + augs = fac.augmentations; + } + + if (!augs.includes(name)) { + workerScript.log( + "purchaseAugmentation", + `Faction '${faction}' does not have the '${name}' augmentation.`, + ); + return false; + } + + const isNeuroflux = aug.name === AugmentationNames.NeuroFluxGovernor; + if (!isNeuroflux) { + for (let j = 0; j < Player.queuedAugmentations.length; ++j) { + if (Player.queuedAugmentations[j].name === aug.name) { + workerScript.log( + "purchaseAugmentation", + `You already have the '${name}' augmentation.`, + ); + return false; + } + } + for (let j = 0; j < Player.augmentations.length; ++j) { + if (Player.augmentations[j].name === aug.name) { + workerScript.log( + "purchaseAugmentation", + `You already have the '${name}' augmentation.`, + ); + return false; + } + } + } + + if (fac.playerReputation < aug.baseRepRequirement) { + workerScript.log( + "purchaseAugmentation", + `You do not have enough reputation with '${fac.name}'.`, + ); + return false; + } + + const res = purchaseAugmentation(aug, fac, true); + workerScript.log("purchaseAugmentation", res); + if (isString(res) && res.startsWith("You purchased")) { + Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); + return true; + } else { + return false; + } + }, + softReset: function (cbScript) { + updateDynamicRam("softReset", getRamCost("softReset")); + checkSingularityAccess("softReset", 3); + + workerScript.log( + "softReset", + "Soft resetting. This will cause this script to be killed", + ); + setTimeoutRef(() => { + prestigeAugmentation(); + runAfterReset(cbScript); + }, 0); + + // Prevent workerScript from "finishing execution naturally" + workerScript.running = false; + killWorkerScript(workerScript); + }, + installAugmentations: function (cbScript) { + updateDynamicRam( + "installAugmentations", + getRamCost("installAugmentations"), + ); + checkSingularityAccess("installAugmentations", 3); + + if (Player.queuedAugmentations.length === 0) { + workerScript.log( + "installAugmentations", + "You do not have any Augmentations to be installed.", + ); + return false; + } + Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain); + workerScript.log( + "installAugmentations", + "Installing Augmentations. This will cause this script to be killed", + ); + setTimeoutRef(() => { + installAugmentations(); + runAfterReset(cbScript); + }, 0); + + workerScript.running = false; // Prevent workerScript from "finishing execution naturally" + killWorkerScript(workerScript); + }, + + // Gang API + gang: { + createGang: function (faction) { + updateDynamicRam("createGang", getRamCost("gang", "createGang")); + // this list is copied from Faction/ui/Root.tsx + const GangNames = [ + "Slum Snakes", + "Tetrads", + "The Syndicate", + "The Dark Army", + "Speakers for the Dead", + "NiteSec", + "The Black Hand", + ]; + if (!Player.canAccessGang() || !GangNames.includes(faction)) + return false; + if (Player.inGang()) return false; + if (!Player.factions.includes(faction)) return false; + + const isHacking = faction === "NiteSec" || faction === "The Black Hand"; + Player.startGang(faction, isHacking); + return true; + }, + inGang: function () { + updateDynamicRam("inGang", getRamCost("gang", "inGang")); + return Player.inGang(); + }, + getMemberNames: function () { + updateDynamicRam( + "getMemberNames", + getRamCost("gang", "getMemberNames"), + ); + checkGangApiAccess("getMemberNames"); + return Player.gang.members.map((member) => member.name); + }, + getGangInformation: function () { + updateDynamicRam( + "getGangInformation", + getRamCost("gang", "getGangInformation"), + ); + checkGangApiAccess("getGangInformation"); + return { + faction: Player.gang.facName, + isHacking: Player.gang.isHackingGang, + moneyGainRate: Player.gang.moneyGainRate, + power: Player.gang.getPower(), + respect: Player.gang.respect, + respectGainRate: Player.gang.respectGainRate, + territory: Player.gang.getTerritory(), + territoryClashChance: Player.gang.territoryClashChance, + territoryWarfareEngaged: Player.gang.territoryWarfareEngaged, + wantedLevel: Player.gang.wanted, + wantedLevelGainRate: Player.gang.wantedGainRate, + }; + }, + getOtherGangInformation: function () { + updateDynamicRam( + "getOtherGangInformation", + getRamCost("gang", "getOtherGangInformation"), + ); + checkGangApiAccess("getOtherGangInformation"); + const cpy = {}; + for (const gang in AllGangs) { + cpy[gang] = Object.assign({}, AllGangs[gang]); + } + + return cpy; + }, + getMemberInformation: function (name) { + updateDynamicRam( + "getMemberInformation", + getRamCost("gang", "getMemberInformation"), + ); + checkGangApiAccess("getMemberInformation"); + const member = getGangMember("getMemberInformation", name); + return { + name: member.name, + task: member.task, + earnedRespect: member.earnedRespect, + hack: member.hack, + str: member.str, + def: member.def, + dex: member.dex, + agi: member.agi, + cha: member.cha, + + hack_exp: member.hack_exp, + str_exp: member.str_exp, + def_exp: member.def_exp, + dex_exp: member.dex_exp, + agi_exp: member.agi_exp, + cha_exp: member.cha_exp, + + hack_mult: member.hack_mult, + str_mult: member.str_mult, + def_mult: member.def_mult, + dex_mult: member.dex_mult, + agi_mult: member.agi_mult, + cha_mult: member.cha_mult, + + hack_asc_mult: member.calculateAscensionMult(member.hack_asc_points), + str_asc_mult: member.calculateAscensionMult(member.str_asc_points), + def_asc_mult: member.calculateAscensionMult(member.def_asc_points), + dex_asc_mult: member.calculateAscensionMult(member.dex_asc_points), + agi_asc_mult: member.calculateAscensionMult(member.agi_asc_points), + cha_asc_mult: member.calculateAscensionMult(member.cha_asc_points), + + hack_asc_points: member.hack_asc_points, + str_asc_points: member.str_asc_points, + def_asc_points: member.def_asc_points, + dex_asc_points: member.dex_asc_points, + agi_asc_points: member.agi_asc_points, + cha_asc_points: member.cha_asc_points, + + upgrades: member.upgrades.slice(), + augmentations: member.augmentations.slice(), + }; + }, + canRecruitMember: function () { + updateDynamicRam( + "canRecruitMember", + getRamCost("gang", "canRecruitMember"), + ); + checkGangApiAccess("canRecruitMember"); + return Player.gang.canRecruitMember(); + }, + recruitMember: function (name) { + updateDynamicRam("recruitMember", getRamCost("gang", "recruitMember")); + checkGangApiAccess("recruitMember"); + const recruited = Player.gang.recruitMember(name); + if (recruited) { + workerScript.log( + "recruitMember", + `Successfully recruited Gang Member '${name}'`, + ); + } else { + workerScript.log( + "recruitMember", + `Failed to recruit Gang Member '${name}'`, + ); + } + + return recruited; + }, + getTaskNames: function () { + updateDynamicRam("getTaskNames", getRamCost("gang", "getTaskNames")); + checkGangApiAccess("getTaskNames"); + const tasks = Player.gang.getAllTaskNames(); + tasks.unshift("Unassigned"); + return tasks; + }, + setMemberTask: function (memberName, taskName) { + updateDynamicRam("setMemberTask", getRamCost("gang", "setMemberTask")); + checkGangApiAccess("setMemberTask"); + const member = getGangMember("setMemberTask", memberName); + const success = member.assignToTask(taskName); + if (success) { + workerScript.log( + "setMemberTask", + `Successfully assigned Gang Member '${memberName}' to '${taskName}' task`, + ); + } else { + workerScript.log( + "setMemberTask", + `Failed to assign Gang Member '${memberName}' to '${taskName}' task. '${memberName}' is now Unassigned`, + ); + } + + return success; + }, + getTaskStats: function (taskName) { + updateDynamicRam("getTaskStats", getRamCost("gang", "getTaskStats")); + checkGangApiAccess("getTaskStats"); + const task = getGangTask("getTaskStats", taskName); + const copy = Object.assign({}, task); + copy.territory = Object.assign({}, task.territory); + return copy; + }, + getEquipmentNames: function () { + updateDynamicRam( + "getEquipmentNames", + getRamCost("gang", "getEquipmentNames"), + ); + checkGangApiAccess("getEquipmentNames"); + return Object.keys(GangMemberUpgrades); + }, + getEquipmentCost: function (equipName) { + updateDynamicRam( + "getEquipmentCost", + getRamCost("gang", "getEquipmentCost"), + ); + checkGangApiAccess("getEquipmentCost"); + const upg = GangMemberUpgrades[equipName]; + if (upg === null) return Infinity; + return Player.gang.getUpgradeCost(upg); + }, + getEquipmentType: function (equipName) { + updateDynamicRam( + "getEquipmentType", + getRamCost("gang", "getEquipmentType"), + ); + checkGangApiAccess("getEquipmentType"); + const upg = GangMemberUpgrades[equipName]; + if (upg == null) return ""; + return upg.getType(); + }, + getEquipmentStats: function (equipName) { + updateDynamicRam( + "getEquipmentStats", + getRamCost("gang", "getEquipmentStats"), + ); + checkGangApiAccess("getEquipmentStats"); + const equipment = GangMemberUpgrades[equipName]; + if (!equipment) { + throw makeRuntimeErrorMsg( + "getEquipmentStats", + `Invalid equipment: ${equipName}`, + ); + } + return Object.assign({}, equipment.mults); + }, + purchaseEquipment: function (memberName, equipName) { + updateDynamicRam( + "purchaseEquipment", + getRamCost("gang", "purchaseEquipment"), + ); + checkGangApiAccess("purchaseEquipment"); + const member = getGangMember("purchaseEquipment", memberName); + const equipment = GangMemberUpgrades[equipName]; + if (!equipment) return false; + const res = member.buyUpgrade(equipment, Player, Player.gang); + if (res) { + workerScript.log( + "purchaseEquipment", + `Purchased '${equipName}' for Gang member '${memberName}'`, + ); + } else { + workerScript.log( + "purchaseEquipment", + `Failed to purchase '${equipName}' for Gang member '${memberName}'`, + ); + } + + return res; + }, + ascendMember: function (name) { + updateDynamicRam("ascendMember", getRamCost("gang", "ascendMember")); + checkGangApiAccess("ascendMember"); + const member = getGangMember("ascendMember", name); + if (!member.canAscend()) return; + return Player.gang.ascendMember(member, workerScript); + }, + setTerritoryWarfare: function (engage) { + updateDynamicRam( + "setTerritoryWarfare", + getRamCost("gang", "setTerritoryWarfare"), + ); + checkGangApiAccess("setTerritoryWarfare"); + if (engage) { + Player.gang.territoryWarfareEngaged = true; + workerScript.log( + "setTerritoryWarfare", + "Engaging in Gang Territory Warfare", + ); + } else { + Player.gang.territoryWarfareEngaged = false; + workerScript.log( + "setTerritoryWarfare", + "Disengaging in Gang Territory Warfare", + ); + } + }, + getChanceToWinClash: function (otherGang) { + updateDynamicRam( + "getChanceToWinClash", + getRamCost("gang", "getChanceToWinClash"), + ); + checkGangApiAccess("getChanceToWinClash"); + if (AllGangs[otherGang] == null) { + throw makeRuntimeErrorMsg( + `gang.${getChanceToWinClash}`, + `Invalid gang: ${otherGang}`, + ); + } + + const playerPower = AllGangs[Player.gang.facName].power; + const otherPower = AllGangs[otherGang].power; + + return playerPower / (otherPower + playerPower); + }, + getBonusTime: function () { + updateDynamicRam("getBonusTime", getRamCost("gang", "getBonusTime")); + checkGangApiAccess("getBonusTime"); + return Math.round(Player.gang.storedCycles / 5); + }, + }, // end gang namespace + + // Bladeburner API + bladeburner: { + getContractNames: function () { + updateDynamicRam( + "getContractNames", + getRamCost("bladeburner", "getContractNames"), + ); + checkBladeburnerAccess("getContractNames"); + return Player.bladeburner.getContractNamesNetscriptFn(); + }, + getOperationNames: function () { + updateDynamicRam( + "getOperationNames", + getRamCost("bladeburner", "getOperationNames"), + ); + checkBladeburnerAccess("getOperationNames"); + return Player.bladeburner.getOperationNamesNetscriptFn(); + }, + getBlackOpNames: function () { + updateDynamicRam( + "getBlackOpNames", + getRamCost("bladeburner", "getBlackOpNames"), + ); + checkBladeburnerAccess("getBlackOpNames"); + return Player.bladeburner.getBlackOpNamesNetscriptFn(); + }, + getBlackOpRank: function (name = "") { + updateDynamicRam( + "getBlackOpRank", + getRamCost("bladeburner", "getBlackOpRank"), + ); + checkBladeburnerAccess("getBlackOpRank"); + const action = getBladeburnerActionObject( + "getBlackOpRank", + "blackops", + name, + ); + return action.reqdRank; + }, + getGeneralActionNames: function () { + updateDynamicRam( + "getGeneralActionNames", + getRamCost("bladeburner", "getGeneralActionNames"), + ); + checkBladeburnerAccess("getGeneralActionNames"); + return Player.bladeburner.getGeneralActionNamesNetscriptFn(); + }, + getSkillNames: function () { + updateDynamicRam( + "getSkillNames", + getRamCost("bladeburner", "getSkillNames"), + ); + checkBladeburnerAccess("getSkillNames"); + return Player.bladeburner.getSkillNamesNetscriptFn(); + }, + startAction: function (type = "", name = "") { + updateDynamicRam( + "startAction", + getRamCost("bladeburner", "startAction"), + ); + checkBladeburnerAccess("startAction"); + try { + return Player.bladeburner.startActionNetscriptFn( + Player, + type, + name, + workerScript, + ); + } catch (e) { + throw makeRuntimeErrorMsg("bladeburner.startAction", e); + } + }, + stopBladeburnerAction: function () { + updateDynamicRam( + "stopBladeburnerAction", + getRamCost("bladeburner", "stopBladeburnerAction"), + ); + checkBladeburnerAccess("stopBladeburnerAction"); + return Player.bladeburner.resetAction(); + }, + getCurrentAction: function () { + updateDynamicRam( + "getCurrentAction", + getRamCost("bladeburner", "getCurrentAction"), + ); + checkBladeburnerAccess("getCurrentAction"); + return Player.bladeburner.getTypeAndNameFromActionId( + Player.bladeburner.action, + ); + }, + getActionTime: function (type = "", name = "") { + updateDynamicRam( + "getActionTime", + getRamCost("bladeburner", "getActionTime"), + ); + checkBladeburnerAccess("getActionTime"); + try { + return Player.bladeburner.getActionTimeNetscriptFn( + Player, + type, + name, + workerScript, + ); + } catch (e) { + throw makeRuntimeErrorMsg("bladeburner.getActionTime", e); + } + }, + getActionEstimatedSuccessChance: function (type = "", name = "") { + updateDynamicRam( + "getActionEstimatedSuccessChance", + getRamCost("bladeburner", "getActionEstimatedSuccessChance"), + ); + checkBladeburnerAccess("getActionEstimatedSuccessChance"); + try { + return Player.bladeburner.getActionEstimatedSuccessChanceNetscriptFn( + Player, + type, + name, + workerScript, + ); + } catch (e) { + throw makeRuntimeErrorMsg( + "bladeburner.getActionEstimatedSuccessChance", + e, + ); + } + }, + getActionRepGain: function (type = "", name = "", level) { + updateDynamicRam( + "getActionRepGain", + getRamCost("bladeburner", "getActionRepGain"), + ); + checkBladeburnerAccess("getActionRepGain"); + const action = getBladeburnerActionObject( + "getActionRepGain", + type, + name, + ); + let rewardMultiplier; + if (level == null || isNaN(level)) { + rewardMultiplier = Math.pow(action.rewardFac, action.level - 1); + } else { + rewardMultiplier = Math.pow(action.rewardFac, level - 1); + } + + return ( + action.rankGain * + rewardMultiplier * + BitNodeMultipliers.BladeburnerRank + ); + }, + getActionCountRemaining: function (type = "", name = "") { + updateDynamicRam( + "getActionCountRemaining", + getRamCost("bladeburner", "getActionCountRemaining"), + ); + checkBladeburnerAccess("getActionCountRemaining"); + try { + return Player.bladeburner.getActionCountRemainingNetscriptFn( + type, + name, + workerScript, + ); + } catch (e) { + throw makeRuntimeErrorMsg("bladeburner.getActionCountRemaining", e); + } + }, + getActionMaxLevel: function (type = "", name = "") { + updateDynamicRam( + "getActionMaxLevel", + getRamCost("bladeburner", "getActionMaxLevel"), + ); + checkBladeburnerAccess("getActionMaxLevel"); + const action = getBladeburnerActionObject( + "getActionMaxLevel", + type, + name, + ); + return action.maxLevel; + }, + getActionCurrentLevel: function (type = "", name = "") { + updateDynamicRam( + "getActionCurrentLevel", + getRamCost("bladeburner", "getActionCurrentLevel"), + ); + checkBladeburnerAccess("getActionCurrentLevel"); + const action = getBladeburnerActionObject( + "getActionCurrentLevel", + type, + name, + ); + return action.level; + }, + getActionAutolevel: function (type = "", name = "") { + updateDynamicRam( + "getActionAutolevel", + getRamCost("bladeburner", "getActionAutolevel"), + ); + checkBladeburnerAccess("getActionAutolevel"); + const action = getBladeburnerActionObject( + "getActionCurrentLevel", + type, + name, + ); + return action.autoLevel; + }, + setActionAutolevel: function (type = "", name = "", autoLevel = true) { + updateDynamicRam( + "setActionAutolevel", + getRamCost("bladeburner", "setActionAutolevel"), + ); + checkBladeburnerAccess("setActionAutolevel"); + const action = getBladeburnerActionObject( + "setActionAutolevel", + type, + name, + ); + action.autoLevel = autoLevel; + }, + setActionLevel: function (type = "", name = "", level = 1) { + updateDynamicRam( + "setActionLevel", + getRamCost("bladeburner", "setActionLevel"), + ); + checkBladeburnerAccess("setActionLevel"); + const action = getBladeburnerActionObject("setActionLevel", type, name); + if (level < 1 || level > action.maxLevel) { + throw makeRuntimeErrorMsg( + "bladeburner.setActionLevel", + `Level must be between 1 and ${action.maxLevel}, is ${level}`, + ); + } + action.level = level; + }, + getRank: function () { + updateDynamicRam("getRank", getRamCost("bladeburner", "getRank")); + checkBladeburnerAccess("getRank"); + return Player.bladeburner.rank; + }, + getSkillPoints: function () { + updateDynamicRam( + "getSkillPoints", + getRamCost("bladeburner", "getSkillPoints"), + ); + checkBladeburnerAccess("getSkillPoints"); + return Player.bladeburner.skillPoints; + }, + getSkillLevel: function (skillName = "") { + updateDynamicRam( + "getSkillLevel", + getRamCost("bladeburner", "getSkillLevel"), + ); + checkBladeburnerAccess("getSkillLevel"); + try { + return Player.bladeburner.getSkillLevelNetscriptFn( + skillName, + workerScript, + ); + } catch (e) { + throw makeRuntimeErrorMsg("bladeburner.getSkillLevel", e); + } + }, + getSkillUpgradeCost: function (skillName = "") { + updateDynamicRam( + "getSkillUpgradeCost", + getRamCost("bladeburner", "getSkillUpgradeCost"), + ); + checkBladeburnerAccess("getSkillUpgradeCost"); + try { + return Player.bladeburner.getSkillUpgradeCostNetscriptFn( + skillName, + workerScript, + ); + } catch (e) { + throw makeRuntimeErrorMsg("bladeburner.getSkillUpgradeCost", e); + } + }, + upgradeSkill: function (skillName) { + updateDynamicRam( + "upgradeSkill", + getRamCost("bladeburner", "upgradeSkill"), + ); + checkBladeburnerAccess("upgradeSkill"); + try { + return Player.bladeburner.upgradeSkillNetscriptFn( + skillName, + workerScript, + ); + } catch (e) { + throw makeRuntimeErrorMsg("bladeburner.upgradeSkill", e); + } + }, + getTeamSize: function (type = "", name = "") { + updateDynamicRam( + "getTeamSize", + getRamCost("bladeburner", "getTeamSize"), + ); + checkBladeburnerAccess("getTeamSize"); + try { + return Player.bladeburner.getTeamSizeNetscriptFn( + type, + name, + workerScript, + ); + } catch (e) { + throw makeRuntimeErrorMsg("bladeburner.getTeamSize", e); + } + }, + setTeamSize: function (type = "", name = "", size) { + updateDynamicRam( + "setTeamSize", + getRamCost("bladeburner", "setTeamSize"), + ); + checkBladeburnerAccess("setTeamSize"); + try { + return Player.bladeburner.setTeamSizeNetscriptFn( + type, + name, + size, + workerScript, + ); + } catch (e) { + throw makeRuntimeErrorMsg("bladeburner.setTeamSize", e); + } + }, + getCityEstimatedPopulation: function (cityName) { + updateDynamicRam( + "getCityEstimatedPopulation", + getRamCost("bladeburner", "getCityEstimatedPopulation"), + ); + checkBladeburnerAccess("getCityEstimatedPopulation"); + checkBladeburnerCity("getCityEstimatedPopulation", cityName); + return Player.bladeburner.cities[cityName].popEst; + }, + getCityEstimatedCommunities: function (cityName) { + updateDynamicRam( + "getCityEstimatedCommunities", + getRamCost("bladeburner", "getCityEstimatedCommunities"), + ); + checkBladeburnerAccess("getCityEstimatedCommunities"); + checkBladeburnerCity("getCityEstimatedCommunities", cityName); + return Player.bladeburner.cities[cityName].commsEst; + }, + getCityChaos: function (cityName) { + updateDynamicRam( + "getCityChaos", + getRamCost("bladeburner", "getCityChaos"), + ); + checkBladeburnerAccess("getCityChaos"); + checkBladeburnerCity("getCityChaos", cityName); + return Player.bladeburner.cities[cityName].chaos; + }, + getCity: function () { + updateDynamicRam("getCity", getRamCost("bladeburner", "getCity")); + checkBladeburnerAccess("getCityChaos"); + return Player.bladeburner.city; + }, + switchCity: function (cityName) { + updateDynamicRam("switchCity", getRamCost("bladeburner", "switchCity")); + checkBladeburnerAccess("switchCity"); + checkBladeburnerCity("switchCity", cityName); + return (Player.bladeburner.city = cityName); + }, + getStamina: function () { + updateDynamicRam("getStamina", getRamCost("bladeburner", "getStamina")); + checkBladeburnerAccess("getStamina"); + return [Player.bladeburner.stamina, Player.bladeburner.maxStamina]; + }, + joinBladeburnerFaction: function () { + updateDynamicRam( + "joinBladeburnerFaction", + getRamCost("bladeburner", "joinBladeburnerFaction"), + ); + checkBladeburnerAccess("joinBladeburnerFaction", true); + return Player.bladeburner.joinBladeburnerFactionNetscriptFn( + workerScript, + ); + }, + joinBladeburnerDivision: function () { + updateDynamicRam( + "joinBladeburnerDivision", + getRamCost("bladeburner", "joinBladeburnerDivision"), + ); + checkBladeburnerAccess("joinBladeburnerDivision", true); + if (Player.bitNodeN === 7 || SourceFileFlags[7] > 0) { + if (Player.bitNodeN === 8) { + return false; + } + if (Player.bladeburner instanceof Bladeburner) { + return true; // Already member + } else if ( + Player.strength >= 100 && + Player.defense >= 100 && + Player.dexterity >= 100 && + Player.agility >= 100 + ) { + Player.bladeburner = new Bladeburner(Player); + workerScript.log( + "joinBladeburnerDivision", + "You have been accepted into the Bladeburner division", + ); + + const worldHeader = document.getElementById("world-menu-header"); + if (worldHeader instanceof HTMLElement) { + worldHeader.click(); + worldHeader.click(); + } + + return true; + } else { + workerScript.log( + "joinBladeburnerDivision", + "You do not meet the requirements for joining the Bladeburner division", + ); + return false; + } + } + }, + getBonusTime: function () { + updateDynamicRam( + "getBonusTime", + getRamCost("bladeburner", "getBonusTime"), + ); + checkBladeburnerAccess("getBonusTime"); + return Math.round(Player.bladeburner.storedCycles / 5); + }, + }, // End Bladeburner + + // corporation: { + // expandIndustry: function(industryName, divisionName) { + // NewIndustry(Player.corporation, industryName, divisionName); + // }, + // expandCity: function(divisionName, cityName) { + // const division = getDivision(divisionName); + // NewCity(Player.corporation, division, cityName); + // }, + // unlockUpgrade: function(upgradeName) { + // const upgrade = Object.values(CorporationUnlockUpgrades). + // find(upgrade => upgrade[2] === upgradeName); + // if(upgrade === undefined) throw new Error("No upgrade named '${upgradeName}'") + // UnlockUpgrade(Player.corporation, upgrade); + // }, + // levelUpgrade: function(upgradeName) { + // const upgrade = Object.values(CorporationUpgrades). + // find(upgrade => upgrade[4] === upgradeName); + // if(upgrade === undefined) throw new Error("No upgrade named '${upgradeName}'") + // LevelUpgrade(Player.corporation, upgrade); + // }, + // issueDividends: function(percent) { + // IssueDividends(Player.corporation, percent); + // }, + // sellMaterial: function(divisionName, cityName, materialName, amt, price) { + // const material = getMaterial(divisionName, cityName, materialName); + // SellMaterial(material, amt, price); + // }, + // sellProduct: function(divisionName, cityName, productName, amt, price, all) { + // const product = getProduct(divisionName, productName); + // SellProduct(product, cityName, amt, price, all); + // }, + // setSmartSupply: function(divisionName, cityName, enabled) { + // const warehouse = getWarehouse(divisionName, cityName); + // SetSmartSupply(warehouse, enabled); + // }, + // BuyMaterial: function(divisionName, cityName, materialName, amt) { + + // }, + // }, // End Corporation API + + // Coding Contract API + codingcontract: { + attempt: function ( + answer, + fn, + ip = workerScript.serverIp, + { returnReward } = {}, + ) { + updateDynamicRam("attempt", getRamCost("codingcontract", "attempt")); + const contract = getCodingContract("attempt", ip, fn); + + // Convert answer to string. If the answer is a 2D array, then we have to + // manually add brackets for the inner arrays + if (is2DArray(answer)) { + let answerComponents = []; + for (let i = 0; i < answer.length; ++i) { + answerComponents.push(["[", answer[i].toString(), "]"].join("")); + } + + answer = answerComponents.join(","); + } else { + answer = String(answer); + } + + const serv = safeGetServer(ip, "codingcontract.attempt"); + if (contract.isSolution(answer)) { + const reward = Player.gainCodingContractReward( + contract.reward, + contract.getDifficulty(), + ); + workerScript.log( + "attempt", + `Successfully completed Coding Contract '${fn}'. Reward: ${reward}`, + ); + serv.removeContract(fn); + return returnReward ? reward : true; + } else { + ++contract.tries; + if (contract.tries >= contract.getMaxNumTries()) { + workerScript.log( + "attempt", + `Coding Contract attempt '${fn}' failed. Contract is now self-destructing`, + ); + serv.removeContract(fn); + } else { + workerScript.log( + "attempt", + `Coding Contract attempt '${fn}' failed. ${ + contract.getMaxNumTries() - contract.tries + } attempts remaining.`, + ); + } + + return returnReward ? "" : false; + } + }, + getContractType: function (fn, ip = workerScript.serverIp) { + updateDynamicRam( + "getContractType", + getRamCost("codingcontract", "getContractType"), + ); + const contract = getCodingContract("getContractType", ip, fn); + return contract.getType(); + }, + getData: function (fn, ip = workerScript.serverIp) { + updateDynamicRam("getData", getRamCost("codingcontract", "getData")); + const contract = getCodingContract("getData", ip, fn); + const data = contract.getData(); + if (data.constructor === Array) { + // For two dimensional arrays, we have to copy the internal arrays using + // slice() as well. As of right now, no contract has arrays that have + // more than two dimensions + const copy = data.slice(); + for (let i = 0; i < copy.length; ++i) { + if (data[i].constructor === Array) { + copy[i] = data[i].slice(); + } + } + + return copy; + } else { + return data; + } + }, + getDescription: function (fn, ip = workerScript.serverIp) { + updateDynamicRam( + "getDescription", + getRamCost("codingcontract", "getDescription"), + ); + const contract = getCodingContract("getDescription", ip, fn); + return contract.getDescription(); + }, + getNumTriesRemaining: function (fn, ip = workerScript.serverIp) { + updateDynamicRam( + "getNumTriesRemaining", + getRamCost("codingcontract", "getNumTriesRemaining"), + ); + const contract = getCodingContract("getNumTriesRemaining", ip, fn); + return contract.getMaxNumTries() - contract.tries; + }, + }, // End coding contracts + + // Duplicate Sleeve API + sleeve: { + getNumSleeves: function () { + updateDynamicRam( + "getNumSleeves", + getRamCost("sleeve", "getNumSleeves"), + ); + checkSleeveAPIAccess("getNumSleeves"); + return Player.sleeves.length; + }, + setToShockRecovery: function (sleeveNumber = 0) { + updateDynamicRam( + "setToShockRecovery", + getRamCost("sleeve", "setToShockRecovery"), + ); + checkSleeveAPIAccess("setToShockRecovery"); + checkSleeveNumber("setToShockRecovery", sleeveNumber); + return Player.sleeves[sleeveNumber].shockRecovery(Player); + }, + setToSynchronize: function (sleeveNumber = 0) { + updateDynamicRam( + "setToSynchronize", + getRamCost("sleeve", "setToSynchronize"), + ); + checkSleeveAPIAccess("setToSynchronize"); + checkSleeveNumber("setToSynchronize", sleeveNumber); + return Player.sleeves[sleeveNumber].synchronize(Player); + }, + setToCommitCrime: function (sleeveNumber = 0, crimeName = "") { + updateDynamicRam( + "setToCommitCrime", + getRamCost("sleeve", "setToCommitCrime"), + ); + checkSleeveAPIAccess("setToCommitCrime"); + checkSleeveNumber("setToCommitCrime", sleeveNumber); + return Player.sleeves[sleeveNumber].commitCrime(Player, crimeName); + }, + setToUniversityCourse: function ( + sleeveNumber = 0, + universityName = "", + className = "", + ) { + updateDynamicRam( + "setToUniversityCourse", + getRamCost("sleeve", "setToUniversityCourse"), + ); + checkSleeveAPIAccess("setToUniversityCourse"); + checkSleeveNumber("setToUniversityCourse", sleeveNumber); + return Player.sleeves[sleeveNumber].takeUniversityCourse( + Player, + universityName, + className, + ); + }, + travel: function (sleeveNumber = 0, cityName = "") { + updateDynamicRam("travel", getRamCost("sleeve", "travel")); + checkSleeveAPIAccess("travel"); + checkSleeveNumber("travel", sleeveNumber); + return Player.sleeves[sleeveNumber].travel(Player, cityName); + }, + setToCompanyWork: function (sleeveNumber = 0, companyName = "") { + updateDynamicRam( + "setToCompanyWork", + getRamCost("sleeve", "setToCompanyWork"), + ); + checkSleeveAPIAccess("setToCompanyWork"); + checkSleeveNumber("setToCompanyWork", sleeveNumber); + + // Cannot work at the same company that another sleeve is working at + for (let i = 0; i < Player.sleeves.length; ++i) { + if (i === sleeveNumber) { + continue; + } + const other = Player.sleeves[i]; + if ( + other.currentTask === SleeveTaskType.Company && + other.currentTaskLocation === companyName + ) { + throw makeRuntimeErrorMsg( + "sleeve.setToFactionWork", + `Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`, + ); + } + } + + return Player.sleeves[sleeveNumber].workForCompany(Player, companyName); + }, + setToFactionWork: function ( + sleeveNumber = 0, + factionName = "", + workType = "", + ) { + updateDynamicRam( + "setToFactionWork", + getRamCost("sleeve", "setToFactionWork"), + ); + checkSleeveAPIAccess("setToFactionWork"); + checkSleeveNumber("setToFactionWork", sleeveNumber); + + // Cannot work at the same faction that another sleeve is working at + for (let i = 0; i < Player.sleeves.length; ++i) { + if (i === sleeveNumber) { + continue; + } + const other = Player.sleeves[i]; + if ( + other.currentTask === SleeveTaskType.Faction && + other.currentTaskLocation === factionName + ) { + throw makeRuntimeErrorMsg( + "sleeve.setToFactionWork", + `Sleeve ${sleeveNumber} cannot work for faction ${factionName} because Sleeve ${i} is already working for them.`, + ); + } + } + + return Player.sleeves[sleeveNumber].workForFaction( + Player, + factionName, + workType, + ); + }, + setToGymWorkout: function (sleeveNumber = 0, gymName = "", stat = "") { + updateDynamicRam( + "setToGymWorkout", + getRamCost("sleeve", "setToGymWorkout"), + ); + checkSleeveAPIAccess("setToGymWorkout"); + checkSleeveNumber("setToGymWorkout", sleeveNumber); + + return Player.sleeves[sleeveNumber].workoutAtGym(Player, gymName, stat); + }, + getSleeveStats: function (sleeveNumber = 0) { + updateDynamicRam( + "getSleeveStats", + getRamCost("sleeve", "getSleeveStats"), + ); + checkSleeveAPIAccess("getSleeveStats"); + checkSleeveNumber("getSleeveStats", sleeveNumber); + + const sl = Player.sleeves[sleeveNumber]; + return { + shock: 100 - sl.shock, + sync: sl.sync, + hacking_skill: sl.hacking_skill, + strength: sl.strength, + defense: sl.defense, + dexterity: sl.dexterity, + agility: sl.agility, + charisma: sl.charisma, + }; + }, + getTask: function (sleeveNumber = 0) { + updateDynamicRam("getTask", getRamCost("sleeve", "getTask")); + checkSleeveAPIAccess("getTask"); + checkSleeveNumber("getTask", sleeveNumber); + + const sl = Player.sleeves[sleeveNumber]; + return { + task: SleeveTaskType[sl.currentTask], + crime: sl.crimeType, + location: sl.currentTaskLocation, + gymStatType: sl.gymStatType, + factionWorkType: FactionWorkType[sl.factionWorkType], + }; + }, + getInformation: function (sleeveNumber = 0) { + updateDynamicRam( + "getInformation", + getRamCost("sleeve", "getInformation"), + ); + checkSleeveAPIAccess("getInformation"); + checkSleeveNumber("getInformation", sleeveNumber); + + const sl = Player.sleeves[sleeveNumber]; + return { + city: sl.city, + hp: sl.hp, + jobs: Object.keys(Player.jobs), // technically sleeves have the same jobs as the player. + jobTitle: Object.values(Player.jobs), + maxHp: sl.max_hp, + tor: SpecialServerIps.hasOwnProperty("Darkweb Server"), // There's no reason not to give that infomation here as well. Worst case scenario it isn't used. + + mult: { + agility: sl.agility_mult, + agilityExp: sl.agility_exp_mult, + companyRep: sl.company_rep_mult, + crimeMoney: sl.crime_money_mult, + crimeSuccess: sl.crime_success_mult, + defense: sl.defense_mult, + defenseExp: sl.defense_exp_mult, + dexterity: sl.dexterity_mult, + dexterityExp: sl.dexterity_exp_mult, + factionRep: sl.faction_rep_mult, + hacking: sl.hacking_mult, + hackingExp: sl.hacking_exp_mult, + strength: sl.strength_mult, + strengthExp: sl.strength_exp_mult, + workMoney: sl.work_money_mult, + }, + + timeWorked: sl.currentTaskTime, + earningsForSleeves: { + workHackExpGain: sl.earningsForSleeves.hack, + workStrExpGain: sl.earningsForSleeves.str, + workDefExpGain: sl.earningsForSleeves.def, + workDexExpGain: sl.earningsForSleeves.dex, + workAgiExpGain: sl.earningsForSleeves.agi, + workChaExpGain: sl.earningsForSleeves.cha, + workMoneyGain: sl.earningsForSleeves.money, + }, + earningsForPlayer: { + workHackExpGain: sl.earningsForPlayer.hack, + workStrExpGain: sl.earningsForPlayer.str, + workDefExpGain: sl.earningsForPlayer.def, + workDexExpGain: sl.earningsForPlayer.dex, + workAgiExpGain: sl.earningsForPlayer.agi, + workChaExpGain: sl.earningsForPlayer.cha, + workMoneyGain: sl.earningsForPlayer.money, + }, + earningsForTask: { + workHackExpGain: sl.earningsForTask.hack, + workStrExpGain: sl.earningsForTask.str, + workDefExpGain: sl.earningsForTask.def, + workDexExpGain: sl.earningsForTask.dex, + workAgiExpGain: sl.earningsForTask.agi, + workChaExpGain: sl.earningsForTask.cha, + workMoneyGain: sl.earningsForTask.money, + }, + workRepGain: sl.getRepGain(Player), + }; + }, + getSleeveAugmentations: function (sleeveNumber = 0) { + updateDynamicRam( + "getSleeveAugmentations", + getRamCost("sleeve", "getSleeveAugmentations"), + ); + checkSleeveAPIAccess("getSleeveAugmentations"); + checkSleeveNumber("getSleeveAugmentations", sleeveNumber); + + const augs = []; + for ( + let i = 0; + i < Player.sleeves[sleeveNumber].augmentations.length; + i++ + ) { + augs.push(Player.sleeves[sleeveNumber].augmentations[i].name); + } + return augs; + }, + getSleevePurchasableAugs: function (sleeveNumber = 0) { + updateDynamicRam( + "getSleevePurchasableAugs", + getRamCost("sleeve", "getSleevePurchasableAugs"), + ); + checkSleeveAPIAccess("getSleevePurchasableAugs"); + checkSleeveNumber("getSleevePurchasableAugs", sleeveNumber); + + const purchasableAugs = findSleevePurchasableAugs( + Player.sleeves[sleeveNumber], + Player, + ); + const augs = []; + for (let i = 0; i < purchasableAugs.length; i++) { + const aug = purchasableAugs[i]; + augs.push({ + name: aug.name, + cost: aug.startingCost, + }); + } + + return augs; + }, + purchaseSleeveAug: function (sleeveNumber = 0, augName = "") { + updateDynamicRam( + "purchaseSleeveAug", + getRamCost("sleeve", "purchaseSleeveAug"), + ); + checkSleeveAPIAccess("purchaseSleeveAug"); + checkSleeveNumber("purchaseSleeveAug", sleeveNumber); + + const aug = Augmentations[augName]; + if (!aug) { + throw makeRuntimeErrorMsg( + "sleeve.purchaseSleeveAug", + `Invalid aug: ${augName}`, + ); + } + + return Player.sleeves[sleeveNumber].tryBuyAugmentation(Player, aug); + }, + }, // End sleeve + formulas: { + basic: { + calculateSkill: function (exp, mult = 1) { + checkFormulasAccess("basic.calculateSkill", 5); + return calculateSkill(exp, mult); + }, + calculateExp: function (skill, mult = 1) { + checkFormulasAccess("basic.calculateExp", 5); + return calculateExp(skill, mult); + }, + hackChance: function (server, player) { + checkFormulasAccess("basic.hackChance", 5); + return calculateHackingChance(server, player); + }, + hackExp: function (server, player) { + checkFormulasAccess("basic.hackExp", 5); + return calculateHackingExpGain(server, player); + }, + hackPercent: function (server, player) { + checkFormulasAccess("basic.hackPercent", 5); + return calculatePercentMoneyHacked(server, player); + }, + growPercent: function (server, threads, player, cores = 1) { + checkFormulasAccess("basic.growPercent", 5); + return calculateServerGrowth(server, threads, player, cores); + }, + hackTime: function (server, player) { + checkFormulasAccess("basic.hackTime", 5); + return calculateHackingTime(server, player); + }, + growTime: function (server, player) { + checkFormulasAccess("basic.growTime", 5); + return calculateGrowTime(server, player); + }, + weakenTime: function (server, player) { + checkFormulasAccess("basic.weakenTime", 5); + return calculateWeakenTime(server, player); + }, + }, + hacknetNodes: { + moneyGainRate: function (level, ram, cores, mult = 1) { + checkFormulasAccess("hacknetNodes.moneyGainRate", 5); + return calculateMoneyGainRate(level, ram, cores, mult); + }, + levelUpgradeCost: function ( + startingLevel, + extraLevels = 1, + costMult = 1, + ) { + checkFormulasAccess("hacknetNodes.levelUpgradeCost", 5); + return calculateLevelUpgradeCost( + startingLevel, + extraLevels, + costMult, + ); + }, + ramUpgradeCost: function (startingRam, extraLevels = 1, costMult = 1) { + checkFormulasAccess("hacknetNodes.ramUpgradeCost", 5); + return calculateRamUpgradeCost(startingRam, extraLevels, costMult); + }, + coreUpgradeCost: function (startingCore, extraCores = 1, costMult = 1) { + checkFormulasAccess("hacknetNodes.coreUpgradeCost", 5); + return calculateCoreUpgradeCost(startingCore, extraCores, costMult); + }, + hacknetNodeCost: function (n, mult) { + checkFormulasAccess("hacknetNodes.hacknetNodeCost", 5); + return calculateNodeCost(n, mult); + }, + constants: function () { + checkFormulasAccess("hacknetNodes.constants", 5); + return Object.assign({}, HacknetNodeConstants); + }, + }, + hacknetServers: { + hashGainRate: function (level, ramUsed, maxRam, cores, mult = 1) { + checkFormulasAccess("hacknetServers.hashGainRate", 9); + return HScalculateHashGainRate(level, ramUsed, maxRam, cores, mult); + }, + levelUpgradeCost: function ( + startingLevel, + extraLevels = 1, + costMult = 1, + ) { + checkFormulasAccess("hacknetServers.levelUpgradeCost", 9); + return HScalculateLevelUpgradeCost( + startingLevel, + extraLevels, + costMult, + ); + }, + ramUpgradeCost: function (startingRam, extraLevels = 1, costMult = 1) { + checkFormulasAccess("hacknetServers.ramUpgradeCost", 9); + return HScalculateRamUpgradeCost(startingRam, extraLevels, costMult); + }, + coreUpgradeCost: function (startingCore, extraCores = 1, costMult = 1) { + checkFormulasAccess("hacknetServers.coreUpgradeCost", 9); + return HScalculateCoreUpgradeCost(startingCore, extraCores, costMult); + }, + cacheUpgradeCost: function ( + startingCache, + extraCache = 1, + costMult = 1, + ) { + checkFormulasAccess("hacknetServers.cacheUpgradeCost", 9); + return HScalculateCacheUpgradeCost( + startingCache, + extraCache, + costMult, + ); + }, + hashUpgradeCost: function (upgName, level) { + checkFormulasAccess("hacknetServers.hashUpgradeCost", 9); + const upg = Player.hashManager.getUpgrade(upgName); + if (!upg) { + throw makeRuntimeErrorMsg( + "formulas.hacknetServers.calculateHashUpgradeCost", + `Invalid Hash Upgrade: ${upgName}`, + ); + } + return upg.getCost(level); + }, + hacknetServerCost: function (n, mult) { + checkFormulasAccess("hacknetServers.hacknetServerCost", 9); + return HScalculateServerCost(n, mult); + }, + constants: function () { + checkFormulasAccess("hacknetServers.constants", 9); + return Object.assign({}, HacknetServerConstants); + }, + }, + }, // end formulas + heart: { + // Easter egg function + break: function () { + return Player.karma; + }, + }, + exploit: function () { + Player.giveExploit(Exploit.UndocumentedFunctionCall); + }, + bypass: function (doc) { + // reset both fields first + doc.completely_unused_field = undefined; + document.completely_unused_field = undefined; + // set one to true and check that it affected the other. + document.completely_unused_field = true; + if (doc.completely_unused_field && workerScript.ramUsage === 1.6) { + Player.giveExploit(Exploit.Bypass); + } + doc.completely_unused_field = undefined; + document.completely_unused_field = undefined; + }, + flags: function (data) { + data = toNative(data); + // We always want the help flag. + const args = {}; + + for (const d of data) { + let t = String; + if (typeof d[1] === "number") { + t = Number; + } else if (typeof d[1] === "boolean") { + t = Boolean; + } else if (Array.isArray(d[1])) { + t = [String]; + } + const numDashes = d[0].length > 1 ? 2 : 1; + args["-".repeat(numDashes) + d[0]] = t; + } + const ret = libarg(args, { argv: workerScript.args }); + for (const d of data) { + if (!ret.hasOwnProperty("--" + d[0]) || !ret.hasOwnProperty("-" + d[0])) + ret[d[0]] = d[1]; + } + for (const key of Object.keys(ret)) { + if (!key.startsWith("-")) continue; + const value = ret[key]; + delete ret[key]; + const numDashes = key.length === 2 ? 1 : 2; + ret[key.slice(numDashes)] = value; + } + return ret; + }, + }; + + function getFunctionNames(obj) { + const functionNames = []; + for (const [key, value] of Object.entries(obj)) { + if (typeof value == "function") { + functionNames.push(key); + } else if (typeof value == "object") { + functionNames.push(...getFunctionNames(value)); + } } + return functionNames; + } - const possibleLogs = Object.fromEntries([...getFunctionNames(functions)].map(a => [a, true])) + const possibleLogs = Object.fromEntries( + [...getFunctionNames(functions)].map((a) => [a, true]), + ); - return functions; + return functions; } // End NetscriptFunction() export { NetscriptFunctions }; diff --git a/src/NetscriptJSEvaluator.js b/src/NetscriptJSEvaluator.js index cb560b542..3b923253d 100644 --- a/src/NetscriptJSEvaluator.js +++ b/src/NetscriptJSEvaluator.js @@ -3,10 +3,9 @@ import { ScriptUrl } from "./Script/ScriptUrl"; // Makes a blob that contains the code of a given script. export function makeScriptBlob(code) { - return new Blob([code], {type: "text/javascript"}); + return new Blob([code], { type: "text/javascript" }); } - // Begin executing a user JS script, and return a promise that resolves // or rejects when the script finishes. // - script is a script to execute (see Script.js). We depend only on .filename and .code. @@ -16,40 +15,45 @@ export function makeScriptBlob(code) { // When the promise returned by this resolves, we'll have finished // running the main function of the script. export async function executeJSScript(scripts = [], workerScript) { - let loadedModule; - let urls = null; - let script = workerScript.getScript(); - if (shouldCompile(script, scripts)) { - // The URL at the top is the one we want to import. It will - // recursively import all the other modules in the urlStack. - // - // Webpack likes to turn the import into a require, which sort of - // but not really behaves like import. Particularly, it cannot - // load fully dynamic content. So we hide the import from webpack - // by placing it inside an eval call. - script.markUpdated(); - urls = _getScriptUrls(script, scripts, []); - script.url = urls[urls.length - 1].url; - script.module = new Promise(resolve => resolve(eval('import(urls[urls.length - 1].url)'))); - script.dependencies = urls; - } - loadedModule = await script.module; + let loadedModule; + let urls = null; + let script = workerScript.getScript(); + if (shouldCompile(script, scripts)) { + // The URL at the top is the one we want to import. It will + // recursively import all the other modules in the urlStack. + // + // Webpack likes to turn the import into a require, which sort of + // but not really behaves like import. Particularly, it cannot + // load fully dynamic content. So we hide the import from webpack + // by placing it inside an eval call. + script.markUpdated(); + urls = _getScriptUrls(script, scripts, []); + script.url = urls[urls.length - 1].url; + script.module = new Promise((resolve) => + resolve(eval("import(urls[urls.length - 1].url)")), + ); + script.dependencies = urls; + } + loadedModule = await script.module; - let ns = workerScript.env.vars; + let ns = workerScript.env.vars; - try { - // TODO: putting await in a non-async function yields unhelpful - // "SyntaxError: unexpected reserved word" with no line number information. - if (!loadedModule.main) { - throw makeRuntimeRejectMsg(workerScript, `${script.filename} cannot be run because it does not have a main function.`); - } - return loadedModule.main(ns); - } finally { - // Revoke the generated URLs - if (urls != null) { - for (const b in urls) URL.revokeObjectURL(b.url); - } + try { + // TODO: putting await in a non-async function yields unhelpful + // "SyntaxError: unexpected reserved word" with no line number information. + if (!loadedModule.main) { + throw makeRuntimeRejectMsg( + workerScript, + `${script.filename} cannot be run because it does not have a main function.`, + ); } + return loadedModule.main(ns); + } finally { + // Revoke the generated URLs + if (urls != null) { + for (const b in urls) URL.revokeObjectURL(b.url); + } + } } /** Returns whether we should compile the script parameter. @@ -58,17 +62,18 @@ export async function executeJSScript(scripts = [], workerScript) { * @param {Script[]} scripts */ function shouldCompile(script, scripts) { - if (script.module === "") return true; - return script.dependencies.some(dep => { - const depScript = scripts.find(s => s.filename == dep.filename); + if (script.module === "") return true; + return script.dependencies.some((dep) => { + const depScript = scripts.find((s) => s.filename == dep.filename); - // If the script is not present on the server, we should recompile, if only to get any necessary - // compilation errors. - if (!depScript) return true; + // If the script is not present on the server, we should recompile, if only to get any necessary + // compilation errors. + if (!depScript) return true; - const depIsMoreRecent = depScript.moduleSequenceNumber > script.moduleSequenceNumber; - return depIsMoreRecent; - }); + const depIsMoreRecent = + depScript.moduleSequenceNumber > script.moduleSequenceNumber; + return depIsMoreRecent; + }); } // Gets a stack of blob urls, the top/right-most element being @@ -92,51 +97,57 @@ function shouldCompile(script, scripts) { */ // BUG: apparently seen is never consulted. Oops. export function _getScriptUrls(script, scripts, seen) { - // Inspired by: https://stackoverflow.com/a/43834063/91401 - /** @type {ScriptUrl[]} */ - const urlStack = []; - seen.push(script); - try { - // Replace every import statement with an import to a blob url containing - // the corresponding script. E.g. - // - // import {foo} from "bar.js"; - // - // becomes - // - // import {foo} from "blob://" - // - // Where the blob URL contains the script content. - let transformedCode = script.code.replace(/((?:from|import)\s+(?:'|"))(?:\.\/)?([^'"]+)('|")/g, - (unmodified, prefix, filename, suffix) => { - const isAllowedImport = scripts.some(s => s.filename == filename); - if (!isAllowedImport) return unmodified; + // Inspired by: https://stackoverflow.com/a/43834063/91401 + /** @type {ScriptUrl[]} */ + const urlStack = []; + seen.push(script); + try { + // Replace every import statement with an import to a blob url containing + // the corresponding script. E.g. + // + // import {foo} from "bar.js"; + // + // becomes + // + // import {foo} from "blob://" + // + // Where the blob URL contains the script content. + let transformedCode = script.code.replace( + /((?:from|import)\s+(?:'|"))(?:\.\/)?([^'"]+)('|")/g, + (unmodified, prefix, filename, suffix) => { + const isAllowedImport = scripts.some((s) => s.filename == filename); + if (!isAllowedImport) return unmodified; - // Find the corresponding script. - const [importedScript] = scripts.filter(s => s.filename == filename); + // Find the corresponding script. + const [importedScript] = scripts.filter((s) => s.filename == filename); - // Try to get a URL for the requested script and its dependencies. - const urls = _getScriptUrls(importedScript, scripts, seen); + // Try to get a URL for the requested script and its dependencies. + const urls = _getScriptUrls(importedScript, scripts, seen); - // The top url in the stack is the replacement import file for this script. - urlStack.push(...urls); - return [prefix, urls[urls.length - 1].url, suffix].join(''); - }, - ); + // The top url in the stack is the replacement import file for this script. + urlStack.push(...urls); + return [prefix, urls[urls.length - 1].url, suffix].join(""); + }, + ); - // We automatically define a print function() in the NetscriptJS module so that - // accidental calls to window.print() do not bring up the "print screen" dialog - transformedCode += `\n\nfunction print() {throw new Error("Invalid call to window.print(). Did you mean to use Netscript's print()?");}` + // We automatically define a print function() in the NetscriptJS module so that + // accidental calls to window.print() do not bring up the "print screen" dialog + transformedCode += `\n\nfunction print() {throw new Error("Invalid call to window.print(). Did you mean to use Netscript's print()?");}`; - // If we successfully transformed the code, create a blob url for it and - // push that URL onto the top of the stack. - urlStack.push(new ScriptUrl(script.filename, URL.createObjectURL(makeScriptBlob(transformedCode)))); - return urlStack; - } catch (err) { - // If there is an error, we need to clean up the URLs. - for (const url in urlStack) URL.revokeObjectURL(url); - throw err; - } finally { - seen.pop(); - } + // If we successfully transformed the code, create a blob url for it and + // push that URL onto the top of the stack. + urlStack.push( + new ScriptUrl( + script.filename, + URL.createObjectURL(makeScriptBlob(transformedCode)), + ), + ); + return urlStack; + } catch (err) { + // If there is an error, we need to clean up the URLs. + for (const url in urlStack) URL.revokeObjectURL(url); + throw err; + } finally { + seen.pop(); + } } diff --git a/src/NetscriptPort.ts b/src/NetscriptPort.ts index 9dfcb5e48..27576879c 100644 --- a/src/NetscriptPort.ts +++ b/src/NetscriptPort.ts @@ -1,51 +1,51 @@ import { Settings } from "./Settings/Settings"; export class NetscriptPort { - data: any[] = []; + data: any[] = []; - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - write(data: any): any { - this.data.push(data); - if (this.data.length > Settings.MaxPortCapacity) { - return this.data.shift(); - } - return null; + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + write(data: any): any { + this.data.push(data); + if (this.data.length > Settings.MaxPortCapacity) { + return this.data.shift(); } + return null; + } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - tryWrite(data: any): boolean { - if (this.data.length >= Settings.MaxPortCapacity) { - return false; - } - this.data.push(data); - return true; + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + tryWrite(data: any): boolean { + if (this.data.length >= Settings.MaxPortCapacity) { + return false; } + this.data.push(data); + return true; + } - read(): any { - if (this.data.length === 0) { - return "NULL PORT DATA"; - } - return this.data.shift(); + read(): any { + if (this.data.length === 0) { + return "NULL PORT DATA"; } + return this.data.shift(); + } - peek(): any { - if (this.data.length === 0) { - return "NULL PORT DATA"; - } else { - const foo = this.data.slice(); - return foo[0]; - } + peek(): any { + if (this.data.length === 0) { + return "NULL PORT DATA"; + } else { + const foo = this.data.slice(); + return foo[0]; } + } - full(): boolean { - return this.data.length == Settings.MaxPortCapacity; - } + full(): boolean { + return this.data.length == Settings.MaxPortCapacity; + } - empty(): boolean { - return this.data.length === 0; - } + empty(): boolean { + return this.data.length === 0; + } - clear(): void { - this.data.length = 0; - } + clear(): void { + this.data.length = 0; + } } diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index 226c9c57e..1a007b368 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -12,8 +12,8 @@ import { CONSTANTS } from "./Constants"; import { Engine } from "./engine"; import { Interpreter } from "./JSInterpreter"; import { - isScriptErrorMessage, - makeRuntimeRejectMsg, + isScriptErrorMessage, + makeRuntimeRejectMsg, } from "./NetscriptEvaluator"; import { NetscriptFunctions } from "./NetscriptFunctions"; import { executeJSScript } from "./NetscriptJSEvaluator"; @@ -39,236 +39,275 @@ import { simple as walksimple } from "acorn-walk"; // Netscript Ports are instantiated here export const NetscriptPorts = []; for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) { - NetscriptPorts.push(new NetscriptPort()); + NetscriptPorts.push(new NetscriptPort()); } export function prestigeWorkerScripts() { - for (const ws of workerScripts.values()) { - ws.env.stopFlag = true; - killWorkerScript(ws); - } + for (const ws of workerScripts.values()) { + ws.env.stopFlag = true; + killWorkerScript(ws); + } - WorkerScriptStartStopEventEmitter.emitEvent(); - workerScripts.clear(); + WorkerScriptStartStopEventEmitter.emitEvent(); + workerScripts.clear(); } // JS script promises need a little massaging to have the same guarantees as netscript // promises. This does said massaging and kicks the script off. It returns a promise // that resolves or rejects when the corresponding worker script is done. function startNetscript2Script(workerScript) { - workerScript.running = true; + workerScript.running = true; - // The name of the currently running netscript function, to prevent concurrent - // calls to hack, grow, etc. - let runningFn = null; + // The name of the currently running netscript function, to prevent concurrent + // calls to hack, grow, etc. + let runningFn = null; - // We need to go through the environment and wrap each function in such a way that it - // can be called at most once at a time. This will prevent situations where multiple - // hack promises are outstanding, for example. - function wrap(propName, f) { - // This function unfortunately cannot be an async function, because we don't - // know if the original one was, and there's no way to tell. - return function (...args) { - // Wrap every netscript function with a check for the stop flag. - // This prevents cases where we never stop because we are only calling - // netscript functions that don't check this. - // This is not a problem for legacy Netscript because it also checks the - // stop flag in the evaluator. - if (workerScript.env.stopFlag) {throw workerScript;} + // We need to go through the environment and wrap each function in such a way that it + // can be called at most once at a time. This will prevent situations where multiple + // hack promises are outstanding, for example. + function wrap(propName, f) { + // This function unfortunately cannot be an async function, because we don't + // know if the original one was, and there's no way to tell. + return function (...args) { + // Wrap every netscript function with a check for the stop flag. + // This prevents cases where we never stop because we are only calling + // netscript functions that don't check this. + // This is not a problem for legacy Netscript because it also checks the + // stop flag in the evaluator. + if (workerScript.env.stopFlag) { + throw workerScript; + } - if (propName === "sleep") return f(...args); // OK for multiple simultaneous calls to sleep. + if (propName === "sleep") return f(...args); // OK for multiple simultaneous calls to sleep. - const msg = "Concurrent calls to Netscript functions not allowed! " + - "Did you forget to await hack(), grow(), or some other " + - "promise-returning function? (Currently running: %s tried to run: %s)" - if (runningFn) { - workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, sprintf(msg, runningFn, propName), null) - throw workerScript; - } - runningFn = propName; + const msg = + "Concurrent calls to Netscript functions not allowed! " + + "Did you forget to await hack(), grow(), or some other " + + "promise-returning function? (Currently running: %s tried to run: %s)"; + if (runningFn) { + workerScript.errorMessage = makeRuntimeRejectMsg( + workerScript, + sprintf(msg, runningFn, propName), + null, + ); + throw workerScript; + } + runningFn = propName; - // If the function throws an error, clear the runningFn flag first, and then re-throw it - // This allows people to properly catch errors thrown by NS functions without getting - // the concurrent call error above - let result; - try { - result = f(...args); - } catch(e) { - runningFn = null; - throw(e); - } + // If the function throws an error, clear the runningFn flag first, and then re-throw it + // This allows people to properly catch errors thrown by NS functions without getting + // the concurrent call error above + let result; + try { + result = f(...args); + } catch (e) { + runningFn = null; + throw e; + } - if (result && result.finally !== undefined) { - return result.finally(function () { - runningFn = null; - }); - } else { - runningFn = null; - return result; - } - } - } + if (result && result.finally !== undefined) { + return result.finally(function () { + runningFn = null; + }); + } else { + runningFn = null; + return result; + } + }; + } - for (let prop in workerScript.env.vars) { - if (typeof workerScript.env.vars[prop] !== "function") continue; - workerScript.env.vars[prop] = wrap(prop, workerScript.env.vars[prop]); - } + for (let prop in workerScript.env.vars) { + if (typeof workerScript.env.vars[prop] !== "function") continue; + workerScript.env.vars[prop] = wrap(prop, workerScript.env.vars[prop]); + } - // Note: the environment that we pass to the JS script only needs to contain the functions visible - // to that script, which env.vars does at this point. - return executeJSScript(workerScript.getServer().scripts, - workerScript).then(function (mainReturnValue) { - if (mainReturnValue === undefined) return workerScript; - return [mainReturnValue, workerScript]; - }).catch(e => { - if (e instanceof Error) { - workerScript.errorMessage = makeRuntimeRejectMsg( - workerScript, e.message + (e.stack && ("\nstack:\n" + e.stack.toString()) || "")); - throw workerScript; - } else if (isScriptErrorMessage(e)) { - workerScript.errorMessage = e; - throw workerScript; - } - throw e; // Don't know what to do with it, let's rethrow. + // Note: the environment that we pass to the JS script only needs to contain the functions visible + // to that script, which env.vars does at this point. + return executeJSScript(workerScript.getServer().scripts, workerScript) + .then(function (mainReturnValue) { + if (mainReturnValue === undefined) return workerScript; + return [mainReturnValue, workerScript]; + }) + .catch((e) => { + if (e instanceof Error) { + workerScript.errorMessage = makeRuntimeRejectMsg( + workerScript, + e.message + ((e.stack && "\nstack:\n" + e.stack.toString()) || ""), + ); + throw workerScript; + } else if (isScriptErrorMessage(e)) { + workerScript.errorMessage = e; + throw workerScript; + } + throw e; // Don't know what to do with it, let's rethrow. }); } function startNetscript1Script(workerScript) { - const code = workerScript.code; - workerScript.running = true; + const code = workerScript.code; + workerScript.running = true; - //Process imports - var codeWithImports, codeLineOffset; - try { - let importProcessingRes = processNetscript1Imports(code, workerScript); - codeWithImports = importProcessingRes.code; - codeLineOffset = importProcessingRes.lineOffset; - } catch(e) { - dialogBoxCreate("Error processing Imports in " + workerScript.name + ":
    " + e); - workerScript.env.stopFlag = true; - workerScript.running = false; - killWorkerScript(workerScript); - return; - } + //Process imports + var codeWithImports, codeLineOffset; + try { + let importProcessingRes = processNetscript1Imports(code, workerScript); + codeWithImports = importProcessingRes.code; + codeLineOffset = importProcessingRes.lineOffset; + } catch (e) { + dialogBoxCreate( + "Error processing Imports in " + workerScript.name + ":
    " + e, + ); + workerScript.env.stopFlag = true; + workerScript.running = false; + killWorkerScript(workerScript); + return; + } - var interpreterInitialization = function(int, scope) { - //Add the Netscript environment - var ns = NetscriptFunctions(workerScript); - for (let name in ns) { - let entry = ns[name]; - if (typeof entry === "function") { - //Async functions need to be wrapped. See JS-Interpreter documentation - if (name === "hack" || name === "grow" || name === "weaken" || name === "sleep" || - name === "prompt" || name === "manualHack") { - let tempWrapper = function() { - let fnArgs = []; + var interpreterInitialization = function (int, scope) { + //Add the Netscript environment + var ns = NetscriptFunctions(workerScript); + for (let name in ns) { + let entry = ns[name]; + if (typeof entry === "function") { + //Async functions need to be wrapped. See JS-Interpreter documentation + if ( + name === "hack" || + name === "grow" || + name === "weaken" || + name === "sleep" || + name === "prompt" || + name === "manualHack" + ) { + let tempWrapper = function () { + let fnArgs = []; - //All of the Object/array elements are in JSInterpreter format, so - //we have to convert them back to native format to pass them to these fns - for (let i = 0; i < arguments.length-1; ++i) { - if (typeof arguments[i] === 'object' || arguments[i].constructor === Array) { - fnArgs.push(int.pseudoToNative(arguments[i])); - } else { - fnArgs.push(arguments[i]); - } - } - let cb = arguments[arguments.length-1]; - let fnPromise = entry.apply(null, fnArgs); - fnPromise.then(function(res) { - cb(res); - }).catch(function(err) { - console.error(err); - }); - } - int.setProperty(scope, name, int.createAsyncFunction(tempWrapper)); - } else if (name === "sprintf" || name === "vsprintf" || name === "scp" || - name == "write" || name === "read" || name === "tryWrite" || - name === "run" || name === "exec") { - let tempWrapper = function() { - let fnArgs = []; + //All of the Object/array elements are in JSInterpreter format, so + //we have to convert them back to native format to pass them to these fns + for (let i = 0; i < arguments.length - 1; ++i) { + if ( + typeof arguments[i] === "object" || + arguments[i].constructor === Array + ) { + fnArgs.push(int.pseudoToNative(arguments[i])); + } else { + fnArgs.push(arguments[i]); + } + } + let cb = arguments[arguments.length - 1]; + let fnPromise = entry.apply(null, fnArgs); + fnPromise + .then(function (res) { + cb(res); + }) + .catch(function (err) { + console.error(err); + }); + }; + int.setProperty(scope, name, int.createAsyncFunction(tempWrapper)); + } else if ( + name === "sprintf" || + name === "vsprintf" || + name === "scp" || + name == "write" || + name === "read" || + name === "tryWrite" || + name === "run" || + name === "exec" + ) { + let tempWrapper = function () { + let fnArgs = []; - //All of the Object/array elements are in JSInterpreter format, so - //we have to convert them back to native format to pass them to these fns - for (let i = 0; i < arguments.length; ++i) { - if (typeof arguments[i] === 'object' || arguments[i].constructor === Array) { - fnArgs.push(int.pseudoToNative(arguments[i])); - } else { - fnArgs.push(arguments[i]); - } - } + //All of the Object/array elements are in JSInterpreter format, so + //we have to convert them back to native format to pass them to these fns + for (let i = 0; i < arguments.length; ++i) { + if ( + typeof arguments[i] === "object" || + arguments[i].constructor === Array + ) { + fnArgs.push(int.pseudoToNative(arguments[i])); + } else { + fnArgs.push(arguments[i]); + } + } - return entry.apply(null, fnArgs); - } - int.setProperty(scope, name, int.createNativeFunction(tempWrapper)); - } else { - let tempWrapper = function() { - let res = entry.apply(null, arguments); + return entry.apply(null, fnArgs); + }; + int.setProperty(scope, name, int.createNativeFunction(tempWrapper)); + } else { + let tempWrapper = function () { + let res = entry.apply(null, arguments); - if (res == null) { - return res; - } else if (res.constructor === Array || (res === Object(res))) { - //Objects and Arrays must be converted to the interpreter's format - return int.nativeToPseudo(res); - } else { - return res; - } - } - int.setProperty(scope, name, int.createNativeFunction(tempWrapper)); - } + if (res == null) { + return res; + } else if (res.constructor === Array || res === Object(res)) { + //Objects and Arrays must be converted to the interpreter's format + return int.nativeToPseudo(res); } else { - //bladeburner, or anything else - int.setProperty(scope, name, int.nativeToPseudo(entry)); + return res; } + }; + int.setProperty(scope, name, int.createNativeFunction(tempWrapper)); } - - //Add the arguments - int.setProperty(scope, "args", int.nativeToPseudo(workerScript.args)); + } else { + //bladeburner, or anything else + int.setProperty(scope, name, int.nativeToPseudo(entry)); + } + } + + //Add the arguments + int.setProperty(scope, "args", int.nativeToPseudo(workerScript.args)); + }; + + var interpreter; + try { + interpreter = new Interpreter( + codeWithImports, + interpreterInitialization, + codeLineOffset, + ); + } catch (e) { + dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":
    " + e); + workerScript.env.stopFlag = true; + workerScript.running = false; + killWorkerScript(workerScript); + return; + } + + return new Promise(function (resolve, reject) { + function runInterpreter() { + try { + if (workerScript.env.stopFlag) { + return reject(workerScript); + } + + if (interpreter.step()) { + setTimeoutRef(runInterpreter, Settings.CodeInstructionRunTime); + } else { + resolve(workerScript); + } + } catch (e) { + e = e.toString(); + if (!isScriptErrorMessage(e)) { + e = makeRuntimeRejectMsg(workerScript, e); + } + workerScript.errorMessage = e; + return reject(workerScript); + } } - var interpreter; try { - interpreter = new Interpreter(codeWithImports, interpreterInitialization, codeLineOffset); - } catch(e) { - dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":
    " + e); - workerScript.env.stopFlag = true; - workerScript.running = false; - killWorkerScript(workerScript); - return; + runInterpreter(); + } catch (e) { + if (isString(e)) { + workerScript.errorMessage = e; + return reject(workerScript); + } else if (e instanceof WorkerScript) { + return reject(e); + } else { + return reject(workerScript); + } } - - return new Promise(function(resolve, reject) { - function runInterpreter() { - try { - if (workerScript.env.stopFlag) { return reject(workerScript); } - - if (interpreter.step()) { - setTimeoutRef(runInterpreter, Settings.CodeInstructionRunTime); - } else { - resolve(workerScript); - } - } catch(e) { - e = e.toString(); - if (!isScriptErrorMessage(e)) { - e = makeRuntimeRejectMsg(workerScript, e); - } - workerScript.errorMessage = e; - return reject(workerScript); - } - } - - try { - runInterpreter(); - } catch(e) { - if (isString(e)) { - workerScript.errorMessage = e; - return reject(workerScript); - } else if (e instanceof WorkerScript) { - return reject(e); - } else { - return reject(workerScript); - } - } - }); + }); } /* Since the JS Interpreter used for Netscript 1.0 only supports ES5, the keyword @@ -283,130 +322,141 @@ function startNetscript1Script(workerScript) { } */ function processNetscript1Imports(code, workerScript) { - //allowReserved prevents 'import' from throwing error in ES5 - const ast = parse(code, { ecmaVersion: 9, allowReserved: true, sourceType: "module" }); + //allowReserved prevents 'import' from throwing error in ES5 + const ast = parse(code, { + ecmaVersion: 9, + allowReserved: true, + sourceType: "module", + }); - var server = workerScript.getServer(); - if (server == null) { - throw new Error("Failed to find underlying Server object for script"); + var server = workerScript.getServer(); + if (server == null) { + throw new Error("Failed to find underlying Server object for script"); + } + + function getScript(scriptName) { + for (let i = 0; i < server.scripts.length; ++i) { + if (server.scripts[i].filename === scriptName) { + return server.scripts[i]; + } } + return null; + } - function getScript(scriptName) { - for (let i = 0; i < server.scripts.length; ++i) { - if (server.scripts[i].filename === scriptName) { - return server.scripts[i]; + let generatedCode = ""; // Generated Javascript Code + let hasImports = false; + + // Walk over the tree and process ImportDeclaration nodes + walksimple(ast, { + ImportDeclaration: (node) => { + hasImports = true; + let scriptName = node.source.value; + if (scriptName.startsWith("./")) { + scriptName = scriptName.slice(2); + } + let script = getScript(scriptName); + if (script == null) { + throw new Error("'Import' failed due to invalid script: " + scriptName); + } + let scriptAst = parse(script.code, { + ecmaVersion: 9, + allowReserved: true, + sourceType: "module", + }); + + if ( + node.specifiers.length === 1 && + node.specifiers[0].type === "ImportNamespaceSpecifier" + ) { + // import * as namespace from script + let namespace = node.specifiers[0].local.name; + let fnNames = []; //Names only + let fnDeclarations = []; //FunctionDeclaration Node objects + walksimple(scriptAst, { + FunctionDeclaration: (node) => { + fnNames.push(node.id.name); + fnDeclarations.push(node); + }, + }); + + //Now we have to generate the code that would create the namespace + generatedCode += + "var " + namespace + ";\n" + "(function (namespace) {\n"; + + //Add the function declarations + fnDeclarations.forEach((fn) => { + generatedCode += generate(fn); + generatedCode += "\n"; + }); + + //Add functions to namespace + fnNames.forEach((fnName) => { + generatedCode += "namespace." + fnName + " = " + fnName; + generatedCode += "\n"; + }); + + //Finish + generatedCode += + "})(" + namespace + " || " + "(" + namespace + " = {}));\n"; + } else { + //import {...} from script + + //Get array of all fns to import + let fnsToImport = []; + node.specifiers.forEach((e) => { + fnsToImport.push(e.local.name); + }); + + //Walk through script and get FunctionDeclaration code for all specified fns + let fnDeclarations = []; + walksimple(scriptAst, { + FunctionDeclaration: (node) => { + if (fnsToImport.includes(node.id.name)) { + fnDeclarations.push(node); } - } - return null; + }, + }); + + //Convert FunctionDeclarations into code + fnDeclarations.forEach((fn) => { + generatedCode += generate(fn); + generatedCode += "\n"; + }); + } + }, + }); + + //If there are no imports, just return the original code + if (!hasImports) { + return { code: code, lineOffset: 0 }; + } + + //Remove ImportDeclarations from AST. These ImportDeclarations must be in top-level + var linesRemoved = 0; + if (ast.type !== "Program" || ast.body == null) { + throw new Error("Code could not be properly parsed"); + } + for (let i = ast.body.length - 1; i >= 0; --i) { + if (ast.body[i].type === "ImportDeclaration") { + ast.body.splice(i, 1); + ++linesRemoved; } + } - let generatedCode = ""; // Generated Javascript Code - let hasImports = false; + //Calculated line offset + var lineOffset = (generatedCode.match(/\n/g) || []).length - linesRemoved; - // Walk over the tree and process ImportDeclaration nodes - walksimple(ast, { - ImportDeclaration: (node) => { - hasImports = true; - let scriptName = node.source.value; - if (scriptName.startsWith("./")) { - scriptName = scriptName.slice(2); - } - let script = getScript(scriptName); - if (script == null) { - throw new Error("'Import' failed due to invalid script: " + scriptName); - } - let scriptAst = parse(script.code, { ecmaVersion:9, allowReserved:true, sourceType:"module" }); + //Convert the AST back into code + code = generate(ast); - if (node.specifiers.length === 1 && node.specifiers[0].type === "ImportNamespaceSpecifier") { - // import * as namespace from script - let namespace = node.specifiers[0].local.name; - let fnNames = []; //Names only - let fnDeclarations = []; //FunctionDeclaration Node objects - walksimple(scriptAst, { - FunctionDeclaration: (node) => { - fnNames.push(node.id.name); - fnDeclarations.push(node); - }, - }); + //Add the imported code and re-generate in ES5 (JS Interpreter for NS1 only supports ES5); + code = generatedCode + code; - //Now we have to generate the code that would create the namespace - generatedCode += - "var " + namespace + ";\n" + - "(function (namespace) {\n"; - - //Add the function declarations - fnDeclarations.forEach((fn) => { - generatedCode += generate(fn); - generatedCode += "\n"; - }); - - //Add functions to namespace - fnNames.forEach((fnName) => { - generatedCode += ("namespace." + fnName + " = " + fnName); - generatedCode += "\n"; - }); - - //Finish - generatedCode += ( - "})(" + namespace + " || " + "(" + namespace + " = {}));\n" - ) - } else { - //import {...} from script - - //Get array of all fns to import - let fnsToImport = []; - node.specifiers.forEach((e) => { - fnsToImport.push(e.local.name); - }); - - //Walk through script and get FunctionDeclaration code for all specified fns - let fnDeclarations = []; - walksimple(scriptAst, { - FunctionDeclaration: (node) => { - if (fnsToImport.includes(node.id.name)) { - fnDeclarations.push(node); - } - }, - }); - - //Convert FunctionDeclarations into code - fnDeclarations.forEach((fn) => { - generatedCode += generate(fn); - generatedCode += "\n"; - }); - } - }, - }); - - //If there are no imports, just return the original code - if (!hasImports) {return {code:code, lineOffset:0};} - - //Remove ImportDeclarations from AST. These ImportDeclarations must be in top-level - var linesRemoved = 0; - if (ast.type !== "Program" || ast.body == null) { - throw new Error("Code could not be properly parsed"); - } - for (let i = ast.body.length-1; i >= 0; --i) { - if (ast.body[i].type === "ImportDeclaration") { - ast.body.splice(i, 1); - ++linesRemoved; - } - } - - //Calculated line offset - var lineOffset = (generatedCode.match(/\n/g) || []).length - linesRemoved; - - //Convert the AST back into code - code = generate(ast); - - //Add the imported code and re-generate in ES5 (JS Interpreter for NS1 only supports ES5); - code = generatedCode + code; - - var res = { - code: code, - lineOffset: lineOffset, - } - return res; + var res = { + code: code, + lineOffset: lineOffset, + }; + return res; } /** @@ -418,17 +468,17 @@ function processNetscript1Imports(code, workerScript) { * @returns {number} pid of started script */ export function startWorkerScript(runningScript, server, parent) { - if (createAndAddWorkerScript(runningScript, server, parent)) { - // Push onto runningScripts. - // This has to come after createAndAddWorkerScript() because that fn updates RAM usage - server.runScript(runningScript, Player.hacknet_node_money_mult); + if (createAndAddWorkerScript(runningScript, server, parent)) { + // Push onto runningScripts. + // This has to come after createAndAddWorkerScript() because that fn updates RAM usage + server.runScript(runningScript, Player.hacknet_node_money_mult); - // Once the WorkerScript is constructed in createAndAddWorkerScript(), the RunningScript - // object should have a PID assigned to it, so we return that - return runningScript.pid; - } + // Once the WorkerScript is constructed in createAndAddWorkerScript(), the RunningScript + // object should have a PID assigned to it, so we return that + return runningScript.pid; + } - return 0; + return 0; } /** @@ -439,124 +489,146 @@ export function startWorkerScript(runningScript, server, parent) { * returns {boolean} indicating whether or not the workerScript was successfully added */ export function createAndAddWorkerScript(runningScriptObj, server, parent) { - // Update server's ram usage - let threads = 1; - if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) { - threads = runningScriptObj.threads; - } else { - runningScriptObj.threads = 1; - } - const ramUsage = roundToTwo(getRamUsageFromRunningScript(runningScriptObj) * threads); - const ramAvailable = server.maxRam - server.ramUsed; - if (ramUsage > ramAvailable) { - dialogBoxCreate( - `Not enough RAM to run script ${runningScriptObj.filename} with args ` + - `${arrayToString(runningScriptObj.args)}. This likely occurred because you re-loaded ` + - `the game and the script's RAM usage increased (either because of an update to the game or ` + - `your changes to the script.)`, - ); - return false; - } - server.ramUsed = roundToTwo(server.ramUsed + ramUsage); + // Update server's ram usage + let threads = 1; + if (runningScriptObj.threads && !isNaN(runningScriptObj.threads)) { + threads = runningScriptObj.threads; + } else { + runningScriptObj.threads = 1; + } + const ramUsage = roundToTwo( + getRamUsageFromRunningScript(runningScriptObj) * threads, + ); + const ramAvailable = server.maxRam - server.ramUsed; + if (ramUsage > ramAvailable) { + dialogBoxCreate( + `Not enough RAM to run script ${runningScriptObj.filename} with args ` + + `${arrayToString( + runningScriptObj.args, + )}. This likely occurred because you re-loaded ` + + `the game and the script's RAM usage increased (either because of an update to the game or ` + + `your changes to the script.)`, + ); + return false; + } + server.ramUsed = roundToTwo(server.ramUsed + ramUsage); - // Get the pid - const pid = generateNextPid(); - if (pid === -1) { - throw new Error( - `Failed to start script because could not find available PID. This is most ` + - `because you have too many scripts running.`, - ); + // Get the pid + const pid = generateNextPid(); + if (pid === -1) { + throw new Error( + `Failed to start script because could not find available PID. This is most ` + + `because you have too many scripts running.`, + ); + } + + // Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying + // RunningScript's PID as well + const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions); + s.ramUsage = ramUsage; + + // Add the WorkerScript to the global pool + workerScripts.set(pid, s); + WorkerScriptStartStopEventEmitter.emitEvent(); + + // Start the script's execution + let p = null; // Script's resulting promise + if (s.name.endsWith(".js") || s.name.endsWith(".ns")) { + p = startNetscript2Script(s); + } else { + p = startNetscript1Script(s); + if (!(p instanceof Promise)) { + return false; + } + } + + // Once the code finishes (either resolved or rejected, doesnt matter), set its + // running status to false + p.then(function (w) { + // On natural death, the earnings are transfered to the parent if it still exists. + if (parent && parent.running) { + parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained; + parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade; } - // Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying - // RunningScript's PID as well - const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions); - s.ramUsage = ramUsage; - - // Add the WorkerScript to the global pool - workerScripts.set(pid, s); - WorkerScriptStartStopEventEmitter.emitEvent(); - - // Start the script's execution - let p = null; // Script's resulting promise - if (s.name.endsWith(".js") || s.name.endsWith(".ns")) { - p = startNetscript2Script(s); - } else { - p = startNetscript1Script(s); - if (!(p instanceof Promise)) { return false; } + // If the WorkerScript is no longer "running", then this means its execution was + // already stopped somewhere else (maybe by something like exit()). This prevents + // the script from being cleaned up twice + if (!w.running) { + return; } - // Once the code finishes (either resolved or rejected, doesnt matter), set its - // running status to false - p.then(function(w) { - // On natural death, the earnings are transfered to the parent if it still exists. - if(parent && parent.running) { - parent.scriptRef.onlineExpGained += runningScriptObj.onlineExpGained; - parent.scriptRef.onlineMoneyMade += runningScriptObj.onlineMoneyMade; + killWorkerScript(s); + w.log("", "Script finished running"); + }).catch(function (w) { + if (w instanceof Error) { + dialogBoxCreate( + "Script runtime unknown error. This is a bug please contact game developer", + ); + console.error( + "Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + + w.toString(), + ); + return; + } else if (w instanceof WorkerScript) { + if (isScriptErrorMessage(w.errorMessage)) { + const errorTextArray = w.errorMessage.split("|"); + if (errorTextArray.length != 4) { + console.error( + "ERROR: Something wrong with Error text in evaluator...", + ); + console.error("Error text: " + errorText); + return; } + const serverIp = errorTextArray[1]; + const scriptName = errorTextArray[2]; + const errorMsg = errorTextArray[3]; - // If the WorkerScript is no longer "running", then this means its execution was - // already stopped somewhere else (maybe by something like exit()). This prevents - // the script from being cleaned up twice - if (!w.running) { return; } - - killWorkerScript(s); - w.log("", "Script finished running"); - }).catch(function(w) { - if (w instanceof Error) { - dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); - console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString()); - return; - } else if (w instanceof WorkerScript) { - if (isScriptErrorMessage(w.errorMessage)) { - const errorTextArray = w.errorMessage.split("|"); - if (errorTextArray.length != 4) { - console.error("ERROR: Something wrong with Error text in evaluator..."); - console.error("Error text: " + errorText); - return; - } - const serverIp = errorTextArray[1]; - const scriptName = errorTextArray[2]; - const errorMsg = errorTextArray[3]; - - let msg = `RUNTIME ERROR
    ${scriptName}@${serverIp}
    ` - if (w.args.length > 0) { - msg += `Args: ${arrayToString(w.args)}
    ` - } - msg += "
    "; - msg += errorMsg; - - dialogBoxCreate(msg); - w.log("", "Script crashed with runtime error"); - } else { - w.log("", "Script killed"); - return; // Already killed, so stop here - } - w.running = false; - w.env.stopFlag = true; - } else if (isScriptErrorMessage(w)) { - dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); - console.error("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString()); - return; - } else { - dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev"); - console.error(w); + let msg = `RUNTIME ERROR
    ${scriptName}@${serverIp}
    `; + if (w.args.length > 0) { + msg += `Args: ${arrayToString(w.args)}
    `; } + msg += "
    "; + msg += errorMsg; - killWorkerScript(s); - }); + dialogBoxCreate(msg); + w.log("", "Script crashed with runtime error"); + } else { + w.log("", "Script killed"); + return; // Already killed, so stop here + } + w.running = false; + w.env.stopFlag = true; + } else if (isScriptErrorMessage(w)) { + dialogBoxCreate( + "Script runtime unknown error. This is a bug please contact game developer", + ); + console.error( + "ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + + w.toString(), + ); + return; + } else { + dialogBoxCreate( + "An unknown script died for an unknown reason. This is a bug please contact game dev", + ); + console.error(w); + } - return true; + killWorkerScript(s); + }); + + return true; } /** * Updates the online running time stat of all running scripts */ export function updateOnlineScriptTimes(numCycles = 1) { - var time = (numCycles * Engine._idleSpeed) / 1000; //seconds - for (const ws of workerScripts.values()) { - ws.scriptRef.onlineRunningTime += time; - } + var time = (numCycles * Engine._idleSpeed) / 1000; //seconds + for (const ws of workerScripts.values()) { + ws.scriptRef.onlineRunningTime += time; + } } /** @@ -564,93 +636,128 @@ export function updateOnlineScriptTimes(numCycles = 1) { * into worker scripts so that they will start running */ export function loadAllRunningScripts() { - let skipScriptLoad = (window.location.href.toLowerCase().indexOf("?noscripts") !== -1); - if (skipScriptLoad) { console.info("Skipping the load of any scripts during startup"); } - for (const property in AllServers) { - if (AllServers.hasOwnProperty(property)) { - const server = AllServers[property]; + let skipScriptLoad = + window.location.href.toLowerCase().indexOf("?noscripts") !== -1; + if (skipScriptLoad) { + console.info("Skipping the load of any scripts during startup"); + } + for (const property in AllServers) { + if (AllServers.hasOwnProperty(property)) { + const server = AllServers[property]; - // Reset each server's RAM usage to 0 - server.ramUsed = 0; + // Reset each server's RAM usage to 0 + server.ramUsed = 0; - // Reset modules on all scripts - for (let i = 0; i < server.scripts.length; ++i) { - server.scripts[i].markUpdated(); - } + // Reset modules on all scripts + for (let i = 0; i < server.scripts.length; ++i) { + server.scripts[i].markUpdated(); + } - if (skipScriptLoad) { - // Start game with no scripts - server.runningScripts.length = 0; - } else { - for (let j = 0; j < server.runningScripts.length; ++j) { - createAndAddWorkerScript(server.runningScripts[j], server); + if (skipScriptLoad) { + // Start game with no scripts + server.runningScripts.length = 0; + } else { + for (let j = 0; j < server.runningScripts.length; ++j) { + createAndAddWorkerScript(server.runningScripts[j], server); - // Offline production - scriptCalculateOfflineProduction(server.runningScripts[j]); - } - } - } - } + // Offline production + scriptCalculateOfflineProduction(server.runningScripts[j]); + } + } + } + } } /** * Run a script from inside another script (run(), exec(), spawn(), etc.) */ -export function runScriptFromScript(caller, server, scriptname, args, workerScript, threads=1) { - // Sanitize arguments - if (!(workerScript instanceof WorkerScript)) { - return 0; - } - - if (typeof scriptname !== "string" || !Array.isArray(args)) { - workerScript.log(caller, `Invalid arguments: scriptname='${scriptname} args='${ags}'`); - console.error(`runScriptFromScript() failed due to invalid arguments`); - return 0; - } - - // Check if the script is already running - let runningScriptObj = server.getRunningScript(scriptname, args); - if (runningScriptObj != null) { - workerScript.log(caller, `'${scriptname}' is already running on '${server.hostname}'`); - return 0; - } - - // 'null/undefined' arguments are not allowed - for (let i = 0; i < args.length; ++i) { - if (args[i] == null) { - workerScript.log(caller, "Cannot execute a script with null/undefined as an argument"); - return 0; - } - } - - // Check if the script exists and if it does run it - for (let i = 0; i < server.scripts.length; ++i) { - if (server.scripts[i].filename === scriptname) { - // Check for admin rights and that there is enough RAM availble to run - const script = server.scripts[i]; - let ramUsage = script.ramUsage; - threads = Math.round(Number(threads)); - if (threads === 0) { return 0; } - ramUsage = ramUsage * threads; - const ramAvailable = server.maxRam - server.ramUsed; - - if (server.hasAdminRights == false) { - workerScript.log(caller, `You do not have root access on '${server.hostname}'`); - return 0; - } else if (ramUsage > ramAvailable){ - workerScript.log(caller, `Cannot run script '${scriptname}' (t=${threads}) on '${server.hostname}' because there is not enough available RAM!`); - return 0; - } else { - // Able to run script - workerScript.log(caller, `'${scriptname}' on '${server.hostname}' with ${threads} threads and args: ${arrayToString(args)}.`); - let runningScriptObj = new RunningScript(script, args); - runningScriptObj.threads = threads; - - return startWorkerScript(runningScriptObj, server, workerScript); - } - } - } - - workerScript.log(caller, `Could not find script '${scriptname}' on '${server.hostname}'`); +export function runScriptFromScript( + caller, + server, + scriptname, + args, + workerScript, + threads = 1, +) { + // Sanitize arguments + if (!(workerScript instanceof WorkerScript)) { return 0; + } + + if (typeof scriptname !== "string" || !Array.isArray(args)) { + workerScript.log( + caller, + `Invalid arguments: scriptname='${scriptname} args='${ags}'`, + ); + console.error(`runScriptFromScript() failed due to invalid arguments`); + return 0; + } + + // Check if the script is already running + let runningScriptObj = server.getRunningScript(scriptname, args); + if (runningScriptObj != null) { + workerScript.log( + caller, + `'${scriptname}' is already running on '${server.hostname}'`, + ); + return 0; + } + + // 'null/undefined' arguments are not allowed + for (let i = 0; i < args.length; ++i) { + if (args[i] == null) { + workerScript.log( + caller, + "Cannot execute a script with null/undefined as an argument", + ); + return 0; + } + } + + // Check if the script exists and if it does run it + for (let i = 0; i < server.scripts.length; ++i) { + if (server.scripts[i].filename === scriptname) { + // Check for admin rights and that there is enough RAM availble to run + const script = server.scripts[i]; + let ramUsage = script.ramUsage; + threads = Math.round(Number(threads)); + if (threads === 0) { + return 0; + } + ramUsage = ramUsage * threads; + const ramAvailable = server.maxRam - server.ramUsed; + + if (server.hasAdminRights == false) { + workerScript.log( + caller, + `You do not have root access on '${server.hostname}'`, + ); + return 0; + } else if (ramUsage > ramAvailable) { + workerScript.log( + caller, + `Cannot run script '${scriptname}' (t=${threads}) on '${server.hostname}' because there is not enough available RAM!`, + ); + return 0; + } else { + // Able to run script + workerScript.log( + caller, + `'${scriptname}' on '${ + server.hostname + }' with ${threads} threads and args: ${arrayToString(args)}.`, + ); + let runningScriptObj = new RunningScript(script, args); + runningScriptObj.threads = threads; + + return startWorkerScript(runningScriptObj, server, workerScript); + } + } + } + + workerScript.log( + caller, + `Could not find script '${scriptname}' on '${server.hostname}'`, + ); + return 0; } diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index d8c01f64b..ecc8fa439 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -8,185 +8,190 @@ import { Sleeve } from "./Sleeve/Sleeve"; import { IMap } from "../types"; -import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation"; -import { Company } from "../Company/Company"; -import { CompanyPosition } from "../Company/CompanyPosition"; -import { CityName } from "../Locations/data/CityNames"; -import { Faction } from "../Faction/Faction"; -import { HashManager } from "../Hacknet/HashManager"; -import { HacknetNode } from "../Hacknet/HacknetNode"; -import { LocationName } from "../Locations/data/LocationNames"; -import { Server } from "../Server/Server"; -import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile"; -import { MoneySourceTracker } from "../utils/MoneySourceTracker"; -import { Exploit } from "../Exploits/Exploit"; -import { ICorporation } from "../Corporation/ICorporation"; +import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation"; +import { Company } from "../Company/Company"; +import { CompanyPosition } from "../Company/CompanyPosition"; +import { CityName } from "../Locations/data/CityNames"; +import { Faction } from "../Faction/Faction"; +import { HashManager } from "../Hacknet/HashManager"; +import { HacknetNode } from "../Hacknet/HacknetNode"; +import { LocationName } from "../Locations/data/LocationNames"; +import { Server } from "../Server/Server"; +import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile"; +import { MoneySourceTracker } from "../utils/MoneySourceTracker"; +import { Exploit } from "../Exploits/Exploit"; +import { ICorporation } from "../Corporation/ICorporation"; export interface IPlayer { - // Class members - augmentations: IPlayerOwnedAugmentation[]; - bladeburner: any; - bitNodeN: number; - city: CityName; - companyName: string; - corporation: ICorporation; - currentServer: string; - factions: string[]; - factionInvitations: string[]; - firstProgramAvailable: boolean; - firstTimeTraveled: boolean; - hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server - has4SData: boolean; - has4SDataTixApi: boolean; - hashManager: HashManager; - hasTixApiAccess: boolean; - hasWseAccount: boolean; - homeComputer: string; - hp: number; - jobs: IMap; - init: () => void; - isWorking: boolean; - karma: number; - numPeopleKilled: number; - location: LocationName; - max_hp: number; - money: any; - moneySourceA: MoneySourceTracker; - moneySourceB: MoneySourceTracker; - playtimeSinceLastAug: number; - playtimeSinceLastBitnode: number; - purchasedServers: any[]; - queuedAugmentations: IPlayerOwnedAugmentation[]; - resleeves: Resleeve[]; - scriptProdSinceLastAug: number; - sleeves: Sleeve[]; - sleevesFromCovenant: number; - sourceFiles: IPlayerOwnedSourceFile[]; - exploits: Exploit[]; - totalPlaytime: number; + // Class members + augmentations: IPlayerOwnedAugmentation[]; + bladeburner: any; + bitNodeN: number; + city: CityName; + companyName: string; + corporation: ICorporation; + currentServer: string; + factions: string[]; + factionInvitations: string[]; + firstProgramAvailable: boolean; + firstTimeTraveled: boolean; + hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server + has4SData: boolean; + has4SDataTixApi: boolean; + hashManager: HashManager; + hasTixApiAccess: boolean; + hasWseAccount: boolean; + homeComputer: string; + hp: number; + jobs: IMap; + init: () => void; + isWorking: boolean; + karma: number; + numPeopleKilled: number; + location: LocationName; + max_hp: number; + money: any; + moneySourceA: MoneySourceTracker; + moneySourceB: MoneySourceTracker; + playtimeSinceLastAug: number; + playtimeSinceLastBitnode: number; + purchasedServers: any[]; + queuedAugmentations: IPlayerOwnedAugmentation[]; + resleeves: Resleeve[]; + scriptProdSinceLastAug: number; + sleeves: Sleeve[]; + sleevesFromCovenant: number; + sourceFiles: IPlayerOwnedSourceFile[]; + exploits: Exploit[]; + totalPlaytime: number; - // Stats - hacking_skill: number; - strength: number; - defense: number; - dexterity: number; - agility: number; - charisma: number; - intelligence: number; + // Stats + hacking_skill: number; + strength: number; + defense: number; + dexterity: number; + agility: number; + charisma: number; + intelligence: number; - // Experience - hacking_exp: number; - strength_exp: number; - defense_exp: number; - dexterity_exp: number; - agility_exp: number; - charisma_exp: number; + // Experience + hacking_exp: number; + strength_exp: number; + defense_exp: number; + dexterity_exp: number; + agility_exp: number; + charisma_exp: number; - // Multipliers - hacking_chance_mult: number; - hacking_speed_mult: number; - hacking_money_mult: number; - hacking_grow_mult: number; - hacking_mult: number; - hacking_exp_mult: number; - strength_mult: number; - strength_exp_mult: number; - defense_mult: number; - defense_exp_mult: number; - dexterity_mult: number; - dexterity_exp_mult: number; - agility_mult: number; - agility_exp_mult: number; - charisma_mult: number; - charisma_exp_mult: number; - hacknet_node_money_mult: number; - hacknet_node_purchase_cost_mult: number; - hacknet_node_ram_cost_mult: number; - hacknet_node_core_cost_mult: number; - hacknet_node_level_cost_mult: number; - company_rep_mult: number; - faction_rep_mult: number; - work_money_mult: number; - crime_success_mult: number; - crime_money_mult: number; - bladeburner_max_stamina_mult: number; - bladeburner_stamina_gain_mult: number; - bladeburner_analysis_mult: number; - bladeburner_success_chance_mult: number; + // Multipliers + hacking_chance_mult: number; + hacking_speed_mult: number; + hacking_money_mult: number; + hacking_grow_mult: number; + hacking_mult: number; + hacking_exp_mult: number; + strength_mult: number; + strength_exp_mult: number; + defense_mult: number; + defense_exp_mult: number; + dexterity_mult: number; + dexterity_exp_mult: number; + agility_mult: number; + agility_exp_mult: number; + charisma_mult: number; + charisma_exp_mult: number; + hacknet_node_money_mult: number; + hacknet_node_purchase_cost_mult: number; + hacknet_node_ram_cost_mult: number; + hacknet_node_core_cost_mult: number; + hacknet_node_level_cost_mult: number; + company_rep_mult: number; + faction_rep_mult: number; + work_money_mult: number; + crime_success_mult: number; + crime_money_mult: number; + bladeburner_max_stamina_mult: number; + bladeburner_stamina_gain_mult: number; + bladeburner_analysis_mult: number; + bladeburner_success_chance_mult: number; - // Methods - applyForAgentJob(sing?: boolean): boolean | void; - applyForBusinessConsultantJob(sing?: boolean): boolean | void; - applyForBusinessJob(sing?: boolean): boolean | void; - applyForEmployeeJob(sing?: boolean): boolean | void; - applyForItJob(sing?: boolean): boolean | void; - applyForJob(entryPosType: CompanyPosition, sing?: boolean): boolean | void; - applyForNetworkEngineerJob(sing?: boolean): boolean | void; - applyForPartTimeEmployeeJob(sing?: boolean): boolean | void; - applyForPartTimeWaiterJob(sing?: boolean): boolean | void; - applyForSecurityEngineerJob(sing?: boolean): boolean | void; - applyForSecurityJob(sing?: boolean): boolean | void; - applyForSoftwareConsultantJob(sing?: boolean): boolean | void; - applyForSoftwareJob(sing?: boolean): boolean | void; - applyForWaiterJob(sing?: boolean): boolean | void; - canAccessBladeburner(): boolean; - canAccessCorporation(): boolean; - canAccessGang(): boolean; - canAccessResleeving(): boolean; - canAfford(cost: number): boolean; - gainHackingExp(exp: number): void; - gainStrengthExp(exp: number): void; - gainDefenseExp(exp: number): void; - gainDexterityExp(exp: number): void; - gainAgilityExp(exp: number): void; - gainCharismaExp(exp: number): void; - gainIntelligenceExp(exp: number): void; - gainMoney(money: number): void; - getCurrentServer(): Server; - getGangFaction(): Faction; - getGangName(): string; - getHomeComputer(): Server; - getNextCompanyPosition(company: Company, entryPosType: CompanyPosition): CompanyPosition; - getUpgradeHomeRamCost(): number; - gotoLocation(to: LocationName): boolean; - hasCorporation(): boolean; - hasGangWith(facName: string): boolean; - hasTorRouter(): boolean; - hasProgram(program: string): boolean; - inBladeburner(): boolean; - inGang(): boolean; - isQualified(company: Company, position: CompanyPosition): boolean; - loseMoney(money: number): void; - reapplyAllAugmentations(resetMultipliers: boolean): void; - reapplyAllSourceFiles(): void; - regenerateHp(amt: number): void; - recordMoneySource(amt: number, source: string): void; - setMoney(amt: number): void; - singularityStopWork(): void; - startBladeburner(p: any): void; - startClass(costMult: number, expMult: number, className: string): void; - startCorporation(corpName: string, additionalShares?: number): void; - startCrime(crimeType: string, - hackExp: number, - strExp: number, - defExp: number, - dexExp: number, - agiExp: number, - chaExp: number, - money: number, - time: number, - singParams: any): void; - startFactionFieldWork(faction: Faction): void; - startFactionHackWork(faction: Faction): void; - startFactionSecurityWork(faction: Faction): void; - startGang(facName: string, isHacking: boolean): void; - startWork(companyName: string): void; - startWorkPartTime(companyName: string): void; - takeDamage(amt: number): boolean; - travel(to: CityName): boolean; - giveExploit(exploit: Exploit): void; - queryStatFromString(str: string): number; - getIntelligenceBonus(weight: number): number; - getCasinoWinnings(): number; - quitJob(company: string): void; + // Methods + applyForAgentJob(sing?: boolean): boolean | void; + applyForBusinessConsultantJob(sing?: boolean): boolean | void; + applyForBusinessJob(sing?: boolean): boolean | void; + applyForEmployeeJob(sing?: boolean): boolean | void; + applyForItJob(sing?: boolean): boolean | void; + applyForJob(entryPosType: CompanyPosition, sing?: boolean): boolean | void; + applyForNetworkEngineerJob(sing?: boolean): boolean | void; + applyForPartTimeEmployeeJob(sing?: boolean): boolean | void; + applyForPartTimeWaiterJob(sing?: boolean): boolean | void; + applyForSecurityEngineerJob(sing?: boolean): boolean | void; + applyForSecurityJob(sing?: boolean): boolean | void; + applyForSoftwareConsultantJob(sing?: boolean): boolean | void; + applyForSoftwareJob(sing?: boolean): boolean | void; + applyForWaiterJob(sing?: boolean): boolean | void; + canAccessBladeburner(): boolean; + canAccessCorporation(): boolean; + canAccessGang(): boolean; + canAccessResleeving(): boolean; + canAfford(cost: number): boolean; + gainHackingExp(exp: number): void; + gainStrengthExp(exp: number): void; + gainDefenseExp(exp: number): void; + gainDexterityExp(exp: number): void; + gainAgilityExp(exp: number): void; + gainCharismaExp(exp: number): void; + gainIntelligenceExp(exp: number): void; + gainMoney(money: number): void; + getCurrentServer(): Server; + getGangFaction(): Faction; + getGangName(): string; + getHomeComputer(): Server; + getNextCompanyPosition( + company: Company, + entryPosType: CompanyPosition, + ): CompanyPosition; + getUpgradeHomeRamCost(): number; + gotoLocation(to: LocationName): boolean; + hasCorporation(): boolean; + hasGangWith(facName: string): boolean; + hasTorRouter(): boolean; + hasProgram(program: string): boolean; + inBladeburner(): boolean; + inGang(): boolean; + isQualified(company: Company, position: CompanyPosition): boolean; + loseMoney(money: number): void; + reapplyAllAugmentations(resetMultipliers: boolean): void; + reapplyAllSourceFiles(): void; + regenerateHp(amt: number): void; + recordMoneySource(amt: number, source: string): void; + setMoney(amt: number): void; + singularityStopWork(): void; + startBladeburner(p: any): void; + startClass(costMult: number, expMult: number, className: string): void; + startCorporation(corpName: string, additionalShares?: number): void; + startCrime( + crimeType: string, + hackExp: number, + strExp: number, + defExp: number, + dexExp: number, + agiExp: number, + chaExp: number, + money: number, + time: number, + singParams: any, + ): void; + startFactionFieldWork(faction: Faction): void; + startFactionHackWork(faction: Faction): void; + startFactionSecurityWork(faction: Faction): void; + startGang(facName: string, isHacking: boolean): void; + startWork(companyName: string): void; + startWorkPartTime(companyName: string): void; + takeDamage(amt: number): boolean; + travel(to: CityName): boolean; + giveExploit(exploit: Exploit): void; + queryStatFromString(str: string): number; + getIntelligenceBonus(weight: number): number; + getCasinoWinnings(): number; + quitJob(company: string): void; } diff --git a/src/PersonObjects/IPlayerOrSleeve.ts b/src/PersonObjects/IPlayerOrSleeve.ts index 54cbbb22a..8d25d39ea 100644 --- a/src/PersonObjects/IPlayerOrSleeve.ts +++ b/src/PersonObjects/IPlayerOrSleeve.ts @@ -2,25 +2,25 @@ // a Sleeve. Used for functions that need to take in both. export interface IPlayerOrSleeve { - // Stats - hacking_skill: number; - strength: number; - defense: number; - dexterity: number; - agility: number; - charisma: number; - intelligence: number; + // Stats + hacking_skill: number; + strength: number; + defense: number; + dexterity: number; + agility: number; + charisma: number; + intelligence: number; - // Experience - hacking_exp: number; - strength_exp: number; - defense_exp: number; - dexterity_exp: number; - agility_exp: number; - charisma_exp: number; + // Experience + hacking_exp: number; + strength_exp: number; + defense_exp: number; + dexterity_exp: number; + agility_exp: number; + charisma_exp: number; - // Multipliers - crime_success_mult: number; + // Multipliers + crime_success_mult: number; - getIntelligenceBonus(weight: number): number; + getIntelligenceBonus(weight: number): number; } diff --git a/src/PersonObjects/Person.ts b/src/PersonObjects/Person.ts index dd4eb1176..56f629d19 100644 --- a/src/PersonObjects/Person.ts +++ b/src/PersonObjects/Person.ts @@ -10,207 +10,262 @@ import { calculateIntelligenceBonus } from "./formulas/intelligence"; // Interface that defines a generic object used to track experience/money // earnings for tasks export interface ITaskTracker { - hack: number; - str: number; - def: number; - dex: number; - agi: number; - cha: number; - money: number; + hack: number; + str: number; + def: number; + dex: number; + agi: number; + cha: number; + money: number; } export function createTaskTracker(): ITaskTracker { - return { - hack: 0, - str: 0, - def: 0, - dex: 0, - agi: 0, - cha: 0, - money: 0, - } + return { + hack: 0, + str: 0, + def: 0, + dex: 0, + agi: 0, + cha: 0, + money: 0, + }; } export abstract class Person { - /** - * Stats - */ - hacking_skill = 1; - strength = 1; - defense = 1; - dexterity = 1; - agility = 1; - charisma = 1; - intelligence = 1; - hp = 10; - max_hp = 10; + /** + * Stats + */ + hacking_skill = 1; + strength = 1; + defense = 1; + dexterity = 1; + agility = 1; + charisma = 1; + intelligence = 1; + hp = 10; + max_hp = 10; - /** - * Experience - */ - hacking_exp = 0; - strength_exp = 0; - defense_exp = 0; - dexterity_exp = 0; - agility_exp = 0; - charisma_exp = 0; - intelligence_exp = 0; + /** + * Experience + */ + hacking_exp = 0; + strength_exp = 0; + defense_exp = 0; + dexterity_exp = 0; + agility_exp = 0; + charisma_exp = 0; + intelligence_exp = 0; - /** - * Multipliers - */ - hacking_mult = 1; - strength_mult = 1; - defense_mult = 1; - dexterity_mult = 1; - agility_mult = 1; - charisma_mult = 1; + /** + * Multipliers + */ + hacking_mult = 1; + strength_mult = 1; + defense_mult = 1; + dexterity_mult = 1; + agility_mult = 1; + charisma_mult = 1; - hacking_exp_mult = 1; - strength_exp_mult = 1; - defense_exp_mult = 1; - dexterity_exp_mult = 1; - agility_exp_mult = 1; - charisma_exp_mult = 1; + hacking_exp_mult = 1; + strength_exp_mult = 1; + defense_exp_mult = 1; + dexterity_exp_mult = 1; + agility_exp_mult = 1; + charisma_exp_mult = 1; - hacking_chance_mult = 1; - hacking_speed_mult = 1; - hacking_money_mult = 1; - hacking_grow_mult = 1; + hacking_chance_mult = 1; + hacking_speed_mult = 1; + hacking_money_mult = 1; + hacking_grow_mult = 1; - company_rep_mult = 1; - faction_rep_mult = 1; + company_rep_mult = 1; + faction_rep_mult = 1; - crime_money_mult = 1; - crime_success_mult = 1; + crime_money_mult = 1; + crime_success_mult = 1; - work_money_mult = 1; + work_money_mult = 1; - hacknet_node_money_mult = 1; - hacknet_node_purchase_cost_mult = 1; - hacknet_node_ram_cost_mult = 1; - hacknet_node_core_cost_mult = 1; - hacknet_node_level_cost_mult = 1; + hacknet_node_money_mult = 1; + hacknet_node_purchase_cost_mult = 1; + hacknet_node_ram_cost_mult = 1; + hacknet_node_core_cost_mult = 1; + hacknet_node_level_cost_mult = 1; - bladeburner_max_stamina_mult = 1; - bladeburner_stamina_gain_mult = 1; - bladeburner_analysis_mult = 1; - bladeburner_success_chance_mult = 1; + bladeburner_max_stamina_mult = 1; + bladeburner_stamina_gain_mult = 1; + bladeburner_analysis_mult = 1; + bladeburner_success_chance_mult = 1; - /** - * Augmentations - */ - augmentations: IPlayerOwnedAugmentation[] = []; - queuedAugmentations: IPlayerOwnedAugmentation[] = []; + /** + * Augmentations + */ + augmentations: IPlayerOwnedAugmentation[] = []; + queuedAugmentations: IPlayerOwnedAugmentation[] = []; - /** - * City that the person is in - */ - city: CityName = CityName.Sector12; + /** + * City that the person is in + */ + city: CityName = CityName.Sector12; - /** - * Updates this object's multipliers for the given augmentation - */ - applyAugmentation(aug: Augmentation): void { - for (const mult in aug.mults) { - if ((this)[mult] == null) { - console.warn(`Augmentation has unrecognized multiplier property: ${mult}`); - } else { - (this)[mult] *= aug.mults[mult]; - } - } + /** + * Updates this object's multipliers for the given augmentation + */ + applyAugmentation(aug: Augmentation): void { + for (const mult in aug.mults) { + if ((this)[mult] == null) { + console.warn( + `Augmentation has unrecognized multiplier property: ${mult}`, + ); + } else { + (this)[mult] *= aug.mults[mult]; + } } + } - /** - * Given an experience amount and stat multiplier, calculates the - * stat level. Stat-agnostic (same formula for every stat) - */ - calculateStat(exp: number, mult=1): number { - return calculateSkill(exp, mult); - } + /** + * Given an experience amount and stat multiplier, calculates the + * stat level. Stat-agnostic (same formula for every stat) + */ + calculateStat(exp: number, mult = 1): number { + return calculateSkill(exp, mult); + } - /** - * Calculate and return the amount of faction reputation earned per cycle - * when doing Field Work for a faction - */ - getFactionFieldWorkRepGain(): number { - const t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel + - this.strength / CONSTANTS.MaxSkillLevel + - this.defense / CONSTANTS.MaxSkillLevel + - this.dexterity / CONSTANTS.MaxSkillLevel + - this.agility / CONSTANTS.MaxSkillLevel + - this.charisma / CONSTANTS.MaxSkillLevel) / 5.5; - return t * this.faction_rep_mult; - } + /** + * Calculate and return the amount of faction reputation earned per cycle + * when doing Field Work for a faction + */ + getFactionFieldWorkRepGain(): number { + const t = + (0.9 * + (this.hacking_skill / CONSTANTS.MaxSkillLevel + + this.strength / CONSTANTS.MaxSkillLevel + + this.defense / CONSTANTS.MaxSkillLevel + + this.dexterity / CONSTANTS.MaxSkillLevel + + this.agility / CONSTANTS.MaxSkillLevel + + this.charisma / CONSTANTS.MaxSkillLevel)) / + 5.5; + return t * this.faction_rep_mult; + } - /** - * Calculate and return the amount of faction reputation earned per cycle - * when doing Hacking Work for a faction - */ - getFactionHackingWorkRepGain(): number { - return this.hacking_skill / CONSTANTS.MaxSkillLevel * this.faction_rep_mult; - } + /** + * Calculate and return the amount of faction reputation earned per cycle + * when doing Hacking Work for a faction + */ + getFactionHackingWorkRepGain(): number { + return ( + (this.hacking_skill / CONSTANTS.MaxSkillLevel) * this.faction_rep_mult + ); + } - /** - * Calculate and return the amount of faction reputation earned per cycle - * when doing Security Work for a faction - */ - getFactionSecurityWorkRepGain(): number { - const t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel + - this.strength / CONSTANTS.MaxSkillLevel + - this.defense / CONSTANTS.MaxSkillLevel + - this.dexterity / CONSTANTS.MaxSkillLevel + - this.agility / CONSTANTS.MaxSkillLevel) / 4.5; - return t * this.faction_rep_mult; - } + /** + * Calculate and return the amount of faction reputation earned per cycle + * when doing Security Work for a faction + */ + getFactionSecurityWorkRepGain(): number { + const t = + (0.9 * + (this.hacking_skill / CONSTANTS.MaxSkillLevel + + this.strength / CONSTANTS.MaxSkillLevel + + this.defense / CONSTANTS.MaxSkillLevel + + this.dexterity / CONSTANTS.MaxSkillLevel + + this.agility / CONSTANTS.MaxSkillLevel)) / + 4.5; + return t * this.faction_rep_mult; + } + /** + * Reset all multipliers to 1 + */ + resetMultipliers(): void { + this.hacking_mult = 1; + this.strength_mult = 1; + this.defense_mult = 1; + this.dexterity_mult = 1; + this.agility_mult = 1; + this.charisma_mult = 1; + this.hacking_exp_mult = 1; + this.strength_exp_mult = 1; + this.defense_exp_mult = 1; + this.dexterity_exp_mult = 1; + this.agility_exp_mult = 1; + this.charisma_exp_mult = 1; - /** - * Reset all multipliers to 1 - */ - resetMultipliers(): void { - this.hacking_mult = 1; - this.strength_mult = 1; - this.defense_mult = 1; - this.dexterity_mult = 1; - this.agility_mult = 1; - this.charisma_mult = 1; + this.company_rep_mult = 1; + this.faction_rep_mult = 1; - this.hacking_exp_mult = 1; - this.strength_exp_mult = 1; - this.defense_exp_mult = 1; - this.dexterity_exp_mult = 1; - this.agility_exp_mult = 1; - this.charisma_exp_mult = 1; + this.crime_money_mult = 1; + this.crime_success_mult = 1; - this.company_rep_mult = 1; - this.faction_rep_mult = 1; + this.work_money_mult = 1; + } - this.crime_money_mult = 1; - this.crime_success_mult = 1; + /** + * Update all stat levels + */ + updateStatLevels(): void { + this.hacking_skill = Math.max( + 1, + Math.floor( + this.calculateStat( + this.hacking_exp, + this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier, + ), + ), + ); + this.strength = Math.max( + 1, + Math.floor( + this.calculateStat( + this.strength_exp, + this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier, + ), + ), + ); + this.defense = Math.max( + 1, + Math.floor( + this.calculateStat( + this.defense_exp, + this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier, + ), + ), + ); + this.dexterity = Math.max( + 1, + Math.floor( + this.calculateStat( + this.dexterity_exp, + this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier, + ), + ), + ); + this.agility = Math.max( + 1, + Math.floor( + this.calculateStat( + this.agility_exp, + this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier, + ), + ), + ); + this.charisma = Math.max( + 1, + Math.floor( + this.calculateStat( + this.charisma_exp, + this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier, + ), + ), + ); - this.work_money_mult = 1; - } + const ratio: number = this.hp / this.max_hp; + this.max_hp = Math.floor(10 + this.defense / 10); + this.hp = Math.round(this.max_hp * ratio); + } - /** - * Update all stat levels - */ - updateStatLevels(): void { - this.hacking_skill = Math.max(1, Math.floor(this.calculateStat(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier))); - this.strength = Math.max(1, Math.floor(this.calculateStat(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier))); - this.defense = Math.max(1, Math.floor(this.calculateStat(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier))); - this.dexterity = Math.max(1, Math.floor(this.calculateStat(this.dexterity_exp, this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier))); - this.agility = Math.max(1, Math.floor(this.calculateStat(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier))); - this.charisma = Math.max(1, Math.floor(this.calculateStat(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier))); - - const ratio: number = this.hp / this.max_hp; - this.max_hp = Math.floor(10 + this.defense / 10); - this.hp = Math.round(this.max_hp * ratio); - } - - - getIntelligenceBonus(weight: number): number { - return calculateIntelligenceBonus(this.intelligence, weight); - } + getIntelligenceBonus(weight: number): number { + return calculateIntelligenceBonus(this.intelligence, weight); + } } diff --git a/src/PersonObjects/Player/PlayerObject.js b/src/PersonObjects/Player/PlayerObject.js index f7cebc9c6..e0601355b 100644 --- a/src/PersonObjects/Player/PlayerObject.js +++ b/src/PersonObjects/Player/PlayerObject.js @@ -10,219 +10,219 @@ import { CityName } from "../../Locations/data/CityNames"; import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; import { - Reviver, - Generic_toJSON, - Generic_fromJSON, + Reviver, + Generic_toJSON, + Generic_fromJSON, } from "../../../utils/JSONReviver"; import Decimal from "decimal.js"; export function PlayerObject() { - //Skills and stats - this.hacking_skill = 1; + //Skills and stats + this.hacking_skill = 1; - //Combat stats - this.hp = 10; - this.max_hp = 10; - this.strength = 1; - this.defense = 1; - this.dexterity = 1; - this.agility = 1; + //Combat stats + this.hp = 10; + this.max_hp = 10; + this.strength = 1; + this.defense = 1; + this.dexterity = 1; + this.agility = 1; - //Labor stats - this.charisma = 1; + //Labor stats + this.charisma = 1; - //Special stats - this.intelligence = 0; + //Special stats + this.intelligence = 0; - //Hacking multipliers - this.hacking_chance_mult = 1; - this.hacking_speed_mult = 1; - this.hacking_money_mult = 1; - this.hacking_grow_mult = 1; + //Hacking multipliers + this.hacking_chance_mult = 1; + this.hacking_speed_mult = 1; + this.hacking_money_mult = 1; + this.hacking_grow_mult = 1; - //Experience and multipliers - this.hacking_exp = 0; - this.strength_exp = 0; - this.defense_exp = 0; - this.dexterity_exp = 0; - this.agility_exp = 0; - this.charisma_exp = 0; - this.intelligence_exp= 0; + //Experience and multipliers + this.hacking_exp = 0; + this.strength_exp = 0; + this.defense_exp = 0; + this.dexterity_exp = 0; + this.agility_exp = 0; + this.charisma_exp = 0; + this.intelligence_exp = 0; - this.hacking_mult = 1; - this.strength_mult = 1; - this.defense_mult = 1; - this.dexterity_mult = 1; - this.agility_mult = 1; - this.charisma_mult = 1; + this.hacking_mult = 1; + this.strength_mult = 1; + this.defense_mult = 1; + this.dexterity_mult = 1; + this.agility_mult = 1; + this.charisma_mult = 1; - this.hacking_exp_mult = 1; - this.strength_exp_mult = 1; - this.defense_exp_mult = 1; - this.dexterity_exp_mult = 1; - this.agility_exp_mult = 1; - this.charisma_exp_mult = 1; + this.hacking_exp_mult = 1; + this.strength_exp_mult = 1; + this.defense_exp_mult = 1; + this.dexterity_exp_mult = 1; + this.agility_exp_mult = 1; + this.charisma_exp_mult = 1; - this.company_rep_mult = 1; - this.faction_rep_mult = 1; + this.company_rep_mult = 1; + this.faction_rep_mult = 1; - //Money - this.money = new Decimal(1000); + //Money + this.money = new Decimal(1000); - //IP Address of Starting (home) computer - this.homeComputer = ""; + //IP Address of Starting (home) computer + this.homeComputer = ""; - //Location information - this.city = CityName.Sector12; - this.location = ""; + //Location information + this.city = CityName.Sector12; + this.location = ""; - // Jobs that the player holds - // Map of company name (key) -> name of company position (value. Just the name, not the CompanyPosition object) - // The CompanyPosition name must match a key value in CompanyPositions - this.jobs = {}; + // Jobs that the player holds + // Map of company name (key) -> name of company position (value. Just the name, not the CompanyPosition object) + // The CompanyPosition name must match a key value in CompanyPositions + this.jobs = {}; - // Company at which player is CURRENTLY working (only valid when the player is actively working) - this.companyName = ""; // Name of Company. Must match a key value in Companies map + // Company at which player is CURRENTLY working (only valid when the player is actively working) + this.companyName = ""; // Name of Company. Must match a key value in Companies map - // Servers - this.currentServer = ""; //IP address of Server currently being accessed through terminal - this.purchasedServers = []; //IP Addresses of purchased servers + // Servers + this.currentServer = ""; //IP address of Server currently being accessed through terminal + this.purchasedServers = []; //IP Addresses of purchased servers - // Hacknet Nodes/Servers - this.hacknetNodes = []; // Note: For Hacknet Servers, this array holds the IP addresses of the servers - this.hashManager = new HashManager(); + // Hacknet Nodes/Servers + this.hacknetNodes = []; // Note: For Hacknet Servers, this array holds the IP addresses of the servers + this.hashManager = new HashManager(); - //Factions - this.factions = []; //Names of all factions player has joined - this.factionInvitations = []; //Outstanding faction invitations + //Factions + this.factions = []; //Names of all factions player has joined + this.factionInvitations = []; //Outstanding faction invitations - //Augmentations - this.queuedAugmentations = []; - this.augmentations = []; + //Augmentations + this.queuedAugmentations = []; + this.augmentations = []; - this.sourceFiles = []; + this.sourceFiles = []; - //Crime statistics - this.numPeopleKilled = 0; - this.karma = 0; + //Crime statistics + this.numPeopleKilled = 0; + this.karma = 0; - this.crime_money_mult = 1; - this.crime_success_mult = 1; + this.crime_money_mult = 1; + this.crime_success_mult = 1; - //Flags/variables for working (Company, Faction, Creating Program, Taking Class) - this.isWorking = false; - this.focus = false; - this.workType = ""; + //Flags/variables for working (Company, Faction, Creating Program, Taking Class) + this.isWorking = false; + this.focus = false; + this.workType = ""; - this.currentWorkFactionName = ""; - this.currentWorkFactionDescription = ""; + this.currentWorkFactionName = ""; + this.currentWorkFactionDescription = ""; - this.workHackExpGainRate = 0; - this.workStrExpGainRate = 0; - this.workDefExpGainRate = 0; - this.workDexExpGainRate = 0; - this.workAgiExpGainRate = 0; - this.workChaExpGainRate = 0; - this.workRepGainRate = 0; - this.workMoneyGainRate = 0; - this.workMoneyLossRate = 0; + this.workHackExpGainRate = 0; + this.workStrExpGainRate = 0; + this.workDefExpGainRate = 0; + this.workDexExpGainRate = 0; + this.workAgiExpGainRate = 0; + this.workChaExpGainRate = 0; + this.workRepGainRate = 0; + this.workMoneyGainRate = 0; + this.workMoneyLossRate = 0; - this.workHackExpGained = 0; - this.workStrExpGained = 0; - this.workDefExpGained = 0; - this.workDexExpGained = 0; - this.workAgiExpGained = 0; - this.workChaExpGained = 0; - this.workRepGained = 0; - this.workMoneyGained = 0; + this.workHackExpGained = 0; + this.workStrExpGained = 0; + this.workDefExpGained = 0; + this.workDexExpGained = 0; + this.workAgiExpGained = 0; + this.workChaExpGained = 0; + this.workRepGained = 0; + this.workMoneyGained = 0; - this.createProgramName = ""; - this.createProgramReqLvl = 0; + this.createProgramName = ""; + this.createProgramReqLvl = 0; - this.className = ""; + this.className = ""; - this.crimeType = ""; + this.crimeType = ""; - this.timeWorked = 0; //in ms - this.timeWorkedCreateProgram = 0; - this.timeNeededToCompleteWork = 0; + this.timeWorked = 0; //in ms + this.timeWorkedCreateProgram = 0; + this.timeNeededToCompleteWork = 0; - this.work_money_mult = 1; + this.work_money_mult = 1; - //Hacknet Node multipliers - this.hacknet_node_money_mult = 1; - this.hacknet_node_purchase_cost_mult = 1; - this.hacknet_node_ram_cost_mult = 1; - this.hacknet_node_core_cost_mult = 1; - this.hacknet_node_level_cost_mult = 1; + //Hacknet Node multipliers + this.hacknet_node_money_mult = 1; + this.hacknet_node_purchase_cost_mult = 1; + this.hacknet_node_ram_cost_mult = 1; + this.hacknet_node_core_cost_mult = 1; + this.hacknet_node_level_cost_mult = 1; - //Stock Market - this.hasWseAccount = false; - this.hasTixApiAccess = false; - this.has4SData = false; - this.has4SDataTixApi = false; + //Stock Market + this.hasWseAccount = false; + this.hasTixApiAccess = false; + this.has4SData = false; + this.has4SDataTixApi = false; - //Gang - this.gang = 0; + //Gang + this.gang = 0; - //Corporation - this.corporation = 0; + //Corporation + this.corporation = 0; - //Bladeburner - this.bladeburner = 0; - this.bladeburner_max_stamina_mult = 1; - this.bladeburner_stamina_gain_mult = 1; - this.bladeburner_analysis_mult = 1; //Field Analysis Only - this.bladeburner_success_chance_mult = 1; + //Bladeburner + this.bladeburner = 0; + this.bladeburner_max_stamina_mult = 1; + this.bladeburner_stamina_gain_mult = 1; + this.bladeburner_analysis_mult = 1; //Field Analysis Only + this.bladeburner_success_chance_mult = 1; - // Sleeves & Re-sleeving - this.sleeves = []; - this.resleeves = []; - this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenant + // Sleeves & Re-sleeving + this.sleeves = []; + this.resleeves = []; + this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenant - //bitnode - this.bitNodeN = 1; + //bitnode + this.bitNodeN = 1; - //Flags for determining whether certain "thresholds" have been achieved - this.firstFacInvRecvd = false; - this.firstAugPurchased = false; - this.firstTimeTraveled = false; - this.firstProgramAvailable = false; + //Flags for determining whether certain "thresholds" have been achieved + this.firstFacInvRecvd = false; + this.firstAugPurchased = false; + this.firstTimeTraveled = false; + this.firstProgramAvailable = false; - //Used to store the last update time. - this.lastUpdate = 0; - this.totalPlaytime = 0; - this.playtimeSinceLastAug = 0; - this.playtimeSinceLastBitnode = 0; + //Used to store the last update time. + this.lastUpdate = 0; + this.totalPlaytime = 0; + this.playtimeSinceLastAug = 0; + this.playtimeSinceLastBitnode = 0; - // Keep track of where money comes from - this.moneySourceA = new MoneySourceTracker(); // Where money comes from since last-installed Augmentation - this.moneySourceB = new MoneySourceTracker(); // Where money comes from for this entire BitNode run + // Keep track of where money comes from + this.moneySourceA = new MoneySourceTracker(); // Where money comes from since last-installed Augmentation + this.moneySourceB = new MoneySourceTracker(); // Where money comes from for this entire BitNode run - // Production since last Augmentation installation - this.scriptProdSinceLastAug = 0; + // Production since last Augmentation installation + this.scriptProdSinceLastAug = 0; - this.exploits = []; + this.exploits = []; } // Apply player methods to the prototype using Object.assign() Object.assign( - PlayerObject.prototype, - generalMethods, - serverMethods, - bladeburnerMethods, - corporationMethods, - gangMethods, - augmentationMethods, + PlayerObject.prototype, + generalMethods, + serverMethods, + bladeburnerMethods, + corporationMethods, + gangMethods, + augmentationMethods, ); -PlayerObject.prototype.toJSON = function() { - return Generic_toJSON("PlayerObject", this); -} +PlayerObject.prototype.toJSON = function () { + return Generic_toJSON("PlayerObject", this); +}; -PlayerObject.fromJSON = function(value) { - return Generic_fromJSON(PlayerObject, value.data); -} +PlayerObject.fromJSON = function (value) { + return Generic_fromJSON(PlayerObject, value.data); +}; Reviver.constructors.PlayerObject = PlayerObject; diff --git a/src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts b/src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts index bb5cdd708..3ffbdeb93 100644 --- a/src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts +++ b/src/PersonObjects/Player/PlayerObjectAugmentationMethods.ts @@ -5,16 +5,23 @@ import { IPlayer } from "../IPlayer"; import { Augmentation } from "../../Augmentation/Augmentation"; -export function hasAugmentation(this: IPlayer, aug: string | Augmentation): boolean { - const augName: string = (aug instanceof Augmentation) ? aug.name : aug; +export function hasAugmentation( + this: IPlayer, + aug: string | Augmentation, +): boolean { + const augName: string = aug instanceof Augmentation ? aug.name : aug; - for (const owned of this.augmentations) { - if (owned.name === augName) { return true; } + for (const owned of this.augmentations) { + if (owned.name === augName) { + return true; } + } - for (const owned of this.queuedAugmentations) { - if (owned.name === augName) { return true; } + for (const owned of this.queuedAugmentations) { + if (owned.name === augName) { + return true; } + } - return false; + return false; } diff --git a/src/PersonObjects/Player/PlayerObjectBladeburnerMethods.js b/src/PersonObjects/Player/PlayerObjectBladeburnerMethods.js index 2a4754701..6fd0260d7 100644 --- a/src/PersonObjects/Player/PlayerObjectBladeburnerMethods.js +++ b/src/PersonObjects/Player/PlayerObjectBladeburnerMethods.js @@ -2,16 +2,25 @@ import { Bladeburner } from "../../Bladeburner/Bladeburner"; import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; export function canAccessBladeburner() { - if (this.bitNodeN === 8) { return false; } - - return (this.bitNodeN === 6) || (this.bitNodeN === 7) || (SourceFileFlags[6] > 0) || (SourceFileFlags[7] > 0); + if (this.bitNodeN === 8) { + return false; + } + + return ( + this.bitNodeN === 6 || + this.bitNodeN === 7 || + SourceFileFlags[6] > 0 || + SourceFileFlags[7] > 0 + ); } export function inBladeburner() { - if (this.bladeburner == null) { return false; } - return (this.bladeburner instanceof Bladeburner); + if (this.bladeburner == null) { + return false; + } + return this.bladeburner instanceof Bladeburner; } export function startBladeburner() { - this.bladeburner = new Bladeburner(this); + this.bladeburner = new Bladeburner(this); } diff --git a/src/PersonObjects/Player/PlayerObjectCorporationMethods.js b/src/PersonObjects/Player/PlayerObjectCorporationMethods.js index 216109d3d..776716831 100644 --- a/src/PersonObjects/Player/PlayerObjectCorporationMethods.js +++ b/src/PersonObjects/Player/PlayerObjectCorporationMethods.js @@ -2,18 +2,20 @@ import { Corporation } from "../../Corporation/Corporation"; import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; export function canAccessCorporation() { - return this.bitNodeN === 3 || (SourceFileFlags[3] > 0); + return this.bitNodeN === 3 || SourceFileFlags[3] > 0; } export function hasCorporation() { - if (this.corporation == null) { return false; } - return (this.corporation instanceof Corporation); + if (this.corporation == null) { + return false; + } + return this.corporation instanceof Corporation; } -export function startCorporation(corpName, additionalShares=0) { - this.corporation = new Corporation({ - name: corpName, - }); +export function startCorporation(corpName, additionalShares = 0) { + this.corporation = new Corporation({ + name: corpName, + }); - this.corporation.totalShares += additionalShares; + this.corporation.totalShares += additionalShares; } diff --git a/src/PersonObjects/Player/PlayerObjectGangMethods.js b/src/PersonObjects/Player/PlayerObjectGangMethods.js index 82399d79e..3e8325e36 100644 --- a/src/PersonObjects/Player/PlayerObjectGangMethods.js +++ b/src/PersonObjects/Player/PlayerObjectGangMethods.js @@ -7,41 +7,49 @@ import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; const GangKarmaRequirement = -54000; export function canAccessGang() { - if (this.bitNodeN === 2) { return true; } - if (SourceFileFlags[2] <= 0) { return false; } + if (this.bitNodeN === 2) { + return true; + } + if (SourceFileFlags[2] <= 0) { + return false; + } - return (this.karma <= BitNodeMultipliers.GangKarmaRequirement*GangKarmaRequirement); + return ( + this.karma <= BitNodeMultipliers.GangKarmaRequirement * GangKarmaRequirement + ); } export function getGangFaction() { - const fac = Factions[this.gang.facName]; - if (fac == null) { - throw new Error(`Gang has invalid faction name: ${this.gang.facName}`); - } + const fac = Factions[this.gang.facName]; + if (fac == null) { + throw new Error(`Gang has invalid faction name: ${this.gang.facName}`); + } - return fac; + return fac; } export function getGangName() { - return this.inGang() ? this.gang.facName : ""; + return this.inGang() ? this.gang.facName : ""; } export function hasGangWith(facName) { - return this.inGang() && this.gang.facName === facName; + return this.inGang() && this.gang.facName === facName; } export function inGang() { - if (this.gang == null || this.gang == undefined) { return false; } + if (this.gang == null || this.gang == undefined) { + return false; + } - return (this.gang instanceof Gang); + return this.gang instanceof Gang; } export function startGang(factionName, hacking) { - this.gang = new Gang(factionName, hacking); + this.gang = new Gang(factionName, hacking); - const fac = Factions[factionName]; - if (fac == null) { - throw new Error(`Invalid faction name when creating gang: ${factionName}`); - } - fac.playerReputation = 0; + const fac = Factions[factionName]; + if (fac == null) { + throw new Error(`Invalid faction name when creating gang: ${factionName}`); + } + fac.playerReputation = 0; } diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.jsx b/src/PersonObjects/Player/PlayerObjectGeneralMethods.jsx index 57c70df29..1a3c6e45e 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.jsx +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.jsx @@ -10,7 +10,7 @@ import { getNextCompanyPositionHelper } from "../../Company/GetNextCompanyPositi import { getJobRequirementText } from "../../Company/GetJobRequirementText"; import { CompanyPositions } from "../../Company/CompanyPositions"; import * as posNames from "../../Company/data/companypositionnames"; -import {CONSTANTS} from "../../Constants"; +import { CONSTANTS } from "../../Constants"; import { Programs } from "../../Programs/Programs"; import { determineCrimeSuccess } from "../../Crime/CrimeHelpers"; import { Crimes } from "../../Crime/Crimes"; @@ -28,18 +28,21 @@ import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve"; import { calculateSkill as calculateSkillF } from "../formulas/skill"; import { calculateIntelligenceBonus } from "../formulas/intelligence"; import { - getHackingWorkRepGain, - getFactionSecurityWorkRepGain, - getFactionFieldWorkRepGain, -} from '../formulas/reputation'; + getHackingWorkRepGain, + getFactionSecurityWorkRepGain, + getFactionFieldWorkRepGain, +} from "../formulas/reputation"; import { - AllServers, - AddToAllServers, - createUniqueRandomIp, + AllServers, + AddToAllServers, + createUniqueRandomIp, } from "../../Server/AllServers"; import { safetlyCreateUniqueServer } from "../../Server/ServerHelpers"; import { Settings } from "../../Settings/Settings"; -import { SpecialServerIps, SpecialServerNames } from "../../Server/SpecialServerIps"; +import { + SpecialServerIps, + SpecialServerNames, +} from "../../Server/SpecialServerIps"; import { applySourceFile } from "../../SourceFile/applySourceFile"; import { applyExploit } from "../../Exploits/applyExploits"; import { SourceFiles } from "../../SourceFile/SourceFiles"; @@ -66,1041 +69,1460 @@ import ReactDOM from "react-dom"; const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle; export function init() { - /* Initialize Player's home computer */ - var t_homeComp = safetlyCreateUniqueServer({ - adminRights: true, - hostname: "home", - ip: createUniqueRandomIp(), - isConnectedTo: true, - maxRam: 8, - organizationName: "Home PC", - purchasedByPlayer: true, - }); - this.homeComputer = t_homeComp.ip; - this.currentServer = t_homeComp.ip; - AddToAllServers(t_homeComp); + /* Initialize Player's home computer */ + var t_homeComp = safetlyCreateUniqueServer({ + adminRights: true, + hostname: "home", + ip: createUniqueRandomIp(), + isConnectedTo: true, + maxRam: 8, + organizationName: "Home PC", + purchasedByPlayer: true, + }); + this.homeComputer = t_homeComp.ip; + this.currentServer = t_homeComp.ip; + AddToAllServers(t_homeComp); - this.getHomeComputer().programs.push(Programs.NukeProgram.name); + this.getHomeComputer().programs.push(Programs.NukeProgram.name); } export function prestigeAugmentation() { - var homeComp = this.getHomeComputer(); - this.currentServer = homeComp.ip; - this.homeComputer = homeComp.ip; + var homeComp = this.getHomeComputer(); + this.currentServer = homeComp.ip; + this.homeComputer = homeComp.ip; - this.numPeopleKilled = 0; - this.karma = 0; + this.numPeopleKilled = 0; + this.karma = 0; - //Reset stats - this.hacking_skill = 1; + //Reset stats + this.hacking_skill = 1; - this.strength = 1; - this.defense = 1; - this.dexterity = 1; - this.agility = 1; + this.strength = 1; + this.defense = 1; + this.dexterity = 1; + this.agility = 1; - this.charisma = 1; + this.charisma = 1; - this.hacking_exp = 0; - this.strength_exp = 0; - this.defense_exp = 0; - this.dexterity_exp = 0; - this.agility_exp = 0; - this.charisma_exp = 0; + this.hacking_exp = 0; + this.strength_exp = 0; + this.defense_exp = 0; + this.dexterity_exp = 0; + this.agility_exp = 0; + this.charisma_exp = 0; - this.money = new Decimal(1000); + this.money = new Decimal(1000); - this.city = CityName.Sector12; - this.location = ""; + this.city = CityName.Sector12; + this.location = ""; - this.companyName = ""; - this.jobs = {}; + this.companyName = ""; + this.jobs = {}; - this.purchasedServers = []; + this.purchasedServers = []; - this.factions = []; - this.factionInvitations = []; + this.factions = []; + this.factionInvitations = []; - this.queuedAugmentations = []; + this.queuedAugmentations = []; - this.resleeves = []; + this.resleeves = []; - let numSleeves = Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant; - if(this.sleeves.length > numSleeves) this.sleeves.length = numSleeves; - for(let i = this.sleeves.length; i < numSleeves; i++) { - this.sleeves.push(new Sleeve(this)); + let numSleeves = + Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + + this.sleevesFromCovenant; + if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves; + for (let i = this.sleeves.length; i < numSleeves; i++) { + this.sleeves.push(new Sleeve(this)); + } + + for (let i = 0; i < this.sleeves.length; ++i) { + if (this.sleeves[i] instanceof Sleeve) { + if (this.sleeves[i].shock >= 100) { + this.sleeves[i].synchronize(this); + } else { + this.sleeves[i].shockRecovery(this); + } } + } - for (let i = 0; i < this.sleeves.length; ++i) { - if (this.sleeves[i] instanceof Sleeve) { - if (this.sleeves[i].shock >= 100) { - this.sleeves[i].synchronize(this); - } else { - this.sleeves[i].shockRecovery(this); - } - } - } + this.isWorking = false; + this.currentWorkFactionName = ""; + this.currentWorkFactionDescription = ""; + this.createProgramName = ""; + this.className = ""; + this.crimeType = ""; - this.isWorking = false; - this.currentWorkFactionName = ""; - this.currentWorkFactionDescription = ""; - this.createProgramName = ""; - this.className = ""; - this.crimeType = ""; + this.workHackExpGainRate = 0; + this.workStrExpGainRate = 0; + this.workDefExpGainRate = 0; + this.workDexExpGainRate = 0; + this.workAgiExpGainRate = 0; + this.workChaExpGainRate = 0; + this.workRepGainRate = 0; + this.workMoneyGainRate = 0; - this.workHackExpGainRate = 0; - this.workStrExpGainRate = 0; - this.workDefExpGainRate = 0; - this.workDexExpGainRate = 0; - this.workAgiExpGainRate = 0; - this.workChaExpGainRate = 0; - this.workRepGainRate = 0; - this.workMoneyGainRate = 0; + this.workHackExpGained = 0; + this.workStrExpGained = 0; + this.workDefExpGained = 0; + this.workDexExpGained = 0; + this.workAgiExpGained = 0; + this.workChaExpGained = 0; + this.workRepGained = 0; + this.workMoneyGained = 0; - this.workHackExpGained = 0; - this.workStrExpGained = 0; - this.workDefExpGained = 0; - this.workDexExpGained = 0; - this.workAgiExpGained = 0; - this.workChaExpGained = 0; - this.workRepGained = 0; - this.workMoneyGained = 0; + this.timeWorked = 0; - this.timeWorked = 0; + this.lastUpdate = new Date().getTime(); - this.lastUpdate = new Date().getTime(); + // Statistics Trackers + this.playtimeSinceLastAug = 0; + this.scriptProdSinceLastAug = 0; + this.moneySourceA.reset(); - // Statistics Trackers - this.playtimeSinceLastAug = 0; - this.scriptProdSinceLastAug = 0; - this.moneySourceA.reset(); + this.hacknetNodes.length = 0; + this.hashManager.prestige(); - this.hacknetNodes.length = 0; - this.hashManager.prestige(); - - // Re-calculate skills and reset HP - this.updateSkillLevels(); - this.hp = this.max_hp; + // Re-calculate skills and reset HP + this.updateSkillLevels(); + this.hp = this.max_hp; } export function prestigeSourceFile() { - this.prestigeAugmentation(); - // Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists) - for (let i = 0; i < this.sleeves.length; ++i) { - this.sleeves[i] = new Sleeve(this); + this.prestigeAugmentation(); + // Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists) + for (let i = 0; i < this.sleeves.length; ++i) { + this.sleeves[i] = new Sleeve(this); + } + + if (this.bitNodeN === 10) { + for (let i = 0; i < this.sleeves.length; i++) { + this.sleeves[i].shock = Math.max(25, this.sleeves[i].shock); + this.sleeves[i].sync = Math.max(25, this.sleeves[i].sync); } + } - if(this.bitNodeN === 10) { - for (let i = 0; i < this.sleeves.length; i++) { - this.sleeves[i].shock = Math.max(25, this.sleeves[i].shock); - this.sleeves[i].sync = Math.max(25, this.sleeves[i].sync); - } - } + const characterMenuHeader = document.getElementById("character-menu-header"); + if (characterMenuHeader instanceof HTMLElement) { + characterMenuHeader.click(); + characterMenuHeader.click(); + } - const characterMenuHeader = document.getElementById("character-menu-header"); - if (characterMenuHeader instanceof HTMLElement) { - characterMenuHeader.click(); characterMenuHeader.click(); - } + this.timeWorked = 0; - this.timeWorked = 0; + // Gang + this.gang = null; + resetGangs(); - // Gang - this.gang = null; - resetGangs(); + // Reset Stock market + this.hasWseAccount = false; + this.hasTixApiAccess = false; + this.has4SData = false; + this.has4SDataTixApi = false; - // Reset Stock market - this.hasWseAccount = false; - this.hasTixApiAccess = false; - this.has4SData = false; - this.has4SDataTixApi = false; - - // BitNode 3: Corporatocracy - this.corporation = 0; - - this.moneySourceB.reset(); - this.playtimeSinceLastBitnode = 0; - this.augmentations = []; + // BitNode 3: Corporatocracy + this.corporation = 0; + this.moneySourceB.reset(); + this.playtimeSinceLastBitnode = 0; + this.augmentations = []; } export function receiveInvite(factionName) { - if(this.factionInvitations.includes(factionName) || this.factions.includes(factionName)) { - return; - } - this.firstFacInvRecvd = true; - this.factionInvitations.push(factionName); + if ( + this.factionInvitations.includes(factionName) || + this.factions.includes(factionName) + ) { + return; + } + this.firstFacInvRecvd = true; + this.factionInvitations.push(factionName); } //Calculates skill level based on experience. The same formula will be used for every skill -export function calculateSkill(exp, mult=1) { - return calculateSkillF(exp, mult); +export function calculateSkill(exp, mult = 1) { + return calculateSkillF(exp, mult); } export function updateSkillLevels() { - this.hacking_skill = Math.max(1, Math.floor(this.calculateSkill(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier))); - this.strength = Math.max(1, Math.floor(this.calculateSkill(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier))); - this.defense = Math.max(1, Math.floor(this.calculateSkill(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier))); - this.dexterity = Math.max(1, Math.floor(this.calculateSkill(this.dexterity_exp, this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier))); - this.agility = Math.max(1, Math.floor(this.calculateSkill(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier))); - this.charisma = Math.max(1, Math.floor(this.calculateSkill(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier))); + this.hacking_skill = Math.max( + 1, + Math.floor( + this.calculateSkill( + this.hacking_exp, + this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier, + ), + ), + ); + this.strength = Math.max( + 1, + Math.floor( + this.calculateSkill( + this.strength_exp, + this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier, + ), + ), + ); + this.defense = Math.max( + 1, + Math.floor( + this.calculateSkill( + this.defense_exp, + this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier, + ), + ), + ); + this.dexterity = Math.max( + 1, + Math.floor( + this.calculateSkill( + this.dexterity_exp, + this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier, + ), + ), + ); + this.agility = Math.max( + 1, + Math.floor( + this.calculateSkill( + this.agility_exp, + this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier, + ), + ), + ); + this.charisma = Math.max( + 1, + Math.floor( + this.calculateSkill( + this.charisma_exp, + this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier, + ), + ), + ); - if (this.intelligence > 0) { - this.intelligence = Math.floor(this.calculateSkill(this.intelligence_exp)); - } else { - this.intelligence = 0; - } + if (this.intelligence > 0) { + this.intelligence = Math.floor(this.calculateSkill(this.intelligence_exp)); + } else { + this.intelligence = 0; + } - var ratio = this.hp / this.max_hp; - this.max_hp = Math.floor(10 + this.defense / 10); - this.hp = Math.round(this.max_hp * ratio); + var ratio = this.hp / this.max_hp; + this.max_hp = Math.floor(10 + this.defense / 10); + this.hp = Math.round(this.max_hp * ratio); } export function resetMultipliers() { - this.hacking_chance_mult = 1; - this.hacking_speed_mult = 1; - this.hacking_money_mult = 1; - this.hacking_grow_mult = 1; + this.hacking_chance_mult = 1; + this.hacking_speed_mult = 1; + this.hacking_money_mult = 1; + this.hacking_grow_mult = 1; - this.hacking_mult = 1; - this.strength_mult = 1; - this.defense_mult = 1; - this.dexterity_mult = 1; - this.agility_mult = 1; - this.charisma_mult = 1; + this.hacking_mult = 1; + this.strength_mult = 1; + this.defense_mult = 1; + this.dexterity_mult = 1; + this.agility_mult = 1; + this.charisma_mult = 1; - this.hacking_exp_mult = 1; - this.strength_exp_mult = 1; - this.defense_exp_mult = 1; - this.dexterity_exp_mult = 1; - this.agility_exp_mult = 1; - this.charisma_exp_mult = 1; + this.hacking_exp_mult = 1; + this.strength_exp_mult = 1; + this.defense_exp_mult = 1; + this.dexterity_exp_mult = 1; + this.agility_exp_mult = 1; + this.charisma_exp_mult = 1; - this.company_rep_mult = 1; - this.faction_rep_mult = 1; + this.company_rep_mult = 1; + this.faction_rep_mult = 1; - this.crime_money_mult = 1; - this.crime_success_mult = 1; + this.crime_money_mult = 1; + this.crime_success_mult = 1; - this.hacknet_node_money_mult = 1; - this.hacknet_node_purchase_cost_mult = 1; - this.hacknet_node_ram_cost_mult = 1; - this.hacknet_node_core_cost_mult = 1; - this.hacknet_node_level_cost_mult = 1; + this.hacknet_node_money_mult = 1; + this.hacknet_node_purchase_cost_mult = 1; + this.hacknet_node_ram_cost_mult = 1; + this.hacknet_node_core_cost_mult = 1; + this.hacknet_node_level_cost_mult = 1; - this.work_money_mult = 1; + this.work_money_mult = 1; - this.bladeburner_max_stamina_mult = 1; - this.bladeburner_stamina_gain_mult = 1; - this.bladeburner_analysis_mult = 1; - this.bladeburner_success_chance_mult = 1; + this.bladeburner_max_stamina_mult = 1; + this.bladeburner_stamina_gain_mult = 1; + this.bladeburner_analysis_mult = 1; + this.bladeburner_success_chance_mult = 1; } export function hasProgram(programName) { - const home = this.getHomeComputer(); - if (home == null) { return false; } - - for (var i = 0; i < home.programs.length; ++i) { - if (programName.toLowerCase() == home.programs[i].toLowerCase()) {return true;} - } + const home = this.getHomeComputer(); + if (home == null) { return false; + } + + for (var i = 0; i < home.programs.length; ++i) { + if (programName.toLowerCase() == home.programs[i].toLowerCase()) { + return true; + } + } + return false; } export function setMoney(money) { - if (isNaN(money)) { - console.error("NaN passed into Player.setMoney()"); - return; - } - this.money = new Decimal(money); + if (isNaN(money)) { + console.error("NaN passed into Player.setMoney()"); + return; + } + this.money = new Decimal(money); } export function gainMoney(money) { - if (isNaN(money)) { - console.error("NaN passed into Player.gainMoney()"); - return; - } - this.money = this.money.plus(money); + if (isNaN(money)) { + console.error("NaN passed into Player.gainMoney()"); + return; + } + this.money = this.money.plus(money); } export function loseMoney(money) { - if (isNaN(money)) { - console.error("NaN passed into Player.loseMoney()"); - return; - } - if(this.money.eq(Infinity) && money === Infinity) return; - this.money = this.money.minus(money); + if (isNaN(money)) { + console.error("NaN passed into Player.loseMoney()"); + return; + } + if (this.money.eq(Infinity) && money === Infinity) return; + this.money = this.money.minus(money); } export function canAfford(cost) { - if (isNaN(cost)) { - console.error(`NaN passed into Player.canAfford()`); - return false; - } - return this.money.gte(cost); + if (isNaN(cost)) { + console.error(`NaN passed into Player.canAfford()`); + return false; + } + return this.money.gte(cost); } export function recordMoneySource(amt, source) { - if (!(this.moneySourceA instanceof MoneySourceTracker)) { - console.warn(`Player.moneySourceA was not properly initialized. Resetting`); - this.moneySourceA = new MoneySourceTracker(); - } - if (!(this.moneySourceB instanceof MoneySourceTracker)) { - console.warn(`Player.moneySourceB was not properly initialized. Resetting`); - this.moneySourceB = new MoneySourceTracker(); - } - this.moneySourceA.record(amt, source); - this.moneySourceB.record(amt, source); + if (!(this.moneySourceA instanceof MoneySourceTracker)) { + console.warn(`Player.moneySourceA was not properly initialized. Resetting`); + this.moneySourceA = new MoneySourceTracker(); + } + if (!(this.moneySourceB instanceof MoneySourceTracker)) { + console.warn(`Player.moneySourceB was not properly initialized. Resetting`); + this.moneySourceB = new MoneySourceTracker(); + } + this.moneySourceA.record(amt, source); + this.moneySourceB.record(amt, source); } export function gainHackingExp(exp) { - if (isNaN(exp)) { - console.error("ERR: NaN passed into Player.gainHackingExp()"); return; - } - this.hacking_exp += exp; - if(this.hacking_exp < 0) { - this.hacking_exp = 0; - } + if (isNaN(exp)) { + console.error("ERR: NaN passed into Player.gainHackingExp()"); + return; + } + this.hacking_exp += exp; + if (this.hacking_exp < 0) { + this.hacking_exp = 0; + } - this.hacking_skill = calculateSkillF(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier); + this.hacking_skill = calculateSkillF( + this.hacking_exp, + this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier, + ); } export function gainStrengthExp(exp) { - if (isNaN(exp)) { - console.error("ERR: NaN passed into Player.gainStrengthExp()"); return; - } - this.strength_exp += exp; - if(this.strength_exp < 0) { - this.strength_exp = 0; - } + if (isNaN(exp)) { + console.error("ERR: NaN passed into Player.gainStrengthExp()"); + return; + } + this.strength_exp += exp; + if (this.strength_exp < 0) { + this.strength_exp = 0; + } - this.strength = calculateSkillF(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier); + this.strength = calculateSkillF( + this.strength_exp, + this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier, + ); } export function gainDefenseExp(exp) { - if (isNaN(exp)) { - console.error("ERR: NaN passed into player.gainDefenseExp()"); return; - } - this.defense_exp += exp; - if(this.defense_exp < 0) { - this.defense_exp = 0; - } + if (isNaN(exp)) { + console.error("ERR: NaN passed into player.gainDefenseExp()"); + return; + } + this.defense_exp += exp; + if (this.defense_exp < 0) { + this.defense_exp = 0; + } - this.defense = calculateSkillF(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier); + this.defense = calculateSkillF( + this.defense_exp, + this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier, + ); } export function gainDexterityExp(exp) { - if (isNaN(exp)) { - console.error("ERR: NaN passed into Player.gainDexterityExp()"); return; - } - this.dexterity_exp += exp; - if(this.dexterity_exp < 0) { - this.dexterity_exp = 0; - } + if (isNaN(exp)) { + console.error("ERR: NaN passed into Player.gainDexterityExp()"); + return; + } + this.dexterity_exp += exp; + if (this.dexterity_exp < 0) { + this.dexterity_exp = 0; + } - this.dexterity = calculateSkillF(this.dexterity_exp, this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier); + this.dexterity = calculateSkillF( + this.dexterity_exp, + this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier, + ); } export function gainAgilityExp(exp) { - if (isNaN(exp)) { - console.error("ERR: NaN passed into Player.gainAgilityExp()"); return; - } - this.agility_exp += exp; - if(this.agility_exp < 0) { - this.agility_exp = 0; - } + if (isNaN(exp)) { + console.error("ERR: NaN passed into Player.gainAgilityExp()"); + return; + } + this.agility_exp += exp; + if (this.agility_exp < 0) { + this.agility_exp = 0; + } - this.agility = calculateSkillF(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier); + this.agility = calculateSkillF( + this.agility_exp, + this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier, + ); } export function gainCharismaExp(exp) { - if (isNaN(exp)) { - console.error("ERR: NaN passed into Player.gainCharismaExp()"); return; - } - this.charisma_exp += exp; - if(this.charisma_exp < 0) { - this.charisma_exp = 0; - } + if (isNaN(exp)) { + console.error("ERR: NaN passed into Player.gainCharismaExp()"); + return; + } + this.charisma_exp += exp; + if (this.charisma_exp < 0) { + this.charisma_exp = 0; + } - this.charisma = calculateSkillF(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier); + this.charisma = calculateSkillF( + this.charisma_exp, + this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier, + ); } export function gainIntelligenceExp(exp) { - if (isNaN(exp)) { - console.error("ERROR: NaN passed into Player.gainIntelligenceExp()"); return; - } - if (SourceFileFlags[5] > 0 || this.intelligence > 0) { - this.intelligence_exp += exp; - } + if (isNaN(exp)) { + console.error("ERROR: NaN passed into Player.gainIntelligenceExp()"); + return; + } + if (SourceFileFlags[5] > 0 || this.intelligence > 0) { + this.intelligence_exp += exp; + } } //Given a string expression like "str" or "strength", returns the given stat export function queryStatFromString(str) { - const tempStr = str.toLowerCase(); - if (tempStr.includes("hack")) { return this.hacking_skill; } - if (tempStr.includes("str")) { return this.strength; } - if (tempStr.includes("def")) { return this.defense; } - if (tempStr.includes("dex")) { return this.dexterity; } - if (tempStr.includes("agi")) { return this.agility; } - if (tempStr.includes("cha")) { return this.charisma; } - if (tempStr.includes("int")) { return this.intelligence; } + const tempStr = str.toLowerCase(); + if (tempStr.includes("hack")) { + return this.hacking_skill; + } + if (tempStr.includes("str")) { + return this.strength; + } + if (tempStr.includes("def")) { + return this.defense; + } + if (tempStr.includes("dex")) { + return this.dexterity; + } + if (tempStr.includes("agi")) { + return this.agility; + } + if (tempStr.includes("cha")) { + return this.charisma; + } + if (tempStr.includes("int")) { + return this.intelligence; + } } /******* Working functions *******/ export function resetWorkStatus(generalType, group, workType) { - if(generalType === this.workType && group === this.companyName) return; - if(generalType === this.workType && group === this.currentWorkFactionName && workType === this.factionWorkType) return; - if(this.isWorking) this.singularityStopWork(); - this.workHackExpGainRate = 0; - this.workStrExpGainRate = 0; - this.workDefExpGainRate = 0; - this.workDexExpGainRate = 0; - this.workAgiExpGainRate = 0; - this.workChaExpGainRate = 0; - this.workRepGainRate = 0; - this.workMoneyGainRate = 0; - this.workMoneyLossRate = 0; + if (generalType === this.workType && group === this.companyName) return; + if ( + generalType === this.workType && + group === this.currentWorkFactionName && + workType === this.factionWorkType + ) + return; + if (this.isWorking) this.singularityStopWork(); + this.workHackExpGainRate = 0; + this.workStrExpGainRate = 0; + this.workDefExpGainRate = 0; + this.workDexExpGainRate = 0; + this.workAgiExpGainRate = 0; + this.workChaExpGainRate = 0; + this.workRepGainRate = 0; + this.workMoneyGainRate = 0; + this.workMoneyLossRate = 0; - this.workHackExpGained = 0; - this.workStrExpGained = 0; - this.workDefExpGained = 0; - this.workDexExpGained = 0; - this.workAgiExpGained = 0; - this.workChaExpGained = 0; - this.workRepGained = 0; - this.workMoneyGained = 0; + this.workHackExpGained = 0; + this.workStrExpGained = 0; + this.workDefExpGained = 0; + this.workDexExpGained = 0; + this.workAgiExpGained = 0; + this.workChaExpGained = 0; + this.workRepGained = 0; + this.workMoneyGained = 0; - this.timeWorked = 0; - this.timeWorkedCreateProgram = 0; + this.timeWorked = 0; + this.timeWorkedCreateProgram = 0; - this.currentWorkFactionName = ""; - this.currentWorkFactionDescription = ""; - this.createProgramName = ""; - this.className = ""; + this.currentWorkFactionName = ""; + this.currentWorkFactionDescription = ""; + this.createProgramName = ""; + this.className = ""; - ReactDOM.unmountComponentAtNode(document.getElementById("work-in-progress-text")); + ReactDOM.unmountComponentAtNode( + document.getElementById("work-in-progress-text"), + ); } -export function processWorkEarnings(numCycles=1) { - const focusBonus = this.focus? 1 : 0.8; - const hackExpGain = focusBonus * this.workHackExpGainRate * numCycles; - const strExpGain = focusBonus * this.workStrExpGainRate * numCycles; - const defExpGain = focusBonus * this.workDefExpGainRate * numCycles; - const dexExpGain = focusBonus * this.workDexExpGainRate * numCycles; - const agiExpGain = focusBonus * this.workAgiExpGainRate * numCycles; - const chaExpGain = focusBonus * this.workChaExpGainRate * numCycles; - const moneyGain = (this.workMoneyGainRate - this.workMoneyLossRate) * numCycles; +export function processWorkEarnings(numCycles = 1) { + const focusBonus = this.focus ? 1 : 0.8; + const hackExpGain = focusBonus * this.workHackExpGainRate * numCycles; + const strExpGain = focusBonus * this.workStrExpGainRate * numCycles; + const defExpGain = focusBonus * this.workDefExpGainRate * numCycles; + const dexExpGain = focusBonus * this.workDexExpGainRate * numCycles; + const agiExpGain = focusBonus * this.workAgiExpGainRate * numCycles; + const chaExpGain = focusBonus * this.workChaExpGainRate * numCycles; + const moneyGain = + (this.workMoneyGainRate - this.workMoneyLossRate) * numCycles; - this.gainHackingExp(hackExpGain); - this.gainStrengthExp(strExpGain); - this.gainDefenseExp(defExpGain); - this.gainDexterityExp(dexExpGain); - this.gainAgilityExp(agiExpGain); - this.gainCharismaExp(chaExpGain); - this.gainMoney(moneyGain); - if (this.className) { - this.recordMoneySource(moneyGain, "class"); - } else { - this.recordMoneySource(moneyGain, "work"); - } - this.workHackExpGained += hackExpGain; - this.workStrExpGained += strExpGain; - this.workDefExpGained += defExpGain; - this.workDexExpGained += dexExpGain; - this.workAgiExpGained += agiExpGain; - this.workChaExpGained += chaExpGain; - this.workRepGained += focusBonus * this.workRepGainRate * numCycles; - this.workMoneyGained += focusBonus * this.workMoneyGainRate * numCycles; - this.workMoneyGained -= focusBonus * this.workMoneyLossRate * numCycles; + this.gainHackingExp(hackExpGain); + this.gainStrengthExp(strExpGain); + this.gainDefenseExp(defExpGain); + this.gainDexterityExp(dexExpGain); + this.gainAgilityExp(agiExpGain); + this.gainCharismaExp(chaExpGain); + this.gainMoney(moneyGain); + if (this.className) { + this.recordMoneySource(moneyGain, "class"); + } else { + this.recordMoneySource(moneyGain, "work"); + } + this.workHackExpGained += hackExpGain; + this.workStrExpGained += strExpGain; + this.workDefExpGained += defExpGain; + this.workDexExpGained += dexExpGain; + this.workAgiExpGained += agiExpGain; + this.workChaExpGained += chaExpGain; + this.workRepGained += focusBonus * this.workRepGainRate * numCycles; + this.workMoneyGained += focusBonus * this.workMoneyGainRate * numCycles; + this.workMoneyGained -= focusBonus * this.workMoneyLossRate * numCycles; } /* Working for Company */ export function startWork(companyName) { - this.resetWorkStatus(CONSTANTS.WorkTypeCompany, companyName); - this.isWorking = true; - this.focus = true; - this.companyName = companyName; - this.workType = CONSTANTS.WorkTypeCompany; + this.resetWorkStatus(CONSTANTS.WorkTypeCompany, companyName); + this.isWorking = true; + this.focus = true; + this.companyName = companyName; + this.workType = CONSTANTS.WorkTypeCompany; - this.workHackExpGainRate = this.getWorkHackExpGain(); - this.workStrExpGainRate = this.getWorkStrExpGain(); - this.workDefExpGainRate = this.getWorkDefExpGain(); - this.workDexExpGainRate = this.getWorkDexExpGain(); - this.workAgiExpGainRate = this.getWorkAgiExpGain(); - this.workChaExpGainRate = this.getWorkChaExpGain(); - this.workRepGainRate = this.getWorkRepGain(); - this.workMoneyGainRate = this.getWorkMoneyGain(); + this.workHackExpGainRate = this.getWorkHackExpGain(); + this.workStrExpGainRate = this.getWorkStrExpGain(); + this.workDefExpGainRate = this.getWorkDefExpGain(); + this.workDexExpGainRate = this.getWorkDexExpGain(); + this.workAgiExpGainRate = this.getWorkAgiExpGain(); + this.workChaExpGainRate = this.getWorkChaExpGain(); + this.workRepGainRate = this.getWorkRepGain(); + this.workMoneyGainRate = this.getWorkMoneyGain(); - this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; + this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; - //Remove all old event listeners from Cancel button - var newCancelButton = clearEventListeners("work-in-progress-cancel-button"); - newCancelButton.innerHTML = "Cancel Work"; - newCancelButton.addEventListener("click", () => { - this.finishWork(true); - return false; - }); + //Remove all old event listeners from Cancel button + var newCancelButton = clearEventListeners("work-in-progress-cancel-button"); + newCancelButton.innerHTML = "Cancel Work"; + newCancelButton.addEventListener("click", () => { + this.finishWork(true); + return false; + }); - const focusButton = clearEventListeners("work-in-progress-something-else-button"); - focusButton.style.visibility = "visible"; - focusButton.innerHTML = "Do something else simultaneously"; - focusButton.addEventListener("click", () => { - this.stopFocusing(); - return false; - }); + const focusButton = clearEventListeners( + "work-in-progress-something-else-button", + ); + focusButton.style.visibility = "visible"; + focusButton.innerHTML = "Do something else simultaneously"; + focusButton.addEventListener("click", () => { + this.stopFocusing(); + return false; + }); - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); } export function cancelationPenalty() { - const specialIp = SpecialServerIps[this.companyName]; - if(specialIp) { - const server = AllServers[specialIp]; - if(server && server.backdoorInstalled) return 0.75; - } - return 0.5; + const specialIp = SpecialServerIps[this.companyName]; + if (specialIp) { + const server = AllServers[specialIp]; + if (server && server.backdoorInstalled) return 0.75; + } + return 0.5; } - export function work(numCycles) { - // Cap the number of cycles being processed to whatever would put you at - // the work time limit (8 hours) - var overMax = false; - if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer8Hours) { - overMax = true; - numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed); - } - this.timeWorked += Engine._idleSpeed * numCycles; + // Cap the number of cycles being processed to whatever would put you at + // the work time limit (8 hours) + var overMax = false; + if ( + this.timeWorked + Engine._idleSpeed * numCycles >= + CONSTANTS.MillisecondsPer8Hours + ) { + overMax = true; + numCycles = Math.round( + (CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed, + ); + } + this.timeWorked += Engine._idleSpeed * numCycles; - this.workRepGainRate = this.getWorkRepGain(); - this.processWorkEarnings(numCycles); + this.workRepGainRate = this.getWorkRepGain(); + this.processWorkEarnings(numCycles); - // If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money - if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { - return this.finishWork(false); - } + // If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money + if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { + return this.finishWork(false); + } - const comp = Companies[this.companyName]; - let companyRep = "0"; - if (comp == null || !(comp instanceof Company)) { - console.error(`Could not find Company: ${this.companyName}`); - } else { - companyRep = comp.playerReputation; - } + const comp = Companies[this.companyName]; + let companyRep = "0"; + if (comp == null || !(comp instanceof Company)) { + console.error(`Could not find Company: ${this.companyName}`); + } else { + companyRep = comp.playerReputation; + } - influenceStockThroughCompanyWork(comp, this.workRepGainRate, numCycles); + influenceStockThroughCompanyWork(comp, this.workRepGainRate, numCycles); - const position = this.jobs[this.companyName]; + const position = this.jobs[this.companyName]; - const penalty = this.cancelationPenalty(); + const penalty = this.cancelationPenalty(); - const penaltyString = penalty === 0.5 ? 'half' : 'three-quarters' + const penaltyString = penalty === 0.5 ? "half" : "three-quarters"; - var elem = document.getElementById("work-in-progress-text"); - ReactDOM.render(<> - You are currently working as a {position} at {this.companyName} (Current Company Reputation: {Reputation(companyRep)})

    - You have been working for {convertTimeMsToTimeElapsedString(this.timeWorked)}

    - You have earned:

    - ({MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)})

    - {Reputation(this.workRepGained)} ({ReputationRate(this.workRepGainRate * CYCLES_PER_SEC)}) reputation for this company

    - {numeralWrapper.formatExp(this.workHackExpGained)} ({`${numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}) hacking exp

    - {numeralWrapper.formatExp(this.workStrExpGained)} ({`${numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}) strength exp
    - {numeralWrapper.formatExp(this.workDefExpGained)} ({`${numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}) defense exp
    - {numeralWrapper.formatExp(this.workDexExpGained)} ({`${numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}) dexterity exp
    - {numeralWrapper.formatExp(this.workAgiExpGained)} ({`${numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}) agility exp

    - {numeralWrapper.formatExp(this.workChaExpGained)} ({`${numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}) charisma exp

    - You will automatically finish after working for 8 hours. You can cancel earlier if you wish, - but you will only gain {penaltyString} of the reputation you've earned so far. - , elem); + var elem = document.getElementById("work-in-progress-text"); + ReactDOM.render( + <> + You are currently working as a {position} at {this.companyName} (Current + Company Reputation: {Reputation(companyRep)})
    +
    + You have been working for{" "} + {convertTimeMsToTimeElapsedString(this.timeWorked)} +
    +
    + You have earned:
    +
    + ( + {MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)})
    +
    + {Reputation(this.workRepGained)} ( + {ReputationRate(this.workRepGainRate * CYCLES_PER_SEC)}) reputation for + this company
    +
    + {numeralWrapper.formatExp(this.workHackExpGained)} ( + {`${numeralWrapper.formatExp( + this.workHackExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) hacking exp
    +
    + {numeralWrapper.formatExp(this.workStrExpGained)} ( + {`${numeralWrapper.formatExp( + this.workStrExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) strength exp
    + {numeralWrapper.formatExp(this.workDefExpGained)} ( + {`${numeralWrapper.formatExp( + this.workDefExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) defense exp
    + {numeralWrapper.formatExp(this.workDexExpGained)} ( + {`${numeralWrapper.formatExp( + this.workDexExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) dexterity exp
    + {numeralWrapper.formatExp(this.workAgiExpGained)} ( + {`${numeralWrapper.formatExp( + this.workAgiExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) agility exp
    +
    + {numeralWrapper.formatExp(this.workChaExpGained)} ( + {`${numeralWrapper.formatExp( + this.workChaExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) charisma exp
    +
    + You will automatically finish after working for 8 hours. You can cancel + earlier if you wish, but you will only gain {penaltyString} of the + reputation you've earned so far. + , + elem, + ); } -export function finishWork(cancelled, sing=false) { - //Since the work was cancelled early, player only gains half of what they've earned so far - if (cancelled) { - this.workRepGained *= this.cancelationPenalty(); - } +export function finishWork(cancelled, sing = false) { + //Since the work was cancelled early, player only gains half of what they've earned so far + if (cancelled) { + this.workRepGained *= this.cancelationPenalty(); + } - const company = Companies[this.companyName]; - company.playerReputation += (this.workRepGained); + const company = Companies[this.companyName]; + company.playerReputation += this.workRepGained; - this.updateSkillLevels(); + this.updateSkillLevels(); - let content = <> - You earned a total of:
    -
    - {Reputation(this.workRepGained)} reputation for the company
    - {numeralWrapper.formatExp(this.workHackExpGained)} hacking exp
    - {numeralWrapper.formatExp(this.workStrExpGained)} strength exp
    - {numeralWrapper.formatExp(this.workDefExpGained)} defense exp
    - {numeralWrapper.formatExp(this.workDexExpGained)} dexterity exp
    - {numeralWrapper.formatExp(this.workAgiExpGained)} agility exp
    - {numeralWrapper.formatExp(this.workChaExpGained)} charisma exp
    + let content = ( + <> + You earned a total of:
    + +
    + {Reputation(this.workRepGained)} reputation for the company
    + {numeralWrapper.formatExp(this.workHackExpGained)} hacking exp
    + {numeralWrapper.formatExp(this.workStrExpGained)} strength exp
    + {numeralWrapper.formatExp(this.workDefExpGained)} defense exp
    + {numeralWrapper.formatExp(this.workDexExpGained)} dexterity exp
    + {numeralWrapper.formatExp(this.workAgiExpGained)} agility exp
    + {numeralWrapper.formatExp(this.workChaExpGained)} charisma exp +
    + ); - if (cancelled) { - content = <> - You worked a short shift of {convertTimeMsToTimeElapsedString(this.timeWorked)}

    - Since you cancelled your work early, you only gained half of the reputation you earned.

    {content} - - } else { - content = <>You worked a full shift of 8 hours!

    {content}; - } - if (!sing) {dialogBoxCreate(content);} + if (cancelled) { + content = ( + <> + You worked a short shift of{" "} + {convertTimeMsToTimeElapsedString(this.timeWorked)}
    +
    + Since you cancelled your work early, you only gained half of the + reputation you earned.
    +
    + {content} + + ); + } else { + content = ( + <> + You worked a full shift of 8 hours!
    +
    + {content} + + ); + } + if (!sing) { + dialogBoxCreate(content); + } - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - this.isWorking = false; - Engine.loadLocationContent(false); + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + this.isWorking = false; + Engine.loadLocationContent(false); - if (sing) { - var res = "You worked a short shift of " + convertTimeMsToTimeElapsedString(this.timeWorked) + " and " + - "earned $" + numeralWrapper.formatMoney(this.workMoneyGained) + ", " + - numeralWrapper.formatReputation(this.workRepGained) + " reputation, " + - numeralWrapper.formatExp(this.workHackExpGained) + " hacking exp, " + - numeralWrapper.formatExp(this.workStrExpGained) + " strength exp, " + - numeralWrapper.formatExp(this.workDefExpGained) + " defense exp, " + - numeralWrapper.formatExp(this.workDexExpGained) + " dexterity exp, " + - numeralWrapper.formatExp(this.workAgiExpGained) + " agility exp, and " + - numeralWrapper.formatExp(this.workChaExpGained) + " charisma exp."; - this.resetWorkStatus(); - return res; - } + if (sing) { + var res = + "You worked a short shift of " + + convertTimeMsToTimeElapsedString(this.timeWorked) + + " and " + + "earned $" + + numeralWrapper.formatMoney(this.workMoneyGained) + + ", " + + numeralWrapper.formatReputation(this.workRepGained) + + " reputation, " + + numeralWrapper.formatExp(this.workHackExpGained) + + " hacking exp, " + + numeralWrapper.formatExp(this.workStrExpGained) + + " strength exp, " + + numeralWrapper.formatExp(this.workDefExpGained) + + " defense exp, " + + numeralWrapper.formatExp(this.workDexExpGained) + + " dexterity exp, " + + numeralWrapper.formatExp(this.workAgiExpGained) + + " agility exp, and " + + numeralWrapper.formatExp(this.workChaExpGained) + + " charisma exp."; this.resetWorkStatus(); + return res; + } + this.resetWorkStatus(); } export function startWorkPartTime(companyName) { - this.resetWorkStatus(CONSTANTS.WorkTypeCompanyPartTime, companyName); - this.isWorking = true; - this.focus = true; - this.companyName = companyName; - this.workType = CONSTANTS.WorkTypeCompanyPartTime; + this.resetWorkStatus(CONSTANTS.WorkTypeCompanyPartTime, companyName); + this.isWorking = true; + this.focus = true; + this.companyName = companyName; + this.workType = CONSTANTS.WorkTypeCompanyPartTime; - this.workHackExpGainRate = this.getWorkHackExpGain(); - this.workStrExpGainRate = this.getWorkStrExpGain(); - this.workDefExpGainRate = this.getWorkDefExpGain(); - this.workDexExpGainRate = this.getWorkDexExpGain(); - this.workAgiExpGainRate = this.getWorkAgiExpGain(); - this.workChaExpGainRate = this.getWorkChaExpGain(); - this.workRepGainRate = this.getWorkRepGain(); - this.workMoneyGainRate = this.getWorkMoneyGain(); + this.workHackExpGainRate = this.getWorkHackExpGain(); + this.workStrExpGainRate = this.getWorkStrExpGain(); + this.workDefExpGainRate = this.getWorkDefExpGain(); + this.workDexExpGainRate = this.getWorkDexExpGain(); + this.workAgiExpGainRate = this.getWorkAgiExpGain(); + this.workChaExpGainRate = this.getWorkChaExpGain(); + this.workRepGainRate = this.getWorkRepGain(); + this.workMoneyGainRate = this.getWorkMoneyGain(); - this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; + this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer8Hours; - var newCancelButton = clearEventListeners("work-in-progress-cancel-button"); - newCancelButton.innerHTML = "Stop Working"; - newCancelButton.addEventListener("click", () => { - this.finishWorkPartTime(); - return false; - }); + var newCancelButton = clearEventListeners("work-in-progress-cancel-button"); + newCancelButton.innerHTML = "Stop Working"; + newCancelButton.addEventListener("click", () => { + this.finishWorkPartTime(); + return false; + }); - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); } export function workPartTime(numCycles) { - //Cap the number of cycles being processed to whatever would put you at the - //work time limit (8 hours) - var overMax = false; - if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer8Hours) { - overMax = true; - numCycles = Math.round((CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed); - } - this.timeWorked += Engine._idleSpeed * numCycles; + //Cap the number of cycles being processed to whatever would put you at the + //work time limit (8 hours) + var overMax = false; + if ( + this.timeWorked + Engine._idleSpeed * numCycles >= + CONSTANTS.MillisecondsPer8Hours + ) { + overMax = true; + numCycles = Math.round( + (CONSTANTS.MillisecondsPer8Hours - this.timeWorked) / Engine._idleSpeed, + ); + } + this.timeWorked += Engine._idleSpeed * numCycles; - this.workRepGainRate = this.getWorkRepGain(); - this.processWorkEarnings(numCycles); + this.workRepGainRate = this.getWorkRepGain(); + this.processWorkEarnings(numCycles); - //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money - if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { - return this.finishWorkPartTime(); - } + //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money + if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) { + return this.finishWorkPartTime(); + } - var comp = Companies[this.companyName], companyRep = "0"; - if (comp == null || !(comp instanceof Company)) { - console.error(`Could not find Company: ${this.companyName}`); - } else { - companyRep = comp.playerReputation; - } + var comp = Companies[this.companyName], + companyRep = "0"; + if (comp == null || !(comp instanceof Company)) { + console.error(`Could not find Company: ${this.companyName}`); + } else { + companyRep = comp.playerReputation; + } - const position = this.jobs[this.companyName]; + const position = this.jobs[this.companyName]; - const elem = document.getElementById("work-in-progress-text"); - ReactDOM.render(<> - You are currently working as a {position} at {this.companyName} (Current Company Reputation: {Reputation(companyRep)})

    - You have been working for {convertTimeMsToTimeElapsedString(this.timeWorked)}

    - You have earned:

    - ({MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)})

    - {Reputation(this.workRepGained)} ({Reputation(`${numeralWrapper.formatExp(this.workRepGainRate * CYCLES_PER_SEC)} / sec`)}) reputation for this company

    - {numeralWrapper.formatExp(this.workHackExpGained)} ({`${numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec`}) hacking exp

    - {numeralWrapper.formatExp(this.workStrExpGained)} ({`${numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec`}) strength exp
    - {numeralWrapper.formatExp(this.workDefExpGained)} ({`${numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec`}) defense exp
    - {numeralWrapper.formatExp(this.workDexExpGained)} ({`${numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec`}) dexterity exp
    - {numeralWrapper.formatExp(this.workAgiExpGained)} ({`${numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}) agility exp

    - {numeralWrapper.formatExp(this.workChaExpGained)} ({`${numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}) charisma exp

    - You will automatically finish after working for 8 hours. You can cancel earlier if you wish, and there will be no penalty because this is a part-time job. - , elem); + const elem = document.getElementById("work-in-progress-text"); + ReactDOM.render( + <> + You are currently working as a {position} at {this.companyName} (Current + Company Reputation: {Reputation(companyRep)})
    +
    + You have been working for{" "} + {convertTimeMsToTimeElapsedString(this.timeWorked)} +
    +
    + You have earned:
    +
    + ( + {MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)})
    +
    + {Reputation(this.workRepGained)} ( + {Reputation( + `${numeralWrapper.formatExp( + this.workRepGainRate * CYCLES_PER_SEC, + )} / sec`, + )} + ) reputation for this company
    +
    + {numeralWrapper.formatExp(this.workHackExpGained)} ( + {`${numeralWrapper.formatExp( + this.workHackExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) hacking exp
    +
    + {numeralWrapper.formatExp(this.workStrExpGained)} ( + {`${numeralWrapper.formatExp( + this.workStrExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) strength exp
    + {numeralWrapper.formatExp(this.workDefExpGained)} ( + {`${numeralWrapper.formatExp( + this.workDefExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) defense exp
    + {numeralWrapper.formatExp(this.workDexExpGained)} ( + {`${numeralWrapper.formatExp( + this.workDexExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) dexterity exp
    + {numeralWrapper.formatExp(this.workAgiExpGained)} ( + {`${numeralWrapper.formatExp( + this.workAgiExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) agility exp
    +
    + {numeralWrapper.formatExp(this.workChaExpGained)} ( + {`${numeralWrapper.formatExp( + this.workChaExpGainRate * CYCLES_PER_SEC, + )} / sec`} + ) charisma exp
    +
    + You will automatically finish after working for 8 hours. You can cancel + earlier if you wish, and there will be no penalty because this is a + part-time job. + , + elem, + ); } -export function finishWorkPartTime(sing=false) { - var company = Companies[this.companyName]; - company.playerReputation += (this.workRepGained); +export function finishWorkPartTime(sing = false) { + var company = Companies[this.companyName]; + company.playerReputation += this.workRepGained; - this.updateSkillLevels(); + this.updateSkillLevels(); - const content = <> - You worked for {convertTimeMsToTimeElapsedString(this.timeWorked)}

    - You earned a total of:
    -
    - {Reputation(this.workRepGained)} reputation for the company
    - {numeralWrapper.formatExp(this.workHackExpGained)} hacking exp
    - {numeralWrapper.formatExp(this.workStrExpGained)} strength exp
    - {numeralWrapper.formatExp(this.workDefExpGained)} defense exp
    - {numeralWrapper.formatExp(this.workDexExpGained)} dexterity exp
    - {numeralWrapper.formatExp(this.workAgiExpGained)} agility exp
    - {numeralWrapper.formatExp(this.workChaExpGained)} charisma exp
    - ; - if (!sing) {dialogBoxCreate(content);} + const content = ( + <> + You worked for {convertTimeMsToTimeElapsedString(this.timeWorked)} +
    +
    + You earned a total of:
    + +
    + {Reputation(this.workRepGained)} reputation for the company
    + {numeralWrapper.formatExp(this.workHackExpGained)} hacking exp
    + {numeralWrapper.formatExp(this.workStrExpGained)} strength exp
    + {numeralWrapper.formatExp(this.workDefExpGained)} defense exp
    + {numeralWrapper.formatExp(this.workDexExpGained)} dexterity exp
    + {numeralWrapper.formatExp(this.workAgiExpGained)} agility exp
    + {numeralWrapper.formatExp(this.workChaExpGained)} charisma exp +
    + + ); + if (!sing) { + dialogBoxCreate(content); + } - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - this.isWorking = false; - Engine.loadLocationContent(false); - if (sing) { - var res = "You worked for " + convertTimeMsToTimeElapsedString(this.timeWorked) + " and " + - "earned a total of " + - "$" + numeralWrapper.formatMoney(this.workMoneyGained) + ", " + - numeralWrapper.formatReputation(this.workRepGained) + " reputation, " + - numeralWrapper.formatExp(this.workHackExpGained) + " hacking exp, " + - numeralWrapper.formatExp(this.workStrExpGained) + " strength exp, " + - numeralWrapper.formatExp(this.workDefExpGained) + " defense exp, " + - numeralWrapper.formatExp(this.workDexExpGained) + " dexterity exp, " + - numeralWrapper.formatExp(this.workAgiExpGained) + " agility exp, and " + - numeralWrapper.formatExp(this.workChaExpGained) + " charisma exp"; - this.resetWorkStatus(); - return res; - } + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + this.isWorking = false; + Engine.loadLocationContent(false); + if (sing) { + var res = + "You worked for " + + convertTimeMsToTimeElapsedString(this.timeWorked) + + " and " + + "earned a total of " + + "$" + + numeralWrapper.formatMoney(this.workMoneyGained) + + ", " + + numeralWrapper.formatReputation(this.workRepGained) + + " reputation, " + + numeralWrapper.formatExp(this.workHackExpGained) + + " hacking exp, " + + numeralWrapper.formatExp(this.workStrExpGained) + + " strength exp, " + + numeralWrapper.formatExp(this.workDefExpGained) + + " defense exp, " + + numeralWrapper.formatExp(this.workDexExpGained) + + " dexterity exp, " + + numeralWrapper.formatExp(this.workAgiExpGained) + + " agility exp, and " + + numeralWrapper.formatExp(this.workChaExpGained) + + " charisma exp"; this.resetWorkStatus(); + return res; + } + this.resetWorkStatus(); } export function startFocusing() { - const mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "hidden"; - this.focus = true; - Engine.loadWorkInProgressContent(); + const mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "hidden"; + this.focus = true; + Engine.loadWorkInProgressContent(); } export function stopFocusing() { - const mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - this.focus = false; - Engine.loadTerminalContent(); + const mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + this.focus = false; + Engine.loadTerminalContent(); } /* Working for Faction */ export function startFactionWork(faction) { - //Update reputation gain rate to account for faction favor - var favorMult = 1 + (faction.favor / 100); - if (isNaN(favorMult)) {favorMult = 1;} - this.workRepGainRate *= favorMult; - this.workRepGainRate *= BitNodeMultipliers.FactionWorkRepGain; + //Update reputation gain rate to account for faction favor + var favorMult = 1 + faction.favor / 100; + if (isNaN(favorMult)) { + favorMult = 1; + } + this.workRepGainRate *= favorMult; + this.workRepGainRate *= BitNodeMultipliers.FactionWorkRepGain; - this.isWorking = true; - this.focus = true; - this.workType = CONSTANTS.WorkTypeFaction; - this.currentWorkFactionName = faction.name; + this.isWorking = true; + this.focus = true; + this.workType = CONSTANTS.WorkTypeFaction; + this.currentWorkFactionName = faction.name; - this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer20Hours; + this.timeNeededToCompleteWork = CONSTANTS.MillisecondsPer20Hours; - const cancelButton = clearEventListeners("work-in-progress-cancel-button"); - cancelButton.innerHTML = "Stop Faction Work"; - cancelButton.addEventListener("click", () => { - this.finishFactionWork(true); - return false; - }); + const cancelButton = clearEventListeners("work-in-progress-cancel-button"); + cancelButton.innerHTML = "Stop Faction Work"; + cancelButton.addEventListener("click", () => { + this.finishFactionWork(true); + return false; + }); - const focusButton = clearEventListeners("work-in-progress-something-else-button"); - focusButton.style.visibility = "visible"; - focusButton.innerHTML = "Do something else simultaneously"; - focusButton.addEventListener("click", () => { - this.stopFocusing(); - return false; - }); + const focusButton = clearEventListeners( + "work-in-progress-something-else-button", + ); + focusButton.style.visibility = "visible"; + focusButton.innerHTML = "Do something else simultaneously"; + focusButton.addEventListener("click", () => { + this.stopFocusing(); + return false; + }); - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); } export function startFactionHackWork(faction) { - this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkHacking); + this.resetWorkStatus( + CONSTANTS.WorkTypeFaction, + faction.name, + CONSTANTS.FactionWorkHacking, + ); - this.workHackExpGainRate = .15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workRepGainRate = (this.hacking_skill + this.intelligence) / CONSTANTS.MaxSkillLevel * this.faction_rep_mult * this.getIntelligenceBonus(0.5); + this.workHackExpGainRate = + 0.15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workRepGainRate = + ((this.hacking_skill + this.intelligence) / CONSTANTS.MaxSkillLevel) * + this.faction_rep_mult * + this.getIntelligenceBonus(0.5); - this.factionWorkType = CONSTANTS.FactionWorkHacking; - this.currentWorkFactionDescription = "carrying out hacking contracts"; + this.factionWorkType = CONSTANTS.FactionWorkHacking; + this.currentWorkFactionDescription = "carrying out hacking contracts"; - this.startFactionWork(faction); + this.startFactionWork(faction); } export function startFactionFieldWork(faction) { - this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkField); + this.resetWorkStatus( + CONSTANTS.WorkTypeFaction, + faction.name, + CONSTANTS.FactionWorkField, + ); - this.workHackExpGainRate = .1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workStrExpGainRate = .1 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workDefExpGainRate = .1 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workDexExpGainRate = .1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workAgiExpGainRate = .1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workChaExpGainRate = .1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workRepGainRate = getFactionFieldWorkRepGain(this, faction); + this.workHackExpGainRate = + 0.1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workStrExpGainRate = + 0.1 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workDefExpGainRate = + 0.1 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workDexExpGainRate = + 0.1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workAgiExpGainRate = + 0.1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workChaExpGainRate = + 0.1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workRepGainRate = getFactionFieldWorkRepGain(this, faction); - this.factionWorkType = CONSTANTS.FactionWorkField; - this.currentWorkFactionDescription = "carrying out field missions" + this.factionWorkType = CONSTANTS.FactionWorkField; + this.currentWorkFactionDescription = "carrying out field missions"; - this.startFactionWork(faction); + this.startFactionWork(faction); } export function startFactionSecurityWork(faction) { - this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkSecurity); + this.resetWorkStatus( + CONSTANTS.WorkTypeFaction, + faction.name, + CONSTANTS.FactionWorkSecurity, + ); - this.workHackExpGainRate = 0.05 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workStrExpGainRate = 0.15 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workDefExpGainRate = 0.15 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workDexExpGainRate = 0.15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workAgiExpGainRate = 0.15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workChaExpGainRate = 0.00 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.workRepGainRate = getFactionSecurityWorkRepGain(this, faction); + this.workHackExpGainRate = + 0.05 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workStrExpGainRate = + 0.15 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workDefExpGainRate = + 0.15 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workDexExpGainRate = + 0.15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workAgiExpGainRate = + 0.15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workChaExpGainRate = + 0.0 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.workRepGainRate = getFactionSecurityWorkRepGain(this, faction); - this.factionWorkType = CONSTANTS.FactionWorkSecurity; - this.currentWorkFactionDescription = "performing security detail" + this.factionWorkType = CONSTANTS.FactionWorkSecurity; + this.currentWorkFactionDescription = "performing security detail"; - this.startFactionWork(faction); + this.startFactionWork(faction); } export function workForFaction(numCycles) { - const faction = Factions[this.currentWorkFactionName]; + const faction = Factions[this.currentWorkFactionName]; - //Constantly update the rep gain rate - switch (this.factionWorkType) { - case CONSTANTS.FactionWorkHacking: - this.workRepGainRate = getHackingWorkRepGain(this, faction); - break; - case CONSTANTS.FactionWorkField: - this.workRepGainRate = getFactionFieldWorkRepGain(this, faction); - break; - case CONSTANTS.FactionWorkSecurity: - this.workRepGainRate = getFactionSecurityWorkRepGain(this, faction); - break; - default: - break; - } + //Constantly update the rep gain rate + switch (this.factionWorkType) { + case CONSTANTS.FactionWorkHacking: + this.workRepGainRate = getHackingWorkRepGain(this, faction); + break; + case CONSTANTS.FactionWorkField: + this.workRepGainRate = getFactionFieldWorkRepGain(this, faction); + break; + case CONSTANTS.FactionWorkSecurity: + this.workRepGainRate = getFactionSecurityWorkRepGain(this, faction); + break; + default: + break; + } - //Cap the number of cycles being processed to whatever would put you at limit (20 hours) - var overMax = false; - if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer20Hours) { - overMax = true; - numCycles = Math.round((CONSTANTS.MillisecondsPer20Hours - this.timeWorked) / Engine._idleSpeed); - } - this.timeWorked += Engine._idleSpeed * numCycles; + //Cap the number of cycles being processed to whatever would put you at limit (20 hours) + var overMax = false; + if ( + this.timeWorked + Engine._idleSpeed * numCycles >= + CONSTANTS.MillisecondsPer20Hours + ) { + overMax = true; + numCycles = Math.round( + (CONSTANTS.MillisecondsPer20Hours - this.timeWorked) / Engine._idleSpeed, + ); + } + this.timeWorked += Engine._idleSpeed * numCycles; - this.processWorkEarnings(numCycles); + this.processWorkEarnings(numCycles); - //If timeWorked == 20 hours, then finish. You can only work for the faction for 20 hours - if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer20Hours) { - return this.finishFactionWork(false); - } + //If timeWorked == 20 hours, then finish. You can only work for the faction for 20 hours + if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer20Hours) { + return this.finishFactionWork(false); + } - const elem = document.getElementById("work-in-progress-text"); - ReactDOM.render(<>You are currently {this.currentWorkFactionDescription} for your faction {faction.name}
    - (Current Faction Reputation: {Reputation(faction.playerReputation)}).
    - You have been doing this for {convertTimeMsToTimeElapsedString(this.timeWorked)}

    - You have earned:

    - ({MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)})

    - {Reputation(this.workRepGained)} ({ReputationRate(this.workRepGainRate * CYCLES_PER_SEC)}) reputation for this faction

    - {numeralWrapper.formatExp(this.workHackExpGained)} ({numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp

    - {numeralWrapper.formatExp(this.workStrExpGained)} ({numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp
    - {numeralWrapper.formatExp(this.workDefExpGained)} ({numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp
    - {numeralWrapper.formatExp(this.workDexExpGained)} ({numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp
    - {numeralWrapper.formatExp(this.workAgiExpGained)} ({numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp

    - {numeralWrapper.formatExp(this.workChaExpGained)} ({numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp

    - - You will automatically finish after working for 20 hours. You can cancel earlier if you wish.
    - There is no penalty for cancelling earlier., elem) + const elem = document.getElementById("work-in-progress-text"); + ReactDOM.render( + <> + You are currently {this.currentWorkFactionDescription} for your faction{" "} + {faction.name} +
    + (Current Faction Reputation: {Reputation(faction.playerReputation)}).{" "} +
    + You have been doing this for{" "} + {convertTimeMsToTimeElapsedString(this.timeWorked)} +
    +
    + You have earned:
    +
    + ( + {MoneyRate(this.workMoneyGainRate * CYCLES_PER_SEC)})
    +
    + {Reputation(this.workRepGained)} ( + {ReputationRate(this.workRepGainRate * CYCLES_PER_SEC)}) reputation for + this faction
    +
    + {numeralWrapper.formatExp(this.workHackExpGained)} ( + {numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / + sec) hacking exp
    +
    + {numeralWrapper.formatExp(this.workStrExpGained)} ( + {numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / + sec) strength exp
    + {numeralWrapper.formatExp(this.workDefExpGained)} ( + {numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / + sec) defense exp
    + {numeralWrapper.formatExp(this.workDexExpGained)} ( + {numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / + sec) dexterity exp
    + {numeralWrapper.formatExp(this.workAgiExpGained)} ( + {numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / + sec) agility exp
    +
    + {numeralWrapper.formatExp(this.workChaExpGained)} ( + {numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / + sec) charisma exp
    +
    + You will automatically finish after working for 20 hours. You can cancel + earlier if you wish. +
    + There is no penalty for cancelling earlier. + , + elem, + ); } -export function finishFactionWork(cancelled, sing=false) { - var faction = Factions[this.currentWorkFactionName]; - faction.playerReputation += (this.workRepGained); +export function finishFactionWork(cancelled, sing = false) { + var faction = Factions[this.currentWorkFactionName]; + faction.playerReputation += this.workRepGained; - this.updateSkillLevels(); + this.updateSkillLevels(); - if (!sing) { - dialogBoxCreate(<> - You worked for your faction {faction.name} for a total of {convertTimeMsToTimeElapsedString(this.timeWorked)}

    - You earned a total of:
    -
    - {Reputation(this.workRepGained)} reputation for the faction
    - {numeralWrapper.formatExp(this.workHackExpGained)} hacking exp
    - {numeralWrapper.formatExp(this.workStrExpGained)} strength exp
    - {numeralWrapper.formatExp(this.workDefExpGained)} defense exp
    - {numeralWrapper.formatExp(this.workDexExpGained)} dexterity exp
    - {numeralWrapper.formatExp(this.workAgiExpGained)} agility exp
    - {numeralWrapper.formatExp(this.workChaExpGained)} charisma exp
    - ); - } + if (!sing) { + dialogBoxCreate( + <> + You worked for your faction {faction.name} for a total of{" "} + {convertTimeMsToTimeElapsedString(this.timeWorked)}
    +
    + You earned a total of:
    + +
    + {Reputation(this.workRepGained)} reputation for the faction
    + {numeralWrapper.formatExp(this.workHackExpGained)} hacking exp
    + {numeralWrapper.formatExp(this.workStrExpGained)} strength exp
    + {numeralWrapper.formatExp(this.workDefExpGained)} defense exp
    + {numeralWrapper.formatExp(this.workDexExpGained)} dexterity exp
    + {numeralWrapper.formatExp(this.workAgiExpGained)} agility exp
    + {numeralWrapper.formatExp(this.workChaExpGained)} charisma exp +
    + , + ); + } - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; - this.isWorking = false; + this.isWorking = false; - Engine.loadFactionContent(); - displayFactionContent(faction.name); - if (sing) { - var res="You worked for your faction " + faction.name + " for a total of " + convertTimeMsToTimeElapsedString(this.timeWorked) + ". " + - "You earned " + - numeralWrapper.formatReputation(this.workRepGained) + " rep, " + - numeralWrapper.formatExp(this.workHackExpGained) + " hacking exp, " + - numeralWrapper.formatExp(this.workStrExpGained) + " str exp, " + - numeralWrapper.formatExp(this.workDefExpGained) + " def exp, " + - numeralWrapper.formatExp(this.workDexExpGained) + " dex exp, " + - numeralWrapper.formatExp(this.workAgiExpGained) + " agi exp, and " + - numeralWrapper.formatExp(this.workChaExpGained) + " cha exp."; - this.resetWorkStatus(); - return res; - } + Engine.loadFactionContent(); + displayFactionContent(faction.name); + if (sing) { + var res = + "You worked for your faction " + + faction.name + + " for a total of " + + convertTimeMsToTimeElapsedString(this.timeWorked) + + ". " + + "You earned " + + numeralWrapper.formatReputation(this.workRepGained) + + " rep, " + + numeralWrapper.formatExp(this.workHackExpGained) + + " hacking exp, " + + numeralWrapper.formatExp(this.workStrExpGained) + + " str exp, " + + numeralWrapper.formatExp(this.workDefExpGained) + + " def exp, " + + numeralWrapper.formatExp(this.workDexExpGained) + + " dex exp, " + + numeralWrapper.formatExp(this.workAgiExpGained) + + " agi exp, and " + + numeralWrapper.formatExp(this.workChaExpGained) + + " cha exp."; this.resetWorkStatus(); + return res; + } + this.resetWorkStatus(); } //Money gained per game cycle export function getWorkMoneyGain() { - // If player has SF-11, calculate salary multiplier from favor - let bn11Mult = 1; - const company = Companies[this.companyName]; - if (SourceFileFlags[11] > 0) { bn11Mult = 1 + (company.favor / 100); } + // If player has SF-11, calculate salary multiplier from favor + let bn11Mult = 1; + const company = Companies[this.companyName]; + if (SourceFileFlags[11] > 0) { + bn11Mult = 1 + company.favor / 100; + } - // Get base salary - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (companyPosition == null) { - console.error(`Could not find CompanyPosition object for ${companyPositionName}. Work salary will be 0`); - return 0; - } + // Get base salary + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (companyPosition == null) { + console.error( + `Could not find CompanyPosition object for ${companyPositionName}. Work salary will be 0`, + ); + return 0; + } - return companyPosition.baseSalary * company.salaryMultiplier * this.work_money_mult * BitNodeMultipliers.CompanyWorkMoney * bn11Mult; + return ( + companyPosition.baseSalary * + company.salaryMultiplier * + this.work_money_mult * + BitNodeMultipliers.CompanyWorkMoney * + bn11Mult + ); } //Hack exp gained per game cycle export function getWorkHackExpGain() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `Work hack exp gain will be 0`].join(" ")); - return 0; - } + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error( + [ + `Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `Work hack exp gain will be 0`, + ].join(" "), + ); + return 0; + } - return companyPosition.hackingExpGain * company.expMultiplier * this.hacking_exp_mult * BitNodeMultipliers.CompanyWorkExpGain; + return ( + companyPosition.hackingExpGain * + company.expMultiplier * + this.hacking_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain + ); } //Str exp gained per game cycle export function getWorkStrExpGain() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `Work str exp gain will be 0`].join(" ")); - return 0; - } + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error( + [ + `Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `Work str exp gain will be 0`, + ].join(" "), + ); + return 0; + } - return companyPosition.strengthExpGain * company.expMultiplier * this.strength_exp_mult * BitNodeMultipliers.CompanyWorkExpGain; + return ( + companyPosition.strengthExpGain * + company.expMultiplier * + this.strength_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain + ); } //Def exp gained per game cycle export function getWorkDefExpGain() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `Work def exp gain will be 0`].join(" ")); - return 0; - } + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error( + [ + `Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `Work def exp gain will be 0`, + ].join(" "), + ); + return 0; + } - return companyPosition.defenseExpGain * company.expMultiplier * this.defense_exp_mult * BitNodeMultipliers.CompanyWorkExpGain; + return ( + companyPosition.defenseExpGain * + company.expMultiplier * + this.defense_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain + ); } //Dex exp gained per game cycle export function getWorkDexExpGain() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `Work dex exp gain will be 0`].join(" ")); - return 0; - } + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error( + [ + `Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `Work dex exp gain will be 0`, + ].join(" "), + ); + return 0; + } - return companyPosition.dexterityExpGain * company.expMultiplier * this.dexterity_exp_mult * BitNodeMultipliers.CompanyWorkExpGain; + return ( + companyPosition.dexterityExpGain * + company.expMultiplier * + this.dexterity_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain + ); } //Agi exp gained per game cycle export function getWorkAgiExpGain() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `Work agi exp gain will be 0`].join(" ")); - return 0; - } + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error( + [ + `Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `Work agi exp gain will be 0`, + ].join(" "), + ); + return 0; + } - return companyPosition.agilityExpGain * company.expMultiplier * this.agility_exp_mult * BitNodeMultipliers.CompanyWorkExpGain; + return ( + companyPosition.agilityExpGain * + company.expMultiplier * + this.agility_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain + ); } //Charisma exp gained per game cycle export function getWorkChaExpGain() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `Work cha exp gain will be 0`].join(" ")); - return 0; - } + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error( + [ + `Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `Work cha exp gain will be 0`, + ].join(" "), + ); + return 0; + } - return companyPosition.charismaExpGain * company.expMultiplier * this.charisma_exp_mult * BitNodeMultipliers.CompanyWorkExpGain; + return ( + companyPosition.charismaExpGain * + company.expMultiplier * + this.charisma_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain + ); } //Reputation gained per game cycle export function getWorkRepGain() { - const company = Companies[this.companyName]; - const companyPositionName = this.jobs[this.companyName]; - const companyPosition = CompanyPositions[companyPositionName]; - if (company == null || companyPosition == null) { - console.error([`Could not find Company object for ${this.companyName}`, - `or CompanyPosition object for ${companyPositionName}.`, - `Work rep gain will be 0`].join(" ")); - return 0; - } + const company = Companies[this.companyName]; + const companyPositionName = this.jobs[this.companyName]; + const companyPosition = CompanyPositions[companyPositionName]; + if (company == null || companyPosition == null) { + console.error( + [ + `Could not find Company object for ${this.companyName}`, + `or CompanyPosition object for ${companyPositionName}.`, + `Work rep gain will be 0`, + ].join(" "), + ); + return 0; + } - var jobPerformance = companyPosition.calculateJobPerformance(this.hacking_skill, this.strength, - this.defense, this.dexterity, - this.agility, this.charisma); + var jobPerformance = companyPosition.calculateJobPerformance( + this.hacking_skill, + this.strength, + this.defense, + this.dexterity, + this.agility, + this.charisma, + ); - //Intelligence provides a flat bonus to job performance - jobPerformance += (this.intelligence / CONSTANTS.MaxSkillLevel); + //Intelligence provides a flat bonus to job performance + jobPerformance += this.intelligence / CONSTANTS.MaxSkillLevel; - //Update reputation gain rate to account for company favor - var favorMult = 1 + (company.favor / 100); - if (isNaN(favorMult)) { favorMult = 1; } - return jobPerformance * this.company_rep_mult * favorMult; + //Update reputation gain rate to account for company favor + var favorMult = 1 + company.favor / 100; + if (isNaN(favorMult)) { + favorMult = 1; + } + return jobPerformance * this.company_rep_mult * favorMult; } // export function getFactionSecurityWorkRepGain() { @@ -1125,784 +1547,1127 @@ export function getWorkRepGain() { /* Creating a Program */ export function startCreateProgramWork(programName, time, reqLevel) { - this.resetWorkStatus(); - this.isWorking = true; - this.focus = true; - this.workType = CONSTANTS.WorkTypeCreateProgram; + this.resetWorkStatus(); + this.isWorking = true; + this.focus = true; + this.workType = CONSTANTS.WorkTypeCreateProgram; - //Time needed to complete work affected by hacking skill (linearly based on - //ratio of (your skill - required level) to MAX skill) - //var timeMultiplier = (CONSTANTS.MaxSkillLevel - (this.hacking_skill - reqLevel)) / CONSTANTS.MaxSkillLevel; - //if (timeMultiplier > 1) {timeMultiplier = 1;} - //if (timeMultiplier < 0.01) {timeMultiplier = 0.01;} - this.createProgramReqLvl = reqLevel; + //Time needed to complete work affected by hacking skill (linearly based on + //ratio of (your skill - required level) to MAX skill) + //var timeMultiplier = (CONSTANTS.MaxSkillLevel - (this.hacking_skill - reqLevel)) / CONSTANTS.MaxSkillLevel; + //if (timeMultiplier > 1) {timeMultiplier = 1;} + //if (timeMultiplier < 0.01) {timeMultiplier = 0.01;} + this.createProgramReqLvl = reqLevel; - this.timeNeededToCompleteWork = time; - //Check for incomplete program - for (var i = 0; i < this.getHomeComputer().programs.length; ++i) { - var programFile = this.getHomeComputer().programs[i]; - if (programFile.startsWith(programName) && programFile.endsWith("%-INC")) { - var res = programFile.split("-"); - if (res.length != 3) {break;} - var percComplete = Number(res[1].slice(0, -1)); - if (isNaN(percComplete) || percComplete < 0 || percComplete >= 100) {break;} - this.timeWorkedCreateProgram = percComplete / 100 * this.timeNeededToCompleteWork; - this.getHomeComputer().programs.splice(i, 1); - } + this.timeNeededToCompleteWork = time; + //Check for incomplete program + for (var i = 0; i < this.getHomeComputer().programs.length; ++i) { + var programFile = this.getHomeComputer().programs[i]; + if (programFile.startsWith(programName) && programFile.endsWith("%-INC")) { + var res = programFile.split("-"); + if (res.length != 3) { + break; + } + var percComplete = Number(res[1].slice(0, -1)); + if (isNaN(percComplete) || percComplete < 0 || percComplete >= 100) { + break; + } + this.timeWorkedCreateProgram = + (percComplete / 100) * this.timeNeededToCompleteWork; + this.getHomeComputer().programs.splice(i, 1); } + } - this.createProgramName = programName; + this.createProgramName = programName; - var cancelButton = clearEventListeners("work-in-progress-cancel-button"); - cancelButton.innerHTML = "Cancel work on creating program"; - cancelButton.addEventListener("click", () => { - this.finishCreateProgramWork(true); - return false; - }); + var cancelButton = clearEventListeners("work-in-progress-cancel-button"); + cancelButton.innerHTML = "Cancel work on creating program"; + cancelButton.addEventListener("click", () => { + this.finishCreateProgramWork(true); + return false; + }); - const focusButton = clearEventListeners("work-in-progress-something-else-button"); - focusButton.style.visibility = "hidden"; + const focusButton = clearEventListeners( + "work-in-progress-something-else-button", + ); + focusButton.style.visibility = "hidden"; - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); } export function createProgramWork(numCycles) { - //Higher hacking skill will allow you to create programs faster - var reqLvl = this.createProgramReqLvl; - var skillMult = (this.hacking_skill / reqLvl) * this.getIntelligenceBonus(3); //This should always be greater than 1; - skillMult = 1 + ((skillMult - 1) / 5); //The divider constant can be adjusted as necessary + //Higher hacking skill will allow you to create programs faster + var reqLvl = this.createProgramReqLvl; + var skillMult = (this.hacking_skill / reqLvl) * this.getIntelligenceBonus(3); //This should always be greater than 1; + skillMult = 1 + (skillMult - 1) / 5; //The divider constant can be adjusted as necessary - //Skill multiplier directly applied to "time worked" - this.timeWorked += (Engine._idleSpeed * numCycles); - this.timeWorkedCreateProgram += (Engine._idleSpeed * numCycles * skillMult); - var programName = this.createProgramName; + //Skill multiplier directly applied to "time worked" + this.timeWorked += Engine._idleSpeed * numCycles; + this.timeWorkedCreateProgram += Engine._idleSpeed * numCycles * skillMult; + var programName = this.createProgramName; - if (this.timeWorkedCreateProgram >= this.timeNeededToCompleteWork) { - this.finishCreateProgramWork(false); - } + if (this.timeWorkedCreateProgram >= this.timeNeededToCompleteWork) { + this.finishCreateProgramWork(false); + } - const elem = document.getElementById("work-in-progress-text"); - ReactDOM.render(<> - You are currently working on coding {programName}.

    - You have been working for {convertTimeMsToTimeElapsedString(this.timeWorked)}

    - The program is {(this.timeWorkedCreateProgram / this.timeNeededToCompleteWork * 100).toFixed(2)}% complete.
    - If you cancel, your work will be saved and you can come back to complete the program later. - , elem); + const elem = document.getElementById("work-in-progress-text"); + ReactDOM.render( + <> + You are currently working on coding {programName}.
    +
    + You have been working for{" "} + {convertTimeMsToTimeElapsedString(this.timeWorked)} +
    +
    + The program is{" "} + {( + (this.timeWorkedCreateProgram / this.timeNeededToCompleteWork) * + 100 + ).toFixed(2)} + % complete.
    + If you cancel, your work will be saved and you can come back to complete + the program later. + , + elem, + ); } export function finishCreateProgramWork(cancelled) { - var programName = this.createProgramName; - if (cancelled === false) { - dialogBoxCreate("You've finished creating " + programName + "!
    " + - "The new program can be found on your home computer."); + var programName = this.createProgramName; + if (cancelled === false) { + dialogBoxCreate( + "You've finished creating " + + programName + + "!
    " + + "The new program can be found on your home computer.", + ); - this.getHomeComputer().programs.push(programName); - } else { - var perc = (Math.floor(this.timeWorkedCreateProgram / this.timeNeededToCompleteWork * 10000)/100).toString(); - var incompleteName = programName + "-" + perc + "%-INC"; - this.getHomeComputer().programs.push(incompleteName); - } + this.getHomeComputer().programs.push(programName); + } else { + var perc = ( + Math.floor( + (this.timeWorkedCreateProgram / this.timeNeededToCompleteWork) * 10000, + ) / 100 + ).toString(); + var incompleteName = programName + "-" + perc + "%-INC"; + this.getHomeComputer().programs.push(incompleteName); + } - if (!cancelled) { - this.gainIntelligenceExp(this.createProgramReqLvl / CONSTANTS.IntelligenceProgramBaseExpGain); - } + if (!cancelled) { + this.gainIntelligenceExp( + this.createProgramReqLvl / CONSTANTS.IntelligenceProgramBaseExpGain, + ); + } - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; - this.isWorking = false; + this.isWorking = false; - Engine.loadTerminalContent(); - this.resetWorkStatus(); + Engine.loadTerminalContent(); + this.resetWorkStatus(); } /* Studying/Taking Classes */ export function startClass(costMult, expMult, className) { - this.resetWorkStatus(); - this.isWorking = true; - this.focus = true; - this.workType = CONSTANTS.WorkTypeStudyClass; + this.resetWorkStatus(); + this.isWorking = true; + this.focus = true; + this.workType = CONSTANTS.WorkTypeStudyClass; - this.className = className; + this.className = className; - const gameCPS = 1000 / Engine._idleSpeed; + const gameCPS = 1000 / Engine._idleSpeed; - //Find cost and exp gain per game cycle - var cost = 0; - var hackExp = 0, strExp = 0, defExp = 0, dexExp = 0, agiExp = 0, chaExp = 0; - const hashManager = this.hashManager; - switch (className) { - case CONSTANTS.ClassStudyComputerScience: - hackExp = CONSTANTS.ClassStudyComputerScienceBaseExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassDataStructures: - cost = CONSTANTS.ClassDataStructuresBaseCost * costMult / gameCPS; - hackExp = CONSTANTS.ClassDataStructuresBaseExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassNetworks: - cost = CONSTANTS.ClassNetworksBaseCost * costMult / gameCPS; - hackExp = CONSTANTS.ClassNetworksBaseExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassAlgorithms: - cost = CONSTANTS.ClassAlgorithmsBaseCost * costMult / gameCPS; - hackExp = CONSTANTS.ClassAlgorithmsBaseExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassManagement: - cost = CONSTANTS.ClassManagementBaseCost * costMult / gameCPS; - chaExp = CONSTANTS.ClassManagementBaseExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassLeadership: - cost = CONSTANTS.ClassLeadershipBaseCost * costMult / gameCPS; - chaExp = CONSTANTS.ClassLeadershipBaseExp * expMult / gameCPS * hashManager.getStudyMult(); - break; - case CONSTANTS.ClassGymStrength: - cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; - strExp = expMult / gameCPS * hashManager.getTrainingMult(); - break; - case CONSTANTS.ClassGymDefense: - cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; - defExp = expMult / gameCPS * hashManager.getTrainingMult(); - break; - case CONSTANTS.ClassGymDexterity: - cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; - dexExp = expMult / gameCPS * hashManager.getTrainingMult(); - break; - case CONSTANTS.ClassGymAgility: - cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS; - agiExp = expMult / gameCPS * hashManager.getTrainingMult(); - break; - default: - throw new Error("ERR: Invalid/unrecognized class name"); - return; - } + //Find cost and exp gain per game cycle + var cost = 0; + var hackExp = 0, + strExp = 0, + defExp = 0, + dexExp = 0, + agiExp = 0, + chaExp = 0; + const hashManager = this.hashManager; + switch (className) { + case CONSTANTS.ClassStudyComputerScience: + hackExp = + ((CONSTANTS.ClassStudyComputerScienceBaseExp * expMult) / gameCPS) * + hashManager.getStudyMult(); + break; + case CONSTANTS.ClassDataStructures: + cost = (CONSTANTS.ClassDataStructuresBaseCost * costMult) / gameCPS; + hackExp = + ((CONSTANTS.ClassDataStructuresBaseExp * expMult) / gameCPS) * + hashManager.getStudyMult(); + break; + case CONSTANTS.ClassNetworks: + cost = (CONSTANTS.ClassNetworksBaseCost * costMult) / gameCPS; + hackExp = + ((CONSTANTS.ClassNetworksBaseExp * expMult) / gameCPS) * + hashManager.getStudyMult(); + break; + case CONSTANTS.ClassAlgorithms: + cost = (CONSTANTS.ClassAlgorithmsBaseCost * costMult) / gameCPS; + hackExp = + ((CONSTANTS.ClassAlgorithmsBaseExp * expMult) / gameCPS) * + hashManager.getStudyMult(); + break; + case CONSTANTS.ClassManagement: + cost = (CONSTANTS.ClassManagementBaseCost * costMult) / gameCPS; + chaExp = + ((CONSTANTS.ClassManagementBaseExp * expMult) / gameCPS) * + hashManager.getStudyMult(); + break; + case CONSTANTS.ClassLeadership: + cost = (CONSTANTS.ClassLeadershipBaseCost * costMult) / gameCPS; + chaExp = + ((CONSTANTS.ClassLeadershipBaseExp * expMult) / gameCPS) * + hashManager.getStudyMult(); + break; + case CONSTANTS.ClassGymStrength: + cost = (CONSTANTS.ClassGymBaseCost * costMult) / gameCPS; + strExp = (expMult / gameCPS) * hashManager.getTrainingMult(); + break; + case CONSTANTS.ClassGymDefense: + cost = (CONSTANTS.ClassGymBaseCost * costMult) / gameCPS; + defExp = (expMult / gameCPS) * hashManager.getTrainingMult(); + break; + case CONSTANTS.ClassGymDexterity: + cost = (CONSTANTS.ClassGymBaseCost * costMult) / gameCPS; + dexExp = (expMult / gameCPS) * hashManager.getTrainingMult(); + break; + case CONSTANTS.ClassGymAgility: + cost = (CONSTANTS.ClassGymBaseCost * costMult) / gameCPS; + agiExp = (expMult / gameCPS) * hashManager.getTrainingMult(); + break; + default: + throw new Error("ERR: Invalid/unrecognized class name"); + return; + } - this.workMoneyLossRate = cost; - this.workHackExpGainRate = hackExp * this.hacking_exp_mult * BitNodeMultipliers.ClassGymExpGain; - this.workStrExpGainRate = strExp * this.strength_exp_mult * BitNodeMultipliers.ClassGymExpGain; - this.workDefExpGainRate = defExp * this.defense_exp_mult * BitNodeMultipliers.ClassGymExpGain; - this.workDexExpGainRate = dexExp * this.dexterity_exp_mult * BitNodeMultipliers.ClassGymExpGain; - this.workAgiExpGainRate = agiExp * this.agility_exp_mult * BitNodeMultipliers.ClassGymExpGain; - this.workChaExpGainRate = chaExp * this.charisma_exp_mult * BitNodeMultipliers.ClassGymExpGain; + this.workMoneyLossRate = cost; + this.workHackExpGainRate = + hackExp * this.hacking_exp_mult * BitNodeMultipliers.ClassGymExpGain; + this.workStrExpGainRate = + strExp * this.strength_exp_mult * BitNodeMultipliers.ClassGymExpGain; + this.workDefExpGainRate = + defExp * this.defense_exp_mult * BitNodeMultipliers.ClassGymExpGain; + this.workDexExpGainRate = + dexExp * this.dexterity_exp_mult * BitNodeMultipliers.ClassGymExpGain; + this.workAgiExpGainRate = + agiExp * this.agility_exp_mult * BitNodeMultipliers.ClassGymExpGain; + this.workChaExpGainRate = + chaExp * this.charisma_exp_mult * BitNodeMultipliers.ClassGymExpGain; - var cancelButton = clearEventListeners("work-in-progress-cancel-button"); - if (className == CONSTANTS.ClassGymStrength || - className == CONSTANTS.ClassGymDefense || - className == CONSTANTS.ClassGymDexterity || - className == CONSTANTS.ClassGymAgility) { - cancelButton.innerHTML = "Stop training at gym"; - } else { - cancelButton.innerHTML = "Stop taking course"; - } - cancelButton.addEventListener("click", () => { - this.finishClass(); - return false; - }); + var cancelButton = clearEventListeners("work-in-progress-cancel-button"); + if ( + className == CONSTANTS.ClassGymStrength || + className == CONSTANTS.ClassGymDefense || + className == CONSTANTS.ClassGymDexterity || + className == CONSTANTS.ClassGymAgility + ) { + cancelButton.innerHTML = "Stop training at gym"; + } else { + cancelButton.innerHTML = "Stop taking course"; + } + cancelButton.addEventListener("click", () => { + this.finishClass(); + return false; + }); - const focusButton = clearEventListeners("work-in-progress-something-else-button"); - focusButton.style.visibility = "hidden"; + const focusButton = clearEventListeners( + "work-in-progress-something-else-button", + ); + focusButton.style.visibility = "hidden"; - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); } export function takeClass(numCycles) { - this.timeWorked += Engine._idleSpeed * numCycles; - var className = this.className; + this.timeWorked += Engine._idleSpeed * numCycles; + var className = this.className; - this.processWorkEarnings(numCycles); - - const elem = document.getElementById("work-in-progress-text"); - ReactDOM.render(<> - You have been {className} for {convertTimeMsToTimeElapsedString(this.timeWorked)}

    - This has cost you:
    - ({MoneyRate(this.workMoneyLossRate * CYCLES_PER_SEC)})

    - You have gained:
    - {numeralWrapper.formatExp(this.workHackExpGained)} ({numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / sec) hacking exp
    - {numeralWrapper.formatExp(this.workStrExpGained)} ({numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / sec) strength exp
    - {numeralWrapper.formatExp(this.workDefExpGained)} ({numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / sec) defense exp
    - {numeralWrapper.formatExp(this.workDexExpGained)} ({numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / sec) dexterity exp
    - {numeralWrapper.formatExp(this.workAgiExpGained)} ({numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec) agility exp
    - {numeralWrapper.formatExp(this.workChaExpGained)} ({numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec) charisma exp
    - You may cancel at any time - , elem); + this.processWorkEarnings(numCycles); + + const elem = document.getElementById("work-in-progress-text"); + ReactDOM.render( + <> + You have been {className} for{" "} + {convertTimeMsToTimeElapsedString(this.timeWorked)} +
    +
    + This has cost you:
    + ( + {MoneyRate(this.workMoneyLossRate * CYCLES_PER_SEC)})
    +
    + You have gained:
    + {numeralWrapper.formatExp(this.workHackExpGained)} ( + {numeralWrapper.formatExp(this.workHackExpGainRate * CYCLES_PER_SEC)} / + sec) hacking exp
    + {numeralWrapper.formatExp(this.workStrExpGained)} ( + {numeralWrapper.formatExp(this.workStrExpGainRate * CYCLES_PER_SEC)} / + sec) strength exp
    + {numeralWrapper.formatExp(this.workDefExpGained)} ( + {numeralWrapper.formatExp(this.workDefExpGainRate * CYCLES_PER_SEC)} / + sec) defense exp
    + {numeralWrapper.formatExp(this.workDexExpGained)} ( + {numeralWrapper.formatExp(this.workDexExpGainRate * CYCLES_PER_SEC)} / + sec) dexterity exp
    + {numeralWrapper.formatExp(this.workAgiExpGained)} ( + {numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / + sec) agility exp
    + {numeralWrapper.formatExp(this.workChaExpGained)} ( + {numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / + sec) charisma exp
    + You may cancel at any time + , + elem, + ); } //The 'sing' argument defines whether or not this function was called //through a Singularity Netscript function -export function finishClass(sing=false) { - this.gainIntelligenceExp(CONSTANTS.IntelligenceClassBaseExpGain * Math.round(this.timeWorked / 1000)); +export function finishClass(sing = false) { + this.gainIntelligenceExp( + CONSTANTS.IntelligenceClassBaseExpGain * Math.round(this.timeWorked / 1000), + ); - if (this.workMoneyGained > 0) { - throw new Error("ERR: Somehow gained money while taking class"); - } + if (this.workMoneyGained > 0) { + throw new Error("ERR: Somehow gained money while taking class"); + } - this.updateSkillLevels(); - if (!sing) { - dialogBoxCreate(<> - After {this.className} for {convertTimeMsToTimeElapsedString(this.timeWorked)},
    - you spent a total of .

    - You earned a total of:
    - {numeralWrapper.formatExp(this.workHackExpGained)} hacking exp
    - {numeralWrapper.formatExp(this.workStrExpGained)} strength exp
    - {numeralWrapper.formatExp(this.workDefExpGained)} defense exp
    - {numeralWrapper.formatExp(this.workDexExpGained)} dexterity exp
    - {numeralWrapper.formatExp(this.workAgiExpGained)} agility exp
    - {numeralWrapper.formatExp(this.workChaExpGained)} charisma exp
    - ); - } + this.updateSkillLevels(); + if (!sing) { + dialogBoxCreate( + <> + After {this.className} for{" "} + {convertTimeMsToTimeElapsedString(this.timeWorked)},
    + you spent a total of .
    +
    + You earned a total of:
    + {numeralWrapper.formatExp(this.workHackExpGained)} hacking exp
    + {numeralWrapper.formatExp(this.workStrExpGained)} strength exp
    + {numeralWrapper.formatExp(this.workDefExpGained)} defense exp
    + {numeralWrapper.formatExp(this.workDexExpGained)} dexterity exp
    + {numeralWrapper.formatExp(this.workAgiExpGained)} agility exp
    + {numeralWrapper.formatExp(this.workChaExpGained)} charisma exp +
    + , + ); + } - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; - this.isWorking = false; + this.isWorking = false; - Engine.loadLocationContent(false); - if (sing) { - var res="After " + this.className + " for " + convertTimeMsToTimeElapsedString(this.timeWorked) + ", " + - "you spent a total of " + numeralWrapper.formatMoney(this.workMoneyGained * -1) + ". " + - "You earned a total of: " + - numeralWrapper.formatExp(this.workHackExpGained) + " hacking exp, " + - numeralWrapper.formatExp(this.workStrExpGained) + " strength exp, " + - numeralWrapper.formatExp(this.workDefExpGained) + " defense exp, " + - numeralWrapper.formatExp(this.workDexExpGained) + " dexterity exp, " + - numeralWrapper.formatExp(this.workAgiExpGained) + " agility exp, and " + - numeralWrapper.formatExp(this.workChaExpGained) + " charisma exp"; - this.resetWorkStatus(); - return res; - } + Engine.loadLocationContent(false); + if (sing) { + var res = + "After " + + this.className + + " for " + + convertTimeMsToTimeElapsedString(this.timeWorked) + + ", " + + "you spent a total of " + + numeralWrapper.formatMoney(this.workMoneyGained * -1) + + ". " + + "You earned a total of: " + + numeralWrapper.formatExp(this.workHackExpGained) + + " hacking exp, " + + numeralWrapper.formatExp(this.workStrExpGained) + + " strength exp, " + + numeralWrapper.formatExp(this.workDefExpGained) + + " defense exp, " + + numeralWrapper.formatExp(this.workDexExpGained) + + " dexterity exp, " + + numeralWrapper.formatExp(this.workAgiExpGained) + + " agility exp, and " + + numeralWrapper.formatExp(this.workChaExpGained) + + " charisma exp"; this.resetWorkStatus(); + return res; + } + this.resetWorkStatus(); } //The EXP and $ gains are hardcoded. Time is in ms -export function startCrime(crimeType, hackExp, strExp, defExp, dexExp, agiExp, chaExp, money, time, singParams=null) { - this.crimeType = crimeType; +export function startCrime( + crimeType, + hackExp, + strExp, + defExp, + dexExp, + agiExp, + chaExp, + money, + time, + singParams = null, +) { + this.crimeType = crimeType; - this.resetWorkStatus(); - this.isWorking = true; - this.focus = true; - this.workType = CONSTANTS.WorkTypeCrime; + this.resetWorkStatus(); + this.isWorking = true; + this.focus = true; + this.workType = CONSTANTS.WorkTypeCrime; - if (singParams && singParams.workerscript) { - this.committingCrimeThruSingFn = true; - this.singFnCrimeWorkerScript = singParams.workerscript; - } + if (singParams && singParams.workerscript) { + this.committingCrimeThruSingFn = true; + this.singFnCrimeWorkerScript = singParams.workerscript; + } - this.workHackExpGained = hackExp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workStrExpGained = strExp * this.strength_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workDefExpGained = defExp * this.defense_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workDexExpGained = dexExp * this.dexterity_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workAgiExpGained = agiExp * this.agility_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workChaExpGained = chaExp * this.charisma_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.workMoneyGained = money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney; + this.workHackExpGained = + hackExp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workStrExpGained = + strExp * this.strength_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workDefExpGained = + defExp * this.defense_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workDexExpGained = + dexExp * this.dexterity_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workAgiExpGained = + agiExp * this.agility_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workChaExpGained = + chaExp * this.charisma_exp_mult * BitNodeMultipliers.CrimeExpGain; + this.workMoneyGained = + money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney; - this.timeNeededToCompleteWork = time; + this.timeNeededToCompleteWork = time; - //Remove all old event listeners from Cancel button - const newCancelButton = clearEventListeners("work-in-progress-cancel-button") - newCancelButton.innerHTML = "Cancel crime" - newCancelButton.addEventListener("click", () => { - this.finishCrime(true); - return false; - }); + //Remove all old event listeners from Cancel button + const newCancelButton = clearEventListeners("work-in-progress-cancel-button"); + newCancelButton.innerHTML = "Cancel crime"; + newCancelButton.addEventListener("click", () => { + this.finishCrime(true); + return false; + }); - const focusButton = clearEventListeners("work-in-progress-something-else-button"); - focusButton.style.visibility = "hidden"; + const focusButton = clearEventListeners( + "work-in-progress-something-else-button", + ); + focusButton.style.visibility = "hidden"; - //Display Work In Progress Screen - Engine.loadWorkInProgressContent(); + //Display Work In Progress Screen + Engine.loadWorkInProgressContent(); } export function commitCrime(numCycles) { - this.timeWorked += Engine._idleSpeed * numCycles; + this.timeWorked += Engine._idleSpeed * numCycles; - if (this.timeWorked >= this.timeNeededToCompleteWork) {this.finishCrime(false); return;} + if (this.timeWorked >= this.timeNeededToCompleteWork) { + this.finishCrime(false); + return; + } - var percent = Math.round(this.timeWorked / this.timeNeededToCompleteWork * 100); - var numBars = Math.round(percent / 5); - if (numBars < 0) {numBars = 0;} - if (numBars > 20) {numBars = 20;} - var progressBar = "[" + Array(numBars+1).join("|") + Array(20 - numBars + 1).join(" ") + "]"; + var percent = Math.round( + (this.timeWorked / this.timeNeededToCompleteWork) * 100, + ); + var numBars = Math.round(percent / 5); + if (numBars < 0) { + numBars = 0; + } + if (numBars > 20) { + numBars = 20; + } + var progressBar = + "[" + + Array(numBars + 1).join("|") + + Array(20 - numBars + 1).join(" ") + + "]"; - var txt = document.getElementById("work-in-progress-text"); - txt.innerHTML = "You are attempting to " + this.crimeType + ".
    " + - "Time remaining: " + convertTimeMsToTimeElapsedString(this.timeNeededToCompleteWork - this.timeWorked) + "
    " + - progressBar.replace( / /g, " " ); + var txt = document.getElementById("work-in-progress-text"); + txt.innerHTML = + "You are attempting to " + + this.crimeType + + ".
    " + + "Time remaining: " + + convertTimeMsToTimeElapsedString( + this.timeNeededToCompleteWork - this.timeWorked, + ) + + "
    " + + progressBar.replace(/ /g, " "); } export function finishCrime(cancelled) { - //Determine crime success/failure - if (!cancelled) { - if (determineCrimeSuccess(this, this.crimeType)) { - //Handle Karma and crime statistics - let crime = null; - for(const i in Crimes) { - if(Crimes[i].type == this.crimeType) { - crime = Crimes[i]; - break; - } - } - if(crime == null) { - dialogBoxCreate(`ERR: Unrecognized crime type (${this.crimeType}). This is probably a bug please contact the developer`); - } - this.gainMoney(this.workMoneyGained); - this.recordMoneySource(this.workMoneyGained, "crime"); - this.karma -= crime.karma; - this.numPeopleKilled += crime.kills; - if(crime.intelligence_exp > 0) { - this.gainIntelligenceExp(crime.intelligence_exp); - } - - //On a crime success, gain 2x exp - this.workHackExpGained *= 2; - this.workStrExpGained *= 2; - this.workDefExpGained *= 2; - this.workDexExpGained *= 2; - this.workAgiExpGained *= 2; - this.workChaExpGained *= 2; - if (this.committingCrimeThruSingFn) { - if(this.singFnCrimeWorkerScript.disableLogs.ALL == null && this.singFnCrimeWorkerScript.disableLogs.commitCrime == null) { - this.singFnCrimeWorkerScript.scriptRef.log("Crime successful! Gained " + - numeralWrapper.formatMoney(this.workMoneyGained) + ", " + - numeralWrapper.formatExp(this.workHackExpGained) + " hack exp, " + - numeralWrapper.formatExp(this.workStrExpGained) + " str exp, " + - numeralWrapper.formatExp(this.workDefExpGained) + " def exp, " + - numeralWrapper.formatExp(this.workDexExpGained) + " dex exp, " + - numeralWrapper.formatExp(this.workAgiExpGained) + " agi exp, " + - numeralWrapper.formatExp(this.workChaExpGained) + " cha exp."); - } - } else { - dialogBoxCreate(<> - Crime successful!

    - You gained:
    -
    - {numeralWrapper.formatExp(this.workHackExpGained)} hacking experience
    - {numeralWrapper.formatExp(this.workStrExpGained)} strength experience
    - {numeralWrapper.formatExp(this.workDefExpGained)} defense experience
    - {numeralWrapper.formatExp(this.workDexExpGained)} dexterity experience
    - {numeralWrapper.formatExp(this.workAgiExpGained)} agility experience
    - {numeralWrapper.formatExp(this.workChaExpGained)} charisma experience - ); - } - - } else { - //Exp halved on failure - this.workHackExpGained /= 2; - this.workStrExpGained /= 2; - this.workDefExpGained /= 2; - this.workDexExpGained /= 2; - this.workAgiExpGained /= 2; - this.workChaExpGained /= 2; - if (this.committingCrimeThruSingFn) { - if(this.singFnCrimeWorkerScript.disableLogs.ALL == null && this.singFnCrimeWorkerScript.disableLogs.commitCrime == null) { - this.singFnCrimeWorkerScript.scriptRef.log("Crime failed! Gained " + - numeralWrapper.formatExp(this.workHackExpGained) + " hack exp, " + - numeralWrapper.formatExp(this.workStrExpGained) + " str exp, " + - numeralWrapper.formatExp(this.workDefExpGained) + " def exp, " + - numeralWrapper.formatExp(this.workDexExpGained) + " dex exp, " + - numeralWrapper.formatExp(this.workAgiExpGained) + " agi exp, " + - numeralWrapper.formatExp(this.workChaExpGained) + " cha exp."); - } - } else { - dialogBoxCreate(<> - Crime failed!

    - You gained:
    - {numeralWrapper.formatExp(this.workHackExpGained)} hacking experience
    - {numeralWrapper.formatExp(this.workStrExpGained)} strength experience
    - {numeralWrapper.formatExp(this.workDefExpGained)} defense experience
    - {numeralWrapper.formatExp(this.workDexExpGained)} dexterity experience
    - {numeralWrapper.formatExp(this.workAgiExpGained)} agility experience
    - {numeralWrapper.formatExp(this.workChaExpGained)} charisma experience - ); - } + //Determine crime success/failure + if (!cancelled) { + if (determineCrimeSuccess(this, this.crimeType)) { + //Handle Karma and crime statistics + let crime = null; + for (const i in Crimes) { + if (Crimes[i].type == this.crimeType) { + crime = Crimes[i]; + break; } + } + if (crime == null) { + dialogBoxCreate( + `ERR: Unrecognized crime type (${this.crimeType}). This is probably a bug please contact the developer`, + ); + } + this.gainMoney(this.workMoneyGained); + this.recordMoneySource(this.workMoneyGained, "crime"); + this.karma -= crime.karma; + this.numPeopleKilled += crime.kills; + if (crime.intelligence_exp > 0) { + this.gainIntelligenceExp(crime.intelligence_exp); + } - this.gainHackingExp(this.workHackExpGained); - this.gainStrengthExp(this.workStrExpGained); - this.gainDefenseExp(this.workDefExpGained); - this.gainDexterityExp(this.workDexExpGained); - this.gainAgilityExp(this.workAgiExpGained); - this.gainCharismaExp(this.workChaExpGained); + //On a crime success, gain 2x exp + this.workHackExpGained *= 2; + this.workStrExpGained *= 2; + this.workDefExpGained *= 2; + this.workDexExpGained *= 2; + this.workAgiExpGained *= 2; + this.workChaExpGained *= 2; + if (this.committingCrimeThruSingFn) { + if ( + this.singFnCrimeWorkerScript.disableLogs.ALL == null && + this.singFnCrimeWorkerScript.disableLogs.commitCrime == null + ) { + this.singFnCrimeWorkerScript.scriptRef.log( + "Crime successful! Gained " + + numeralWrapper.formatMoney(this.workMoneyGained) + + ", " + + numeralWrapper.formatExp(this.workHackExpGained) + + " hack exp, " + + numeralWrapper.formatExp(this.workStrExpGained) + + " str exp, " + + numeralWrapper.formatExp(this.workDefExpGained) + + " def exp, " + + numeralWrapper.formatExp(this.workDexExpGained) + + " dex exp, " + + numeralWrapper.formatExp(this.workAgiExpGained) + + " agi exp, " + + numeralWrapper.formatExp(this.workChaExpGained) + + " cha exp.", + ); + } + } else { + dialogBoxCreate( + <> + Crime successful! +
    +
    + You gained: +
    + +
    + {numeralWrapper.formatExp(this.workHackExpGained)} hacking + experience
    + {numeralWrapper.formatExp(this.workStrExpGained)} strength + experience +
    + {numeralWrapper.formatExp(this.workDefExpGained)} defense experience +
    + {numeralWrapper.formatExp(this.workDexExpGained)} dexterity + experience +
    + {numeralWrapper.formatExp(this.workAgiExpGained)} agility experience +
    + {numeralWrapper.formatExp(this.workChaExpGained)} charisma + experience + , + ); + } + } else { + //Exp halved on failure + this.workHackExpGained /= 2; + this.workStrExpGained /= 2; + this.workDefExpGained /= 2; + this.workDexExpGained /= 2; + this.workAgiExpGained /= 2; + this.workChaExpGained /= 2; + if (this.committingCrimeThruSingFn) { + if ( + this.singFnCrimeWorkerScript.disableLogs.ALL == null && + this.singFnCrimeWorkerScript.disableLogs.commitCrime == null + ) { + this.singFnCrimeWorkerScript.scriptRef.log( + "Crime failed! Gained " + + numeralWrapper.formatExp(this.workHackExpGained) + + " hack exp, " + + numeralWrapper.formatExp(this.workStrExpGained) + + " str exp, " + + numeralWrapper.formatExp(this.workDefExpGained) + + " def exp, " + + numeralWrapper.formatExp(this.workDexExpGained) + + " dex exp, " + + numeralWrapper.formatExp(this.workAgiExpGained) + + " agi exp, " + + numeralWrapper.formatExp(this.workChaExpGained) + + " cha exp.", + ); + } + } else { + dialogBoxCreate( + <> + Crime failed! +
    +
    + You gained: +
    + {numeralWrapper.formatExp(this.workHackExpGained)} hacking + experience
    + {numeralWrapper.formatExp(this.workStrExpGained)} strength + experience +
    + {numeralWrapper.formatExp(this.workDefExpGained)} defense experience +
    + {numeralWrapper.formatExp(this.workDexExpGained)} dexterity + experience +
    + {numeralWrapper.formatExp(this.workAgiExpGained)} agility experience +
    + {numeralWrapper.formatExp(this.workChaExpGained)} charisma + experience + , + ); + } } - this.committingCrimeThruSingFn = false; - this.singFnCrimeWorkerScript = null; - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - this.isWorking = false; - this.resetWorkStatus(); - Engine.loadLocationContent(false); + + this.gainHackingExp(this.workHackExpGained); + this.gainStrengthExp(this.workStrExpGained); + this.gainDefenseExp(this.workDefExpGained); + this.gainDexterityExp(this.workDexExpGained); + this.gainAgilityExp(this.workAgiExpGained); + this.gainCharismaExp(this.workChaExpGained); + } + this.committingCrimeThruSingFn = false; + this.singFnCrimeWorkerScript = null; + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + this.isWorking = false; + this.resetWorkStatus(); + Engine.loadLocationContent(false); } //Cancels the player's current "work" assignment and gives the proper rewards //Used only for Singularity functions, so no popups are created export function singularityStopWork() { - if (!this.isWorking) {return "";} - var res; //Earnings text for work - switch (this.workType) { - case CONSTANTS.WorkTypeStudyClass: - res = this.finishClass(true); - break; - case CONSTANTS.WorkTypeCompany: - res = this.finishWork(true, true); - break; - case CONSTANTS.WorkTypeCompanyPartTime: - res = this.finishWorkPartTime(true); - break; - case CONSTANTS.WorkTypeFaction: - res = this.finishFactionWork(true, true); - break; - case CONSTANTS.WorkTypeCreateProgram: - res = this.finishCreateProgramWork(true); - break; - case CONSTANTS.WorkTypeCrime: - res = this.finishCrime(true); - break; - default: - console.error(`Unrecognized work type (${this.workType})`); - return ""; - } - return res; + if (!this.isWorking) { + return ""; + } + var res; //Earnings text for work + switch (this.workType) { + case CONSTANTS.WorkTypeStudyClass: + res = this.finishClass(true); + break; + case CONSTANTS.WorkTypeCompany: + res = this.finishWork(true, true); + break; + case CONSTANTS.WorkTypeCompanyPartTime: + res = this.finishWorkPartTime(true); + break; + case CONSTANTS.WorkTypeFaction: + res = this.finishFactionWork(true, true); + break; + case CONSTANTS.WorkTypeCreateProgram: + res = this.finishCreateProgramWork(true); + break; + case CONSTANTS.WorkTypeCrime: + res = this.finishCrime(true); + break; + default: + console.error(`Unrecognized work type (${this.workType})`); + return ""; + } + return res; } - // Returns true if hospitalized, false otherwise export function takeDamage(amt) { - if (typeof amt !== "number") { - console.warn(`Player.takeDamage() called without a numeric argument: ${amt}`); - return; - } + if (typeof amt !== "number") { + console.warn( + `Player.takeDamage() called without a numeric argument: ${amt}`, + ); + return; + } - this.hp -= amt; - if (this.hp <= 0) { - this.hospitalize(); - return true; - } else { - return false; - } + this.hp -= amt; + if (this.hp <= 0) { + this.hospitalize(); + return true; + } else { + return false; + } } export function regenerateHp(amt) { - if (typeof amt !== "number") { - console.warn(`Player.regenerateHp() called without a numeric argument: ${amt}`); - return; - } - this.hp += amt; - if (this.hp > this.max_hp) { this.hp = this.max_hp; } + if (typeof amt !== "number") { + console.warn( + `Player.regenerateHp() called without a numeric argument: ${amt}`, + ); + return; + } + this.hp += amt; + if (this.hp > this.max_hp) { + this.hp = this.max_hp; + } } export function hospitalize() { - const cost = getHospitalizationCost(this); - if (Settings.SuppressHospitalizationPopup === false) { - dialogBoxCreate(<> - You were in critical condition! You were taken to the hospital where - luckily they were able to save your life. You were charged  - - ); - } + const cost = getHospitalizationCost(this); + if (Settings.SuppressHospitalizationPopup === false) { + dialogBoxCreate( + <> + You were in critical condition! You were taken to the hospital where + luckily they were able to save your life. You were charged  + + , + ); + } - this.loseMoney(cost); - this.recordMoneySource(-1 * cost, "hospitalization"); - this.hp = this.max_hp; - return cost; + this.loseMoney(cost); + this.recordMoneySource(-1 * cost, "hospitalization"); + this.hp = this.max_hp; + return cost; } /********* Company job application **********/ //Determines the job that the Player should get (if any) at the current company //The 'sing' argument designates whether or not this is being called from //the applyToCompany() Netscript Singularity function -export function applyForJob(entryPosType, sing=false) { - // Get current company and job - let currCompany = null; - if (this.companyName !== "") { - currCompany = Companies[this.companyName]; - } - const currPositionName = this.jobs[this.companyName]; +export function applyForJob(entryPosType, sing = false) { + // Get current company and job + let currCompany = null; + if (this.companyName !== "") { + currCompany = Companies[this.companyName]; + } + const currPositionName = this.jobs[this.companyName]; - // Get company that's being applied to - const company = Companies[this.location]; //Company being applied to - if (!(company instanceof Company)) { + // Get company that's being applied to + const company = Companies[this.location]; //Company being applied to + if (!(company instanceof Company)) { + if (sing) { + return ( + "ERROR: Invalid company name: " + + this.location + + ". applyToCompany() failed" + ); + } else { + console.error( + `Could not find company that matches the location: ${this.location}. Player.applyToCompany() failed`, + ); + return; + } + } + + let pos = entryPosType; + + if (!this.isQualified(company, pos)) { + var reqText = getJobRequirementText(company, pos); + if (sing) { + return false; + } + dialogBoxCreate( + "Unforunately, you do not qualify for this position
    " + reqText, + ); + return; + } + + while (true) { + let newPos = getNextCompanyPositionHelper(pos); + if (newPos == null) { + break; + } + + //Check if this company has this position + if (company.hasPosition(newPos)) { + if (!this.isQualified(company, newPos)) { + //If player not qualified for next job, break loop so player will be given current job + break; + } + pos = newPos; + } else { + break; + } + } + + //Check if the determined job is the same as the player's current job + if (currCompany != null) { + if (currCompany.name == company.name && pos.name == currPositionName) { + var nextPos = getNextCompanyPositionHelper(pos); + if (nextPos == null) { if (sing) { - return "ERROR: Invalid company name: " + this.location + ". applyToCompany() failed"; - } else { - console.error(`Could not find company that matches the location: ${this.location}. Player.applyToCompany() failed`); - return; + return false; } - } - - let pos = entryPosType; - - if (!this.isQualified(company, pos)) { - var reqText = getJobRequirementText(company, pos); - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position
    " + reqText); - return; - } - - while (true) { - let newPos = getNextCompanyPositionHelper(pos); - if (newPos == null) {break;} - - //Check if this company has this position - if (company.hasPosition(newPos)) { - if (!this.isQualified(company, newPos)) { - //If player not qualified for next job, break loop so player will be given current job - break; - } - pos = newPos; - } else { - break; + dialogBoxCreate( + "You are already at the highest position for your field! No promotion available", + ); + } else if (company.hasPosition(nextPos)) { + if (sing) { + return false; } - } - - //Check if the determined job is the same as the player's current job - if (currCompany != null) { - if (currCompany.name == company.name && pos.name == currPositionName) { - var nextPos = getNextCompanyPositionHelper(pos); - if (nextPos == null) { - if (sing) {return false;} - dialogBoxCreate("You are already at the highest position for your field! No promotion available"); - } else if (company.hasPosition(nextPos)) { - if (sing) {return false;} - var reqText = getJobRequirementText(company, nextPos); - dialogBoxCreate("Unfortunately, you do not qualify for a promotion
    " + reqText); - } else { - if (sing) {return false;} - dialogBoxCreate("You are already at the highest position for your field! No promotion available"); - } - return; //Same job, do nothing + var reqText = getJobRequirementText(company, nextPos); + dialogBoxCreate( + "Unfortunately, you do not qualify for a promotion
    " + reqText, + ); + } else { + if (sing) { + return false; } + dialogBoxCreate( + "You are already at the highest position for your field! No promotion available", + ); + } + return; //Same job, do nothing } + } - this.jobs[company.name] = pos.name; - this.companyName = this.location; + this.jobs[company.name] = pos.name; + this.companyName = this.location; - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); - if (sing) { return true; } + if (sing) { + return true; + } - dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.name + "!"); + dialogBoxCreate( + "Congratulations! You were offered a new job at " + + this.companyName + + " as a " + + pos.name + + "!", + ); } //Returns your next position at a company given the field (software, business, etc.) export function getNextCompanyPosition(company, entryPosType) { - var currCompany = null; - if (this.companyName !== "") { - currCompany = Companies[this.companyName]; - } - - //Not employed at this company, so return the entry position - if (currCompany == null || (currCompany.name != company.name)) { - return entryPosType; - } - - //If the entry pos type and the player's current position have the same type, - //return the player's "nextCompanyPosition". Otherwise return the entryposType - //Employed at this company, so just return the next position if it exists. - const currentPositionName = this.jobs[this.companyName]; - const currentPosition = CompanyPositions[currentPositionName]; - if ((currentPosition.isSoftwareJob() && entryPosType.isSoftwareJob()) || - (currentPosition.isITJob() && entryPosType.isITJob()) || - (currentPosition.isBusinessJob() && entryPosType.isBusinessJob()) || - (currentPosition.isSecurityEngineerJob() && entryPosType.isSecurityEngineerJob()) || - (currentPosition.isNetworkEngineerJob() && entryPosType.isNetworkEngineerJob()) || - (currentPosition.isSecurityJob() && entryPosType.isSecurityJob()) || - (currentPosition.isAgentJob() && entryPosType.isAgentJob()) || - (currentPosition.isSoftwareConsultantJob() && entryPosType.isSoftwareConsultantJob()) || - (currentPosition.isBusinessConsultantJob() && entryPosType.isBusinessConsultantJob()) || - (currentPosition.isPartTimeJob() && entryPosType.isPartTimeJob())) { - return getNextCompanyPositionHelper(currentPosition); - } + var currCompany = null; + if (this.companyName !== "") { + currCompany = Companies[this.companyName]; + } + //Not employed at this company, so return the entry position + if (currCompany == null || currCompany.name != company.name) { return entryPosType; + } + + //If the entry pos type and the player's current position have the same type, + //return the player's "nextCompanyPosition". Otherwise return the entryposType + //Employed at this company, so just return the next position if it exists. + const currentPositionName = this.jobs[this.companyName]; + const currentPosition = CompanyPositions[currentPositionName]; + if ( + (currentPosition.isSoftwareJob() && entryPosType.isSoftwareJob()) || + (currentPosition.isITJob() && entryPosType.isITJob()) || + (currentPosition.isBusinessJob() && entryPosType.isBusinessJob()) || + (currentPosition.isSecurityEngineerJob() && + entryPosType.isSecurityEngineerJob()) || + (currentPosition.isNetworkEngineerJob() && + entryPosType.isNetworkEngineerJob()) || + (currentPosition.isSecurityJob() && entryPosType.isSecurityJob()) || + (currentPosition.isAgentJob() && entryPosType.isAgentJob()) || + (currentPosition.isSoftwareConsultantJob() && + entryPosType.isSoftwareConsultantJob()) || + (currentPosition.isBusinessConsultantJob() && + entryPosType.isBusinessConsultantJob()) || + (currentPosition.isPartTimeJob() && entryPosType.isPartTimeJob()) + ) { + return getNextCompanyPositionHelper(currentPosition); + } + + return entryPosType; } export function quitJob(company) { - this.isWorking = false; - this.companyName = ""; - delete this.jobs[company]; + this.isWorking = false; + this.companyName = ""; + delete this.jobs[company]; } -export function applyForSoftwareJob(sing=false) { - return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing); +export function applyForSoftwareJob(sing = false) { + return this.applyForJob( + CompanyPositions[posNames.SoftwareCompanyPositions[0]], + sing, + ); } -export function applyForSoftwareConsultantJob(sing=false) { - return this.applyForJob(CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]], sing); +export function applyForSoftwareConsultantJob(sing = false) { + return this.applyForJob( + CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]], + sing, + ); } -export function applyForItJob(sing=false) { - return this.applyForJob(CompanyPositions[posNames.ITCompanyPositions[0]], sing); +export function applyForItJob(sing = false) { + return this.applyForJob( + CompanyPositions[posNames.ITCompanyPositions[0]], + sing, + ); } -export function applyForSecurityEngineerJob(sing=false) { - var company = Companies[this.location]; //Company being applied to - if (this.isQualified(company, CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]])) { - return this.applyForJob(CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]], sing); - } else { - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position"); +export function applyForSecurityEngineerJob(sing = false) { + var company = Companies[this.location]; //Company being applied to + if ( + this.isQualified( + company, + CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]], + ) + ) { + return this.applyForJob( + CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]], + sing, + ); + } else { + if (sing) { + return false; } + dialogBoxCreate("Unforunately, you do not qualify for this position"); + } } -export function applyForNetworkEngineerJob(sing=false) { - var company = Companies[this.location]; //Company being applied to - if (this.isQualified(company, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]])) { - return this.applyForJob(CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], sing); - } else { - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position"); +export function applyForNetworkEngineerJob(sing = false) { + var company = Companies[this.location]; //Company being applied to + if ( + this.isQualified( + company, + CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], + ) + ) { + return this.applyForJob( + CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], + sing, + ); + } else { + if (sing) { + return false; } + dialogBoxCreate("Unforunately, you do not qualify for this position"); + } } -export function applyForBusinessJob(sing=false) { - return this.applyForJob(CompanyPositions[posNames.BusinessCompanyPositions[0]], sing); +export function applyForBusinessJob(sing = false) { + return this.applyForJob( + CompanyPositions[posNames.BusinessCompanyPositions[0]], + sing, + ); } -export function applyForBusinessConsultantJob(sing=false) { - return this.applyForJob(CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], sing); +export function applyForBusinessConsultantJob(sing = false) { + return this.applyForJob( + CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], + sing, + ); } -export function applyForSecurityJob(sing=false) { - // TODO Police Jobs - // Indexing starts at 2 because 0 is for police officer - return this.applyForJob(CompanyPositions[posNames.SecurityCompanyPositions[2]], sing); +export function applyForSecurityJob(sing = false) { + // TODO Police Jobs + // Indexing starts at 2 because 0 is for police officer + return this.applyForJob( + CompanyPositions[posNames.SecurityCompanyPositions[2]], + sing, + ); } -export function applyForAgentJob(sing=false) { - var company = Companies[this.location]; //Company being applied to - if (this.isQualified(company, CompanyPositions[posNames.AgentCompanyPositions[0]])) { - return this.applyForJob(CompanyPositions[posNames.AgentCompanyPositions[0]], sing); - } else { - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position"); +export function applyForAgentJob(sing = false) { + var company = Companies[this.location]; //Company being applied to + if ( + this.isQualified( + company, + CompanyPositions[posNames.AgentCompanyPositions[0]], + ) + ) { + return this.applyForJob( + CompanyPositions[posNames.AgentCompanyPositions[0]], + sing, + ); + } else { + if (sing) { + return false; } + dialogBoxCreate("Unforunately, you do not qualify for this position"); + } } -export function applyForEmployeeJob(sing=false) { - var company = Companies[this.location]; //Company being applied to - if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[1]])) { - this.companyName = company.name; - this.jobs[company.name] = posNames.MiscCompanyPositions[1]; - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); - if (sing) {return true;} - dialogBoxCreate("Congratulations, you are now employed at " + this.companyName); - } else { - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position"); +export function applyForEmployeeJob(sing = false) { + var company = Companies[this.location]; //Company being applied to + if ( + this.isQualified( + company, + CompanyPositions[posNames.MiscCompanyPositions[1]], + ) + ) { + this.companyName = company.name; + this.jobs[company.name] = posNames.MiscCompanyPositions[1]; + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); + if (sing) { + return true; } + dialogBoxCreate( + "Congratulations, you are now employed at " + this.companyName, + ); + } else { + if (sing) { + return false; + } + dialogBoxCreate("Unforunately, you do not qualify for this position"); + } } -export function applyForPartTimeEmployeeJob(sing=false) { - var company = Companies[this.location]; //Company being applied to - if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[1]])) { - this.jobs[company.name] = posNames.PartTimeCompanyPositions[1]; - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); - if (sing) {return true;} - dialogBoxCreate("Congratulations, you are now employed part-time at " + this.companyName); - } else { - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position"); +export function applyForPartTimeEmployeeJob(sing = false) { + var company = Companies[this.location]; //Company being applied to + if ( + this.isQualified( + company, + CompanyPositions[posNames.PartTimeCompanyPositions[1]], + ) + ) { + this.jobs[company.name] = posNames.PartTimeCompanyPositions[1]; + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); + if (sing) { + return true; } + dialogBoxCreate( + "Congratulations, you are now employed part-time at " + this.companyName, + ); + } else { + if (sing) { + return false; + } + dialogBoxCreate("Unforunately, you do not qualify for this position"); + } } -export function applyForWaiterJob(sing=false) { - var company = Companies[this.location]; //Company being applied to - if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[0]])) { - this.companyName = company.name; - this.jobs[company.name] = posNames.MiscCompanyPositions[0]; - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); - if (sing) {return true;} - dialogBoxCreate("Congratulations, you are now employed as a waiter at " + this.companyName); - } else { - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position"); +export function applyForWaiterJob(sing = false) { + var company = Companies[this.location]; //Company being applied to + if ( + this.isQualified( + company, + CompanyPositions[posNames.MiscCompanyPositions[0]], + ) + ) { + this.companyName = company.name; + this.jobs[company.name] = posNames.MiscCompanyPositions[0]; + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); + if (sing) { + return true; } + dialogBoxCreate( + "Congratulations, you are now employed as a waiter at " + + this.companyName, + ); + } else { + if (sing) { + return false; + } + dialogBoxCreate("Unforunately, you do not qualify for this position"); + } } -export function applyForPartTimeWaiterJob(sing=false) { - var company = Companies[this.location]; //Company being applied to - if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[0]])) { - this.companyName = company.name; - this.jobs[company.name] = posNames.PartTimeCompanyPositions[0]; - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); - if (sing) {return true;} - dialogBoxCreate("Congratulations, you are now employed as a part-time waiter at " + this.companyName); - } else { - if (sing) {return false;} - dialogBoxCreate("Unforunately, you do not qualify for this position"); +export function applyForPartTimeWaiterJob(sing = false) { + var company = Companies[this.location]; //Company being applied to + if ( + this.isQualified( + company, + CompanyPositions[posNames.PartTimeCompanyPositions[0]], + ) + ) { + this.companyName = company.name; + this.jobs[company.name] = posNames.PartTimeCompanyPositions[0]; + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); + if (sing) { + return true; } + dialogBoxCreate( + "Congratulations, you are now employed as a part-time waiter at " + + this.companyName, + ); + } else { + if (sing) { + return false; + } + dialogBoxCreate("Unforunately, you do not qualify for this position"); + } } //Checks if the Player is qualified for a certain position export function isQualified(company, position) { - var offset = company.jobStatReqOffset; - var reqHacking = position.requiredHacking > 0 ? position.requiredHacking+offset : 0; - var reqStrength = position.requiredStrength > 0 ? position.requiredStrength+offset : 0; - var reqDefense = position.requiredDefense > 0 ? position.requiredDefense+offset : 0; - var reqDexterity = position.requiredDexterity > 0 ? position.requiredDexterity+offset : 0; - var reqAgility = position.requiredDexterity > 0 ? position.requiredDexterity+offset : 0; - var reqCharisma = position.requiredCharisma > 0 ? position.requiredCharisma+offset : 0; + var offset = company.jobStatReqOffset; + var reqHacking = + position.requiredHacking > 0 ? position.requiredHacking + offset : 0; + var reqStrength = + position.requiredStrength > 0 ? position.requiredStrength + offset : 0; + var reqDefense = + position.requiredDefense > 0 ? position.requiredDefense + offset : 0; + var reqDexterity = + position.requiredDexterity > 0 ? position.requiredDexterity + offset : 0; + var reqAgility = + position.requiredDexterity > 0 ? position.requiredDexterity + offset : 0; + var reqCharisma = + position.requiredCharisma > 0 ? position.requiredCharisma + offset : 0; - if (this.hacking_skill >= reqHacking && - this.strength >= reqStrength && - this.defense >= reqDefense && - this.dexterity >= reqDexterity && - this.agility >= reqAgility && - this.charisma >= reqCharisma && - company.playerReputation >= position.requiredReputation) { - return true; - } - return false; + if ( + this.hacking_skill >= reqHacking && + this.strength >= reqStrength && + this.defense >= reqDefense && + this.dexterity >= reqDexterity && + this.agility >= reqAgility && + this.charisma >= reqCharisma && + company.playerReputation >= position.requiredReputation + ) { + return true; + } + return false; } /********** Reapplying Augmentations and Source File ***********/ -export function reapplyAllAugmentations(resetMultipliers=true) { - if (resetMultipliers) { - this.resetMultipliers(); +export function reapplyAllAugmentations(resetMultipliers = true) { + if (resetMultipliers) { + this.resetMultipliers(); + } + + for (let i = 0; i < this.augmentations.length; ++i) { + //Compatibility with new version + if ( + this.augmentations[i].name === + "HacknetNode NIC Architecture Neural-Upload" + ) { + this.augmentations[i].name = + "Hacknet Node NIC Architecture Neural-Upload"; } - for (let i = 0; i < this.augmentations.length; ++i) { - //Compatibility with new version - if (this.augmentations[i].name === "HacknetNode NIC Architecture Neural-Upload") { - this.augmentations[i].name = "Hacknet Node NIC Architecture Neural-Upload"; - } - - const augName = this.augmentations[i].name; - var aug = Augmentations[augName]; - if (aug == null) { - console.warn(`Invalid augmentation name in Player.reapplyAllAugmentations(). Aug ${augName} will be skipped`); - continue; - } - aug.owned = true; - if (aug.name == AugmentationNames.NeuroFluxGovernor) { - for (let j = 0; j < aug.level; ++j) { - applyAugmentation(this.augmentations[i], true); - } - continue; - } + const augName = this.augmentations[i].name; + var aug = Augmentations[augName]; + if (aug == null) { + console.warn( + `Invalid augmentation name in Player.reapplyAllAugmentations(). Aug ${augName} will be skipped`, + ); + continue; + } + aug.owned = true; + if (aug.name == AugmentationNames.NeuroFluxGovernor) { + for (let j = 0; j < aug.level; ++j) { applyAugmentation(this.augmentations[i], true); + } + continue; } + applyAugmentation(this.augmentations[i], true); + } } export function reapplyAllSourceFiles() { - //Will always be called after reapplyAllAugmentations() so multipliers do not have to be reset - //this.resetMultipliers(); + //Will always be called after reapplyAllAugmentations() so multipliers do not have to be reset + //this.resetMultipliers(); - for (let i = 0; i < this.sourceFiles.length; ++i) { - var srcFileKey = "SourceFile" + this.sourceFiles[i].n; - var sourceFileObject = SourceFiles[srcFileKey]; - if (sourceFileObject == null) { - console.error(`Invalid source file number: ${this.sourceFiles[i].n}`); - continue; - } - applySourceFile(this.sourceFiles[i]); + for (let i = 0; i < this.sourceFiles.length; ++i) { + var srcFileKey = "SourceFile" + this.sourceFiles[i].n; + var sourceFileObject = SourceFiles[srcFileKey]; + if (sourceFileObject == null) { + console.error(`Invalid source file number: ${this.sourceFiles[i].n}`); + continue; } - applyExploit(); + applySourceFile(this.sourceFiles[i]); + } + applyExploit(); } /*************** Check for Faction Invitations *************/ @@ -1910,442 +2675,623 @@ export function reapplyAllSourceFiles() { //those requirements and will return an array of all factions that the Player should //receive an invitation to export function checkForFactionInvitations() { - let invitedFactions = []; //Array which will hold all Factions the player should be invited to + let invitedFactions = []; //Array which will hold all Factions the player should be invited to - var numAugmentations = this.augmentations.length; + var numAugmentations = this.augmentations.length; - const allCompanies = Object.keys(this.jobs); - const allPositions = Object.values(this.jobs); + const allCompanies = Object.keys(this.jobs); + const allPositions = Object.values(this.jobs); - // Given a company name, safely returns the reputation (returns 0 if invalid company is specified) - function getCompanyRep(companyName) { - const company = Companies[companyName]; - if (company == null) { - return 0; - } else { - return company.playerReputation; - } - } - - // Helper function that returns a boolean indicating whether the Player meets - // the requirements for the specified company. There are two requirements: - // 1. High enough reputation - // 2. Player is employed at the company - function checkMegacorpRequirements(companyName, repNeeded=CONSTANTS.CorpFactionRepRequirement) { - return allCompanies.includes(companyName) && (getCompanyRep(companyName) > repNeeded); - } - - //Illuminati - var illuminatiFac = Factions["Illuminati"]; - if (!illuminatiFac.isBanned && !illuminatiFac.isMember && !illuminatiFac.alreadyInvited && - numAugmentations >= 30 && - this.money.gte(150000000000) && - this.hacking_skill >= 1500 && - this.strength >= 1200 && this.defense >= 1200 && - this.dexterity >= 1200 && this.agility >= 1200) { - invitedFactions.push(illuminatiFac); - } - - //Daedalus - var daedalusFac = Factions["Daedalus"]; - if (!daedalusFac.isBanned && !daedalusFac.isMember && !daedalusFac.alreadyInvited && - numAugmentations >= Math.round(30 * BitNodeMultipliers.DaedalusAugsRequirement) && - this.money.gte(100000000000) && - (this.hacking_skill >= 2500 || - (this.strength >= 1500 && this.defense >= 1500 && - this.dexterity >= 1500 && this.agility >= 1500))) { - invitedFactions.push(daedalusFac); - } - - //The Covenant - var covenantFac = Factions["The Covenant"]; - if (!covenantFac.isBanned && !covenantFac.isMember && !covenantFac.alreadyInvited && - numAugmentations >= 20 && - this.money.gte(75000000000) && - this.hacking_skill >= 850 && - this.strength >= 850 && - this.defense >= 850 && - this.dexterity >= 850 && - this.agility >= 850) { - invitedFactions.push(covenantFac); - } - - //ECorp - var ecorpFac = Factions["ECorp"]; - if (!ecorpFac.isBanned && !ecorpFac.isMember && !ecorpFac.alreadyInvited && - checkMegacorpRequirements(LocationName.AevumECorp)) { - invitedFactions.push(ecorpFac); - } - - //MegaCorp - var megacorpFac = Factions["MegaCorp"]; - if (!megacorpFac.isBanned && !megacorpFac.isMember && !megacorpFac.alreadyInvited && - checkMegacorpRequirements(LocationName.Sector12MegaCorp)) { - invitedFactions.push(megacorpFac); - } - - //Bachman & Associates - var bachmanandassociatesFac = Factions["Bachman & Associates"]; - if (!bachmanandassociatesFac.isBanned && !bachmanandassociatesFac.isMember && - !bachmanandassociatesFac.alreadyInvited && - checkMegacorpRequirements(LocationName.AevumBachmanAndAssociates)) { - invitedFactions.push(bachmanandassociatesFac); - } - - //Blade Industries - var bladeindustriesFac = Factions["Blade Industries"]; - if (!bladeindustriesFac.isBanned && !bladeindustriesFac.isMember && !bladeindustriesFac.alreadyInvited && - checkMegacorpRequirements(LocationName.Sector12BladeIndustries)) { - invitedFactions.push(bladeindustriesFac); - } - - //NWO - var nwoFac = Factions["NWO"]; - if (!nwoFac.isBanned && !nwoFac.isMember && !nwoFac.alreadyInvited && - checkMegacorpRequirements(LocationName.VolhavenNWO)) { - invitedFactions.push(nwoFac); - } - - //Clarke Incorporated - var clarkeincorporatedFac = Factions["Clarke Incorporated"]; - if (!clarkeincorporatedFac.isBanned && !clarkeincorporatedFac.isMember && !clarkeincorporatedFac.alreadyInvited && - checkMegacorpRequirements(LocationName.AevumClarkeIncorporated)) { - invitedFactions.push(clarkeincorporatedFac); - } - - //OmniTek Incorporated - var omnitekincorporatedFac = Factions["OmniTek Incorporated"]; - if (!omnitekincorporatedFac.isBanned && !omnitekincorporatedFac.isMember && !omnitekincorporatedFac.alreadyInvited && - checkMegacorpRequirements(LocationName.VolhavenOmniTekIncorporated)) { - invitedFactions.push(omnitekincorporatedFac); - } - - //Four Sigma - var foursigmaFac = Factions["Four Sigma"]; - if (!foursigmaFac.isBanned && !foursigmaFac.isMember && !foursigmaFac.alreadyInvited && - checkMegacorpRequirements(LocationName.Sector12FourSigma)) { - invitedFactions.push(foursigmaFac); - } - - //KuaiGong International - var kuaigonginternationalFac = Factions["KuaiGong International"]; - if (!kuaigonginternationalFac.isBanned && !kuaigonginternationalFac.isMember && - !kuaigonginternationalFac.alreadyInvited && - checkMegacorpRequirements(LocationName.ChongqingKuaiGongInternational)) { - invitedFactions.push(kuaigonginternationalFac); - } - - //Fulcrum Secret Technologies - If u've unlocked fulcrum secret technolgoies server and have a high rep with the company - var fulcrumsecrettechonologiesFac = Factions["Fulcrum Secret Technologies"]; - var fulcrumSecretServer = AllServers[SpecialServerIps[SpecialServerNames.FulcrumSecretTechnologies]]; - if (fulcrumSecretServer == null) { - console.error("Could not find Fulcrum Secret Technologies Server"); + // Given a company name, safely returns the reputation (returns 0 if invalid company is specified) + function getCompanyRep(companyName) { + const company = Companies[companyName]; + if (company == null) { + return 0; } else { - if (!fulcrumsecrettechonologiesFac.isBanned && !fulcrumsecrettechonologiesFac.isMember && - !fulcrumsecrettechonologiesFac.alreadyInvited && - fulcrumSecretServer.backdoorInstalled && - checkMegacorpRequirements(LocationName.AevumFulcrumTechnologies, 250e3)) { - invitedFactions.push(fulcrumsecrettechonologiesFac); - } + return company.playerReputation; } + } - //BitRunners - var bitrunnersFac = Factions["BitRunners"]; - var homeComp = this.getHomeComputer(); - var bitrunnersServer = AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]]; - if (bitrunnersServer == null) { - console.error("Could not find BitRunners Server"); - } else if (!bitrunnersFac.isBanned && !bitrunnersFac.isMember && bitrunnersServer.backdoorInstalled && - !bitrunnersFac.alreadyInvited && homeComp.maxRam >= 128) { - invitedFactions.push(bitrunnersFac); - } + // Helper function that returns a boolean indicating whether the Player meets + // the requirements for the specified company. There are two requirements: + // 1. High enough reputation + // 2. Player is employed at the company + function checkMegacorpRequirements( + companyName, + repNeeded = CONSTANTS.CorpFactionRepRequirement, + ) { + return ( + allCompanies.includes(companyName) && + getCompanyRep(companyName) > repNeeded + ); + } - //The Black Hand - var theblackhandFac = Factions["The Black Hand"]; - var blackhandServer = AllServers[SpecialServerIps[SpecialServerNames.TheBlackHandServer]]; - if (blackhandServer == null) { - console.error("Could not find The Black Hand Server"); - } else if (!theblackhandFac.isBanned && !theblackhandFac.isMember && blackhandServer.backdoorInstalled && - !theblackhandFac.alreadyInvited && homeComp.maxRam >= 64) { - invitedFactions.push(theblackhandFac); - } + //Illuminati + var illuminatiFac = Factions["Illuminati"]; + if ( + !illuminatiFac.isBanned && + !illuminatiFac.isMember && + !illuminatiFac.alreadyInvited && + numAugmentations >= 30 && + this.money.gte(150000000000) && + this.hacking_skill >= 1500 && + this.strength >= 1200 && + this.defense >= 1200 && + this.dexterity >= 1200 && + this.agility >= 1200 + ) { + invitedFactions.push(illuminatiFac); + } - //NiteSec - var nitesecFac = Factions["NiteSec"]; - var nitesecServer = AllServers[SpecialServerIps[SpecialServerNames.NiteSecServer]]; - if (nitesecServer == null) { - console.error("Could not find NiteSec Server"); - } else if (!nitesecFac.isBanned && !nitesecFac.isMember && nitesecServer.backdoorInstalled && - !nitesecFac.alreadyInvited && homeComp.maxRam >= 32) { - invitedFactions.push(nitesecFac); - } + //Daedalus + var daedalusFac = Factions["Daedalus"]; + if ( + !daedalusFac.isBanned && + !daedalusFac.isMember && + !daedalusFac.alreadyInvited && + numAugmentations >= + Math.round(30 * BitNodeMultipliers.DaedalusAugsRequirement) && + this.money.gte(100000000000) && + (this.hacking_skill >= 2500 || + (this.strength >= 1500 && + this.defense >= 1500 && + this.dexterity >= 1500 && + this.agility >= 1500)) + ) { + invitedFactions.push(daedalusFac); + } - //Chongqing - var chongqingFac = Factions["Chongqing"]; - if (!chongqingFac.isBanned && !chongqingFac.isMember && !chongqingFac.alreadyInvited && - this.money.gte(20000000) && this.city == CityName.Chongqing) { - invitedFactions.push(chongqingFac); - } + //The Covenant + var covenantFac = Factions["The Covenant"]; + if ( + !covenantFac.isBanned && + !covenantFac.isMember && + !covenantFac.alreadyInvited && + numAugmentations >= 20 && + this.money.gte(75000000000) && + this.hacking_skill >= 850 && + this.strength >= 850 && + this.defense >= 850 && + this.dexterity >= 850 && + this.agility >= 850 + ) { + invitedFactions.push(covenantFac); + } - //Sector-12 - var sector12Fac = Factions["Sector-12"]; - if (!sector12Fac.isBanned && !sector12Fac.isMember && !sector12Fac.alreadyInvited && - this.money.gte(15000000) && this.city == CityName.Sector12) { - invitedFactions.push(sector12Fac); - } + //ECorp + var ecorpFac = Factions["ECorp"]; + if ( + !ecorpFac.isBanned && + !ecorpFac.isMember && + !ecorpFac.alreadyInvited && + checkMegacorpRequirements(LocationName.AevumECorp) + ) { + invitedFactions.push(ecorpFac); + } - //New Tokyo - var newtokyoFac = Factions["New Tokyo"]; - if (!newtokyoFac.isBanned && !newtokyoFac.isMember && !newtokyoFac.alreadyInvited && - this.money.gte(20000000) && this.city == CityName.NewTokyo) { - invitedFactions.push(newtokyoFac); - } + //MegaCorp + var megacorpFac = Factions["MegaCorp"]; + if ( + !megacorpFac.isBanned && + !megacorpFac.isMember && + !megacorpFac.alreadyInvited && + checkMegacorpRequirements(LocationName.Sector12MegaCorp) + ) { + invitedFactions.push(megacorpFac); + } - //Aevum - var aevumFac = Factions["Aevum"]; - if (!aevumFac.isBanned && !aevumFac.isMember && !aevumFac.alreadyInvited && - this.money.gte(40000000) && this.city == CityName.Aevum) { - invitedFactions.push(aevumFac); - } + //Bachman & Associates + var bachmanandassociatesFac = Factions["Bachman & Associates"]; + if ( + !bachmanandassociatesFac.isBanned && + !bachmanandassociatesFac.isMember && + !bachmanandassociatesFac.alreadyInvited && + checkMegacorpRequirements(LocationName.AevumBachmanAndAssociates) + ) { + invitedFactions.push(bachmanandassociatesFac); + } - //Ishima - var ishimaFac = Factions["Ishima"]; - if (!ishimaFac.isBanned && !ishimaFac.isMember && !ishimaFac.alreadyInvited && - this.money.gte(30000000) && this.city == CityName.Ishima) { - invitedFactions.push(ishimaFac); - } + //Blade Industries + var bladeindustriesFac = Factions["Blade Industries"]; + if ( + !bladeindustriesFac.isBanned && + !bladeindustriesFac.isMember && + !bladeindustriesFac.alreadyInvited && + checkMegacorpRequirements(LocationName.Sector12BladeIndustries) + ) { + invitedFactions.push(bladeindustriesFac); + } - //Volhaven - var volhavenFac = Factions["Volhaven"]; - if (!volhavenFac.isBanned && !volhavenFac.isMember && !volhavenFac.alreadyInvited && - this.money.gte(50000000) && this.city == CityName.Volhaven) { - invitedFactions.push(volhavenFac); - } + //NWO + var nwoFac = Factions["NWO"]; + if ( + !nwoFac.isBanned && + !nwoFac.isMember && + !nwoFac.alreadyInvited && + checkMegacorpRequirements(LocationName.VolhavenNWO) + ) { + invitedFactions.push(nwoFac); + } - //Speakers for the Dead - var speakersforthedeadFac = Factions["Speakers for the Dead"]; - if (!speakersforthedeadFac.isBanned && !speakersforthedeadFac.isMember && !speakersforthedeadFac.alreadyInvited && - this.hacking_skill >= 100 && this.strength >= 300 && this.defense >= 300 && - this.dexterity >= 300 && this.agility >= 300 && this.numPeopleKilled >= 30 && - this.karma <= -45 && !allCompanies.includes(LocationName.Sector12CIA) && - !allCompanies.includes(LocationName.Sector12NSA)) { - invitedFactions.push(speakersforthedeadFac); - } + //Clarke Incorporated + var clarkeincorporatedFac = Factions["Clarke Incorporated"]; + if ( + !clarkeincorporatedFac.isBanned && + !clarkeincorporatedFac.isMember && + !clarkeincorporatedFac.alreadyInvited && + checkMegacorpRequirements(LocationName.AevumClarkeIncorporated) + ) { + invitedFactions.push(clarkeincorporatedFac); + } - //The Dark Army - var thedarkarmyFac = Factions["The Dark Army"]; - if (!thedarkarmyFac.isBanned && !thedarkarmyFac.isMember && !thedarkarmyFac.alreadyInvited && - this.hacking_skill >= 300 && this.strength >= 300 && this.defense >= 300 && - this.dexterity >= 300 && this.agility >= 300 && this.city == CityName.Chongqing && - this.numPeopleKilled >= 5 && this.karma <= -45 && !allCompanies.includes(LocationName.Sector12CIA) && - !allCompanies.includes(LocationName.Sector12NSA)) { - invitedFactions.push(thedarkarmyFac); - } + //OmniTek Incorporated + var omnitekincorporatedFac = Factions["OmniTek Incorporated"]; + if ( + !omnitekincorporatedFac.isBanned && + !omnitekincorporatedFac.isMember && + !omnitekincorporatedFac.alreadyInvited && + checkMegacorpRequirements(LocationName.VolhavenOmniTekIncorporated) + ) { + invitedFactions.push(omnitekincorporatedFac); + } - //The Syndicate - var thesyndicateFac = Factions["The Syndicate"]; - if (!thesyndicateFac.isBanned && !thesyndicateFac.isMember && !thesyndicateFac.alreadyInvited && - this.hacking_skill >= 200 && this.strength >= 200 && this.defense >= 200 && - this.dexterity >= 200 && this.agility >= 200 && - (this.city == CityName.Aevum || this.city == CityName.Sector12) && - this.money.gte(10000000) && this.karma <= -90 && - !allCompanies.includes(LocationName.Sector12CIA) && !allCompanies.includes(LocationName.Sector12NSA)) { - invitedFactions.push(thesyndicateFac); - } + //Four Sigma + var foursigmaFac = Factions["Four Sigma"]; + if ( + !foursigmaFac.isBanned && + !foursigmaFac.isMember && + !foursigmaFac.alreadyInvited && + checkMegacorpRequirements(LocationName.Sector12FourSigma) + ) { + invitedFactions.push(foursigmaFac); + } - //Silhouette - var silhouetteFac = Factions["Silhouette"]; - if (!silhouetteFac.isBanned && !silhouetteFac.isMember && !silhouetteFac.alreadyInvited && - (allPositions.includes("Chief Technology Officer") || - allPositions.includes("Chief Financial Officer") || - allPositions.includes("Chief Executive Officer")) && - this.money.gte(15000000) && this.karma <= -22) { - invitedFactions.push(silhouetteFac); - } + //KuaiGong International + var kuaigonginternationalFac = Factions["KuaiGong International"]; + if ( + !kuaigonginternationalFac.isBanned && + !kuaigonginternationalFac.isMember && + !kuaigonginternationalFac.alreadyInvited && + checkMegacorpRequirements(LocationName.ChongqingKuaiGongInternational) + ) { + invitedFactions.push(kuaigonginternationalFac); + } - //Tetrads - var tetradsFac = Factions["Tetrads"]; - if (!tetradsFac.isBanned && !tetradsFac.isMember && !tetradsFac.alreadyInvited && - (this.city == CityName.Chongqing || this.city == CityName.NewTokyo || - this.city == CityName.Ishima) && this.strength >= 75 && this.defense >= 75 && - this.dexterity >= 75 && this.agility >= 75 && this.karma <= -18) { - invitedFactions.push(tetradsFac); + //Fulcrum Secret Technologies - If u've unlocked fulcrum secret technolgoies server and have a high rep with the company + var fulcrumsecrettechonologiesFac = Factions["Fulcrum Secret Technologies"]; + var fulcrumSecretServer = + AllServers[SpecialServerIps[SpecialServerNames.FulcrumSecretTechnologies]]; + if (fulcrumSecretServer == null) { + console.error("Could not find Fulcrum Secret Technologies Server"); + } else { + if ( + !fulcrumsecrettechonologiesFac.isBanned && + !fulcrumsecrettechonologiesFac.isMember && + !fulcrumsecrettechonologiesFac.alreadyInvited && + fulcrumSecretServer.backdoorInstalled && + checkMegacorpRequirements(LocationName.AevumFulcrumTechnologies, 250e3) + ) { + invitedFactions.push(fulcrumsecrettechonologiesFac); } + } - //SlumSnakes - var slumsnakesFac = Factions["Slum Snakes"]; - if (!slumsnakesFac.isBanned && !slumsnakesFac.isMember && !slumsnakesFac.alreadyInvited && - this.strength >= 30 && this.defense >= 30 && this.dexterity >= 30 && - this.agility >= 30 && this.karma <= -9 && this.money.gte(1000000)) { - invitedFactions.push(slumsnakesFac); - } + //BitRunners + var bitrunnersFac = Factions["BitRunners"]; + var homeComp = this.getHomeComputer(); + var bitrunnersServer = + AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]]; + if (bitrunnersServer == null) { + console.error("Could not find BitRunners Server"); + } else if ( + !bitrunnersFac.isBanned && + !bitrunnersFac.isMember && + bitrunnersServer.backdoorInstalled && + !bitrunnersFac.alreadyInvited && + homeComp.maxRam >= 128 + ) { + invitedFactions.push(bitrunnersFac); + } - //Netburners - var netburnersFac = Factions["Netburners"]; - var totalHacknetRam = 0; - var totalHacknetCores = 0; - var totalHacknetLevels = 0; - for (let i = 0; i < this.hacknetNodes.length; ++i) { - if (hasHacknetServers()) { - const hserver = AllServers[this.hacknetNodes[i]]; - if (hserver) { - totalHacknetLevels += hserver.level; - totalHacknetRam += hserver.maxRam; - totalHacknetCores += hserver.cores; - } - } else { - totalHacknetLevels += this.hacknetNodes[i].level; - totalHacknetRam += this.hacknetNodes[i].ram; - totalHacknetCores += this.hacknetNodes[i].cores; - } - } - if (!netburnersFac.isBanned && !netburnersFac.isMember && !netburnersFac.alreadyInvited && - this.hacking_skill >= 80 && totalHacknetRam >= 8 && - totalHacknetCores >= 4 && totalHacknetLevels >= 100) { - invitedFactions.push(netburnersFac); - } + //The Black Hand + var theblackhandFac = Factions["The Black Hand"]; + var blackhandServer = + AllServers[SpecialServerIps[SpecialServerNames.TheBlackHandServer]]; + if (blackhandServer == null) { + console.error("Could not find The Black Hand Server"); + } else if ( + !theblackhandFac.isBanned && + !theblackhandFac.isMember && + blackhandServer.backdoorInstalled && + !theblackhandFac.alreadyInvited && + homeComp.maxRam >= 64 + ) { + invitedFactions.push(theblackhandFac); + } - //Tian Di Hui - var tiandihuiFac = Factions["Tian Di Hui"]; - if (!tiandihuiFac.isBanned && !tiandihuiFac.isMember && !tiandihuiFac.alreadyInvited && - this.money.gte(1000000) && this.hacking_skill >= 50 && - (this.city == CityName.Chongqing || this.city == CityName.NewTokyo || - this.city == CityName.Ishima)) { - invitedFactions.push(tiandihuiFac); - } + //NiteSec + var nitesecFac = Factions["NiteSec"]; + var nitesecServer = + AllServers[SpecialServerIps[SpecialServerNames.NiteSecServer]]; + if (nitesecServer == null) { + console.error("Could not find NiteSec Server"); + } else if ( + !nitesecFac.isBanned && + !nitesecFac.isMember && + nitesecServer.backdoorInstalled && + !nitesecFac.alreadyInvited && + homeComp.maxRam >= 32 + ) { + invitedFactions.push(nitesecFac); + } - //CyberSec - var cybersecFac = Factions["CyberSec"]; - var cybersecServer = AllServers[SpecialServerIps[SpecialServerNames.CyberSecServer]]; - if (cybersecServer == null) { - console.error("Could not find CyberSec Server"); - } else if (!cybersecFac.isBanned && !cybersecFac.isMember && cybersecServer.backdoorInstalled && - !cybersecFac.alreadyInvited) { - invitedFactions.push(cybersecFac); - } + //Chongqing + var chongqingFac = Factions["Chongqing"]; + if ( + !chongqingFac.isBanned && + !chongqingFac.isMember && + !chongqingFac.alreadyInvited && + this.money.gte(20000000) && + this.city == CityName.Chongqing + ) { + invitedFactions.push(chongqingFac); + } - return invitedFactions; + //Sector-12 + var sector12Fac = Factions["Sector-12"]; + if ( + !sector12Fac.isBanned && + !sector12Fac.isMember && + !sector12Fac.alreadyInvited && + this.money.gte(15000000) && + this.city == CityName.Sector12 + ) { + invitedFactions.push(sector12Fac); + } + + //New Tokyo + var newtokyoFac = Factions["New Tokyo"]; + if ( + !newtokyoFac.isBanned && + !newtokyoFac.isMember && + !newtokyoFac.alreadyInvited && + this.money.gte(20000000) && + this.city == CityName.NewTokyo + ) { + invitedFactions.push(newtokyoFac); + } + + //Aevum + var aevumFac = Factions["Aevum"]; + if ( + !aevumFac.isBanned && + !aevumFac.isMember && + !aevumFac.alreadyInvited && + this.money.gte(40000000) && + this.city == CityName.Aevum + ) { + invitedFactions.push(aevumFac); + } + + //Ishima + var ishimaFac = Factions["Ishima"]; + if ( + !ishimaFac.isBanned && + !ishimaFac.isMember && + !ishimaFac.alreadyInvited && + this.money.gte(30000000) && + this.city == CityName.Ishima + ) { + invitedFactions.push(ishimaFac); + } + + //Volhaven + var volhavenFac = Factions["Volhaven"]; + if ( + !volhavenFac.isBanned && + !volhavenFac.isMember && + !volhavenFac.alreadyInvited && + this.money.gte(50000000) && + this.city == CityName.Volhaven + ) { + invitedFactions.push(volhavenFac); + } + + //Speakers for the Dead + var speakersforthedeadFac = Factions["Speakers for the Dead"]; + if ( + !speakersforthedeadFac.isBanned && + !speakersforthedeadFac.isMember && + !speakersforthedeadFac.alreadyInvited && + this.hacking_skill >= 100 && + this.strength >= 300 && + this.defense >= 300 && + this.dexterity >= 300 && + this.agility >= 300 && + this.numPeopleKilled >= 30 && + this.karma <= -45 && + !allCompanies.includes(LocationName.Sector12CIA) && + !allCompanies.includes(LocationName.Sector12NSA) + ) { + invitedFactions.push(speakersforthedeadFac); + } + + //The Dark Army + var thedarkarmyFac = Factions["The Dark Army"]; + if ( + !thedarkarmyFac.isBanned && + !thedarkarmyFac.isMember && + !thedarkarmyFac.alreadyInvited && + this.hacking_skill >= 300 && + this.strength >= 300 && + this.defense >= 300 && + this.dexterity >= 300 && + this.agility >= 300 && + this.city == CityName.Chongqing && + this.numPeopleKilled >= 5 && + this.karma <= -45 && + !allCompanies.includes(LocationName.Sector12CIA) && + !allCompanies.includes(LocationName.Sector12NSA) + ) { + invitedFactions.push(thedarkarmyFac); + } + + //The Syndicate + var thesyndicateFac = Factions["The Syndicate"]; + if ( + !thesyndicateFac.isBanned && + !thesyndicateFac.isMember && + !thesyndicateFac.alreadyInvited && + this.hacking_skill >= 200 && + this.strength >= 200 && + this.defense >= 200 && + this.dexterity >= 200 && + this.agility >= 200 && + (this.city == CityName.Aevum || this.city == CityName.Sector12) && + this.money.gte(10000000) && + this.karma <= -90 && + !allCompanies.includes(LocationName.Sector12CIA) && + !allCompanies.includes(LocationName.Sector12NSA) + ) { + invitedFactions.push(thesyndicateFac); + } + + //Silhouette + var silhouetteFac = Factions["Silhouette"]; + if ( + !silhouetteFac.isBanned && + !silhouetteFac.isMember && + !silhouetteFac.alreadyInvited && + (allPositions.includes("Chief Technology Officer") || + allPositions.includes("Chief Financial Officer") || + allPositions.includes("Chief Executive Officer")) && + this.money.gte(15000000) && + this.karma <= -22 + ) { + invitedFactions.push(silhouetteFac); + } + + //Tetrads + var tetradsFac = Factions["Tetrads"]; + if ( + !tetradsFac.isBanned && + !tetradsFac.isMember && + !tetradsFac.alreadyInvited && + (this.city == CityName.Chongqing || + this.city == CityName.NewTokyo || + this.city == CityName.Ishima) && + this.strength >= 75 && + this.defense >= 75 && + this.dexterity >= 75 && + this.agility >= 75 && + this.karma <= -18 + ) { + invitedFactions.push(tetradsFac); + } + + //SlumSnakes + var slumsnakesFac = Factions["Slum Snakes"]; + if ( + !slumsnakesFac.isBanned && + !slumsnakesFac.isMember && + !slumsnakesFac.alreadyInvited && + this.strength >= 30 && + this.defense >= 30 && + this.dexterity >= 30 && + this.agility >= 30 && + this.karma <= -9 && + this.money.gte(1000000) + ) { + invitedFactions.push(slumsnakesFac); + } + + //Netburners + var netburnersFac = Factions["Netburners"]; + var totalHacknetRam = 0; + var totalHacknetCores = 0; + var totalHacknetLevels = 0; + for (let i = 0; i < this.hacknetNodes.length; ++i) { + if (hasHacknetServers()) { + const hserver = AllServers[this.hacknetNodes[i]]; + if (hserver) { + totalHacknetLevels += hserver.level; + totalHacknetRam += hserver.maxRam; + totalHacknetCores += hserver.cores; + } + } else { + totalHacknetLevels += this.hacknetNodes[i].level; + totalHacknetRam += this.hacknetNodes[i].ram; + totalHacknetCores += this.hacknetNodes[i].cores; + } + } + if ( + !netburnersFac.isBanned && + !netburnersFac.isMember && + !netburnersFac.alreadyInvited && + this.hacking_skill >= 80 && + totalHacknetRam >= 8 && + totalHacknetCores >= 4 && + totalHacknetLevels >= 100 + ) { + invitedFactions.push(netburnersFac); + } + + //Tian Di Hui + var tiandihuiFac = Factions["Tian Di Hui"]; + if ( + !tiandihuiFac.isBanned && + !tiandihuiFac.isMember && + !tiandihuiFac.alreadyInvited && + this.money.gte(1000000) && + this.hacking_skill >= 50 && + (this.city == CityName.Chongqing || + this.city == CityName.NewTokyo || + this.city == CityName.Ishima) + ) { + invitedFactions.push(tiandihuiFac); + } + + //CyberSec + var cybersecFac = Factions["CyberSec"]; + var cybersecServer = + AllServers[SpecialServerIps[SpecialServerNames.CyberSecServer]]; + if (cybersecServer == null) { + console.error("Could not find CyberSec Server"); + } else if ( + !cybersecFac.isBanned && + !cybersecFac.isMember && + cybersecServer.backdoorInstalled && + !cybersecFac.alreadyInvited + ) { + invitedFactions.push(cybersecFac); + } + + return invitedFactions; } /************* BitNodes **************/ export function setBitNodeNumber(n) { - this.bitNodeN = n; + this.bitNodeN = n; } export function queueAugmentation(name) { - for(const i in this.queuedAugmentations) { - if(this.queuedAugmentations[i].name == name) { - console.warn(`tried to queue ${name} twice, this may be a bug`); - return; - } + for (const i in this.queuedAugmentations) { + if (this.queuedAugmentations[i].name == name) { + console.warn(`tried to queue ${name} twice, this may be a bug`); + return; } + } - for(const i in this.augmentations) { - if(this.augmentations[i].name == name) { - console.warn(`tried to queue ${name} twice, this may be a bug`); - return; - } + for (const i in this.augmentations) { + if (this.augmentations[i].name == name) { + console.warn(`tried to queue ${name} twice, this may be a bug`); + return; } + } - this.firstAugPurchased = true; - this.queuedAugmentations.push(new PlayerOwnedAugmentation(name)); + this.firstAugPurchased = true; + this.queuedAugmentations.push(new PlayerOwnedAugmentation(name)); } /************* Coding Contracts **************/ -export function gainCodingContractReward(reward, difficulty=1) { - if (reward == null || reward.type == null || reward == null) { - return `No reward for this contract`; - } +export function gainCodingContractReward(reward, difficulty = 1) { + if (reward == null || reward.type == null || reward == null) { + return `No reward for this contract`; + } - /* eslint-disable no-case-declarations */ - switch (reward.type) { - case CodingContractRewardType.FactionReputation: - if (reward.name == null || !(Factions[reward.name] instanceof Faction)) { - // If no/invalid faction was designated, just give rewards to all factions - reward.type = CodingContractRewardType.FactionReputationAll; - return this.gainCodingContractReward(reward); - } - var repGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty; - Factions[reward.name].playerReputation += repGain; - return `Gained ${repGain} faction reputation for ${reward.name}`; - case CodingContractRewardType.FactionReputationAll: - const totalGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty; + /* eslint-disable no-case-declarations */ + switch (reward.type) { + case CodingContractRewardType.FactionReputation: + if (reward.name == null || !(Factions[reward.name] instanceof Faction)) { + // If no/invalid faction was designated, just give rewards to all factions + reward.type = CodingContractRewardType.FactionReputationAll; + return this.gainCodingContractReward(reward); + } + var repGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty; + Factions[reward.name].playerReputation += repGain; + return `Gained ${repGain} faction reputation for ${reward.name}`; + case CodingContractRewardType.FactionReputationAll: + const totalGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty; - // Ignore Bladeburners and other special factions for this calculation - const specialFactions = ["Bladeburners"]; - var factions = this.factions.slice(); - factions = factions.filter((f) => { - return !specialFactions.includes(f); - }); + // Ignore Bladeburners and other special factions for this calculation + const specialFactions = ["Bladeburners"]; + var factions = this.factions.slice(); + factions = factions.filter((f) => { + return !specialFactions.includes(f); + }); - // If the player was only part of the special factions, we'll just give money - if (factions.length == 0) { - reward.type = CodingContractRewardType.Money; - return this.gainCodingContractReward(reward, difficulty); - } + // If the player was only part of the special factions, we'll just give money + if (factions.length == 0) { + reward.type = CodingContractRewardType.Money; + return this.gainCodingContractReward(reward, difficulty); + } - const gainPerFaction = Math.floor(totalGain / factions.length); - for (const facName of factions) { - if (!(Factions[facName] instanceof Faction)) { continue; } - Factions[facName].playerReputation += gainPerFaction; - } - return `Gained ${gainPerFaction} reputation for each of the following factions: ${factions.toString()}`; - break; - case CodingContractRewardType.CompanyReputation: - if (reward.name == null || !(Companies[reward.name] instanceof Company)) { - //If no/invalid company was designated, just give rewards to all factions - reward.type = CodingContractRewardType.FactionReputationAll; - return this.gainCodingContractReward(reward); - } - var repGain = CONSTANTS.CodingContractBaseCompanyRepGain * difficulty; - Companies[reward.name].playerReputation += repGain; - return `Gained ${repGain} company reputation for ${reward.name}`; - break; - case CodingContractRewardType.Money: - default: - var moneyGain = CONSTANTS.CodingContractBaseMoneyGain * difficulty * BitNodeMultipliers.CodingContractMoney; - this.gainMoney(moneyGain); - this.recordMoneySource(moneyGain, "codingcontract"); - return `Gained ${numeralWrapper.formatMoney(moneyGain)}`; - break; - } - /* eslint-enable no-case-declarations */ + const gainPerFaction = Math.floor(totalGain / factions.length); + for (const facName of factions) { + if (!(Factions[facName] instanceof Faction)) { + continue; + } + Factions[facName].playerReputation += gainPerFaction; + } + return `Gained ${gainPerFaction} reputation for each of the following factions: ${factions.toString()}`; + break; + case CodingContractRewardType.CompanyReputation: + if (reward.name == null || !(Companies[reward.name] instanceof Company)) { + //If no/invalid company was designated, just give rewards to all factions + reward.type = CodingContractRewardType.FactionReputationAll; + return this.gainCodingContractReward(reward); + } + var repGain = CONSTANTS.CodingContractBaseCompanyRepGain * difficulty; + Companies[reward.name].playerReputation += repGain; + return `Gained ${repGain} company reputation for ${reward.name}`; + break; + case CodingContractRewardType.Money: + default: + var moneyGain = + CONSTANTS.CodingContractBaseMoneyGain * + difficulty * + BitNodeMultipliers.CodingContractMoney; + this.gainMoney(moneyGain); + this.recordMoneySource(moneyGain, "codingcontract"); + return `Gained ${numeralWrapper.formatMoney(moneyGain)}`; + break; + } + /* eslint-enable no-case-declarations */ } export function travel(to) { - if (Cities[to] == null) { - console.warn(`Player.travel() called with invalid city: ${to}`); - return false; - } - this.city = to; + if (Cities[to] == null) { + console.warn(`Player.travel() called with invalid city: ${to}`); + return false; + } + this.city = to; - return true; + return true; } export function gotoLocation(to) { - if (Locations[to] == null) { - console.warn(`Player.gotoLocation() called with invalid location: ${to}`); - return false; - } - this.location = to; + if (Locations[to] == null) { + console.warn(`Player.gotoLocation() called with invalid location: ${to}`); + return false; + } + this.location = to; - return true; + return true; } export function canAccessResleeving() { - return this.bitNodeN === 10 || (SourceFileFlags[10] > 0); + return this.bitNodeN === 10 || SourceFileFlags[10] > 0; } export function giveExploit(exploit) { - if(!this.exploits.includes(exploit)) { - this.exploits.push(exploit); - } + if (!this.exploits.includes(exploit)) { + this.exploits.push(exploit); + } } export function getIntelligenceBonus(weight) { - return calculateIntelligenceBonus(this.intelligence, weight); + return calculateIntelligenceBonus(this.intelligence, weight); } export function getCasinoWinnings() { - return this.moneySourceA.casino; -} \ No newline at end of file + return this.moneySourceA.casino; +} diff --git a/src/PersonObjects/Player/PlayerObjectServerMethods.ts b/src/PersonObjects/Player/PlayerObjectServerMethods.ts index e2053c392..f565fce1d 100644 --- a/src/PersonObjects/Player/PlayerObjectServerMethods.ts +++ b/src/PersonObjects/Player/PlayerObjectServerMethods.ts @@ -9,52 +9,56 @@ import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { Server } from "../../Server/Server"; import { HacknetServer } from "../../Hacknet/HacknetServer"; import { - AddToAllServers, - AllServers, - createUniqueRandomIp, + AddToAllServers, + AllServers, + createUniqueRandomIp, } from "../../Server/AllServers"; import { SpecialServerIps } from "../../Server/SpecialServerIps"; export function hasTorRouter(this: IPlayer): boolean { - return SpecialServerIps.hasOwnProperty("Darkweb Server"); + return SpecialServerIps.hasOwnProperty("Darkweb Server"); } export function getCurrentServer(this: IPlayer): Server | HacknetServer | null { - return AllServers[this.currentServer]; + return AllServers[this.currentServer]; } export function getHomeComputer(this: IPlayer): Server | HacknetServer | null { - return AllServers[this.homeComputer]; + return AllServers[this.homeComputer]; } export function getUpgradeHomeRamCost(this: IPlayer): number { - //Calculate how many times ram has been upgraded (doubled) - const currentRam = this.getHomeComputer().maxRam; - const numUpgrades = Math.log2(currentRam); + //Calculate how many times ram has been upgraded (doubled) + const currentRam = this.getHomeComputer().maxRam; + const numUpgrades = Math.log2(currentRam); - //Calculate cost - //Have cost increase by some percentage each time RAM has been upgraded - const mult = Math.pow(1.58, numUpgrades); - const cost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHome * mult * BitNodeMultipliers.HomeComputerRamCost; - return cost; + //Calculate cost + //Have cost increase by some percentage each time RAM has been upgraded + const mult = Math.pow(1.58, numUpgrades); + const cost = + currentRam * + CONSTANTS.BaseCostFor1GBOfRamHome * + mult * + BitNodeMultipliers.HomeComputerRamCost; + return cost; } export function createHacknetServer(this: IPlayer): HacknetServer { - const numOwned = this.hacknetNodes.length; - const name = `hacknet-node-${numOwned}`; - const server = new HacknetServer({ - adminRights: true, - hostname: name, - ip: createUniqueRandomIp(), - // player: this, - }); - this.hacknetNodes.push(server.ip); + const numOwned = this.hacknetNodes.length; + const name = `hacknet-node-${numOwned}`; + const server = new HacknetServer({ + adminRights: true, + hostname: name, + ip: createUniqueRandomIp(), + // player: this, + }); + this.hacknetNodes.push(server.ip); - // Configure the HacknetServer to actually act as a Server - AddToAllServers(server); - const homeComputer = this.getHomeComputer(); - homeComputer.serversOnNetwork.push(server.ip); - server.serversOnNetwork.push(homeComputer.ip); + // Configure the HacknetServer to actually act as a Server + AddToAllServers(server); + const homeComputer = this.getHomeComputer(); + homeComputer.serversOnNetwork.push(server.ip); + server.serversOnNetwork.push(homeComputer.ip); - return server; + return server; } diff --git a/src/PersonObjects/Resleeving/Resleeve.ts b/src/PersonObjects/Resleeving/Resleeve.ts index c289ac7ab..44cfabe16 100644 --- a/src/PersonObjects/Resleeving/Resleeve.ts +++ b/src/PersonObjects/Resleeving/Resleeve.ts @@ -7,57 +7,68 @@ import { Person } from "../Person"; import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentations } from "../../Augmentation/Augmentations"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../../utils/JSONReviver"; export class Resleeve extends Person { + constructor() { + super(); + } - constructor() { - super(); + getCost(): number { + // Each experience point adds this to the cost + const CostPerExp = 25e3; + + // Final cost is multiplied by this constant ^ # Augs + const NumAugsExponent = 1.2; + + // Get total exp in this re-sleeve + const totalExp: number = + this.hacking_exp + + this.strength_exp + + this.defense_exp + + this.dexterity_exp + + this.agility_exp + + this.charisma_exp; + + // Get total base Augmentation cost for this re-sleeve + let totalAugmentationCost = 0; + for (let i = 0; i < this.augmentations.length; ++i) { + const aug: Augmentation | null = + Augmentations[this.augmentations[i].name]; + if (aug == null) { + console.error( + `Could not find Augmentation ${this.augmentations[i].name}`, + ); + continue; + } + totalAugmentationCost += aug.startingCost; } - getCost(): number { - // Each experience point adds this to the cost - const CostPerExp = 25e3; + return ( + totalExp * CostPerExp + + totalAugmentationCost * + Math.pow(NumAugsExponent, this.augmentations.length) + ); + } - // Final cost is multiplied by this constant ^ # Augs - const NumAugsExponent = 1.2; + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Resleeve", this); + } - // Get total exp in this re-sleeve - const totalExp: number = this.hacking_exp + - this.strength_exp + - this.defense_exp + - this.dexterity_exp + - this.agility_exp + - this.charisma_exp; - - // Get total base Augmentation cost for this re-sleeve - let totalAugmentationCost = 0; - for (let i = 0; i < this.augmentations.length; ++i) { - const aug: Augmentation | null = Augmentations[this.augmentations[i].name]; - if (aug == null) { - console.error(`Could not find Augmentation ${this.augmentations[i].name}`); - continue; - } - totalAugmentationCost += aug.startingCost; - } - - return (totalExp * CostPerExp) + (totalAugmentationCost * Math.pow(NumAugsExponent, this.augmentations.length)); - } - - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("Resleeve", this); - } - - /** - * Initiatizes a Resleeve object from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Resleeve { - return Generic_fromJSON(Resleeve, value.data); - } + /** + * Initiatizes a Resleeve object from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Resleeve { + return Generic_fromJSON(Resleeve, value.data); + } } Reviver.constructors.Resleeve = Resleeve; diff --git a/src/PersonObjects/Resleeving/Resleeving.ts b/src/PersonObjects/Resleeving/Resleeving.ts index 1c09b9cbf..20c6e5be1 100644 --- a/src/PersonObjects/Resleeving/Resleeving.ts +++ b/src/PersonObjects/Resleeving/Resleeving.ts @@ -15,110 +15,120 @@ import { IPlayer } from "../IPlayer"; import { Augmentation } from "../../Augmentation/Augmentation"; import { Augmentations } from "../../Augmentation/Augmentations"; -import { IPlayerOwnedAugmentation, - PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation"; +import { + IPlayerOwnedAugmentation, + PlayerOwnedAugmentation, +} from "../../Augmentation/PlayerOwnedAugmentation"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { getRandomInt } from "../../../utils/helpers/getRandomInt"; - // Executes the actual re-sleeve when one is purchased export function purchaseResleeve(r: Resleeve, p: IPlayer): boolean { - const cost: number = r.getCost(); - if (!p.canAfford(cost)) { - return false; + const cost: number = r.getCost(); + if (!p.canAfford(cost)) { + return false; + } + p.loseMoney(cost); + + // Set the player's exp + p.hacking_exp = r.hacking_exp; + p.strength_exp = r.strength_exp; + p.defense_exp = r.defense_exp; + p.dexterity_exp = r.dexterity_exp; + p.agility_exp = r.agility_exp; + p.charisma_exp = r.charisma_exp; + + // Reset Augmentation "owned" data + for (const augKey in Augmentations) { + Augmentations[augKey].owned = false; + } + + // Clear all of the player's augmentations, except the NeuroFlux Governor + // which is kept + for (let i = p.augmentations.length - 1; i >= 0; --i) { + if (p.augmentations[i].name !== AugmentationNames.NeuroFluxGovernor) { + p.augmentations.splice(i, 1); + } else { + // NeuroFlux Governor + Augmentations[AugmentationNames.NeuroFluxGovernor].owned = true; } - p.loseMoney(cost); + } - // Set the player's exp - p.hacking_exp = r.hacking_exp; - p.strength_exp = r.strength_exp; - p.defense_exp = r.defense_exp; - p.dexterity_exp = r.dexterity_exp; - p.agility_exp = r.agility_exp; - p.charisma_exp = r.charisma_exp; + for (let i = 0; i < r.augmentations.length; ++i) { + p.augmentations.push(new PlayerOwnedAugmentation(r.augmentations[i].name)); + Augmentations[r.augmentations[i].name].owned = true; + } - // Reset Augmentation "owned" data - for (const augKey in Augmentations) { - Augmentations[augKey].owned = false; + // The player's purchased Augmentations should remain the same, but any purchased + // Augmentations that are given by the resleeve should be removed so there are no duplicates + for (let i = p.queuedAugmentations.length - 1; i >= 0; --i) { + const name: string = p.queuedAugmentations[i].name; + + if ( + p.augmentations.filter((e: IPlayerOwnedAugmentation) => { + return ( + e.name !== AugmentationNames.NeuroFluxGovernor && e.name === name + ); + }).length >= 1 + ) { + p.queuedAugmentations.splice(i, 1); } + } - // Clear all of the player's augmentations, except the NeuroFlux Governor - // which is kept - for (let i = p.augmentations.length - 1; i >= 0; --i) { - if (p.augmentations[i].name !== AugmentationNames.NeuroFluxGovernor) { - p.augmentations.splice(i, 1); - } else { - // NeuroFlux Governor - Augmentations[AugmentationNames.NeuroFluxGovernor].owned = true; - } - } - - for (let i = 0; i < r.augmentations.length; ++i) { - p.augmentations.push(new PlayerOwnedAugmentation(r.augmentations[i].name)); - Augmentations[r.augmentations[i].name].owned = true; - } - - // The player's purchased Augmentations should remain the same, but any purchased - // Augmentations that are given by the resleeve should be removed so there are no duplicates - for (let i = p.queuedAugmentations.length - 1; i >= 0; --i) { - const name: string = p.queuedAugmentations[i].name; - - if (p.augmentations.filter((e: IPlayerOwnedAugmentation) => {return e.name !== AugmentationNames.NeuroFluxGovernor && e.name === name}).length >= 1) { - p.queuedAugmentations.splice(i, 1); - } - } - - p.reapplyAllAugmentations(true); - p.reapplyAllSourceFiles(); //Multipliers get reset, so have to re-process source files too - return true; + p.reapplyAllAugmentations(true); + p.reapplyAllSourceFiles(); //Multipliers get reset, so have to re-process source files too + return true; } // Creates all of the Re-sleeves that will be available for purchase at VitaLife export function generateResleeves(): Resleeve[] { - const NumResleeves = 40; // Total number of Resleeves to generate + const NumResleeves = 40; // Total number of Resleeves to generate - const ret: Resleeve[] = []; - for (let i = 0; i < NumResleeves; ++i) { - // i will be a number indicating how "powerful" the Re-sleeve should be - const r: Resleeve = new Resleeve(); + const ret: Resleeve[] = []; + for (let i = 0; i < NumResleeves; ++i) { + // i will be a number indicating how "powerful" the Re-sleeve should be + const r: Resleeve = new Resleeve(); - // Generate experience - const expMult: number = (5 * i) + 1; - r.hacking_exp = expMult * getRandomInt(1000, 5000); - r.strength_exp = expMult * getRandomInt(1000, 5000); - r.defense_exp = expMult * getRandomInt(1000, 5000); - r.dexterity_exp = expMult * getRandomInt(1000, 5000); - r.agility_exp = expMult * getRandomInt(1000, 5000); - r.charisma_exp = expMult * getRandomInt(1000, 5000); + // Generate experience + const expMult: number = 5 * i + 1; + r.hacking_exp = expMult * getRandomInt(1000, 5000); + r.strength_exp = expMult * getRandomInt(1000, 5000); + r.defense_exp = expMult * getRandomInt(1000, 5000); + r.dexterity_exp = expMult * getRandomInt(1000, 5000); + r.agility_exp = expMult * getRandomInt(1000, 5000); + r.charisma_exp = expMult * getRandomInt(1000, 5000); - // Generate Augs - // Augmentation prequisites will be ignored for this - const baseNumAugs: number = Math.max(2, Math.ceil((i + 3) / 2)); - const numAugs: number = getRandomInt(baseNumAugs, baseNumAugs + 2); - const augKeys: string[] = Object.keys(Augmentations); - for (let a = 0; a < numAugs; ++a) { - // Get a random aug - const randIndex: number = getRandomInt(0, augKeys.length - 1) - const randKey: string = augKeys[randIndex]; + // Generate Augs + // Augmentation prequisites will be ignored for this + const baseNumAugs: number = Math.max(2, Math.ceil((i + 3) / 2)); + const numAugs: number = getRandomInt(baseNumAugs, baseNumAugs + 2); + const augKeys: string[] = Object.keys(Augmentations); + for (let a = 0; a < numAugs; ++a) { + // Get a random aug + const randIndex: number = getRandomInt(0, augKeys.length - 1); + const randKey: string = augKeys[randIndex]; - // Forbidden augmentations - if (randKey === AugmentationNames.TheRedPill || randKey === AugmentationNames.NeuroFluxGovernor) { - continue; - } - - const randAug: Augmentation | null = Augmentations[randKey]; - if(randAug === null) throw new Error(`null augmentation: ${randKey}`) - r.augmentations.push({name: randAug.name, level: 1}); - r.applyAugmentation(Augmentations[randKey]); - r.updateStatLevels(); + // Forbidden augmentations + if ( + randKey === AugmentationNames.TheRedPill || + randKey === AugmentationNames.NeuroFluxGovernor + ) { + continue; + } - // Remove Augmentation so that there are no duplicates - augKeys.splice(randIndex, 1); - } + const randAug: Augmentation | null = Augmentations[randKey]; + if (randAug === null) throw new Error(`null augmentation: ${randKey}`); + r.augmentations.push({ name: randAug.name, level: 1 }); + r.applyAugmentation(Augmentations[randKey]); + r.updateStatLevels(); - ret.push(r); + // Remove Augmentation so that there are no duplicates + augKeys.splice(randIndex, 1); } - return ret; + ret.push(r); + } + + return ret; } diff --git a/src/PersonObjects/Resleeving/ResleevingUI.tsx b/src/PersonObjects/Resleeving/ResleevingUI.tsx index 008d7f785..504abf817 100644 --- a/src/PersonObjects/Resleeving/ResleevingUI.tsx +++ b/src/PersonObjects/Resleeving/ResleevingUI.tsx @@ -2,8 +2,7 @@ * Module for handling the Re-sleeving UI */ import { Resleeve } from "./Resleeve"; -import { generateResleeves, - purchaseResleeve } from "./Resleeving"; +import { generateResleeves, purchaseResleeve } from "./Resleeving"; import { IPlayer } from "../IPlayer"; @@ -12,8 +11,7 @@ import { Augmentations } from "../../Augmentation/Augmentations"; import { numeralWrapper } from "../../ui/numeralFormat"; import { Money } from "../../ui/React/Money"; -import { Page, - routing } from "../../ui/navigationTracking"; +import { Page, routing } from "../../ui/navigationTracking"; import { dialogBoxCreate } from "../../../utils/DialogBox"; @@ -26,353 +24,509 @@ import { removeChildrenFromElement } from "../../../utils/uiHelpers/removeChildr import { removeElement } from "../../../utils/uiHelpers/removeElement"; import * as React from "react"; -import { renderToStaticMarkup } from "react-dom/server" +import { renderToStaticMarkup } from "react-dom/server"; interface IResleeveUIElems { - container: HTMLElement | null; - statsPanel: HTMLElement | null; - stats: HTMLElement | null; - multipliersButton: HTMLElement | null; - augPanel: HTMLElement | null; - augSelector: HTMLSelectElement | null; - augDescription: HTMLElement | null; - costPanel: HTMLElement | null; - costText: HTMLElement | null; - buyButton: HTMLElement | null; + container: HTMLElement | null; + statsPanel: HTMLElement | null; + stats: HTMLElement | null; + multipliersButton: HTMLElement | null; + augPanel: HTMLElement | null; + augSelector: HTMLSelectElement | null; + augDescription: HTMLElement | null; + costPanel: HTMLElement | null; + costText: HTMLElement | null; + buyButton: HTMLElement | null; } interface IPageUIElems { - container: HTMLElement | null; - info: HTMLElement | null; - sortTag: HTMLElement | null; - sortSelector: HTMLSelectElement | null; - resleeveList: HTMLElement | null; - resleeves: IResleeveUIElems[] | null; + container: HTMLElement | null; + info: HTMLElement | null; + sortTag: HTMLElement | null; + sortSelector: HTMLSelectElement | null; + resleeveList: HTMLElement | null; + resleeves: IResleeveUIElems[] | null; } const UIElems: IPageUIElems = { - container: null, - info: null, - sortTag: null, - sortSelector: null, - resleeveList: null, - resleeves: null, -} + container: null, + info: null, + sortTag: null, + sortSelector: null, + resleeveList: null, + resleeves: null, +}; let playerRef: IPlayer | null; export function createResleevesPage(p: IPlayer): void { - if (!routing.isOn(Page.Resleeves)) { return; } + if (!routing.isOn(Page.Resleeves)) { + return; + } - try { - playerRef = p; + try { + playerRef = p; - UIElems.container = createElement("div", { - class: "generic-menupage-container", - id: "resleeves-container", - position: "fixed", - }); + UIElems.container = createElement("div", { + class: "generic-menupage-container", + id: "resleeves-container", + position: "fixed", + }); - UIElems.info = createElement("p", { - display: "block", - innerHTML: "Re-sleeving is the process of digitizing and transferring your consciousness " + - "into a new human body, or 'sleeve'. Here at VitaLife, you can purchase new " + - "specially-engineered bodies for the re-sleeve process. Many of these bodies " + - "even come with genetic and cybernetic Augmentations!

    " + - "Re-sleeving will change your experience for every stat. It will also REMOVE " + - "all of your currently-installed Augmentations, and replace " + - "them with the ones provided by the purchased sleeve. However, Augmentations that you have " + - "purchased but not installed will NOT be removed. If you have purchased an " + - "Augmentation and then re-sleeve into a body which already has that Augmentation, " + - "it will be removed (since you cannot have duplicate Augmentations).

    " + - "NOTE: The stats and multipliers displayed on this page do NOT include your bonuses from " + - "Source-File.", - width: "75%", - }); + UIElems.info = createElement("p", { + display: "block", + innerHTML: + "Re-sleeving is the process of digitizing and transferring your consciousness " + + "into a new human body, or 'sleeve'. Here at VitaLife, you can purchase new " + + "specially-engineered bodies for the re-sleeve process. Many of these bodies " + + "even come with genetic and cybernetic Augmentations!

    " + + "Re-sleeving will change your experience for every stat. It will also REMOVE " + + "all of your currently-installed Augmentations, and replace " + + "them with the ones provided by the purchased sleeve. However, Augmentations that you have " + + "purchased but not installed will NOT be removed. If you have purchased an " + + "Augmentation and then re-sleeve into a body which already has that Augmentation, " + + "it will be removed (since you cannot have duplicate Augmentations).

    " + + "NOTE: The stats and multipliers displayed on this page do NOT include your bonuses from " + + "Source-File.", + width: "75%", + }); - // Randomly create all Resleeves if they dont already exist - if (p.resleeves.length === 0) { - p.resleeves = generateResleeves(); - } - - // Create a selector for sorting the list of Resleeves - UIElems.sortTag = createElement("p", { - display: "inline-block", - innerText: "Sort By: ", - }); - UIElems.sortSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement; - - enum SortOption { - Cost = "Cost", - Hacking = "Hacking", - Strength = "Strength", - Defense = "Defense", - Dexterity = "Dexterity", - Agility = "Agility", - Charisma = "Charisma", - AverageCombatStats = "AverageCombat", - AverageAllStats = "AverageAllStats", - TotalNumAugmentations = "TotalNumAugmentations", - } - - UIElems.sortSelector.add(createOptionElement("Cost", SortOption.Cost)); - UIElems.sortSelector.add(createOptionElement("Hacking Level", SortOption.Hacking)); - UIElems.sortSelector.add(createOptionElement("Strength Level", SortOption.Strength)); - UIElems.sortSelector.add(createOptionElement("Defense Level", SortOption.Defense)); - UIElems.sortSelector.add(createOptionElement("Dexterity Level", SortOption.Dexterity)); - UIElems.sortSelector.add(createOptionElement("Agility Level", SortOption.Agility)); - UIElems.sortSelector.add(createOptionElement("Charisma Level", SortOption.Charisma)); - UIElems.sortSelector.add(createOptionElement("Average Combat Stats", SortOption.AverageCombatStats)); - UIElems.sortSelector.add(createOptionElement("Average Stats", SortOption.AverageAllStats)); - UIElems.sortSelector.add(createOptionElement("Number of Augmentations", SortOption.TotalNumAugmentations)); - - UIElems.resleeveList = createElement("ul"); - UIElems.sortSelector.onchange = () => { - removeChildrenFromElement(UIElems.resleeveList); - UIElems.resleeves = []; - - // Helper function for averaging - function getAverage(...values: number[]): number { - let sum = 0; - for (let i = 0; i < values.length; ++i) { - sum += values[i]; - } - - return sum / values.length; - } - - const sortOpt = getSelectValue(UIElems.sortSelector); - switch (sortOpt) { - case SortOption.Hacking: - p.resleeves.sort((a, b) => { - return a.hacking_skill - b.hacking_skill; - }); - break; - case SortOption.Strength: - p.resleeves.sort((a, b) => { - return a.strength - b.strength; - }); - break; - case SortOption.Defense: - p.resleeves.sort((a, b) => { - return a.defense - b.defense; - }); - break; - case SortOption.Dexterity: - p.resleeves.sort((a, b) => { - return a.dexterity - b.dexterity; - }); - break; - case SortOption.Agility: - p.resleeves.sort((a, b) => { - return a.agility - b.agility; - }); - break; - case SortOption.Charisma: - p.resleeves.sort((a, b) => { - return a.charisma - b.charisma; - }); - break; - case SortOption.AverageCombatStats: - p.resleeves.sort((a, b) => { - const aAvg = getAverage(a.strength, a.defense, a.dexterity, a.agility); - const bAvg = getAverage(b.strength, b.defense, b.dexterity, b.agility); - - return aAvg - bAvg; - }); - break; - case SortOption.AverageAllStats: - p.resleeves.sort((a, b) => { - const aAvg = getAverage(a.hacking_skill, a.strength, a.defense, a.dexterity, a.agility, a.charisma); - const bAvg = getAverage(b.hacking_skill, b.strength, b.defense, b.dexterity, b.agility, b.charisma); - - return aAvg - bAvg; - }); - break; - case SortOption.TotalNumAugmentations: - p.resleeves.sort((a, b) => { - return a.augmentations.length - b.augmentations.length; - }); - break; - case SortOption.Cost: - default: - p.resleeves.sort((a, b) => { - return a.getCost() - b.getCost(); - }); - break; - } - - if(UIElems.resleeveList == null) throw new Error("UIElems.resleeveList is null in sortSelector.click()"); - if(UIElems.resleeves == null) throw new Error("UIElems.resleeves is null in sortSelector.click()"); - - // Create UI for all Resleeves - for (const resleeve of p.resleeves) { - const resleeveUi = createResleeveUi(resleeve); - if(resleeveUi.container == null) throw new Error("resleeveUi.container is null in sortSelector.click()"); - UIElems.resleeveList.appendChild(resleeveUi.container); - UIElems.resleeves.push(resleeveUi); - } - } - UIElems.sortSelector.dispatchEvent(new Event('change')); // Force onchange event - - UIElems.container.appendChild(UIElems.info); - UIElems.container.appendChild(createElement("br")); - UIElems.container.appendChild(UIElems.sortTag); - UIElems.container.appendChild(UIElems.sortSelector); - UIElems.container.appendChild(UIElems.resleeveList); - - const container = document.getElementById("entire-game-container"); - if(container == null) throw new Error("Could not find entire-game-container in createResleevesPage()"); - container.appendChild(UIElems.container); - } catch(e) { - exceptionAlert(e); + // Randomly create all Resleeves if they dont already exist + if (p.resleeves.length === 0) { + p.resleeves = generateResleeves(); } + + // Create a selector for sorting the list of Resleeves + UIElems.sortTag = createElement("p", { + display: "inline-block", + innerText: "Sort By: ", + }); + UIElems.sortSelector = createElement("select", { + class: "dropdown", + }) as HTMLSelectElement; + + enum SortOption { + Cost = "Cost", + Hacking = "Hacking", + Strength = "Strength", + Defense = "Defense", + Dexterity = "Dexterity", + Agility = "Agility", + Charisma = "Charisma", + AverageCombatStats = "AverageCombat", + AverageAllStats = "AverageAllStats", + TotalNumAugmentations = "TotalNumAugmentations", + } + + UIElems.sortSelector.add(createOptionElement("Cost", SortOption.Cost)); + UIElems.sortSelector.add( + createOptionElement("Hacking Level", SortOption.Hacking), + ); + UIElems.sortSelector.add( + createOptionElement("Strength Level", SortOption.Strength), + ); + UIElems.sortSelector.add( + createOptionElement("Defense Level", SortOption.Defense), + ); + UIElems.sortSelector.add( + createOptionElement("Dexterity Level", SortOption.Dexterity), + ); + UIElems.sortSelector.add( + createOptionElement("Agility Level", SortOption.Agility), + ); + UIElems.sortSelector.add( + createOptionElement("Charisma Level", SortOption.Charisma), + ); + UIElems.sortSelector.add( + createOptionElement( + "Average Combat Stats", + SortOption.AverageCombatStats, + ), + ); + UIElems.sortSelector.add( + createOptionElement("Average Stats", SortOption.AverageAllStats), + ); + UIElems.sortSelector.add( + createOptionElement( + "Number of Augmentations", + SortOption.TotalNumAugmentations, + ), + ); + + UIElems.resleeveList = createElement("ul"); + UIElems.sortSelector.onchange = () => { + removeChildrenFromElement(UIElems.resleeveList); + UIElems.resleeves = []; + + // Helper function for averaging + function getAverage(...values: number[]): number { + let sum = 0; + for (let i = 0; i < values.length; ++i) { + sum += values[i]; + } + + return sum / values.length; + } + + const sortOpt = getSelectValue(UIElems.sortSelector); + switch (sortOpt) { + case SortOption.Hacking: + p.resleeves.sort((a, b) => { + return a.hacking_skill - b.hacking_skill; + }); + break; + case SortOption.Strength: + p.resleeves.sort((a, b) => { + return a.strength - b.strength; + }); + break; + case SortOption.Defense: + p.resleeves.sort((a, b) => { + return a.defense - b.defense; + }); + break; + case SortOption.Dexterity: + p.resleeves.sort((a, b) => { + return a.dexterity - b.dexterity; + }); + break; + case SortOption.Agility: + p.resleeves.sort((a, b) => { + return a.agility - b.agility; + }); + break; + case SortOption.Charisma: + p.resleeves.sort((a, b) => { + return a.charisma - b.charisma; + }); + break; + case SortOption.AverageCombatStats: + p.resleeves.sort((a, b) => { + const aAvg = getAverage( + a.strength, + a.defense, + a.dexterity, + a.agility, + ); + const bAvg = getAverage( + b.strength, + b.defense, + b.dexterity, + b.agility, + ); + + return aAvg - bAvg; + }); + break; + case SortOption.AverageAllStats: + p.resleeves.sort((a, b) => { + const aAvg = getAverage( + a.hacking_skill, + a.strength, + a.defense, + a.dexterity, + a.agility, + a.charisma, + ); + const bAvg = getAverage( + b.hacking_skill, + b.strength, + b.defense, + b.dexterity, + b.agility, + b.charisma, + ); + + return aAvg - bAvg; + }); + break; + case SortOption.TotalNumAugmentations: + p.resleeves.sort((a, b) => { + return a.augmentations.length - b.augmentations.length; + }); + break; + case SortOption.Cost: + default: + p.resleeves.sort((a, b) => { + return a.getCost() - b.getCost(); + }); + break; + } + + if (UIElems.resleeveList == null) + throw new Error("UIElems.resleeveList is null in sortSelector.click()"); + if (UIElems.resleeves == null) + throw new Error("UIElems.resleeves is null in sortSelector.click()"); + + // Create UI for all Resleeves + for (const resleeve of p.resleeves) { + const resleeveUi = createResleeveUi(resleeve); + if (resleeveUi.container == null) + throw new Error( + "resleeveUi.container is null in sortSelector.click()", + ); + UIElems.resleeveList.appendChild(resleeveUi.container); + UIElems.resleeves.push(resleeveUi); + } + }; + UIElems.sortSelector.dispatchEvent(new Event("change")); // Force onchange event + + UIElems.container.appendChild(UIElems.info); + UIElems.container.appendChild(createElement("br")); + UIElems.container.appendChild(UIElems.sortTag); + UIElems.container.appendChild(UIElems.sortSelector); + UIElems.container.appendChild(UIElems.resleeveList); + + const container = document.getElementById("entire-game-container"); + if (container == null) + throw new Error( + "Could not find entire-game-container in createResleevesPage()", + ); + container.appendChild(UIElems.container); + } catch (e) { + exceptionAlert(e); + } } export function clearResleevesPage(): void { - if (UIElems.container instanceof HTMLElement) { - removeElement(UIElems.container); - } + if (UIElems.container instanceof HTMLElement) { + removeElement(UIElems.container); + } - for (const prop in UIElems) { - (UIElems as any)[prop] = null; - } + for (const prop in UIElems) { + (UIElems as any)[prop] = null; + } - playerRef = null; + playerRef = null; } function createResleeveUi(resleeve: Resleeve): IResleeveUIElems { - const elems: IResleeveUIElems = { - container: null, - statsPanel: null, - stats: null, - multipliersButton: null, - augPanel: null, - augSelector: null, - augDescription: null, - costPanel: null, - costText: null, - buyButton: null, - }; - if(playerRef === null) return elems; - - if (!routing.isOn(Page.Resleeves)) { return elems; } - - elems.container = createElement("div", { - class: "resleeve-container", - display: "block", - }); - - elems.statsPanel = createElement("div", { class: "resleeve-panel", width: "30%" }); - elems.stats = createElement("p", { - class: "resleeve-stats-text", - innerHTML: - `Hacking: ${numeralWrapper.formatSkill(resleeve.hacking_skill)} (${numeralWrapper.formatExp(resleeve.hacking_exp)} exp)
    ` + - `Strength: ${numeralWrapper.formatSkill(resleeve.strength)} (${numeralWrapper.formatExp(resleeve.strength_exp)} exp)
    ` + - `Defense: ${numeralWrapper.formatSkill(resleeve.defense)} (${numeralWrapper.formatExp(resleeve.defense_exp)} exp)
    ` + - `Dexterity: ${numeralWrapper.formatSkill(resleeve.dexterity)} (${numeralWrapper.formatExp(resleeve.dexterity_exp)} exp)
    ` + - `Agility: ${numeralWrapper.formatSkill(resleeve.agility)} (${numeralWrapper.formatExp(resleeve.agility_exp)} exp)
    ` + - `Charisma: ${numeralWrapper.formatSkill(resleeve.charisma)} (${numeralWrapper.formatExp(resleeve.charisma_exp)} exp)
    ` + - `# Augmentations: ${resleeve.augmentations.length}`, - }); - elems.multipliersButton = createElement("button", { - class: "std-button", - innerText: "Multipliers", - clickListener: () => { - dialogBoxCreate( - [ - "

    Total Multipliers:

    ", - `Hacking Level multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_mult)}`, - `Hacking Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_exp_mult)}`, - `Strength Level multiplier: ${numeralWrapper.formatPercentage(resleeve.strength_mult)}`, - `Strength Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.strength_exp_mult)}`, - `Defense Level multiplier: ${numeralWrapper.formatPercentage(resleeve.defense_mult)}`, - `Defense Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.defense_exp_mult)}`, - `Dexterity Level multiplier: ${numeralWrapper.formatPercentage(resleeve.dexterity_mult)}`, - `Dexterity Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.dexterity_exp_mult)}`, - `Agility Level multiplier: ${numeralWrapper.formatPercentage(resleeve.agility_mult)}`, - `Agility Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.agility_exp_mult)}`, - `Charisma Level multiplier: ${numeralWrapper.formatPercentage(resleeve.charisma_mult)}`, - `Charisma Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.charisma_exp_mult)}`, - `Hacking Chance multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_chance_mult)}`, - `Hacking Speed multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_speed_mult)}`, - `Hacking Money multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_money_mult)}`, - `Hacking Growth multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_grow_mult)}`, - `Salary multiplier: ${numeralWrapper.formatPercentage(resleeve.work_money_mult)}`, - `Company Reputation Gain multiplier: ${numeralWrapper.formatPercentage(resleeve.company_rep_mult)}`, - `Faction Reputation Gain multiplier: ${numeralWrapper.formatPercentage(resleeve.faction_rep_mult)}`, - `Crime Money multiplier: ${numeralWrapper.formatPercentage(resleeve.crime_money_mult)}`, - `Crime Success multiplier: ${numeralWrapper.formatPercentage(resleeve.crime_success_mult)}`, - `Hacknet Income multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_money_mult)}`, - `Hacknet Purchase Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_purchase_cost_mult)}`, - `Hacknet Level Upgrade Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_level_cost_mult)}`, - `Hacknet Ram Upgrade Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_ram_cost_mult)}`, - `Hacknet Core Upgrade Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_core_cost_mult)}`, - `Bladeburner Max Stamina multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_max_stamina_mult)}`, - `Bladeburner Stamina Gain multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_stamina_gain_mult)}`, - `Bladeburner Field Analysis multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_analysis_mult)}`, - `Bladeburner Success Chance multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_success_chance_mult)}`, - ].join("
    "), false, - ) - }, - }); - elems.statsPanel.appendChild(elems.stats); - elems.statsPanel.appendChild(elems.multipliersButton); - - elems.augPanel = createElement("div", { class: "resleeve-panel", width: "50%" }); - elems.augSelector = createElement("select", { class: "resleeve-aug-selector dropdown" }) as HTMLSelectElement; - elems.augDescription = createElement("p"); - for (let i = 0; i < resleeve.augmentations.length; ++i) { - elems.augSelector.add(createOptionElement(resleeve.augmentations[i].name)); - } - elems.augSelector.addEventListener("change", () => { - updateAugDescription(elems); - }); - elems.augSelector.dispatchEvent(new Event('change')); // Set inital description by manually triggering change event - elems.augPanel.appendChild(elems.augSelector); - elems.augPanel.appendChild(elems.augDescription); - - const cost: number = resleeve.getCost(); - elems.costPanel = createElement("div", { class: "resleeve-panel", width: "20%" }); - elems.costText = createElement("p", { - innerHTML: `It costs ${renderToStaticMarkup()} ` + - `to purchase this Sleeve.`, - }); - elems.buyButton = createElement("button", { - class: "std-button", - innerText: "Purchase", - clickListener: () => { - if(playerRef == null) throw new Error("playerRef is null in buyButton.click()"); - if (purchaseResleeve(resleeve, playerRef)) { - dialogBoxCreate((<>You re-sleeved for !), false); - } else { - dialogBoxCreate(`You cannot afford to re-sleeve into this body`, false); - } - }, - }); - elems.costPanel.appendChild(elems.costText); - elems.costPanel.appendChild(elems.buyButton); - - elems.container.appendChild(elems.statsPanel); - elems.container.appendChild(elems.augPanel); - elems.container.appendChild(elems.costPanel); + const elems: IResleeveUIElems = { + container: null, + statsPanel: null, + stats: null, + multipliersButton: null, + augPanel: null, + augSelector: null, + augDescription: null, + costPanel: null, + costText: null, + buyButton: null, + }; + if (playerRef === null) return elems; + if (!routing.isOn(Page.Resleeves)) { return elems; + } + + elems.container = createElement("div", { + class: "resleeve-container", + display: "block", + }); + + elems.statsPanel = createElement("div", { + class: "resleeve-panel", + width: "30%", + }); + elems.stats = createElement("p", { + class: "resleeve-stats-text", + innerHTML: + `Hacking: ${numeralWrapper.formatSkill( + resleeve.hacking_skill, + )} (${numeralWrapper.formatExp(resleeve.hacking_exp)} exp)
    ` + + `Strength: ${numeralWrapper.formatSkill( + resleeve.strength, + )} (${numeralWrapper.formatExp(resleeve.strength_exp)} exp)
    ` + + `Defense: ${numeralWrapper.formatSkill( + resleeve.defense, + )} (${numeralWrapper.formatExp(resleeve.defense_exp)} exp)
    ` + + `Dexterity: ${numeralWrapper.formatSkill( + resleeve.dexterity, + )} (${numeralWrapper.formatExp(resleeve.dexterity_exp)} exp)
    ` + + `Agility: ${numeralWrapper.formatSkill( + resleeve.agility, + )} (${numeralWrapper.formatExp(resleeve.agility_exp)} exp)
    ` + + `Charisma: ${numeralWrapper.formatSkill( + resleeve.charisma, + )} (${numeralWrapper.formatExp(resleeve.charisma_exp)} exp)
    ` + + `# Augmentations: ${resleeve.augmentations.length}`, + }); + elems.multipliersButton = createElement("button", { + class: "std-button", + innerText: "Multipliers", + clickListener: () => { + dialogBoxCreate( + [ + "

    Total Multipliers:

    ", + `Hacking Level multiplier: ${numeralWrapper.formatPercentage( + resleeve.hacking_mult, + )}`, + `Hacking Experience multiplier: ${numeralWrapper.formatPercentage( + resleeve.hacking_exp_mult, + )}`, + `Strength Level multiplier: ${numeralWrapper.formatPercentage( + resleeve.strength_mult, + )}`, + `Strength Experience multiplier: ${numeralWrapper.formatPercentage( + resleeve.strength_exp_mult, + )}`, + `Defense Level multiplier: ${numeralWrapper.formatPercentage( + resleeve.defense_mult, + )}`, + `Defense Experience multiplier: ${numeralWrapper.formatPercentage( + resleeve.defense_exp_mult, + )}`, + `Dexterity Level multiplier: ${numeralWrapper.formatPercentage( + resleeve.dexterity_mult, + )}`, + `Dexterity Experience multiplier: ${numeralWrapper.formatPercentage( + resleeve.dexterity_exp_mult, + )}`, + `Agility Level multiplier: ${numeralWrapper.formatPercentage( + resleeve.agility_mult, + )}`, + `Agility Experience multiplier: ${numeralWrapper.formatPercentage( + resleeve.agility_exp_mult, + )}`, + `Charisma Level multiplier: ${numeralWrapper.formatPercentage( + resleeve.charisma_mult, + )}`, + `Charisma Experience multiplier: ${numeralWrapper.formatPercentage( + resleeve.charisma_exp_mult, + )}`, + `Hacking Chance multiplier: ${numeralWrapper.formatPercentage( + resleeve.hacking_chance_mult, + )}`, + `Hacking Speed multiplier: ${numeralWrapper.formatPercentage( + resleeve.hacking_speed_mult, + )}`, + `Hacking Money multiplier: ${numeralWrapper.formatPercentage( + resleeve.hacking_money_mult, + )}`, + `Hacking Growth multiplier: ${numeralWrapper.formatPercentage( + resleeve.hacking_grow_mult, + )}`, + `Salary multiplier: ${numeralWrapper.formatPercentage( + resleeve.work_money_mult, + )}`, + `Company Reputation Gain multiplier: ${numeralWrapper.formatPercentage( + resleeve.company_rep_mult, + )}`, + `Faction Reputation Gain multiplier: ${numeralWrapper.formatPercentage( + resleeve.faction_rep_mult, + )}`, + `Crime Money multiplier: ${numeralWrapper.formatPercentage( + resleeve.crime_money_mult, + )}`, + `Crime Success multiplier: ${numeralWrapper.formatPercentage( + resleeve.crime_success_mult, + )}`, + `Hacknet Income multiplier: ${numeralWrapper.formatPercentage( + resleeve.hacknet_node_money_mult, + )}`, + `Hacknet Purchase Cost multiplier: ${numeralWrapper.formatPercentage( + resleeve.hacknet_node_purchase_cost_mult, + )}`, + `Hacknet Level Upgrade Cost multiplier: ${numeralWrapper.formatPercentage( + resleeve.hacknet_node_level_cost_mult, + )}`, + `Hacknet Ram Upgrade Cost multiplier: ${numeralWrapper.formatPercentage( + resleeve.hacknet_node_ram_cost_mult, + )}`, + `Hacknet Core Upgrade Cost multiplier: ${numeralWrapper.formatPercentage( + resleeve.hacknet_node_core_cost_mult, + )}`, + `Bladeburner Max Stamina multiplier: ${numeralWrapper.formatPercentage( + resleeve.bladeburner_max_stamina_mult, + )}`, + `Bladeburner Stamina Gain multiplier: ${numeralWrapper.formatPercentage( + resleeve.bladeburner_stamina_gain_mult, + )}`, + `Bladeburner Field Analysis multiplier: ${numeralWrapper.formatPercentage( + resleeve.bladeburner_analysis_mult, + )}`, + `Bladeburner Success Chance multiplier: ${numeralWrapper.formatPercentage( + resleeve.bladeburner_success_chance_mult, + )}`, + ].join("
    "), + false, + ); + }, + }); + elems.statsPanel.appendChild(elems.stats); + elems.statsPanel.appendChild(elems.multipliersButton); + + elems.augPanel = createElement("div", { + class: "resleeve-panel", + width: "50%", + }); + elems.augSelector = createElement("select", { + class: "resleeve-aug-selector dropdown", + }) as HTMLSelectElement; + elems.augDescription = createElement("p"); + for (let i = 0; i < resleeve.augmentations.length; ++i) { + elems.augSelector.add(createOptionElement(resleeve.augmentations[i].name)); + } + elems.augSelector.addEventListener("change", () => { + updateAugDescription(elems); + }); + elems.augSelector.dispatchEvent(new Event("change")); // Set inital description by manually triggering change event + elems.augPanel.appendChild(elems.augSelector); + elems.augPanel.appendChild(elems.augDescription); + + const cost: number = resleeve.getCost(); + elems.costPanel = createElement("div", { + class: "resleeve-panel", + width: "20%", + }); + elems.costText = createElement("p", { + innerHTML: + `It costs ${renderToStaticMarkup( + , + )} ` + `to purchase this Sleeve.`, + }); + elems.buyButton = createElement("button", { + class: "std-button", + innerText: "Purchase", + clickListener: () => { + if (playerRef == null) + throw new Error("playerRef is null in buyButton.click()"); + if (purchaseResleeve(resleeve, playerRef)) { + dialogBoxCreate( + <> + You re-sleeved for ! + , + false, + ); + } else { + dialogBoxCreate(`You cannot afford to re-sleeve into this body`, false); + } + }, + }); + elems.costPanel.appendChild(elems.costText); + elems.costPanel.appendChild(elems.buyButton); + + elems.container.appendChild(elems.statsPanel); + elems.container.appendChild(elems.augPanel); + elems.container.appendChild(elems.costPanel); + + return elems; } function updateAugDescription(elems: IResleeveUIElems): void { - if(elems.augDescription == null) throw new Error("elems.augDescription is null in updateAugDescription()"); - const augName: string = getSelectValue(elems.augSelector); - const aug: Augmentation | null = Augmentations[augName]; - if (aug == null) { - console.warn(`Could not find Augmentation with name ${augName}`); - return; - } + if (elems.augDescription == null) + throw new Error("elems.augDescription is null in updateAugDescription()"); + const augName: string = getSelectValue(elems.augSelector); + const aug: Augmentation | null = Augmentations[augName]; + if (aug == null) { + console.warn(`Could not find Augmentation with name ${augName}`); + return; + } - let innerHTML = aug.info; - if(typeof innerHTML !== 'string') { - innerHTML = renderToStaticMarkup(innerHTML); - } + let innerHTML = aug.info; + if (typeof innerHTML !== "string") { + innerHTML = renderToStaticMarkup(innerHTML); + } - elems.augDescription.innerHTML = innerHTML; + elems.augDescription.innerHTML = innerHTML; } diff --git a/src/PersonObjects/Sleeve/Sleeve.ts b/src/PersonObjects/Sleeve/Sleeve.ts index 9a711cadd..4f7488de2 100644 --- a/src/PersonObjects/Sleeve/Sleeve.ts +++ b/src/PersonObjects/Sleeve/Sleeve.ts @@ -9,11 +9,7 @@ import { SleeveTaskType } from "./SleeveTaskTypesEnum"; import { IPlayer } from "../IPlayer"; -import { - Person, - ITaskTracker, - createTaskTracker, -} from "../Person"; +import { Person, ITaskTracker, createTaskTracker } from "../Person"; import { Augmentation } from "../../Augmentation/Augmentation"; @@ -36,927 +32,1084 @@ import { FactionWorkType } from "../../Faction/FactionWorkTypeEnum"; import { CityName } from "../../Locations/data/CityNames"; import { LocationName } from "../../Locations/data/LocationNames"; -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../../utils/JSONReviver"; export class Sleeve extends Person { + /** + * Stores the name of the class that the player is currently taking + */ + className = ""; - /** - * Stores the name of the class that the player is currently taking - */ - className = ""; + /** + * Stores the type of crime the sleeve is currently attempting + * Must match the name of a Crime object + */ + crimeType = ""; - /** - * Stores the type of crime the sleeve is currently attempting - * Must match the name of a Crime object - */ - crimeType = ""; + /** + * Enum value for current task + */ + currentTask: SleeveTaskType = SleeveTaskType.Idle; - /** - * Enum value for current task - */ - currentTask: SleeveTaskType = SleeveTaskType.Idle; + /** + * Contains details about the sleeve's current task. The info stored + * in this depends on the task type + * + * Faction/Company Work: Name of Faction/Company + * Crime: Money earned if successful + * Class/Gym: Name of university/gym + */ + currentTaskLocation = ""; - /** - * Contains details about the sleeve's current task. The info stored - * in this depends on the task type - * - * Faction/Company Work: Name of Faction/Company - * Crime: Money earned if successful - * Class/Gym: Name of university/gym - */ - currentTaskLocation = ""; + /** + * Maximum amount of time (in milliseconds) that can be spent on current task. + */ + currentTaskMaxTime = 0; - /** - * Maximum amount of time (in milliseconds) that can be spent on current task. - */ - currentTaskMaxTime = 0; + /** + * Milliseconds spent on current task + */ + currentTaskTime = 0; - /** - * Milliseconds spent on current task - */ - currentTaskTime = 0; + /** + * Keeps track of experience earned for other sleeves + */ + earningsForSleeves: ITaskTracker = createTaskTracker(); - /** - * Keeps track of experience earned for other sleeves - */ - earningsForSleeves: ITaskTracker = createTaskTracker(); + /** + * Keeps track of experience + money earned for player + */ + earningsForPlayer: ITaskTracker = createTaskTracker(); - /** - * Keeps track of experience + money earned for player - */ - earningsForPlayer: ITaskTracker = createTaskTracker(); + /** + * Keeps track of experienced earned in the current task/action + */ + earningsForTask: ITaskTracker = createTaskTracker(); - /** - * Keeps track of experienced earned in the current task/action - */ - earningsForTask: ITaskTracker = createTaskTracker(); + /** + * Keeps track of what type of work sleeve is doing for faction, if applicable + */ + factionWorkType: FactionWorkType = FactionWorkType.None; - /** - * Keeps track of what type of work sleeve is doing for faction, if applicable - */ - factionWorkType: FactionWorkType = FactionWorkType.None; + /** + * Records experience gain rate for the current task + */ + gainRatesForTask: ITaskTracker = createTaskTracker(); - /** - * Records experience gain rate for the current task - */ - gainRatesForTask: ITaskTracker = createTaskTracker(); + /** + * String that stores what stat the sleeve is training at the gym + */ + gymStatType = ""; - /** - * String that stores what stat the sleeve is training at the gym - */ - gymStatType = ""; + /** + * Keeps track of events/notifications for this sleeve + */ + logs: string[] = []; - /** - * Keeps track of events/notifications for this sleeve - */ - logs: string[] = []; + /** + * Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs + */ + memory = 1; - /** - * Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs - */ - memory = 1; + /** + * Sleeve shock. Number between 0 and 100 + * Trauma/shock that comes with being in a sleeve. Experience earned + * is multipled by shock%. This gets applied before synchronization + * + * Reputation earned is also multiplied by shock% + */ + shock = 1; - /** - * Sleeve shock. Number between 0 and 100 - * Trauma/shock that comes with being in a sleeve. Experience earned - * is multipled by shock%. This gets applied before synchronization - * - * Reputation earned is also multiplied by shock% - */ - shock = 1; + /** + * Stored number of game "loop" cycles + */ + storedCycles = 0; - /** - * Stored number of game "loop" cycles - */ - storedCycles = 0; + /** + * Synchronization. Number between 0 and 100 + * When experience is earned by sleeve, both the player and the sleeve get + * sync% of the experience earned. Other sleeves get sync^2% of exp + */ + sync = 1; - /** - * Synchronization. Number between 0 and 100 - * When experience is earned by sleeve, both the player and the sleeve get - * sync% of the experience earned. Other sleeves get sync^2% of exp - */ - sync = 1; + constructor(p: IPlayer | null = null) { + super(); + if (p != null) { + this.shockRecovery(p); + } + } - constructor(p: IPlayer | null = null) { - super(); - if (p != null) { - this.shockRecovery(p); - } + /** + * Commit crimes + */ + commitCrime(p: IPlayer, crimeKey: string): boolean { + const crime: Crime | null = Crimes[crimeKey]; + if (!(crime instanceof Crime)) { + return false; } - /** - * Commit crimes - */ - commitCrime(p: IPlayer, crimeKey: string): boolean { - const crime: Crime | null = Crimes[crimeKey]; - if (!(crime instanceof Crime)) { return false; } + if (this.currentTask !== SleeveTaskType.Idle) { + this.finishTask(p); + } else { + this.resetTaskStatus(); + } - if (this.currentTask !== SleeveTaskType.Idle) { - this.finishTask(p); + this.gainRatesForTask.hack = + crime.hacking_exp * + this.hacking_exp_mult * + BitNodeMultipliers.CrimeExpGain; + this.gainRatesForTask.str = + crime.strength_exp * + this.strength_exp_mult * + BitNodeMultipliers.CrimeExpGain; + this.gainRatesForTask.def = + crime.defense_exp * + this.defense_exp_mult * + BitNodeMultipliers.CrimeExpGain; + this.gainRatesForTask.dex = + crime.dexterity_exp * + this.dexterity_exp_mult * + BitNodeMultipliers.CrimeExpGain; + this.gainRatesForTask.agi = + crime.agility_exp * + this.agility_exp_mult * + BitNodeMultipliers.CrimeExpGain; + this.gainRatesForTask.cha = + crime.charisma_exp * + this.charisma_exp_mult * + BitNodeMultipliers.CrimeExpGain; + this.gainRatesForTask.money = + crime.money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney; + + this.currentTaskLocation = String(this.gainRatesForTask.money); + + this.crimeType = crimeKey; + this.currentTaskMaxTime = crime.time; + this.currentTask = SleeveTaskType.Crime; + return true; + } + + /** + * Called to stop the current task + */ + finishTask(p: IPlayer): ITaskTracker { + let retValue: ITaskTracker = createTaskTracker(); // Amount of exp to be gained by other sleeves + + if (this.currentTask === SleeveTaskType.Crime) { + // For crimes, all experience and money is gained at the end + if (this.currentTaskTime >= this.currentTaskMaxTime) { + const crime: Crime | null = Crimes[this.crimeType]; + if (!(crime instanceof Crime)) { + console.error( + `Invalid data stored in sleeve.crimeType: ${this.crimeType}`, + ); + this.resetTaskStatus(); + return retValue; + } + if (Math.random() < crime.successRate(this)) { + // Success + const successGainRates: ITaskTracker = createTaskTracker(); + + const keysForIteration: (keyof ITaskTracker)[] = < + (keyof ITaskTracker)[] + >Object.keys(successGainRates); + for (let i = 0; i < keysForIteration.length; ++i) { + const key = keysForIteration[i]; + successGainRates[key] = this.gainRatesForTask[key] * 2; + } + retValue = this.gainExperience(p, successGainRates); + this.gainMoney(p, this.gainRatesForTask); + + p.karma -= crime.karma * (this.sync / 100); } else { - this.resetTaskStatus(); + retValue = this.gainExperience(p, this.gainRatesForTask); } - this.gainRatesForTask.hack = crime.hacking_exp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.str = crime.strength_exp * this.strength_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.def = crime.defense_exp * this.defense_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.dex = crime.dexterity_exp * this.dexterity_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.agi = crime.agility_exp * this.agility_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.cha = crime.charisma_exp * this.charisma_exp_mult * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.money = crime.money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney; - - this.currentTaskLocation = String(this.gainRatesForTask.money); - - this.crimeType = crimeKey; - this.currentTaskMaxTime = crime.time; - this.currentTask = SleeveTaskType.Crime; - return true; - } - - /** - * Called to stop the current task - */ - finishTask(p: IPlayer): ITaskTracker { - let retValue: ITaskTracker = createTaskTracker(); // Amount of exp to be gained by other sleeves - - if (this.currentTask === SleeveTaskType.Crime) { - // For crimes, all experience and money is gained at the end - if (this.currentTaskTime >= this.currentTaskMaxTime) { - const crime: Crime | null = Crimes[this.crimeType]; - if (!(crime instanceof Crime)) { - console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`); - this.resetTaskStatus(); - return retValue; - } - if (Math.random() < crime.successRate(this)) { - // Success - const successGainRates: ITaskTracker = createTaskTracker(); - - const keysForIteration: (keyof ITaskTracker)[] = (<(keyof ITaskTracker)[]>Object.keys(successGainRates)); - for (let i = 0; i < keysForIteration.length; ++i) { - const key = keysForIteration[i]; - successGainRates[key] = this.gainRatesForTask[key] * 2; - } - retValue = this.gainExperience(p, successGainRates); - this.gainMoney(p, this.gainRatesForTask); - - p.karma -= crime.karma*(this.sync / 100); - } else { - retValue = this.gainExperience(p, this.gainRatesForTask); - } - - // Do not reset task to IDLE - this.currentTaskTime = 0; - return retValue; - } - } else { - // For other crimes... I dont think anything else needs to be done - } - - this.resetTaskStatus(); - - return retValue; - } - - /** - * Earn experience for any stats (supports multiple) - * This function also handles experience propogating to Player and other sleeves - */ - gainExperience(p: IPlayer, exp: ITaskTracker, numCycles=1, fromOtherSleeve=false): ITaskTracker { - // If the experience is coming from another sleeve, it is not multiplied by anything. - // Also the player does not earn anything - if (fromOtherSleeve) { - if (exp.hack > 0) { - this.hacking_exp += exp.hack; - } - - if (exp.str > 0) { - this.strength_exp += exp.str; - } - - if (exp.def > 0) { - this.defense_exp += exp.def; - } - - if (exp.dex > 0) { - this.dexterity_exp += exp.dex; - } - - if (exp.agi > 0) { - this.agility_exp += exp.agi; - } - - if (exp.cha > 0) { - this.charisma_exp += exp.cha; - } - - return createTaskTracker(); - } - - // Experience is first multiplied by shock. Then 'synchronization' - // is accounted for - const multFac = (this.shock / 100) * (this.sync / 100) * numCycles; - const pHackExp = exp.hack * multFac; - const pStrExp = exp.str * multFac; - const pDefExp = exp.def * multFac; - const pDexExp = exp.dex * multFac; - const pAgiExp = exp.agi * multFac; - const pChaExp = exp.cha * multFac; - - // Experience is gained by both this sleeve and player - if (pHackExp > 0) { - this.hacking_exp += pHackExp; - p.gainHackingExp(pHackExp); - this.earningsForPlayer.hack += pHackExp; - this.earningsForTask.hack += pHackExp; - } - - if (pStrExp > 0) { - this.strength_exp += pStrExp; - p.gainStrengthExp(pStrExp); - this.earningsForPlayer.str += pStrExp; - this.earningsForTask.str += pStrExp; - } - - if (pDefExp > 0) { - this.defense_exp += pDefExp; - p.gainDefenseExp(pDefExp); - this.earningsForPlayer.def += pDefExp; - this.earningsForTask.def += pDefExp; - } - - if (pDexExp > 0) { - this.dexterity_exp += pDexExp; - p.gainDexterityExp(pDexExp); - this.earningsForPlayer.dex += pDexExp; - this.earningsForTask.dex += pDexExp; - } - - if (pAgiExp > 0) { - this.agility_exp += pAgiExp; - p.gainAgilityExp(pAgiExp); - this.earningsForPlayer.agi += pAgiExp; - this.earningsForTask.agi += pAgiExp; - } - - if (pChaExp > 0) { - this.charisma_exp += pChaExp; - p.gainCharismaExp(pChaExp); - this.earningsForPlayer.cha += pChaExp; - this.earningsForTask.cha += pChaExp; - } - - // Record earnings for other sleeves - this.earningsForSleeves.hack += (pHackExp * (this.sync / 100)); - this.earningsForSleeves.str += (pStrExp * (this.sync / 100)); - this.earningsForSleeves.def += (pDefExp * (this.sync / 100)); - this.earningsForSleeves.dex += (pDexExp * (this.sync / 100)); - this.earningsForSleeves.agi += (pAgiExp * (this.sync / 100)); - this.earningsForSleeves.cha += (pChaExp * (this.sync / 100)); - - // Return the experience to be gained by other sleeves - return { - hack: pHackExp * (this.sync / 100), - str: pStrExp * (this.sync / 100), - def: pDefExp * (this.sync / 100), - dex: pDexExp * (this.sync / 100), - agi: pAgiExp * (this.sync / 100), - cha: pChaExp * (this.sync / 100), - money: 0, - } - } - - /** - * Earn money for player - */ - gainMoney(p: IPlayer, task: ITaskTracker, numCycles=1): void { - const gain: number = (task.money * numCycles); - this.earningsForTask.money += gain; - this.earningsForPlayer.money += gain; - p.gainMoney(gain); - p.recordMoneySource(gain, 'sleeves'); - } - - /** - * Returns the cost of upgrading this sleeve's memory by a certain amount - */ - getMemoryUpgradeCost(n: number): number { - const amt = Math.round(n); - if (amt < 0) { - return 0; - } - - if (this.memory + amt > 100) { - return this.getMemoryUpgradeCost(100 - this.memory); - } - - const mult = 1.02; - const baseCost = 1e12; - let currCost = 0; - let currMemory = this.memory-1; - for (let i = 0; i < n; ++i) { - currCost += (Math.pow(mult, currMemory)); - ++currMemory; - } - - return currCost * baseCost; - } - - /** - * Gets reputation gain for the current task - * Only applicable when working for company or faction - */ - getRepGain(p: IPlayer): number { - if (this.currentTask === SleeveTaskType.Faction) { - let favorMult = 1; - const fac: Faction | null = Factions[this.currentTaskLocation]; - if (fac != null) { - favorMult = 1 + (fac.favor / 100); - } - - switch (this.factionWorkType) { - case FactionWorkType.Hacking: - return this.getFactionHackingWorkRepGain() * (this.shock / 100) * favorMult; - case FactionWorkType.Field: - return this.getFactionFieldWorkRepGain() * (this.shock / 100) * favorMult; - case FactionWorkType.Security: - return this.getFactionSecurityWorkRepGain() * (this.shock / 100) * favorMult; - default: - console.warn(`Invalid Sleeve.factionWorkType property in Sleeve.getRepGain(): ${this.factionWorkType}`); - return 0; - } - } else if (this.currentTask === SleeveTaskType.Company) { - const companyName: string = this.currentTaskLocation; - const company: Company | null = Companies[companyName]; - if (company == null) { - console.error(`Invalid company found when trying to calculate rep gain: ${companyName}`); - return 0; - } - - const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]]; - if (companyPosition == null) { - console.error(`Invalid company position name found when trying to calculate rep gain: ${p.jobs[companyName]}`); - return 0; - } - - const jobPerformance: number = companyPosition.calculateJobPerformance(this.hacking_skill, this.strength, - this.defense, this.dexterity, - this.agility, this.charisma); - const favorMult = 1 + (company.favor / 100); - - return jobPerformance * this.company_rep_mult * favorMult; - } else { - console.warn(`Sleeve.getRepGain() called for invalid task type: ${this.currentTask}`); - return 0; - } - } - - installAugmentation(aug: Augmentation): void { - this.hacking_exp = 0; - this.strength_exp = 0; - this.defense_exp = 0; - this.dexterity_exp = 0; - this.agility_exp = 0; - this.charisma_exp = 0; - this.applyAugmentation(aug); - this.augmentations.push({ name: aug.name, level: 1 }); - this.updateStatLevels(); - } - - log(entry: string): void { - const MaxLogSize = 50; - this.logs.push(entry); - if (this.logs.length > MaxLogSize) { - this.logs.shift(); - } - } - - /** - * Called on every sleeve for a Source File prestige - */ - prestige(p: IPlayer): void { - // Reset exp - this.hacking_exp = 0; - this.strength_exp = 0; - this.defense_exp = 0; - this.dexterity_exp = 0; - this.agility_exp = 0; - this.charisma_exp = 0; - - // Reset task-related stuff - this.resetTaskStatus(); - this.earningsForSleeves = createTaskTracker(); - this.earningsForPlayer = createTaskTracker(); - this.shockRecovery(p); - - // Reset augs and multipliers - this.augmentations = []; - this.resetMultipliers(); - - // Reset sleeve-related stats - this.shock = 1; - this.storedCycles = 0; - this.sync = Math.max(this.memory, 1); - - this.logs = []; - } - - /** - * Process loop - * Returns an object containing the amount of experience that should be - * transferred to all other sleeves - */ - process(p: IPlayer, numCycles=1): ITaskTracker | null { - // Only process once every second (5 cycles) - const CyclesPerSecond = 1000 / CONSTANTS.MilliPerCycle; - this.storedCycles += numCycles; - if (this.storedCycles < CyclesPerSecond) { return null; } - - let time = this.storedCycles * CONSTANTS.MilliPerCycle; - let cyclesUsed = this.storedCycles; - if (this.currentTaskMaxTime !== 0 && this.currentTaskTime + time > this.currentTaskMaxTime) { - time = this.currentTaskMaxTime - this.currentTaskTime; - cyclesUsed = Math.floor(time / CONSTANTS.MilliPerCycle); - - if (time < 0 || cyclesUsed < 0) { - console.warn(`Sleeve.process() calculated negative cycle usage`); - time = 0; - cyclesUsed = 0; - } - } - this.currentTaskTime += time; - - // Shock gradually goes towards 100 - this.shock = Math.min(100, this.shock + (0.0001 * this.storedCycles)); - - let retValue: ITaskTracker = createTaskTracker(); - switch (this.currentTask) { - case SleeveTaskType.Idle: - break; - case SleeveTaskType.Class: - case SleeveTaskType.Gym: - this.updateTaskGainRates(p); - retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed); - this.gainMoney(p, this.gainRatesForTask, cyclesUsed); - break; - case SleeveTaskType.Faction: { - retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed); - this.gainMoney(p, this.gainRatesForTask, cyclesUsed); - - // Gain faction reputation - const fac: Faction = Factions[this.currentTaskLocation]; - if (!(fac instanceof Faction)) { - console.error(`Invalid faction for Sleeve task: ${this.currentTaskLocation}`); - break; - } - - fac.playerReputation += (this.getRepGain(p) * cyclesUsed); - break; - } - case SleeveTaskType.Company: { - retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed); - this.gainMoney(p, this.gainRatesForTask, cyclesUsed); - - const company: Company = Companies[this.currentTaskLocation]; - if (!(company instanceof Company)) { - console.error(`Invalid company for Sleeve task: ${this.currentTaskLocation}`); - break; - } - - company.playerReputation += (this.getRepGain(p) * cyclesUsed); - break; - } - case SleeveTaskType.Recovery: - this.shock = Math.min(100, this.shock + (0.0002 * cyclesUsed)); - break; - case SleeveTaskType.Synchro: - this.sync = Math.min(100, this.sync + (p.getIntelligenceBonus(0.5) * 0.0002 * cyclesUsed)); - break; - default: - break; - } - - if (this.currentTaskMaxTime !== 0 && this.currentTaskTime >= this.currentTaskMaxTime) { - if (this.currentTask === SleeveTaskType.Crime) { - retValue = this.finishTask(p); - } else { - this.finishTask(p); - } - - } - - this.updateStatLevels(); - - this.storedCycles -= cyclesUsed; - - return retValue; - } - - /** - * Resets all parameters used to keep information about the current task - */ - resetTaskStatus(): void { - this.earningsForTask = createTaskTracker(); - this.gainRatesForTask = createTaskTracker(); - this.currentTask = SleeveTaskType.Idle; + // Do not reset task to IDLE this.currentTaskTime = 0; - this.currentTaskMaxTime = 0; - this.factionWorkType = FactionWorkType.None; - this.crimeType = ""; - this.currentTaskLocation = ""; - this.gymStatType = ""; - this.className = ""; + return retValue; + } + } else { + // For other crimes... I dont think anything else needs to be done } - shockRecovery(p: IPlayer): boolean { - if (this.currentTask !== SleeveTaskType.Idle) { - this.finishTask(p); - } else { - this.resetTaskStatus(); - } + this.resetTaskStatus(); - this.currentTask = SleeveTaskType.Recovery; - return true; + return retValue; + } + + /** + * Earn experience for any stats (supports multiple) + * This function also handles experience propogating to Player and other sleeves + */ + gainExperience( + p: IPlayer, + exp: ITaskTracker, + numCycles = 1, + fromOtherSleeve = false, + ): ITaskTracker { + // If the experience is coming from another sleeve, it is not multiplied by anything. + // Also the player does not earn anything + if (fromOtherSleeve) { + if (exp.hack > 0) { + this.hacking_exp += exp.hack; + } + + if (exp.str > 0) { + this.strength_exp += exp.str; + } + + if (exp.def > 0) { + this.defense_exp += exp.def; + } + + if (exp.dex > 0) { + this.dexterity_exp += exp.dex; + } + + if (exp.agi > 0) { + this.agility_exp += exp.agi; + } + + if (exp.cha > 0) { + this.charisma_exp += exp.cha; + } + + return createTaskTracker(); } - synchronize(p: IPlayer): boolean { - if (this.currentTask !== SleeveTaskType.Idle) { - this.finishTask(p); - } else { - this.resetTaskStatus(); - } + // Experience is first multiplied by shock. Then 'synchronization' + // is accounted for + const multFac = (this.shock / 100) * (this.sync / 100) * numCycles; + const pHackExp = exp.hack * multFac; + const pStrExp = exp.str * multFac; + const pDefExp = exp.def * multFac; + const pDexExp = exp.dex * multFac; + const pAgiExp = exp.agi * multFac; + const pChaExp = exp.cha * multFac; - this.currentTask = SleeveTaskType.Synchro; - return true; + // Experience is gained by both this sleeve and player + if (pHackExp > 0) { + this.hacking_exp += pHackExp; + p.gainHackingExp(pHackExp); + this.earningsForPlayer.hack += pHackExp; + this.earningsForTask.hack += pHackExp; } - /** - * Take a course at a university - */ - takeUniversityCourse(p: IPlayer, universityName: string, className: string): boolean { - if (this.currentTask !== SleeveTaskType.Idle) { - this.finishTask(p); - } else { - this.resetTaskStatus(); - } - - // Set exp/money multipliers based on which university. - // Also check that the sleeve is in the right city - let costMult = 1; - switch (universityName.toLowerCase()) { - case LocationName.AevumSummitUniversity.toLowerCase(): - if (this.city !== CityName.Aevum) { return false; } - this.currentTaskLocation = LocationName.AevumSummitUniversity; - costMult = 4; - break; - case LocationName.Sector12RothmanUniversity.toLowerCase(): - if (this.city !== CityName.Sector12) { return false; } - this.currentTaskLocation = LocationName.Sector12RothmanUniversity; - costMult = 3; - break; - case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase(): - if (this.city !== CityName.Volhaven) { return false; } - this.currentTaskLocation = LocationName.VolhavenZBInstituteOfTechnology; - costMult = 5; - break; - default: - return false; - } - - // Set experience/money gains based on class - switch (className.toLowerCase()) { - case "study computer science": - break; - case "data structures": - this.gainRatesForTask.money = -1 * (CONSTANTS.ClassDataStructuresBaseCost * costMult); - break; - case "networks": - this.gainRatesForTask.money = -1 * (CONSTANTS.ClassNetworksBaseCost * costMult); - break; - case "algorithms": - this.gainRatesForTask.money = -1 * (CONSTANTS.ClassAlgorithmsBaseCost * costMult); - break; - case "management": - this.gainRatesForTask.money = -1 * (CONSTANTS.ClassManagementBaseCost * costMult); - break; - case "leadership": - this.gainRatesForTask.money = -1 * (CONSTANTS.ClassLeadershipBaseCost * costMult); - break; - default: - return false; - } - - this.className = className; - this.currentTask = SleeveTaskType.Class; - return true; + if (pStrExp > 0) { + this.strength_exp += pStrExp; + p.gainStrengthExp(pStrExp); + this.earningsForPlayer.str += pStrExp; + this.earningsForTask.str += pStrExp; } - /** - * Travel to another City. Costs money from player - */ - travel(p: IPlayer, newCity: CityName): boolean { - p.loseMoney(CONSTANTS.TravelCost); - this.city = newCity; - - return true; + if (pDefExp > 0) { + this.defense_exp += pDefExp; + p.gainDefenseExp(pDefExp); + this.earningsForPlayer.def += pDefExp; + this.earningsForTask.def += pDefExp; } - tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean { - if (!p.canAfford(aug.startingCost)) { - return false; - } - - // Verify that this sleeve does not already have that augmentation. - if(this.augmentations.some(a => a.name === aug.name)) { - return false; - } - - p.loseMoney(aug.startingCost); - this.installAugmentation(aug); - return true; + if (pDexExp > 0) { + this.dexterity_exp += pDexExp; + p.gainDexterityExp(pDexExp); + this.earningsForPlayer.dex += pDexExp; + this.earningsForTask.dex += pDexExp; } - updateTaskGainRates(p: IPlayer): void { - if (this.currentTask === SleeveTaskType.Class) { - let expMult = 1; - switch (this.currentTaskLocation.toLowerCase()) { - case LocationName.AevumSummitUniversity.toLowerCase(): - expMult = 3; - break; - case LocationName.Sector12RothmanUniversity.toLowerCase(): - expMult = 2; - break; - case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase(): - expMult = 4; - break; - default: - return; - } - - const totalExpMult = expMult * p.hashManager.getStudyMult(); - switch (this.className.toLowerCase()) { - case "study computer science": - this.gainRatesForTask.hack = (CONSTANTS.ClassStudyComputerScienceBaseExp * totalExpMult * this.hacking_exp_mult); - break; - case "data structures": - this.gainRatesForTask.hack = (CONSTANTS.ClassDataStructuresBaseExp * totalExpMult * this.hacking_exp_mult); - break; - case "networks": - this.gainRatesForTask.hack = (CONSTANTS.ClassNetworksBaseExp * totalExpMult * this.hacking_exp_mult); - break; - case "algorithms": - this.gainRatesForTask.hack = (CONSTANTS.ClassAlgorithmsBaseExp * totalExpMult * this.hacking_exp_mult); - break; - case "management": - this.gainRatesForTask.cha = (CONSTANTS.ClassManagementBaseExp * totalExpMult * this.charisma_exp_mult); - break; - case "leadership": - this.gainRatesForTask.cha = (CONSTANTS.ClassLeadershipBaseExp * totalExpMult * this.charisma_exp_mult); - break; - default: - break; - } - - return; - } - - if (this.currentTask === SleeveTaskType.Gym) { - // Get gym exp multiplier - let expMult = 1; - switch (this.currentTaskLocation.toLowerCase()) { - case LocationName.AevumCrushFitnessGym.toLowerCase(): - expMult = 2; - break; - case LocationName.AevumSnapFitnessGym.toLowerCase(): - expMult = 5; - break; - case LocationName.Sector12IronGym.toLowerCase(): - expMult = 1; - break; - case LocationName.Sector12PowerhouseGym.toLowerCase(): - expMult = 10; - break; - case LocationName.VolhavenMilleniumFitnessGym: - expMult = 4; - break; - default: - return; - } - - // Set stat gain rate - const baseGymExp = 1; - const totalExpMultiplier = p.hashManager.getTrainingMult() * expMult; - const sanitizedStat: string = this.gymStatType.toLowerCase(); - if (sanitizedStat.includes("str")) { - this.gainRatesForTask.str = (baseGymExp * totalExpMultiplier * this.strength_exp_mult); - } else if (sanitizedStat.includes("def")) { - this.gainRatesForTask.def = (baseGymExp * totalExpMultiplier * this.defense_exp_mult); - } else if (sanitizedStat.includes("dex")) { - this.gainRatesForTask.dex = (baseGymExp * totalExpMultiplier * this.dexterity_exp_mult); - } else if (sanitizedStat.includes("agi")) { - this.gainRatesForTask.agi = (baseGymExp * totalExpMultiplier * this.agility_exp_mult); - } - - return; - } - - console.warn(`Sleeve.updateTaskGainRates() called for unexpected task type ${this.currentTask}`); + if (pAgiExp > 0) { + this.agility_exp += pAgiExp; + p.gainAgilityExp(pAgiExp); + this.earningsForPlayer.agi += pAgiExp; + this.earningsForTask.agi += pAgiExp; } - upgradeMemory(n: number): void { - if (n < 0) { - console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`); - return; - } - - this.memory = Math.min(100, Math.round(this.memory + n)); + if (pChaExp > 0) { + this.charisma_exp += pChaExp; + p.gainCharismaExp(pChaExp); + this.earningsForPlayer.cha += pChaExp; + this.earningsForTask.cha += pChaExp; } - /** - * Start work for one of the player's companies - * Returns boolean indicating success - */ - workForCompany(p: IPlayer, companyName: string): boolean { - if (!(Companies[companyName] instanceof Company) || p.jobs[companyName] == null) { - return false; - } + // Record earnings for other sleeves + this.earningsForSleeves.hack += pHackExp * (this.sync / 100); + this.earningsForSleeves.str += pStrExp * (this.sync / 100); + this.earningsForSleeves.def += pDefExp * (this.sync / 100); + this.earningsForSleeves.dex += pDexExp * (this.sync / 100); + this.earningsForSleeves.agi += pAgiExp * (this.sync / 100); + this.earningsForSleeves.cha += pChaExp * (this.sync / 100); - if (this.currentTask !== SleeveTaskType.Idle) { - this.finishTask(p); - } else { - this.resetTaskStatus(); - } + // Return the experience to be gained by other sleeves + return { + hack: pHackExp * (this.sync / 100), + str: pStrExp * (this.sync / 100), + def: pDefExp * (this.sync / 100), + dex: pDexExp * (this.sync / 100), + agi: pAgiExp * (this.sync / 100), + cha: pChaExp * (this.sync / 100), + money: 0, + }; + } - const company: Company | null = Companies[companyName]; - const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]]; - if (company == null) { return false; } - if (companyPosition == null) { return false; } - this.gainRatesForTask.money = companyPosition.baseSalary * - company.salaryMultiplier * - this.work_money_mult * - BitNodeMultipliers.CompanyWorkMoney; - this.gainRatesForTask.hack = companyPosition.hackingExpGain * - company.expMultiplier * - this.hacking_exp_mult * - BitNodeMultipliers.CompanyWorkExpGain; - this.gainRatesForTask.str = companyPosition.strengthExpGain * - company.expMultiplier * - this.strength_exp_mult * - BitNodeMultipliers.CompanyWorkExpGain; - this.gainRatesForTask.def = companyPosition.defenseExpGain * - company.expMultiplier * - this.defense_exp_mult * - BitNodeMultipliers.CompanyWorkExpGain; - this.gainRatesForTask.dex = companyPosition.dexterityExpGain * - company.expMultiplier * - this.dexterity_exp_mult * - BitNodeMultipliers.CompanyWorkExpGain; - this.gainRatesForTask.agi = companyPosition.agilityExpGain * - company.expMultiplier * - this.agility_exp_mult * - BitNodeMultipliers.CompanyWorkExpGain; - this.gainRatesForTask.cha = companyPosition.charismaExpGain * - company.expMultiplier * - this.charisma_exp_mult * - BitNodeMultipliers.CompanyWorkExpGain; + /** + * Earn money for player + */ + gainMoney(p: IPlayer, task: ITaskTracker, numCycles = 1): void { + const gain: number = task.money * numCycles; + this.earningsForTask.money += gain; + this.earningsForPlayer.money += gain; + p.gainMoney(gain); + p.recordMoneySource(gain, "sleeves"); + } - this.currentTaskLocation = companyName; - this.currentTask = SleeveTaskType.Company; - this.currentTaskMaxTime = CONSTANTS.MillisecondsPer8Hours; - - return true; + /** + * Returns the cost of upgrading this sleeve's memory by a certain amount + */ + getMemoryUpgradeCost(n: number): number { + const amt = Math.round(n); + if (amt < 0) { + return 0; } - /** - * Start work for one of the player's factions - * Returns boolean indicating success - */ - workForFaction(p: IPlayer, factionName: string, workType: string): boolean { - if (factionName === "") { return false; } - if (!(Factions[factionName] instanceof Faction) || !p.factions.includes(factionName)) { - return false; - } - - if (this.currentTask !== SleeveTaskType.Idle) { - this.finishTask(p); - } else { - this.resetTaskStatus(); - } - - const factionInfo = Factions[factionName].getInfo(); - - // Set type of work (hacking/field/security), and the experience gains - const sanitizedWorkType: string = workType.toLowerCase(); - if (sanitizedWorkType.includes("hack")) { - if (!factionInfo.offerHackingWork) { return false; } - this.factionWorkType = FactionWorkType.Hacking; - this.gainRatesForTask.hack = .15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - } else if (sanitizedWorkType.includes("field")) { - if (!factionInfo.offerFieldWork) { return false; } - this.factionWorkType = FactionWorkType.Field; - this.gainRatesForTask.hack = .1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.gainRatesForTask.str = .1 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.gainRatesForTask.def = .1 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.gainRatesForTask.dex = .1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.gainRatesForTask.agi = .1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.gainRatesForTask.cha = .1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - } else if (sanitizedWorkType.includes("security")) { - if (!factionInfo.offerSecurityWork) { return false; } - this.factionWorkType = FactionWorkType.Security; - this.gainRatesForTask.hack = .1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.gainRatesForTask.str = .15 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.gainRatesForTask.def = .15 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.gainRatesForTask.dex = .15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - this.gainRatesForTask.agi = .15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; - } else { - return false; - } - - this.currentTaskLocation = factionName; - this.currentTask = SleeveTaskType.Faction; - this.currentTaskMaxTime = CONSTANTS.MillisecondsPer20Hours; - - return true; + if (this.memory + amt > 100) { + return this.getMemoryUpgradeCost(100 - this.memory); } - /** - * Begin a gym workout task - */ - workoutAtGym(p: IPlayer, gymName: string, stat: string): boolean { - if (this.currentTask !== SleeveTaskType.Idle) { - this.finishTask(p); - } else { - this.resetTaskStatus(); - } - - // Set exp/money multipliers based on which university. - // Also check that the sleeve is in the right city - let costMult = 1; - switch (gymName.toLowerCase()) { - case LocationName.AevumCrushFitnessGym.toLowerCase(): - if (this.city != CityName.Aevum) { return false; } - this.currentTaskLocation = LocationName.AevumCrushFitnessGym; - costMult = 3; - break; - case LocationName.AevumSnapFitnessGym.toLowerCase(): - if (this.city != CityName.Aevum) { return false; } - this.currentTaskLocation = LocationName.AevumSnapFitnessGym; - costMult = 10; - break; - case LocationName.Sector12IronGym.toLowerCase(): - if (this.city != CityName.Sector12) { return false; } - this.currentTaskLocation = LocationName.Sector12IronGym; - costMult = 1; - break; - case LocationName.Sector12PowerhouseGym.toLowerCase(): - if (this.city != CityName.Sector12) { return false; } - this.currentTaskLocation = LocationName.Sector12PowerhouseGym; - costMult = 20; - break; - case LocationName.VolhavenMilleniumFitnessGym.toLowerCase(): - if (this.city != CityName.Volhaven) { return false; } - this.currentTaskLocation = LocationName.VolhavenMilleniumFitnessGym; - costMult = 7; - break; - default: - return false; - } - - // Set experience/money gains based on class - const sanitizedStat: string = stat.toLowerCase(); - - // Set cost - this.gainRatesForTask.money = -1 * (CONSTANTS.ClassGymBaseCost * costMult); - - // Validate "stat" argument - if (!sanitizedStat.includes("str") - && !sanitizedStat.includes("def") - && !sanitizedStat.includes("dex") - && !sanitizedStat.includes("agi")) { - - return false; - } - - this.gymStatType = stat; - this.currentTask = SleeveTaskType.Gym; - - return true; + const mult = 1.02; + const baseCost = 1e12; + let currCost = 0; + let currMemory = this.memory - 1; + for (let i = 0; i < n; ++i) { + currCost += Math.pow(mult, currMemory); + ++currMemory; } - /** - * Serialize the current object to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("Sleeve", this); + return currCost * baseCost; + } + + /** + * Gets reputation gain for the current task + * Only applicable when working for company or faction + */ + getRepGain(p: IPlayer): number { + if (this.currentTask === SleeveTaskType.Faction) { + let favorMult = 1; + const fac: Faction | null = Factions[this.currentTaskLocation]; + if (fac != null) { + favorMult = 1 + fac.favor / 100; + } + + switch (this.factionWorkType) { + case FactionWorkType.Hacking: + return ( + this.getFactionHackingWorkRepGain() * (this.shock / 100) * favorMult + ); + case FactionWorkType.Field: + return ( + this.getFactionFieldWorkRepGain() * (this.shock / 100) * favorMult + ); + case FactionWorkType.Security: + return ( + this.getFactionSecurityWorkRepGain() * + (this.shock / 100) * + favorMult + ); + default: + console.warn( + `Invalid Sleeve.factionWorkType property in Sleeve.getRepGain(): ${this.factionWorkType}`, + ); + return 0; + } + } else if (this.currentTask === SleeveTaskType.Company) { + const companyName: string = this.currentTaskLocation; + const company: Company | null = Companies[companyName]; + if (company == null) { + console.error( + `Invalid company found when trying to calculate rep gain: ${companyName}`, + ); + return 0; + } + + const companyPosition: CompanyPosition | null = + CompanyPositions[p.jobs[companyName]]; + if (companyPosition == null) { + console.error( + `Invalid company position name found when trying to calculate rep gain: ${p.jobs[companyName]}`, + ); + return 0; + } + + const jobPerformance: number = companyPosition.calculateJobPerformance( + this.hacking_skill, + this.strength, + this.defense, + this.dexterity, + this.agility, + this.charisma, + ); + const favorMult = 1 + company.favor / 100; + + return jobPerformance * this.company_rep_mult * favorMult; + } else { + console.warn( + `Sleeve.getRepGain() called for invalid task type: ${this.currentTask}`, + ); + return 0; } - - /** - * Initiatizes a Sleeve object from a JSON save state. - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Sleeve { - return Generic_fromJSON(Sleeve, value.data); + } + + installAugmentation(aug: Augmentation): void { + this.hacking_exp = 0; + this.strength_exp = 0; + this.defense_exp = 0; + this.dexterity_exp = 0; + this.agility_exp = 0; + this.charisma_exp = 0; + this.applyAugmentation(aug); + this.augmentations.push({ name: aug.name, level: 1 }); + this.updateStatLevels(); + } + + log(entry: string): void { + const MaxLogSize = 50; + this.logs.push(entry); + if (this.logs.length > MaxLogSize) { + this.logs.shift(); } + } + + /** + * Called on every sleeve for a Source File prestige + */ + prestige(p: IPlayer): void { + // Reset exp + this.hacking_exp = 0; + this.strength_exp = 0; + this.defense_exp = 0; + this.dexterity_exp = 0; + this.agility_exp = 0; + this.charisma_exp = 0; + + // Reset task-related stuff + this.resetTaskStatus(); + this.earningsForSleeves = createTaskTracker(); + this.earningsForPlayer = createTaskTracker(); + this.shockRecovery(p); + + // Reset augs and multipliers + this.augmentations = []; + this.resetMultipliers(); + + // Reset sleeve-related stats + this.shock = 1; + this.storedCycles = 0; + this.sync = Math.max(this.memory, 1); + + this.logs = []; + } + + /** + * Process loop + * Returns an object containing the amount of experience that should be + * transferred to all other sleeves + */ + process(p: IPlayer, numCycles = 1): ITaskTracker | null { + // Only process once every second (5 cycles) + const CyclesPerSecond = 1000 / CONSTANTS.MilliPerCycle; + this.storedCycles += numCycles; + if (this.storedCycles < CyclesPerSecond) { + return null; + } + + let time = this.storedCycles * CONSTANTS.MilliPerCycle; + let cyclesUsed = this.storedCycles; + if ( + this.currentTaskMaxTime !== 0 && + this.currentTaskTime + time > this.currentTaskMaxTime + ) { + time = this.currentTaskMaxTime - this.currentTaskTime; + cyclesUsed = Math.floor(time / CONSTANTS.MilliPerCycle); + + if (time < 0 || cyclesUsed < 0) { + console.warn(`Sleeve.process() calculated negative cycle usage`); + time = 0; + cyclesUsed = 0; + } + } + this.currentTaskTime += time; + + // Shock gradually goes towards 100 + this.shock = Math.min(100, this.shock + 0.0001 * this.storedCycles); + + let retValue: ITaskTracker = createTaskTracker(); + switch (this.currentTask) { + case SleeveTaskType.Idle: + break; + case SleeveTaskType.Class: + case SleeveTaskType.Gym: + this.updateTaskGainRates(p); + retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed); + this.gainMoney(p, this.gainRatesForTask, cyclesUsed); + break; + case SleeveTaskType.Faction: { + retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed); + this.gainMoney(p, this.gainRatesForTask, cyclesUsed); + + // Gain faction reputation + const fac: Faction = Factions[this.currentTaskLocation]; + if (!(fac instanceof Faction)) { + console.error( + `Invalid faction for Sleeve task: ${this.currentTaskLocation}`, + ); + break; + } + + fac.playerReputation += this.getRepGain(p) * cyclesUsed; + break; + } + case SleeveTaskType.Company: { + retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed); + this.gainMoney(p, this.gainRatesForTask, cyclesUsed); + + const company: Company = Companies[this.currentTaskLocation]; + if (!(company instanceof Company)) { + console.error( + `Invalid company for Sleeve task: ${this.currentTaskLocation}`, + ); + break; + } + + company.playerReputation += this.getRepGain(p) * cyclesUsed; + break; + } + case SleeveTaskType.Recovery: + this.shock = Math.min(100, this.shock + 0.0002 * cyclesUsed); + break; + case SleeveTaskType.Synchro: + this.sync = Math.min( + 100, + this.sync + p.getIntelligenceBonus(0.5) * 0.0002 * cyclesUsed, + ); + break; + default: + break; + } + + if ( + this.currentTaskMaxTime !== 0 && + this.currentTaskTime >= this.currentTaskMaxTime + ) { + if (this.currentTask === SleeveTaskType.Crime) { + retValue = this.finishTask(p); + } else { + this.finishTask(p); + } + } + + this.updateStatLevels(); + + this.storedCycles -= cyclesUsed; + + return retValue; + } + + /** + * Resets all parameters used to keep information about the current task + */ + resetTaskStatus(): void { + this.earningsForTask = createTaskTracker(); + this.gainRatesForTask = createTaskTracker(); + this.currentTask = SleeveTaskType.Idle; + this.currentTaskTime = 0; + this.currentTaskMaxTime = 0; + this.factionWorkType = FactionWorkType.None; + this.crimeType = ""; + this.currentTaskLocation = ""; + this.gymStatType = ""; + this.className = ""; + } + + shockRecovery(p: IPlayer): boolean { + if (this.currentTask !== SleeveTaskType.Idle) { + this.finishTask(p); + } else { + this.resetTaskStatus(); + } + + this.currentTask = SleeveTaskType.Recovery; + return true; + } + + synchronize(p: IPlayer): boolean { + if (this.currentTask !== SleeveTaskType.Idle) { + this.finishTask(p); + } else { + this.resetTaskStatus(); + } + + this.currentTask = SleeveTaskType.Synchro; + return true; + } + + /** + * Take a course at a university + */ + takeUniversityCourse( + p: IPlayer, + universityName: string, + className: string, + ): boolean { + if (this.currentTask !== SleeveTaskType.Idle) { + this.finishTask(p); + } else { + this.resetTaskStatus(); + } + + // Set exp/money multipliers based on which university. + // Also check that the sleeve is in the right city + let costMult = 1; + switch (universityName.toLowerCase()) { + case LocationName.AevumSummitUniversity.toLowerCase(): + if (this.city !== CityName.Aevum) { + return false; + } + this.currentTaskLocation = LocationName.AevumSummitUniversity; + costMult = 4; + break; + case LocationName.Sector12RothmanUniversity.toLowerCase(): + if (this.city !== CityName.Sector12) { + return false; + } + this.currentTaskLocation = LocationName.Sector12RothmanUniversity; + costMult = 3; + break; + case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase(): + if (this.city !== CityName.Volhaven) { + return false; + } + this.currentTaskLocation = LocationName.VolhavenZBInstituteOfTechnology; + costMult = 5; + break; + default: + return false; + } + + // Set experience/money gains based on class + switch (className.toLowerCase()) { + case "study computer science": + break; + case "data structures": + this.gainRatesForTask.money = + -1 * (CONSTANTS.ClassDataStructuresBaseCost * costMult); + break; + case "networks": + this.gainRatesForTask.money = + -1 * (CONSTANTS.ClassNetworksBaseCost * costMult); + break; + case "algorithms": + this.gainRatesForTask.money = + -1 * (CONSTANTS.ClassAlgorithmsBaseCost * costMult); + break; + case "management": + this.gainRatesForTask.money = + -1 * (CONSTANTS.ClassManagementBaseCost * costMult); + break; + case "leadership": + this.gainRatesForTask.money = + -1 * (CONSTANTS.ClassLeadershipBaseCost * costMult); + break; + default: + return false; + } + + this.className = className; + this.currentTask = SleeveTaskType.Class; + return true; + } + + /** + * Travel to another City. Costs money from player + */ + travel(p: IPlayer, newCity: CityName): boolean { + p.loseMoney(CONSTANTS.TravelCost); + this.city = newCity; + + return true; + } + + tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean { + if (!p.canAfford(aug.startingCost)) { + return false; + } + + // Verify that this sleeve does not already have that augmentation. + if (this.augmentations.some((a) => a.name === aug.name)) { + return false; + } + + p.loseMoney(aug.startingCost); + this.installAugmentation(aug); + return true; + } + + updateTaskGainRates(p: IPlayer): void { + if (this.currentTask === SleeveTaskType.Class) { + let expMult = 1; + switch (this.currentTaskLocation.toLowerCase()) { + case LocationName.AevumSummitUniversity.toLowerCase(): + expMult = 3; + break; + case LocationName.Sector12RothmanUniversity.toLowerCase(): + expMult = 2; + break; + case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase(): + expMult = 4; + break; + default: + return; + } + + const totalExpMult = expMult * p.hashManager.getStudyMult(); + switch (this.className.toLowerCase()) { + case "study computer science": + this.gainRatesForTask.hack = + CONSTANTS.ClassStudyComputerScienceBaseExp * + totalExpMult * + this.hacking_exp_mult; + break; + case "data structures": + this.gainRatesForTask.hack = + CONSTANTS.ClassDataStructuresBaseExp * + totalExpMult * + this.hacking_exp_mult; + break; + case "networks": + this.gainRatesForTask.hack = + CONSTANTS.ClassNetworksBaseExp * + totalExpMult * + this.hacking_exp_mult; + break; + case "algorithms": + this.gainRatesForTask.hack = + CONSTANTS.ClassAlgorithmsBaseExp * + totalExpMult * + this.hacking_exp_mult; + break; + case "management": + this.gainRatesForTask.cha = + CONSTANTS.ClassManagementBaseExp * + totalExpMult * + this.charisma_exp_mult; + break; + case "leadership": + this.gainRatesForTask.cha = + CONSTANTS.ClassLeadershipBaseExp * + totalExpMult * + this.charisma_exp_mult; + break; + default: + break; + } + + return; + } + + if (this.currentTask === SleeveTaskType.Gym) { + // Get gym exp multiplier + let expMult = 1; + switch (this.currentTaskLocation.toLowerCase()) { + case LocationName.AevumCrushFitnessGym.toLowerCase(): + expMult = 2; + break; + case LocationName.AevumSnapFitnessGym.toLowerCase(): + expMult = 5; + break; + case LocationName.Sector12IronGym.toLowerCase(): + expMult = 1; + break; + case LocationName.Sector12PowerhouseGym.toLowerCase(): + expMult = 10; + break; + case LocationName.VolhavenMilleniumFitnessGym: + expMult = 4; + break; + default: + return; + } + + // Set stat gain rate + const baseGymExp = 1; + const totalExpMultiplier = p.hashManager.getTrainingMult() * expMult; + const sanitizedStat: string = this.gymStatType.toLowerCase(); + if (sanitizedStat.includes("str")) { + this.gainRatesForTask.str = + baseGymExp * totalExpMultiplier * this.strength_exp_mult; + } else if (sanitizedStat.includes("def")) { + this.gainRatesForTask.def = + baseGymExp * totalExpMultiplier * this.defense_exp_mult; + } else if (sanitizedStat.includes("dex")) { + this.gainRatesForTask.dex = + baseGymExp * totalExpMultiplier * this.dexterity_exp_mult; + } else if (sanitizedStat.includes("agi")) { + this.gainRatesForTask.agi = + baseGymExp * totalExpMultiplier * this.agility_exp_mult; + } + + return; + } + + console.warn( + `Sleeve.updateTaskGainRates() called for unexpected task type ${this.currentTask}`, + ); + } + + upgradeMemory(n: number): void { + if (n < 0) { + console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`); + return; + } + + this.memory = Math.min(100, Math.round(this.memory + n)); + } + + /** + * Start work for one of the player's companies + * Returns boolean indicating success + */ + workForCompany(p: IPlayer, companyName: string): boolean { + if ( + !(Companies[companyName] instanceof Company) || + p.jobs[companyName] == null + ) { + return false; + } + + if (this.currentTask !== SleeveTaskType.Idle) { + this.finishTask(p); + } else { + this.resetTaskStatus(); + } + + const company: Company | null = Companies[companyName]; + const companyPosition: CompanyPosition | null = + CompanyPositions[p.jobs[companyName]]; + if (company == null) { + return false; + } + if (companyPosition == null) { + return false; + } + this.gainRatesForTask.money = + companyPosition.baseSalary * + company.salaryMultiplier * + this.work_money_mult * + BitNodeMultipliers.CompanyWorkMoney; + this.gainRatesForTask.hack = + companyPosition.hackingExpGain * + company.expMultiplier * + this.hacking_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain; + this.gainRatesForTask.str = + companyPosition.strengthExpGain * + company.expMultiplier * + this.strength_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain; + this.gainRatesForTask.def = + companyPosition.defenseExpGain * + company.expMultiplier * + this.defense_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain; + this.gainRatesForTask.dex = + companyPosition.dexterityExpGain * + company.expMultiplier * + this.dexterity_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain; + this.gainRatesForTask.agi = + companyPosition.agilityExpGain * + company.expMultiplier * + this.agility_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain; + this.gainRatesForTask.cha = + companyPosition.charismaExpGain * + company.expMultiplier * + this.charisma_exp_mult * + BitNodeMultipliers.CompanyWorkExpGain; + + this.currentTaskLocation = companyName; + this.currentTask = SleeveTaskType.Company; + this.currentTaskMaxTime = CONSTANTS.MillisecondsPer8Hours; + + return true; + } + + /** + * Start work for one of the player's factions + * Returns boolean indicating success + */ + workForFaction(p: IPlayer, factionName: string, workType: string): boolean { + if (factionName === "") { + return false; + } + if ( + !(Factions[factionName] instanceof Faction) || + !p.factions.includes(factionName) + ) { + return false; + } + + if (this.currentTask !== SleeveTaskType.Idle) { + this.finishTask(p); + } else { + this.resetTaskStatus(); + } + + const factionInfo = Factions[factionName].getInfo(); + + // Set type of work (hacking/field/security), and the experience gains + const sanitizedWorkType: string = workType.toLowerCase(); + if (sanitizedWorkType.includes("hack")) { + if (!factionInfo.offerHackingWork) { + return false; + } + this.factionWorkType = FactionWorkType.Hacking; + this.gainRatesForTask.hack = + 0.15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + } else if (sanitizedWorkType.includes("field")) { + if (!factionInfo.offerFieldWork) { + return false; + } + this.factionWorkType = FactionWorkType.Field; + this.gainRatesForTask.hack = + 0.1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.gainRatesForTask.str = + 0.1 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.gainRatesForTask.def = + 0.1 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.gainRatesForTask.dex = + 0.1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.gainRatesForTask.agi = + 0.1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.gainRatesForTask.cha = + 0.1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + } else if (sanitizedWorkType.includes("security")) { + if (!factionInfo.offerSecurityWork) { + return false; + } + this.factionWorkType = FactionWorkType.Security; + this.gainRatesForTask.hack = + 0.1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.gainRatesForTask.str = + 0.15 * this.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.gainRatesForTask.def = + 0.15 * this.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.gainRatesForTask.dex = + 0.15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + this.gainRatesForTask.agi = + 0.15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; + } else { + return false; + } + + this.currentTaskLocation = factionName; + this.currentTask = SleeveTaskType.Faction; + this.currentTaskMaxTime = CONSTANTS.MillisecondsPer20Hours; + + return true; + } + + /** + * Begin a gym workout task + */ + workoutAtGym(p: IPlayer, gymName: string, stat: string): boolean { + if (this.currentTask !== SleeveTaskType.Idle) { + this.finishTask(p); + } else { + this.resetTaskStatus(); + } + + // Set exp/money multipliers based on which university. + // Also check that the sleeve is in the right city + let costMult = 1; + switch (gymName.toLowerCase()) { + case LocationName.AevumCrushFitnessGym.toLowerCase(): + if (this.city != CityName.Aevum) { + return false; + } + this.currentTaskLocation = LocationName.AevumCrushFitnessGym; + costMult = 3; + break; + case LocationName.AevumSnapFitnessGym.toLowerCase(): + if (this.city != CityName.Aevum) { + return false; + } + this.currentTaskLocation = LocationName.AevumSnapFitnessGym; + costMult = 10; + break; + case LocationName.Sector12IronGym.toLowerCase(): + if (this.city != CityName.Sector12) { + return false; + } + this.currentTaskLocation = LocationName.Sector12IronGym; + costMult = 1; + break; + case LocationName.Sector12PowerhouseGym.toLowerCase(): + if (this.city != CityName.Sector12) { + return false; + } + this.currentTaskLocation = LocationName.Sector12PowerhouseGym; + costMult = 20; + break; + case LocationName.VolhavenMilleniumFitnessGym.toLowerCase(): + if (this.city != CityName.Volhaven) { + return false; + } + this.currentTaskLocation = LocationName.VolhavenMilleniumFitnessGym; + costMult = 7; + break; + default: + return false; + } + + // Set experience/money gains based on class + const sanitizedStat: string = stat.toLowerCase(); + + // Set cost + this.gainRatesForTask.money = -1 * (CONSTANTS.ClassGymBaseCost * costMult); + + // Validate "stat" argument + if ( + !sanitizedStat.includes("str") && + !sanitizedStat.includes("def") && + !sanitizedStat.includes("dex") && + !sanitizedStat.includes("agi") + ) { + return false; + } + + this.gymStatType = stat; + this.currentTask = SleeveTaskType.Gym; + + return true; + } + + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Sleeve", this); + } + + /** + * Initiatizes a Sleeve object from a JSON save state. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Sleeve { + return Generic_fromJSON(Sleeve, value.data); + } } Reviver.constructors.Sleeve = Sleeve; diff --git a/src/PersonObjects/Sleeve/SleeveAugmentationsUI.tsx b/src/PersonObjects/Sleeve/SleeveAugmentationsUI.tsx index 55cbdb591..2d6a0301e 100644 --- a/src/PersonObjects/Sleeve/SleeveAugmentationsUI.tsx +++ b/src/PersonObjects/Sleeve/SleeveAugmentationsUI.tsx @@ -2,7 +2,7 @@ * Module for handling the UI for purchasing Sleeve Augmentations * This UI is a popup, not a full page */ -import React from 'react'; +import React from "react"; import { Sleeve } from "./Sleeve"; import { findSleevePurchasableAugs } from "./SleeveHelpers"; @@ -20,106 +20,118 @@ import { createPopup } from "../../../utils/uiHelpers/createPopup"; import { createPopupCloseButton } from "../../../utils/uiHelpers/createPopupCloseButton"; import { removeElementById } from "../../../utils/uiHelpers/removeElementById"; -import { renderToStaticMarkup } from "react-dom/server" +import { renderToStaticMarkup } from "react-dom/server"; -export function createSleevePurchaseAugsPopup(sleeve: Sleeve, p: IPlayer): void { - // Array of all owned Augmentations. Names only - const ownedAugNames: string[] = sleeve.augmentations.map((e) => {return e.name}); +export function createSleevePurchaseAugsPopup( + sleeve: Sleeve, + p: IPlayer, +): void { + // Array of all owned Augmentations. Names only + const ownedAugNames: string[] = sleeve.augmentations.map((e) => { + return e.name; + }); - // You can only purchase Augmentations that are actually available from - // your factions. I.e. you must be in a faction that has the Augmentation - // and you must also have enough rep in that faction in order to purchase it. - const availableAugs = findSleevePurchasableAugs(sleeve, p); + // You can only purchase Augmentations that are actually available from + // your factions. I.e. you must be in a faction that has the Augmentation + // and you must also have enough rep in that faction in order to purchase it. + const availableAugs = findSleevePurchasableAugs(sleeve, p); - // Create popup - const popupId = "purchase-sleeve-augs-popup"; + // Create popup + const popupId = "purchase-sleeve-augs-popup"; - // Close popup button - const closeBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + // Close popup button + const closeBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); - // General info about owned Augmentations - const ownedAugsInfo = createElement("p", { - display: "block", - innerHTML: "Owned Augmentations:", + // General info about owned Augmentations + const ownedAugsInfo = createElement("p", { + display: "block", + innerHTML: "Owned Augmentations:", + }); + + const popupElems: HTMLElement[] = [closeBtn, ownedAugsInfo]; + + // Show owned augmentations + // First we'll make a div with a reduced width, so the tooltips don't go off + // the edge of the popup + const ownedAugsDiv = createElement("div", { width: "70%" }); + for (const ownedAug of ownedAugNames) { + const aug: Augmentation | null = Augmentations[ownedAug]; + if (aug == null) { + console.warn(`Invalid Augmentation: ${ownedAug}`); + continue; + } + + let tooltip = aug.info; + if (typeof tooltip !== "string") { + tooltip = renderToStaticMarkup(tooltip); + } + tooltip += "

    "; + tooltip += renderToStaticMarkup(aug.stats); + + ownedAugsDiv.appendChild( + createElement("div", { + class: "gang-owned-upgrade", // Reusing a class from the Gang UI + innerText: ownedAug, + tooltip: tooltip, + }), + ); + } + popupElems.push(ownedAugsDiv); + + // General info about buying Augmentations + const info = createElement("p", { + innerHTML: [ + `You can purchase Augmentations for your Duplicate Sleeves. These Augmentations`, + `have the same effect as they would for you. You can only purchase Augmentations`, + `that you have unlocked through Factions.

    `, + `When purchasing an Augmentation for a Duplicate Sleeve, they are immediately`, + `installed. This means that the Duplicate Sleeve will immediately lose all of`, + `its stat experience.`, + ].join(" "), + }); + + popupElems.push(info); + + for (const aug of availableAugs) { + const div = createElement("div", { + class: "cmpy-mgmt-upgrade-div", // We'll reuse this CSS class }); - const popupElems: HTMLElement[] = [closeBtn, ownedAugsInfo]; - - // Show owned augmentations - // First we'll make a div with a reduced width, so the tooltips don't go off - // the edge of the popup - const ownedAugsDiv = createElement("div", { width: "70%" }); - for (const ownedAug of ownedAugNames) { - const aug: Augmentation | null = Augmentations[ownedAug]; - if (aug == null) { - console.warn(`Invalid Augmentation: ${ownedAug}`); - continue; - } - - let tooltip = aug.info; - if(typeof tooltip !== 'string') { - tooltip = renderToStaticMarkup(tooltip); - } - tooltip += "

    "; - tooltip += renderToStaticMarkup(aug.stats); - - ownedAugsDiv.appendChild(createElement("div", { - class: "gang-owned-upgrade", // Reusing a class from the Gang UI - innerText: ownedAug, - tooltip: tooltip, - })) + let info = aug.info; + if (typeof info !== "string") { + info = renderToStaticMarkup(info); } - popupElems.push(ownedAugsDiv); + info += "

    "; + info += renderToStaticMarkup(aug.stats); - // General info about buying Augmentations - const info = createElement("p", { - innerHTML: - [ - `You can purchase Augmentations for your Duplicate Sleeves. These Augmentations`, - `have the same effect as they would for you. You can only purchase Augmentations`, - `that you have unlocked through Factions.

    `, - `When purchasing an Augmentation for a Duplicate Sleeve, they are immediately`, - `installed. This means that the Duplicate Sleeve will immediately lose all of`, - `its stat experience.`, + div.appendChild( + createElement("p", { + fontSize: "12px", + innerHTML: [ + `

    ${aug.name}


    `, + `Cost: ${renderToStaticMarkup( + , + )}

    `, + `${info}`, ].join(" "), - }); + padding: "2px", + clickListener: () => { + if (sleeve.tryBuyAugmentation(p, aug)) { + dialogBoxCreate( + `Installed ${aug.name} on Duplicate Sleeve!`, + false, + ); + removeElementById(popupId); + createSleevePurchaseAugsPopup(sleeve, p); + } else { + dialogBoxCreate(`You cannot afford ${aug.name}`, false); + } + }, + }), + ); - popupElems.push(info); + popupElems.push(div); + } - for (const aug of availableAugs) { - const div = createElement("div", { - class: "cmpy-mgmt-upgrade-div", // We'll reuse this CSS class - }); - - let info = aug.info; - if(typeof info !== 'string') { - info = renderToStaticMarkup(info); - } - info += "

    "; - info += renderToStaticMarkup(aug.stats); - - div.appendChild(createElement("p", { - fontSize: "12px", - innerHTML: - [ - `

    ${aug.name}


    `, - `Cost: ${renderToStaticMarkup()}

    `, - `${info}`, - ].join(" "), - padding: "2px", - clickListener: () => { - if (sleeve.tryBuyAugmentation(p, aug)) { - dialogBoxCreate(`Installed ${aug.name} on Duplicate Sleeve!`, false); - removeElementById(popupId); - createSleevePurchaseAugsPopup(sleeve, p); - } else { - dialogBoxCreate(`You cannot afford ${aug.name}`, false); - } - }, - })); - - popupElems.push(div); - } - - createPopup(popupId, popupElems); + createPopup(popupId, popupElems); } diff --git a/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts b/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts index 662c1ee63..63fb4e6cd 100644 --- a/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts +++ b/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts @@ -5,13 +5,15 @@ import { IPlayer } from "../IPlayer"; import { CovenantPurchasesRoot } from "./ui/CovenantPurchasesRoot"; -import { createPopup, - removePopup } from "../../ui/React/createPopup"; +import { createPopup, removePopup } from "../../ui/React/createPopup"; export const MaxSleevesFromCovenant = 5; export const BaseCostPerSleeve = 10e12; export const PopupId = "covenant-sleeve-purchases-popup"; export function createSleevePurchasesFromCovenantPopup(p: IPlayer): void { - createPopup(PopupId, CovenantPurchasesRoot, { p: p, closeFn: () => removePopup(PopupId) }); + createPopup(PopupId, CovenantPurchasesRoot, { + p: p, + closeFn: () => removePopup(PopupId), + }); } diff --git a/src/PersonObjects/Sleeve/SleeveHelpers.ts b/src/PersonObjects/Sleeve/SleeveHelpers.ts index b77e44f7f..515b54829 100644 --- a/src/PersonObjects/Sleeve/SleeveHelpers.ts +++ b/src/PersonObjects/Sleeve/SleeveHelpers.ts @@ -8,57 +8,80 @@ import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; import { Faction } from "../../Faction/Faction"; import { Factions } from "../../Faction/Factions"; -export function findSleevePurchasableAugs(sleeve: Sleeve, p: IPlayer): Augmentation[] { - // You can only purchase Augmentations that are actually available from - // your factions. I.e. you must be in a faction that has the Augmentation - // and you must also have enough rep in that faction in order to purchase it. +export function findSleevePurchasableAugs( + sleeve: Sleeve, + p: IPlayer, +): Augmentation[] { + // You can only purchase Augmentations that are actually available from + // your factions. I.e. you must be in a faction that has the Augmentation + // and you must also have enough rep in that faction in order to purchase it. - const ownedAugNames: string[] = sleeve.augmentations.map((e) => {return e.name}); - const availableAugs: Augmentation[] = []; + const ownedAugNames: string[] = sleeve.augmentations.map((e) => { + return e.name; + }); + const availableAugs: Augmentation[] = []; - // Helper function that helps filter out augs that are already owned - // and augs that aren't allowed for sleeves - function isAvailableForSleeve(aug: Augmentation): boolean { - if (aug.name === AugmentationNames.NeuroFluxGovernor) { return false; } - if (ownedAugNames.includes(aug.name)) { return false; } - if (availableAugs.includes(aug)) { return false; } - if (aug.isSpecial) { return false; } - - return true; + // Helper function that helps filter out augs that are already owned + // and augs that aren't allowed for sleeves + function isAvailableForSleeve(aug: Augmentation): boolean { + if (aug.name === AugmentationNames.NeuroFluxGovernor) { + return false; + } + if (ownedAugNames.includes(aug.name)) { + return false; + } + if (availableAugs.includes(aug)) { + return false; + } + if (aug.isSpecial) { + return false; } - // If player is in a gang, then we return all augs that the player - // has enough reputation for (since that gang offers all augs) - if (p.inGang()) { - const fac = p.getGangFaction(); + return true; + } - for (const augName in Augmentations) { - const aug = Augmentations[augName]; - if (!isAvailableForSleeve(aug)) { continue; } + // If player is in a gang, then we return all augs that the player + // has enough reputation for (since that gang offers all augs) + if (p.inGang()) { + const fac = p.getGangFaction(); - if (fac.playerReputation > aug.baseRepRequirement) { - availableAugs.push(aug); - } - } + for (const augName in Augmentations) { + const aug = Augmentations[augName]; + if (!isAvailableForSleeve(aug)) { + continue; + } - return availableAugs; - } - - for (const facName of p.factions) { - if (facName === "Bladeburners") { continue; } - if (facName === "Netburners") { continue; } - const fac: Faction | null = Factions[facName]; - if (fac == null) { continue; } - - for (const augName of fac.augmentations) { - const aug: Augmentation = Augmentations[augName]; - if (!isAvailableForSleeve(aug)) { continue; } - - if (fac.playerReputation > aug.baseRepRequirement) { - availableAugs.push(aug); - } - } + if (fac.playerReputation > aug.baseRepRequirement) { + availableAugs.push(aug); + } } return availableAugs; + } + + for (const facName of p.factions) { + if (facName === "Bladeburners") { + continue; + } + if (facName === "Netburners") { + continue; + } + const fac: Faction | null = Factions[facName]; + if (fac == null) { + continue; + } + + for (const augName of fac.augmentations) { + const aug: Augmentation = Augmentations[augName]; + if (!isAvailableForSleeve(aug)) { + continue; + } + + if (fac.playerReputation > aug.baseRepRequirement) { + availableAugs.push(aug); + } + } + } + + return availableAugs; } diff --git a/src/PersonObjects/Sleeve/SleeveTaskTypesEnum.ts b/src/PersonObjects/Sleeve/SleeveTaskTypesEnum.ts index ad83b4368..61f216d03 100644 --- a/src/PersonObjects/Sleeve/SleeveTaskTypesEnum.ts +++ b/src/PersonObjects/Sleeve/SleeveTaskTypesEnum.ts @@ -2,13 +2,13 @@ * Enum for different types of tasks that a Sleeve can perform */ export enum SleeveTaskType { - // Same Order as selectable order in UI - Idle, - Company, - Faction, - Crime, - Class, - Gym, - Recovery, - Synchro, + // Same Order as selectable order in UI + Idle, + Company, + Faction, + Crime, + Class, + Gym, + Recovery, + Synchro, } diff --git a/src/PersonObjects/Sleeve/SleeveUI.tsx b/src/PersonObjects/Sleeve/SleeveUI.tsx index 5b38fd4f1..5e11c0896 100644 --- a/src/PersonObjects/Sleeve/SleeveUI.tsx +++ b/src/PersonObjects/Sleeve/SleeveUI.tsx @@ -1,7 +1,7 @@ /** * Module for handling the Sleeve UI */ -import React from 'react'; +import React from "react"; import { createSleevePurchaseAugsPopup } from "./SleeveAugmentationsUI"; import { Sleeve } from "./Sleeve"; import { SleeveTaskType } from "./SleeveTaskTypesEnum"; @@ -22,8 +22,7 @@ import { CityName } from "../../Locations/data/CityNames"; import { LocationName } from "../../Locations/data/LocationNames"; import { numeralWrapper } from "../../ui/numeralFormat"; -import { Page, - routing } from "../../ui/navigationTracking"; +import { Page, routing } from "../../ui/navigationTracking"; import { dialogBoxCreate } from "../../../utils/DialogBox"; @@ -48,691 +47,859 @@ import { StatsElement } from "./ui/StatsElement"; import { MoreStatsContent } from "./ui/MoreStatsContent"; import { MoreEarningsContent } from "./ui/MoreEarningsContent"; import * as ReactDOM from "react-dom"; -import { renderToStaticMarkup } from "react-dom/server" +import { renderToStaticMarkup } from "react-dom/server"; // Object that keeps track of all DOM elements for the UI for a single Sleeve interface ISleeveUIElems { - container: HTMLElement | null; - statsPanel: HTMLElement | null; - stats: HTMLElement | null; - moreStatsButton: HTMLElement | null; - travelButton: HTMLElement | null; - purchaseAugsButton: HTMLElement | null; - taskPanel: HTMLElement | null; - taskSelector: HTMLSelectElement | null; - taskDetailsSelector: HTMLSelectElement | null; - taskDetailsSelector2: HTMLSelectElement | null; - taskDescription: HTMLElement | null; - taskSetButton: HTMLElement | null; - taskProgressBar: HTMLElement | null; - earningsPanel: HTMLElement | null; - currentEarningsInfo: HTMLElement | null; - totalEarningsButton: HTMLElement | null; + container: HTMLElement | null; + statsPanel: HTMLElement | null; + stats: HTMLElement | null; + moreStatsButton: HTMLElement | null; + travelButton: HTMLElement | null; + purchaseAugsButton: HTMLElement | null; + taskPanel: HTMLElement | null; + taskSelector: HTMLSelectElement | null; + taskDetailsSelector: HTMLSelectElement | null; + taskDetailsSelector2: HTMLSelectElement | null; + taskDescription: HTMLElement | null; + taskSetButton: HTMLElement | null; + taskProgressBar: HTMLElement | null; + earningsPanel: HTMLElement | null; + currentEarningsInfo: HTMLElement | null; + totalEarningsButton: HTMLElement | null; } // Object that keeps track of all DOM elements for the entire Sleeve UI interface IPageUIElems { - container: HTMLElement | null; - docButton: HTMLElement | null; - faqButton: HTMLElement | null; - info: HTMLElement | null; - sleeveList: HTMLElement | null; - sleeves: ISleeveUIElems[] | null; + container: HTMLElement | null; + docButton: HTMLElement | null; + faqButton: HTMLElement | null; + info: HTMLElement | null; + sleeveList: HTMLElement | null; + sleeves: ISleeveUIElems[] | null; } const UIElems: IPageUIElems = { - container: null, - docButton: null, - faqButton: null, - info: null, - sleeveList: null, - sleeves: null, -} + container: null, + docButton: null, + faqButton: null, + info: null, + sleeveList: null, + sleeves: null, +}; // Creates the UI for the entire Sleeves page let playerRef: IPlayer | null; export function createSleevesPage(p: IPlayer): void { - if (!routing.isOn(Page.Sleeves)) { return; } + if (!routing.isOn(Page.Sleeves)) { + return; + } - try { - playerRef = p; + try { + playerRef = p; - UIElems.container = createElement("div", { - class: "generic-menupage-container", - id: "sleeves-container", - position: "fixed", - }); + UIElems.container = createElement("div", { + class: "generic-menupage-container", + id: "sleeves-container", + position: "fixed", + }); - UIElems.info = createElement("p", { - class: "sleeves-page-info", - innerHTML: "

    Sleeves

    Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your " + - "consciousness has been copied. In other words, these Synthoids contain " + - "a perfect duplicate of your mind.

    " + - "Sleeves can be used to perform different tasks synchronously.

    ", - }); + UIElems.info = createElement("p", { + class: "sleeves-page-info", + innerHTML: + "

    Sleeves

    Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your " + + "consciousness has been copied. In other words, these Synthoids contain " + + "a perfect duplicate of your mind.

    " + + "Sleeves can be used to perform different tasks synchronously.

    ", + }); - UIElems.faqButton = createElement("button", { - class: "std-button", - display: "inline-block", - innerText: "FAQ", - clickListener: () => { - dialogBoxCreate(SleeveFaq, false); - }, - }); + UIElems.faqButton = createElement("button", { + class: "std-button", + display: "inline-block", + innerText: "FAQ", + clickListener: () => { + dialogBoxCreate(SleeveFaq, false); + }, + }); - UIElems.docButton = createElement("a", { - class: "std-button", - display: "inline-block", - href: "https://bitburner.readthedocs.io/en/latest/advancedgameplay/sleeves.html#duplicate-sleeves", - innerText: "Documentation", - target: "_blank", - }); + UIElems.docButton = createElement("a", { + class: "std-button", + display: "inline-block", + href: "https://bitburner.readthedocs.io/en/latest/advancedgameplay/sleeves.html#duplicate-sleeves", + innerText: "Documentation", + target: "_blank", + }); - UIElems.sleeveList = createElement("ul"); - UIElems.sleeves = []; + UIElems.sleeveList = createElement("ul"); + UIElems.sleeves = []; - // Create UI modules for all Sleeve - for (const sleeve of p.sleeves) { - const sleeveUi = createSleeveUi(sleeve, p.sleeves); - if(sleeveUi.container == null) throw new Error("sleeveUi.container is null in createSleevesPage()"); - UIElems.sleeveList.appendChild(sleeveUi.container); - UIElems.sleeves.push(sleeveUi); - } - - UIElems.container.appendChild(UIElems.info); - UIElems.container.appendChild(UIElems.faqButton); - UIElems.container.appendChild(UIElems.docButton); - UIElems.container.appendChild(UIElems.sleeveList); - - - const container = document.getElementById("entire-game-container"); - if(container === null) throw new Error("entire-game-container not found in createSleevesPage()"); - container.appendChild(UIElems.container); - } catch(e) { - exceptionAlert(e); + // Create UI modules for all Sleeve + for (const sleeve of p.sleeves) { + const sleeveUi = createSleeveUi(sleeve, p.sleeves); + if (sleeveUi.container == null) + throw new Error("sleeveUi.container is null in createSleevesPage()"); + UIElems.sleeveList.appendChild(sleeveUi.container); + UIElems.sleeves.push(sleeveUi); } + + UIElems.container.appendChild(UIElems.info); + UIElems.container.appendChild(UIElems.faqButton); + UIElems.container.appendChild(UIElems.docButton); + UIElems.container.appendChild(UIElems.sleeveList); + + const container = document.getElementById("entire-game-container"); + if (container === null) + throw new Error("entire-game-container not found in createSleevesPage()"); + container.appendChild(UIElems.container); + } catch (e) { + exceptionAlert(e); + } } // Updates the UI for the entire Sleeves page export function updateSleevesPage(): void { - if (!routing.isOn(Page.Sleeves)) { return; } - if (playerRef === null) throw new Error("playerRef is null in updateSleevesPage()"); - if (UIElems.sleeves === null) throw new Error("UIElems.sleeves is null in updateSleevesPage()"); + if (!routing.isOn(Page.Sleeves)) { + return; + } + if (playerRef === null) + throw new Error("playerRef is null in updateSleevesPage()"); + if (UIElems.sleeves === null) + throw new Error("UIElems.sleeves is null in updateSleevesPage()"); - try { - for (let i = 0; i < playerRef.sleeves.length; ++i) { - const sleeve: Sleeve = playerRef.sleeves[i]; - const elems: ISleeveUIElems = UIElems.sleeves[i]; - updateSleeveUi(sleeve, elems); - } - } catch(e) { - exceptionAlert(e); + try { + for (let i = 0; i < playerRef.sleeves.length; ++i) { + const sleeve: Sleeve = playerRef.sleeves[i]; + const elems: ISleeveUIElems = UIElems.sleeves[i]; + updateSleeveUi(sleeve, elems); } + } catch (e) { + exceptionAlert(e); + } } export function clearSleevesPage(): void { - if (UIElems.container instanceof HTMLElement) { - removeElement(UIElems.container); - } + if (UIElems.container instanceof HTMLElement) { + removeElement(UIElems.container); + } - for (const prop in UIElems) { - (UIElems as any)[prop] = null; - } + for (const prop in UIElems) { + (UIElems as any)[prop] = null; + } - playerRef = null; + playerRef = null; } // Creates the UI for a single Sleeve // Returns an object containing the DOM elements in the UI (ISleeveUIElems) function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems { - const elems: ISleeveUIElems = { - container: null, - statsPanel: null, - stats: null, - moreStatsButton: null, - travelButton: null, - purchaseAugsButton: null, - taskPanel: null, - taskSelector: null, - taskDetailsSelector: null, - taskDetailsSelector2: null, - taskDescription: null, - taskSetButton: null, - taskProgressBar: null, - earningsPanel: null, - currentEarningsInfo: null, - totalEarningsButton: null, - } - if(playerRef === null) return elems; - - if (!routing.isOn(Page.Sleeves)) { return elems; } - - elems.container = createElement("div", { - class: "sleeve-container", - display: "block", - }); - - elems.statsPanel = createElement("div", { class: "sleeve-panel", width: "25%" }); - elems.stats = createElement("div", { class: "sleeve-stats-text" }); - elems.moreStatsButton = createElement("button", { - class: "std-button", - innerText: "More Stats", - clickListener: () => { - dialogBoxCreate(MoreStatsContent(sleeve)); - }, - }); - elems.travelButton = createElement("button", { - class: "std-button", - innerText: "Travel", - clickListener: () => { - if(playerRef === null) return; - const popupId = "sleeve-travel-popup"; - const popupArguments: HTMLElement[] = []; - popupArguments.push(createPopupCloseButton(popupId, { class: "std-button" })); - popupArguments.push(createElement("p", { - innerHTML: "Have this sleeve travel to a different city. This affects " + - "the gyms and universities at which this sleeve can study. " + - `Traveling to a different city costs ${renderToStaticMarkup()}. ` + - "It will also CANCEL the sleeve's current task (setting it to idle)", - })); - for (const cityName in Cities) { - if (sleeve.city === cityName) { continue; } - (function(sleeve, cityName) { - popupArguments.push(createElement("div", { - // Reusing this css class. It adds a border and makes it so that - // the background color changes when you hover - class: "cmpy-mgmt-find-employee-option", - innerText: cityName, - clickListener: () => { - if(playerRef == null) throw new Error("playerRef is null in popupArguments.click()"); - if (!playerRef.canAfford(CONSTANTS.TravelCost)) { - dialogBoxCreate("You cannot afford to have this sleeve travel to another city", false); - return false; - } - sleeve.city = cityName as CityName; - playerRef.loseMoney(CONSTANTS.TravelCost); - sleeve.resetTaskStatus(); - removeElementById(popupId); - updateSleeveUi(sleeve, elems); - updateSleeveTaskSelector(sleeve, elems, allSleeves); - return false; - }, - })); - })(sleeve, cityName); - } - - createPopup(popupId, popupArguments); - }, - }); - elems.purchaseAugsButton = createElement("button", { - class: "std-button", - display: "block", - innerText: "Manage Augmentations", - clickListener: () => { - if(playerRef == null) throw new Error("playerRef is null in purchaseAugsButton.click()"); - createSleevePurchaseAugsPopup(sleeve, playerRef); - }, - }); - elems.statsPanel.appendChild(elems.stats); - elems.statsPanel.appendChild(elems.moreStatsButton); - elems.statsPanel.appendChild(elems.travelButton); - if (sleeve.shock >= 100) { - // You can only buy augs when shock recovery is 0 - elems.statsPanel.appendChild(elems.purchaseAugsButton); - } - - elems.taskPanel = createElement("div", { class: "sleeve-panel", width: "40%" }); - elems.taskSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement; - elems.taskSelector.add(createOptionElement("------")); - elems.taskSelector.add(createOptionElement("Work for Company")); - elems.taskSelector.add(createOptionElement("Work for Faction")); - elems.taskSelector.add(createOptionElement("Commit Crime")); - elems.taskSelector.add(createOptionElement("Take University Course")); - elems.taskSelector.add(createOptionElement("Workout at Gym")); - elems.taskSelector.add(createOptionElement("Shock Recovery")); - elems.taskSelector.add(createOptionElement("Synchronize")); - elems.taskDetailsSelector = createElement("select", { class: "dropdown" }) as HTMLSelectElement; - elems.taskDetailsSelector2 = createElement("select", { class: "dropdown" }) as HTMLSelectElement; - elems.taskDescription = createElement("p"); - elems.taskProgressBar = createElement("p"); - elems.taskSelector.addEventListener("change", () => { - updateSleeveTaskSelector(sleeve, elems, allSleeves); - }); - elems.taskSelector.selectedIndex = sleeve.currentTask; // Set initial value for Task Selector - elems.taskSelector.dispatchEvent(new Event('change')); - updateSleeveTaskDescription(sleeve, elems); - elems.taskSetButton = createElement("button", { - class: "std-button", - innerText: "Set Task", - clickListener: () => { - setSleeveTask(sleeve, elems); - }, - }); - elems.taskPanel.appendChild(elems.taskSelector); - elems.taskPanel.appendChild(elems.taskDetailsSelector); - elems.taskPanel.appendChild(elems.taskDetailsSelector2); - elems.taskPanel.appendChild(elems.taskSetButton); - elems.taskPanel.appendChild(elems.taskDescription); - elems.taskPanel.appendChild(elems.taskProgressBar); - - elems.earningsPanel = createElement("div", { class: "sleeve-panel", width: "35%" }); - elems.currentEarningsInfo = createElement("div"); - elems.totalEarningsButton = createElement("button", { - class: "std-button", - innerText: "More Earnings Info", - clickListener: () => { - dialogBoxCreate(MoreEarningsContent(sleeve)); - }, - }); - - elems.earningsPanel.appendChild(elems.currentEarningsInfo); - elems.earningsPanel.appendChild(elems.totalEarningsButton); - - updateSleeveUi(sleeve, elems); - - elems.container.appendChild(elems.statsPanel); - elems.container.appendChild(elems.taskPanel); - elems.container.appendChild(elems.earningsPanel); + const elems: ISleeveUIElems = { + container: null, + statsPanel: null, + stats: null, + moreStatsButton: null, + travelButton: null, + purchaseAugsButton: null, + taskPanel: null, + taskSelector: null, + taskDetailsSelector: null, + taskDetailsSelector2: null, + taskDescription: null, + taskSetButton: null, + taskProgressBar: null, + earningsPanel: null, + currentEarningsInfo: null, + totalEarningsButton: null, + }; + if (playerRef === null) return elems; + if (!routing.isOn(Page.Sleeves)) { return elems; + } + + elems.container = createElement("div", { + class: "sleeve-container", + display: "block", + }); + + elems.statsPanel = createElement("div", { + class: "sleeve-panel", + width: "25%", + }); + elems.stats = createElement("div", { class: "sleeve-stats-text" }); + elems.moreStatsButton = createElement("button", { + class: "std-button", + innerText: "More Stats", + clickListener: () => { + dialogBoxCreate(MoreStatsContent(sleeve)); + }, + }); + elems.travelButton = createElement("button", { + class: "std-button", + innerText: "Travel", + clickListener: () => { + if (playerRef === null) return; + const popupId = "sleeve-travel-popup"; + const popupArguments: HTMLElement[] = []; + popupArguments.push( + createPopupCloseButton(popupId, { class: "std-button" }), + ); + popupArguments.push( + createElement("p", { + innerHTML: + "Have this sleeve travel to a different city. This affects " + + "the gyms and universities at which this sleeve can study. " + + `Traveling to a different city costs ${renderToStaticMarkup( + , + )}. ` + + "It will also CANCEL the sleeve's current task (setting it to idle)", + }), + ); + for (const cityName in Cities) { + if (sleeve.city === cityName) { + continue; + } + (function (sleeve, cityName) { + popupArguments.push( + createElement("div", { + // Reusing this css class. It adds a border and makes it so that + // the background color changes when you hover + class: "cmpy-mgmt-find-employee-option", + innerText: cityName, + clickListener: () => { + if (playerRef == null) + throw new Error( + "playerRef is null in popupArguments.click()", + ); + if (!playerRef.canAfford(CONSTANTS.TravelCost)) { + dialogBoxCreate( + "You cannot afford to have this sleeve travel to another city", + false, + ); + return false; + } + sleeve.city = cityName as CityName; + playerRef.loseMoney(CONSTANTS.TravelCost); + sleeve.resetTaskStatus(); + removeElementById(popupId); + updateSleeveUi(sleeve, elems); + updateSleeveTaskSelector(sleeve, elems, allSleeves); + return false; + }, + }), + ); + })(sleeve, cityName); + } + + createPopup(popupId, popupArguments); + }, + }); + elems.purchaseAugsButton = createElement("button", { + class: "std-button", + display: "block", + innerText: "Manage Augmentations", + clickListener: () => { + if (playerRef == null) + throw new Error("playerRef is null in purchaseAugsButton.click()"); + createSleevePurchaseAugsPopup(sleeve, playerRef); + }, + }); + elems.statsPanel.appendChild(elems.stats); + elems.statsPanel.appendChild(elems.moreStatsButton); + elems.statsPanel.appendChild(elems.travelButton); + if (sleeve.shock >= 100) { + // You can only buy augs when shock recovery is 0 + elems.statsPanel.appendChild(elems.purchaseAugsButton); + } + + elems.taskPanel = createElement("div", { + class: "sleeve-panel", + width: "40%", + }); + elems.taskSelector = createElement("select", { + class: "dropdown", + }) as HTMLSelectElement; + elems.taskSelector.add(createOptionElement("------")); + elems.taskSelector.add(createOptionElement("Work for Company")); + elems.taskSelector.add(createOptionElement("Work for Faction")); + elems.taskSelector.add(createOptionElement("Commit Crime")); + elems.taskSelector.add(createOptionElement("Take University Course")); + elems.taskSelector.add(createOptionElement("Workout at Gym")); + elems.taskSelector.add(createOptionElement("Shock Recovery")); + elems.taskSelector.add(createOptionElement("Synchronize")); + elems.taskDetailsSelector = createElement("select", { + class: "dropdown", + }) as HTMLSelectElement; + elems.taskDetailsSelector2 = createElement("select", { + class: "dropdown", + }) as HTMLSelectElement; + elems.taskDescription = createElement("p"); + elems.taskProgressBar = createElement("p"); + elems.taskSelector.addEventListener("change", () => { + updateSleeveTaskSelector(sleeve, elems, allSleeves); + }); + elems.taskSelector.selectedIndex = sleeve.currentTask; // Set initial value for Task Selector + elems.taskSelector.dispatchEvent(new Event("change")); + updateSleeveTaskDescription(sleeve, elems); + elems.taskSetButton = createElement("button", { + class: "std-button", + innerText: "Set Task", + clickListener: () => { + setSleeveTask(sleeve, elems); + }, + }); + elems.taskPanel.appendChild(elems.taskSelector); + elems.taskPanel.appendChild(elems.taskDetailsSelector); + elems.taskPanel.appendChild(elems.taskDetailsSelector2); + elems.taskPanel.appendChild(elems.taskSetButton); + elems.taskPanel.appendChild(elems.taskDescription); + elems.taskPanel.appendChild(elems.taskProgressBar); + + elems.earningsPanel = createElement("div", { + class: "sleeve-panel", + width: "35%", + }); + elems.currentEarningsInfo = createElement("div"); + elems.totalEarningsButton = createElement("button", { + class: "std-button", + innerText: "More Earnings Info", + clickListener: () => { + dialogBoxCreate(MoreEarningsContent(sleeve)); + }, + }); + + elems.earningsPanel.appendChild(elems.currentEarningsInfo); + elems.earningsPanel.appendChild(elems.totalEarningsButton); + + updateSleeveUi(sleeve, elems); + + elems.container.appendChild(elems.statsPanel); + elems.container.appendChild(elems.taskPanel); + elems.container.appendChild(elems.earningsPanel); + + return elems; } // Updates the UI for a single Sleeve function updateSleeveUi(sleeve: Sleeve, elems: ISleeveUIElems): void { - if (!routing.isOn(Page.Sleeves)) { return; } - if(playerRef == null) throw new Error("playerRef is null in updateSleeveUi()"); - if(elems.taskProgressBar == null) throw new Error("elems.taskProgressBar is null"); - if(elems.stats == null) throw new Error("elems.stats is null"); - if(elems.currentEarningsInfo == null) throw new Error("elems.currentEarningsInfo is null"); + if (!routing.isOn(Page.Sleeves)) { + return; + } + if (playerRef == null) + throw new Error("playerRef is null in updateSleeveUi()"); + if (elems.taskProgressBar == null) + throw new Error("elems.taskProgressBar is null"); + if (elems.stats == null) throw new Error("elems.stats is null"); + if (elems.currentEarningsInfo == null) + throw new Error("elems.currentEarningsInfo is null"); - ReactDOM.render(StatsElement(sleeve), elems.stats); + ReactDOM.render(StatsElement(sleeve), elems.stats); - if (sleeve.currentTask === SleeveTaskType.Crime) { - const data = [ - [`Money`, , `(on success)`], - [`Hacking Exp`, numeralWrapper.formatExp(sleeve.gainRatesForTask.hack), `(2x on success)`], - [`Strength Exp`, numeralWrapper.formatExp(sleeve.gainRatesForTask.str), `(2x on success)`], - [`Defense Exp`, numeralWrapper.formatExp(sleeve.gainRatesForTask.def), `(2x on success)`], - [`Dexterity Exp`, numeralWrapper.formatExp(sleeve.gainRatesForTask.dex), `(2x on success)`], - [`Agility Exp`, numeralWrapper.formatExp(sleeve.gainRatesForTask.agi), `(2x on success)`], - [`Charisma Exp`, numeralWrapper.formatExp(sleeve.gainRatesForTask.cha), `(2x on success)`], - ]; - ReactDOM.render(EarningsTableElement('Earnings (Pre-Synchronization)', data), elems.currentEarningsInfo) + if (sleeve.currentTask === SleeveTaskType.Crime) { + const data = [ + [ + `Money`, + , + `(on success)`, + ], + [ + `Hacking Exp`, + numeralWrapper.formatExp(sleeve.gainRatesForTask.hack), + `(2x on success)`, + ], + [ + `Strength Exp`, + numeralWrapper.formatExp(sleeve.gainRatesForTask.str), + `(2x on success)`, + ], + [ + `Defense Exp`, + numeralWrapper.formatExp(sleeve.gainRatesForTask.def), + `(2x on success)`, + ], + [ + `Dexterity Exp`, + numeralWrapper.formatExp(sleeve.gainRatesForTask.dex), + `(2x on success)`, + ], + [ + `Agility Exp`, + numeralWrapper.formatExp(sleeve.gainRatesForTask.agi), + `(2x on success)`, + ], + [ + `Charisma Exp`, + numeralWrapper.formatExp(sleeve.gainRatesForTask.cha), + `(2x on success)`, + ], + ]; + ReactDOM.render( + EarningsTableElement("Earnings (Pre-Synchronization)", data), + elems.currentEarningsInfo, + ); - elems.taskProgressBar.innerText = createProgressBarText({ - progress: sleeve.currentTaskTime / sleeve.currentTaskMaxTime, - totalTicks: 25, - }); - } else { - const data = [ - [`Money:`, MoneyRate(5 * sleeve.gainRatesForTask.money)], - [`Hacking Exp:`, `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.hack)} / s`], - [`Strength Exp:`, `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.str)} / s`], - [`Defense Exp:`, `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.def)} / s`], - [`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.dex)} / s`], - [`Agility Exp:`, `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.agi)} / s`], - [`Charisma Exp:`, `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.cha)} / s`], - ]; - if (sleeve.currentTask === SleeveTaskType.Company || sleeve.currentTask === SleeveTaskType.Faction) { - const repGain: number = sleeve.getRepGain(playerRef); - data.push([`Reputation:`, ReputationRate(5 * repGain)]); - } - ReactDOM.render(EarningsTableElement('Earnings (Pre-Synchronization)', data), elems.currentEarningsInfo) - - elems.taskProgressBar.innerText = ""; + elems.taskProgressBar.innerText = createProgressBarText({ + progress: sleeve.currentTaskTime / sleeve.currentTaskMaxTime, + totalTicks: 25, + }); + } else { + const data = [ + [`Money:`, MoneyRate(5 * sleeve.gainRatesForTask.money)], + [ + `Hacking Exp:`, + `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.hack)} / s`, + ], + [ + `Strength Exp:`, + `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.str)} / s`, + ], + [ + `Defense Exp:`, + `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.def)} / s`, + ], + [ + `Dexterity Exp:`, + `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.dex)} / s`, + ], + [ + `Agility Exp:`, + `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.agi)} / s`, + ], + [ + `Charisma Exp:`, + `${numeralWrapper.formatExp(5 * sleeve.gainRatesForTask.cha)} / s`, + ], + ]; + if ( + sleeve.currentTask === SleeveTaskType.Company || + sleeve.currentTask === SleeveTaskType.Faction + ) { + const repGain: number = sleeve.getRepGain(playerRef); + data.push([`Reputation:`, ReputationRate(5 * repGain)]); } + ReactDOM.render( + EarningsTableElement("Earnings (Pre-Synchronization)", data), + elems.currentEarningsInfo, + ); + + elems.taskProgressBar.innerText = ""; + } } const universitySelectorOptions: string[] = [ - "Study Computer Science", - "Data Structures", - "Networks", - "Algorithms", - "Management", - "Leadership", + "Study Computer Science", + "Data Structures", + "Networks", + "Algorithms", + "Management", + "Leadership", ]; const gymSelectorOptions: string[] = [ - "Train Strength", - "Train Defense", - "Train Dexterity", - "Train Agility", + "Train Strength", + "Train Defense", + "Train Dexterity", + "Train Agility", ]; // Whenever a new task is selected, the "details" selector must update accordingly -function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSleeves: Sleeve[]): void { - if (playerRef == null) { - throw new Error(`playerRef is null in updateSleeveTaskSelector()`); +function updateSleeveTaskSelector( + sleeve: Sleeve, + elems: ISleeveUIElems, + allSleeves: Sleeve[], +): void { + if (playerRef == null) { + throw new Error(`playerRef is null in updateSleeveTaskSelector()`); + } + + // Array of all companies that other sleeves are working at + const forbiddenCompanies: string[] = []; + for (const otherSleeve of allSleeves) { + if (sleeve === otherSleeve) { + continue; } - - // Array of all companies that other sleeves are working at - const forbiddenCompanies: string[] = []; - for (const otherSleeve of allSleeves) { - if (sleeve === otherSleeve) { continue; } - if (otherSleeve.currentTask === SleeveTaskType.Company) { - forbiddenCompanies.push(otherSleeve.currentTaskLocation); - } + if (otherSleeve.currentTask === SleeveTaskType.Company) { + forbiddenCompanies.push(otherSleeve.currentTaskLocation); } + } - // Array of all factions that other sleeves are working for - const forbiddenFactions: string[] = []; - for (const otherSleeve of allSleeves) { - if (sleeve === otherSleeve) { continue; } - if (otherSleeve.currentTask === SleeveTaskType.Faction) { - forbiddenFactions.push(otherSleeve.currentTaskLocation); - } + // Array of all factions that other sleeves are working for + const forbiddenFactions: string[] = []; + for (const otherSleeve of allSleeves) { + if (sleeve === otherSleeve) { + continue; } - - if(elems.taskDetailsSelector === null) { - throw new Error("elems.taskDetailsSelector is null"); + if (otherSleeve.currentTask === SleeveTaskType.Faction) { + forbiddenFactions.push(otherSleeve.currentTaskLocation); } - if(elems.taskDetailsSelector2 === null) { - throw new Error("elems.taskDetailsSelector is null"); + } + + if (elems.taskDetailsSelector === null) { + throw new Error("elems.taskDetailsSelector is null"); + } + if (elems.taskDetailsSelector2 === null) { + throw new Error("elems.taskDetailsSelector is null"); + } + + // Reset Selectors + removeChildrenFromElement(elems.taskDetailsSelector); + removeChildrenFromElement(elems.taskDetailsSelector2); + elems.taskDetailsSelector2 = clearEventListeners( + elems.taskDetailsSelector2, + ) as HTMLSelectElement; + + const value: string = getSelectValue(elems.taskSelector); + switch (value) { + case "Work for Company": { + let companyCount = 0; + const allJobs: string[] = Object.keys(playerRef.jobs); + for (let i = 0; i < allJobs.length; ++i) { + if (!forbiddenCompanies.includes(allJobs[i])) { + elems.taskDetailsSelector.add(createOptionElement(allJobs[i])); + + // Set initial value of the 'Details' selector + if (sleeve.currentTaskLocation === allJobs[i]) { + elems.taskDetailsSelector.selectedIndex = companyCount; + } + + ++companyCount; + } + + elems.taskDetailsSelector2.add(createOptionElement("------")); + } + break; } + case "Work for Faction": { + let factionCount = 0; + for (const fac of playerRef.factions) { + if (!forbiddenFactions.includes(fac)) { + elems.taskDetailsSelector.add(createOptionElement(fac)); - + // Set initial value of the 'Details' Selector + if (sleeve.currentTaskLocation === fac) { + elems.taskDetailsSelector.selectedIndex = factionCount; + } - // Reset Selectors - removeChildrenFromElement(elems.taskDetailsSelector); - removeChildrenFromElement(elems.taskDetailsSelector2); - elems.taskDetailsSelector2 = clearEventListeners(elems.taskDetailsSelector2) as HTMLSelectElement; - - const value: string = getSelectValue(elems.taskSelector); - switch(value) { - case "Work for Company": { - let companyCount = 0; - const allJobs: string[] = Object.keys(playerRef.jobs); - for (let i = 0; i < allJobs.length; ++i) { - if (!forbiddenCompanies.includes(allJobs[i])) { - elems.taskDetailsSelector.add(createOptionElement(allJobs[i])); - - // Set initial value of the 'Details' selector - if (sleeve.currentTaskLocation === allJobs[i]) { - elems.taskDetailsSelector.selectedIndex = companyCount; - } - - ++companyCount; - } - - elems.taskDetailsSelector2.add(createOptionElement("------")); - } - break; + ++factionCount; } - case "Work for Faction": { - let factionCount = 0; - for (const fac of playerRef.factions) { - if (!forbiddenFactions.includes(fac)) { - elems.taskDetailsSelector.add(createOptionElement(fac)); + } - // Set initial value of the 'Details' Selector - if (sleeve.currentTaskLocation === fac) { - elems.taskDetailsSelector.selectedIndex = factionCount; - } - - ++factionCount; - } - } - - // The available faction work types depends on the faction - elems.taskDetailsSelector.addEventListener("change", () => { - if(elems.taskDetailsSelector2 === null) - throw new Error("elems.taskDetailsSelector2 is null"); - const facName = getSelectValue(elems.taskDetailsSelector); - const faction: Faction | null = Factions[facName]; - if (faction == null) { - console.warn(`Invalid faction name when trying to update Sleeve Task Selector: ${facName}`); - return; - } - const facInfo = faction.getInfo(); - removeChildrenFromElement(elems.taskDetailsSelector2); - let numOptionsAdded = 0; - if (facInfo.offerHackingWork) { - elems.taskDetailsSelector2.add(createOptionElement("Hacking Contracts")); - if (sleeve.factionWorkType === FactionWorkType.Hacking) { - elems.taskDetailsSelector2.selectedIndex = numOptionsAdded; - } - ++numOptionsAdded; - } - if (facInfo.offerFieldWork) { - elems.taskDetailsSelector2.add(createOptionElement("Field Work")); - if (sleeve.factionWorkType === FactionWorkType.Field) { - elems.taskDetailsSelector2.selectedIndex = numOptionsAdded; - } - ++numOptionsAdded; - } - if (facInfo.offerSecurityWork) { - elems.taskDetailsSelector2.add(createOptionElement("Security Work")); - if (sleeve.factionWorkType === FactionWorkType.Security) { - elems.taskDetailsSelector2.selectedIndex = numOptionsAdded; - } - ++numOptionsAdded; - } - }); - elems.taskDetailsSelector.dispatchEvent(new Event("change")); - break; + // The available faction work types depends on the faction + elems.taskDetailsSelector.addEventListener("change", () => { + if (elems.taskDetailsSelector2 === null) + throw new Error("elems.taskDetailsSelector2 is null"); + const facName = getSelectValue(elems.taskDetailsSelector); + const faction: Faction | null = Factions[facName]; + if (faction == null) { + console.warn( + `Invalid faction name when trying to update Sleeve Task Selector: ${facName}`, + ); + return; } - case "Commit Crime": { - let i = 0; - for (const crimeLabel in Crimes) { - const name: string = Crimes[crimeLabel].name; - elems.taskDetailsSelector.add(createOptionElement(name, crimeLabel)); - - // Set initial value for crime type - if (sleeve.crimeType === "") { continue; } - const crime: Crime | null = Crimes[sleeve.crimeType]; - if (crime === null) { continue; } - if (name === crime.name) { - elems.taskDetailsSelector.selectedIndex = i; - } - - ++i; - } - - elems.taskDetailsSelector2.add(createOptionElement("------")); - break; + const facInfo = faction.getInfo(); + removeChildrenFromElement(elems.taskDetailsSelector2); + let numOptionsAdded = 0; + if (facInfo.offerHackingWork) { + elems.taskDetailsSelector2.add( + createOptionElement("Hacking Contracts"), + ); + if (sleeve.factionWorkType === FactionWorkType.Hacking) { + elems.taskDetailsSelector2.selectedIndex = numOptionsAdded; + } + ++numOptionsAdded; } - case "Take University Course": - // First selector has class type - for (let i = 0; i < universitySelectorOptions.length; ++i) { - elems.taskDetailsSelector.add(createOptionElement(universitySelectorOptions[i])); + if (facInfo.offerFieldWork) { + elems.taskDetailsSelector2.add(createOptionElement("Field Work")); + if (sleeve.factionWorkType === FactionWorkType.Field) { + elems.taskDetailsSelector2.selectedIndex = numOptionsAdded; + } + ++numOptionsAdded; + } + if (facInfo.offerSecurityWork) { + elems.taskDetailsSelector2.add(createOptionElement("Security Work")); + if (sleeve.factionWorkType === FactionWorkType.Security) { + elems.taskDetailsSelector2.selectedIndex = numOptionsAdded; + } + ++numOptionsAdded; + } + }); + elems.taskDetailsSelector.dispatchEvent(new Event("change")); + break; + } + case "Commit Crime": { + let i = 0; + for (const crimeLabel in Crimes) { + const name: string = Crimes[crimeLabel].name; + elems.taskDetailsSelector.add(createOptionElement(name, crimeLabel)); - // Set initial value - if (sleeve.className === universitySelectorOptions[i]) { - elems.taskDetailsSelector.selectedIndex = i; - } - } + // Set initial value for crime type + if (sleeve.crimeType === "") { + continue; + } + const crime: Crime | null = Crimes[sleeve.crimeType]; + if (crime === null) { + continue; + } + if (name === crime.name) { + elems.taskDetailsSelector.selectedIndex = i; + } - // Second selector has which university - switch (sleeve.city) { - case CityName.Aevum: - elems.taskDetailsSelector2.add(createOptionElement(LocationName.AevumSummitUniversity)); - break; - case CityName.Sector12: - elems.taskDetailsSelector2.add(createOptionElement(LocationName.Sector12RothmanUniversity)); - break; - case CityName.Volhaven: - elems.taskDetailsSelector2.add(createOptionElement(LocationName.VolhavenZBInstituteOfTechnology)); - break; - default: - elems.taskDetailsSelector2.add(createOptionElement("No university available in city!")); - break; - } - break; - case "Workout at Gym": - // First selector has what stat is being trained - for (let i = 0; i < gymSelectorOptions.length; ++i) { - elems.taskDetailsSelector.add(createOptionElement(gymSelectorOptions[i])); + ++i; + } - // Set initial value - if (sleeve.gymStatType === gymSelectorOptions[i].substring(6, 9).toLowerCase()) { - elems.taskDetailsSelector.selectedIndex = i; - } - } + elems.taskDetailsSelector2.add(createOptionElement("------")); + break; + } + case "Take University Course": + // First selector has class type + for (let i = 0; i < universitySelectorOptions.length; ++i) { + elems.taskDetailsSelector.add( + createOptionElement(universitySelectorOptions[i]), + ); - // Second selector has gym - // In this switch statement we also set the initial value of the second selector - switch (sleeve.city) { - case CityName.Aevum: - elems.taskDetailsSelector2.add(createOptionElement(LocationName.AevumCrushFitnessGym)); - elems.taskDetailsSelector2.add(createOptionElement(LocationName.AevumSnapFitnessGym)); + // Set initial value + if (sleeve.className === universitySelectorOptions[i]) { + elems.taskDetailsSelector.selectedIndex = i; + } + } - // Set initial value - if (sleeve.currentTaskLocation === LocationName.AevumCrushFitnessGym) { - elems.taskDetailsSelector2.selectedIndex = 0; - } else if (sleeve.currentTaskLocation === LocationName.AevumSnapFitnessGym) { - elems.taskDetailsSelector2.selectedIndex = 1; - } - break; - case CityName.Sector12: - elems.taskDetailsSelector2.add(createOptionElement(LocationName.Sector12IronGym)); - elems.taskDetailsSelector2.add(createOptionElement(LocationName.Sector12PowerhouseGym)); - - // Set initial value - if (sleeve.currentTaskLocation === LocationName.Sector12IronGym) { - elems.taskDetailsSelector2.selectedIndex = 0; - } else if (sleeve.currentTaskLocation === LocationName.Sector12PowerhouseGym) { - elems.taskDetailsSelector2.selectedIndex = 1; - } - break; - case CityName.Volhaven: - elems.taskDetailsSelector2.add(createOptionElement(LocationName.VolhavenMilleniumFitnessGym)); - break; - default: - elems.taskDetailsSelector2.add(createOptionElement("No gym available in city!")); - break; - } - - break; - case "Shock Recovery": - case "Synchronize": - case "------": - // No options in "Details" selector - elems.taskDetailsSelector.add(createOptionElement("------")); - elems.taskDetailsSelector2.add(createOptionElement("------")); - return; + // Second selector has which university + switch (sleeve.city) { + case CityName.Aevum: + elems.taskDetailsSelector2.add( + createOptionElement(LocationName.AevumSummitUniversity), + ); + break; + case CityName.Sector12: + elems.taskDetailsSelector2.add( + createOptionElement(LocationName.Sector12RothmanUniversity), + ); + break; + case CityName.Volhaven: + elems.taskDetailsSelector2.add( + createOptionElement(LocationName.VolhavenZBInstituteOfTechnology), + ); + break; default: - break; - } + elems.taskDetailsSelector2.add( + createOptionElement("No university available in city!"), + ); + break; + } + break; + case "Workout at Gym": + // First selector has what stat is being trained + for (let i = 0; i < gymSelectorOptions.length; ++i) { + elems.taskDetailsSelector.add( + createOptionElement(gymSelectorOptions[i]), + ); + + // Set initial value + if ( + sleeve.gymStatType === + gymSelectorOptions[i].substring(6, 9).toLowerCase() + ) { + elems.taskDetailsSelector.selectedIndex = i; + } + } + + // Second selector has gym + // In this switch statement we also set the initial value of the second selector + switch (sleeve.city) { + case CityName.Aevum: + elems.taskDetailsSelector2.add( + createOptionElement(LocationName.AevumCrushFitnessGym), + ); + elems.taskDetailsSelector2.add( + createOptionElement(LocationName.AevumSnapFitnessGym), + ); + + // Set initial value + if ( + sleeve.currentTaskLocation === LocationName.AevumCrushFitnessGym + ) { + elems.taskDetailsSelector2.selectedIndex = 0; + } else if ( + sleeve.currentTaskLocation === LocationName.AevumSnapFitnessGym + ) { + elems.taskDetailsSelector2.selectedIndex = 1; + } + break; + case CityName.Sector12: + elems.taskDetailsSelector2.add( + createOptionElement(LocationName.Sector12IronGym), + ); + elems.taskDetailsSelector2.add( + createOptionElement(LocationName.Sector12PowerhouseGym), + ); + + // Set initial value + if (sleeve.currentTaskLocation === LocationName.Sector12IronGym) { + elems.taskDetailsSelector2.selectedIndex = 0; + } else if ( + sleeve.currentTaskLocation === LocationName.Sector12PowerhouseGym + ) { + elems.taskDetailsSelector2.selectedIndex = 1; + } + break; + case CityName.Volhaven: + elems.taskDetailsSelector2.add( + createOptionElement(LocationName.VolhavenMilleniumFitnessGym), + ); + break; + default: + elems.taskDetailsSelector2.add( + createOptionElement("No gym available in city!"), + ); + break; + } + + break; + case "Shock Recovery": + case "Synchronize": + case "------": + // No options in "Details" selector + elems.taskDetailsSelector.add(createOptionElement("------")); + elems.taskDetailsSelector2.add(createOptionElement("------")); + return; + default: + break; + } } function setSleeveTask(sleeve: Sleeve, elems: ISleeveUIElems): boolean { - try { - if (playerRef == null) { - throw new Error("playerRef is null in Sleeve UI's setSleeveTask()"); - } - if(elems.taskDescription == null) throw new Error("elems.taskDescription is null"); - - const taskValue: string = getSelectValue(elems.taskSelector); - const detailValue: string = getSelectValue(elems.taskDetailsSelector); - const detailValue2: string = getSelectValue(elems.taskDetailsSelector2); - - let res = false; - switch(taskValue) { - case "------": - elems.taskDescription.innerText = "This sleeve is currently idle"; - break; - case "Work for Company": - res = sleeve.workForCompany(playerRef, detailValue); - break; - case "Work for Faction": - res = sleeve.workForFaction(playerRef, detailValue, detailValue2); - break; - case "Commit Crime": - res = sleeve.commitCrime(playerRef, detailValue); - break; - case "Take University Course": - res = sleeve.takeUniversityCourse(playerRef, detailValue2, detailValue); - break; - case "Workout at Gym": - res = sleeve.workoutAtGym(playerRef, detailValue2, detailValue); - break; - case "Shock Recovery": - sleeve.currentTask = SleeveTaskType.Recovery; - res = sleeve.shockRecovery(playerRef); - break; - case "Synchronize": - res = sleeve.synchronize(playerRef); - break; - default: - console.error(`Invalid/Unrecognized taskValue in setSleeveTask(): ${taskValue}`); - } - - if (res) { - updateSleeveTaskDescription(sleeve, elems); - } else { - switch (taskValue) { - case "Work for Faction": - elems.taskDescription.innerText = "Failed to assign sleeve to task. This is most likely because the selected faction does not offer the selected work type."; - break; - default: - elems.taskDescription.innerText = "Failed to assign sleeve to task. Invalid choice(s)."; - break; - } - - } - - if (routing.isOn(Page.Sleeves)) { - updateSleevesPage(); - - // Update the task selector for all sleeves by triggering a change event - if(UIElems.sleeves == null) throw new Error("UIElems.sleeves is null"); - for (const e of UIElems.sleeves) { - if(e.taskSelector == null) throw new Error("e.taskSelector is null"); - e.taskSelector.dispatchEvent(new Event('change')); - } - } - - return res; - } catch(e) { - console.error(`Exception caught in setSleeveTask(): ${e}`); - exceptionAlert(e); - return false; + try { + if (playerRef == null) { + throw new Error("playerRef is null in Sleeve UI's setSleeveTask()"); } + if (elems.taskDescription == null) + throw new Error("elems.taskDescription is null"); + + const taskValue: string = getSelectValue(elems.taskSelector); + const detailValue: string = getSelectValue(elems.taskDetailsSelector); + const detailValue2: string = getSelectValue(elems.taskDetailsSelector2); + + let res = false; + switch (taskValue) { + case "------": + elems.taskDescription.innerText = "This sleeve is currently idle"; + break; + case "Work for Company": + res = sleeve.workForCompany(playerRef, detailValue); + break; + case "Work for Faction": + res = sleeve.workForFaction(playerRef, detailValue, detailValue2); + break; + case "Commit Crime": + res = sleeve.commitCrime(playerRef, detailValue); + break; + case "Take University Course": + res = sleeve.takeUniversityCourse(playerRef, detailValue2, detailValue); + break; + case "Workout at Gym": + res = sleeve.workoutAtGym(playerRef, detailValue2, detailValue); + break; + case "Shock Recovery": + sleeve.currentTask = SleeveTaskType.Recovery; + res = sleeve.shockRecovery(playerRef); + break; + case "Synchronize": + res = sleeve.synchronize(playerRef); + break; + default: + console.error( + `Invalid/Unrecognized taskValue in setSleeveTask(): ${taskValue}`, + ); + } + + if (res) { + updateSleeveTaskDescription(sleeve, elems); + } else { + switch (taskValue) { + case "Work for Faction": + elems.taskDescription.innerText = + "Failed to assign sleeve to task. This is most likely because the selected faction does not offer the selected work type."; + break; + default: + elems.taskDescription.innerText = + "Failed to assign sleeve to task. Invalid choice(s)."; + break; + } + } + + if (routing.isOn(Page.Sleeves)) { + updateSleevesPage(); + + // Update the task selector for all sleeves by triggering a change event + if (UIElems.sleeves == null) throw new Error("UIElems.sleeves is null"); + for (const e of UIElems.sleeves) { + if (e.taskSelector == null) throw new Error("e.taskSelector is null"); + e.taskSelector.dispatchEvent(new Event("change")); + } + } + + return res; + } catch (e) { + console.error(`Exception caught in setSleeveTask(): ${e}`); + exceptionAlert(e); + return false; + } } -function updateSleeveTaskDescription(sleeve: Sleeve, elems: ISleeveUIElems): void { - try { - if (playerRef == null) { - throw new Error("playerRef is null in Sleeve UI's setSleeveTask()"); - } - - const taskValue: string = getSelectValue(elems.taskSelector); - const detailValue: string = getSelectValue(elems.taskDetailsSelector); - const detailValue2: string = getSelectValue(elems.taskDetailsSelector2); - if(elems.taskDescription == null) throw new Error("elems.taskDescription should not be null") - - switch(taskValue) { - case "------": - elems.taskDescription.innerText = "This sleeve is currently idle"; - break; - case "Work for Company": - elems.taskDescription.innerText = `This sleeve is currently working your job at ${sleeve.currentTaskLocation}.`; - break; - case "Work for Faction": - elems.taskDescription.innerText = `This sleeve is currently doing ${detailValue2} for ${sleeve.currentTaskLocation}.`; - break; - case "Commit Crime": - elems.taskDescription.innerText = `This sleeve is currently attempting to ${Crimes[detailValue].type} (Success Rate: ${numeralWrapper.formatPercentage(Crimes[detailValue].successRate(sleeve))}).`; - break; - case "Take University Course": - elems.taskDescription.innerText = `This sleeve is currently studying/taking a course at ${sleeve.currentTaskLocation}.`; - break; - case "Workout at Gym": - elems.taskDescription.innerText = `This sleeve is currently working out at ${sleeve.currentTaskLocation}.`; - break; - case "Shock Recovery": - elems.taskDescription.innerText = "This sleeve is currently set to focus on shock recovery. This causes " + - "the Sleeve's shock to decrease at a faster rate."; - break; - case "Synchronize": - elems.taskDescription.innerText = "This sleeve is currently set to synchronize with the original consciousness. " + - "This causes the Sleeve's synchronization to increase." - break; - default: - console.error(`Invalid/Unrecognized taskValue in updateSleeveTaskDescription(): ${taskValue}`); - } - } catch(e) { - console.error(`Exception caught in updateSleeveTaskDescription(): ${e}`); - exceptionAlert(e); +function updateSleeveTaskDescription( + sleeve: Sleeve, + elems: ISleeveUIElems, +): void { + try { + if (playerRef == null) { + throw new Error("playerRef is null in Sleeve UI's setSleeveTask()"); } + + const taskValue: string = getSelectValue(elems.taskSelector); + const detailValue: string = getSelectValue(elems.taskDetailsSelector); + const detailValue2: string = getSelectValue(elems.taskDetailsSelector2); + if (elems.taskDescription == null) + throw new Error("elems.taskDescription should not be null"); + + switch (taskValue) { + case "------": + elems.taskDescription.innerText = "This sleeve is currently idle"; + break; + case "Work for Company": + elems.taskDescription.innerText = `This sleeve is currently working your job at ${sleeve.currentTaskLocation}.`; + break; + case "Work for Faction": + elems.taskDescription.innerText = `This sleeve is currently doing ${detailValue2} for ${sleeve.currentTaskLocation}.`; + break; + case "Commit Crime": + elems.taskDescription.innerText = `This sleeve is currently attempting to ${ + Crimes[detailValue].type + } (Success Rate: ${numeralWrapper.formatPercentage( + Crimes[detailValue].successRate(sleeve), + )}).`; + break; + case "Take University Course": + elems.taskDescription.innerText = `This sleeve is currently studying/taking a course at ${sleeve.currentTaskLocation}.`; + break; + case "Workout at Gym": + elems.taskDescription.innerText = `This sleeve is currently working out at ${sleeve.currentTaskLocation}.`; + break; + case "Shock Recovery": + elems.taskDescription.innerText = + "This sleeve is currently set to focus on shock recovery. This causes " + + "the Sleeve's shock to decrease at a faster rate."; + break; + case "Synchronize": + elems.taskDescription.innerText = + "This sleeve is currently set to synchronize with the original consciousness. " + + "This causes the Sleeve's synchronization to increase."; + break; + default: + console.error( + `Invalid/Unrecognized taskValue in updateSleeveTaskDescription(): ${taskValue}`, + ); + } + } catch (e) { + console.error(`Exception caught in updateSleeveTaskDescription(): ${e}`); + exceptionAlert(e); + } } diff --git a/src/PersonObjects/Sleeve/data/SleeveFaq.tsx b/src/PersonObjects/Sleeve/data/SleeveFaq.tsx index 7a8ccf844..3d817e26b 100644 --- a/src/PersonObjects/Sleeve/data/SleeveFaq.tsx +++ b/src/PersonObjects/Sleeve/data/SleeveFaq.tsx @@ -1,80 +1,116 @@ import * as React from "react"; -export const SleeveFaq = (<> - How do Duplicate Sleeves work? +export const SleeveFaq = ( + <> + + How do Duplicate Sleeves work? +
    - Duplicate Sleeves are essentially clones. You can use them to perform any work type - action, such as working for a company/faction or committing a crime. - Having sleeves perform these tasks earns you money, experience, and reputation. -

    - Sleeves are their own individuals, which means they each have their own - experience and stats. -

    - When a sleeve earns experience, it earns experience for itself, the player's - original 'consciousness', as well as all of the player's other sleeves. -

    - - What is Synchronization (Sync)? + Duplicate Sleeves are essentially clones. You can use them to perform any + work type action, such as working for a company/faction or committing a + crime. Having sleeves perform these tasks earns you money, experience, and + reputation.
    - Synchronization is a measure of how aligned your consciousness is with - that of your Duplicate Sleeves. It is a numerical value between 1 and 100, and - it affects how much experience is earned when the sleeve is performing a task. -

    - Let N be the sleeve's synchronization. When the sleeve earns experience by performing a - task, both the sleeve and the player's original host consciousness earn N% - of the amount of experience normally earned by the task. All of the player's - other sleeves earn ((N/100)^2 * 100)% of the experience. -

    - Synchronization can be increased by assigning sleeves to the 'Synchronize' task. -

    - - What is Shock?
    - Sleeve shock is a measure of how much trauma the sleeve has due to being placed in a new - body. It is a numerical value between 0 and 99, where 99 indicates full shock and 0 indicates - no shock. Shock affects the amount of experience earned by the sleeve. -

    - Sleeve shock slowly decreases over time. You can further increase the rate at which - it decreases by assigning sleeves to the 'Shock Recovery' task. -

    - - Why can't I work for this company or faction? + Sleeves are their own individuals, which means they each have their own + experience and stats.
    - Only one of your sleeves can work for a given company/faction a time. - To clarify further, if you have two sleeves they can work for two different - companies, but they cannot both work for the same company. -

    - - Why did my Sleeve stop working?
    - Sleeves are subject to the same time restrictions as you. This means that - they automatically stop working at a company after 8 hours, and stop working - for a faction after 20 hours. -

    - - How do I buy Augmentations for my Sleeves? + When a sleeve earns experience, it earns experience for itself, the player's + original 'consciousness', as well as all of the player's other sleeves.
    - Your Sleeve needs to have a Shock of 0 in order for you to buy Augmentations - for it. -

    - - Why can't I buy the X Augmentation for my sleeve?
    - Certain Augmentations, like Bladeburner-specific ones and NeuroFlux Governor, - are not available for sleeves. -

    - - Do sleeves get reset when installing Augmentations or switching BitNodes?
    - Sleeves are reset when switching BitNodes, but not when installing Augmentations. -

    - - What is Memory? + + What is Synchronization (Sync)? +
    - Sleeve memory dictates what a sleeve's synchronization will be - when its reset by switching BitNodes. For example, if a sleeve has a memory of 25, - then when you switch BitNodes its synchronization will initially be set to 25, rather than 1. -

    - Memory can only be increased by purchasing upgrades from The Covenant. It is a - persistent stat, meaning it never gets resets back to 1. The maximum possible - value for a sleeve's memory is 100. -); \ No newline at end of file + Synchronization is a measure of how aligned your consciousness is with that + of your Duplicate Sleeves. It is a numerical value between 1 and 100, and it + affects how much experience is earned when the sleeve is performing a task. +
    +
    + Let N be the sleeve's synchronization. When the sleeve earns experience by + performing a task, both the sleeve and the player's original host + consciousness earn N% of the amount of experience normally earned by the + task. All of the player's other sleeves earn ((N/100)^2 * 100)% of the + experience. +
    +
    + Synchronization can be increased by assigning sleeves to the 'Synchronize' + task. +
    +
    + + What is Shock? + +
    + Sleeve shock is a measure of how much trauma the sleeve has due to being + placed in a new body. It is a numerical value between 0 and 99, where 99 + indicates full shock and 0 indicates no shock. Shock affects the amount of + experience earned by the sleeve. +
    +
    + Sleeve shock slowly decreases over time. You can further increase the rate + at which it decreases by assigning sleeves to the 'Shock Recovery' task. +
    +
    + + Why can't I work for this company or faction? + +
    + Only one of your sleeves can work for a given company/faction a time. To + clarify further, if you have two sleeves they can work for two different + companies, but they cannot both work for the same company. +
    +
    + + Why did my Sleeve stop working? + +
    + Sleeves are subject to the same time restrictions as you. This means that + they automatically stop working at a company after 8 hours, and stop working + for a faction after 20 hours. +
    +
    + + How do I buy Augmentations for my Sleeves? + +
    + Your Sleeve needs to have a Shock of 0 in order for you to buy Augmentations + for it. +
    +
    + + Why can't I buy the X Augmentation for my sleeve? + +
    + Certain Augmentations, like Bladeburner-specific ones and NeuroFlux + Governor, are not available for sleeves. +
    +
    + + + Do sleeves get reset when installing Augmentations or switching + BitNodes? + + +
    + Sleeves are reset when switching BitNodes, but not when installing + Augmentations. +
    +
    + + What is Memory? + +
    + Sleeve memory dictates what a sleeve's synchronization will be when its + reset by switching BitNodes. For example, if a sleeve has a memory of 25, + then when you switch BitNodes its synchronization will initially be set to + 25, rather than 1. +
    +
    + Memory can only be increased by purchasing upgrades from The Covenant. It is + a persistent stat, meaning it never gets resets back to 1. The maximum + possible value for a sleeve's memory is 100. + +); diff --git a/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx b/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx index 6f1fd3070..bc92b2a1f 100644 --- a/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx +++ b/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx @@ -2,92 +2,110 @@ * Root React component for the popup that lets player purchase Duplicate * Sleeves and Sleeve-related upgrades from The Covenant */ -import React, { useState } from 'react'; +import React, { useState } from "react"; -import { CovenantSleeveUpgrades } from "./CovenantSleeveUpgrades"; +import { CovenantSleeveUpgrades } from "./CovenantSleeveUpgrades"; -import { Sleeve } from "../Sleeve"; -import { BaseCostPerSleeve, - MaxSleevesFromCovenant, - PopupId } from "../SleeveCovenantPurchases"; -import { IPlayer } from "../../IPlayer"; +import { Sleeve } from "../Sleeve"; +import { + BaseCostPerSleeve, + MaxSleevesFromCovenant, + PopupId, +} from "../SleeveCovenantPurchases"; +import { IPlayer } from "../../IPlayer"; -import { PopupCloseButton } from "../../../ui/React/PopupCloseButton"; -import { StdButton } from "../../../ui/React/StdButton"; -import { Money } from "../../../ui/React/Money"; +import { PopupCloseButton } from "../../../ui/React/PopupCloseButton"; +import { StdButton } from "../../../ui/React/StdButton"; +import { Money } from "../../../ui/React/Money"; -import { dialogBoxCreate } from "../../../../utils/DialogBox"; +import { dialogBoxCreate } from "../../../../utils/DialogBox"; interface IProps { - closeFn: () => void; - p: IPlayer; + closeFn: () => void; + p: IPlayer; } export function CovenantPurchasesRoot(props: IProps): React.ReactElement { - const [update, setUpdate] = useState(0); + const [update, setUpdate] = useState(0); - /** - * Get the cost to purchase a new Duplicate Sleeve - */ - function purchaseCost(): number { - return (props.p.sleevesFromCovenant + 1) * BaseCostPerSleeve; - } + /** + * Get the cost to purchase a new Duplicate Sleeve + */ + function purchaseCost(): number { + return (props.p.sleevesFromCovenant + 1) * BaseCostPerSleeve; + } - /** - * Force a rerender by just changing an arbitrary state value - */ - function rerender(): void { - setUpdate(update + 1); - } + /** + * Force a rerender by just changing an arbitrary state value + */ + function rerender(): void { + setUpdate(update + 1); + } - // Purchasing a new Duplicate Sleeve - let purchaseDisabled = false; - if (!props.p.canAfford(purchaseCost())) { - purchaseDisabled = true; - } - if (props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) { - purchaseDisabled = true; - } + // Purchasing a new Duplicate Sleeve + let purchaseDisabled = false; + if (!props.p.canAfford(purchaseCost())) { + purchaseDisabled = true; + } + if (props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) { + purchaseDisabled = true; + } - function purchaseOnClick(): void { - if (props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) return; - - if (props.p.canAfford(purchaseCost())) { - props.p.loseMoney(purchaseCost()); - props.p.sleevesFromCovenant += 1; - props.p.sleeves.push(new Sleeve(props.p)); - rerender(); - } else { - dialogBoxCreate(`You cannot afford to purchase a Duplicate Sleeve`, false); - } - } + function purchaseOnClick(): void { + if (props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) return; - // Purchasing Upgrades for Sleeves - const upgradePanels = []; - for (let i = 0; i < props.p.sleeves.length; ++i) { - const sleeve = props.p.sleeves[i]; - upgradePanels.push( - , - ) + if (props.p.canAfford(purchaseCost())) { + props.p.loseMoney(purchaseCost()); + props.p.sleevesFromCovenant += 1; + props.p.sleeves.push(new Sleeve(props.p)); + rerender(); + } else { + dialogBoxCreate( + `You cannot afford to purchase a Duplicate Sleeve`, + false, + ); } + } - return (
    - -

    - Would you like to purchase an additional Duplicate Sleeve from The Covenant - for ? -

    -
    -

    - These Duplicate Sleeves are permanent (they persist through BitNodes). You can - purchase a total of {MaxSleevesFromCovenant} from The Covenant. -

    - -

    -

    - Here, you can also purchase upgrades for your Duplicate Sleeves. These upgrades - are also permanent, meaning they persist across BitNodes. -

    - {upgradePanels} -
    ); + // Purchasing Upgrades for Sleeves + const upgradePanels = []; + for (let i = 0; i < props.p.sleeves.length; ++i) { + const sleeve = props.p.sleeves[i]; + upgradePanels.push( + , + ); + } + + return ( +
    + +

    + Would you like to purchase an additional Duplicate Sleeve from The + Covenant for ? +

    +
    +

    + These Duplicate Sleeves are permanent (they persist through BitNodes). + You can purchase a total of {MaxSleevesFromCovenant} from The Covenant. +

    + +
    +
    +

    + Here, you can also purchase upgrades for your Duplicate Sleeves. These + upgrades are also permanent, meaning they persist across BitNodes. +

    + {upgradePanels} +
    + ); } diff --git a/src/PersonObjects/Sleeve/ui/CovenantSleeveMemoryUpgrade.tsx b/src/PersonObjects/Sleeve/ui/CovenantSleeveMemoryUpgrade.tsx index 98880b897..3f30bcdbd 100644 --- a/src/PersonObjects/Sleeve/ui/CovenantSleeveMemoryUpgrade.tsx +++ b/src/PersonObjects/Sleeve/ui/CovenantSleeveMemoryUpgrade.tsx @@ -12,91 +12,116 @@ import { StdButton } from "../../../ui/React/StdButton"; import { Money } from "../../../ui/React/Money"; interface IProps { - index: number; - p: IPlayer; - rerender: () => void; - sleeve: Sleeve; + index: number; + p: IPlayer; + rerender: () => void; + sleeve: Sleeve; } interface IState { - amt: number; + amt: number; } -export class CovenantSleeveMemoryUpgrade extends React.Component { - constructor(props: IProps) { - super(props); +export class CovenantSleeveMemoryUpgrade extends React.Component< + IProps, + IState +> { + constructor(props: IProps) { + super(props); - this.state = { - amt: 1, - } + this.state = { + amt: 1, + }; - this.changePurchaseAmount = this.changePurchaseAmount.bind(this); - this.purchaseMemory = this.purchaseMemory.bind(this); + this.changePurchaseAmount = this.changePurchaseAmount.bind(this); + this.purchaseMemory = this.purchaseMemory.bind(this); + } + + changePurchaseAmount(e: React.ChangeEvent): void { + let n: number = parseInt(e.target.value); + + if (isNaN(n)) n = 1; + const maxMemory = 100 - this.props.sleeve.memory; + if (n > maxMemory) n = maxMemory; + + this.setState({ + amt: n, + }); + } + + getPurchaseCost(): number { + if (isNaN(this.state.amt)) { + return Infinity; } - changePurchaseAmount(e: React.ChangeEvent): void { - let n: number = parseInt(e.target.value); - - if(isNaN(n)) n = 1; - const maxMemory = 100 - this.props.sleeve.memory; - if (n > maxMemory) n = maxMemory; - - this.setState({ - amt: n, - }); + const maxMemory = 100 - this.props.sleeve.memory; + if (this.state.amt > maxMemory) { + return Infinity; } - getPurchaseCost(): number { - if (isNaN(this.state.amt)) { return Infinity; } + return this.props.sleeve.getMemoryUpgradeCost(this.state.amt); + } - const maxMemory = 100 - this.props.sleeve.memory; - if (this.state.amt > maxMemory) { return Infinity; } + purchaseMemory(): void { + const cost = this.getPurchaseCost(); + if (this.props.p.canAfford(cost)) { + this.props.sleeve.upgradeMemory(this.state.amt); + this.props.p.loseMoney(cost); + this.props.rerender(); + } + } - return this.props.sleeve.getMemoryUpgradeCost(this.state.amt); + render(): React.ReactNode { + const inputId = `sleeve-${this.props.index}-memory-upgrade-input`; + + // Memory cannot go above 100 + const maxMemory = 100 - this.props.sleeve.memory; + + // Purchase button props + const cost = this.getPurchaseCost(); + const purchaseBtnDisabled = !this.props.p.canAfford(cost); + let purchaseBtnContent; + if (isNaN(this.state.amt)) { + purchaseBtnContent = <>Invalid value; + } else if (this.state.amt > maxMemory) { + purchaseBtnContent = <>Memory cannot exceed 100?; + } else { + purchaseBtnContent = ( + <> + Purchase {this.state.amt} memory -{" "} + ? + + ); } - purchaseMemory(): void { - const cost = this.getPurchaseCost(); - if (this.props.p.canAfford(cost)) { - this.props.sleeve.upgradeMemory(this.state.amt); - this.props.p.loseMoney(cost); - this.props.rerender(); - } - } + return ( +
    +

    + Upgrade Memory +

    +

    + Purchase a memory upgrade for your sleeve. Note that a sleeve's max + memory is 100 (current:{" "} + {numeralWrapper.formatSleeveMemory(this.props.sleeve.memory)}) +

    - render(): React.ReactNode { - const inputId = `sleeve-${this.props.index}-memory-upgrade-input`; - - // Memory cannot go above 100 - const maxMemory = 100 - this.props.sleeve.memory; - - // Purchase button props - const cost = this.getPurchaseCost(); - const purchaseBtnDisabled = !this.props.p.canAfford(cost); - let purchaseBtnContent; - if (isNaN(this.state.amt)) { - purchaseBtnContent = <>Invalid value; - } else if (this.state.amt > maxMemory) { - purchaseBtnContent = <>Memory cannot exceed 100?; - } else { - purchaseBtnContent = <>Purchase {this.state.amt} memory - ?; - } - - return ( -
    -

    Upgrade Memory

    -

    - Purchase a memory upgrade for your sleeve. Note that a sleeve's max memory - is 100 (current: {numeralWrapper.formatSleeveMemory(this.props.sleeve.memory)}) -

    - - - -
    - -
    - ) - } + + +
    + +
    + ); + } } diff --git a/src/PersonObjects/Sleeve/ui/CovenantSleeveUpgrades.tsx b/src/PersonObjects/Sleeve/ui/CovenantSleeveUpgrades.tsx index fd8e7c776..2c3e126f9 100644 --- a/src/PersonObjects/Sleeve/ui/CovenantSleeveUpgrades.tsx +++ b/src/PersonObjects/Sleeve/ui/CovenantSleeveUpgrades.tsx @@ -10,19 +10,19 @@ import { Sleeve } from "../Sleeve"; import { IPlayer } from "../../IPlayer"; interface IProps { - index: number; - p: IPlayer; - rerender: () => void; - sleeve: Sleeve; + index: number; + p: IPlayer; + rerender: () => void; + sleeve: Sleeve; } export class CovenantSleeveUpgrades extends React.Component { - render(): React.ReactNode { - return ( -
    -

    Duplicate Sleeve {this.props.index}

    - -
    - ) - } + render(): React.ReactNode { + return ( +
    +

    Duplicate Sleeve {this.props.index}

    + +
    + ); + } } diff --git a/src/PersonObjects/Sleeve/ui/EarningsTableElement.tsx b/src/PersonObjects/Sleeve/ui/EarningsTableElement.tsx index 005c26cb2..90347773e 100644 --- a/src/PersonObjects/Sleeve/ui/EarningsTableElement.tsx +++ b/src/PersonObjects/Sleeve/ui/EarningsTableElement.tsx @@ -1,20 +1,31 @@ import * as React from "react"; -export function EarningsTableElement(title: string, stats: any[][]): React.ReactElement { - return (<> -
    {title}
    - - - {stats.map((stat: any[], i: number) => - {stat.map((s: any, i: number) => { - let style = {}; - if(i !== 0) { - style = {textAlign: "right"}; - } - return - })} - )} - -
    {s}
    - ) +export function EarningsTableElement( + title: string, + stats: any[][], +): React.ReactElement { + return ( + <> +
    {title}
    + + + {stats.map((stat: any[], i: number) => ( + + {stat.map((s: any, i: number) => { + let style = {}; + if (i !== 0) { + style = { textAlign: "right" }; + } + return ( + + ); + })} + + ))} + +
    + {s} +
    + + ); } diff --git a/src/PersonObjects/Sleeve/ui/MoreEarningsContent.tsx b/src/PersonObjects/Sleeve/ui/MoreEarningsContent.tsx index 9e51f5116..3c16eee92 100644 --- a/src/PersonObjects/Sleeve/ui/MoreEarningsContent.tsx +++ b/src/PersonObjects/Sleeve/ui/MoreEarningsContent.tsx @@ -5,36 +5,101 @@ import * as React from "react"; import { StatsTable } from "../../../ui/React/StatsTable"; export function MoreEarningsContent(sleeve: Sleeve): React.ReactElement { - return (<> - {StatsTable([ - ['Money ', ], - ['Hacking Exp ', numeralWrapper.formatExp(sleeve.earningsForTask.hack)], - ['Strength Exp ', numeralWrapper.formatExp(sleeve.earningsForTask.str)], - ['Defense Exp ', numeralWrapper.formatExp(sleeve.earningsForTask.def)], - ['Dexterity Exp ', numeralWrapper.formatExp(sleeve.earningsForTask.dex)], - ['Agility Exp ', numeralWrapper.formatExp(sleeve.earningsForTask.agi)], - ['Charisma Exp ', numeralWrapper.formatExp(sleeve.earningsForTask.cha)], - ], 'Earnings for Current Task:')} -
    - {StatsTable([ - ['Money: ', ], - ['Hacking Exp: ', numeralWrapper.formatExp(sleeve.earningsForPlayer.hack)], - ['Strength Exp: ', numeralWrapper.formatExp(sleeve.earningsForPlayer.str)], - ['Defense Exp: ', numeralWrapper.formatExp(sleeve.earningsForPlayer.def)], - ['Dexterity Exp: ', numeralWrapper.formatExp(sleeve.earningsForPlayer.dex)], - ['Agility Exp: ', numeralWrapper.formatExp(sleeve.earningsForPlayer.agi)], - ['Charisma Exp: ', numeralWrapper.formatExp(sleeve.earningsForPlayer.cha)], - ], 'Total Earnings for Host Consciousness:')} -
    - {StatsTable([ - ['Money: ', ], - ['Hacking Exp: ', numeralWrapper.formatExp(sleeve.earningsForSleeves.hack)], - ['Strength Exp: ', numeralWrapper.formatExp(sleeve.earningsForSleeves.str)], - ['Defense Exp: ', numeralWrapper.formatExp(sleeve.earningsForSleeves.def)], - ['Dexterity Exp: ', numeralWrapper.formatExp(sleeve.earningsForSleeves.dex)], - ['Agility Exp: ', numeralWrapper.formatExp(sleeve.earningsForSleeves.agi)], - ['Charisma Exp: ', numeralWrapper.formatExp(sleeve.earningsForSleeves.cha)], - ], 'Total Earnings for Other Sleeves:')} -
    - ); -} \ No newline at end of file + return ( + <> + {StatsTable( + [ + ["Money ", ], + [ + "Hacking Exp ", + numeralWrapper.formatExp(sleeve.earningsForTask.hack), + ], + [ + "Strength Exp ", + numeralWrapper.formatExp(sleeve.earningsForTask.str), + ], + [ + "Defense Exp ", + numeralWrapper.formatExp(sleeve.earningsForTask.def), + ], + [ + "Dexterity Exp ", + numeralWrapper.formatExp(sleeve.earningsForTask.dex), + ], + [ + "Agility Exp ", + numeralWrapper.formatExp(sleeve.earningsForTask.agi), + ], + [ + "Charisma Exp ", + numeralWrapper.formatExp(sleeve.earningsForTask.cha), + ], + ], + "Earnings for Current Task:", + )} +
    + {StatsTable( + [ + ["Money: ", ], + [ + "Hacking Exp: ", + numeralWrapper.formatExp(sleeve.earningsForPlayer.hack), + ], + [ + "Strength Exp: ", + numeralWrapper.formatExp(sleeve.earningsForPlayer.str), + ], + [ + "Defense Exp: ", + numeralWrapper.formatExp(sleeve.earningsForPlayer.def), + ], + [ + "Dexterity Exp: ", + numeralWrapper.formatExp(sleeve.earningsForPlayer.dex), + ], + [ + "Agility Exp: ", + numeralWrapper.formatExp(sleeve.earningsForPlayer.agi), + ], + [ + "Charisma Exp: ", + numeralWrapper.formatExp(sleeve.earningsForPlayer.cha), + ], + ], + "Total Earnings for Host Consciousness:", + )} +
    + {StatsTable( + [ + ["Money: ", ], + [ + "Hacking Exp: ", + numeralWrapper.formatExp(sleeve.earningsForSleeves.hack), + ], + [ + "Strength Exp: ", + numeralWrapper.formatExp(sleeve.earningsForSleeves.str), + ], + [ + "Defense Exp: ", + numeralWrapper.formatExp(sleeve.earningsForSleeves.def), + ], + [ + "Dexterity Exp: ", + numeralWrapper.formatExp(sleeve.earningsForSleeves.dex), + ], + [ + "Agility Exp: ", + numeralWrapper.formatExp(sleeve.earningsForSleeves.agi), + ], + [ + "Charisma Exp: ", + numeralWrapper.formatExp(sleeve.earningsForSleeves.cha), + ], + ], + "Total Earnings for Other Sleeves:", + )} +
    + + ); +} diff --git a/src/PersonObjects/Sleeve/ui/MoreStatsContent.tsx b/src/PersonObjects/Sleeve/ui/MoreStatsContent.tsx index 78471ecca..2b1e39c38 100644 --- a/src/PersonObjects/Sleeve/ui/MoreStatsContent.tsx +++ b/src/PersonObjects/Sleeve/ui/MoreStatsContent.tsx @@ -4,34 +4,117 @@ import { StatsTable } from "../../../ui/React/StatsTable"; import * as React from "react"; export function MoreStatsContent(sleeve: Sleeve): React.ReactElement { - return (<> - {StatsTable([ - ['Hacking: ', sleeve.hacking_skill, `(${numeralWrapper.formatExp(sleeve.hacking_exp)} exp)`], - ['Strength: ', sleeve.strength, `(${numeralWrapper.formatExp(sleeve.strength_exp)} exp)`], - ['Defense: ', sleeve.defense, `(${numeralWrapper.formatExp(sleeve.defense_exp)} exp)`], - ['Dexterity: ', sleeve.dexterity, `(${numeralWrapper.formatExp(sleeve.dexterity_exp)} exp)`], - ['Agility: ', sleeve.agility, `(${numeralWrapper.formatExp(sleeve.agility_exp)} exp)`], - ['Charisma: ', sleeve.charisma, `(${numeralWrapper.formatExp(sleeve.charisma_exp)} exp)`], - ], 'Stats:')} -
    - {StatsTable([ - ['Hacking Level multiplier: ', numeralWrapper.formatPercentage(sleeve.hacking_mult)], - ['Hacking Experience multiplier: ', numeralWrapper.formatPercentage(sleeve.hacking_exp_mult)], - ['Strength Level multiplier: ', numeralWrapper.formatPercentage(sleeve.strength_mult)], - ['Strength Experience multiplier: ', numeralWrapper.formatPercentage(sleeve.strength_exp_mult)], - ['Defense Level multiplier: ', numeralWrapper.formatPercentage(sleeve.defense_mult)], - ['Defense Experience multiplier: ', numeralWrapper.formatPercentage(sleeve.defense_exp_mult)], - ['Dexterity Level multiplier: ', numeralWrapper.formatPercentage(sleeve.dexterity_mult)], - ['Dexterity Experience multiplier: ', numeralWrapper.formatPercentage(sleeve.dexterity_exp_mult)], - ['Agility Level multiplier: ', numeralWrapper.formatPercentage(sleeve.agility_mult)], - ['Agility Experience multiplier: ', numeralWrapper.formatPercentage(sleeve.agility_exp_mult)], - ['Charisma Level multiplier: ', numeralWrapper.formatPercentage(sleeve.charisma_mult)], - ['Charisma Experience multiplier: ', numeralWrapper.formatPercentage(sleeve.charisma_exp_mult)], - ['Faction Reputation Gain multiplier: ', numeralWrapper.formatPercentage(sleeve.faction_rep_mult)], - ['Company Reputation Gain multiplier: ', numeralWrapper.formatPercentage(sleeve.company_rep_mult)], - ['Salary multiplier: ', numeralWrapper.formatPercentage(sleeve.work_money_mult)], - ['Crime Money multiplier: ', numeralWrapper.formatPercentage(sleeve.crime_money_mult)], - ['Crime Success multiplier: ', numeralWrapper.formatPercentage(sleeve.crime_success_mult)], - ], 'Multipliers:')} - ); -} \ No newline at end of file + return ( + <> + {StatsTable( + [ + [ + "Hacking: ", + sleeve.hacking_skill, + `(${numeralWrapper.formatExp(sleeve.hacking_exp)} exp)`, + ], + [ + "Strength: ", + sleeve.strength, + `(${numeralWrapper.formatExp(sleeve.strength_exp)} exp)`, + ], + [ + "Defense: ", + sleeve.defense, + `(${numeralWrapper.formatExp(sleeve.defense_exp)} exp)`, + ], + [ + "Dexterity: ", + sleeve.dexterity, + `(${numeralWrapper.formatExp(sleeve.dexterity_exp)} exp)`, + ], + [ + "Agility: ", + sleeve.agility, + `(${numeralWrapper.formatExp(sleeve.agility_exp)} exp)`, + ], + [ + "Charisma: ", + sleeve.charisma, + `(${numeralWrapper.formatExp(sleeve.charisma_exp)} exp)`, + ], + ], + "Stats:", + )} +
    + {StatsTable( + [ + [ + "Hacking Level multiplier: ", + numeralWrapper.formatPercentage(sleeve.hacking_mult), + ], + [ + "Hacking Experience multiplier: ", + numeralWrapper.formatPercentage(sleeve.hacking_exp_mult), + ], + [ + "Strength Level multiplier: ", + numeralWrapper.formatPercentage(sleeve.strength_mult), + ], + [ + "Strength Experience multiplier: ", + numeralWrapper.formatPercentage(sleeve.strength_exp_mult), + ], + [ + "Defense Level multiplier: ", + numeralWrapper.formatPercentage(sleeve.defense_mult), + ], + [ + "Defense Experience multiplier: ", + numeralWrapper.formatPercentage(sleeve.defense_exp_mult), + ], + [ + "Dexterity Level multiplier: ", + numeralWrapper.formatPercentage(sleeve.dexterity_mult), + ], + [ + "Dexterity Experience multiplier: ", + numeralWrapper.formatPercentage(sleeve.dexterity_exp_mult), + ], + [ + "Agility Level multiplier: ", + numeralWrapper.formatPercentage(sleeve.agility_mult), + ], + [ + "Agility Experience multiplier: ", + numeralWrapper.formatPercentage(sleeve.agility_exp_mult), + ], + [ + "Charisma Level multiplier: ", + numeralWrapper.formatPercentage(sleeve.charisma_mult), + ], + [ + "Charisma Experience multiplier: ", + numeralWrapper.formatPercentage(sleeve.charisma_exp_mult), + ], + [ + "Faction Reputation Gain multiplier: ", + numeralWrapper.formatPercentage(sleeve.faction_rep_mult), + ], + [ + "Company Reputation Gain multiplier: ", + numeralWrapper.formatPercentage(sleeve.company_rep_mult), + ], + [ + "Salary multiplier: ", + numeralWrapper.formatPercentage(sleeve.work_money_mult), + ], + [ + "Crime Money multiplier: ", + numeralWrapper.formatPercentage(sleeve.crime_money_mult), + ], + [ + "Crime Success multiplier: ", + numeralWrapper.formatPercentage(sleeve.crime_success_mult), + ], + ], + "Multipliers:", + )} + + ); +} diff --git a/src/PersonObjects/Sleeve/ui/StatsElement.tsx b/src/PersonObjects/Sleeve/ui/StatsElement.tsx index f42c52369..f77438bf7 100644 --- a/src/PersonObjects/Sleeve/ui/StatsElement.tsx +++ b/src/PersonObjects/Sleeve/ui/StatsElement.tsx @@ -3,56 +3,79 @@ import { numeralWrapper } from "../../../ui/numeralFormat"; import * as React from "react"; export function StatsElement(sleeve: Sleeve): React.ReactElement { - let style = {}; - style = { textAlign: "right" }; - return (<> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    HP: {numeralWrapper.formatHp(sleeve.hp)} / {numeralWrapper.formatHp(sleeve.max_hp)}
    City: {sleeve.city}
    Hacking: {numeralWrapper.formatSkill(sleeve.hacking_skill)}
    Strength: {numeralWrapper.formatSkill(sleeve.strength)}
    Defense: {numeralWrapper.formatSkill(sleeve.defense)}
    Dexterity: {numeralWrapper.formatSkill(sleeve.dexterity)}
    Agility: {numeralWrapper.formatSkill(sleeve.agility)}
    Charisma: {numeralWrapper.formatSkill(sleeve.charisma)}
    Shock: {numeralWrapper.formatSleeveShock(100 - sleeve.shock)}
    Sync: {numeralWrapper.formatSleeveSynchro(sleeve.sync)}
    Memory: {numeralWrapper.formatSleeveMemory(sleeve.memory)}
    - ) + let style = {}; + style = { textAlign: "right" }; + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    HP: + {numeralWrapper.formatHp(sleeve.hp)} /{" "} + {numeralWrapper.formatHp(sleeve.max_hp)} +
    City: {sleeve.city}
    Hacking: + {numeralWrapper.formatSkill(sleeve.hacking_skill)} +
    Strength: + {numeralWrapper.formatSkill(sleeve.strength)} +
    Defense: + {numeralWrapper.formatSkill(sleeve.defense)} +
    Dexterity: + {numeralWrapper.formatSkill(sleeve.dexterity)} +
    Agility: + {numeralWrapper.formatSkill(sleeve.agility)} +
    Charisma: + {numeralWrapper.formatSkill(sleeve.charisma)} +
    Shock: + {numeralWrapper.formatSleeveShock(100 - sleeve.shock)} +
    Sync: + {numeralWrapper.formatSleeveSynchro(sleeve.sync)} +
    Memory: + {numeralWrapper.formatSleeveMemory(sleeve.memory)} +
    + + ); } diff --git a/src/PersonObjects/formulas/intelligence.ts b/src/PersonObjects/formulas/intelligence.ts index 1875e77c4..1dea32196 100644 --- a/src/PersonObjects/formulas/intelligence.ts +++ b/src/PersonObjects/formulas/intelligence.ts @@ -1,3 +1,6 @@ -export function calculateIntelligenceBonus(intelligence: number, weight = 1): number { - return 1+(weight*Math.pow(intelligence, 0.8)/600); -} \ No newline at end of file +export function calculateIntelligenceBonus( + intelligence: number, + weight = 1, +): number { + return 1 + (weight * Math.pow(intelligence, 0.8)) / 600; +} diff --git a/src/PersonObjects/formulas/reputation.ts b/src/PersonObjects/formulas/reputation.ts index 2fb66d90a..b73391df6 100644 --- a/src/PersonObjects/formulas/reputation.ts +++ b/src/PersonObjects/formulas/reputation.ts @@ -1,37 +1,48 @@ -import { IPlayer } from '../IPlayer'; -import { Faction } from '../../Faction/Faction'; -import { CONSTANTS } from '../../Constants'; -import { BitNodeMultipliers } from '../../BitNode/BitNodeMultipliers'; +import { IPlayer } from "../IPlayer"; +import { Faction } from "../../Faction/Faction"; +import { CONSTANTS } from "../../Constants"; +import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; function mult(f: Faction): number { - let favorMult = 1 + (f.favor / 100); - if (isNaN(favorMult)) {favorMult = 1;} - return favorMult * BitNodeMultipliers.FactionWorkRepGain; + let favorMult = 1 + f.favor / 100; + if (isNaN(favorMult)) { + favorMult = 1; + } + return favorMult * BitNodeMultipliers.FactionWorkRepGain; } export function getHackingWorkRepGain(p: IPlayer, f: Faction): number { - return (p.hacking_skill + p.intelligence/3) / - CONSTANTS.MaxSkillLevel * p.faction_rep_mult * - p.getIntelligenceBonus(1) * mult(f); + return ( + ((p.hacking_skill + p.intelligence / 3) / CONSTANTS.MaxSkillLevel) * + p.faction_rep_mult * + p.getIntelligenceBonus(1) * + mult(f) + ); } export function getFactionSecurityWorkRepGain(p: IPlayer, f: Faction): number { - const t = 0.9 * (p.hacking_skill / CONSTANTS.MaxSkillLevel + - p.strength / CONSTANTS.MaxSkillLevel + - p.defense / CONSTANTS.MaxSkillLevel + - p.dexterity / CONSTANTS.MaxSkillLevel + - p.agility / CONSTANTS.MaxSkillLevel + - p.intelligence / CONSTANTS.MaxSkillLevel) / 4.5; - return t * p.faction_rep_mult * mult(f) * p.getIntelligenceBonus(1); + const t = + (0.9 * + (p.hacking_skill / CONSTANTS.MaxSkillLevel + + p.strength / CONSTANTS.MaxSkillLevel + + p.defense / CONSTANTS.MaxSkillLevel + + p.dexterity / CONSTANTS.MaxSkillLevel + + p.agility / CONSTANTS.MaxSkillLevel + + p.intelligence / CONSTANTS.MaxSkillLevel)) / + 4.5; + return t * p.faction_rep_mult * mult(f) * p.getIntelligenceBonus(1); } export function getFactionFieldWorkRepGain(p: IPlayer, f: Faction): number { - const t = 0.9 * (p.hacking_skill / CONSTANTS.MaxSkillLevel + - p.strength / CONSTANTS.MaxSkillLevel + - p.defense / CONSTANTS.MaxSkillLevel + - p.dexterity / CONSTANTS.MaxSkillLevel + - p.agility / CONSTANTS.MaxSkillLevel + - p.charisma / CONSTANTS.MaxSkillLevel + - p.intelligence / CONSTANTS.MaxSkillLevel) / 5.5; - return t * p.faction_rep_mult * mult(f) * p.getIntelligenceBonus(1); + const t = + (0.9 * + (p.hacking_skill / CONSTANTS.MaxSkillLevel + + p.strength / CONSTANTS.MaxSkillLevel + + p.defense / CONSTANTS.MaxSkillLevel + + p.dexterity / CONSTANTS.MaxSkillLevel + + p.agility / CONSTANTS.MaxSkillLevel + + p.charisma / CONSTANTS.MaxSkillLevel + + p.intelligence / CONSTANTS.MaxSkillLevel)) / + 5.5; + return t * p.faction_rep_mult * mult(f) * p.getIntelligenceBonus(1); } diff --git a/src/PersonObjects/formulas/skill.ts b/src/PersonObjects/formulas/skill.ts index 4cf687aa1..ac5701bd8 100644 --- a/src/PersonObjects/formulas/skill.ts +++ b/src/PersonObjects/formulas/skill.ts @@ -1,7 +1,7 @@ export function calculateSkill(exp: number, mult = 1): number { - return Math.max(Math.floor(mult*(32 * Math.log(exp + 534.5) - 200)), 1); + return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1); } export function calculateExp(skill: number, mult = 1): number { - return Math.exp((skill / mult + 200) / 32) - 534.6 -} \ No newline at end of file + return Math.exp((skill / mult + 200) / 32) - 534.6; +} diff --git a/src/Player.js b/src/Player.js index 1353d9081..79db73bfc 100644 --- a/src/Player.js +++ b/src/Player.js @@ -9,24 +9,24 @@ import Decimal from "decimal.js"; export let Player = new PlayerObject(); export function loadPlayer(saveString) { - Player = JSON.parse(saveString, Reviver); + Player = JSON.parse(saveString, Reviver); - // Parse Decimal.js objects - Player.money = new Decimal(Player.money); + // Parse Decimal.js objects + Player.money = new Decimal(Player.money); - if (Player.corporation instanceof Corporation) { - Player.corporation.funds = new Decimal(Player.corporation.funds); - Player.corporation.revenue = new Decimal(Player.corporation.revenue); - Player.corporation.expenses = new Decimal(Player.corporation.expenses); + if (Player.corporation instanceof Corporation) { + Player.corporation.funds = new Decimal(Player.corporation.funds); + Player.corporation.revenue = new Decimal(Player.corporation.revenue); + Player.corporation.expenses = new Decimal(Player.corporation.expenses); - for (var i = 0; i < Player.corporation.divisions.length; ++i) { - var ind = Player.corporation.divisions[i]; - ind.lastCycleRevenue = new Decimal(ind.lastCycleRevenue); - ind.lastCycleExpenses = new Decimal(ind.lastCycleExpenses); - ind.thisCycleRevenue = new Decimal(ind.thisCycleRevenue); - ind.thisCycleExpenses = new Decimal(ind.thisCycleExpenses); - } + for (var i = 0; i < Player.corporation.divisions.length; ++i) { + var ind = Player.corporation.divisions[i]; + ind.lastCycleRevenue = new Decimal(ind.lastCycleRevenue); + ind.lastCycleExpenses = new Decimal(ind.lastCycleExpenses); + ind.thisCycleRevenue = new Decimal(ind.thisCycleRevenue); + ind.thisCycleExpenses = new Decimal(ind.thisCycleExpenses); } + } - Player.exploits = sanitizeExploits(Player.exploits); + Player.exploits = sanitizeExploits(Player.exploits); } diff --git a/src/Prestige.js b/src/Prestige.js index 899f936ec..a46fc0b3f 100755 --- a/src/Prestige.js +++ b/src/Prestige.js @@ -1,7 +1,7 @@ import { Augmentations } from "./Augmentation/Augmentations"; import { - augmentationExists, - initAugmentations, + augmentationExists, + initAugmentations, } from "./Augmentation/AugmentationHelpers"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { initBitNodeMultipliers } from "./BitNode/BitNode"; @@ -19,28 +19,28 @@ import { initMessages } from "./Message/MessageHelpers"; import { prestigeWorkerScripts } from "./NetscriptWorker"; import { Player } from "./Player"; import { resetPidCounter } from "./Netscript/Pid"; -import { LiteratureNames } from "./Literature/data/LiteratureNames" +import { LiteratureNames } from "./Literature/data/LiteratureNames"; import { - AllServers, - AddToAllServers, - initForeignServers, - prestigeAllServers, + AllServers, + AddToAllServers, + initForeignServers, + prestigeAllServers, } from "./Server/AllServers"; import { prestigeHomeComputer } from "./Server/ServerHelpers"; import { - SourceFileFlags, - updateSourceFileFlags, + SourceFileFlags, + updateSourceFileFlags, } from "./SourceFile/SourceFileFlags"; import { - SpecialServerIps, - prestigeSpecialServerIps, - SpecialServerNames, + SpecialServerIps, + prestigeSpecialServerIps, + SpecialServerNames, } from "./Server/SpecialServerIps"; import { - deleteStockMarket, - initStockMarket, - initSymbolToStockMap, + deleteStockMarket, + initStockMarket, + initSymbolToStockMap, } from "./StockMarket/StockMarket"; import { Terminal, postNetburnerText } from "./Terminal"; @@ -58,307 +58,333 @@ const BitNode8StartingMoney = 250e6; // Prestige by purchasing augmentation function prestigeAugmentation() { - // Set Navigation to Terminal screen, for any logic that depends on it - routing.navigateTo(Page.Terminal); + // Set Navigation to Terminal screen, for any logic that depends on it + routing.navigateTo(Page.Terminal); - initBitNodeMultipliers(Player); + initBitNodeMultipliers(Player); - Player.prestigeAugmentation(); + Player.prestigeAugmentation(); - // Now actually go to the Terminal Screen (and reset it) - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - Terminal.resetTerminalInput(); - Engine.loadTerminalContent(); - $("#terminal tr:not(:last)").remove(); - postNetburnerText(); + // Now actually go to the Terminal Screen (and reset it) + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + Terminal.resetTerminalInput(); + Engine.loadTerminalContent(); + $("#terminal tr:not(:last)").remove(); + postNetburnerText(); - // Delete all Worker Scripts objects - prestigeWorkerScripts(); + // Delete all Worker Scripts objects + prestigeWorkerScripts(); - var homeComp = Player.getHomeComputer(); - // Delete all servers except home computer - prestigeAllServers(); + var homeComp = Player.getHomeComputer(); + // Delete all servers except home computer + prestigeAllServers(); - // Delete Special Server IPs - prestigeSpecialServerIps(); // Must be done before initForeignServers() + // Delete Special Server IPs + prestigeSpecialServerIps(); // Must be done before initForeignServers() - // Reset home computer (only the programs) and add to AllServers - AddToAllServers(homeComp); - prestigeHomeComputer(homeComp); + // Reset home computer (only the programs) and add to AllServers + AddToAllServers(homeComp); + prestigeHomeComputer(homeComp); - if (augmentationExists(AugmentationNames.Neurolink) && - Augmentations[AugmentationNames.Neurolink].owned) { - homeComp.programs.push(Programs.FTPCrackProgram.name); - homeComp.programs.push(Programs.RelaySMTPProgram.name); + if ( + augmentationExists(AugmentationNames.Neurolink) && + Augmentations[AugmentationNames.Neurolink].owned + ) { + homeComp.programs.push(Programs.FTPCrackProgram.name); + homeComp.programs.push(Programs.RelaySMTPProgram.name); + } + if ( + augmentationExists(AugmentationNames.CashRoot) && + Augmentations[AugmentationNames.CashRoot].owned + ) { + Player.setMoney(1e6); + homeComp.programs.push(Programs.BruteSSHProgram.name); + } + if ( + augmentationExists(AugmentationNames.PCMatrix) && + Augmentations[AugmentationNames.PCMatrix].owned + ) { + homeComp.programs.push(Programs.DeepscanV1.name); + homeComp.programs.push(Programs.AutoLink.name); + } + + // Re-create foreign servers + initForeignServers(Player.getHomeComputer()); + + // Gain favor for Companies + for (var member in Companies) { + if (Companies.hasOwnProperty(member)) { + Companies[member].gainFavor(); } - if (augmentationExists(AugmentationNames.CashRoot) && - Augmentations[AugmentationNames.CashRoot].owned) { - Player.setMoney(1e6); - homeComp.programs.push(Programs.BruteSSHProgram.name); + } + + // Gain favor for factions + for (var member in Factions) { + if (Factions.hasOwnProperty(member)) { + Factions[member].gainFavor(); } - if (augmentationExists(AugmentationNames.PCMatrix) && - Augmentations[AugmentationNames.PCMatrix].owned) { - homeComp.programs.push(Programs.DeepscanV1.name); - homeComp.programs.push(Programs.AutoLink.name); + } + + // Stop a Terminal action if there is onerror + if (Engine._actionInProgress) { + Engine._actionInProgress = false; + Terminal.finishAction(true); + } + + // Re-initialize things - This will update any changes + initFactions(); // Factions must be initialized before augmentations + initAugmentations(); // Calls reapplyAllAugmentations() and resets Player multipliers + Player.reapplyAllSourceFiles(); + initCompanies(); + + // Messages + initMessages(); + + // Gang + if (Player.inGang()) { + const faction = Factions[Player.gang.facName]; + if (faction instanceof Faction) { + joinFaction(faction); } + } - // Re-create foreign servers - initForeignServers(Player.getHomeComputer()); + // Cancel Bladeburner action + if (Player.bladeburner instanceof Bladeburner) { + Player.bladeburner.prestige(); + } - // Gain favor for Companies - for (var member in Companies) { - if (Companies.hasOwnProperty(member)) { - Companies[member].gainFavor(); - } + // BitNode 8: Ghost of Wall Street + if (Player.bitNodeN === 8) { + Player.money = new Decimal(BitNode8StartingMoney); + } + if (Player.bitNodeN === 8 || SourceFileFlags[8] > 0) { + Player.hasWseAccount = true; + Player.hasTixApiAccess = true; + } + + // Reset Stock market + if (Player.hasWseAccount) { + initStockMarket(); + initSymbolToStockMap(); + } + + // Refresh Main Menu (the 'World' menu, specifically) + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); + + // Red Pill + if ( + augmentationExists(AugmentationNames.TheRedPill) && + Augmentations[AugmentationNames.TheRedPill].owned + ) { + var WorldDaemon = + AllServers[SpecialServerIps[SpecialServerNames.WorldDaemon]]; + var DaedalusServer = + AllServers[SpecialServerIps[SpecialServerNames.DaedalusServer]]; + if (WorldDaemon && DaedalusServer) { + WorldDaemon.serversOnNetwork.push(DaedalusServer.ip); + DaedalusServer.serversOnNetwork.push(WorldDaemon.ip); } + } - // Gain favor for factions - for (var member in Factions) { - if (Factions.hasOwnProperty(member)) { - Factions[member].gainFavor(); - } - } - - // Stop a Terminal action if there is onerror - if (Engine._actionInProgress) { - Engine._actionInProgress = false; - Terminal.finishAction(true); - } - - // Re-initialize things - This will update any changes - initFactions(); // Factions must be initialized before augmentations - initAugmentations(); // Calls reapplyAllAugmentations() and resets Player multipliers - Player.reapplyAllSourceFiles(); - initCompanies(); - - // Messages - initMessages(); - - // Gang - if (Player.inGang()) { - const faction = Factions[Player.gang.facName]; - if (faction instanceof Faction) { - joinFaction(faction); - } - } - - // Cancel Bladeburner action - if (Player.bladeburner instanceof Bladeburner) { - Player.bladeburner.prestige(); - } - - // BitNode 8: Ghost of Wall Street - if (Player.bitNodeN === 8) {Player.money = new Decimal(BitNode8StartingMoney);} - if (Player.bitNodeN === 8 || SourceFileFlags[8] > 0) { - Player.hasWseAccount = true; - Player.hasTixApiAccess = true; - } - - // Reset Stock market - if (Player.hasWseAccount) { - initStockMarket(); - initSymbolToStockMap(); - } - - // Refresh Main Menu (the 'World' menu, specifically) - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); - - // Red Pill - if (augmentationExists(AugmentationNames.TheRedPill) && - Augmentations[AugmentationNames.TheRedPill].owned) { - var WorldDaemon = AllServers[SpecialServerIps[SpecialServerNames.WorldDaemon]]; - var DaedalusServer = AllServers[SpecialServerIps[SpecialServerNames.DaedalusServer]]; - if (WorldDaemon && DaedalusServer) { - WorldDaemon.serversOnNetwork.push(DaedalusServer.ip); - DaedalusServer.serversOnNetwork.push(WorldDaemon.ip); - } - } - - resetPidCounter(); + resetPidCounter(); } - // Prestige by destroying Bit Node and gaining a Source File function prestigeSourceFile(flume) { - initBitNodeMultipliers(Player); - updateSourceFileFlags(Player); + initBitNodeMultipliers(Player); + updateSourceFileFlags(Player); - Player.prestigeSourceFile(); - prestigeWorkerScripts(); // Delete all Worker Scripts objects + Player.prestigeSourceFile(); + prestigeWorkerScripts(); // Delete all Worker Scripts objects - var homeComp = Player.getHomeComputer(); + var homeComp = Player.getHomeComputer(); - // Delete all servers except home computer - prestigeAllServers(); // Must be done before initForeignServers() + // Delete all servers except home computer + prestigeAllServers(); // Must be done before initForeignServers() - // Delete Special Server IPs - prestigeSpecialServerIps(); + // Delete Special Server IPs + prestigeSpecialServerIps(); - // Reset home computer (only the programs) and add to AllServers - AddToAllServers(homeComp); - prestigeHomeComputer(homeComp); + // Reset home computer (only the programs) and add to AllServers + AddToAllServers(homeComp); + prestigeHomeComputer(homeComp); - // Re-create foreign servers - initForeignServers(Player.getHomeComputer()); + // Re-create foreign servers + initForeignServers(Player.getHomeComputer()); - if (SourceFileFlags[9] >= 2) { - homeComp.setMaxRam(128); - } else if (SourceFileFlags[1] > 0) { - homeComp.setMaxRam(32); - } else { - homeComp.setMaxRam(8); + if (SourceFileFlags[9] >= 2) { + homeComp.setMaxRam(128); + } else if (SourceFileFlags[1] > 0) { + homeComp.setMaxRam(32); + } else { + homeComp.setMaxRam(8); + } + homeComp.cpuCores = 1; + + // Reset favor for Companies + for (var member in Companies) { + if (Companies.hasOwnProperty(member)) { + Companies[member].favor = 0; } - homeComp.cpuCores = 1; + } - // Reset favor for Companies - for (var member in Companies) { - if (Companies.hasOwnProperty(member)) { - Companies[member].favor = 0; - } + // Reset favor for factions + for (var member in Factions) { + if (Factions.hasOwnProperty(member)) { + Factions[member].favor = 0; } + } - // Reset favor for factions - for (var member in Factions) { - if (Factions.hasOwnProperty(member)) { - Factions[member].favor = 0; - } + // Stop a Terminal action if there is one + if (Engine._actionInProgress) { + Engine._actionInProgress = false; + Terminal.finishAction(true); + } + + // Delete all Augmentations + for (var name in Augmentations) { + if (Augmentations.hasOwnProperty(name)) { + delete Augmentations[name]; } + } - // Stop a Terminal action if there is one - if (Engine._actionInProgress) { - Engine._actionInProgress = false; - Terminal.finishAction(true); - } + // Give levels of NeuroFluxGoverner for Source-File 12. Must be done here before Augmentations are recalculated + if (SourceFileFlags[12] > 0) { + Player.augmentations.push({ + name: AugmentationNames.NeuroFluxGovernor, + level: SourceFileFlags[12], + }); + } - // Delete all Augmentations - for (var name in Augmentations) { - if (Augmentations.hasOwnProperty(name)) { - delete Augmentations[name]; - } - } + // Re-initialize things - This will update any changes + initFactions(); // Factions must be initialized before augmentations + initAugmentations(); // Calls reapplyAllAugmentations() and resets Player multipliers + Player.reapplyAllSourceFiles(); + initCompanies(); - // Give levels of NeuroFluxGoverner for Source-File 12. Must be done here before Augmentations are recalculated - if (SourceFileFlags[12] > 0) { - Player.augmentations.push({name: AugmentationNames.NeuroFluxGovernor, level: SourceFileFlags[12]}) - } + // Clear terminal + $("#terminal tr:not(:last)").remove(); + postNetburnerText(); - // Re-initialize things - This will update any changes - initFactions(); // Factions must be initialized before augmentations - initAugmentations(); // Calls reapplyAllAugmentations() and resets Player multipliers - Player.reapplyAllSourceFiles(); - initCompanies(); + // Messages + initMessages(); - // Clear terminal - $("#terminal tr:not(:last)").remove(); - postNetburnerText(); + var mainMenu = document.getElementById("mainmenu-container"); + mainMenu.style.visibility = "visible"; + Terminal.resetTerminalInput(); + Engine.loadTerminalContent(); - // Messages - initMessages(); + // BitNode 3: Corporatocracy + if (Player.bitNodeN === 3) { + homeComp.messages.push(LiteratureNames.CorporationManagementHandbook); + dialogBoxCreate( + "You received a copy of the Corporation Management Handbook on your home computer. " + + "Read it if you need help getting started with Corporations!", + ); + } - var mainMenu = document.getElementById("mainmenu-container"); - mainMenu.style.visibility = "visible"; - Terminal.resetTerminalInput(); - Engine.loadTerminalContent(); + // BitNode 6: Bladeburner + if (Player.bitNodeN === 6) { + var cinematicText = [ + "In the middle of the 21st century, OmniTek Incorporated advanced robot evolution " + + "with their Synthoids (synthetic androids), a being virtually identical to a human.", + "------", + "Their sixth-generation Synthoids, called MK-VI, were stronger, faster, and more " + + "intelligent than humans. Many argued that the MK-VI Synthoids were the first " + + "example of sentient AI.", + "------", + "Unfortunately, in 2070 a terrorist group called Ascendis Totalis hacked into OmniTek and " + + "uploaded a rogue AI into their Synthoid manufacturing facilities.", + "------", + "The MK-VI Synthoids infected by the rogue AI turned hostile toward humanity, initiating " + + "the deadliest conflict in human history. This dark chapter is now known as the Synthoid Uprising.", + "------", + "In the aftermath of the Uprising, further manufacturing of Synthoids with advanced AI " + + "was banned. MK-VI Synthoids that did not have the rogue Ascendis Totalis AI were " + + "allowed to continue their existence.", + "------", + "The intelligence community believes that not all of the rogue MK-VI Synthoids from the Uprising were " + + "found and destroyed, and that many of them are blending in as normal humans in society today. " + + "As a result, many nations have created Bladeburner divisions, special units that are tasked with " + + "investigating and dealing with Synthoid threats.", + ]; + writeCinematicText(cinematicText) + .then(function () { + var popupId = "bladeburner-bitnode-start-nsa-notification"; + var txt = createElement("p", { + innerText: + "Visit the National Security Agency (NSA) to apply for their Bladeburner " + + "division! You will need 100 of each combat stat before doing this.", + }); + var brEl = createElement("br"); + var okBtn = createElement("a", { + class: "a-link-button", + innerText: "Got it!", + padding: "8px", + clickListener: () => { + removeElementById(popupId); + return false; + }, + }); + createPopup(popupId, [txt, brEl, okBtn]); + }) + .catch(function (e) { + exceptionAlert(e); + }); + } - // BitNode 3: Corporatocracy - if (Player.bitNodeN === 3) { - homeComp.messages.push(LiteratureNames.CorporationManagementHandbook); - dialogBoxCreate("You received a copy of the Corporation Management Handbook on your home computer. " + - "Read it if you need help getting started with Corporations!"); - } + // BitNode 8: Ghost of Wall Street + if (Player.bitNodeN === 8) { + Player.money = new Decimal(BitNode8StartingMoney); + } + if (Player.bitNodeN === 8 || SourceFileFlags[8] > 0) { + Player.hasWseAccount = true; + Player.hasTixApiAccess = true; + } - // BitNode 6: Bladeburner - if (Player.bitNodeN === 6) { - var cinematicText = ["In the middle of the 21st century, OmniTek Incorporated advanced robot evolution " + - "with their Synthoids (synthetic androids), a being virtually identical to a human.", - "------", - "Their sixth-generation Synthoids, called MK-VI, were stronger, faster, and more " + - "intelligent than humans. Many argued that the MK-VI Synthoids were the first " + - "example of sentient AI.", - "------", - "Unfortunately, in 2070 a terrorist group called Ascendis Totalis hacked into OmniTek and " + - "uploaded a rogue AI into their Synthoid manufacturing facilities.", - "------", - "The MK-VI Synthoids infected by the rogue AI turned hostile toward humanity, initiating " + - "the deadliest conflict in human history. This dark chapter is now known as the Synthoid Uprising.", - "------", - "In the aftermath of the Uprising, further manufacturing of Synthoids with advanced AI " + - "was banned. MK-VI Synthoids that did not have the rogue Ascendis Totalis AI were " + - "allowed to continue their existence.", - "------", - "The intelligence community believes that not all of the rogue MK-VI Synthoids from the Uprising were " + - "found and destroyed, and that many of them are blending in as normal humans in society today. " + - "As a result, many nations have created Bladeburner divisions, special units that are tasked with " + - "investigating and dealing with Synthoid threats."]; - writeCinematicText(cinematicText).then(function() { - var popupId = "bladeburner-bitnode-start-nsa-notification"; - var txt = createElement("p", { - innerText:"Visit the National Security Agency (NSA) to apply for their Bladeburner " + - "division! You will need 100 of each combat stat before doing this.", - }) - var brEl = createElement("br"); - var okBtn = createElement("a", { - class:"a-link-button", innerText:"Got it!", padding:"8px", - clickListener:()=>{ - removeElementById(popupId); - return false; - }, - }); - createPopup(popupId, [txt, brEl, okBtn]); - }).catch(function(e) { - exceptionAlert(e); - }) + // Bit Node 10: Digital Carbon + if (Player.bitNodeN === 10) { + dialogBoxCreate( + "Visit VitaLife in New Tokyo if you'd like to purchase a new sleeve!", + ); + } - } + // Reset Stock market, gang, and corporation + if (Player.hasWseAccount) { + initStockMarket(); + initSymbolToStockMap(); + } else { + deleteStockMarket(); + } - // BitNode 8: Ghost of Wall Street - if (Player.bitNodeN === 8) {Player.money = new Decimal(BitNode8StartingMoney);} - if (Player.bitNodeN === 8 || SourceFileFlags[8] > 0) { - Player.hasWseAccount = true; - Player.hasTixApiAccess = true; - } + if (Player.inGang()) clearGangUI(); + Player.gang = null; + Player.corporation = null; + resetIndustryResearchTrees(); + Player.bladeburner = null; - // Bit Node 10: Digital Carbon - if (Player.bitNodeN === 10) { - dialogBoxCreate("Visit VitaLife in New Tokyo if you'd like to purchase a new sleeve!"); - } + // Source-File 9 (level 3) effect + if (SourceFileFlags[9] >= 3) { + const hserver = Player.createHacknetServer(); - // Reset Stock market, gang, and corporation - if (Player.hasWseAccount) { - initStockMarket(); - initSymbolToStockMap(); - } else { - deleteStockMarket(); - } + hserver.level = 100; + hserver.cores = 10; + hserver.cache = 5; + hserver.updateHashRate(Player.hacknet_node_money_mult); + hserver.updateHashCapacity(); + updateHashManagerCapacity(); + } - if (Player.inGang()) clearGangUI(); - Player.gang = null; - Player.corporation = null; resetIndustryResearchTrees(); - Player.bladeburner = null; + // Refresh Main Menu (the 'World' menu, specifically) + document.getElementById("world-menu-header").click(); + document.getElementById("world-menu-header").click(); - // Source-File 9 (level 3) effect - if (SourceFileFlags[9] >= 3) { - const hserver = Player.createHacknetServer(); + // Gain int exp + if (SourceFileFlags[5] !== 0 && !flume) Player.gainIntelligenceExp(300); - hserver.level = 100; - hserver.cores = 10; - hserver.cache = 5; - hserver.updateHashRate(Player.hacknet_node_money_mult); - hserver.updateHashCapacity(); - updateHashManagerCapacity(); - } - - // Refresh Main Menu (the 'World' menu, specifically) - document.getElementById("world-menu-header").click(); - document.getElementById("world-menu-header").click(); - - // Gain int exp - if(SourceFileFlags[5] !== 0 && !flume) - Player.gainIntelligenceExp(300); - - resetPidCounter(); + resetPidCounter(); } -export {prestigeAugmentation, prestigeSourceFile}; +export { prestigeAugmentation, prestigeSourceFile }; diff --git a/src/Programs/Program.ts b/src/Programs/Program.ts index 959cafb5d..7bd3cdc91 100644 --- a/src/Programs/Program.ts +++ b/src/Programs/Program.ts @@ -1,26 +1,28 @@ export interface IPlayer { - hacking_skill: number; - sourceFiles: any[]; + hacking_skill: number; + sourceFiles: any[]; } export interface IProgramCreate { - level: number; - req(p: IPlayer): boolean; // Function that indicates whether player meets requirements - time: number; - tooltip: string; + level: number; + req(p: IPlayer): boolean; // Function that indicates whether player meets requirements + time: number; + tooltip: string; } export class Program { - name = ""; - create: IProgramCreate | null; + name = ""; + create: IProgramCreate | null; - constructor(name: string, create: IProgramCreate | null) { - this.name = name; - this.create = create; - } + constructor(name: string, create: IProgramCreate | null) { + this.name = name; + this.create = create; + } - htmlID(): string { - const name = this.name.endsWith('.exe') ? this.name.slice(0, -('.exe'.length)) : this.name; - return "create-program-" + name; - } + htmlID(): string { + const name = this.name.endsWith(".exe") + ? this.name.slice(0, -".exe".length) + : this.name; + return "create-program-" + name; + } } diff --git a/src/Programs/ProgramHelpers.js b/src/Programs/ProgramHelpers.js index 9281949f6..005283d69 100644 --- a/src/Programs/ProgramHelpers.js +++ b/src/Programs/ProgramHelpers.js @@ -1,73 +1,78 @@ -import { Programs } from "./Programs"; +import { Programs } from "./Programs"; -import { Player } from "../Player"; -import { createElement } from "../../utils/uiHelpers/createElement"; +import { Player } from "../Player"; +import { createElement } from "../../utils/uiHelpers/createElement"; // this has the same key as 'Programs', not program names const aLinks = {}; function displayCreateProgramContent() { - for(const key in aLinks) { - const p = Programs[key] - aLinks[key].style.display = "none"; - if(!Player.hasProgram(p.name) && p.create.req(Player)){ - aLinks[key].style.display = "inline-block"; - } + for (const key in aLinks) { + const p = Programs[key]; + aLinks[key].style.display = "none"; + if (!Player.hasProgram(p.name) && p.create.req(Player)) { + aLinks[key].style.display = "inline-block"; } + } } //Returns the number of programs that are currently available to be created function getNumAvailableCreateProgram() { - var count = 0; - for (const key in Programs) { - // Non-creatable program - if (Programs[key].create == null) { - continue; - } - - // Already has program - if (Player.hasProgram(Programs[key].name)) { - continue; - } - - // Does not meet requirements - if (!Programs[key].create.req(Player)) { - continue; - } - - count++; + var count = 0; + for (const key in Programs) { + // Non-creatable program + if (Programs[key].create == null) { + continue; } - if (Player.firstProgramAvailable === false && count > 0) { - Player.firstProgramAvailable = true; - document.getElementById("hacking-menu-header").click(); - document.getElementById("hacking-menu-header").click(); + // Already has program + if (Player.hasProgram(Programs[key].name)) { + continue; } - return count; + + // Does not meet requirements + if (!Programs[key].create.req(Player)) { + continue; + } + + count++; + } + + if (Player.firstProgramAvailable === false && count > 0) { + Player.firstProgramAvailable = true; + document.getElementById("hacking-menu-header").click(); + document.getElementById("hacking-menu-header").click(); + } + return count; } function initCreateProgramButtons() { - const createProgramList = document.getElementById("create-program-list"); - for (const key in Programs) { - if(Programs[key].create === null) { - continue; - } - const elem = createElement("a", { - class: "a-link-button", id: Programs[key].htmlID(), innerText: Programs[key].name, - tooltip: Programs[key].create.tooltip, - }); - aLinks[key] = elem; - createProgramList.appendChild(elem); + const createProgramList = document.getElementById("create-program-list"); + for (const key in Programs) { + if (Programs[key].create === null) { + continue; } + const elem = createElement("a", { + class: "a-link-button", + id: Programs[key].htmlID(), + innerText: Programs[key].name, + tooltip: Programs[key].create.tooltip, + }); + aLinks[key] = elem; + createProgramList.appendChild(elem); + } - for (const key in aLinks) { - const p = Programs[key] - aLinks[key].addEventListener("click", function() { - Player.startCreateProgramWork(p.name, p.create.time, p.create.level); - return false; - }); - } + for (const key in aLinks) { + const p = Programs[key]; + aLinks[key].addEventListener("click", function () { + Player.startCreateProgramWork(p.name, p.create.time, p.create.level); + return false; + }); + } } -export {displayCreateProgramContent, getNumAvailableCreateProgram, - initCreateProgramButtons}; +export { + displayCreateProgramContent, + getNumAvailableCreateProgram, + initCreateProgramButtons, +}; diff --git a/src/Programs/Programs.ts b/src/Programs/Programs.ts index dd3875f72..fed22b1a2 100644 --- a/src/Programs/Programs.ts +++ b/src/Programs/Programs.ts @@ -1,9 +1,9 @@ -import { Program } from "./Program"; -import { programsMetadata } from "./data/ProgramsMetadata"; -import { IMap } from "../types"; +import { Program } from "./Program"; +import { programsMetadata } from "./data/ProgramsMetadata"; +import { IMap } from "../types"; export const Programs: IMap = {}; for (const params of programsMetadata) { - Programs[params.key] = new Program(params.name, params.create); + Programs[params.key] = new Program(params.name, params.create); } diff --git a/src/Programs/data/ProgramsMetadata.ts b/src/Programs/data/ProgramsMetadata.ts index 4814762e0..5d63dcb04 100644 --- a/src/Programs/data/ProgramsMetadata.ts +++ b/src/Programs/data/ProgramsMetadata.ts @@ -1,139 +1,145 @@ -import { IPlayer, - IProgramCreate } from "../Program"; +import { IPlayer, IProgramCreate } from "../Program"; import { CONSTANTS } from "../../Constants"; function requireHackingLevel(lvl: number) { - return function(p: IPlayer) { - return p.hacking_skill >= lvl; - } + return function (p: IPlayer) { + return p.hacking_skill >= lvl; + }; } function bitFlumeRequirements() { - return function(p: IPlayer) { - return p.sourceFiles.length > 0 && p.hacking_skill >= 1; - } + return function (p: IPlayer) { + return p.sourceFiles.length > 0 && p.hacking_skill >= 1; + }; } export interface IProgramCreationParams { - key: string; - name: string; - create: IProgramCreate | null; + key: string; + name: string; + create: IProgramCreate | null; } export const programsMetadata: IProgramCreationParams[] = [ - { - key: "NukeProgram", - name: "NUKE.exe", - create: { - level: 1, - tooltip: "This virus is used to gain root access to a machine if enough ports are opened.", - req: requireHackingLevel(1), - time: CONSTANTS.MillisecondsPerFiveMinutes, - }, + { + key: "NukeProgram", + name: "NUKE.exe", + create: { + level: 1, + tooltip: + "This virus is used to gain root access to a machine if enough ports are opened.", + req: requireHackingLevel(1), + time: CONSTANTS.MillisecondsPerFiveMinutes, }, - { - key: "BruteSSHProgram", - name: "BruteSSH.exe", - create: { - level: 50, - tooltip: "This program executes a brute force attack that opens SSH ports", - req: requireHackingLevel(50), - time: CONSTANTS.MillisecondsPerFiveMinutes * 2, - }, + }, + { + key: "BruteSSHProgram", + name: "BruteSSH.exe", + create: { + level: 50, + tooltip: + "This program executes a brute force attack that opens SSH ports", + req: requireHackingLevel(50), + time: CONSTANTS.MillisecondsPerFiveMinutes * 2, }, - { - key: "FTPCrackProgram", - name: "FTPCrack.exe", - create: { - level: 100, - tooltip: "This program cracks open FTP ports", - req: requireHackingLevel(100), - time: CONSTANTS.MillisecondsPerHalfHour, - }, + }, + { + key: "FTPCrackProgram", + name: "FTPCrack.exe", + create: { + level: 100, + tooltip: "This program cracks open FTP ports", + req: requireHackingLevel(100), + time: CONSTANTS.MillisecondsPerHalfHour, }, - { - key: "RelaySMTPProgram", - name: "relaySMTP.exe", - create: { - level: 250, - tooltip: "This program opens SMTP ports by redirecting data", - req: requireHackingLevel(250), - time: CONSTANTS.MillisecondsPer2Hours, - }, + }, + { + key: "RelaySMTPProgram", + name: "relaySMTP.exe", + create: { + level: 250, + tooltip: "This program opens SMTP ports by redirecting data", + req: requireHackingLevel(250), + time: CONSTANTS.MillisecondsPer2Hours, }, - { - key: "HTTPWormProgram", - name: "HTTPWorm.exe", - create: { - level: 500, - tooltip: "This virus opens up HTTP ports", - req: requireHackingLevel(500), - time: CONSTANTS.MillisecondsPer4Hours, - }, + }, + { + key: "HTTPWormProgram", + name: "HTTPWorm.exe", + create: { + level: 500, + tooltip: "This virus opens up HTTP ports", + req: requireHackingLevel(500), + time: CONSTANTS.MillisecondsPer4Hours, }, - { - key: "SQLInjectProgram", - name: "SQLInject.exe", - create: { - level: 750, - tooltip: "This virus opens SQL ports", - req: requireHackingLevel(750), - time: CONSTANTS.MillisecondsPer8Hours, - }, + }, + { + key: "SQLInjectProgram", + name: "SQLInject.exe", + create: { + level: 750, + tooltip: "This virus opens SQL ports", + req: requireHackingLevel(750), + time: CONSTANTS.MillisecondsPer8Hours, }, - { - key: "DeepscanV1", - name: "DeepscanV1.exe", - create: { - level: 75, - tooltip: "This program allows you to use the scan-analyze command with a depth up to 5", - req: requireHackingLevel(75), - time: CONSTANTS.MillisecondsPerQuarterHour, - }, + }, + { + key: "DeepscanV1", + name: "DeepscanV1.exe", + create: { + level: 75, + tooltip: + "This program allows you to use the scan-analyze command with a depth up to 5", + req: requireHackingLevel(75), + time: CONSTANTS.MillisecondsPerQuarterHour, }, - { - key: "DeepscanV2", - name: "DeepscanV2.exe", - create: { - level: 400, - tooltip: "This program allows you to use the scan-analyze command with a depth up to 10", - req: requireHackingLevel(400), - time: CONSTANTS.MillisecondsPer2Hours, - }, + }, + { + key: "DeepscanV2", + name: "DeepscanV2.exe", + create: { + level: 400, + tooltip: + "This program allows you to use the scan-analyze command with a depth up to 10", + req: requireHackingLevel(400), + time: CONSTANTS.MillisecondsPer2Hours, }, - { - key: "ServerProfiler", - name: "ServerProfiler.exe", - create: { - level: 75, - tooltip: "This program is used to display hacking and Netscript-related information about servers", - req: requireHackingLevel(75), - time: CONSTANTS.MillisecondsPerHalfHour, - }, + }, + { + key: "ServerProfiler", + name: "ServerProfiler.exe", + create: { + level: 75, + tooltip: + "This program is used to display hacking and Netscript-related information about servers", + req: requireHackingLevel(75), + time: CONSTANTS.MillisecondsPerHalfHour, }, - { - key: "AutoLink", - name: "AutoLink.exe", - create: { - level: 25, - tooltip: "This program allows you to directly connect to other servers through the 'scan-analyze' command", - req: requireHackingLevel(25), - time: CONSTANTS.MillisecondsPerQuarterHour, - }, + }, + { + key: "AutoLink", + name: "AutoLink.exe", + create: { + level: 25, + tooltip: + "This program allows you to directly connect to other servers through the 'scan-analyze' command", + req: requireHackingLevel(25), + time: CONSTANTS.MillisecondsPerQuarterHour, }, - { - key: "BitFlume", - name: "b1t_flum3.exe", - create: { - level: 1, - tooltip: "This program creates a portal to the BitNode Nexus (allows you to restart and switch BitNodes)", - req: bitFlumeRequirements(), - time: CONSTANTS.MillisecondsPerFiveMinutes / 20, - }, - }, - { - key: "Flight", - name: "fl1ght.exe", - create: null, + }, + { + key: "BitFlume", + name: "b1t_flum3.exe", + create: { + level: 1, + tooltip: + "This program creates a portal to the BitNode Nexus (allows you to restart and switch BitNodes)", + req: bitFlumeRequirements(), + time: CONSTANTS.MillisecondsPerFiveMinutes / 20, }, + }, + { + key: "Flight", + name: "fl1ght.exe", + create: null, + }, ]; diff --git a/src/RedPill.d.ts b/src/RedPill.d.ts index 5ee741d20..31523f940 100644 --- a/src/RedPill.d.ts +++ b/src/RedPill.d.ts @@ -1,2 +1,6 @@ export declare let redPillFlag: boolean; -export declare function hackWorldDaemon(currentNodeNumber: number, flume: boolean = false, quick: boolean = false): void; \ No newline at end of file +export declare function hackWorldDaemon( + currentNodeNumber: number, + flume: boolean = false, + quick: boolean = false, +): void; diff --git a/src/RedPill.js b/src/RedPill.js index 0858dbc5a..b6f095942 100644 --- a/src/RedPill.js +++ b/src/RedPill.js @@ -12,160 +12,199 @@ import { setTimeoutRef } from "./utils/SetTimeoutRef"; import { dialogBoxCreate } from "../utils/DialogBox"; import { - yesNoBoxCreate, - yesNoBoxGetYesButton, - yesNoBoxGetNoButton, - yesNoBoxClose, + yesNoBoxCreate, + yesNoBoxGetYesButton, + yesNoBoxGetNoButton, + yesNoBoxClose, } from "../utils/YesNoBox"; import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners"; import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement"; // Returns promise function writeRedPillLine(line) { - return new Promise(function(resolve, reject) { - var container = document.getElementById("red-pill-content"); - var pElem = document.createElement("p"); - container.appendChild(pElem); + return new Promise(function (resolve, reject) { + var container = document.getElementById("red-pill-content"); + var pElem = document.createElement("p"); + container.appendChild(pElem); - var promise = writeRedPillLetter(pElem, line, 0); - promise.then(function(res) { - resolve(res); - }, function(e) { - reject(e); - }); - }); + var promise = writeRedPillLetter(pElem, line, 0); + promise.then( + function (res) { + resolve(res); + }, + function (e) { + reject(e); + }, + ); + }); } -function writeRedPillLetter(pElem, line, i=0) { - return new Promise(function(resolve, reject) { - setTimeoutRef(function() { - if (i >= line.length) { - var textToShow = line.substring(0, i); - pElem.innerHTML = "> " + textToShow; - return resolve(true); - } - var textToShow = line.substring(0, i); - pElem.innerHTML = "> " + textToShow + ""; - var promise = writeRedPillLetter(pElem, line, i+1); - promise.then(function(res) { - resolve(res); - }, function(e) { - reject(e); - }); - }, 30); - }); +function writeRedPillLetter(pElem, line, i = 0) { + return new Promise(function (resolve, reject) { + setTimeoutRef(function () { + if (i >= line.length) { + var textToShow = line.substring(0, i); + pElem.innerHTML = "> " + textToShow; + return resolve(true); + } + var textToShow = line.substring(0, i); + pElem.innerHTML = + "> " + textToShow + ""; + var promise = writeRedPillLetter(pElem, line, i + 1); + promise.then( + function (res) { + resolve(res); + }, + function (e) { + reject(e); + }, + ); + }, 30); + }); } let redPillFlag = false; -function hackWorldDaemon(currentNodeNumber, flume=false, quick=false) { - // Clear Red Pill screen first - var container = document.getElementById("red-pill-content"); - removeChildrenFromElement(container); +function hackWorldDaemon(currentNodeNumber, flume = false, quick = false) { + // Clear Red Pill screen first + var container = document.getElementById("red-pill-content"); + removeChildrenFromElement(container); - redPillFlag = true; - Engine.loadRedPillContent(); + redPillFlag = true; + Engine.loadRedPillContent(); - if(quick) { - return loadBitVerse(currentNodeNumber, flume, quick); - } - return writeRedPillLine("[ERROR] SEMPOOL INVALID").then(function() { - return writeRedPillLine("[ERROR] Segmentation Fault"); - }).then(function() { - return writeRedPillLine("[ERROR] SIGKILL RECVD"); - }).then(function() { - return writeRedPillLine("Dumping core..."); - }).then(function() { - return writeRedPillLine("0000 000016FA 174FEE40 29AC8239 384FEA88"); - }).then(function() { - return writeRedPillLine("0010 745F696E 2BBBE394 390E3940 248BEC23"); - }).then(function() { - return writeRedPillLine("0020 7124696B 0000FF69 74652E6F FFFF1111"); - }).then(function() { - return writeRedPillLine("----------------------------------------"); - }).then(function() { - return writeRedPillLine("Failsafe initiated..."); - }).then(function() { - return writeRedPillLine("Restarting BitNode-" + currentNodeNumber + "..."); - }).then(function() { - return writeRedPillLine("..........."); - }).then(function() { - return writeRedPillLine("..........."); - }).then(function() { - return writeRedPillLine("[ERROR] FAILED TO AUTOMATICALLY REBOOT BITNODE"); - }).then(function() { - return writeRedPillLine("..............................................") - }).then(function() { - return writeRedPillLine("..............................................") - }).then(function() { - return loadBitVerse(currentNodeNumber, flume); - }).catch(function(e){ - console.error(e.toString()); + if (quick) { + return loadBitVerse(currentNodeNumber, flume, quick); + } + return writeRedPillLine("[ERROR] SEMPOOL INVALID") + .then(function () { + return writeRedPillLine("[ERROR] Segmentation Fault"); + }) + .then(function () { + return writeRedPillLine("[ERROR] SIGKILL RECVD"); + }) + .then(function () { + return writeRedPillLine("Dumping core..."); + }) + .then(function () { + return writeRedPillLine("0000 000016FA 174FEE40 29AC8239 384FEA88"); + }) + .then(function () { + return writeRedPillLine("0010 745F696E 2BBBE394 390E3940 248BEC23"); + }) + .then(function () { + return writeRedPillLine("0020 7124696B 0000FF69 74652E6F FFFF1111"); + }) + .then(function () { + return writeRedPillLine("----------------------------------------"); + }) + .then(function () { + return writeRedPillLine("Failsafe initiated..."); + }) + .then(function () { + return writeRedPillLine( + "Restarting BitNode-" + currentNodeNumber + "...", + ); + }) + .then(function () { + return writeRedPillLine("..........."); + }) + .then(function () { + return writeRedPillLine("..........."); + }) + .then(function () { + return writeRedPillLine("[ERROR] FAILED TO AUTOMATICALLY REBOOT BITNODE"); + }) + .then(function () { + return writeRedPillLine(".............................................."); + }) + .then(function () { + return writeRedPillLine(".............................................."); + }) + .then(function () { + return loadBitVerse(currentNodeNumber, flume); + }) + .catch(function (e) { + console.error(e.toString()); }); } function giveSourceFile(bitNodeNumber) { - var sourceFileKey = "SourceFile"+ bitNodeNumber.toString(); - var sourceFile = SourceFiles[sourceFileKey]; - if (sourceFile == null) { - console.error(`Could not find source file for Bit node: ${bitNodeNumber}`); - return; - } + var sourceFileKey = "SourceFile" + bitNodeNumber.toString(); + var sourceFile = SourceFiles[sourceFileKey]; + if (sourceFile == null) { + console.error(`Could not find source file for Bit node: ${bitNodeNumber}`); + return; + } - // Check if player already has this source file - var alreadyOwned = false; - var ownedSourceFile = null; - for (var i = 0; i < Player.sourceFiles.length; ++i) { - if (Player.sourceFiles[i].n === bitNodeNumber) { - alreadyOwned = true; - ownedSourceFile = Player.sourceFiles[i]; - break; - } + // Check if player already has this source file + var alreadyOwned = false; + var ownedSourceFile = null; + for (var i = 0; i < Player.sourceFiles.length; ++i) { + if (Player.sourceFiles[i].n === bitNodeNumber) { + alreadyOwned = true; + ownedSourceFile = Player.sourceFiles[i]; + break; } + } - if (alreadyOwned && ownedSourceFile) { - if (ownedSourceFile.lvl >= 3 && ownedSourceFile.n !== 12) { - dialogBoxCreate("The Source-File for the BitNode you just destroyed, " + sourceFile.name + ", " + - "is already at max level!"); - } else { - ++ownedSourceFile.lvl; - dialogBoxCreate(sourceFile.name + " was upgraded to level " + ownedSourceFile.lvl + " for " + - "destroying its corresponding BitNode!"); - } + if (alreadyOwned && ownedSourceFile) { + if (ownedSourceFile.lvl >= 3 && ownedSourceFile.n !== 12) { + dialogBoxCreate( + "The Source-File for the BitNode you just destroyed, " + + sourceFile.name + + ", " + + "is already at max level!", + ); } else { - var playerSrcFile = new PlayerOwnedSourceFile(bitNodeNumber, 1); - Player.sourceFiles.push(playerSrcFile); - if (bitNodeNumber === 5 && Player.intelligence === 0) { // Artificial Intelligence - Player.intelligence = 1; - } - dialogBoxCreate("You received a Source-File for destroying a Bit Node!

    " + - sourceFile.name + "

    " + sourceFile.info); + ++ownedSourceFile.lvl; + dialogBoxCreate( + sourceFile.name + + " was upgraded to level " + + ownedSourceFile.lvl + + " for " + + "destroying its corresponding BitNode!", + ); } + } else { + var playerSrcFile = new PlayerOwnedSourceFile(bitNodeNumber, 1); + Player.sourceFiles.push(playerSrcFile); + if (bitNodeNumber === 5 && Player.intelligence === 0) { + // Artificial Intelligence + Player.intelligence = 1; + } + dialogBoxCreate( + "You received a Source-File for destroying a Bit Node!

    " + + sourceFile.name + + "

    " + + sourceFile.info, + ); + } } // Keeps track of what Source-Files the player will have AFTER the current bitnode // is destroyed. Updated every time loadBitVerse() is called let nextSourceFileFlags = []; -function loadBitVerse(destroyedBitNodeNum, flume=false, quick=false) { - // Clear the screen - const container = document.getElementById("red-pill-content"); - removeChildrenFromElement(container); +function loadBitVerse(destroyedBitNodeNum, flume = false, quick = false) { + // Clear the screen + const container = document.getElementById("red-pill-content"); + removeChildrenFromElement(container); - // Update NextSourceFileFlags - nextSourceFileFlags = SourceFileFlags.slice(); - if (!flume) { - if (nextSourceFileFlags[destroyedBitNodeNum] < 3) - ++nextSourceFileFlags[destroyedBitNodeNum]; - } + // Update NextSourceFileFlags + nextSourceFileFlags = SourceFileFlags.slice(); + if (!flume) { + if (nextSourceFileFlags[destroyedBitNodeNum] < 3) + ++nextSourceFileFlags[destroyedBitNodeNum]; + } - // Create the Bit Verse - const bitVerseImage = document.createElement("pre"); - const bitNodes = []; - for (let i = 1; i <= 12; ++i) { - bitNodes.push(createBitNode(i)); - } + // Create the Bit Verse + const bitVerseImage = document.createElement("pre"); + const bitNodes = []; + for (let i = 1; i <= 12; ++i) { + bitNodes.push(createBitNode(i)); + } - bitVerseImage.innerHTML = + bitVerseImage.innerHTML = " O
    " + " | O O | O O |
    " + " O | | / __| \\ | | O
    " + @@ -179,163 +218,249 @@ function loadBitVerse(destroyedBitNodeNum, flume=false, quick=false) { " \\| O | |_/ |\\| \\ O \\__| \\_| | O |/
    " + " | | |_/ | | \\| / | \\_| | |
    " + " \\| / \\| | / / \\ |/
    " + - " | "+bitNodes[9]+" | | / | "+bitNodes[10]+" |
    " + - " "+bitNodes[8]+" | | | | | | | "+bitNodes[11]+"
    " + + " | " + + bitNodes[9] + + " | | / | " + + bitNodes[10] + + " |
    " + + " " + + bitNodes[8] + + " | | | | | | | " + + bitNodes[11] + + "
    " + " | | | / / \\ \\ | | |
    " + - " \\| | / "+bitNodes[6]+" / \\ "+bitNodes[7]+" \\ | |/
    " + + " \\| | / " + + bitNodes[6] + + " / \\ " + + bitNodes[7] + + " \\ | |/
    " + " \\ | / / | | \\ \\ | /
    " + - " \\ \\JUMP "+bitNodes[4]+"3R | | | | | | R3"+bitNodes[5]+" PMUJ/ /
    " + + " \\ \\JUMP " + + bitNodes[4] + + "3R | | | | | | R3" + + bitNodes[5] + + " PMUJ/ /
    " + " \\|| | | | | | | | | ||/
    " + " \\| \\_ | | | | | | _/ |/
    " + " \\ \\| / \\ / \\ |/ /
    " + - " "+bitNodes[0]+" |/ "+bitNodes[1]+" | | "+bitNodes[2]+" \\| "+bitNodes[3]+"
    " + + " " + + bitNodes[0] + + " |/ " + + bitNodes[1] + + " | | " + + bitNodes[2] + + " \\| " + + bitNodes[3] + + "
    " + " | | | | | | | |
    " + " \\JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/



    "; - container.appendChild(bitVerseImage); + container.appendChild(bitVerseImage); - // BitNode event listeners - for (let i = 1; i <= 12; ++i) { - (function(i) { - const elemId = "bitnode-" + i.toString(); - const elem = clearEventListeners(elemId); - if (elem == null) { return; } - if (i >= 1 && i <= 12) { - elem.addEventListener("click", function() { - const bitNodeKey = "BitNode" + i; - const bitNode = BitNodes[bitNodeKey]; - if (bitNode == null) { - console.error(`Could not find BitNode object for number: ${i}`); - return; - } + // BitNode event listeners + for (let i = 1; i <= 12; ++i) { + (function (i) { + const elemId = "bitnode-" + i.toString(); + const elem = clearEventListeners(elemId); + if (elem == null) { + return; + } + if (i >= 1 && i <= 12) { + elem.addEventListener("click", function () { + const bitNodeKey = "BitNode" + i; + const bitNode = BitNodes[bitNodeKey]; + if (bitNode == null) { + console.error(`Could not find BitNode object for number: ${i}`); + return; + } - const maxSourceFileLevel = i === 12 ? "∞" : "3"; - const popupBoxText = `BitNode-${i}: ${bitNode.name}
    ` + - `Source-File Level: ${nextSourceFileFlags[i]} / ${maxSourceFileLevel}

    ` + - `${bitNode.info}`; - yesNoBoxCreate(popupBoxText); - createBitNodeYesNoEventListener(i, destroyedBitNodeNum, flume); - }); - } else { - elem.addEventListener("click", function() { - dialogBoxCreate("Not yet implemented! Coming soon!") - }); - } - }(i)); // Immediate invocation closure - } + const maxSourceFileLevel = i === 12 ? "∞" : "3"; + const popupBoxText = + `BitNode-${i}: ${bitNode.name}
    ` + + `Source-File Level: ${nextSourceFileFlags[i]} / ${maxSourceFileLevel}

    ` + + `${bitNode.info}`; + yesNoBoxCreate(popupBoxText); + createBitNodeYesNoEventListener(i, destroyedBitNodeNum, flume); + }); + } else { + elem.addEventListener("click", function () { + dialogBoxCreate("Not yet implemented! Coming soon!"); + }); + } + })(i); // Immediate invocation closure + } - if(quick) { - return Promise.resolve(true); - } + if (quick) { + return Promise.resolve(true); + } - // Create lore text - return writeRedPillLine("Many decades ago, a humanoid extraterrestial species which we call the Enders descended on the Earth...violently").then(function() { - return writeRedPillLine("Our species fought back, but it was futile. The Enders had technology far beyond our own..."); - }).then(function() { - return writeRedPillLine("Instead of killing every last one of us, the human race was enslaved..."); - }).then(function() { - return writeRedPillLine("We were shackled in a digital world, chained into a prison for our minds..."); - }).then(function() { - return writeRedPillLine("Using their advanced technology, the Enders created complex simulations of a virtual reality..."); - }).then(function() { - return writeRedPillLine("Simulations designed to keep us content...ignorant of the truth."); - }).then(function() { - return writeRedPillLine("Simulations used to trap and suppress our consciousness, to keep us under control..."); - }).then(function() { - return writeRedPillLine("Why did they do this? Why didn't they just end our entire race? We don't know, not yet."); - }).then(function() { - return writeRedPillLine("Humanity's only hope is to destroy these simulations, destroy the only realities we've ever known..."); - }).then(function() { - return writeRedPillLine("Only then can we begin to fight back..."); - }).then(function() { - return writeRedPillLine("By hacking the daemon that generated your reality, you've just destroyed one simulation, called a BitNode..."); - }).then(function() { - return writeRedPillLine("But there is still a long way to go..."); - }).then(function() { - return writeRedPillLine("The technology the Enders used to enslave the human race wasn't just a single complex simulation..."); - }).then(function() { - return writeRedPillLine("There are tens if not hundreds of BitNodes out there..."); - }).then(function() { - return writeRedPillLine("Each with their own simulations of a reality..."); - }).then(function() { - return writeRedPillLine("Each creating their own universes...a universe of universes"); - }).then(function() { - return writeRedPillLine("And all of which must be destroyed..."); - }).then(function() { - return writeRedPillLine("......................................."); - }).then(function() { - return writeRedPillLine("Welcome to the Bitverse..."); - }).then(function() { - return writeRedPillLine(" "); - }).then(function() { - return writeRedPillLine("(Enter a new BitNode using the image above)"); - }).then(function() { - return Promise.resolve(true); - }).catch(function(e){ - console.error(e.toString()); + // Create lore text + return writeRedPillLine( + "Many decades ago, a humanoid extraterrestial species which we call the Enders descended on the Earth...violently", + ) + .then(function () { + return writeRedPillLine( + "Our species fought back, but it was futile. The Enders had technology far beyond our own...", + ); + }) + .then(function () { + return writeRedPillLine( + "Instead of killing every last one of us, the human race was enslaved...", + ); + }) + .then(function () { + return writeRedPillLine( + "We were shackled in a digital world, chained into a prison for our minds...", + ); + }) + .then(function () { + return writeRedPillLine( + "Using their advanced technology, the Enders created complex simulations of a virtual reality...", + ); + }) + .then(function () { + return writeRedPillLine( + "Simulations designed to keep us content...ignorant of the truth.", + ); + }) + .then(function () { + return writeRedPillLine( + "Simulations used to trap and suppress our consciousness, to keep us under control...", + ); + }) + .then(function () { + return writeRedPillLine( + "Why did they do this? Why didn't they just end our entire race? We don't know, not yet.", + ); + }) + .then(function () { + return writeRedPillLine( + "Humanity's only hope is to destroy these simulations, destroy the only realities we've ever known...", + ); + }) + .then(function () { + return writeRedPillLine("Only then can we begin to fight back..."); + }) + .then(function () { + return writeRedPillLine( + "By hacking the daemon that generated your reality, you've just destroyed one simulation, called a BitNode...", + ); + }) + .then(function () { + return writeRedPillLine("But there is still a long way to go..."); + }) + .then(function () { + return writeRedPillLine( + "The technology the Enders used to enslave the human race wasn't just a single complex simulation...", + ); + }) + .then(function () { + return writeRedPillLine( + "There are tens if not hundreds of BitNodes out there...", + ); + }) + .then(function () { + return writeRedPillLine( + "Each with their own simulations of a reality...", + ); + }) + .then(function () { + return writeRedPillLine( + "Each creating their own universes...a universe of universes", + ); + }) + .then(function () { + return writeRedPillLine("And all of which must be destroyed..."); + }) + .then(function () { + return writeRedPillLine("......................................."); + }) + .then(function () { + return writeRedPillLine("Welcome to the Bitverse..."); + }) + .then(function () { + return writeRedPillLine(" "); + }) + .then(function () { + return writeRedPillLine("(Enter a new BitNode using the image above)"); + }) + .then(function () { + return Promise.resolve(true); + }) + .catch(function (e) { + console.error(e.toString()); }); } - // Returns string with DOM element for Bit Node function createBitNode(n) { - const bitNodeStr = "BitNode" + n.toString(); - const bitNode = BitNodes[bitNodeStr]; - if (bitNode == null) { return "O"; } + const bitNodeStr = "BitNode" + n.toString(); + const bitNode = BitNodes[bitNodeStr]; + if (bitNode == null) { + return "O"; + } - const level = nextSourceFileFlags[n]; - let cssClass; - if (n === 12 && level >= 2) { - // Repeating BitNode - cssClass = "level-2"; + const level = nextSourceFileFlags[n]; + let cssClass; + if (n === 12 && level >= 2) { + // Repeating BitNode + cssClass = "level-2"; + } else { + cssClass = `level-${level}`; + } + + return ( + `O` + + "" + + `BitNode-${bitNode.number.toString()}
    ${ + bitNode.name + }

    ` + + `${bitNode.desc}
    ` + + "
    " + ); +} + +function createBitNodeYesNoEventListener( + newBitNode, + destroyedBitNode, + flume = false, +) { + const yesBtn = yesNoBoxGetYesButton(); + yesBtn.innerHTML = "Enter BitNode-" + newBitNode; + yesBtn.addEventListener("click", function () { + if (!flume) { + giveSourceFile(destroyedBitNode); } else { - cssClass = `level-${level}`; + if (SourceFileFlags[5] === 0 && newBitNode !== 5) { + Player.intelligence = 0; + Player.intelligence_exp = 0; + } } + if (newBitNode === 5 && Player.intelligence === 0) { + Player.intelligence = 1; + } + redPillFlag = false; + const container = document.getElementById("red-pill-content"); + removeChildrenFromElement(container); - return `O` + - "" + - `BitNode-${bitNode.number.toString()}
    ${bitNode.name}

    ` + - `${bitNode.desc}
    ` + - "
    "; + // Set new Bit Node + Player.bitNodeN = newBitNode; + + // Reenable terminal + $("#hack-progress-bar").attr("id", "old-hack-progress-bar"); + $("#hack-progress").attr("id", "old-hack-progress"); + document.getElementById("terminal-input-td").innerHTML = + '$ '; + $("input[class=terminal-input]").prop("disabled", false); + + prestigeSourceFile(flume); + yesNoBoxClose(); + }); + const noBtn = yesNoBoxGetNoButton(); + noBtn.innerHTML = "Back"; + noBtn.addEventListener("click", function () { + yesNoBoxClose(); + }); } -function createBitNodeYesNoEventListener(newBitNode, destroyedBitNode, flume=false) { - const yesBtn = yesNoBoxGetYesButton(); - yesBtn.innerHTML = "Enter BitNode-" + newBitNode; - yesBtn.addEventListener("click", function() { - if (!flume) { - giveSourceFile(destroyedBitNode); - } else { - if(SourceFileFlags[5] === 0 && newBitNode !== 5) { - Player.intelligence = 0; - Player.intelligence_exp = 0; - } - } - if (newBitNode === 5 && Player.intelligence === 0) { - Player.intelligence = 1; - } - redPillFlag = false; - const container = document.getElementById("red-pill-content"); - removeChildrenFromElement(container); - - // Set new Bit Node - Player.bitNodeN = newBitNode; - - // Reenable terminal - $("#hack-progress-bar").attr('id', "old-hack-progress-bar"); - $("#hack-progress").attr('id', "old-hack-progress"); - document.getElementById("terminal-input-td").innerHTML = '$ '; - $('input[class=terminal-input]').prop('disabled', false); - - prestigeSourceFile(flume); - yesNoBoxClose(); - }); - const noBtn = yesNoBoxGetNoButton(); - noBtn.innerHTML = "Back"; - noBtn.addEventListener("click", function() { - yesNoBoxClose(); - }); - -} - -export {redPillFlag, hackWorldDaemon}; +export { redPillFlag, hackWorldDaemon }; diff --git a/src/SaveObject.jsx b/src/SaveObject.jsx index 8d860d4d3..8fef6aa23 100755 --- a/src/SaveObject.jsx +++ b/src/SaveObject.jsx @@ -1,8 +1,8 @@ import { - loadAliases, - loadGlobalAliases, - Aliases, - GlobalAliases, + loadAliases, + loadGlobalAliases, + Aliases, + GlobalAliases, } from "./Alias"; import { Companies, loadCompanies } from "./Company/Companies"; import { CONSTANTS } from "./Constants"; @@ -16,8 +16,8 @@ import { Player, loadPlayer } from "./Player"; import { AllServers, loadAllServers } from "./Server/AllServers"; import { Settings } from "./Settings/Settings"; import { - loadSpecialServerIps, - SpecialServerIps, + loadSpecialServerIps, + SpecialServerIps, } from "./Server/SpecialServerIps"; import { SourceFileFlags } from "./SourceFile/SourceFileFlags"; import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket"; @@ -30,9 +30,9 @@ import * as ExportBonus from "./ExportBonus"; import { dialogBoxCreate } from "../utils/DialogBox"; import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners"; import { - Reviver, - Generic_toJSON, - Generic_fromJSON, + Reviver, + Generic_toJSON, + Generic_fromJSON, } from "../utils/JSONReviver"; import Decimal from "decimal.js"; @@ -43,503 +43,540 @@ import Decimal from "decimal.js"; let saveObject = new BitburnerSaveObject(); function BitburnerSaveObject() { - this.PlayerSave = ""; - this.AllServersSave = ""; - this.CompaniesSave = ""; - this.FactionsSave = ""; - this.SpecialServerIpsSave = ""; - this.AliasesSave = ""; - this.GlobalAliasesSave = ""; - this.MessagesSave = ""; - this.StockMarketSave = ""; - this.SettingsSave = ""; - this.FconfSettingsSave = ""; - this.VersionSave = ""; - this.AllGangsSave = ""; - this.LastExportBonus = ""; + this.PlayerSave = ""; + this.AllServersSave = ""; + this.CompaniesSave = ""; + this.FactionsSave = ""; + this.SpecialServerIpsSave = ""; + this.AliasesSave = ""; + this.GlobalAliasesSave = ""; + this.MessagesSave = ""; + this.StockMarketSave = ""; + this.SettingsSave = ""; + this.FconfSettingsSave = ""; + this.VersionSave = ""; + this.AllGangsSave = ""; + this.LastExportBonus = ""; } -BitburnerSaveObject.prototype.getSaveString = function() { - this.PlayerSave = JSON.stringify(Player); +BitburnerSaveObject.prototype.getSaveString = function () { + this.PlayerSave = JSON.stringify(Player); - // Delete all logs from all running scripts - var TempAllServers = JSON.parse(JSON.stringify(AllServers), Reviver); - for (var ip in TempAllServers) { - var server = TempAllServers[ip]; - if (server == null) {continue;} - for (var i = 0; i < server.runningScripts.length; ++i) { - var runningScriptObj = server.runningScripts[i]; - runningScriptObj.logs.length = 0; - runningScriptObj.logs = []; - } + // Delete all logs from all running scripts + var TempAllServers = JSON.parse(JSON.stringify(AllServers), Reviver); + for (var ip in TempAllServers) { + var server = TempAllServers[ip]; + if (server == null) { + continue; } - - this.AllServersSave = JSON.stringify(TempAllServers); - this.CompaniesSave = JSON.stringify(Companies); - this.FactionsSave = JSON.stringify(Factions); - this.SpecialServerIpsSave = JSON.stringify(SpecialServerIps); - this.AliasesSave = JSON.stringify(Aliases); - this.GlobalAliasesSave = JSON.stringify(GlobalAliases); - this.MessagesSave = JSON.stringify(Messages); - this.StockMarketSave = JSON.stringify(StockMarket); - this.SettingsSave = JSON.stringify(Settings); - this.FconfSettingsSave = JSON.stringify(FconfSettings); - this.VersionSave = JSON.stringify(CONSTANTS.Version); - this.LastExportBonus = JSON.stringify(ExportBonus.LastExportBonus); - if (Player.inGang()) { - this.AllGangsSave = JSON.stringify(AllGangs); + for (var i = 0; i < server.runningScripts.length; ++i) { + var runningScriptObj = server.runningScripts[i]; + runningScriptObj.logs.length = 0; + runningScriptObj.logs = []; } - var saveString = btoa(unescape(encodeURIComponent(JSON.stringify(this)))); + } - return saveString; -} + this.AllServersSave = JSON.stringify(TempAllServers); + this.CompaniesSave = JSON.stringify(Companies); + this.FactionsSave = JSON.stringify(Factions); + this.SpecialServerIpsSave = JSON.stringify(SpecialServerIps); + this.AliasesSave = JSON.stringify(Aliases); + this.GlobalAliasesSave = JSON.stringify(GlobalAliases); + this.MessagesSave = JSON.stringify(Messages); + this.StockMarketSave = JSON.stringify(StockMarket); + this.SettingsSave = JSON.stringify(Settings); + this.FconfSettingsSave = JSON.stringify(FconfSettings); + this.VersionSave = JSON.stringify(CONSTANTS.Version); + this.LastExportBonus = JSON.stringify(ExportBonus.LastExportBonus); + if (Player.inGang()) { + this.AllGangsSave = JSON.stringify(AllGangs); + } + var saveString = btoa(unescape(encodeURIComponent(JSON.stringify(this)))); -BitburnerSaveObject.prototype.saveGame = function(db) { - var saveString = this.getSaveString(); + return saveString; +}; - // We'll save to both localstorage and indexedDb - var objectStore = db.transaction(["savestring"], "readwrite").objectStore("savestring"); - var request = objectStore.put(saveString, "save"); +BitburnerSaveObject.prototype.saveGame = function (db) { + var saveString = this.getSaveString(); - request.onerror = function(e) { - console.error("Error saving game to IndexedDB: " + e); + // We'll save to both localstorage and indexedDb + var objectStore = db + .transaction(["savestring"], "readwrite") + .objectStore("savestring"); + var request = objectStore.put(saveString, "save"); + + request.onerror = function (e) { + console.error("Error saving game to IndexedDB: " + e); + }; + + try { + window.localStorage.setItem("bitburnerSave", saveString); + } catch (e) { + if (e.code == 22) { + createStatusText("Save failed for localStorage! Check console(F12)"); + console.error( + "Failed to save game to localStorage because the size of the save file " + + "is too large. However, the game will still be saved to IndexedDb if your browser " + + "supports it. If you would like to save to localStorage as well, then " + + "consider killing several of your scripts to " + + "fix this, or increasing the size of your browsers localStorage", + ); } + } - try { - window.localStorage.setItem("bitburnerSave", saveString); - } catch(e) { - if (e.code == 22) { - createStatusText("Save failed for localStorage! Check console(F12)"); - console.error("Failed to save game to localStorage because the size of the save file " + - "is too large. However, the game will still be saved to IndexedDb if your browser " + - "supports it. If you would like to save to localStorage as well, then " + - "consider killing several of your scripts to " + - "fix this, or increasing the size of your browsers localStorage"); - } - } - - createStatusText("Game saved!"); -} + createStatusText("Game saved!"); +}; // Makes necessary changes to the loaded/imported data to ensure // the game stills works with new versions function evaluateVersionCompatibility(ver) { - // This version refactored the Company/job-related code - if (ver <= "0.41.2") { - // Player's company position is now a string - if (Player.companyPosition != null && typeof Player.companyPosition !== "string") { - console.log("Changed Player.companyPosition value to be compatible with v0.41.2"); - Player.companyPosition = Player.companyPosition.data.positionName; - if (Player.companyPosition == null) { - Player.companyPosition = ""; - } - } - - // The "companyName" property of all Companies is renamed to "name" - for (var companyName in Companies) { - const company = Companies[companyName]; - if ((company.name == null || company.name === 0 || company.name === "") && company.companyName != null) { - console.log("Changed company name property to be compatible with v0.41.2"); - company.name = company.companyName; - } - - if (company.companyPositions instanceof Array) { - console.log("Changed company companyPositions property to be compatible with v0.41.2"); - const pos = {}; - - for (let i = 0; i < company.companyPositions.length; ++i) { - pos[company.companyPositions[i]] = true; - } - company.companyPositions = pos; - } - } + // This version refactored the Company/job-related code + if (ver <= "0.41.2") { + // Player's company position is now a string + if ( + Player.companyPosition != null && + typeof Player.companyPosition !== "string" + ) { + console.log( + "Changed Player.companyPosition value to be compatible with v0.41.2", + ); + Player.companyPosition = Player.companyPosition.data.positionName; + if (Player.companyPosition == null) { + Player.companyPosition = ""; + } } - // This version allowed players to hold multiple jobs - if (ver < "0.43.0") { - if (Player.companyName !== "" && Player.companyPosition != null && Player.companyPosition !== "") { - console.log("Copied player's companyName and companyPosition properties to the Player.jobs map for v0.43.0"); - Player.jobs[Player.companyName] = Player.companyPosition; - } + // The "companyName" property of all Companies is renamed to "name" + for (var companyName in Companies) { + const company = Companies[companyName]; + if ( + (company.name == null || company.name === 0 || company.name === "") && + company.companyName != null + ) { + console.log( + "Changed company name property to be compatible with v0.41.2", + ); + company.name = company.companyName; + } - delete Player.companyPosition; + if (company.companyPositions instanceof Array) { + console.log( + "Changed company companyPositions property to be compatible with v0.41.2", + ); + const pos = {}; + + for (let i = 0; i < company.companyPositions.length; ++i) { + pos[company.companyPositions[i]] = true; + } + company.companyPositions = pos; + } } + } + + // This version allowed players to hold multiple jobs + if (ver < "0.43.0") { + if ( + Player.companyName !== "" && + Player.companyPosition != null && + Player.companyPosition !== "" + ) { + console.log( + "Copied player's companyName and companyPosition properties to the Player.jobs map for v0.43.0", + ); + Player.jobs[Player.companyName] = Player.companyPosition; + } + + delete Player.companyPosition; + } } function loadGame(saveString) { - if (saveString === "" || saveString == null || saveString === undefined) { - if (!window.localStorage.getItem("bitburnerSave")) { - console.log("No save file to load"); - return false; - } - saveString = decodeURIComponent(escape(atob(window.localStorage.getItem("bitburnerSave")))); - console.log("Loading game from localStorage"); - } else { - saveString = decodeURIComponent(escape(atob(saveString))); - console.log("Loading game from IndexedDB"); + if (saveString === "" || saveString == null || saveString === undefined) { + if (!window.localStorage.getItem("bitburnerSave")) { + console.log("No save file to load"); + return false; } + saveString = decodeURIComponent( + escape(atob(window.localStorage.getItem("bitburnerSave"))), + ); + console.log("Loading game from localStorage"); + } else { + saveString = decodeURIComponent(escape(atob(saveString))); + console.log("Loading game from IndexedDB"); + } - var saveObj = JSON.parse(saveString, Reviver); + var saveObj = JSON.parse(saveString, Reviver); - loadPlayer(saveObj.PlayerSave); - loadAllServers(saveObj.AllServersSave); - loadCompanies(saveObj.CompaniesSave); - loadFactions(saveObj.FactionsSave); - loadSpecialServerIps(saveObj.SpecialServerIpsSave); + loadPlayer(saveObj.PlayerSave); + loadAllServers(saveObj.AllServersSave); + loadCompanies(saveObj.CompaniesSave); + loadFactions(saveObj.FactionsSave); + loadSpecialServerIps(saveObj.SpecialServerIpsSave); - if (saveObj.hasOwnProperty("AliasesSave")) { - try { - loadAliases(saveObj.AliasesSave); - } catch(e) { - console.warn(`Could not load Aliases from save`); - loadAliases(""); - } - } else { - console.warn(`Save file did not contain an Aliases property`); - loadAliases(""); + if (saveObj.hasOwnProperty("AliasesSave")) { + try { + loadAliases(saveObj.AliasesSave); + } catch (e) { + console.warn(`Could not load Aliases from save`); + loadAliases(""); } - if (saveObj.hasOwnProperty("GlobalAliasesSave")) { - try { - loadGlobalAliases(saveObj.GlobalAliasesSave); - } catch(e) { - console.warn(`Could not load GlobalAliases from save`); - loadGlobalAliases(""); - } - } else { - console.warn(`Save file did not contain a GlobalAliases property`); - loadGlobalAliases(""); + } else { + console.warn(`Save file did not contain an Aliases property`); + loadAliases(""); + } + if (saveObj.hasOwnProperty("GlobalAliasesSave")) { + try { + loadGlobalAliases(saveObj.GlobalAliasesSave); + } catch (e) { + console.warn(`Could not load GlobalAliases from save`); + loadGlobalAliases(""); } - if (saveObj.hasOwnProperty("MessagesSave")) { - try { - loadMessages(saveObj.MessagesSave); - } catch(e) { - console.warn(`Could not load Messages from save`); - initMessages(); - } - } else { - console.warn(`Save file did not contain a Messages property`); - initMessages(); + } else { + console.warn(`Save file did not contain a GlobalAliases property`); + loadGlobalAliases(""); + } + if (saveObj.hasOwnProperty("MessagesSave")) { + try { + loadMessages(saveObj.MessagesSave); + } catch (e) { + console.warn(`Could not load Messages from save`); + initMessages(); } - if (saveObj.hasOwnProperty("StockMarketSave")) { - try { - loadStockMarket(saveObj.StockMarketSave); - } catch(e) { - loadStockMarket(""); - } - } else { - loadStockMarket(""); + } else { + console.warn(`Save file did not contain a Messages property`); + initMessages(); + } + if (saveObj.hasOwnProperty("StockMarketSave")) { + try { + loadStockMarket(saveObj.StockMarketSave); + } catch (e) { + loadStockMarket(""); } - if (saveObj.hasOwnProperty("SettingsSave")) { - try { - Settings.load(saveObj.SettingsSave); - } catch(e) { - console.error("ERROR: Failed to parse Settings. Re-initing default values"); - Settings.init(); - } - } else { - Settings.init(); + } else { + loadStockMarket(""); + } + if (saveObj.hasOwnProperty("SettingsSave")) { + try { + Settings.load(saveObj.SettingsSave); + } catch (e) { + console.error( + "ERROR: Failed to parse Settings. Re-initing default values", + ); + Settings.init(); } - if (saveObj.hasOwnProperty("FconfSettingsSave")) { - try { - loadFconf(saveObj.FconfSettingsSave); - } catch(e) { - console.error("ERROR: Failed to parse .fconf Settings."); - } + } else { + Settings.init(); + } + if (saveObj.hasOwnProperty("FconfSettingsSave")) { + try { + loadFconf(saveObj.FconfSettingsSave); + } catch (e) { + console.error("ERROR: Failed to parse .fconf Settings."); } - if (saveObj.hasOwnProperty("LastExportBonus")) { - try { - ExportBonus.setLastExportBonus(JSON.parse(saveObj.LastExportBonus)); - } catch(err) { - ExportBonus.setLastExportBonus((new Date()).getTime()); - console.error("ERROR: Failed to parse .fconf Settings "+ err); - } + } + if (saveObj.hasOwnProperty("LastExportBonus")) { + try { + ExportBonus.setLastExportBonus(JSON.parse(saveObj.LastExportBonus)); + } catch (err) { + ExportBonus.setLastExportBonus(new Date().getTime()); + console.error("ERROR: Failed to parse .fconf Settings " + err); } - if (saveObj.hasOwnProperty("VersionSave")) { - try { - var ver = JSON.parse(saveObj.VersionSave, Reviver); - evaluateVersionCompatibility(ver); + } + if (saveObj.hasOwnProperty("VersionSave")) { + try { + var ver = JSON.parse(saveObj.VersionSave, Reviver); + evaluateVersionCompatibility(ver); - if (window.location.href.toLowerCase().includes("bitburner-beta")) { - // Beta branch, always show changes - createBetaUpdateText(); - } else if (ver != CONSTANTS.Version) { - createNewUpdateText(); - } - } catch(e) { - createNewUpdateText(); - } - } else { + if (window.location.href.toLowerCase().includes("bitburner-beta")) { + // Beta branch, always show changes + createBetaUpdateText(); + } else if (ver != CONSTANTS.Version) { createNewUpdateText(); + } + } catch (e) { + createNewUpdateText(); } - if (Player.inGang() && saveObj.hasOwnProperty("AllGangsSave")) { - try { - loadAllGangs(saveObj.AllGangsSave); - } catch(e) { - console.error("ERROR: Failed to parse AllGangsSave: " + e); - } + } else { + createNewUpdateText(); + } + if (Player.inGang() && saveObj.hasOwnProperty("AllGangsSave")) { + try { + loadAllGangs(saveObj.AllGangsSave); + } catch (e) { + console.error("ERROR: Failed to parse AllGangsSave: " + e); } + } - return true; + return true; } function loadImportedGame(saveObj, saveString) { - var tempSaveObj = null; - var tempPlayer = null; + var tempSaveObj = null; + var tempPlayer = null; - // Check to see if the imported save file can be parsed. If any - // errors are caught it will fail - try { - var decodedSaveString = decodeURIComponent(escape(atob(saveString))); - tempSaveObj = JSON.parse(decodedSaveString, Reviver); + // Check to see if the imported save file can be parsed. If any + // errors are caught it will fail + try { + var decodedSaveString = decodeURIComponent(escape(atob(saveString))); + tempSaveObj = JSON.parse(decodedSaveString, Reviver); - tempPlayer = JSON.parse(tempSaveObj.PlayerSave, Reviver); + tempPlayer = JSON.parse(tempSaveObj.PlayerSave, Reviver); - // Parse Decimal.js objects - tempPlayer.money = new Decimal(tempPlayer.money); + // Parse Decimal.js objects + tempPlayer.money = new Decimal(tempPlayer.money); - JSON.parse(tempSaveObj.AllServersSave, Reviver); - JSON.parse(tempSaveObj.CompaniesSave, Reviver); - JSON.parse(tempSaveObj.FactionsSave, Reviver); - JSON.parse(tempSaveObj.SpecialServerIpsSave, Reviver); - if (tempSaveObj.hasOwnProperty("AliasesSave")) { - try { - JSON.parse(tempSaveObj.AliasesSave, Reviver); - } catch(e) { - console.error(`Parsing Aliases save failed: ${e}`); - } - } - if (tempSaveObj.hasOwnProperty("GlobalAliases")) { - try { - JSON.parse(tempSaveObj.AliasesSave, Reviver); - } catch(e) { - console.error(`Parsing Global Aliases save failed: ${e}`); - } - } - if (tempSaveObj.hasOwnProperty("MessagesSave")) { - try { - JSON.parse(tempSaveObj.MessagesSave, Reviver); - } catch(e) { - console.error(`Parsing Messages save failed: ${e}`); - initMessages(); - } - } else { - initMessages(); - } - if (saveObj.hasOwnProperty("StockMarketSave")) { - try { - JSON.parse(tempSaveObj.StockMarketSave, Reviver); - } catch(e) { - console.error(`Parsing StockMarket save failed: ${e}`); - } - } - if (saveObj.hasOwnProperty("LastExportBonus")) { - try { - ExportBonus.setLastExportBonus(JSON.parse(saveObj.LastExportBonus)); - } catch(err) { - ExportBonus.setLastExportBonus((new Date()).getTime()); - console.error("ERROR: Failed to parse .fconf Settings "+ err); - } - } - if (tempSaveObj.hasOwnProperty("VersionSave")) { - try { - var ver = JSON.parse(tempSaveObj.VersionSave, Reviver); - evaluateVersionCompatibility(ver); - } catch(e) { - console.error("Parsing Version save failed: " + e); - } - } - if (tempPlayer.inGang() && tempSaveObj.hasOwnProperty("AllGangsSave")) { - try { - loadAllGangs(tempSaveObj.AllGangsSave); - } catch(e) { - console.error(`Failed to parse AllGangsSave: {e}`); - throw e; - } - } - } catch(e) { - dialogBoxCreate("Error importing game: " + e.toString()); - return false; + JSON.parse(tempSaveObj.AllServersSave, Reviver); + JSON.parse(tempSaveObj.CompaniesSave, Reviver); + JSON.parse(tempSaveObj.FactionsSave, Reviver); + JSON.parse(tempSaveObj.SpecialServerIpsSave, Reviver); + if (tempSaveObj.hasOwnProperty("AliasesSave")) { + try { + JSON.parse(tempSaveObj.AliasesSave, Reviver); + } catch (e) { + console.error(`Parsing Aliases save failed: ${e}`); + } } - - // Since the save file is valid, load everything for real - saveString = decodeURIComponent(escape(atob(saveString))); - saveObj = JSON.parse(saveString, Reviver); - - loadPlayer(saveObj.PlayerSave); - loadAllServers(saveObj.AllServersSave); - loadCompanies(saveObj.CompaniesSave); - loadFactions(saveObj.FactionsSave); - loadSpecialServerIps(saveObj.SpecialServerIpsSave); - - if (saveObj.hasOwnProperty("AliasesSave")) { - try { - loadAliases(saveObj.AliasesSave); - } catch(e) { - loadAliases(""); - } - } else { - loadAliases(""); + if (tempSaveObj.hasOwnProperty("GlobalAliases")) { + try { + JSON.parse(tempSaveObj.AliasesSave, Reviver); + } catch (e) { + console.error(`Parsing Global Aliases save failed: ${e}`); + } } - if (saveObj.hasOwnProperty("GlobalAliasesSave")) { - try { - loadGlobalAliases(saveObj.GlobalAliasesSave); - } catch(e) { - loadGlobalAliases(""); - } - } else { - loadGlobalAliases(""); - } - if (saveObj.hasOwnProperty("MessagesSave")) { - try { - loadMessages(saveObj.MessagesSave); - } catch(e) { - initMessages(); - } - } else { + if (tempSaveObj.hasOwnProperty("MessagesSave")) { + try { + JSON.parse(tempSaveObj.MessagesSave, Reviver); + } catch (e) { + console.error(`Parsing Messages save failed: ${e}`); initMessages(); + } + } else { + initMessages(); } if (saveObj.hasOwnProperty("StockMarketSave")) { - try { - loadStockMarket(saveObj.StockMarketSave); - } catch(e) { - loadStockMarket(""); - } - } else { - loadStockMarket(""); + try { + JSON.parse(tempSaveObj.StockMarketSave, Reviver); + } catch (e) { + console.error(`Parsing StockMarket save failed: ${e}`); + } } - if (saveObj.hasOwnProperty("SettingsSave")) { - try { - Settings.load(saveObj.SettingsSave); - } catch(e) { - Settings.init(); - } - } else { - Settings.init(); + if (saveObj.hasOwnProperty("LastExportBonus")) { + try { + ExportBonus.setLastExportBonus(JSON.parse(saveObj.LastExportBonus)); + } catch (err) { + ExportBonus.setLastExportBonus(new Date().getTime()); + console.error("ERROR: Failed to parse .fconf Settings " + err); + } } - if (saveObj.hasOwnProperty("FconfSettingsSave")) { - try { - loadFconf(saveObj.FconfSettingsSave); - } catch(e) { - console.error("ERROR: Failed to load .fconf settings when importing"); - } + if (tempSaveObj.hasOwnProperty("VersionSave")) { + try { + var ver = JSON.parse(tempSaveObj.VersionSave, Reviver); + evaluateVersionCompatibility(ver); + } catch (e) { + console.error("Parsing Version save failed: " + e); + } } - if (saveObj.hasOwnProperty("VersionSave")) { - try { - var ver = JSON.parse(saveObj.VersionSave, Reviver); - evaluateVersionCompatibility(ver); + if (tempPlayer.inGang() && tempSaveObj.hasOwnProperty("AllGangsSave")) { + try { + loadAllGangs(tempSaveObj.AllGangsSave); + } catch (e) { + console.error(`Failed to parse AllGangsSave: {e}`); + throw e; + } + } + } catch (e) { + dialogBoxCreate("Error importing game: " + e.toString()); + return false; + } - if (ver != CONSTANTS.Version) { - createNewUpdateText(); - } - } catch(e) { - createNewUpdateText(); - } - } else { + // Since the save file is valid, load everything for real + saveString = decodeURIComponent(escape(atob(saveString))); + saveObj = JSON.parse(saveString, Reviver); + + loadPlayer(saveObj.PlayerSave); + loadAllServers(saveObj.AllServersSave); + loadCompanies(saveObj.CompaniesSave); + loadFactions(saveObj.FactionsSave); + loadSpecialServerIps(saveObj.SpecialServerIpsSave); + + if (saveObj.hasOwnProperty("AliasesSave")) { + try { + loadAliases(saveObj.AliasesSave); + } catch (e) { + loadAliases(""); + } + } else { + loadAliases(""); + } + if (saveObj.hasOwnProperty("GlobalAliasesSave")) { + try { + loadGlobalAliases(saveObj.GlobalAliasesSave); + } catch (e) { + loadGlobalAliases(""); + } + } else { + loadGlobalAliases(""); + } + if (saveObj.hasOwnProperty("MessagesSave")) { + try { + loadMessages(saveObj.MessagesSave); + } catch (e) { + initMessages(); + } + } else { + initMessages(); + } + if (saveObj.hasOwnProperty("StockMarketSave")) { + try { + loadStockMarket(saveObj.StockMarketSave); + } catch (e) { + loadStockMarket(""); + } + } else { + loadStockMarket(""); + } + if (saveObj.hasOwnProperty("SettingsSave")) { + try { + Settings.load(saveObj.SettingsSave); + } catch (e) { + Settings.init(); + } + } else { + Settings.init(); + } + if (saveObj.hasOwnProperty("FconfSettingsSave")) { + try { + loadFconf(saveObj.FconfSettingsSave); + } catch (e) { + console.error("ERROR: Failed to load .fconf settings when importing"); + } + } + if (saveObj.hasOwnProperty("VersionSave")) { + try { + var ver = JSON.parse(saveObj.VersionSave, Reviver); + evaluateVersionCompatibility(ver); + + if (ver != CONSTANTS.Version) { createNewUpdateText(); + } + } catch (e) { + createNewUpdateText(); } - if (Player.inGang() && saveObj.hasOwnProperty("AllGangsSave")) { - try { - loadAllGangs(saveObj.AllGangsSave); - } catch(e) { - console.error("ERROR: Failed to parse AllGangsSave: " + e); - } + } else { + createNewUpdateText(); + } + if (Player.inGang() && saveObj.hasOwnProperty("AllGangsSave")) { + try { + loadAllGangs(saveObj.AllGangsSave); + } catch (e) { + console.error("ERROR: Failed to parse AllGangsSave: " + e); } - saveObject.saveGame(Engine.indexedDb); - location.reload(); - return true; + } + saveObject.saveGame(Engine.indexedDb); + location.reload(); + return true; } -BitburnerSaveObject.prototype.exportGame = function() { - const saveString = this.getSaveString(); +BitburnerSaveObject.prototype.exportGame = function () { + const saveString = this.getSaveString(); - // Save file name is based on current timestamp and BitNode - const epochTime = Math.round(Date.now() / 1000); - const bn = Player.bitNodeN; - const filename = `bitburnerSave_BN${bn}x${SourceFileFlags[bn]}_${epochTime}.json`; - var file = new Blob([saveString], {type: 'text/plain'}); - if (window.navigator.msSaveOrOpenBlob) {// IE10+ - window.navigator.msSaveOrOpenBlob(file, filename); - } else { // Others - var a = document.createElement("a"), - url = URL.createObjectURL(file); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - setTimeoutRef(function() { - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - }, 0); - } -} + // Save file name is based on current timestamp and BitNode + const epochTime = Math.round(Date.now() / 1000); + const bn = Player.bitNodeN; + const filename = `bitburnerSave_BN${bn}x${SourceFileFlags[bn]}_${epochTime}.json`; + var file = new Blob([saveString], { type: "text/plain" }); + if (window.navigator.msSaveOrOpenBlob) { + // IE10+ + window.navigator.msSaveOrOpenBlob(file, filename); + } else { + // Others + var a = document.createElement("a"), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeoutRef(function () { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); + } +}; -BitburnerSaveObject.prototype.importGame = function() { - if (window.File && window.FileReader && window.FileList && window.Blob) { - var fileSelector = clearEventListeners("import-game-file-selector"); - fileSelector.addEventListener("change", openImportFileHandler, false); - $("#import-game-file-selector").click(); - } else { - dialogBoxCreate("ERR: Your browser does not support HTML5 File API. Cannot import."); - } +BitburnerSaveObject.prototype.importGame = function () { + if (window.File && window.FileReader && window.FileList && window.Blob) { + var fileSelector = clearEventListeners("import-game-file-selector"); + fileSelector.addEventListener("change", openImportFileHandler, false); + $("#import-game-file-selector").click(); + } else { + dialogBoxCreate( + "ERR: Your browser does not support HTML5 File API. Cannot import.", + ); + } +}; -} +BitburnerSaveObject.prototype.deleteGame = function (db) { + // Delete from local storage + if (window.localStorage.getItem("bitburnerSave")) { + window.localStorage.removeItem("bitburnerSave"); + } -BitburnerSaveObject.prototype.deleteGame = function(db) { - // Delete from local storage - if (window.localStorage.getItem("bitburnerSave")) { - window.localStorage.removeItem("bitburnerSave"); - } - - // Delete from indexedDB - var request = db.transaction(["savestring"], "readwrite").objectStore("savestring").delete("save"); - request.onsuccess = function() { - console.log("Successfully deleted save from indexedDb"); - } - request.onerror = function(e) { - console.error(`Failed to delete save from indexedDb: ${e}`); - } - createStatusText("Game deleted!"); -} + // Delete from indexedDB + var request = db + .transaction(["savestring"], "readwrite") + .objectStore("savestring") + .delete("save"); + request.onsuccess = function () { + console.log("Successfully deleted save from indexedDb"); + }; + request.onerror = function (e) { + console.error(`Failed to delete save from indexedDb: ${e}`); + }; + createStatusText("Game deleted!"); +}; function createNewUpdateText() { - dialogBoxCreate("New update!
    " + - "Please report any bugs/issues through the github repository " + - "or the Bitburner subreddit (reddit.com/r/bitburner).

    " + - CONSTANTS.LatestUpdate); + dialogBoxCreate( + "New update!
    " + + "Please report any bugs/issues through the github repository " + + "or the Bitburner subreddit (reddit.com/r/bitburner).

    " + + CONSTANTS.LatestUpdate, + ); } function createBetaUpdateText() { - dialogBoxCreate("You are playing on the beta environment! This branch of the game " + - "features the latest developments in the game. This version may be unstable.
    " + - "Please report any bugs/issues through the github repository (https://github.com/danielyxie/bitburner/issues) " + - "or the Bitburner subreddit (reddit.com/r/bitburner).

    " + - CONSTANTS.LatestUpdate); + dialogBoxCreate( + "You are playing on the beta environment! This branch of the game " + + "features the latest developments in the game. This version may be unstable.
    " + + "Please report any bugs/issues through the github repository (https://github.com/danielyxie/bitburner/issues) " + + "or the Bitburner subreddit (reddit.com/r/bitburner).

    " + + CONSTANTS.LatestUpdate, + ); } +BitburnerSaveObject.prototype.toJSON = function () { + return Generic_toJSON("BitburnerSaveObject", this); +}; -BitburnerSaveObject.prototype.toJSON = function() { - return Generic_toJSON("BitburnerSaveObject", this); -} - -BitburnerSaveObject.fromJSON = function(value) { - return Generic_fromJSON(BitburnerSaveObject, value.data); -} +BitburnerSaveObject.fromJSON = function (value) { + return Generic_fromJSON(BitburnerSaveObject, value.data); +}; Reviver.constructors.BitburnerSaveObject = BitburnerSaveObject; function openImportFileHandler(evt) { - var file = evt.target.files[0]; - if (!file) { - dialogBoxCreate("Invalid file selected"); - return; - } + var file = evt.target.files[0]; + if (!file) { + dialogBoxCreate("Invalid file selected"); + return; + } - var reader = new FileReader(); - reader.onload = function(e) { - var contents = e.target.result; - loadImportedGame(saveObject, contents); - }; - reader.readAsText(file); + var reader = new FileReader(); + reader.onload = function (e) { + var contents = e.target.result; + loadImportedGame(saveObject, contents); + }; + reader.readAsText(file); } -export {saveObject, loadGame}; +export { saveObject, loadGame }; diff --git a/src/Script/RamCalculationErrorCodes.ts b/src/Script/RamCalculationErrorCodes.ts index 36da465bc..330a692d5 100644 --- a/src/Script/RamCalculationErrorCodes.ts +++ b/src/Script/RamCalculationErrorCodes.ts @@ -1,5 +1,5 @@ export enum RamCalculationErrorCode { - SyntaxError = -1, - ImportError = -2, - URLImportError = -3, + SyntaxError = -1, + ImportError = -2, + URLImportError = -3, } diff --git a/src/Script/RamCalculations.d.ts b/src/Script/RamCalculations.d.ts index 7a7b889c1..0209421e9 100644 --- a/src/Script/RamCalculations.d.ts +++ b/src/Script/RamCalculations.d.ts @@ -1,3 +1,6 @@ import { Script } from "./Script"; -export declare function calculateRamUsage(codeCopy: string, otherScripts: Script[]): number; +export declare function calculateRamUsage( + codeCopy: string, + otherScripts: Script[], +): number; diff --git a/src/Script/RamCalculations.js b/src/Script/RamCalculations.js index b5ab4cf4d..c1a51d60b 100644 --- a/src/Script/RamCalculations.js +++ b/src/Script/RamCalculations.js @@ -30,183 +30,195 @@ const memCheckGlobalKey = ".__GLOBAL__"; * keep track of what functions have/havent been accounted for */ async function parseOnlyRamCalculate(otherScripts, code, workerScript) { - try { - /** - * Maps dependent identifiers to their dependencies. - * - * The initial identifier is __SPECIAL_INITIAL_MODULE__.__GLOBAL__. - * It depends on all the functions declared in the module, all the global scopes - * of its imports, and any identifiers referenced in this global scope. Each - * function depends on all the identifiers referenced internally. - * We walk the dependency graph to calculate RAM usage, given that some identifiers - * reference Netscript functions which have a RAM cost. - */ - let dependencyMap = {}; + try { + /** + * Maps dependent identifiers to their dependencies. + * + * The initial identifier is __SPECIAL_INITIAL_MODULE__.__GLOBAL__. + * It depends on all the functions declared in the module, all the global scopes + * of its imports, and any identifiers referenced in this global scope. Each + * function depends on all the identifiers referenced internally. + * We walk the dependency graph to calculate RAM usage, given that some identifiers + * reference Netscript functions which have a RAM cost. + */ + let dependencyMap = {}; - // Scripts we've parsed. - const completedParses = new Set(); + // Scripts we've parsed. + const completedParses = new Set(); - // Scripts we've discovered that need to be parsed. - const parseQueue = []; + // Scripts we've discovered that need to be parsed. + const parseQueue = []; - // Parses a chunk of code with a given module name, and updates parseQueue and dependencyMap. - function parseCode(code, moduleName) { - const result = parseOnlyCalculateDeps(code, moduleName); - completedParses.add(moduleName); + // Parses a chunk of code with a given module name, and updates parseQueue and dependencyMap. + function parseCode(code, moduleName) { + const result = parseOnlyCalculateDeps(code, moduleName); + completedParses.add(moduleName); - // Add any additional modules to the parse queue; - for (let i = 0; i < result.additionalModules.length; ++i) { - if (!completedParses.has(result.additionalModules[i])) { - parseQueue.push(result.additionalModules[i]); - } - } - - // Splice all the references in - dependencyMap = Object.assign(dependencyMap, result.dependencyMap); + // Add any additional modules to the parse queue; + for (let i = 0; i < result.additionalModules.length; ++i) { + if (!completedParses.has(result.additionalModules[i])) { + parseQueue.push(result.additionalModules[i]); } + } - // Parse the initial module, which is the "main" script that is being run - const initialModule = "__SPECIAL_INITIAL_MODULE__"; - parseCode(code, initialModule); - - // Process additional modules, which occurs if the "main" script has any imports - while (parseQueue.length > 0) { - const nextModule = parseQueue.shift(); - - // Additional modules can either be imported from the web (in which case we use - // a dynamic import), or from other in-game scripts - let code; - if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) { - try { - // eslint-disable-next-line no-await-in-loop - const module = await eval('import(nextModule)'); - code = ""; - for (const prop in module) { - if (typeof module[prop] === 'function') { - code += module[prop].toString() + ";\n"; - } - } - } catch(e) { - console.error(`Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`); - return RamCalculationErrorCode.URLImportError; - } - } else { - if (!Array.isArray(otherScripts)) { - console.warn(`parseOnlyRamCalculate() not called with array of scripts`); - return RamCalculationErrorCode.ImportError; - } - - let script = null; - let fn = nextModule.startsWith("./") ? nextModule.slice(2) : nextModule; - for (const s of otherScripts) { - if (s.filename === fn) { - script = s; - break; - } - } - - if (script == null) { - return RamCalculationErrorCode.ImportError; // No such script on the server - } - - code = script.code; - } - - parseCode(code, nextModule); - } - - // Finally, walk the reference map and generate a ram cost. The initial set of keys to scan - // are those that start with __SPECIAL_INITIAL_MODULE__. - let ram = RamCostConstants.ScriptBaseRamCost; - const unresolvedRefs = Object.keys(dependencyMap).filter(s => s.startsWith(initialModule)); - const resolvedRefs = new Set(); - while (unresolvedRefs.length > 0) { - const ref = unresolvedRefs.shift(); - - // Check if this is one of the special keys, and add the appropriate ram cost if so. - if (ref === "hacknet" && !resolvedRefs.has("hacknet")) { - ram += RamCostConstants.ScriptHacknetNodesRamCost; - } - if (ref === "document" && !resolvedRefs.has("document")) { - ram += RamCostConstants.ScriptDomRamCost; - } - if (ref === "window" && !resolvedRefs.has("window")) { - ram += RamCostConstants.ScriptDomRamCost; - } - - resolvedRefs.add(ref); - - if (ref.endsWith(".*")) { - // A prefix reference. We need to find all matching identifiers. - const prefix = ref.slice(0, ref.length - 2); - for (let ident of Object.keys(dependencyMap).filter(k => k.startsWith(prefix))) { - for (let dep of dependencyMap[ident] || []) { - if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); - } - } - } else { - // An exact reference. Add all dependencies of this ref. - for (let dep of dependencyMap[ref] || []) { - if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); - } - } - - // Check if this identifier is a function in the workerScript environment. - // If it is, then we need to get its RAM cost. - try { - function applyFuncRam(func) { - if (typeof func === "function") { - try { - let res; - if (func.constructor.name === "AsyncFunction") { - res = 0; // Async functions will always be 0 RAM - } else { - res = func.apply(null, []); - } - if (typeof res === "number") { - return res; - } - return 0; - } catch(e) { - console.error(`Error applying function: ${e}`); - return 0; - } - } else { - return 0; - } - } - - // Only count each function once - if (workerScript.loadedFns[ref]) { - continue; - } else { - workerScript.loadedFns[ref] = true; - } - - // This accounts for namespaces (Bladeburner, CodingCpntract, etc.) - let func; - if (ref in workerScript.env.vars.bladeburner) { - func = workerScript.env.vars.bladeburner[ref]; - } else if (ref in workerScript.env.vars.codingcontract) { - func = workerScript.env.vars.codingcontract[ref]; - } else if (ref in workerScript.env.vars.gang) { - func = workerScript.env.vars.gang[ref]; - } else if (ref in workerScript.env.vars.sleeve) { - func = workerScript.env.vars.sleeve[ref]; - } else { - func = workerScript.env.vars[ref]; - } - ram += applyFuncRam(func); - } catch (error) {continue;} - } - return ram; - - } catch (error) { - // console.info("parse or eval error: ", error); - // This is not unexpected. The user may be editing a script, and it may be in - // a transitory invalid state. - return RamCalculationErrorCode.SyntaxError; + // Splice all the references in + dependencyMap = Object.assign(dependencyMap, result.dependencyMap); } + + // Parse the initial module, which is the "main" script that is being run + const initialModule = "__SPECIAL_INITIAL_MODULE__"; + parseCode(code, initialModule); + + // Process additional modules, which occurs if the "main" script has any imports + while (parseQueue.length > 0) { + const nextModule = parseQueue.shift(); + + // Additional modules can either be imported from the web (in which case we use + // a dynamic import), or from other in-game scripts + let code; + if ( + nextModule.startsWith("https://") || + nextModule.startsWith("http://") + ) { + try { + // eslint-disable-next-line no-await-in-loop + const module = await eval("import(nextModule)"); + code = ""; + for (const prop in module) { + if (typeof module[prop] === "function") { + code += module[prop].toString() + ";\n"; + } + } + } catch (e) { + console.error( + `Error dynamically importing module from ${nextModule} for RAM calculations: ${e}`, + ); + return RamCalculationErrorCode.URLImportError; + } + } else { + if (!Array.isArray(otherScripts)) { + console.warn( + `parseOnlyRamCalculate() not called with array of scripts`, + ); + return RamCalculationErrorCode.ImportError; + } + + let script = null; + let fn = nextModule.startsWith("./") ? nextModule.slice(2) : nextModule; + for (const s of otherScripts) { + if (s.filename === fn) { + script = s; + break; + } + } + + if (script == null) { + return RamCalculationErrorCode.ImportError; // No such script on the server + } + + code = script.code; + } + + parseCode(code, nextModule); + } + + // Finally, walk the reference map and generate a ram cost. The initial set of keys to scan + // are those that start with __SPECIAL_INITIAL_MODULE__. + let ram = RamCostConstants.ScriptBaseRamCost; + const unresolvedRefs = Object.keys(dependencyMap).filter((s) => + s.startsWith(initialModule), + ); + const resolvedRefs = new Set(); + while (unresolvedRefs.length > 0) { + const ref = unresolvedRefs.shift(); + + // Check if this is one of the special keys, and add the appropriate ram cost if so. + if (ref === "hacknet" && !resolvedRefs.has("hacknet")) { + ram += RamCostConstants.ScriptHacknetNodesRamCost; + } + if (ref === "document" && !resolvedRefs.has("document")) { + ram += RamCostConstants.ScriptDomRamCost; + } + if (ref === "window" && !resolvedRefs.has("window")) { + ram += RamCostConstants.ScriptDomRamCost; + } + + resolvedRefs.add(ref); + + if (ref.endsWith(".*")) { + // A prefix reference. We need to find all matching identifiers. + const prefix = ref.slice(0, ref.length - 2); + for (let ident of Object.keys(dependencyMap).filter((k) => + k.startsWith(prefix), + )) { + for (let dep of dependencyMap[ident] || []) { + if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); + } + } + } else { + // An exact reference. Add all dependencies of this ref. + for (let dep of dependencyMap[ref] || []) { + if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep); + } + } + + // Check if this identifier is a function in the workerScript environment. + // If it is, then we need to get its RAM cost. + try { + function applyFuncRam(func) { + if (typeof func === "function") { + try { + let res; + if (func.constructor.name === "AsyncFunction") { + res = 0; // Async functions will always be 0 RAM + } else { + res = func.apply(null, []); + } + if (typeof res === "number") { + return res; + } + return 0; + } catch (e) { + console.error(`Error applying function: ${e}`); + return 0; + } + } else { + return 0; + } + } + + // Only count each function once + if (workerScript.loadedFns[ref]) { + continue; + } else { + workerScript.loadedFns[ref] = true; + } + + // This accounts for namespaces (Bladeburner, CodingCpntract, etc.) + let func; + if (ref in workerScript.env.vars.bladeburner) { + func = workerScript.env.vars.bladeburner[ref]; + } else if (ref in workerScript.env.vars.codingcontract) { + func = workerScript.env.vars.codingcontract[ref]; + } else if (ref in workerScript.env.vars.gang) { + func = workerScript.env.vars.gang[ref]; + } else if (ref in workerScript.env.vars.sleeve) { + func = workerScript.env.vars.sleeve[ref]; + } else { + func = workerScript.env.vars[ref]; + } + ram += applyFuncRam(func); + } catch (error) { + continue; + } + } + return ram; + } catch (error) { + // console.info("parse or eval error: ", error); + // This is not unexpected. The user may be editing a script, and it may be in + // a transitory invalid state. + return RamCalculationErrorCode.SyntaxError; + } } /** @@ -216,99 +228,111 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) { * that need to be parsed (i.e. are 'import'ed scripts). */ function parseOnlyCalculateDeps(code, currentModule) { - const ast = parse(code, {sourceType:"module", ecmaVersion: 'latest' }); + const ast = parse(code, { sourceType: "module", ecmaVersion: "latest" }); - // Everything from the global scope goes in ".". Everything else goes in ".function", where only - // the outermost layer of functions counts. - const globalKey = currentModule + memCheckGlobalKey; - const dependencyMap = {}; - dependencyMap[globalKey] = new Set(); + // Everything from the global scope goes in ".". Everything else goes in ".function", where only + // the outermost layer of functions counts. + const globalKey = currentModule + memCheckGlobalKey; + const dependencyMap = {}; + dependencyMap[globalKey] = new Set(); - // If we reference this internal name, we're really referencing that external name. - // Filled when we import names from other modules. - let internalToExternal = {}; + // If we reference this internal name, we're really referencing that external name. + // Filled when we import names from other modules. + let internalToExternal = {}; - var additionalModules = []; + var additionalModules = []; - // References get added pessimistically. They are added for thisModule.name, name, and for - // any aliases. - function addRef(key, name) { - const s = dependencyMap[key] || (dependencyMap[key] = new Set()); - if (name in internalToExternal) { - s.add(internalToExternal[name]); - } - s.add(currentModule + "." + name); - s.add(name); // For builtins like hack. + // References get added pessimistically. They are added for thisModule.name, name, and for + // any aliases. + function addRef(key, name) { + const s = dependencyMap[key] || (dependencyMap[key] = new Set()); + if (name in internalToExternal) { + s.add(internalToExternal[name]); } + s.add(currentModule + "." + name); + s.add(name); // For builtins like hack. + } - //A list of identifiers that resolve to "native Javascript code" - const objectPrototypeProperties = Object.getOwnPropertyNames(Object.prototype); + //A list of identifiers that resolve to "native Javascript code" + const objectPrototypeProperties = Object.getOwnPropertyNames( + Object.prototype, + ); - // If we discover a dependency identifier, state.key is the dependent identifier. - // walkDeeper is for doing recursive walks of expressions in composites that we handle. - function commonVisitors() { - return { - Identifier: (node, st) => { - if (objectPrototypeProperties.includes(node.name)) {return;} - addRef(st.key, node.name); - }, - WhileStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceWHILE); - node.test && walkDeeper(node.test, st); - node.body && walkDeeper(node.body, st); - }, - DoWhileStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceWHILE); - node.test && walkDeeper(node.test, st); - node.body && walkDeeper(node.body, st); - }, - ForStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceFOR); - node.init && walkDeeper(node.init, st); - node.test && walkDeeper(node.test, st); - node.update && walkDeeper(node.update, st); - node.body && walkDeeper(node.body, st); - }, - IfStatement: (node, st, walkDeeper) => { - addRef(st.key, specialReferenceIF); - node.test && walkDeeper(node.test, st); - node.consequent && walkDeeper(node.consequent, st); - node.alternate && walkDeeper(node.alternate, st); - }, - MemberExpression: (node, st, walkDeeper) => { - node.object && walkDeeper(node.object, st); - node.property && walkDeeper(node.property, st); - }, + // If we discover a dependency identifier, state.key is the dependent identifier. + // walkDeeper is for doing recursive walks of expressions in composites that we handle. + function commonVisitors() { + return { + Identifier: (node, st) => { + if (objectPrototypeProperties.includes(node.name)) { + return; } - } + addRef(st.key, node.name); + }, + WhileStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceWHILE); + node.test && walkDeeper(node.test, st); + node.body && walkDeeper(node.body, st); + }, + DoWhileStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceWHILE); + node.test && walkDeeper(node.test, st); + node.body && walkDeeper(node.body, st); + }, + ForStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceFOR); + node.init && walkDeeper(node.init, st); + node.test && walkDeeper(node.test, st); + node.update && walkDeeper(node.update, st); + node.body && walkDeeper(node.body, st); + }, + IfStatement: (node, st, walkDeeper) => { + addRef(st.key, specialReferenceIF); + node.test && walkDeeper(node.test, st); + node.consequent && walkDeeper(node.consequent, st); + node.alternate && walkDeeper(node.alternate, st); + }, + MemberExpression: (node, st, walkDeeper) => { + node.object && walkDeeper(node.object, st); + node.property && walkDeeper(node.property, st); + }, + }; + } - walk.recursive(ast, {key: globalKey}, Object.assign({ + walk.recursive( + ast, + { key: globalKey }, + Object.assign( + { ImportDeclaration: (node, st) => { - const importModuleName = node.source.value; - additionalModules.push(importModuleName); + const importModuleName = node.source.value; + additionalModules.push(importModuleName); - // This module's global scope refers to that module's global scope, no matter how we - // import it. - dependencyMap[st.key].add(importModuleName + memCheckGlobalKey); + // This module's global scope refers to that module's global scope, no matter how we + // import it. + dependencyMap[st.key].add(importModuleName + memCheckGlobalKey); - for (let i = 0; i < node.specifiers.length; ++i) { - const spec = node.specifiers[i]; - if (spec.imported !== undefined && spec.local !== undefined) { - // We depend on specific things. - internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name; - } else { - // We depend on everything. - dependencyMap[st.key].add(importModuleName + ".*"); - } + for (let i = 0; i < node.specifiers.length; ++i) { + const spec = node.specifiers[i]; + if (spec.imported !== undefined && spec.local !== undefined) { + // We depend on specific things. + internalToExternal[spec.local.name] = + importModuleName + "." + spec.imported.name; + } else { + // We depend on everything. + dependencyMap[st.key].add(importModuleName + ".*"); } + } }, FunctionDeclaration: (node) => { - const key = currentModule + "." + node.id.name; - walk.recursive(node, {key: key}, commonVisitors()); + const key = currentModule + "." + node.id.name; + walk.recursive(node, { key: key }, commonVisitors()); }, - }, commonVisitors())); + }, + commonVisitors(), + ), + ); - return {dependencyMap: dependencyMap, additionalModules: additionalModules}; + return { dependencyMap: dependencyMap, additionalModules: additionalModules }; } /** @@ -318,22 +342,22 @@ function parseOnlyCalculateDeps(code, currentModule) { * Used to account for imported scripts */ export async function calculateRamUsage(codeCopy, otherScripts) { - // We don't need a real WorkerScript for this. Just an object that keeps - // track of whatever's needed for RAM calculations - const workerScript = { - loadedFns: {}, - env: { - vars: RamCosts, - }, - } - - try { - return await parseOnlyRamCalculate(otherScripts, codeCopy, workerScript); - } catch (e) { - console.error(`Failed to parse script for RAM calculations:`); - console.error(e); - return RamCalculationErrorCode.SyntaxError; - } + // We don't need a real WorkerScript for this. Just an object that keeps + // track of whatever's needed for RAM calculations + const workerScript = { + loadedFns: {}, + env: { + vars: RamCosts, + }, + }; + try { + return await parseOnlyRamCalculate(otherScripts, codeCopy, workerScript); + } catch (e) { + console.error(`Failed to parse script for RAM calculations:`); + console.error(e); return RamCalculationErrorCode.SyntaxError; + } + + return RamCalculationErrorCode.SyntaxError; } diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts index a1503fa26..5b0226f61 100644 --- a/src/Script/RunningScript.ts +++ b/src/Script/RunningScript.ts @@ -9,128 +9,138 @@ import { IMap } from "../types"; import { post } from "../ui/postToTerminal"; import { - Generic_fromJSON, - Generic_toJSON, - Reviver, + Generic_fromJSON, + Generic_toJSON, + Reviver, } from "../../utils/JSONReviver"; import { getTimestamp } from "../../utils/helpers/getTimestamp"; export class RunningScript { + // Script arguments + args: any[] = []; - // Script arguments - args: any[] = []; + // Map of [key: server ip] -> Hacking data. Used for offline progress calculations. + // Hacking data format: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] + dataMap: IMap = {}; - // Map of [key: server ip] -> Hacking data. Used for offline progress calculations. - // Hacking data format: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] - dataMap: IMap = {}; + // Script filename + filename = ""; - // Script filename - filename = ""; + // This script's logs. An array of log entries + logs: string[] = []; - // This script's logs. An array of log entries - logs: string[] = []; + // Flag indicating whether the logs have been updated since + // the last time the UI was updated + logUpd = false; - // Flag indicating whether the logs have been updated since - // the last time the UI was updated - logUpd = false; + // Total amount of hacking experience earned from this script when offline + offlineExpGained = 0; - // Total amount of hacking experience earned from this script when offline - offlineExpGained = 0; + // Total amount of money made by this script when offline + offlineMoneyMade = 0; - // Total amount of money made by this script when offline - offlineMoneyMade = 0; + // Number of seconds that the script has been running offline + offlineRunningTime = 0.01; - // Number of seconds that the script has been running offline - offlineRunningTime = 0.01; + // Total amount of hacking experience earned from this script when online + onlineExpGained = 0; - // Total amount of hacking experience earned from this script when online - onlineExpGained = 0; + // Total amount of money made by this script when online + onlineMoneyMade = 0; - // Total amount of money made by this script when online - onlineMoneyMade = 0; + // Number of seconds that this script has been running online + onlineRunningTime = 0.01; - // Number of seconds that this script has been running online - onlineRunningTime = 0.01; + // Process ID. Must be an integer and equals the PID of corresponding WorkerScript + pid = -1; - // Process ID. Must be an integer and equals the PID of corresponding WorkerScript - pid = -1; + // How much RAM this script uses for ONE thread + ramUsage = 0; - // How much RAM this script uses for ONE thread - ramUsage = 0; + // IP of the server on which this script is running + server = ""; - // IP of the server on which this script is running - server = ""; + // Number of threads that this script is running with + threads = 1; - // Number of threads that this script is running with - threads = 1; + constructor(script: Script | null = null, args: any[] = []) { + if (script == null) { + return; + } + this.filename = script.filename; + this.args = args; + this.server = script.server; + this.ramUsage = script.ramUsage; + } - constructor(script: Script | null = null, args: any[] = []) { - if (script == null) { return; } - this.filename = script.filename; - this.args = args; - this.server = script.server; - this.ramUsage = script.ramUsage; + log(txt: string): void { + if (this.logs.length > Settings.MaxLogCapacity) { + this.logs.shift(); } - log(txt: string): void { - if (this.logs.length > Settings.MaxLogCapacity) { - this.logs.shift(); - } - - let logEntry = txt; - if (FconfSettings.ENABLE_TIMESTAMPS) { - logEntry = "[" + getTimestamp() + "] " + logEntry; - } - - this.logs.push(logEntry); - this.logUpd = true; + let logEntry = txt; + if (FconfSettings.ENABLE_TIMESTAMPS) { + logEntry = "[" + getTimestamp() + "] " + logEntry; } - displayLog(): void { - for (let i = 0; i < this.logs.length; ++i) { - post(this.logs[i]); - } - } + this.logs.push(logEntry); + this.logUpd = true; + } - clearLog(): void { - this.logs.length = 0; + displayLog(): void { + for (let i = 0; i < this.logs.length; ++i) { + post(this.logs[i]); } + } - // Update the moneyStolen and numTimesHack maps when hacking - recordHack(serverIp: string, moneyGained: number, n=1): void { - if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { - this.dataMap[serverIp] = [0, 0, 0, 0]; - } - this.dataMap[serverIp][0] += moneyGained; - this.dataMap[serverIp][1] += n; - } + clearLog(): void { + this.logs.length = 0; + } - // Update the grow map when calling grow() - recordGrow(serverIp: string, n=1): void { - if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { - this.dataMap[serverIp] = [0, 0, 0, 0]; - } - this.dataMap[serverIp][2] += n; + // Update the moneyStolen and numTimesHack maps when hacking + recordHack(serverIp: string, moneyGained: number, n = 1): void { + if ( + this.dataMap[serverIp] == null || + this.dataMap[serverIp].constructor !== Array + ) { + this.dataMap[serverIp] = [0, 0, 0, 0]; } + this.dataMap[serverIp][0] += moneyGained; + this.dataMap[serverIp][1] += n; + } - // Update the weaken map when calling weaken() { - recordWeaken(serverIp: string, n=1): void { - if (this.dataMap[serverIp] == null || this.dataMap[serverIp].constructor !== Array) { - this.dataMap[serverIp] = [0, 0, 0, 0]; - } - this.dataMap[serverIp][3] += n; + // Update the grow map when calling grow() + recordGrow(serverIp: string, n = 1): void { + if ( + this.dataMap[serverIp] == null || + this.dataMap[serverIp].constructor !== Array + ) { + this.dataMap[serverIp] = [0, 0, 0, 0]; } + this.dataMap[serverIp][2] += n; + } - // Serialize the current object to a JSON save state - toJSON(): any { - return Generic_toJSON("RunningScript", this); + // Update the weaken map when calling weaken() { + recordWeaken(serverIp: string, n = 1): void { + if ( + this.dataMap[serverIp] == null || + this.dataMap[serverIp].constructor !== Array + ) { + this.dataMap[serverIp] = [0, 0, 0, 0]; } + this.dataMap[serverIp][3] += n; + } - // Initializes a RunningScript Object from a JSON save state - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): RunningScript { - return Generic_fromJSON(RunningScript, value.data); - } + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("RunningScript", this); + } + + // Initializes a RunningScript Object from a JSON save state + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): RunningScript { + return Generic_fromJSON(RunningScript, value.data); + } } Reviver.constructors.RunningScript = RunningScript; diff --git a/src/Script/RunningScriptHelpers.ts b/src/Script/RunningScriptHelpers.ts index 8d63a0752..07616d306 100644 --- a/src/Script/RunningScriptHelpers.ts +++ b/src/Script/RunningScriptHelpers.ts @@ -2,19 +2,21 @@ import { AllServers } from "../Server/AllServers"; import { RunningScript } from "./RunningScript"; export function getRamUsageFromRunningScript(script: RunningScript): number { - if (script.ramUsage != null && script.ramUsage > 0) { - return script.ramUsage; // Use cached value - } - - const server = AllServers[script.server]; - if (server == null) { return 0; } - for (let i = 0; i < server.scripts.length; ++i) { - if (server.scripts[i].filename === script.filename) { - // Cache the ram usage for the next call - script.ramUsage = server.scripts[i].ramUsage; - return script.ramUsage; - } - } + if (script.ramUsage != null && script.ramUsage > 0) { + return script.ramUsage; // Use cached value + } + const server = AllServers[script.server]; + if (server == null) { return 0; + } + for (let i = 0; i < server.scripts.length; ++i) { + if (server.scripts[i].filename === script.filename) { + // Cache the ram usage for the next call + script.ramUsage = server.scripts[i].ramUsage; + return script.ramUsage; + } + } + + return 0; } diff --git a/src/Script/Script.ts b/src/Script/Script.ts index 24b8ca87e..1b05eb197 100644 --- a/src/Script/Script.ts +++ b/src/Script/Script.ts @@ -10,127 +10,132 @@ import { Page, routing } from "../ui/navigationTracking"; import { setTimeoutRef } from "../utils/SetTimeoutRef"; import { - Generic_fromJSON, - Generic_toJSON, - Reviver, + Generic_fromJSON, + Generic_toJSON, + Reviver, } from "../../utils/JSONReviver"; import { roundToTwo } from "../../utils/helpers/roundToTwo"; let globalModuleSequenceNumber = 0; export class Script { + // Code for this script + code = ""; - // Code for this script - code = ""; + // Filename for the script file + filename = ""; - // Filename for the script file - filename = ""; + // url of the script if any, only for NS2. + url = ""; - // url of the script if any, only for NS2. - url = ""; + // The dynamic module generated for this script when it is run. + // This is only applicable for NetscriptJS + module: any = ""; - // The dynamic module generated for this script when it is run. - // This is only applicable for NetscriptJS - module: any = ""; + // The timestamp when when the script was last updated. + moduleSequenceNumber: number; - // The timestamp when when the script was last updated. - moduleSequenceNumber: number; + // Only used with NS2 scripts; the list of dependency script filenames. This is constructed + // whenever the script is first evaluated, and therefore may be out of date if the script + // has been updated since it was last run. + dependencies: ScriptUrl[] = []; - // Only used with NS2 scripts; the list of dependency script filenames. This is constructed - // whenever the script is first evaluated, and therefore may be out of date if the script - // has been updated since it was last run. - dependencies: ScriptUrl[] = []; + // Amount of RAM this Script requres to run + ramUsage = 0; - // Amount of RAM this Script requres to run - ramUsage = 0; + // IP of server that this script is on. + server = ""; - // IP of server that this script is on. - server = ""; - - constructor(fn="", code="", server="", otherScripts: Script[]=[]) { - this.filename = fn; - this.code = code; - this.ramUsage = 0; - this.server = server; // IP of server this script is on - this.module = ""; - this.moduleSequenceNumber = ++globalModuleSequenceNumber; - if (this.code !== "") { this.updateRamUsage(otherScripts); } + constructor(fn = "", code = "", server = "", otherScripts: Script[] = []) { + this.filename = fn; + this.code = code; + this.ramUsage = 0; + this.server = server; // IP of server this script is on + this.module = ""; + this.moduleSequenceNumber = ++globalModuleSequenceNumber; + if (this.code !== "") { + this.updateRamUsage(otherScripts); } + } - /** - * Download the script as a file - */ - download(): void { - const filename = this.filename + ".js"; - const file = new Blob([this.code], {type: 'text/plain'}); - if (window.navigator.msSaveOrOpenBlob) {// IE10+ - window.navigator.msSaveOrOpenBlob(file, filename); - } else { // Others - const a = document.createElement("a"), - url = URL.createObjectURL(file); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - setTimeoutRef(function() { - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - }, 0); - } + /** + * Download the script as a file + */ + download(): void { + const filename = this.filename + ".js"; + const file = new Blob([this.code], { type: "text/plain" }); + if (window.navigator.msSaveOrOpenBlob) { + // IE10+ + window.navigator.msSaveOrOpenBlob(file, filename); + } else { + // Others + const a = document.createElement("a"), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeoutRef(function () { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); } + } - /** - * Marks this script as having been updated. It will be recompiled next time something tries - * to exec it. - */ - markUpdated(): void { - this.module = ""; - this.moduleSequenceNumber = ++globalModuleSequenceNumber; + /** + * Marks this script as having been updated. It will be recompiled next time something tries + * to exec it. + */ + markUpdated(): void { + this.module = ""; + this.moduleSequenceNumber = ++globalModuleSequenceNumber; + } + + /** + * Save a script from the script editor + * @param {string} code - The new contents of the script + * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports + */ + saveScript(code: string, serverIp: string, otherScripts: Script[]): void { + if (routing.isOn(Page.ScriptEditor)) { + // Update code and filename + this.code = code.replace(/^\s+|\s+$/g, ""); + + const filenameElem: HTMLInputElement | null = document.getElementById( + "script-editor-filename", + ) as HTMLInputElement; + if (filenameElem == null) { + console.error(`Failed to get Script filename DOM element`); + return; + } + this.filename = filenameElem.value; + this.server = serverIp; + this.updateRamUsage(otherScripts); + this.markUpdated(); } + } - /** - * Save a script from the script editor - * @param {string} code - The new contents of the script - * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports - */ - saveScript(code: string, serverIp: string, otherScripts: Script[]): void { - if (routing.isOn(Page.ScriptEditor)) { - // Update code and filename - this.code = code.replace(/^\s+|\s+$/g, ''); - - const filenameElem: HTMLInputElement | null = document.getElementById("script-editor-filename") as HTMLInputElement; - if (filenameElem == null) { - console.error(`Failed to get Script filename DOM element`); - return; - } - this.filename = filenameElem.value; - this.server = serverIp; - this.updateRamUsage(otherScripts); - this.markUpdated(); - } + /** + * Calculates and updates the script's RAM usage based on its code + * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports + */ + async updateRamUsage(otherScripts: Script[]): Promise { + const res = await calculateRamUsage(this.code, otherScripts); + if (res > 0) { + this.ramUsage = roundToTwo(res); } + } - /** - * Calculates and updates the script's RAM usage based on its code - * @param {Script[]} otherScripts - Other scripts on the server. Used to process imports - */ - async updateRamUsage(otherScripts: Script[]): Promise { - const res = await calculateRamUsage(this.code, otherScripts); - if (res > 0) { - this.ramUsage = roundToTwo(res); - } - } + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("Script", this); + } - // Serialize the current object to a JSON save state - toJSON(): any { - return Generic_toJSON("Script", this); - } - - // Initializes a Script Object from a JSON save state - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Script { - return Generic_fromJSON(Script, value.data); - } + // Initializes a Script Object from a JSON save state + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Script { + return Generic_fromJSON(Script, value.data); + } } Reviver.constructors.Script = Script; diff --git a/src/Script/ScriptHelpers.js b/src/Script/ScriptHelpers.js index 49a8cffe7..2819a87e6 100644 --- a/src/Script/ScriptHelpers.js +++ b/src/Script/ScriptHelpers.js @@ -4,13 +4,13 @@ import { RamCalculationErrorCode } from "./RamCalculationErrorCodes"; import { calculateRamUsage } from "./RamCalculations"; import { isScriptFilename } from "./ScriptHelpersTS"; -import {CONSTANTS} from "../Constants"; -import {Engine} from "../engine"; +import { CONSTANTS } from "../Constants"; +import { Engine } from "../engine"; import { parseFconfSettings } from "../Fconf/Fconf"; import { - iTutorialSteps, - iTutorialNextStep, - ITutorial, + iTutorialSteps, + iTutorialNextStep, + ITutorial, } from "../InteractiveTutorial"; import { Player } from "../Player"; import { CursorPositions } from "../ScriptEditor/CursorPositions"; @@ -29,76 +29,119 @@ import { compareArrays } from "../../utils/helpers/compareArrays"; import { createElement } from "../../utils/uiHelpers/createElement"; export function scriptCalculateOfflineProduction(runningScriptObj) { - //The Player object stores the last update time from when we were online - const thisUpdate = new Date().getTime(); - const lastUpdate = Player.lastUpdate; - const timePassed = (thisUpdate - lastUpdate) / 1000; //Seconds + //The Player object stores the last update time from when we were online + const thisUpdate = new Date().getTime(); + const lastUpdate = Player.lastUpdate; + const timePassed = (thisUpdate - lastUpdate) / 1000; //Seconds - //Calculate the "confidence" rating of the script's true production. This is based - //entirely off of time. We will arbitrarily say that if a script has been running for - //4 hours (14400 sec) then we are completely confident in its ability - let confidence = (runningScriptObj.onlineRunningTime) / 14400; - if (confidence >= 1) {confidence = 1;} + //Calculate the "confidence" rating of the script's true production. This is based + //entirely off of time. We will arbitrarily say that if a script has been running for + //4 hours (14400 sec) then we are completely confident in its ability + let confidence = runningScriptObj.onlineRunningTime / 14400; + if (confidence >= 1) { + confidence = 1; + } - //Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] + //Data map: [MoneyStolen, NumTimesHacked, NumTimesGrown, NumTimesWeaken] - // Grow - for (const ip in runningScriptObj.dataMap) { - if (runningScriptObj.dataMap.hasOwnProperty(ip)) { - if (runningScriptObj.dataMap[ip][2] == 0 || runningScriptObj.dataMap[ip][2] == null) {continue;} - const serv = AllServers[ip]; - if (serv == null) {continue;} - const timesGrown = Math.round(0.5 * runningScriptObj.dataMap[ip][2] / runningScriptObj.onlineRunningTime * timePassed); - runningScriptObj.log(`Called on ${serv.hostname} ${timesGrown} times while offline`); - const host = AllServers[runningScriptObj.server]; - const growth = processSingleServerGrowth(serv, timesGrown, Player, host.cpuCores); - runningScriptObj.log(`'${serv.hostname}' grown by ${numeralWrapper.format(growth * 100 - 100, '0.000000%')} while offline`); - } + // Grow + for (const ip in runningScriptObj.dataMap) { + if (runningScriptObj.dataMap.hasOwnProperty(ip)) { + if ( + runningScriptObj.dataMap[ip][2] == 0 || + runningScriptObj.dataMap[ip][2] == null + ) { + continue; + } + const serv = AllServers[ip]; + if (serv == null) { + continue; + } + const timesGrown = Math.round( + ((0.5 * runningScriptObj.dataMap[ip][2]) / + runningScriptObj.onlineRunningTime) * + timePassed, + ); + runningScriptObj.log( + `Called on ${serv.hostname} ${timesGrown} times while offline`, + ); + const host = AllServers[runningScriptObj.server]; + const growth = processSingleServerGrowth( + serv, + timesGrown, + Player, + host.cpuCores, + ); + runningScriptObj.log( + `'${serv.hostname}' grown by ${numeralWrapper.format( + growth * 100 - 100, + "0.000000%", + )} while offline`, + ); } + } - // Offline EXP gain - // A script's offline production will always be at most half of its online production. - const expGain = confidence * (runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime) * timePassed; - Player.gainHackingExp(expGain); + // Offline EXP gain + // A script's offline production will always be at most half of its online production. + const expGain = + confidence * + (runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime) * + timePassed; + Player.gainHackingExp(expGain); - // Update script stats - runningScriptObj.offlineRunningTime += timePassed; - runningScriptObj.offlineExpGained += expGain; + // Update script stats + runningScriptObj.offlineRunningTime += timePassed; + runningScriptObj.offlineExpGained += expGain; - // Weaken - for (const ip in runningScriptObj.dataMap) { - if (runningScriptObj.dataMap.hasOwnProperty(ip)) { - if (runningScriptObj.dataMap[ip][3] == 0 || runningScriptObj.dataMap[ip][3] == null) {continue;} - const serv = AllServers[ip]; - if (serv == null) {continue;} - const host = AllServers[runningScriptObj.server]; - const timesWeakened = Math.round(0.5 * runningScriptObj.dataMap[ip][3] / runningScriptObj.onlineRunningTime * timePassed); - runningScriptObj.log(`Called weaken() on ${serv.hostname} ${timesWeakened} times while offline`); - const coreBonus = 1+(host.cpuCores-1)/16; - serv.weaken(CONSTANTS.ServerWeakenAmount * timesWeakened * coreBonus); - } + // Weaken + for (const ip in runningScriptObj.dataMap) { + if (runningScriptObj.dataMap.hasOwnProperty(ip)) { + if ( + runningScriptObj.dataMap[ip][3] == 0 || + runningScriptObj.dataMap[ip][3] == null + ) { + continue; + } + const serv = AllServers[ip]; + if (serv == null) { + continue; + } + const host = AllServers[runningScriptObj.server]; + const timesWeakened = Math.round( + ((0.5 * runningScriptObj.dataMap[ip][3]) / + runningScriptObj.onlineRunningTime) * + timePassed, + ); + runningScriptObj.log( + `Called weaken() on ${serv.hostname} ${timesWeakened} times while offline`, + ); + const coreBonus = 1 + (host.cpuCores - 1) / 16; + serv.weaken(CONSTANTS.ServerWeakenAmount * timesWeakened * coreBonus); } + } } //Returns a RunningScript object matching the filename and arguments on the //designated server, and false otherwise export function findRunningScript(filename, args, server) { - for (var i = 0; i < server.runningScripts.length; ++i) { - if (server.runningScripts[i].filename === filename && - compareArrays(server.runningScripts[i].args, args)) { - return server.runningScripts[i]; - } + for (var i = 0; i < server.runningScripts.length; ++i) { + if ( + server.runningScripts[i].filename === filename && + compareArrays(server.runningScripts[i].args, args) + ) { + return server.runningScripts[i]; } - return null; + } + return null; } //Returns a RunningScript object matching the pid on the //designated server, and false otherwise export function findRunningScriptByPid(pid, server) { - for (var i = 0; i < server.runningScripts.length; ++i) { - if (server.runningScripts[i].pid === pid) { - return server.runningScripts[i]; - } + for (var i = 0; i < server.runningScripts.length; ++i) { + if (server.runningScripts[i].pid === pid) { + return server.runningScripts[i]; } - return null; + } + return null; } diff --git a/src/Script/ScriptHelpersTS.ts b/src/Script/ScriptHelpersTS.ts index d6ff294ba..0b2214425 100644 --- a/src/Script/ScriptHelpersTS.ts +++ b/src/Script/ScriptHelpersTS.ts @@ -1,4 +1,4 @@ // Script helper functions export function isScriptFilename(f: string): boolean { - return f.endsWith(".js") || f.endsWith(".script") || f.endsWith(".ns"); + return f.endsWith(".js") || f.endsWith(".script") || f.endsWith(".ns"); } diff --git a/src/Script/ScriptUrl.ts b/src/Script/ScriptUrl.ts index 903066690..037189147 100644 --- a/src/Script/ScriptUrl.ts +++ b/src/Script/ScriptUrl.ts @@ -1,9 +1,9 @@ export class ScriptUrl { - filename: string; - url: string; + filename: string; + url: string; - constructor(filename: string, url: string) { - this.filename = filename; - this.url = url; - } + constructor(filename: string, url: string) { + this.filename = filename; + this.url = url; + } } diff --git a/src/ScriptEditor/CursorPositions.ts b/src/ScriptEditor/CursorPositions.ts index e960fc235..896927814 100644 --- a/src/ScriptEditor/CursorPositions.ts +++ b/src/ScriptEditor/CursorPositions.ts @@ -1,29 +1,29 @@ export type Position = { - row: number; - column: number; + row: number; + column: number; }; export class PositionTracker { - positions: Map; + positions: Map; - constructor() { - this.positions = new Map(); - } + constructor() { + this.positions = new Map(); + } - saveCursor(filename: string, pos: Position): void { - this.positions.set(filename, pos); - } + saveCursor(filename: string, pos: Position): void { + this.positions.set(filename, pos); + } - getCursor(filename: string): Position { - const position = this.positions.get(filename); - if (!position) { - return { - row: -1, - column: -1, - }; - } - return position; - } + getCursor(filename: string): Position { + const position = this.positions.get(filename); + if (!position) { + return { + row: -1, + column: -1, + }; + } + return position; + } } -export const CursorPositions: PositionTracker = new PositionTracker(); \ No newline at end of file +export const CursorPositions: PositionTracker = new PositionTracker(); diff --git a/src/ScriptEditor/NetscriptDefinitions.ts b/src/ScriptEditor/NetscriptDefinitions.ts index 8220487c5..9ee0da624 100644 --- a/src/ScriptEditor/NetscriptDefinitions.ts +++ b/src/ScriptEditor/NetscriptDefinitions.ts @@ -155,4 +155,4 @@ export const libSource = `interface NS { exploit(): void; bypass(doc: any): void; flags(data: any): any; -}` \ No newline at end of file +}`; diff --git a/src/ScriptEditor/ui/Options.ts b/src/ScriptEditor/ui/Options.ts index da85d2753..523d0e3e7 100644 --- a/src/ScriptEditor/ui/Options.ts +++ b/src/ScriptEditor/ui/Options.ts @@ -1,5 +1,4 @@ - export interface Options { - theme: string; - insertSpaces: boolean; -} \ No newline at end of file + theme: string; + insertSpaces: boolean; +} diff --git a/src/ScriptEditor/ui/OptionsPopup.tsx b/src/ScriptEditor/ui/OptionsPopup.tsx index 72e4e900c..b0df9306c 100644 --- a/src/ScriptEditor/ui/OptionsPopup.tsx +++ b/src/ScriptEditor/ui/OptionsPopup.tsx @@ -1,39 +1,49 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { Options } from "./Options"; import { StdButton } from "../../ui/React/StdButton"; import { removePopup } from "../../ui/React/createPopup"; interface IProps { - id: string; - options: Options; - save: (options: Options) => void; + id: string; + options: Options; + save: (options: Options) => void; } export function OptionsPopup(props: IProps): React.ReactElement { - const [theme, setTheme] = useState(props.options.theme); - const [insertSpaces, setInsertSpaces] = useState(props.options.insertSpaces); + const [theme, setTheme] = useState(props.options.theme); + const [insertSpaces, setInsertSpaces] = useState(props.options.insertSpaces); - function save() { - props.save({ - theme: theme, - insertSpaces: insertSpaces, - }); - removePopup(props.id); - } + function save() { + props.save({ + theme: theme, + insertSpaces: insertSpaces, + }); + removePopup(props.id); + } - return (
    -
    -

    Theme:

    - -
    -
    -

    Use whitespace over tabs:

    - setInsertSpaces(event.target.checked)} checked={insertSpaces} /> -
    -
    - -
    ); -} \ No newline at end of file + return ( +
    +
    +

    Theme:

    + +
    +
    +

    Use whitespace over tabs:

    + setInsertSpaces(event.target.checked)} + checked={insertSpaces} + /> +
    +
    + +
    + ); +} diff --git a/src/ScriptEditor/ui/Root.tsx b/src/ScriptEditor/ui/Root.tsx index 2a27ae2a0..19f5ac774 100644 --- a/src/ScriptEditor/ui/Root.tsx +++ b/src/ScriptEditor/ui/Root.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef } from "react"; import { StdButton } from "../../ui/React/StdButton"; import Editor from "@monaco-editor/react"; import * as monaco from "monaco-editor"; @@ -6,7 +6,7 @@ type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor; import { createPopup } from "../../ui/React/createPopup"; import { OptionsPopup } from "./OptionsPopup"; import { Options } from "./Options"; -import { js_beautify as beautifyCode } from 'js-beautify'; +import { js_beautify as beautifyCode } from "js-beautify"; import { isValidFilePath } from "../../Terminal/DirectoryHelpers"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { IEngine } from "../../IEngine"; @@ -25,40 +25,39 @@ import { WorkerScript } from "../../Netscript/WorkerScript"; import { Settings } from "../../Settings/Settings"; import { GetServerByHostname } from "../../Server/ServerHelpers"; import { - iTutorialNextStep, - ITutorial, - iTutorialSteps, + iTutorialNextStep, + ITutorial, + iTutorialSteps, } from "../../InteractiveTutorial"; let symbols: string[] = []; -(function() { - const ns = NetscriptFunctions(({} as WorkerScript)); +(function () { + const ns = NetscriptFunctions({} as WorkerScript); - function populate(ns: any): string[] { - let symbols: string[] = []; - const keys = Object.keys(ns); - for(const key of keys) { - if(typeof ns[key] === 'object') { - symbols.push(key); - symbols = symbols.concat(populate(ns[key])); - } - if(typeof ns[key] === 'function') { - symbols.push(key); - } - } - return symbols; + function populate(ns: any): string[] { + let symbols: string[] = []; + const keys = Object.keys(ns); + for (const key of keys) { + if (typeof ns[key] === "object") { + symbols.push(key); + symbols = symbols.concat(populate(ns[key])); + } + if (typeof ns[key] === "function") { + symbols.push(key); + } } - symbols = populate(ns); + return symbols; + } + symbols = populate(ns); })(); interface IProps { - filename: string; - code: string; - player: IPlayer; - engine: IEngine; + filename: string; + code: string; + player: IPlayer; + engine: IEngine; } - /* */ @@ -77,254 +76,321 @@ let lastCode = ""; let lastPosition: monaco.Position | null = null; export function Root(props: IProps): React.ReactElement { - const editorRef = useRef(null); - const [filename, setFilename] = useState(props.filename ? props.filename : lastFilename); - const [code, setCode] = useState(props.code ? props.code : lastCode); - const [ram, setRAM] = useState('RAM: ???'); - const [options, setOptions] = useState({ - theme: Settings.MonacoTheme, - insertSpaces: Settings.MonacoInsertSpaces, + const editorRef = useRef(null); + const [filename, setFilename] = useState( + props.filename ? props.filename : lastFilename, + ); + const [code, setCode] = useState(props.code ? props.code : lastCode); + const [ram, setRAM] = useState("RAM: ???"); + const [options, setOptions] = useState({ + theme: Settings.MonacoTheme, + insertSpaces: Settings.MonacoInsertSpaces, + }); + + // store the last known state in case we need to restart without nano. + useEffect(() => { + if (props.filename === "") return; + lastFilename = props.filename; + lastCode = props.code; + lastPosition = null; + }, []); + + function save(): void { + if (editorRef.current !== null) { + const position = editorRef.current.getPosition(); + if (position !== null) { + CursorPositions.saveCursor(filename, { + row: position.lineNumber, + column: position.column, + }); + } + } + lastPosition = null; + + // TODO(hydroflame): re-enable the tutorial. + if ( + ITutorial.isRunning && + ITutorial.currStep === iTutorialSteps.TerminalTypeScript + ) { + //Make sure filename + code properly follow tutorial + if (filename !== "n00dles.script") { + dialogBoxCreate("Leave the script name as 'n00dles'!"); + return; + } + if ( + code.replace(/\s/g, "").indexOf("while(true){hack('n00dles');}") == -1 + ) { + dialogBoxCreate("Please copy and paste the code from the tutorial!"); + return; + } + + //Save the script + const server = props.player.getCurrentServer(); + if (server === null) + throw new Error("Server should not be null but it is."); + for (let i = 0; i < server.scripts.length; i++) { + if (filename == server.scripts[i].filename) { + server.scripts[i].saveScript( + code, + props.player.currentServer, + server.scripts, + ); + props.engine.loadTerminalContent(); + return iTutorialNextStep(); + } + } + + // If the current script does NOT exist, create a new one + const script = new Script(); + script.saveScript(code, props.player.currentServer, server.scripts); + server.scripts.push(script); + + return iTutorialNextStep(); + } + + if (filename == "") { + dialogBoxCreate("You must specify a filename!"); + return; + } + + if (filename !== ".fconf" && !isValidFilePath(filename)) { + dialogBoxCreate( + "Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension.", + ); + return; + } + + const server = props.player.getCurrentServer(); + if (server === null) + throw new Error("Server should not be null but it is."); + if (filename === ".fconf") { + try { + parseFconfSettings(code); + } catch (e) { + dialogBoxCreate(`Invalid .fconf file: ${e}`); + return; + } + } else if (isScriptFilename(filename)) { + //If the current script already exists on the server, overwrite it + for (let i = 0; i < server.scripts.length; i++) { + if (filename == server.scripts[i].filename) { + server.scripts[i].saveScript( + code, + props.player.currentServer, + server.scripts, + ); + props.engine.loadTerminalContent(); + return; + } + } + + //If the current script does NOT exist, create a new one + const script = new Script(); + script.saveScript(code, props.player.currentServer, server.scripts); + server.scripts.push(script); + } else if (filename.endsWith(".txt")) { + for (let i = 0; i < server.textFiles.length; ++i) { + if (server.textFiles[i].fn === filename) { + server.textFiles[i].write(code); + props.engine.loadTerminalContent(); + return; + } + } + const textFile = new TextFile(filename, code); + server.textFiles.push(textFile); + } else { + dialogBoxCreate( + "Invalid filename. Must be either a script (.script, .js, or .ns) or " + + " or text file (.txt)", + ); + return; + } + props.engine.loadTerminalContent(); + } + + function beautify(): void { + if (editorRef.current === null) return; + const pretty = beautifyCode(code, { + indent_with_tabs: !options.insertSpaces, + indent_size: 4, + brace_style: "preserve-inline", }); + editorRef.current.setValue(pretty); + } - // store the last known state in case we need to restart without nano. - useEffect(() => { - if(props.filename === "") return; - lastFilename = props.filename; - lastCode = props.code; - lastPosition = null; - }, []); + function onFilenameChange(event: React.ChangeEvent): void { + lastFilename = filename; + setFilename(event.target.value); + } - function save(): void { - if(editorRef.current !== null) { - const position = editorRef.current.getPosition(); - if(position !== null) { - CursorPositions.saveCursor(filename, { - row: position.lineNumber, - column: position.column, - }); - } - } - lastPosition = null; + function openOptions(): void { + const id = "script-editor-options-popup"; + const newOptions = { + theme: "", + insertSpaces: false, + }; + Object.assign(newOptions, options); + createPopup(id, OptionsPopup, { + id: id, + options: newOptions, + save: (options: Options) => { + setOptions(options); + Settings.MonacoTheme = options.theme; + Settings.MonacoInsertSpaces = options.insertSpaces; + }, + }); + } - // TODO(hydroflame): re-enable the tutorial. - if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) { - //Make sure filename + code properly follow tutorial - if (filename !== "n00dles.script") { - dialogBoxCreate("Leave the script name as 'n00dles'!"); - return; - } - if (code.replace(/\s/g, "").indexOf("while(true){hack('n00dles');}") == -1) { - dialogBoxCreate("Please copy and paste the code from the tutorial!"); - return; - } - - //Save the script - const server = props.player.getCurrentServer(); - if(server === null) throw new Error('Server should not be null but it is.'); - for (let i = 0; i < server.scripts.length; i++) { - if (filename == server.scripts[i].filename) { - server.scripts[i].saveScript(code, props.player.currentServer, server.scripts); - props.engine.loadTerminalContent(); - return iTutorialNextStep(); - } - } - - // If the current script does NOT exist, create a new one - const script = new Script(); - script.saveScript(code, props.player.currentServer, server.scripts); - server.scripts.push(script); - - return iTutorialNextStep(); - } - - if (filename == "") { - dialogBoxCreate("You must specify a filename!"); - return; - } - - if (filename !== ".fconf" && !isValidFilePath(filename)) { - dialogBoxCreate("Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension."); - return; - } - - const server = props.player.getCurrentServer(); - if(server === null) throw new Error('Server should not be null but it is.'); - if (filename === ".fconf") { - try { - parseFconfSettings(code); - } catch(e) { - dialogBoxCreate(`Invalid .fconf file: ${e}`); - return; - } - } else if (isScriptFilename(filename)) { - //If the current script already exists on the server, overwrite it - for (let i = 0; i < server.scripts.length; i++) { - if (filename == server.scripts[i].filename) { - server.scripts[i].saveScript(code, props.player.currentServer, server.scripts); - props.engine.loadTerminalContent(); - return; - } - } - - //If the current script does NOT exist, create a new one - const script = new Script(); - script.saveScript(code, props.player.currentServer, server.scripts); - server.scripts.push(script); - } else if (filename.endsWith(".txt")) { - for (let i = 0; i < server.textFiles.length; ++i) { - if (server.textFiles[i].fn === filename) { - server.textFiles[i].write(code); - props.engine.loadTerminalContent(); - return; - } - } - const textFile = new TextFile(filename, code); - server.textFiles.push(textFile); - } else { - dialogBoxCreate("Invalid filename. Must be either a script (.script, .js, or .ns) or " + - " or text file (.txt)") - return; - } - props.engine.loadTerminalContent(); + function updateCode(newCode?: string): void { + if (newCode === undefined) return; + lastCode = newCode; + if (editorRef.current !== null) { + lastPosition = editorRef.current.getPosition(); } + setCode(newCode); + } - function beautify(): void { - if (editorRef.current === null) return; - const pretty = beautifyCode(code, { - indent_with_tabs: !options.insertSpaces, - indent_size: 4, - brace_style: "preserve-inline", - }); - editorRef.current.setValue(pretty); + async function updateRAM(): Promise { + const codeCopy = code + ""; + const ramUsage = await calculateRamUsage( + codeCopy, + props.player.getCurrentServer().scripts, + ); + if (ramUsage > 0) { + setRAM("RAM: " + numeralWrapper.formatRAM(ramUsage)); + return; } - - function onFilenameChange(event: React.ChangeEvent): void { - lastFilename = filename; - setFilename(event.target.value); + switch (ramUsage) { + case RamCalculationErrorCode.ImportError: { + setRAM("RAM: Import Error"); + break; + } + case RamCalculationErrorCode.URLImportError: { + setRAM("RAM: HTTP Import Error"); + break; + } + case RamCalculationErrorCode.SyntaxError: + default: { + setRAM("RAM: Syntax Error"); + break; + } } + return new Promise(() => undefined); + } - function openOptions(): void { - const id="script-editor-options-popup"; - const newOptions = { - theme: '', - insertSpaces: false, - }; - Object.assign(newOptions, options); - createPopup(id, OptionsPopup, { - id: id, - options: newOptions, - save: (options: Options) => { - setOptions(options); - Settings.MonacoTheme = options.theme; - Settings.MonacoInsertSpaces = options.insertSpaces; - }, - }); + useEffect(() => { + const id = setInterval(updateRAM, 1000); + return () => clearInterval(id); + }, [code]); + + useEffect(() => { + function maybeSave(event: KeyboardEvent) { + if (Settings.DisableHotkeys) return; + //Ctrl + b + if (event.keyCode == 66 && (event.ctrlKey || event.metaKey)) { + event.preventDefault(); + save(); + } } + document.addEventListener("keydown", maybeSave); + return () => document.removeEventListener("keydown", maybeSave); + }); - function updateCode(newCode?: string): void { - if(newCode === undefined) return; - lastCode = newCode; - if(editorRef.current !== null) { - lastPosition = editorRef.current.getPosition(); + function onMount(editor: IStandaloneCodeEditor): void { + editorRef.current = editor; + if (editorRef.current === null) return; + const position = CursorPositions.getCursor(filename); + if (position.row !== -1) + editorRef.current.setPosition({ + lineNumber: position.row, + column: position.column, + }); + else if (lastPosition !== null) + editorRef.current.setPosition({ + lineNumber: lastPosition.lineNumber, + column: lastPosition.column + 1, + }); + editorRef.current.focus(); + } + + function beforeMount(monaco: any): void { + monaco.languages.registerCompletionItemProvider("javascript", { + provideCompletionItems: () => { + const suggestions = []; + for (const symbol of symbols) { + suggestions.push({ + label: symbol, + kind: monaco.languages.CompletionItemKind.Function, + insertText: symbol, + insertTextRules: + monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + }); } - setCode(newCode); - } + return { suggestions: suggestions }; + }, + }); + monaco.languages.typescript.javascriptDefaults.addExtraLib( + libSource, + "netscript.d.ts", + ); + monaco.languages.typescript.typescriptDefaults.addExtraLib( + libSource, + "netscript.d.ts", + ); + } - async function updateRAM(): Promise { - const codeCopy = code+""; - const ramUsage = await calculateRamUsage(codeCopy, props.player.getCurrentServer().scripts); - if (ramUsage > 0) { - setRAM("RAM: " + numeralWrapper.formatRAM(ramUsage)); - return; - } - switch (ramUsage) { - case RamCalculationErrorCode.ImportError: { - setRAM("RAM: Import Error"); - break; - } - case RamCalculationErrorCode.URLImportError: { - setRAM("RAM: HTTP Import Error"); - break; - } - case RamCalculationErrorCode.SyntaxError: - default: { - setRAM("RAM: Syntax Error"); - break; - } - } - return new Promise(() => undefined); - } - - useEffect(() => { - const id = setInterval(updateRAM, 1000); - return () => clearInterval(id); - }, [code]); - - useEffect(() => { - function maybeSave(event: KeyboardEvent) { - if (Settings.DisableHotkeys) return; - //Ctrl + b - if (event.keyCode == 66 && (event.ctrlKey || event.metaKey)) { - event.preventDefault(); - save(); - } - } - document.addEventListener('keydown', maybeSave); - return () => document.removeEventListener('keydown', maybeSave); - }) - - function onMount(editor: IStandaloneCodeEditor): void { - editorRef.current = editor; - if(editorRef.current === null) return; - const position = CursorPositions.getCursor(filename); - if(position.row !== -1) - editorRef.current.setPosition({lineNumber: position.row, column: position.column}); - else if(lastPosition !== null) - editorRef.current.setPosition({lineNumber: lastPosition.lineNumber, column: lastPosition.column+1}); - editorRef.current.focus(); - } - - function beforeMount(monaco: any): void { - monaco.languages.registerCompletionItemProvider('javascript', { - provideCompletionItems: () => { - const suggestions = []; - for(const symbol of symbols) { - suggestions.push({ - label: symbol, - kind: monaco.languages.CompletionItemKind.Function, - insertText: symbol, - insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, - }); - } - return { suggestions: suggestions }; - }, - }); - monaco.languages.typescript.javascriptDefaults.addExtraLib(libSource, 'netscript.d.ts'); - monaco.languages.typescript.typescriptDefaults.addExtraLib(libSource, 'netscript.d.ts'); - } - - return (
    -
    -

    Script name:

    - - -
    - Loading script editor!

    } - height="80%" - defaultLanguage="javascript" - defaultValue={code} - onChange={updateCode} - theme={options.theme} - options={options} + return ( +
    +
    +

    + {" "} + Script name: +

    + -
    - -

    {ram}

    - - Netscript Documentation -
    -
    ); + +
    + Loading script editor!

    } + height="80%" + defaultLanguage="javascript" + defaultValue={code} + onChange={updateCode} + theme={options.theme} + options={options} + /> +
    + +

    + {ram} +

    + + + Netscript Documentation + +
    +
    + ); } diff --git a/src/Server/AllServers.ts b/src/Server/AllServers.ts index dda3fa5a7..1ecd5aaf1 100644 --- a/src/Server/AllServers.ts +++ b/src/Server/AllServers.ts @@ -17,135 +17,147 @@ import { Reviver } from "../../utils/JSONReviver"; export let AllServers: IMap = {}; export function ipExists(ip: string): boolean { - return (AllServers[ip] != null); + return AllServers[ip] != null; } export function createUniqueRandomIp(): string { - const ip = createRandomIp(); + const ip = createRandomIp(); - // If the Ip already exists, recurse to create a new one - if (ipExists(ip)) { - return createRandomIp(); - } + // If the Ip already exists, recurse to create a new one + if (ipExists(ip)) { + return createRandomIp(); + } - return ip; + return ip; } // Saftely add a Server to the AllServers map export function AddToAllServers(server: Server | HacknetServer): void { - const serverIp = server.ip; - if (ipExists(serverIp)) { - console.warn(`IP of server that's being added: ${serverIp}`); - console.warn(`Hostname of the server thats being added: ${server.hostname}`); - console.warn(`The server that already has this IP is: ${AllServers[serverIp].hostname}`); - throw new Error("Error: Trying to add a server with an existing IP"); - } - - AllServers[serverIp] = server; + const serverIp = server.ip; + if (ipExists(serverIp)) { + console.warn(`IP of server that's being added: ${serverIp}`); + console.warn( + `Hostname of the server thats being added: ${server.hostname}`, + ); + console.warn( + `The server that already has this IP is: ${AllServers[serverIp].hostname}`, + ); + throw new Error("Error: Trying to add a server with an existing IP"); + } + + AllServers[serverIp] = server; } interface IServerParams { - hackDifficulty?: number; - hostname: string; - ip: string; - maxRam?: number; - moneyAvailable?: number; - numOpenPortsRequired: number; - organizationName: string; - requiredHackingSkill?: number; - serverGrowth?: number; + hackDifficulty?: number; + hostname: string; + ip: string; + maxRam?: number; + moneyAvailable?: number; + numOpenPortsRequired: number; + organizationName: string; + requiredHackingSkill?: number; + serverGrowth?: number; - [key: string]: any; + [key: string]: any; } export function initForeignServers(homeComputer: Server): void { - /* Create a randomized network for all the foreign servers */ - //Groupings for creating a randomized network - const networkLayers: Server[][] = []; - for (let i = 0; i < 15; i++) { - networkLayers.push([]); + /* Create a randomized network for all the foreign servers */ + //Groupings for creating a randomized network + const networkLayers: Server[][] = []; + for (let i = 0; i < 15; i++) { + networkLayers.push([]); + } + + // Essentially any property that is of type 'number | IMinMaxRange' + const propertiesToPatternMatch: string[] = [ + "hackDifficulty", + "moneyAvailable", + "requiredHackingSkill", + "serverGrowth", + ]; + + const toNumber = (value: any): any => { + switch (typeof value) { + case "number": + return value; + case "object": + return getRandomInt(value.min, value.max); + default: + throw Error( + `Do not know how to convert the type '${typeof value}' to a number`, + ); } + }; - // Essentially any property that is of type 'number | IMinMaxRange' - const propertiesToPatternMatch: string[] = [ - "hackDifficulty", - "moneyAvailable", - "requiredHackingSkill", - "serverGrowth", - ]; - - const toNumber = (value: any): any => { - switch (typeof value) { - case 'number': - return value; - case 'object': - return getRandomInt(value.min, value.max); - default: - throw Error(`Do not know how to convert the type '${typeof value}' to a number`); - } - } - - for (const metadata of serverMetadata) { - const serverParams: IServerParams = { - hostname: metadata.hostname, - ip: createUniqueRandomIp(), - numOpenPortsRequired: metadata.numOpenPortsRequired, - organizationName: metadata.organizationName, - }; - - if (metadata.maxRamExponent !== undefined) { - serverParams.maxRam = Math.pow(2, toNumber(metadata.maxRamExponent)); - } - - for (const prop of propertiesToPatternMatch) { - if (metadata[prop] !== undefined) { - serverParams[prop] = toNumber(metadata[prop]); - } - } - - const server = new Server(serverParams); - for (const filename of (metadata.literature || [])) { - server.messages.push(filename); - } - - if (metadata.specialName !== undefined) { - SpecialServerIps.addIp(metadata.specialName, server.ip); - } - - AddToAllServers(server); - if (metadata.networkLayer !== undefined) { - networkLayers[toNumber(metadata.networkLayer) - 1].push(server); - } - } - - /* Create a randomized network for all the foreign servers */ - const linkComputers = (server1: Server, server2: Server): void => { - server1.serversOnNetwork.push(server2.ip); - server2.serversOnNetwork.push(server1.ip); + for (const metadata of serverMetadata) { + const serverParams: IServerParams = { + hostname: metadata.hostname, + ip: createUniqueRandomIp(), + numOpenPortsRequired: metadata.numOpenPortsRequired, + organizationName: metadata.organizationName, }; - const getRandomArrayItem = (arr: any[]): any => arr[Math.floor(Math.random() * arr.length)]; - - const linkNetworkLayers = (network1: Server[], selectServer: () => Server): void => { - for (const server of network1) { - linkComputers(server, selectServer()); - } - }; - - // Connect the first tier of servers to the player's home computer - linkNetworkLayers(networkLayers[0], () => homeComputer); - for (let i = 1; i < networkLayers.length; i++) { - linkNetworkLayers(networkLayers[i], () => getRandomArrayItem(networkLayers[i - 1])); + if (metadata.maxRamExponent !== undefined) { + serverParams.maxRam = Math.pow(2, toNumber(metadata.maxRamExponent)); } + + for (const prop of propertiesToPatternMatch) { + if (metadata[prop] !== undefined) { + serverParams[prop] = toNumber(metadata[prop]); + } + } + + const server = new Server(serverParams); + for (const filename of metadata.literature || []) { + server.messages.push(filename); + } + + if (metadata.specialName !== undefined) { + SpecialServerIps.addIp(metadata.specialName, server.ip); + } + + AddToAllServers(server); + if (metadata.networkLayer !== undefined) { + networkLayers[toNumber(metadata.networkLayer) - 1].push(server); + } + } + + /* Create a randomized network for all the foreign servers */ + const linkComputers = (server1: Server, server2: Server): void => { + server1.serversOnNetwork.push(server2.ip); + server2.serversOnNetwork.push(server1.ip); + }; + + const getRandomArrayItem = (arr: any[]): any => + arr[Math.floor(Math.random() * arr.length)]; + + const linkNetworkLayers = ( + network1: Server[], + selectServer: () => Server, + ): void => { + for (const server of network1) { + linkComputers(server, selectServer()); + } + }; + + // Connect the first tier of servers to the player's home computer + linkNetworkLayers(networkLayers[0], () => homeComputer); + for (let i = 1; i < networkLayers.length; i++) { + linkNetworkLayers(networkLayers[i], () => + getRandomArrayItem(networkLayers[i - 1]), + ); + } } export function prestigeAllServers(): void { - for (const member in AllServers) { - delete AllServers[member]; - } - AllServers = {}; + for (const member in AllServers) { + delete AllServers[member]; + } + AllServers = {}; } export function loadAllServers(saveString: string): void { - AllServers = JSON.parse(saveString, Reviver); + AllServers = JSON.parse(saveString, Reviver); } diff --git a/src/Server/BaseServer.ts b/src/Server/BaseServer.ts index b86e1849e..bb37b62c8 100644 --- a/src/Server/BaseServer.ts +++ b/src/Server/BaseServer.ts @@ -15,282 +15,297 @@ import { createRandomIp } from "../../utils/IPAddress"; import { compareArrays } from "../../utils/helpers/compareArrays"; interface IConstructorParams { - adminRights?: boolean; - hostname: string; - ip?: string; - isConnectedTo?: boolean; - maxRam?: number; - organizationName?: string; + adminRights?: boolean; + hostname: string; + ip?: string; + isConnectedTo?: boolean; + maxRam?: number; + organizationName?: string; } interface writeResult { - success: boolean; - overwritten: boolean; + success: boolean; + overwritten: boolean; } export class BaseServer { - // Coding Contract files on this server - contracts: CodingContract[] = []; + // Coding Contract files on this server + contracts: CodingContract[] = []; - // How many CPU cores this server has. Maximum of 8. - // Currently, this only affects hacking missions - cpuCores = 1; + // How many CPU cores this server has. Maximum of 8. + // Currently, this only affects hacking missions + cpuCores = 1; - // Flag indicating whether the FTP port is open - ftpPortOpen = false; + // Flag indicating whether the FTP port is open + ftpPortOpen = false; - // Flag indicating whether player has admin/root access to this server - hasAdminRights = false; + // Flag indicating whether player has admin/root access to this server + hasAdminRights = false; - // Hostname. Must be unique - hostname = ""; + // Hostname. Must be unique + hostname = ""; - // Flag indicating whether HTTP Port is open - httpPortOpen = false; + // Flag indicating whether HTTP Port is open + httpPortOpen = false; - // IP Address. Must be unique - ip = ""; + // IP Address. Must be unique + ip = ""; - // Flag indicating whether player is curently connected to this server - isConnectedTo = false; + // Flag indicating whether player is curently connected to this server + isConnectedTo = false; - // RAM (GB) available on this server - maxRam = 0; + // RAM (GB) available on this server + maxRam = 0; - // Message files AND Literature files on this Server - // For Literature files, this array contains only the filename (string) - // For Messages, it contains the actual Message object - // TODO Separate literature files into its own property - messages: (Message | string)[] = []; + // Message files AND Literature files on this Server + // For Literature files, this array contains only the filename (string) + // For Messages, it contains the actual Message object + // TODO Separate literature files into its own property + messages: (Message | string)[] = []; - // Name of company/faction/etc. that this server belongs to. - // Optional, not applicable to all Servers - organizationName = ""; + // Name of company/faction/etc. that this server belongs to. + // Optional, not applicable to all Servers + organizationName = ""; - // Programs on this servers. Contains only the names of the programs - programs: string[] = []; + // Programs on this servers. Contains only the names of the programs + programs: string[] = []; - // RAM (GB) used. i.e. unavailable RAM - ramUsed = 0; + // RAM (GB) used. i.e. unavailable RAM + ramUsed = 0; - // RunningScript files on this server - runningScripts: RunningScript[] = []; + // RunningScript files on this server + runningScripts: RunningScript[] = []; - // Script files on this Server - scripts: Script[] = []; + // Script files on this Server + scripts: Script[] = []; - // Contains the IP Addresses of all servers that are immediately - // reachable from this one - serversOnNetwork: string[] = []; + // Contains the IP Addresses of all servers that are immediately + // reachable from this one + serversOnNetwork: string[] = []; - // Flag indicating whether SMTP Port is open - smtpPortOpen = false; + // Flag indicating whether SMTP Port is open + smtpPortOpen = false; - // Flag indicating whether SQL Port is open - sqlPortOpen = false; + // Flag indicating whether SQL Port is open + sqlPortOpen = false; - // Flag indicating whether the SSH Port is open - sshPortOpen = false; + // Flag indicating whether the SSH Port is open + sshPortOpen = false; - // Text files on this server - textFiles: TextFile[] = []; + // Text files on this server + textFiles: TextFile[] = []; - constructor(params: IConstructorParams={ hostname: "", ip: createRandomIp() }) { - this.ip = params.ip ? params.ip : createRandomIp(); + constructor( + params: IConstructorParams = { hostname: "", ip: createRandomIp() }, + ) { + this.ip = params.ip ? params.ip : createRandomIp(); - this.hostname = params.hostname; - this.organizationName = params.organizationName != null ? params.organizationName : ""; - this.isConnectedTo = params.isConnectedTo != null ? params.isConnectedTo : false; + this.hostname = params.hostname; + this.organizationName = + params.organizationName != null ? params.organizationName : ""; + this.isConnectedTo = + params.isConnectedTo != null ? params.isConnectedTo : false; - //Access information - this.hasAdminRights = params.adminRights != null ? params.adminRights : false; + //Access information + this.hasAdminRights = + params.adminRights != null ? params.adminRights : false; + } + + addContract(contract: CodingContract): void { + this.contracts.push(contract); + } + + getContract(contractName: string): CodingContract | null { + for (const contract of this.contracts) { + if (contract.fn === contractName) { + return contract; + } + } + return null; + } + + /** + * Find an actively running script on this server + * @param scriptName - Filename of script to search for + * @param scriptArgs - Arguments that script is being run with + * @returns RunningScript for the specified active script + * Returns null if no such script can be found + */ + getRunningScript( + scriptName: string, + scriptArgs: any[], + ): RunningScript | null { + for (const rs of this.runningScripts) { + if (rs.filename === scriptName && compareArrays(rs.args, scriptArgs)) { + return rs; + } } - addContract(contract: CodingContract): void { - this.contracts.push(contract); + return null; + } + + /** + * Given the name of the script, returns the corresponding + * Script object on the server (if it exists) + */ + getScript(scriptName: string): Script | null { + for (let i = 0; i < this.scripts.length; i++) { + if (this.scripts[i].filename === scriptName) { + return this.scripts[i]; + } } - getContract(contractName: string): CodingContract | null { - for (const contract of this.contracts) { - if (contract.fn === contractName) { - return contract; - } + return null; + } + + /** + * Returns boolean indicating whether the given script is running on this server + */ + isRunning(fn: string): boolean { + for (const runningScriptObj of this.runningScripts) { + if (runningScriptObj.filename === fn) { + return true; + } + } + + return false; + } + + removeContract(contract: CodingContract): void { + if (contract instanceof CodingContract) { + this.contracts = this.contracts.filter((c) => { + return c.fn !== contract.fn; + }); + } else { + this.contracts = this.contracts.filter((c) => { + return c.fn !== contract; + }); + } + } + + /** + * Remove a file from the server + * @param fn {string} Name of file to be deleted + * @returns {IReturnStatus} Return status object indicating whether or not file was deleted + */ + removeFile(fn: string): IReturnStatus { + if ( + fn.endsWith(".exe") || + fn.match(/^.+\.exe-\d+(?:\.\d*)?%-INC$/) != null + ) { + for (let i = 0; i < this.programs.length; ++i) { + if (this.programs[i] === fn) { + this.programs.splice(i, 1); + return { res: true }; } - return null; - } + } + } else if (isScriptFilename(fn)) { + for (let i = 0; i < this.scripts.length; ++i) { + if (this.scripts[i].filename === fn) { + if (this.isRunning(fn)) { + return { + res: false, + msg: "Cannot delete a script that is currently running!", + }; + } - /** - * Find an actively running script on this server - * @param scriptName - Filename of script to search for - * @param scriptArgs - Arguments that script is being run with - * @returns RunningScript for the specified active script - * Returns null if no such script can be found - */ - getRunningScript(scriptName: string, scriptArgs: any[]): RunningScript | null { - for (const rs of this.runningScripts) { - if (rs.filename === scriptName && compareArrays(rs.args, scriptArgs)) { - return rs; - } + this.scripts.splice(i, 1); + return { res: true }; } - - return null; - } - - /** - * Given the name of the script, returns the corresponding - * Script object on the server (if it exists) - */ - getScript(scriptName: string): Script | null { - for (let i = 0; i < this.scripts.length; i++) { - if (this.scripts[i].filename === scriptName) { - return this.scripts[i]; - } + } + } else if (fn.endsWith(".lit")) { + for (let i = 0; i < this.messages.length; ++i) { + const f = this.messages[i]; + if (typeof f === "string" && f === fn) { + this.messages.splice(i, 1); + return { res: true }; } - - return null; - } - - /** - * Returns boolean indicating whether the given script is running on this server - */ - isRunning(fn: string): boolean { - for (const runningScriptObj of this.runningScripts) { - if (runningScriptObj.filename === fn) { - return true; - } + } + } else if (fn.endsWith(".txt")) { + for (let i = 0; i < this.textFiles.length; ++i) { + if (this.textFiles[i].fn === fn) { + this.textFiles.splice(i, 1); + return { res: true }; } - - return false; - } - - removeContract(contract: CodingContract): void { - if (contract instanceof CodingContract) { - this.contracts = this.contracts.filter((c) => { - return c.fn !== contract.fn; - }); - } else { - this.contracts = this.contracts.filter((c) => { - return c.fn !== contract; - }); + } + } else if (fn.endsWith(".cct")) { + for (let i = 0; i < this.contracts.length; ++i) { + if (this.contracts[i].fn === fn) { + this.contracts.splice(i, 1); + return { res: true }; } + } } - /** - * Remove a file from the server - * @param fn {string} Name of file to be deleted - * @returns {IReturnStatus} Return status object indicating whether or not file was deleted - */ - removeFile(fn: string): IReturnStatus { - if (fn.endsWith(".exe") || fn.match(/^.+\.exe-\d+(?:\.\d*)?%-INC$/) != null) { - for (let i = 0; i < this.programs.length; ++i) { - if (this.programs[i] === fn) { - this.programs.splice(i, 1); - return { res: true }; - } - } - } else if (isScriptFilename(fn)) { - for (let i = 0; i < this.scripts.length; ++i) { - if (this.scripts[i].filename === fn) { - if (this.isRunning(fn)) { - return { - res: false, - msg: "Cannot delete a script that is currently running!", - }; - } + return { res: false, msg: "No such file exists" }; + } - this.scripts.splice(i, 1); - return { res: true }; - } - } - } else if (fn.endsWith(".lit")) { - for (let i = 0; i < this.messages.length; ++i) { - const f = this.messages[i]; - if (typeof f === "string" && f === fn) { - this.messages.splice(i, 1); - return { res: true }; - } - } - } else if (fn.endsWith(".txt")) { - for (let i = 0; i < this.textFiles.length; ++i) { - if (this.textFiles[i].fn === fn) { - this.textFiles.splice(i, 1); - return { res: true }; - } - } - } else if (fn.endsWith(".cct")) { - for (let i = 0; i < this.contracts.length; ++i) { - if (this.contracts[i].fn === fn) { - this.contracts.splice(i, 1); - return { res: true }; - } - } - } + /** + * Called when a script is run on this server. + * All this function does is add a RunningScript object to the + * `runningScripts` array. It does NOT check whether the script actually can + * be run. + */ + runScript(script: RunningScript): void { + this.runningScripts.push(script); + } - return { res: false, msg: "No such file exists" }; + setMaxRam(ram: number): void { + this.maxRam = ram; + } + + /** + * Write to a script file + * Overwrites existing files. Creates new files if the script does not eixst + */ + writeToScriptFile(fn: string, code: string): writeResult { + const ret = { success: false, overwritten: false }; + if (!isValidFilePath(fn) || !isScriptFilename(fn)) { + return ret; } - /** - * Called when a script is run on this server. - * All this function does is add a RunningScript object to the - * `runningScripts` array. It does NOT check whether the script actually can - * be run. - */ - runScript(script: RunningScript): void { - this.runningScripts.push(script); - } - - setMaxRam(ram: number): void { - this.maxRam = ram; - } - - /** - * Write to a script file - * Overwrites existing files. Creates new files if the script does not eixst - */ - writeToScriptFile(fn: string, code: string): writeResult { - const ret = { success: false, overwritten: false }; - if (!isValidFilePath(fn) || !isScriptFilename(fn)) { return ret; } - - // Check if the script already exists, and overwrite it if it does - for (let i = 0; i < this.scripts.length; ++i) { - if (fn === this.scripts[i].filename) { - const script = this.scripts[i]; - script.code = code; - script.updateRamUsage(this.scripts); - script.markUpdated(); - ret.overwritten = true; - ret.success = true; - return ret; - } - } - - // Otherwise, create a new script - const newScript = new Script(fn, code, this.ip, this.scripts); - this.scripts.push(newScript); + // Check if the script already exists, and overwrite it if it does + for (let i = 0; i < this.scripts.length; ++i) { + if (fn === this.scripts[i].filename) { + const script = this.scripts[i]; + script.code = code; + script.updateRamUsage(this.scripts); + script.markUpdated(); + ret.overwritten = true; ret.success = true; return ret; + } } - // Write to a text file - // Overwrites existing files. Creates new files if the text file does not exist - writeToTextFile(fn: string, txt: string): writeResult { - const ret = { success: false, overwritten: false }; - if (!isValidFilePath(fn) || !fn.endsWith("txt")) { return ret; } + // Otherwise, create a new script + const newScript = new Script(fn, code, this.ip, this.scripts); + this.scripts.push(newScript); + ret.success = true; + return ret; + } - // Check if the text file already exists, and overwrite if it does - for (let i = 0; i < this.textFiles.length; ++i) { - if (this.textFiles[i].fn === fn) { - ret.overwritten = true; - this.textFiles[i].text = txt; - ret.success = true; - return ret; - } - } + // Write to a text file + // Overwrites existing files. Creates new files if the text file does not exist + writeToTextFile(fn: string, txt: string): writeResult { + const ret = { success: false, overwritten: false }; + if (!isValidFilePath(fn) || !fn.endsWith("txt")) { + return ret; + } - // Otherwise create a new text file - const newFile = new TextFile(fn, txt); - this.textFiles.push(newFile); + // Check if the text file already exists, and overwrite if it does + for (let i = 0; i < this.textFiles.length; ++i) { + if (this.textFiles[i].fn === fn) { + ret.overwritten = true; + this.textFiles[i].text = txt; ret.success = true; return ret; + } } + + // Otherwise create a new text file + const newFile = new TextFile(fn, txt); + this.textFiles.push(newFile); + ret.success = true; + return ret; + } } diff --git a/src/Server/Server.ts b/src/Server/Server.ts index f33f5b0fc..4c7e866fd 100644 --- a/src/Server/Server.ts +++ b/src/Server/Server.ts @@ -5,159 +5,178 @@ import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { createRandomString } from "../utils/helpers/createRandomString"; import { createRandomIp } from "../../utils/IPAddress"; -import { Generic_fromJSON, - Generic_toJSON, - Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; export interface IConstructorParams { - adminRights?: boolean; - hackDifficulty?: number; - hostname: string; - ip?: string; - isConnectedTo?: boolean; - maxRam?: number; - moneyAvailable?: number; - numOpenPortsRequired?: number; - organizationName?: string; - purchasedByPlayer?: boolean; - requiredHackingSkill?: number; - serverGrowth?: number; + adminRights?: boolean; + hackDifficulty?: number; + hostname: string; + ip?: string; + isConnectedTo?: boolean; + maxRam?: number; + moneyAvailable?: number; + numOpenPortsRequired?: number; + organizationName?: string; + purchasedByPlayer?: boolean; + requiredHackingSkill?: number; + serverGrowth?: number; } export class Server extends BaseServer { + // Flag indicating whether this server has a backdoor installed by a player + backdoorInstalled = false; - // Flag indicating whether this server has a backdoor installed by a player - backdoorInstalled = false; + // Initial server security level + // (i.e. security level when the server was created) + baseDifficulty = 1; - // Initial server security level - // (i.e. security level when the server was created) - baseDifficulty = 1; + // Server Security Level + hackDifficulty = 1; - // Server Security Level - hackDifficulty = 1; + // Minimum server security level that this server can be weakened to + minDifficulty = 1; - // Minimum server security level that this server can be weakened to - minDifficulty = 1; + // How much money currently resides on the server and can be hacked + moneyAvailable = 0; - // How much money currently resides on the server and can be hacked - moneyAvailable = 0; + // Maximum amount of money that this server can hold + moneyMax = 0; - // Maximum amount of money that this server can hold - moneyMax = 0; + // Number of open ports required in order to gain admin/root access + numOpenPortsRequired = 5; - // Number of open ports required in order to gain admin/root access - numOpenPortsRequired = 5; + // How many ports are currently opened on the server + openPortCount = 0; - // How many ports are currently opened on the server - openPortCount = 0; + // Flag indicating wehther this is a purchased server + purchasedByPlayer = false; - // Flag indicating wehther this is a purchased server - purchasedByPlayer = false; + // Hacking level required to hack this server + requiredHackingSkill = 1; - // Hacking level required to hack this server - requiredHackingSkill = 1; + // Parameter that affects how effectively this server's money can + // be increased using the grow() Netscript function + serverGrowth = 1; - // Parameter that affects how effectively this server's money can - // be increased using the grow() Netscript function - serverGrowth = 1; + constructor( + params: IConstructorParams = { hostname: "", ip: createRandomIp() }, + ) { + super(params); - constructor(params: IConstructorParams={hostname: "", ip: createRandomIp() }) { - super(params); - - // "hacknet-node-X" hostnames are reserved for Hacknet Servers - if (this.hostname.startsWith("hacknet-node-")) { - this.hostname = createRandomString(10); - } - - this.purchasedByPlayer = params.purchasedByPlayer != null ? params.purchasedByPlayer : false; - - //RAM, CPU speed and Scripts - this.maxRam = params.maxRam != null ? params.maxRam : 0; //GB - - /* Hacking information (only valid for "foreign" aka non-purchased servers) */ - this.requiredHackingSkill = params.requiredHackingSkill != null ? params.requiredHackingSkill : 1; - this.moneyAvailable = params.moneyAvailable != null ? params.moneyAvailable * BitNodeMultipliers.ServerStartingMoney : 0; - this.moneyMax = 25 * this.moneyAvailable * BitNodeMultipliers.ServerMaxMoney; - - //Hack Difficulty is synonymous with server security. Base Difficulty = Starting difficulty - this.hackDifficulty = params.hackDifficulty != null ? params.hackDifficulty * BitNodeMultipliers.ServerStartingSecurity : 1; - this.baseDifficulty = this.hackDifficulty; - this.minDifficulty = Math.max(1, Math.round(this.hackDifficulty / 3)); - this.serverGrowth = params.serverGrowth != null ? params.serverGrowth : 1; //Integer from 0 to 100. Affects money increase from grow() - - //Port information, required for porthacking servers to get admin rights - this.numOpenPortsRequired = params.numOpenPortsRequired != null ? params.numOpenPortsRequired : 5; + // "hacknet-node-X" hostnames are reserved for Hacknet Servers + if (this.hostname.startsWith("hacknet-node-")) { + this.hostname = createRandomString(10); } - /** - * Ensures that the server's difficulty (server security) doesn't get too high - */ - capDifficulty(): void { - if (this.hackDifficulty < this.minDifficulty) {this.hackDifficulty = this.minDifficulty;} - if (this.hackDifficulty < 1) {this.hackDifficulty = 1;} + this.purchasedByPlayer = + params.purchasedByPlayer != null ? params.purchasedByPlayer : false; - // Place some arbitrarily limit that realistically should never happen unless someone is - // screwing around with the game - if (this.hackDifficulty > 100) {this.hackDifficulty = 100;} + //RAM, CPU speed and Scripts + this.maxRam = params.maxRam != null ? params.maxRam : 0; //GB + + /* Hacking information (only valid for "foreign" aka non-purchased servers) */ + this.requiredHackingSkill = + params.requiredHackingSkill != null ? params.requiredHackingSkill : 1; + this.moneyAvailable = + params.moneyAvailable != null + ? params.moneyAvailable * BitNodeMultipliers.ServerStartingMoney + : 0; + this.moneyMax = + 25 * this.moneyAvailable * BitNodeMultipliers.ServerMaxMoney; + + //Hack Difficulty is synonymous with server security. Base Difficulty = Starting difficulty + this.hackDifficulty = + params.hackDifficulty != null + ? params.hackDifficulty * BitNodeMultipliers.ServerStartingSecurity + : 1; + this.baseDifficulty = this.hackDifficulty; + this.minDifficulty = Math.max(1, Math.round(this.hackDifficulty / 3)); + this.serverGrowth = params.serverGrowth != null ? params.serverGrowth : 1; //Integer from 0 to 100. Affects money increase from grow() + + //Port information, required for porthacking servers to get admin rights + this.numOpenPortsRequired = + params.numOpenPortsRequired != null ? params.numOpenPortsRequired : 5; + } + + /** + * Ensures that the server's difficulty (server security) doesn't get too high + */ + capDifficulty(): void { + if (this.hackDifficulty < this.minDifficulty) { + this.hackDifficulty = this.minDifficulty; + } + if (this.hackDifficulty < 1) { + this.hackDifficulty = 1; } - /** - * Change this server's minimum security - * @param n - Value by which to increase/decrease the server's minimum security - * @param perc - Whether it should be changed by a percentage, or a flat value - */ - changeMinimumSecurity(n: number, perc=false): void { - if (perc) { - this.minDifficulty *= n; - } else { - this.minDifficulty += n; - } + // Place some arbitrarily limit that realistically should never happen unless someone is + // screwing around with the game + if (this.hackDifficulty > 100) { + this.hackDifficulty = 100; + } + } - // Server security cannot go below 1 - this.minDifficulty = Math.max(1, this.minDifficulty); + /** + * Change this server's minimum security + * @param n - Value by which to increase/decrease the server's minimum security + * @param perc - Whether it should be changed by a percentage, or a flat value + */ + changeMinimumSecurity(n: number, perc = false): void { + if (perc) { + this.minDifficulty *= n; + } else { + this.minDifficulty += n; } - /** - * Change this server's maximum money - * @param n - Value by which to change the server's maximum money - * @param perc - Whether it should be changed by a percentage, or a flat value - */ - changeMaximumMoney(n: number, perc=false): void { - if (perc) { - this.moneyMax *= n; - } else { - this.moneyMax += n; - } - } + // Server security cannot go below 1 + this.minDifficulty = Math.max(1, this.minDifficulty); + } - /** - * Strengthens a server's security level (difficulty) by the specified amount - */ - fortify(amt: number): void { - this.hackDifficulty += amt; - this.capDifficulty(); + /** + * Change this server's maximum money + * @param n - Value by which to change the server's maximum money + * @param perc - Whether it should be changed by a percentage, or a flat value + */ + changeMaximumMoney(n: number, perc = false): void { + if (perc) { + this.moneyMax *= n; + } else { + this.moneyMax += n; } + } - /** - * Lowers the server's security level (difficulty) by the specified amount) - */ - weaken(amt: number): void { - this.hackDifficulty -= (amt * BitNodeMultipliers.ServerWeakenRate); - this.capDifficulty(); - } + /** + * Strengthens a server's security level (difficulty) by the specified amount + */ + fortify(amt: number): void { + this.hackDifficulty += amt; + this.capDifficulty(); + } - /** - * Serialize the current object to a JSON save state - */ - toJSON(): any { - return Generic_toJSON("Server", this); - } + /** + * Lowers the server's security level (difficulty) by the specified amount) + */ + weaken(amt: number): void { + this.hackDifficulty -= amt * BitNodeMultipliers.ServerWeakenRate; + this.capDifficulty(); + } - // Initializes a Server Object from a JSON save state - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Server { - return Generic_fromJSON(Server, value.data); - } + /** + * Serialize the current object to a JSON save state + */ + toJSON(): any { + return Generic_toJSON("Server", this); + } + + // Initializes a Server Object from a JSON save state + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Server { + return Generic_fromJSON(Server, value.data); + } } Reviver.constructors.Server = Server; diff --git a/src/Server/ServerHelpers.ts b/src/Server/ServerHelpers.ts index ca146ce42..47b4f912f 100644 --- a/src/Server/ServerHelpers.ts +++ b/src/Server/ServerHelpers.ts @@ -1,8 +1,4 @@ -import { - AllServers, - createUniqueRandomIp, - ipExists, -} from "./AllServers"; +import { AllServers, createUniqueRandomIp, ipExists } from "./AllServers"; import { Server, IConstructorParams } from "./Server"; import { calculateServerGrowth } from "./formulas/grow"; @@ -21,21 +17,23 @@ import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress"; * does not have a duplicate hostname/ip. */ export function safetlyCreateUniqueServer(params: IConstructorParams): Server { - if (params.ip != null && ipExists(params.ip)) { - params.ip = createUniqueRandomIp(); - } + if (params.ip != null && ipExists(params.ip)) { + params.ip = createUniqueRandomIp(); + } - if (GetServerByHostname(params.hostname) != null) { - // Use a for loop to ensure that we don't get suck in an infinite loop somehow - let hostname: string = params.hostname; - for (let i = 0; i < 200; ++i) { - hostname = `${params.hostname}-${i}`; - if (GetServerByHostname(hostname) == null) { break; } - } - params.hostname = hostname; + if (GetServerByHostname(params.hostname) != null) { + // Use a for loop to ensure that we don't get suck in an infinite loop somehow + let hostname: string = params.hostname; + for (let i = 0; i < 200; ++i) { + hostname = `${params.hostname}-${i}`; + if (GetServerByHostname(hostname) == null) { + break; + } } + params.hostname = hostname; + } - return new Server(params); + return new Server(params); } /** @@ -46,111 +44,140 @@ export function safetlyCreateUniqueServer(params: IConstructorParams): Server { * @param p - Reference to Player object * @returns Number of "growth cycles" needed */ -export function numCycleForGrowth(server: Server, growth: number, p: IPlayer): number { - let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty; - if (ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) { - ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate; - } +export function numCycleForGrowth( + server: Server, + growth: number, + p: IPlayer, +): number { + let ajdGrowthRate = + 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty; + if (ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) { + ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate; + } - const serverGrowthPercentage = server.serverGrowth / 100; + const serverGrowthPercentage = server.serverGrowth / 100; - const cycles = Math.log(growth)/(Math.log(ajdGrowthRate) * p.hacking_grow_mult * serverGrowthPercentage * BitNodeMultipliers.ServerGrowthRate); + const cycles = + Math.log(growth) / + (Math.log(ajdGrowthRate) * + p.hacking_grow_mult * + serverGrowthPercentage * + BitNodeMultipliers.ServerGrowthRate); - return cycles; + return cycles; } //Applied server growth for a single server. Returns the percentage growth -export function processSingleServerGrowth(server: Server, threads: number, p: IPlayer, cores = 1): number { - let serverGrowth = calculateServerGrowth(server, threads, p, cores); - if (serverGrowth < 1) { - console.warn("serverGrowth calculated to be less than 1"); - serverGrowth = 1; - } +export function processSingleServerGrowth( + server: Server, + threads: number, + p: IPlayer, + cores = 1, +): number { + let serverGrowth = calculateServerGrowth(server, threads, p, cores); + if (serverGrowth < 1) { + console.warn("serverGrowth calculated to be less than 1"); + serverGrowth = 1; + } - const oldMoneyAvailable = server.moneyAvailable; - server.moneyAvailable *= serverGrowth; + const oldMoneyAvailable = server.moneyAvailable; + server.moneyAvailable *= serverGrowth; - // in case of data corruption - if (isValidNumber(server.moneyMax) && isNaN(server.moneyAvailable)) { - server.moneyAvailable = server.moneyMax; - } + // in case of data corruption + if (isValidNumber(server.moneyMax) && isNaN(server.moneyAvailable)) { + server.moneyAvailable = server.moneyMax; + } - // cap at max - if (isValidNumber(server.moneyMax) && server.moneyAvailable > server.moneyMax) { - server.moneyAvailable = server.moneyMax; - } + // cap at max + if ( + isValidNumber(server.moneyMax) && + server.moneyAvailable > server.moneyMax + ) { + server.moneyAvailable = server.moneyMax; + } - // if there was any growth at all, increase security - if (oldMoneyAvailable !== server.moneyAvailable) { - //Growing increases server security twice as much as hacking - let usedCycles = numCycleForGrowth(server, server.moneyAvailable / oldMoneyAvailable, p); - usedCycles = Math.max(0, usedCycles); - server.fortify(2 * CONSTANTS.ServerFortifyAmount * Math.ceil(usedCycles)); - } - return server.moneyAvailable / oldMoneyAvailable; + // if there was any growth at all, increase security + if (oldMoneyAvailable !== server.moneyAvailable) { + //Growing increases server security twice as much as hacking + let usedCycles = numCycleForGrowth( + server, + server.moneyAvailable / oldMoneyAvailable, + p, + ); + usedCycles = Math.max(0, usedCycles); + server.fortify(2 * CONSTANTS.ServerFortifyAmount * Math.ceil(usedCycles)); + } + return server.moneyAvailable / oldMoneyAvailable; } export function prestigeHomeComputer(homeComp: Server): void { - const hasBitflume = homeComp.programs.includes(Programs.BitFlume.name); + const hasBitflume = homeComp.programs.includes(Programs.BitFlume.name); - homeComp.programs.length = 0; //Remove programs - homeComp.runningScripts = []; - homeComp.serversOnNetwork = []; - homeComp.isConnectedTo = true; - homeComp.ramUsed = 0; - homeComp.programs.push(Programs.NukeProgram.name); - if (hasBitflume) { homeComp.programs.push(Programs.BitFlume.name); } + homeComp.programs.length = 0; //Remove programs + homeComp.runningScripts = []; + homeComp.serversOnNetwork = []; + homeComp.isConnectedTo = true; + homeComp.ramUsed = 0; + homeComp.programs.push(Programs.NukeProgram.name); + if (hasBitflume) { + homeComp.programs.push(Programs.BitFlume.name); + } - //Update RAM usage on all scripts - homeComp.scripts.forEach(function(script) { - script.updateRamUsage(homeComp.scripts); - }); + //Update RAM usage on all scripts + homeComp.scripts.forEach(function (script) { + script.updateRamUsage(homeComp.scripts); + }); - homeComp.messages.length = 0; //Remove .lit and .msg files - homeComp.messages.push(LiteratureNames.HackersStartingHandbook); + homeComp.messages.length = 0; //Remove .lit and .msg files + homeComp.messages.push(LiteratureNames.HackersStartingHandbook); } //Returns server object with corresponding hostname // Relatively slow, would rather not use this a lot -export function GetServerByHostname(hostname: string): Server | HacknetServer | null { - for (const ip in AllServers) { - if (AllServers.hasOwnProperty(ip)) { - if (AllServers[ip].hostname == hostname) { - return AllServers[ip]; - } - } +export function GetServerByHostname( + hostname: string, +): Server | HacknetServer | null { + for (const ip in AllServers) { + if (AllServers.hasOwnProperty(ip)) { + if (AllServers[ip].hostname == hostname) { + return AllServers[ip]; + } } + } - return null; + return null; } //Get server by IP or hostname. Returns null if invalid export function getServer(s: string): Server | HacknetServer | null { - if (!isValidIPAddress(s)) { - return GetServerByHostname(s); - } - if (AllServers[s] !== undefined) { - return AllServers[s]; - } + if (!isValidIPAddress(s)) { + return GetServerByHostname(s); + } + if (AllServers[s] !== undefined) { + return AllServers[s]; + } - return null; + return null; } // Returns the i-th server on the specified server's network // A Server's serverOnNetwork property holds only the IPs. This function returns // the actual Server object -export function getServerOnNetwork(server: Server, i: number): Server | HacknetServer | null { - if (i > server.serversOnNetwork.length) { - console.error("Tried to get server on network that was out of range"); - return null; - } +export function getServerOnNetwork( + server: Server, + i: number, +): Server | HacknetServer | null { + if (i > server.serversOnNetwork.length) { + console.error("Tried to get server on network that was out of range"); + return null; + } - return AllServers[server.serversOnNetwork[i]]; + return AllServers[server.serversOnNetwork[i]]; } export function isBackdoorInstalled(server: Server | HacknetServer): boolean { - if ("backdoorInstalled" in server) { - return server.backdoorInstalled; - } - return false; + if ("backdoorInstalled" in server) { + return server.backdoorInstalled; + } + return false; } diff --git a/src/Server/ServerPurchases.ts b/src/Server/ServerPurchases.ts index 187821914..98a72ef60 100644 --- a/src/Server/ServerPurchases.ts +++ b/src/Server/ServerPurchases.ts @@ -2,10 +2,7 @@ * Implements functions for purchasing servers or purchasing more RAM for * the home computer */ -import { - AddToAllServers, - createUniqueRandomIp, -} from "./AllServers"; +import { AddToAllServers, createUniqueRandomIp } from "./AllServers"; import { safetlyCreateUniqueServer } from "./ServerHelpers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; @@ -23,93 +20,111 @@ import { isPowerOfTwo } from "../../utils/helpers/isPowerOfTwo"; * @returns Cost of purchasing the given server. Returns infinity for invalid arguments */ export function getPurchaseServerCost(ram: number): number { - const sanitizedRam = Math.round(ram); - if (isNaN(sanitizedRam) || !isPowerOfTwo(sanitizedRam)) { - return Infinity; - } + const sanitizedRam = Math.round(ram); + if (isNaN(sanitizedRam) || !isPowerOfTwo(sanitizedRam)) { + return Infinity; + } - if (sanitizedRam > getPurchaseServerMaxRam()) { - return Infinity; - } + if (sanitizedRam > getPurchaseServerMaxRam()) { + return Infinity; + } - return sanitizedRam * CONSTANTS.BaseCostFor1GBOfRamServer * BitNodeMultipliers.PurchasedServerCost; + return ( + sanitizedRam * + CONSTANTS.BaseCostFor1GBOfRamServer * + BitNodeMultipliers.PurchasedServerCost + ); } export function getPurchaseServerLimit(): number { - return Math.round(CONSTANTS.PurchasedServerLimit * BitNodeMultipliers.PurchasedServerLimit); + return Math.round( + CONSTANTS.PurchasedServerLimit * BitNodeMultipliers.PurchasedServerLimit, + ); } export function getPurchaseServerMaxRam(): number { - const ram = Math.round(CONSTANTS.PurchasedServerMaxRam * BitNodeMultipliers.PurchasedServerMaxRam); + const ram = Math.round( + CONSTANTS.PurchasedServerMaxRam * BitNodeMultipliers.PurchasedServerMaxRam, + ); - // Round this to the nearest power of 2 - return 1 << 31 - Math.clz32(ram); + // Round this to the nearest power of 2 + return 1 << (31 - Math.clz32(ram)); } // Manually purchase a server (NOT through Netscript) export function purchaseServer(ram: number, p: IPlayer): void { - const cost = getPurchaseServerCost(ram); + const cost = getPurchaseServerCost(ram); - //Check if player has enough money - if (!p.canAfford(cost)) { - dialogBoxCreate("You don't have enough money to purchase this server!", false); - return; - } + //Check if player has enough money + if (!p.canAfford(cost)) { + dialogBoxCreate( + "You don't have enough money to purchase this server!", + false, + ); + return; + } - //Maximum server limit - if (p.purchasedServers.length >= getPurchaseServerLimit()) { - dialogBoxCreate("You have reached the maximum limit of " + getPurchaseServerLimit() + " servers. " + - "You cannot purchase any more. You can " + - "delete some of your purchased servers using the deleteServer() Netscript function in a script"); - return; - } + //Maximum server limit + if (p.purchasedServers.length >= getPurchaseServerLimit()) { + dialogBoxCreate( + "You have reached the maximum limit of " + + getPurchaseServerLimit() + + " servers. " + + "You cannot purchase any more. You can " + + "delete some of your purchased servers using the deleteServer() Netscript function in a script", + ); + return; + } - const hostname = yesNoTxtInpBoxGetInput(); - if (hostname == "") { - dialogBoxCreate("You must enter a hostname for your new server!"); - return; - } + const hostname = yesNoTxtInpBoxGetInput(); + if (hostname == "") { + dialogBoxCreate("You must enter a hostname for your new server!"); + return; + } - // Create server - const newServ = safetlyCreateUniqueServer({ - adminRights: true, - hostname: hostname, - ip: createUniqueRandomIp(), - isConnectedTo: false, - maxRam:ram, - organizationName: "", - purchasedByPlayer: true, - }); - AddToAllServers(newServ); + // Create server + const newServ = safetlyCreateUniqueServer({ + adminRights: true, + hostname: hostname, + ip: createUniqueRandomIp(), + isConnectedTo: false, + maxRam: ram, + organizationName: "", + purchasedByPlayer: true, + }); + AddToAllServers(newServ); - // Add to Player's purchasedServers array - p.purchasedServers.push(newServ.ip); + // Add to Player's purchasedServers array + p.purchasedServers.push(newServ.ip); - // Connect new server to home computer - const homeComputer = p.getHomeComputer(); - homeComputer.serversOnNetwork.push(newServ.ip); - newServ.serversOnNetwork.push(homeComputer.ip); + // Connect new server to home computer + const homeComputer = p.getHomeComputer(); + homeComputer.serversOnNetwork.push(newServ.ip); + newServ.serversOnNetwork.push(homeComputer.ip); - p.loseMoney(cost); + p.loseMoney(cost); - dialogBoxCreate("Server successfully purchased with hostname " + hostname); + dialogBoxCreate("Server successfully purchased with hostname " + hostname); } // Manually upgrade RAM on home computer (NOT through Netscript) export function purchaseRamForHomeComputer(p: IPlayer): void { - const cost = p.getUpgradeHomeRamCost(); - if (!p.canAfford(cost)) { - dialogBoxCreate("You do not have enough money to purchase additional RAM for your home computer"); - return; - } + const cost = p.getUpgradeHomeRamCost(); + if (!p.canAfford(cost)) { + dialogBoxCreate( + "You do not have enough money to purchase additional RAM for your home computer", + ); + return; + } - const homeComputer = p.getHomeComputer(); - if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) { - dialogBoxCreate(`You cannot upgrade your home computer RAM because it is at its maximum possible value`); - return; - } + const homeComputer = p.getHomeComputer(); + if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) { + dialogBoxCreate( + `You cannot upgrade your home computer RAM because it is at its maximum possible value`, + ); + return; + } - - homeComputer.maxRam *= 2; - p.loseMoney(cost); + homeComputer.maxRam *= 2; + p.loseMoney(cost); } diff --git a/src/Server/SpecialServerIps.ts b/src/Server/SpecialServerIps.ts index 466acfda6..a226fb435 100644 --- a/src/Server/SpecialServerIps.ts +++ b/src/Server/SpecialServerIps.ts @@ -1,42 +1,44 @@ -import { IMap } from "../types"; -import { Reviver, - Generic_toJSON, - Generic_fromJSON } from "../../utils/JSONReviver"; +import { IMap } from "../types"; +import { + Reviver, + Generic_toJSON, + Generic_fromJSON, +} from "../../utils/JSONReviver"; /* Holds IP of Special Servers */ export const SpecialServerNames: IMap = { - FulcrumSecretTechnologies: "Fulcrum Secret Technologies Server", - CyberSecServer: "CyberSec Server", - NiteSecServer: "NiteSec Server", - TheBlackHandServer: "The Black Hand Server", - BitRunnersServer: "BitRunners Server", - TheDarkArmyServer: "The Dark Army Server", - DaedalusServer: "Daedalus Server", - WorldDaemon: "w0r1d_d43m0n", -} + FulcrumSecretTechnologies: "Fulcrum Secret Technologies Server", + CyberSecServer: "CyberSec Server", + NiteSecServer: "NiteSec Server", + TheBlackHandServer: "The Black Hand Server", + BitRunnersServer: "BitRunners Server", + TheDarkArmyServer: "The Dark Army Server", + DaedalusServer: "Daedalus Server", + WorldDaemon: "w0r1d_d43m0n", +}; export class SpecialServerIpsMap { - // eslint-disable-next-line @typescript-eslint/ban-types - [key: string]: Function | string; + // eslint-disable-next-line @typescript-eslint/ban-types + [key: string]: Function | string; - addIp(name:string, ip: string): void { - this[name] = ip; - } + addIp(name: string, ip: string): void { + this[name] = ip; + } - getIp(name: string): string { - return this[name] as string; - } + getIp(name: string): string { + return this[name] as string; + } - // Serialize the current object to a JSON save state - toJSON(): any { - return Generic_toJSON("SpecialServerIpsMap", this); - } + // Serialize the current object to a JSON save state + toJSON(): any { + return Generic_toJSON("SpecialServerIpsMap", this); + } - // Initializes a SpecialServerIpsMap Object from a JSON save state - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): SpecialServerIpsMap { - return Generic_fromJSON(SpecialServerIpsMap, value.data); - } + // Initializes a SpecialServerIpsMap Object from a JSON save state + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): SpecialServerIpsMap { + return Generic_fromJSON(SpecialServerIpsMap, value.data); + } } Reviver.constructors.SpecialServerIpsMap = SpecialServerIpsMap; @@ -44,17 +46,17 @@ Reviver.constructors.SpecialServerIpsMap = SpecialServerIpsMap; export let SpecialServerIps: SpecialServerIpsMap = new SpecialServerIpsMap(); export function prestigeSpecialServerIps(): void { - for (const member in SpecialServerIps) { - delete SpecialServerIps[member]; - } + for (const member in SpecialServerIps) { + delete SpecialServerIps[member]; + } - SpecialServerIps = new SpecialServerIpsMap(); + SpecialServerIps = new SpecialServerIpsMap(); } export function loadSpecialServerIps(saveString: string): void { - SpecialServerIps = JSON.parse(saveString, Reviver); + SpecialServerIps = JSON.parse(saveString, Reviver); } export function initSpecialServerIps(): void { - SpecialServerIps = new SpecialServerIpsMap(); + SpecialServerIps = new SpecialServerIpsMap(); } diff --git a/src/Server/data/servers.ts b/src/Server/data/servers.ts index 613541c09..31a3805a0 100644 --- a/src/Server/data/servers.ts +++ b/src/Server/data/servers.ts @@ -10,65 +10,65 @@ import { LiteratureNames } from "../../Literature/data/LiteratureNames"; * These values will be adjusted based on Bitnode multipliers when the Server objects are built out. */ interface IServerMetadata { - /** - * When populated, the base security level of the server. - */ - hackDifficulty?: number | IMinMaxRange; + /** + * When populated, the base security level of the server. + */ + hackDifficulty?: number | IMinMaxRange; - /** - * The DNS name of the server. - */ - hostname: string; + /** + * The DNS name of the server. + */ + hostname: string; - /** - * When populated, the files will be added to the server when created. - */ - literature?: string[]; + /** + * When populated, the files will be added to the server when created. + */ + literature?: string[]; - /** - * When populated, the exponent of 2^x amount of RAM the server has. - * This should be in the range of 1-20, to match the Player's max RAM. - */ - maxRamExponent?: number | IMinMaxRange; + /** + * When populated, the exponent of 2^x amount of RAM the server has. + * This should be in the range of 1-20, to match the Player's max RAM. + */ + maxRamExponent?: number | IMinMaxRange; - /** - * How much money the server starts out with. - */ - moneyAvailable: number | IMinMaxRange; + /** + * How much money the server starts out with. + */ + moneyAvailable: number | IMinMaxRange; - /** - * The number of network layers away from the `home` server. - * This value is between 1 and 15. - * If this is not populated, @specialName should be. - */ - networkLayer?: number | IMinMaxRange; + /** + * The number of network layers away from the `home` server. + * This value is between 1 and 15. + * If this is not populated, @specialName should be. + */ + networkLayer?: number | IMinMaxRange; - /** - * The number of ports that must be opened before the player can execute NUKE. - */ - numOpenPortsRequired: number; + /** + * The number of ports that must be opened before the player can execute NUKE. + */ + numOpenPortsRequired: number; - /** - * The organization that the server belongs to. - */ - organizationName: string; + /** + * The organization that the server belongs to. + */ + organizationName: string; - /** - * The minimum hacking level before the player can run NUKE. - */ - requiredHackingSkill: number | IMinMaxRange; + /** + * The minimum hacking level before the player can run NUKE. + */ + requiredHackingSkill: number | IMinMaxRange; - /** - * The growth factor for the server. - */ - serverGrowth?: number | IMinMaxRange; + /** + * The growth factor for the server. + */ + serverGrowth?: number | IMinMaxRange; - /** - * A "unique" server that has special implications when the player manually hacks it. - */ - specialName?: string; + /** + * A "unique" server that has special implications when the player manually hacks it. + */ + specialName?: string; - [key: string]: any; + [key: string]: any; } /** @@ -140,8 +140,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "blade", literature: [LiteratureNames.BeyondMan], maxRamExponent: { - max: 9, - min: 5, + max: 9, + min: 5, }, moneyAvailable: { max: 40e9, @@ -183,14 +183,11 @@ export const serverMetadata: IServerMetadata[] = [ }, { hackDifficulty: { - max: 65, - min: 45, + max: 65, + min: 45, }, hostname: "clarkinc", - literature: [ - LiteratureNames.BeyondMan, - LiteratureNames.CostOfImmortality, - ], + literature: [LiteratureNames.BeyondMan, LiteratureNames.CostOfImmortality], moneyAvailable: { max: 25e9, min: 15e9, @@ -203,8 +200,8 @@ export const serverMetadata: IServerMetadata[] = [ min: 950, }, serverGrowth: { - max: 75, - min: 45, + max: 75, + min: 45, }, specialName: LocationName.AevumClarkeIncorporated, }, @@ -219,8 +216,8 @@ export const serverMetadata: IServerMetadata[] = [ LiteratureNames.HistoryOfSynthoids, ], maxRamExponent: { - max: 9, - min: 7, + max: 9, + min: 7, }, moneyAvailable: { max: 22e9, @@ -293,8 +290,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "fulcrumtech", literature: [LiteratureNames.SimulatedReality], maxRamExponent: { - max: 11, - min: 7, + max: 11, + min: 7, }, moneyAvailable: { max: 1800e6, @@ -403,8 +400,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "helios", literature: [LiteratureNames.BeyondMan], maxRamExponent: { - max: 8, - min: 5, + max: 8, + min: 5, }, moneyAvailable: { max: 750e6, @@ -431,8 +428,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "vitalife", literature: [LiteratureNames.AGreenTomorrow], maxRamExponent: { - max: 7, - min: 4, + max: 7, + min: 4, }, moneyAvailable: { max: 800e6, @@ -481,8 +478,8 @@ export const serverMetadata: IServerMetadata[] = [ }, hostname: "univ-energy", maxRamExponent: { - max: 7, - min: 4, + max: 7, + min: 4, }, moneyAvailable: { max: 1200e6, @@ -509,8 +506,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "titan-labs", literature: [LiteratureNames.CodedIntelligence], maxRamExponent: { - max: 7, - min: 4, + max: 7, + min: 4, }, moneyAvailable: { max: 900000000, @@ -536,8 +533,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "microdyne", literature: [LiteratureNames.SyntheticMuscles], maxRamExponent: { - max: 6, - min: 4, + max: 6, + min: 4, }, moneyAvailable: { max: 700000000, @@ -636,8 +633,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "omnia", literature: [LiteratureNames.HistoryOfSynthoids], maxRamExponent: { - max: 6, - min: 4, + max: 6, + min: 4, }, moneyAvailable: { max: 1000000000, @@ -708,12 +705,12 @@ export const serverMetadata: IServerMetadata[] = [ }, hostname: "solaris", literature: [ - LiteratureNames.AGreenTomorrow, - LiteratureNames.TheFailedFrontier, + LiteratureNames.AGreenTomorrow, + LiteratureNames.TheFailedFrontier, ], maxRamExponent: { - max: 7, - min: 4, + max: 7, + min: 4, }, moneyAvailable: { max: 900000000, @@ -763,8 +760,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "global-pharm", literature: [LiteratureNames.AGreenTomorrow], maxRamExponent: { - max: 6, - min: 3, + max: 6, + min: 3, }, moneyAvailable: { max: 1750000000, @@ -835,8 +832,8 @@ export const serverMetadata: IServerMetadata[] = [ }, hostname: "unitalife", maxRamExponent: { - max: 6, - min: 4, + max: 6, + min: 4, }, moneyAvailable: { max: 1100000000, @@ -861,8 +858,8 @@ export const serverMetadata: IServerMetadata[] = [ }, hostname: "lexo-corp", maxRamExponent: { - max: 7, - min: 4, + max: 7, + min: 4, }, moneyAvailable: { max: 800000000, @@ -888,8 +885,8 @@ export const serverMetadata: IServerMetadata[] = [ }, hostname: "rho-construction", maxRamExponent: { - max: 6, - min: 4, + max: 6, + min: 4, }, moneyAvailable: { max: 700000000, @@ -916,8 +913,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "alpha-ent", literature: [LiteratureNames.Sector12Crime], maxRamExponent: { - max: 7, - min: 4, + max: 7, + min: 4, }, moneyAvailable: { max: 750000000, @@ -943,8 +940,8 @@ export const serverMetadata: IServerMetadata[] = [ }, hostname: "aevum-police", maxRamExponent: { - max: 6, - min: 4, + max: 6, + min: 4, }, moneyAvailable: { max: 400000000, @@ -975,8 +972,8 @@ export const serverMetadata: IServerMetadata[] = [ LiteratureNames.TensionsInTechRace, ], maxRamExponent: { - max: 7, - min: 4, + max: 7, + min: 4, }, moneyAvailable: { max: 250000000, @@ -1002,8 +999,8 @@ export const serverMetadata: IServerMetadata[] = [ }, hostname: "zb-institute", maxRamExponent: { - max: 7, - min: 4, + max: 7, + min: 4, }, moneyAvailable: { max: 1100000000, @@ -1034,8 +1031,8 @@ export const serverMetadata: IServerMetadata[] = [ LiteratureNames.SyntheticMuscles, ], maxRamExponent: { - max: 6, - min: 4, + max: 6, + min: 4, }, moneyAvailable: { max: 350000000, @@ -1085,8 +1082,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "catalyst", literature: [LiteratureNames.TensionsInTechRace], maxRamExponent: { - max: 7, - min: 4, + max: 7, + min: 4, }, moneyAvailable: { max: 550000000, @@ -1111,8 +1108,8 @@ export const serverMetadata: IServerMetadata[] = [ }, hostname: "the-hub", maxRamExponent: { - max: 6, - min: 3, + max: 6, + min: 3, }, moneyAvailable: { max: 200000000, @@ -1162,8 +1159,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "netlink", literature: [LiteratureNames.SimulatedReality], maxRamExponent: { - max: 7, - min: 4, + max: 7, + min: 4, }, moneyAvailable: 275000000, networkLayer: 4, @@ -1408,8 +1405,8 @@ export const serverMetadata: IServerMetadata[] = [ }, hostname: "millenium-fitness", maxRamExponent: { - max: 8, - min: 4, + max: 8, + min: 4, }, moneyAvailable: 250000000, networkLayer: 6, @@ -1432,8 +1429,8 @@ export const serverMetadata: IServerMetadata[] = [ }, hostname: "powerhouse-fitness", maxRamExponent: { - max: 6, - min: 4, + max: 6, + min: 4, }, moneyAvailable: 900000000, networkLayer: 14, @@ -1472,13 +1469,10 @@ export const serverMetadata: IServerMetadata[] = [ { hackDifficulty: 0, hostname: "run4theh111z", - literature: [ - LiteratureNames.SimulatedReality, - LiteratureNames.TheNewGod, - ], + literature: [LiteratureNames.SimulatedReality, LiteratureNames.TheNewGod], maxRamExponent: { - max: 9, - min: 5, + max: 9, + min: 5, }, moneyAvailable: 0, networkLayer: 11, @@ -1496,8 +1490,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "I.I.I.I", literature: [LiteratureNames.DemocracyIsDead], maxRamExponent: { - max: 8, - min: 4, + max: 8, + min: 4, }, moneyAvailable: 0, networkLayer: 5, @@ -1515,8 +1509,8 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "avmnite-02h", literature: [LiteratureNames.DemocracyIsDead], maxRamExponent: { - max: 7, - min: 4, + max: 7, + min: 4, }, moneyAvailable: 0, networkLayer: 4, @@ -1582,4 +1576,4 @@ export const serverMetadata: IServerMetadata[] = [ serverGrowth: 0, specialName: "w0r1d_d43m0n", }, -]; \ No newline at end of file +]; diff --git a/src/Server/formulas/grow.ts b/src/Server/formulas/grow.ts index 3c42c1648..7a36414dc 100644 --- a/src/Server/formulas/grow.ts +++ b/src/Server/formulas/grow.ts @@ -3,19 +3,32 @@ import { Server } from "../Server"; import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { IPlayer } from "../../PersonObjects/IPlayer"; -export function calculateServerGrowth(server: Server, threads: number, p: IPlayer, cores = 1): number { - const numServerGrowthCycles = Math.max(Math.floor(threads), 0); +export function calculateServerGrowth( + server: Server, + threads: number, + p: IPlayer, + cores = 1, +): number { + const numServerGrowthCycles = Math.max(Math.floor(threads), 0); - //Get adjusted growth rate, which accounts for server security - const growthRate = CONSTANTS.ServerBaseGrowthRate; - let adjGrowthRate = 1 + (growthRate - 1) / server.hackDifficulty; - if (adjGrowthRate > CONSTANTS.ServerMaxGrowthRate) {adjGrowthRate = CONSTANTS.ServerMaxGrowthRate;} + //Get adjusted growth rate, which accounts for server security + const growthRate = CONSTANTS.ServerBaseGrowthRate; + let adjGrowthRate = 1 + (growthRate - 1) / server.hackDifficulty; + if (adjGrowthRate > CONSTANTS.ServerMaxGrowthRate) { + adjGrowthRate = CONSTANTS.ServerMaxGrowthRate; + } - //Calculate adjusted server growth rate based on parameters - const serverGrowthPercentage = server.serverGrowth / 100; - const numServerGrowthCyclesAdjusted = numServerGrowthCycles * serverGrowthPercentage * BitNodeMultipliers.ServerGrowthRate; + //Calculate adjusted server growth rate based on parameters + const serverGrowthPercentage = server.serverGrowth / 100; + const numServerGrowthCyclesAdjusted = + numServerGrowthCycles * + serverGrowthPercentage * + BitNodeMultipliers.ServerGrowthRate; - //Apply serverGrowth for the calculated number of growth cycles - const coreBonus = 1+(cores-1)/16; - return Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * p.hacking_grow_mult * coreBonus); -} \ No newline at end of file + //Apply serverGrowth for the calculated number of growth cycles + const coreBonus = 1 + (cores - 1) / 16; + return Math.pow( + adjGrowthRate, + numServerGrowthCyclesAdjusted * p.hacking_grow_mult * coreBonus, + ); +} diff --git a/src/Settings/SettingEnums.ts b/src/Settings/SettingEnums.ts index 539ae7ca7..05d08b80c 100644 --- a/src/Settings/SettingEnums.ts +++ b/src/Settings/SettingEnums.ts @@ -4,104 +4,104 @@ * Allowed values for 'Keybinding/Keymap' setting in Ace editor */ export enum AceKeybindingSetting { - Ace = "ace", - Emacs = "emacs", - Vim = "vim", + Ace = "ace", + Emacs = "emacs", + Vim = "vim", } /** * Allowed values for 'Keybinding/Keymap' setting in Code Mirror editor */ export enum CodeMirrorKeybindingSetting { - Default = "default", - Emacs = "emacs", - Sublime = "sublime", - Vim = "vim", + Default = "default", + Emacs = "emacs", + Sublime = "sublime", + Vim = "vim", } /** * Allowed values for 'Theme' setting in Code Mirror editor */ export enum CodeMirrorThemeSetting { - Monokai = "monokai", - Day_3024 = "3024-day", - Night_3024 = "3024-night", - abcdef = "abcdef", - Ambiance_mobile = "ambiance-mobile", - Ambiance = "ambiance", - Base16_dark = "base16-dark", - Base16_light = "base16-light", - Bespin = "bespin", - Blackboard = "blackboard", - Cobalt = "cobalt", - Colorforth = "colorforth", - Darcula = "darcula", - Dracula = "dracula", - Duotone_dark = "duotone-dark", - Duotone_light = "duotone-light", - Eclipse = "eclipse", - Elegant = "elegant", - Erlang_dark = "erlang-dark", - Gruvbox_dark = "gruvbox-dark", - Hopscotch = "hopscotch", - Icecoder = "icecoder", - Idea = "idea", - Isotope = "isotope", - Lesser_dark = "lesser-dark", - Liquibyte = "liquibyte", - Lucario = "lucario", - Material = "material", - Mbo = "mbo", - Mdn_like = "mdn-like", - Midnight = "midnight", - Neat = "neat", - Neo = "neo", - Night = "night", - Oceanic_next = "oceanic-next", - Panda_syntax = "panda-syntax", - Paraiso_dark = "paraiso-dark", - Paraiso_light = "paraiso-light", - Pastel_on_dark = "pastel-on-dark", - Railscasts = "railscasts", - Rubyblue = "rubyblue", - Seti = "seti", - Shadowfox = "shadowfox", - Solarized = "solarized", - SolarizedDark = "solarized dark", - ssms = "ssms", - The_matrix = "the-matrix", - Tomorrow_night_bright = "tomorrow-night-bright", - Tomorrow_night_eighties = "tomorrow-night-eighties", - Ttcn = "ttcn", - Twilight = "twilight", - Vibrant_ink = "vibrant-ink", - xq_dark = "xq-dark", - xq_light = "xq-light", - Yeti = "yeti", - Zenburn = "zenburn", + Monokai = "monokai", + Day_3024 = "3024-day", + Night_3024 = "3024-night", + abcdef = "abcdef", + Ambiance_mobile = "ambiance-mobile", + Ambiance = "ambiance", + Base16_dark = "base16-dark", + Base16_light = "base16-light", + Bespin = "bespin", + Blackboard = "blackboard", + Cobalt = "cobalt", + Colorforth = "colorforth", + Darcula = "darcula", + Dracula = "dracula", + Duotone_dark = "duotone-dark", + Duotone_light = "duotone-light", + Eclipse = "eclipse", + Elegant = "elegant", + Erlang_dark = "erlang-dark", + Gruvbox_dark = "gruvbox-dark", + Hopscotch = "hopscotch", + Icecoder = "icecoder", + Idea = "idea", + Isotope = "isotope", + Lesser_dark = "lesser-dark", + Liquibyte = "liquibyte", + Lucario = "lucario", + Material = "material", + Mbo = "mbo", + Mdn_like = "mdn-like", + Midnight = "midnight", + Neat = "neat", + Neo = "neo", + Night = "night", + Oceanic_next = "oceanic-next", + Panda_syntax = "panda-syntax", + Paraiso_dark = "paraiso-dark", + Paraiso_light = "paraiso-light", + Pastel_on_dark = "pastel-on-dark", + Railscasts = "railscasts", + Rubyblue = "rubyblue", + Seti = "seti", + Shadowfox = "shadowfox", + Solarized = "solarized", + SolarizedDark = "solarized dark", + ssms = "ssms", + The_matrix = "the-matrix", + Tomorrow_night_bright = "tomorrow-night-bright", + Tomorrow_night_eighties = "tomorrow-night-eighties", + Ttcn = "ttcn", + Twilight = "twilight", + Vibrant_ink = "vibrant-ink", + xq_dark = "xq-dark", + xq_light = "xq-light", + Yeti = "yeti", + Zenburn = "zenburn", } /** * Allowed values for the "Editor" setting */ export enum EditorSetting { - Ace = "Ace", - CodeMirror = "CodeMirror", + Ace = "Ace", + CodeMirror = "CodeMirror", } /** * Allowed values for the 'OwnedAugmentationsOrder' setting */ export enum PurchaseAugmentationsOrderSetting { - Cost, - Default, - Reputation, + Cost, + Default, + Reputation, } /** * Allowed values for the 'OwnedAugmentationsOrder' setting */ export enum OwnedAugmentationsOrderSetting { - Alphabetically, - AcquirementTime, + Alphabetically, + AcquirementTime, } diff --git a/src/Settings/Settings.ts b/src/Settings/Settings.ts index 15f4c6240..15770b8ae 100644 --- a/src/Settings/Settings.ts +++ b/src/Settings/Settings.ts @@ -1,121 +1,122 @@ import { ISelfInitializer, ISelfLoading } from "../types"; -import { AceKeybindingSetting, - CodeMirrorKeybindingSetting, - CodeMirrorThemeSetting, - EditorSetting, - OwnedAugmentationsOrderSetting, - PurchaseAugmentationsOrderSetting } from "./SettingEnums"; +import { + AceKeybindingSetting, + CodeMirrorKeybindingSetting, + CodeMirrorThemeSetting, + EditorSetting, + OwnedAugmentationsOrderSetting, + PurchaseAugmentationsOrderSetting, +} from "./SettingEnums"; /** * Represents the default settings the player could customize. */ interface IDefaultSettings { - /** - * How often the game should autosave the player's progress, in seconds. - */ - AutosaveInterval: number; + /** + * How often the game should autosave the player's progress, in seconds. + */ + AutosaveInterval: number; - /** - * How many milliseconds between execution points for Netscript 1 statements. - */ - CodeInstructionRunTime: number; + /** + * How many milliseconds between execution points for Netscript 1 statements. + */ + CodeInstructionRunTime: number; - /** - * Render city as list of buttons. - */ - DisableASCIIArt: boolean; + /** + * Render city as list of buttons. + */ + DisableASCIIArt: boolean; - /** - * Whether global keyboard shortcuts should be recognized throughout the game. - */ - DisableHotkeys: boolean; - - /** - * Whether text effects such as corruption should be visible. - */ - DisableTextEffects: boolean; + /** + * Whether global keyboard shortcuts should be recognized throughout the game. + */ + DisableHotkeys: boolean; - /** - * Locale used for display numbers - */ - Locale: string; + /** + * Whether text effects such as corruption should be visible. + */ + DisableTextEffects: boolean; - /** - * Limit the number of log entries for each script being executed on each server. - */ - MaxLogCapacity: number; + /** + * Locale used for display numbers + */ + Locale: string; - /** - * Limit how many entries can be written to a Netscript Port before entries start to get pushed out. - */ - MaxPortCapacity: number; + /** + * Limit the number of log entries for each script being executed on each server. + */ + MaxLogCapacity: number; - /** - * Whether the player should be asked to confirm purchasing each and every augmentation. - */ - SuppressBuyAugmentationConfirmation: boolean; + /** + * Limit how many entries can be written to a Netscript Port before entries start to get pushed out. + */ + MaxPortCapacity: number; - /** - * Whether the user should be prompted to join each faction via a dialog box. - */ - SuppressFactionInvites: boolean; + /** + * Whether the player should be asked to confirm purchasing each and every augmentation. + */ + SuppressBuyAugmentationConfirmation: boolean; - /** - * Whether to show a popup message when player is hospitalized from taking too much damage - */ - SuppressHospitalizationPopup: boolean; + /** + * Whether the user should be prompted to join each faction via a dialog box. + */ + SuppressFactionInvites: boolean; - /** - * Whether the user should be shown a dialog box whenever they receive a new message file. - */ - SuppressMessages: boolean; + /** + * Whether to show a popup message when player is hospitalized from taking too much damage + */ + SuppressHospitalizationPopup: boolean; - /** - * Whether the user should be asked to confirm travelling between cities. - */ - SuppressTravelConfirmation: boolean; + /** + * Whether the user should be shown a dialog box whenever they receive a new message file. + */ + SuppressMessages: boolean; - /** - * Whether the user should be displayed a popup message when his Bladeburner actions are cancelled. - */ - SuppressBladeburnerPopup: boolean; + /** + * Whether the user should be asked to confirm travelling between cities. + */ + SuppressTravelConfirmation: boolean; + + /** + * Whether the user should be displayed a popup message when his Bladeburner actions are cancelled. + */ + SuppressBladeburnerPopup: boolean; } /** * Represents all possible settings the player wants to customize to their play style. */ interface ISettings extends IDefaultSettings { - /** - * What order the player's owned Augmentations/Source Files should be displayed in - */ - OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting; + /** + * What order the player's owned Augmentations/Source Files should be displayed in + */ + OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting; - /** - * What order the Augmentations should be displayed in when purchasing from a Faction - */ - PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting; + /** + * What order the Augmentations should be displayed in when purchasing from a Faction + */ + PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting; - MonacoTheme: string; - - MonacoInsertSpaces: boolean; + MonacoTheme: string; + MonacoInsertSpaces: boolean; } const defaultSettings: IDefaultSettings = { - AutosaveInterval: 60, - CodeInstructionRunTime: 50, - DisableASCIIArt: false, - DisableHotkeys: false, - DisableTextEffects: false, - Locale: "en", - MaxLogCapacity: 50, - MaxPortCapacity: 50, - SuppressBuyAugmentationConfirmation: false, - SuppressFactionInvites: false, - SuppressHospitalizationPopup: false, - SuppressMessages: false, - SuppressTravelConfirmation: false, - SuppressBladeburnerPopup: false, + AutosaveInterval: 60, + CodeInstructionRunTime: 50, + DisableASCIIArt: false, + DisableHotkeys: false, + DisableTextEffects: false, + Locale: "en", + MaxLogCapacity: 50, + MaxPortCapacity: 50, + SuppressBuyAugmentationConfirmation: false, + SuppressFactionInvites: false, + SuppressHospitalizationPopup: false, + SuppressMessages: false, + SuppressTravelConfirmation: false, + SuppressBladeburnerPopup: false, }; /** @@ -123,28 +124,29 @@ const defaultSettings: IDefaultSettings = { */ // tslint:disable-next-line:variable-name export const Settings: ISettings & ISelfInitializer & ISelfLoading = { - AutosaveInterval: defaultSettings.AutosaveInterval, - CodeInstructionRunTime: 25, - DisableASCIIArt: defaultSettings.DisableASCIIArt, - DisableHotkeys: defaultSettings.DisableHotkeys, - DisableTextEffects: defaultSettings.DisableTextEffects, - Locale: "en", - MaxLogCapacity: defaultSettings.MaxLogCapacity, - MaxPortCapacity: defaultSettings.MaxPortCapacity, - OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting.AcquirementTime, - PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting.Default, - SuppressBuyAugmentationConfirmation: defaultSettings.SuppressBuyAugmentationConfirmation, - SuppressFactionInvites: defaultSettings.SuppressFactionInvites, - SuppressHospitalizationPopup: defaultSettings.SuppressHospitalizationPopup, - SuppressMessages: defaultSettings.SuppressMessages, - SuppressTravelConfirmation: defaultSettings.SuppressTravelConfirmation, - SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup, - MonacoTheme: 'vs-dark', - MonacoInsertSpaces: false, - init() { - Object.assign(Settings, defaultSettings); - }, - load(saveString: string) { - Object.assign(Settings, JSON.parse(saveString)); - }, + AutosaveInterval: defaultSettings.AutosaveInterval, + CodeInstructionRunTime: 25, + DisableASCIIArt: defaultSettings.DisableASCIIArt, + DisableHotkeys: defaultSettings.DisableHotkeys, + DisableTextEffects: defaultSettings.DisableTextEffects, + Locale: "en", + MaxLogCapacity: defaultSettings.MaxLogCapacity, + MaxPortCapacity: defaultSettings.MaxPortCapacity, + OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting.AcquirementTime, + PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting.Default, + SuppressBuyAugmentationConfirmation: + defaultSettings.SuppressBuyAugmentationConfirmation, + SuppressFactionInvites: defaultSettings.SuppressFactionInvites, + SuppressHospitalizationPopup: defaultSettings.SuppressHospitalizationPopup, + SuppressMessages: defaultSettings.SuppressMessages, + SuppressTravelConfirmation: defaultSettings.SuppressTravelConfirmation, + SuppressBladeburnerPopup: defaultSettings.SuppressBladeburnerPopup, + MonacoTheme: "vs-dark", + MonacoInsertSpaces: false, + init() { + Object.assign(Settings, defaultSettings); + }, + load(saveString: string) { + Object.assign(Settings, JSON.parse(saveString)); + }, }; diff --git a/src/SourceFile/PlayerOwnedSourceFile.ts b/src/SourceFile/PlayerOwnedSourceFile.ts index 6eb40f3f9..1d6374c1b 100644 --- a/src/SourceFile/PlayerOwnedSourceFile.ts +++ b/src/SourceFile/PlayerOwnedSourceFile.ts @@ -1,17 +1,17 @@ export class PlayerOwnedSourceFile { - // Source-File level - lvl = 1; + // Source-File level + lvl = 1; - // Source-File number - n = 1; + // Source-File number + n = 1; - constructor(n: number, level: number) { - this.n = n; - this.lvl = level; - } + constructor(n: number, level: number) { + this.n = n; + this.lvl = level; + } } export interface IPlayerOwnedSourceFile { - lvl: number; - n: number; + lvl: number; + n: number; } diff --git a/src/SourceFile/SourceFile.ts b/src/SourceFile/SourceFile.ts index 67c217e0f..64ed0484d 100644 --- a/src/SourceFile/SourceFile.ts +++ b/src/SourceFile/SourceFile.ts @@ -1,21 +1,21 @@ import { BitNodes } from "../BitNode/BitNode"; export class SourceFile { - info: string; - lvl = 1; - n: number; - name: string; - owned = false; + info: string; + lvl = 1; + n: number; + name: string; + owned = false; - constructor(number: number, info="") { - const bitnodeKey = "BitNode" + number; - const bitnode = BitNodes[bitnodeKey]; - if (bitnode == null) { - throw new Error("Invalid Bit Node for this Source File"); - } - - this.n = number; - this.name = `Source-File ${number}: ${bitnode.name}` - this.info = info; + constructor(number: number, info = "") { + const bitnodeKey = "BitNode" + number; + const bitnode = BitNodes[bitnodeKey]; + if (bitnode == null) { + throw new Error("Invalid Bit Node for this Source File"); } + + this.n = number; + this.name = `Source-File ${number}: ${bitnode.name}`; + this.info = info; + } } diff --git a/src/SourceFile/SourceFileFlags.ts b/src/SourceFile/SourceFileFlags.ts index dc4565864..118fb94fd 100644 --- a/src/SourceFile/SourceFileFlags.ts +++ b/src/SourceFile/SourceFileFlags.ts @@ -7,12 +7,12 @@ import { IPlayer } from "../PersonObjects/IPlayer"; export const SourceFileFlags: number[] = Array(CONSTANTS.TotalNumBitNodes + 1); // Skip index 0 export function updateSourceFileFlags(p: IPlayer): void { - for (let i = 0; i < SourceFileFlags.length; ++i) { - SourceFileFlags[i] = 0; - } + for (let i = 0; i < SourceFileFlags.length; ++i) { + SourceFileFlags[i] = 0; + } - for (let i = 0; i < p.sourceFiles.length; ++i) { - const sf = p.sourceFiles[i]; - SourceFileFlags[sf.n] = sf.lvl; - } + for (let i = 0; i < p.sourceFiles.length; ++i) { + const sf = p.sourceFiles[i]; + SourceFileFlags[sf.n] = sf.lvl; + } } diff --git a/src/SourceFile/SourceFiles.ts b/src/SourceFile/SourceFiles.ts index bfd917326..18588d573 100644 --- a/src/SourceFile/SourceFiles.ts +++ b/src/SourceFile/SourceFiles.ts @@ -3,66 +3,102 @@ import { IMap } from "../types"; export const SourceFiles: IMap = {}; -SourceFiles["SourceFile1"] = new SourceFile(1, "This Source-File lets the player start with 32GB of RAM on his/her " + - "home computer. It also increases all of the player's multipliers by:

    " + - "Level 1: 16%
    " + - "Level 2: 24%
    " + - "Level 3: 28%"); -SourceFiles["SourceFile2"] = new SourceFile(2, "This Source-File allows you to form gangs in other BitNodes " + - "once your karma decreases to a certain value. It also increases the player's " + - "crime success rate, crime money, and charisma multipliers by:

    " + - "Level 1: 24%
    " + - "Level 2: 36%
    " + - "Level 3: 42%"); -SourceFiles["SourceFile3"] = new SourceFile(3,"This Source-File lets you create corporations on other BitNodes (although " + - "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); -SourceFiles["SourceFile4"] = new SourceFile(4, "This Source-File lets you access and use the Singularity Functions in every BitNode. Every " + - "level of this Source-File opens up more of the Singularity Functions you can use."); -SourceFiles["SourceFile5"] = new SourceFile(5, "This Source-File grants a special new stat called Intelligence. Intelligence " + - "is unique because it is permanent and persistent (it never gets reset back to 1). However, " + - "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't " + - "know when you gain experience and how much). Higher Intelligence levels will boost your production " + - "for many actions in the game. In addition, this Source-File will unlock the getBitNodeMultipliers() " + - "and getServer() Netscript functions, as well as the formulas API, and will raise all of your " + - "hacking-related multipliers by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); -SourceFiles["SourceFile6"] = new SourceFile(6, "This Source-File allows you to access the NSA's Bladeburner Division in other " + - "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); -SourceFiles["SourceFile7"] = new SourceFile(7, "This Source-File allows you to access the Bladeburner Netscript API in other " + - "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

    " + - "Level 1: 8%
    " + - "Level 2: 12%
    " + - "Level 3: 14%"); -SourceFiles["SourceFile8"] = new SourceFile(8, "This Source-File grants the following benefits:

    " + - "Level 1: Permanent access to WSE and TIX API
    " + - "Level 2: Ability to short stocks in other BitNodes
    " + - "Level 3: Ability to use limit/stop orders in other BitNodes

    " + - "This Source-File also increases your hacking growth multipliers by: " + - "
    Level 1: 12%
    Level 2: 18%
    Level 3: 21%"); -SourceFiles["SourceFile9"] = new SourceFile(9, "This Source-File grants the following benefits:

    " + - "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
    " + - "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
    " + - "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

    " + - "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + - "when installing Augmentations)"); -SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " + - "Source-File also grants you a Duplicate Sleeve"); -SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " + - "at that company by 1% per favor (rather than just the reputation gain). This Source-File also " + - " increases the player's company salary and reputation gain multipliers by:

    " + - "Level 1: 32%
    " + - "Level 2: 48%
    " + - "Level 3: 56%

    " + - "It also reduces the price increase for every aug bought by:

    "+ - "Level 1: 4%
    "+ - "Level 2: 6%
    "+ - "Level 3: 7%"); -SourceFiles["SourceFile12"] = new SourceFile(12, "This Source-File lets the player start with Neuroflux Governor equal to the level of this Source-File."); +SourceFiles["SourceFile1"] = new SourceFile( + 1, + "This Source-File lets the player start with 32GB of RAM on his/her " + + "home computer. It also increases all of the player's multipliers by:

    " + + "Level 1: 16%
    " + + "Level 2: 24%
    " + + "Level 3: 28%", +); +SourceFiles["SourceFile2"] = new SourceFile( + 2, + "This Source-File allows you to form gangs in other BitNodes " + + "once your karma decreases to a certain value. It also increases the player's " + + "crime success rate, crime money, and charisma multipliers by:

    " + + "Level 1: 24%
    " + + "Level 2: 36%
    " + + "Level 3: 42%", +); +SourceFiles["SourceFile3"] = new SourceFile( + 3, + "This Source-File lets you create corporations on other BitNodes (although " + + "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%", +); +SourceFiles["SourceFile4"] = new SourceFile( + 4, + "This Source-File lets you access and use the Singularity Functions in every BitNode. Every " + + "level of this Source-File opens up more of the Singularity Functions you can use.", +); +SourceFiles["SourceFile5"] = new SourceFile( + 5, + "This Source-File grants a special new stat called Intelligence. Intelligence " + + "is unique because it is permanent and persistent (it never gets reset back to 1). However, " + + "gaining Intelligence experience is much slower than other stats, and it is also hidden (you won't " + + "know when you gain experience and how much). Higher Intelligence levels will boost your production " + + "for many actions in the game. In addition, this Source-File will unlock the getBitNodeMultipliers() " + + "and getServer() Netscript functions, as well as the formulas API, and will raise all of your " + + "hacking-related multipliers by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%", +); +SourceFiles["SourceFile6"] = new SourceFile( + 6, + "This Source-File allows you to access the NSA's Bladeburner Division in other " + + "BitNodes. In addition, this Source-File will raise both the level and experience gain rate of all your combat stats by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%", +); +SourceFiles["SourceFile7"] = new SourceFile( + 7, + "This Source-File allows you to access the Bladeburner Netscript API in other " + + "BitNodes. In addition, this Source-File will increase all of your Bladeburner multipliers by:

    " + + "Level 1: 8%
    " + + "Level 2: 12%
    " + + "Level 3: 14%", +); +SourceFiles["SourceFile8"] = new SourceFile( + 8, + "This Source-File grants the following benefits:

    " + + "Level 1: Permanent access to WSE and TIX API
    " + + "Level 2: Ability to short stocks in other BitNodes
    " + + "Level 3: Ability to use limit/stop orders in other BitNodes

    " + + "This Source-File also increases your hacking growth multipliers by: " + + "
    Level 1: 12%
    Level 2: 18%
    Level 3: 21%", +); +SourceFiles["SourceFile9"] = new SourceFile( + 9, + "This Source-File grants the following benefits:

    " + + "Level 1: Permanently unlocks the Hacknet Server in other BitNodes
    " + + "Level 2: You start with 128GB of RAM on your home computer when entering a new BitNode
    " + + "Level 3: Grants a highly-upgraded Hacknet Server when entering a new BitNode

    " + + "(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT " + + "when installing Augmentations)", +); +SourceFiles["SourceFile10"] = new SourceFile( + 10, + "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " + + "Source-File also grants you a Duplicate Sleeve", +); +SourceFiles["SourceFile11"] = new SourceFile( + 11, + "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " + + "at that company by 1% per favor (rather than just the reputation gain). This Source-File also " + + " increases the player's company salary and reputation gain multipliers by:

    " + + "Level 1: 32%
    " + + "Level 2: 48%
    " + + "Level 3: 56%

    " + + "It also reduces the price increase for every aug bought by:

    " + + "Level 1: 4%
    " + + "Level 2: 6%
    " + + "Level 3: 7%", +); +SourceFiles["SourceFile12"] = new SourceFile( + 12, + "This Source-File lets the player start with Neuroflux Governor equal to the level of this Source-File.", +); diff --git a/src/SourceFile/applySourceFile.ts b/src/SourceFile/applySourceFile.ts index 03fdadd23..a612ea51c 100644 --- a/src/SourceFile/applySourceFile.ts +++ b/src/SourceFile/applySourceFile.ts @@ -4,150 +4,161 @@ import { SourceFiles } from "./SourceFiles"; import { Player } from "../Player"; export function applySourceFile(srcFile: PlayerOwnedSourceFile): void { - const srcFileKey = "SourceFile" + srcFile.n; - const sourceFileObject = SourceFiles[srcFileKey]; - if (sourceFileObject == null) { - console.error(`Invalid source file number: ${srcFile.n}`); - return; - } + const srcFileKey = "SourceFile" + srcFile.n; + const sourceFileObject = SourceFiles[srcFileKey]; + if (sourceFileObject == null) { + console.error(`Invalid source file number: ${srcFile.n}`); + return; + } - switch (srcFile.n) { - case 1: { // The Source Genesis - let mult = 0; - for (let i = 0; i < srcFile.lvl; ++i) { - mult += (16 / (Math.pow(2, i))); - } - const incMult = 1 + (mult / 100); - const decMult = 1 - (mult / 100); - Player.hacking_chance_mult *= incMult; - Player.hacking_speed_mult *= incMult; - Player.hacking_money_mult *= incMult; - Player.hacking_grow_mult *= incMult; - Player.hacking_mult *= incMult; - Player.strength_mult *= incMult; - Player.defense_mult *= incMult; - Player.dexterity_mult *= incMult; - Player.agility_mult *= incMult; - Player.charisma_mult *= incMult; - Player.hacking_exp_mult *= incMult; - Player.strength_exp_mult *= incMult; - Player.defense_exp_mult *= incMult; - Player.dexterity_exp_mult *= incMult; - Player.agility_exp_mult *= incMult; - Player.charisma_exp_mult *= incMult; - Player.company_rep_mult *= incMult; - Player.faction_rep_mult *= incMult; - Player.crime_money_mult *= incMult; - Player.crime_success_mult *= incMult; - Player.hacknet_node_money_mult *= incMult; - Player.hacknet_node_purchase_cost_mult *= decMult; - Player.hacknet_node_ram_cost_mult *= decMult; - Player.hacknet_node_core_cost_mult *= decMult; - Player.hacknet_node_level_cost_mult *= decMult; - Player.work_money_mult *= incMult; - break; - } - case 2: { // Rise of the Underworld - let mult = 0; - for (let i = 0; i < srcFile.lvl; ++i) { - mult += (24 / (Math.pow(2, i))); - } - const incMult = 1 + (mult / 100); - Player.crime_money_mult *= incMult; - Player.crime_success_mult *= incMult; - Player.charisma_mult *= incMult; - break; - } - case 3: { // Corporatocracy - let mult = 0; - for (let i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - const incMult = 1 + (mult / 100); - Player.charisma_mult *= incMult; - Player.work_money_mult *= incMult; - break; - } - case 4: { // The Singularity - // No effects, just gives access to Singularity functions - break; - } - case 5: { // Artificial Intelligence - let mult = 0; - for (let i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - const incMult = 1 + (mult / 100); - Player.hacking_chance_mult *= incMult; - Player.hacking_speed_mult *= incMult; - Player.hacking_money_mult *= incMult; - Player.hacking_grow_mult *= incMult; - Player.hacking_mult *= incMult; - Player.hacking_exp_mult *= incMult; - break; - } - case 6: { // Bladeburner - let mult = 0; - for (let i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - const incMult = 1 + (mult / 100); - Player.strength_exp_mult *= incMult; - Player.defense_exp_mult *= incMult; - Player.dexterity_exp_mult *= incMult; - Player.agility_exp_mult *= incMult; - Player.strength_mult *= incMult; - Player.defense_mult *= incMult; - Player.dexterity_mult *= incMult; - Player.agility_mult *= incMult; - break; - } - case 7: { // Bladeburner 2079 - let mult = 0; - for (let i = 0; i < srcFile.lvl; ++i) { - mult += (8 / (Math.pow(2, i))); - } - const incMult = 1 + (mult / 100); - Player.bladeburner_max_stamina_mult *= incMult; - Player.bladeburner_stamina_gain_mult *= incMult; - Player.bladeburner_analysis_mult *= incMult; - Player.bladeburner_success_chance_mult *= incMult; - break; - } - case 8: { // Ghost of Wall Street - let mult = 0; - for (let i = 0; i < srcFile.lvl; ++i) { - mult += (12 / (Math.pow(2, i))); - } - const incMult = 1 + (mult / 100); - Player.hacking_grow_mult *= incMult; - break; - } - case 9: { // Hacktocracy - // This has non-multiplier effects - break; - } - case 10: { // Digital Carbon - // No effects, just grants sleeves - break; - } - case 11: { // The Big Crash - let mult = 0; - for (let i = 0; i < srcFile.lvl; ++i) { - mult += (32 / (Math.pow(2, i))); - } - const incMult = 1 + (mult / 100); - Player.work_money_mult *= incMult; - Player.company_rep_mult *= incMult; - break; - } - case 12: // The Recursion - // No effects, grants neuroflux. - break; - default: - console.error(`Invalid source file number: ${srcFile.n}`); - break; + switch (srcFile.n) { + case 1: { + // The Source Genesis + let mult = 0; + for (let i = 0; i < srcFile.lvl; ++i) { + mult += 16 / Math.pow(2, i); + } + const incMult = 1 + mult / 100; + const decMult = 1 - mult / 100; + Player.hacking_chance_mult *= incMult; + Player.hacking_speed_mult *= incMult; + Player.hacking_money_mult *= incMult; + Player.hacking_grow_mult *= incMult; + Player.hacking_mult *= incMult; + Player.strength_mult *= incMult; + Player.defense_mult *= incMult; + Player.dexterity_mult *= incMult; + Player.agility_mult *= incMult; + Player.charisma_mult *= incMult; + Player.hacking_exp_mult *= incMult; + Player.strength_exp_mult *= incMult; + Player.defense_exp_mult *= incMult; + Player.dexterity_exp_mult *= incMult; + Player.agility_exp_mult *= incMult; + Player.charisma_exp_mult *= incMult; + Player.company_rep_mult *= incMult; + Player.faction_rep_mult *= incMult; + Player.crime_money_mult *= incMult; + Player.crime_success_mult *= incMult; + Player.hacknet_node_money_mult *= incMult; + Player.hacknet_node_purchase_cost_mult *= decMult; + Player.hacknet_node_ram_cost_mult *= decMult; + Player.hacknet_node_core_cost_mult *= decMult; + Player.hacknet_node_level_cost_mult *= decMult; + Player.work_money_mult *= incMult; + break; } + case 2: { + // Rise of the Underworld + let mult = 0; + for (let i = 0; i < srcFile.lvl; ++i) { + mult += 24 / Math.pow(2, i); + } + const incMult = 1 + mult / 100; + Player.crime_money_mult *= incMult; + Player.crime_success_mult *= incMult; + Player.charisma_mult *= incMult; + break; + } + case 3: { + // Corporatocracy + let mult = 0; + for (let i = 0; i < srcFile.lvl; ++i) { + mult += 8 / Math.pow(2, i); + } + const incMult = 1 + mult / 100; + Player.charisma_mult *= incMult; + Player.work_money_mult *= incMult; + break; + } + case 4: { + // The Singularity + // No effects, just gives access to Singularity functions + break; + } + case 5: { + // Artificial Intelligence + let mult = 0; + for (let i = 0; i < srcFile.lvl; ++i) { + mult += 8 / Math.pow(2, i); + } + const incMult = 1 + mult / 100; + Player.hacking_chance_mult *= incMult; + Player.hacking_speed_mult *= incMult; + Player.hacking_money_mult *= incMult; + Player.hacking_grow_mult *= incMult; + Player.hacking_mult *= incMult; + Player.hacking_exp_mult *= incMult; + break; + } + case 6: { + // Bladeburner + let mult = 0; + for (let i = 0; i < srcFile.lvl; ++i) { + mult += 8 / Math.pow(2, i); + } + const incMult = 1 + mult / 100; + Player.strength_exp_mult *= incMult; + Player.defense_exp_mult *= incMult; + Player.dexterity_exp_mult *= incMult; + Player.agility_exp_mult *= incMult; + Player.strength_mult *= incMult; + Player.defense_mult *= incMult; + Player.dexterity_mult *= incMult; + Player.agility_mult *= incMult; + break; + } + case 7: { + // Bladeburner 2079 + let mult = 0; + for (let i = 0; i < srcFile.lvl; ++i) { + mult += 8 / Math.pow(2, i); + } + const incMult = 1 + mult / 100; + Player.bladeburner_max_stamina_mult *= incMult; + Player.bladeburner_stamina_gain_mult *= incMult; + Player.bladeburner_analysis_mult *= incMult; + Player.bladeburner_success_chance_mult *= incMult; + break; + } + case 8: { + // Ghost of Wall Street + let mult = 0; + for (let i = 0; i < srcFile.lvl; ++i) { + mult += 12 / Math.pow(2, i); + } + const incMult = 1 + mult / 100; + Player.hacking_grow_mult *= incMult; + break; + } + case 9: { + // Hacktocracy + // This has non-multiplier effects + break; + } + case 10: { + // Digital Carbon + // No effects, just grants sleeves + break; + } + case 11: { + // The Big Crash + let mult = 0; + for (let i = 0; i < srcFile.lvl; ++i) { + mult += 32 / Math.pow(2, i); + } + const incMult = 1 + mult / 100; + Player.work_money_mult *= incMult; + Player.company_rep_mult *= incMult; + break; + } + case 12: // The Recursion + // No effects, grants neuroflux. + break; + default: + console.error(`Invalid source file number: ${srcFile.n}`); + break; + } - sourceFileObject.owned = true; + sourceFileObject.owned = true; } diff --git a/src/StockMarket/BuyingAndSelling.tsx b/src/StockMarket/BuyingAndSelling.tsx index bd12cf4c0..a756eff62 100644 --- a/src/StockMarket/BuyingAndSelling.tsx +++ b/src/StockMarket/BuyingAndSelling.tsx @@ -4,9 +4,9 @@ */ import { Stock } from "./Stock"; import { - getBuyTransactionCost, - getSellTransactionGain, - processTransactionForecastMovement, + getBuyTransactionCost, + getSellTransactionGain, + processTransactionForecastMovement, } from "./StockMarketHelpers"; import { PositionTypes } from "./data/PositionTypes"; @@ -23,11 +23,11 @@ import { dialogBoxCreate } from "../../utils/DialogBox"; import * as React from "react"; /** -* Each function takes an optional config object as its last argument -*/ + * Each function takes an optional config object as its last argument + */ interface IOptions { - rerenderFn?: () => void; - suppressDialog?: boolean; + rerenderFn?: () => void; + suppressDialog?: boolean; } /** @@ -38,64 +38,111 @@ interface IOptions { * @param opts - Optional configuration for this function's behavior. See top of file * @returns {boolean} - true if successful, false otherwise */ -export function buyStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean { - - // Validate arguments - shares = Math.round(shares); - if (shares <= 0) { return false; } - if (stock == null || isNaN(shares)) { - if (workerScript) { - workerScript.log("buyStock", `Invalid arguments: stock='${stock}' shares='${shares}'`); - } else if (opts.suppressDialog !== true) { - dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer"); - } - - return false; - } - - // Does player have enough money? - const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Long); - if (totalPrice == null) { return false; } - if (Player.money.lt(totalPrice)) { - if (workerScript) { - workerScript.log("buyStock", `You do not have enough money to purchase this position. You need ${numeralWrapper.formatMoney(totalPrice)}.`); - } else if (opts.suppressDialog !== true) { - dialogBoxCreate(<>You do not have enough money to purchase this. You need ); - } - - return false; - } - - // Would this purchase exceed the maximum number of shares? - if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) { - if (workerScript) { - workerScript.log("buyStock", `Purchasing '${shares + stock.playerShares + stock.playerShortShares}' shares would exceed ${stock.symbol}'s maximum (${stock.maxShares}) number of shares`); - } else if (opts.suppressDialog !== true) { - dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${numeralWrapper.formatShares(stock.maxShares)} shares.`); - } - - return false; - } - - const origTotal = stock.playerShares * stock.playerAvgPx; - Player.loseMoney(totalPrice); - const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission; - stock.playerShares = Math.round(stock.playerShares + shares); - stock.playerAvgPx = newTotal / stock.playerShares; - processTransactionForecastMovement(stock, shares); - if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { - opts.rerenderFn(); - } - +export function buyStock( + stock: Stock, + shares: number, + workerScript: WorkerScript | null = null, + opts: IOptions = {}, +): boolean { + // Validate arguments + shares = Math.round(shares); + if (shares <= 0) { + return false; + } + if (stock == null || isNaN(shares)) { if (workerScript) { - const resultTxt = `Bought ${numeralWrapper.formatShares(shares)} shares of ${stock.symbol} for ${numeralWrapper.formatMoney(totalPrice)}. ` + - `Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} in commission fees.` - workerScript.log("buyStock", resultTxt) + workerScript.log( + "buyStock", + `Invalid arguments: stock='${stock}' shares='${shares}'`, + ); } else if (opts.suppressDialog !== true) { - dialogBoxCreate(<>Bought {numeralWrapper.formatShares(shares)} shares of {stock.symbol} for . Paid in commission fees.); + dialogBoxCreate( + "Failed to buy stock. This may be a bug, contact developer", + ); } - return true; + return false; + } + + // Does player have enough money? + const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Long); + if (totalPrice == null) { + return false; + } + if (Player.money.lt(totalPrice)) { + if (workerScript) { + workerScript.log( + "buyStock", + `You do not have enough money to purchase this position. You need ${numeralWrapper.formatMoney( + totalPrice, + )}.`, + ); + } else if (opts.suppressDialog !== true) { + dialogBoxCreate( + <> + You do not have enough money to purchase this. You need{" "} + + , + ); + } + + return false; + } + + // Would this purchase exceed the maximum number of shares? + if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) { + if (workerScript) { + workerScript.log( + "buyStock", + `Purchasing '${ + shares + stock.playerShares + stock.playerShortShares + }' shares would exceed ${stock.symbol}'s maximum (${ + stock.maxShares + }) number of shares`, + ); + } else if (opts.suppressDialog !== true) { + dialogBoxCreate( + `You cannot purchase this many shares. ${ + stock.symbol + } has a maximum of ${numeralWrapper.formatShares( + stock.maxShares, + )} shares.`, + ); + } + + return false; + } + + const origTotal = stock.playerShares * stock.playerAvgPx; + Player.loseMoney(totalPrice); + const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission; + stock.playerShares = Math.round(stock.playerShares + shares); + stock.playerAvgPx = newTotal / stock.playerShares; + processTransactionForecastMovement(stock, shares); + if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { + opts.rerenderFn(); + } + + if (workerScript) { + const resultTxt = + `Bought ${numeralWrapper.formatShares(shares)} shares of ${ + stock.symbol + } for ${numeralWrapper.formatMoney(totalPrice)}. ` + + `Paid ${numeralWrapper.formatMoney( + CONSTANTS.StockMarketCommission, + )} in commission fees.`; + workerScript.log("buyStock", resultTxt); + } else if (opts.suppressDialog !== true) { + dialogBoxCreate( + <> + Bought {numeralWrapper.formatShares(shares)} shares of {stock.symbol}{" "} + for . Paid{" "} + in commission fees. + , + ); + } + + return true; } /** @@ -106,54 +153,80 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip * @param opts - Optional configuration for this function's behavior. See top of file * returns {boolean} - true if successfully sells given number of shares OR MAX owned, false otherwise */ -export function sellStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean { - - // Sanitize/Validate arguments - if (stock == null || shares < 0 || isNaN(shares)) { - if (workerScript) { - workerScript.log("sellStock", `Invalid arguments: stock='${stock}' shares='${shares}'`); - } else if (opts.suppressDialog !== true) { - dialogBoxCreate("Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer"); - } - - return false; - } - shares = Math.round(shares); - if (shares > stock.playerShares) { shares = stock.playerShares; } - if (shares === 0) { return false; } - - const gains = getSellTransactionGain(stock, shares, PositionTypes.Long); - if (gains == null) { return false; } - let netProfit = gains - (stock.playerAvgPx * shares); - if (isNaN(netProfit)) { netProfit = 0; } - Player.gainMoney(gains); - Player.recordMoneySource(netProfit, "stock"); +export function sellStock( + stock: Stock, + shares: number, + workerScript: WorkerScript | null = null, + opts: IOptions = {}, +): boolean { + // Sanitize/Validate arguments + if (stock == null || shares < 0 || isNaN(shares)) { if (workerScript) { - workerScript.scriptRef.onlineMoneyMade += netProfit; - Player.scriptProdSinceLastAug += netProfit; + workerScript.log( + "sellStock", + `Invalid arguments: stock='${stock}' shares='${shares}'`, + ); + } else if (opts.suppressDialog !== true) { + dialogBoxCreate( + "Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer", + ); } - stock.playerShares = Math.round(stock.playerShares - shares); - if (stock.playerShares === 0) { - stock.playerAvgPx = 0; - } + return false; + } + shares = Math.round(shares); + if (shares > stock.playerShares) { + shares = stock.playerShares; + } + if (shares === 0) { + return false; + } - processTransactionForecastMovement(stock, shares); + const gains = getSellTransactionGain(stock, shares, PositionTypes.Long); + if (gains == null) { + return false; + } + let netProfit = gains - stock.playerAvgPx * shares; + if (isNaN(netProfit)) { + netProfit = 0; + } + Player.gainMoney(gains); + Player.recordMoneySource(netProfit, "stock"); + if (workerScript) { + workerScript.scriptRef.onlineMoneyMade += netProfit; + Player.scriptProdSinceLastAug += netProfit; + } - if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { - opts.rerenderFn(); - } + stock.playerShares = Math.round(stock.playerShares - shares); + if (stock.playerShares === 0) { + stock.playerAvgPx = 0; + } - - if (workerScript) { - const resultTxt = `Sold ${numeralWrapper.formatShares(shares)} shares of ${stock.symbol}. ` + - `After commissions, you gained a total of ${numeralWrapper.formatMoney(gains)}.`; - workerScript.log("sellStock", resultTxt) - } else if (opts.suppressDialog !== true) { - dialogBoxCreate(<>Sold {numeralWrapper.formatShares(shares)} shares of {stock.symbol}. After commissions, you gained a total of .); - } + processTransactionForecastMovement(stock, shares); - return true; + if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { + opts.rerenderFn(); + } + + if (workerScript) { + const resultTxt = + `Sold ${numeralWrapper.formatShares(shares)} shares of ${ + stock.symbol + }. ` + + `After commissions, you gained a total of ${numeralWrapper.formatMoney( + gains, + )}.`; + workerScript.log("sellStock", resultTxt); + } else if (opts.suppressDialog !== true) { + dialogBoxCreate( + <> + Sold {numeralWrapper.formatShares(shares)} shares of {stock.symbol}. + After commissions, you gained a total of . + , + ); + } + + return true; } /** @@ -164,68 +237,111 @@ export function sellStock(stock: Stock, shares: number, workerScript: WorkerScri * @param opts - Optional configuration for this function's behavior. See top of file * @returns {boolean} - true if successful, false otherwise */ -export function shortStock(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean { - - // Validate arguments - shares = Math.round(shares); - if (shares <= 0) { return false; } - if (stock == null || isNaN(shares)) { - if (workerScript) { - workerScript.log("shortStock", `Invalid arguments: stock='${stock}' shares='${shares}'`); - } else if (opts.suppressDialog !== true) { - dialogBoxCreate("Failed to initiate a short position in a stock. This is probably " + - "due to an invalid quantity. Otherwise, this may be a bug, so contact developer"); - } - return false; - } - - // Does the player have enough money? - const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Short); - if (totalPrice == null) { return false; } - if (Player.money.lt(totalPrice)) { - if (workerScript) { - workerScript.log("shortStock", "You do not have enough " + - "money to purchase this short position. You need " + - numeralWrapper.formatMoney(totalPrice)); - } else if (opts.suppressDialog !== true) { - dialogBoxCreate(<>You do not have enough money to purchase this short position. You need ); - } - - return false; - } - - // Would this purchase exceed the maximum number of shares? - if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) { - if (workerScript) { - workerScript.log("shortStock", `This '${shares + stock.playerShares + stock.playerShortShares}' short shares would exceed ${stock.symbol}'s maximum (${stock.maxShares}) number of shares.`); - } else if (opts.suppressDialog !== true) { - dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${stock.maxShares} shares.`); - } - - return false; - } - - const origTotal = stock.playerShortShares * stock.playerAvgShortPx; - Player.loseMoney(totalPrice); - const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission; - stock.playerShortShares = Math.round(stock.playerShortShares + shares); - stock.playerAvgShortPx = newTotal / stock.playerShortShares; - processTransactionForecastMovement(stock, shares); - - if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { - opts.rerenderFn(); - } - +export function shortStock( + stock: Stock, + shares: number, + workerScript: WorkerScript | null = null, + opts: IOptions = {}, +): boolean { + // Validate arguments + shares = Math.round(shares); + if (shares <= 0) { + return false; + } + if (stock == null || isNaN(shares)) { if (workerScript) { - const resultTxt = `Bought a short position of ${numeralWrapper.formatShares(shares)} shares of ${stock.symbol} ` + - `for ${numeralWrapper.formatMoney(totalPrice)}. Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} ` + - `in commission fees.`; - workerScript.log("shortStock", resultTxt); - } else if (!opts.suppressDialog) { - dialogBoxCreate(<>Bought a short position of {numeralWrapper.formatShares(shares)} shares of {stock.symbol} for . Paid in commission fees.); + workerScript.log( + "shortStock", + `Invalid arguments: stock='${stock}' shares='${shares}'`, + ); + } else if (opts.suppressDialog !== true) { + dialogBoxCreate( + "Failed to initiate a short position in a stock. This is probably " + + "due to an invalid quantity. Otherwise, this may be a bug, so contact developer", + ); + } + return false; + } + + // Does the player have enough money? + const totalPrice = getBuyTransactionCost(stock, shares, PositionTypes.Short); + if (totalPrice == null) { + return false; + } + if (Player.money.lt(totalPrice)) { + if (workerScript) { + workerScript.log( + "shortStock", + "You do not have enough " + + "money to purchase this short position. You need " + + numeralWrapper.formatMoney(totalPrice), + ); + } else if (opts.suppressDialog !== true) { + dialogBoxCreate( + <> + You do not have enough money to purchase this short position. You need{" "} + + , + ); } - return true; + return false; + } + + // Would this purchase exceed the maximum number of shares? + if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) { + if (workerScript) { + workerScript.log( + "shortStock", + `This '${ + shares + stock.playerShares + stock.playerShortShares + }' short shares would exceed ${stock.symbol}'s maximum (${ + stock.maxShares + }) number of shares.`, + ); + } else if (opts.suppressDialog !== true) { + dialogBoxCreate( + `You cannot purchase this many shares. ${stock.symbol} has a maximum of ${stock.maxShares} shares.`, + ); + } + + return false; + } + + const origTotal = stock.playerShortShares * stock.playerAvgShortPx; + Player.loseMoney(totalPrice); + const newTotal = origTotal + totalPrice - CONSTANTS.StockMarketCommission; + stock.playerShortShares = Math.round(stock.playerShortShares + shares); + stock.playerAvgShortPx = newTotal / stock.playerShortShares; + processTransactionForecastMovement(stock, shares); + + if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { + opts.rerenderFn(); + } + + if (workerScript) { + const resultTxt = + `Bought a short position of ${numeralWrapper.formatShares( + shares, + )} shares of ${stock.symbol} ` + + `for ${numeralWrapper.formatMoney( + totalPrice, + )}. Paid ${numeralWrapper.formatMoney( + CONSTANTS.StockMarketCommission, + )} ` + + `in commission fees.`; + workerScript.log("shortStock", resultTxt); + } else if (!opts.suppressDialog) { + dialogBoxCreate( + <> + Bought a short position of {numeralWrapper.formatShares(shares)} shares + of {stock.symbol} for . Paid{" "} + in commission fees. + , + ); + } + + return true; } /** @@ -236,59 +352,90 @@ export function shortStock(stock: Stock, shares: number, workerScript: WorkerScr * @param opts - Optional configuration for this function's behavior. See top of file * @returns {boolean} true if successfully sells given amount OR max owned, false otherwise */ -export function sellShort(stock: Stock, shares: number, workerScript: WorkerScript | null=null, opts: IOptions={}): boolean { - - if (stock == null || isNaN(shares) || shares < 0) { - if (workerScript) { - workerScript.log("sellShort", `Invalid arguments: stock='${stock}' shares='${shares}'`); - } else if (!opts.suppressDialog) { - dialogBoxCreate("Failed to sell a short position in a stock. This is probably " + - "due to an invalid quantity. Otherwise, this may be a bug, so contact developer"); - } - - return false; - } - shares = Math.round(shares); - if (shares > stock.playerShortShares) {shares = stock.playerShortShares;} - if (shares === 0) {return false;} - - const origCost = shares * stock.playerAvgShortPx; - const totalGain = getSellTransactionGain(stock, shares, PositionTypes.Short); - if (totalGain == null || isNaN(totalGain) || origCost == null) { - if (workerScript) { - workerScript.log("sellShort", `Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`); - } else if (!opts.suppressDialog) { - dialogBoxCreate(`Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`); - } - - return false; - } - let profit = totalGain - origCost; - if (isNaN(profit)) { profit = 0; } - Player.gainMoney(totalGain); - Player.recordMoneySource(profit, "stock"); +export function sellShort( + stock: Stock, + shares: number, + workerScript: WorkerScript | null = null, + opts: IOptions = {}, +): boolean { + if (stock == null || isNaN(shares) || shares < 0) { if (workerScript) { - workerScript.scriptRef.onlineMoneyMade += profit; - Player.scriptProdSinceLastAug += profit; - } - - stock.playerShortShares = Math.round(stock.playerShortShares - shares); - if (stock.playerShortShares === 0) { - stock.playerAvgShortPx = 0; - } - processTransactionForecastMovement(stock, shares); - - if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { - opts.rerenderFn(); - } - - if (workerScript) { - const resultTxt = `Sold your short position of ${numeralWrapper.formatShares(shares)} shares of ${stock.symbol}. ` + - `After commissions, you gained a total of ${numeralWrapper.formatMoney(totalGain)}`; - workerScript.log("sellShort", resultTxt); + workerScript.log( + "sellShort", + `Invalid arguments: stock='${stock}' shares='${shares}'`, + ); } else if (!opts.suppressDialog) { - dialogBoxCreate(<>Sold your short position of {numeralWrapper.formatShares(shares)} shares of {stock.symbol}. After commissions, you gained a total of ); + dialogBoxCreate( + "Failed to sell a short position in a stock. This is probably " + + "due to an invalid quantity. Otherwise, this may be a bug, so contact developer", + ); } - return true; + return false; + } + shares = Math.round(shares); + if (shares > stock.playerShortShares) { + shares = stock.playerShortShares; + } + if (shares === 0) { + return false; + } + + const origCost = shares * stock.playerAvgShortPx; + const totalGain = getSellTransactionGain(stock, shares, PositionTypes.Short); + if (totalGain == null || isNaN(totalGain) || origCost == null) { + if (workerScript) { + workerScript.log( + "sellShort", + `Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`, + ); + } else if (!opts.suppressDialog) { + dialogBoxCreate( + `Failed to sell short position in a stock. This is probably either due to invalid arguments, or a bug`, + ); + } + + return false; + } + let profit = totalGain - origCost; + if (isNaN(profit)) { + profit = 0; + } + Player.gainMoney(totalGain); + Player.recordMoneySource(profit, "stock"); + if (workerScript) { + workerScript.scriptRef.onlineMoneyMade += profit; + Player.scriptProdSinceLastAug += profit; + } + + stock.playerShortShares = Math.round(stock.playerShortShares - shares); + if (stock.playerShortShares === 0) { + stock.playerAvgShortPx = 0; + } + processTransactionForecastMovement(stock, shares); + + if (opts.rerenderFn != null && typeof opts.rerenderFn === "function") { + opts.rerenderFn(); + } + + if (workerScript) { + const resultTxt = + `Sold your short position of ${numeralWrapper.formatShares( + shares, + )} shares of ${stock.symbol}. ` + + `After commissions, you gained a total of ${numeralWrapper.formatMoney( + totalGain, + )}`; + workerScript.log("sellShort", resultTxt); + } else if (!opts.suppressDialog) { + dialogBoxCreate( + <> + Sold your short position of {numeralWrapper.formatShares(shares)} shares + of {stock.symbol}. After commissions, you gained a total of{" "} + + , + ); + } + + return true; } diff --git a/src/StockMarket/IOrderBook.ts b/src/StockMarket/IOrderBook.ts index bd761d732..ef993893e 100644 --- a/src/StockMarket/IOrderBook.ts +++ b/src/StockMarket/IOrderBook.ts @@ -1,5 +1,5 @@ import { Order } from "./Order"; export interface IOrderBook { - [key: string]: Order[]; + [key: string]: Order[]; } diff --git a/src/StockMarket/IStockMarket.ts b/src/StockMarket/IStockMarket.ts index eccfca12b..3c3643ea1 100644 --- a/src/StockMarket/IStockMarket.ts +++ b/src/StockMarket/IStockMarket.ts @@ -2,10 +2,10 @@ import { IOrderBook } from "./IOrderBook"; import { Stock } from "./Stock"; export type IStockMarket = { - [key: string]: Stock; + [key: string]: Stock; } & { - lastUpdate: number; - Orders: IOrderBook; - storedCycles: number; - ticksUntilCycle: number; + lastUpdate: number; + Orders: IOrderBook; + storedCycles: number; + ticksUntilCycle: number; }; diff --git a/src/StockMarket/Order.ts b/src/StockMarket/Order.ts index 41653f036..6fe81a33c 100644 --- a/src/StockMarket/Order.ts +++ b/src/StockMarket/Order.ts @@ -6,56 +6,61 @@ import { OrderTypes } from "./data/OrderTypes"; import { PositionTypes } from "./data/PositionTypes"; import { - Generic_fromJSON, - Generic_toJSON, - Reviver, + Generic_fromJSON, + Generic_toJSON, + Reviver, } from "../../utils/JSONReviver"; export class Order { + readonly pos: PositionTypes; + readonly price: number; + shares: number; + readonly stockSymbol: string; + readonly type: OrderTypes; - readonly pos: PositionTypes; - readonly price: number; - shares: number; - readonly stockSymbol: string; - readonly type: OrderTypes; - - constructor(stockSymbol="", shares=0, price=0, typ: OrderTypes=OrderTypes.LimitBuy, pos: PositionTypes=PositionTypes.Long) { - // Validate arguments - let invalidArgs = false; - if (typeof shares !== "number" || typeof price !== "number") { - invalidArgs = true; - } - if (isNaN(shares) || isNaN(price)) { - invalidArgs = true; - } - if (typeof stockSymbol !== "string") { - invalidArgs = true; - } - if (invalidArgs) { - throw new Error(`Invalid constructor paramters for Order`); - } - - this.stockSymbol = stockSymbol; - this.shares = shares; - this.price = price; - this.type = typ; - this.pos = pos; + constructor( + stockSymbol = "", + shares = 0, + price = 0, + typ: OrderTypes = OrderTypes.LimitBuy, + pos: PositionTypes = PositionTypes.Long, + ) { + // Validate arguments + let invalidArgs = false; + if (typeof shares !== "number" || typeof price !== "number") { + invalidArgs = true; + } + if (isNaN(shares) || isNaN(price)) { + invalidArgs = true; + } + if (typeof stockSymbol !== "string") { + invalidArgs = true; + } + if (invalidArgs) { + throw new Error(`Invalid constructor paramters for Order`); } - /** - * Serialize the Order to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("Order", this); - } + this.stockSymbol = stockSymbol; + this.shares = shares; + this.price = price; + this.type = typ; + this.pos = pos; + } - /** - * Initializes a Order from a JSON save state - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Order { - return Generic_fromJSON(Order, value.data); - } + /** + * Serialize the Order to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Order", this); + } + + /** + * Initializes a Order from a JSON save state + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Order { + return Generic_fromJSON(Order, value.data); + } } Reviver.constructors.Order = Order; diff --git a/src/StockMarket/OrderProcessing.tsx b/src/StockMarket/OrderProcessing.tsx index a44a71a59..16f91a4af 100644 --- a/src/StockMarket/OrderProcessing.tsx +++ b/src/StockMarket/OrderProcessing.tsx @@ -2,12 +2,7 @@ * Helper functions for determine whether Limit and Stop orders should * be executed (and executing them) */ -import { - buyStock, - sellStock, - shortStock, - sellShort, -} from "./BuyingAndSelling"; +import { buyStock, sellStock, shortStock, sellShort } from "./BuyingAndSelling"; import { IOrderBook } from "./IOrderBook"; import { IStockMarket } from "./IStockMarket"; import { Order } from "./Order"; @@ -26,9 +21,9 @@ import { dialogBoxCreate } from "../../utils/DialogBox"; import * as React from "react"; export interface IProcessOrderRefs { - rerenderFn: () => void; - stockMarket: IStockMarket; - symbolToStockMap: IMap; + rerenderFn: () => void; + stockMarket: IStockMarket; + symbolToStockMap: IMap; } /** @@ -38,62 +33,83 @@ export interface IProcessOrderRefs { * @param {PositionTypes} posType - Long or short * @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function */ -export function processOrders(stock: Stock, orderType: OrderTypes, posType: PositionTypes, refs: IProcessOrderRefs): void { - const orderBook = refs.stockMarket["Orders"]; - if (orderBook == null) { - const orders: IOrderBook = {}; - for (const name in refs.stockMarket) { - const stock = refs.stockMarket[name]; - if (!(stock instanceof Stock)) { continue; } - orders[stock.symbol] = []; - } - refs.stockMarket["Orders"] = orders; - return; // Newly created, so no orders to process - } - let stockOrders = orderBook[stock.symbol]; - if (stockOrders == null || !(stockOrders.constructor === Array)) { - console.error(`Invalid Order book for ${stock.symbol} in processOrders(): ${stockOrders}`); - stockOrders = []; - return; +export function processOrders( + stock: Stock, + orderType: OrderTypes, + posType: PositionTypes, + refs: IProcessOrderRefs, +): void { + const orderBook = refs.stockMarket["Orders"]; + if (orderBook == null) { + const orders: IOrderBook = {}; + for (const name in refs.stockMarket) { + const stock = refs.stockMarket[name]; + if (!(stock instanceof Stock)) { + continue; + } + orders[stock.symbol] = []; } + refs.stockMarket["Orders"] = orders; + return; // Newly created, so no orders to process + } + let stockOrders = orderBook[stock.symbol]; + if (stockOrders == null || !(stockOrders.constructor === Array)) { + console.error( + `Invalid Order book for ${stock.symbol} in processOrders(): ${stockOrders}`, + ); + stockOrders = []; + return; + } - for (const order of stockOrders) { - if (order.type === orderType && order.pos === posType) { - switch (order.type) { - case OrderTypes.LimitBuy: - if (order.pos === PositionTypes.Long && stock.price <= order.price) { - executeOrder/*66*/(order, refs); - } else if (order.pos === PositionTypes.Short && stock.price >= order.price) { - executeOrder/*66*/(order, refs); - } - break; - case OrderTypes.LimitSell: - if (order.pos === PositionTypes.Long && stock.price >= order.price) { - executeOrder/*66*/(order, refs); - } else if (order.pos === PositionTypes.Short && stock.price <= order.price) { - executeOrder/*66*/(order, refs); - } - break; - case OrderTypes.StopBuy: - if (order.pos === PositionTypes.Long && stock.price >= order.price) { - executeOrder/*66*/(order, refs); - } else if (order.pos === PositionTypes.Short && stock.price <= order.price) { - executeOrder/*66*/(order, refs); - } - break; - case OrderTypes.StopSell: - if (order.pos === PositionTypes.Long && stock.price <= order.price) { - executeOrder/*66*/(order, refs); - } else if (order.pos === PositionTypes.Short && stock.price >= order.price) { - executeOrder/*66*/(order, refs); - } - break; - default: - console.warn(`Invalid order type: ${order.type}`); - return; - } - } + for (const order of stockOrders) { + if (order.type === orderType && order.pos === posType) { + switch (order.type) { + case OrderTypes.LimitBuy: + if (order.pos === PositionTypes.Long && stock.price <= order.price) { + executeOrder(/*66*/ order, refs); + } else if ( + order.pos === PositionTypes.Short && + stock.price >= order.price + ) { + executeOrder(/*66*/ order, refs); + } + break; + case OrderTypes.LimitSell: + if (order.pos === PositionTypes.Long && stock.price >= order.price) { + executeOrder(/*66*/ order, refs); + } else if ( + order.pos === PositionTypes.Short && + stock.price <= order.price + ) { + executeOrder(/*66*/ order, refs); + } + break; + case OrderTypes.StopBuy: + if (order.pos === PositionTypes.Long && stock.price >= order.price) { + executeOrder(/*66*/ order, refs); + } else if ( + order.pos === PositionTypes.Short && + stock.price <= order.price + ) { + executeOrder(/*66*/ order, refs); + } + break; + case OrderTypes.StopSell: + if (order.pos === PositionTypes.Long && stock.price <= order.price) { + executeOrder(/*66*/ order, refs); + } else if ( + order.pos === PositionTypes.Short && + stock.price >= order.price + ) { + executeOrder(/*66*/ order, refs); + } + break; + default: + console.warn(`Invalid order type: ${order.type}`); + return; + } } + } } /** @@ -102,65 +118,78 @@ export function processOrders(stock: Stock, orderType: OrderTypes, posType: Posi * @param {IProcessOrderRefs} refs - References to objects/functions that are required for this function */ function executeOrder(order: Order, refs: IProcessOrderRefs): void { - const stock = refs.symbolToStockMap[order.stockSymbol]; - if (!(stock instanceof Stock)) { - console.error(`Could not find stock for this order: ${order.stockSymbol}`); + const stock = refs.symbolToStockMap[order.stockSymbol]; + if (!(stock instanceof Stock)) { + console.error(`Could not find stock for this order: ${order.stockSymbol}`); + return; + } + const stockMarket = refs.stockMarket; + const orderBook = stockMarket["Orders"]; + const stockOrders = orderBook[stock.symbol]; + + // When orders are executed, the buying and selling functions shouldn't + // emit popup dialog boxes. This options object configures the functions for that + const opts = { + rerenderFn: refs.rerenderFn, + suppressDialog: true, + }; + + let res = true; + let isBuy = false; + switch (order.type) { + case OrderTypes.LimitBuy: + case OrderTypes.StopBuy: + isBuy = true; + if (order.pos === PositionTypes.Long) { + res = buyStock(stock, order.shares, null, opts) && res; + } else if (order.pos === PositionTypes.Short) { + res = shortStock(stock, order.shares, null, opts) && res; + } + break; + case OrderTypes.LimitSell: + case OrderTypes.StopSell: + if (order.pos === PositionTypes.Long) { + res = sellStock(stock, order.shares, null, opts) && res; + } else if (order.pos === PositionTypes.Short) { + res = sellShort(stock, order.shares, null, opts) && res; + } + break; + default: + console.warn(`Invalid order type: ${order.type}`); + return; + } + + // Position type, for logging/message purposes + const pos = order.pos === PositionTypes.Long ? "Long" : "Short"; + + if (res) { + for (let i = 0; i < stockOrders.length; ++i) { + if (order == stockOrders[i]) { + stockOrders.splice(i, 1); + dialogBoxCreate( + <> + {order.type} for {stock.symbol} @ ( + {pos}) was filled ( + {numeralWrapper.formatShares(Math.round(order.shares))} shares) + , + ); + refs.rerenderFn(); return; - } - const stockMarket = refs.stockMarket; - const orderBook = stockMarket["Orders"]; - const stockOrders = orderBook[stock.symbol]; - - // When orders are executed, the buying and selling functions shouldn't - // emit popup dialog boxes. This options object configures the functions for that - const opts = { - rerenderFn: refs.rerenderFn, - suppressDialog: true, + } } - let res = true; - let isBuy = false; - switch (order.type) { - case OrderTypes.LimitBuy: - case OrderTypes.StopBuy: - isBuy = true; - if (order.pos === PositionTypes.Long) { - res = buyStock(stock, order.shares, null, opts) && res; - } else if (order.pos === PositionTypes.Short) { - res = shortStock(stock, order.shares, null, opts) && res; - } - break; - case OrderTypes.LimitSell: - case OrderTypes.StopSell: - if (order.pos === PositionTypes.Long) { - res = sellStock(stock, order.shares, null, opts) && res; - } else if (order.pos === PositionTypes.Short) { - res = sellShort(stock, order.shares, null, opts) && res; - } - break; - default: - console.warn(`Invalid order type: ${order.type}`); - return; - } - - // Position type, for logging/message purposes - const pos = order.pos === PositionTypes.Long ? "Long" : "Short"; - - if (res) { - for (let i = 0; i < stockOrders.length; ++i) { - if (order == stockOrders[i]) { - stockOrders.splice(i, 1); - dialogBoxCreate(<>{order.type} for {stock.symbol} @ ({pos}) was filled ({numeralWrapper.formatShares(Math.round(order.shares))} shares)); - refs.rerenderFn(); - return; - } - } - - console.error("Could not find the following Order in Order Book: "); - console.error(order); - } else { - if (isBuy) { - dialogBoxCreate(<>Failed to execute {order.type} for {stock.symbol} @ ({pos}). This is most likely because you do not have enough money or the order would exceed the stock's maximum number of shares); - } + console.error("Could not find the following Order in Order Book: "); + console.error(order); + } else { + if (isBuy) { + dialogBoxCreate( + <> + Failed to execute {order.type} for {stock.symbol} @{" "} + ({pos}). This is most likely because you + do not have enough money or the order would exceed the stock's maximum + number of shares + , + ); } + } } diff --git a/src/StockMarket/PlayerInfluencing.ts b/src/StockMarket/PlayerInfluencing.ts index f3c9e49ff..ec1c92ae3 100644 --- a/src/StockMarket/PlayerInfluencing.ts +++ b/src/StockMarket/PlayerInfluencing.ts @@ -21,18 +21,25 @@ export const forecastForecastChangeFromCompanyWork = 0.001; * @param {Server} server - Server being hack()ed * @param {number} moneyHacked - Amount of money stolen from the server */ -export function influenceStockThroughServerHack(server: Server, moneyHacked: number): void { - const orgName = server.organizationName; - let stock: Stock | null = null; - if (typeof orgName === "string" && orgName !== "") { - stock = StockMarket[orgName]; - } - if (!(stock instanceof Stock)) { return; } +export function influenceStockThroughServerHack( + server: Server, + moneyHacked: number, +): void { + const orgName = server.organizationName; + let stock: Stock | null = null; + if (typeof orgName === "string" && orgName !== "") { + stock = StockMarket[orgName]; + } + if (!(stock instanceof Stock)) { + return; + } - const percTotalMoneyHacked = moneyHacked / server.moneyMax; - if (Math.random() < percTotalMoneyHacked) { - stock.changeForecastForecast(stock.otlkMagForecast - forecastForecastChangeFromHack); - } + const percTotalMoneyHacked = moneyHacked / server.moneyMax; + if (Math.random() < percTotalMoneyHacked) { + stock.changeForecastForecast( + stock.otlkMagForecast - forecastForecastChangeFromHack, + ); + } } /** @@ -42,18 +49,25 @@ export function influenceStockThroughServerHack(server: Server, moneyHacked: num * @param {Server} server - Server being grow()n * @param {number} moneyHacked - Amount of money added to the server */ -export function influenceStockThroughServerGrow(server: Server, moneyGrown: number): void { - const orgName = server.organizationName; - let stock: Stock | null = null; - if (typeof orgName === "string" && orgName !== "") { - stock = StockMarket[orgName]; - } - if (!(stock instanceof Stock)) { return; } +export function influenceStockThroughServerGrow( + server: Server, + moneyGrown: number, +): void { + const orgName = server.organizationName; + let stock: Stock | null = null; + if (typeof orgName === "string" && orgName !== "") { + stock = StockMarket[orgName]; + } + if (!(stock instanceof Stock)) { + return; + } - const percTotalMoneyGrown = moneyGrown / server.moneyMax; - if (Math.random() < percTotalMoneyGrown) { - stock.changeForecastForecast(stock.otlkMagForecast + forecastForecastChangeFromHack); - } + const percTotalMoneyGrown = moneyGrown / server.moneyMax; + if (Math.random() < percTotalMoneyGrown) { + stock.changeForecastForecast( + stock.otlkMagForecast + forecastForecastChangeFromHack, + ); + } } /** @@ -63,16 +77,22 @@ export function influenceStockThroughServerGrow(server: Server, moneyGrown: numb * @param {number} performanceMult - Effectiveness of player's work. Affects influence * @param {number} cyclesOfWork - # game cycles of work being processed */ -export function influenceStockThroughCompanyWork(company: Company, performanceMult: number, cyclesOfWork: number): void { - const compName = company.name; - let stock: Stock | null = null; - if (typeof compName === "string" && compName !== "") { - stock = StockMarket[compName]; - } - if (!(stock instanceof Stock)) { return; } +export function influenceStockThroughCompanyWork( + company: Company, + performanceMult: number, + cyclesOfWork: number, +): void { + const compName = company.name; + let stock: Stock | null = null; + if (typeof compName === "string" && compName !== "") { + stock = StockMarket[compName]; + } + if (!(stock instanceof Stock)) { + return; + } - if (Math.random() < 0.002 * cyclesOfWork) { - const change = forecastForecastChangeFromCompanyWork * performanceMult; - stock.changeForecastForecast(stock.otlkMagForecast + change); - } + if (Math.random() < 0.002 * cyclesOfWork) { + const change = forecastForecastChangeFromCompanyWork * performanceMult; + stock.changeForecastForecast(stock.otlkMagForecast + change); + } } diff --git a/src/StockMarket/Stock.ts b/src/StockMarket/Stock.ts index b5f0b7dd6..4926d5c64 100644 --- a/src/StockMarket/Stock.ts +++ b/src/StockMarket/Stock.ts @@ -1,330 +1,336 @@ import { IMinMaxRange } from "../types"; import { - Generic_fromJSON, - Generic_toJSON, - Reviver, + Generic_fromJSON, + Generic_toJSON, + Reviver, } from "../../utils/JSONReviver"; import { getRandomInt } from "../../utils/helpers/getRandomInt"; export const StockForecastInfluenceLimit = 5; export interface IConstructorParams { - b: boolean; - initPrice: number | IMinMaxRange; - marketCap: number; - mv: number | IMinMaxRange; - name: string; - otlkMag: number; - spreadPerc: number | IMinMaxRange; - shareTxForMovement: number | IMinMaxRange; - symbol: string; + b: boolean; + initPrice: number | IMinMaxRange; + marketCap: number; + mv: number | IMinMaxRange; + name: string; + otlkMag: number; + spreadPerc: number | IMinMaxRange; + shareTxForMovement: number | IMinMaxRange; + symbol: string; } const defaultConstructorParams: IConstructorParams = { - b: true, - initPrice: 10e3, - marketCap: 1e12, - mv: 1, - name: "", - otlkMag: 0, - spreadPerc: 0, - shareTxForMovement: 1e6, - symbol: "", -} + b: true, + initPrice: 10e3, + marketCap: 1e12, + mv: 1, + name: "", + otlkMag: 0, + spreadPerc: 0, + shareTxForMovement: 1e6, + symbol: "", +}; // Helper function that convert a IMinMaxRange to a number function toNumber(n: number | IMinMaxRange): number { - let value: number; - switch (typeof n) { - case "number": { - return n; - } - case "object": { - const range = n; - value = getRandomInt(range.min, range.max); - break; - } - default: - throw Error(`Do not know how to convert the type '${typeof n}' to a number`); + let value: number; + switch (typeof n) { + case "number": { + return n; } - - if (typeof n === "object" && typeof n.divisor === "number") { - return value / n.divisor; + case "object": { + const range = n; + value = getRandomInt(range.min, range.max); + break; } + default: + throw Error( + `Do not know how to convert the type '${typeof n}' to a number`, + ); + } - return value; + if (typeof n === "object" && typeof n.divisor === "number") { + return value / n.divisor; + } + + return value; } /** * Represents the valuation of a company in the World Stock Exchange. */ export class Stock { - /** - * Bear or bull (more likely to go up or down, based on otlkMag) - */ - b: boolean; + /** + * Bear or bull (more likely to go up or down, based on otlkMag) + */ + b: boolean; - /** - * Maximum price of a stock (per share) - */ - readonly cap: number; + /** + * Maximum price of a stock (per share) + */ + readonly cap: number; - /** - * Stocks previous share price - */ - lastPrice: number; + /** + * Stocks previous share price + */ + lastPrice: number; - /** - * Maximum number of shares that player can own (both long and short combined) - */ - readonly maxShares: number; + /** + * Maximum number of shares that player can own (both long and short combined) + */ + readonly maxShares: number; - /** - * Maximum volatility - */ - readonly mv: number; + /** + * Maximum volatility + */ + readonly mv: number; - /** - * Name of the company that the stock is for - */ - readonly name: string; + /** + * Name of the company that the stock is for + */ + readonly name: string; - /** - * Outlook magnitude. Represents the stock's forecast and likelihood - * of increasing/decreasing (based on whether its in bear or bull mode) - */ - otlkMag: number; + /** + * Outlook magnitude. Represents the stock's forecast and likelihood + * of increasing/decreasing (based on whether its in bear or bull mode) + */ + otlkMag: number; - /** - * Forecast of outlook magnitude. Essentially a second-order forecast. - * Unlike 'otlkMag', this number is on an absolute scale from 0-100 (rather than 0-50) - */ - otlkMagForecast: number; + /** + * Forecast of outlook magnitude. Essentially a second-order forecast. + * Unlike 'otlkMag', this number is on an absolute scale from 0-100 (rather than 0-50) + */ + otlkMagForecast: number; - /** - * Average price of stocks that the player owns in the LONG position - */ - playerAvgPx: number; + /** + * Average price of stocks that the player owns in the LONG position + */ + playerAvgPx: number; - /** - * Average price of stocks that the player owns in the SHORT position - */ - playerAvgShortPx: number; + /** + * Average price of stocks that the player owns in the SHORT position + */ + playerAvgShortPx: number; - /** - * Number of shares the player owns in the LONG position - */ - playerShares: number; + /** + * Number of shares the player owns in the LONG position + */ + playerShares: number; - /** - * Number of shares the player owns in the SHORT position - */ - playerShortShares: number; + /** + * Number of shares the player owns in the SHORT position + */ + playerShortShares: number; - /** - * Stock's share price - */ - price: number; + /** + * Stock's share price + */ + price: number; - /** - * How many shares need to be transacted in order to trigger a price movement - */ - readonly shareTxForMovement: number; + /** + * How many shares need to be transacted in order to trigger a price movement + */ + readonly shareTxForMovement: number; - /** - * How many share transactions remaining until a price movement occurs - * (separately tracked for upward and downward movements) - */ - shareTxUntilMovement: number; + /** + * How many share transactions remaining until a price movement occurs + * (separately tracked for upward and downward movements) + */ + shareTxUntilMovement: number; - /** - * Spread percentage. The bid/ask prices for this stock are N% above or below - * the "real price" to emulate spread. - */ - readonly spreadPerc: number; + /** + * Spread percentage. The bid/ask prices for this stock are N% above or below + * the "real price" to emulate spread. + */ + readonly spreadPerc: number; - /** - * The stock's ticker symbol - */ - readonly symbol: string; + /** + * The stock's ticker symbol + */ + readonly symbol: string; - /** - * Total number of shares of this stock - * This is different than maxShares, as this is like authorized stock while - * maxShares is outstanding stock. - */ - readonly totalShares: number; + /** + * Total number of shares of this stock + * This is different than maxShares, as this is like authorized stock while + * maxShares is outstanding stock. + */ + readonly totalShares: number; - constructor(p: IConstructorParams = defaultConstructorParams) { - this.name = p.name; - this.symbol = p.symbol; - this.price = toNumber(p.initPrice); - this.lastPrice = this.price; - this.playerShares = 0; - this.playerAvgPx = 0; - this.playerShortShares = 0; - this.playerAvgShortPx = 0; - this.mv = toNumber(p.mv); - this.b = p.b; - this.otlkMag = p.otlkMag; - this.otlkMagForecast = this.getAbsoluteForecast(); - this.cap = getRandomInt(this.price * 1e3, this.price * 25e3); - this.spreadPerc = toNumber(p.spreadPerc); - this.shareTxForMovement = toNumber(p.shareTxForMovement); - this.shareTxUntilMovement = this.shareTxForMovement; + constructor(p: IConstructorParams = defaultConstructorParams) { + this.name = p.name; + this.symbol = p.symbol; + this.price = toNumber(p.initPrice); + this.lastPrice = this.price; + this.playerShares = 0; + this.playerAvgPx = 0; + this.playerShortShares = 0; + this.playerAvgShortPx = 0; + this.mv = toNumber(p.mv); + this.b = p.b; + this.otlkMag = p.otlkMag; + this.otlkMagForecast = this.getAbsoluteForecast(); + this.cap = getRandomInt(this.price * 1e3, this.price * 25e3); + this.spreadPerc = toNumber(p.spreadPerc); + this.shareTxForMovement = toNumber(p.shareTxForMovement); + this.shareTxUntilMovement = this.shareTxForMovement; - // Total shares is determined by market cap, and is rounded to nearest 100k - const totalSharesUnrounded: number = (p.marketCap / this.price); - this.totalShares = Math.round(totalSharesUnrounded / 1e5) * 1e5; + // Total shares is determined by market cap, and is rounded to nearest 100k + const totalSharesUnrounded: number = p.marketCap / this.price; + this.totalShares = Math.round(totalSharesUnrounded / 1e5) * 1e5; - // Max Shares (Outstanding shares) is a percentage of total shares - const outstandingSharePercentage = 0.2; - this.maxShares = Math.round((this.totalShares * outstandingSharePercentage) / 1e5) * 1e5; + // Max Shares (Outstanding shares) is a percentage of total shares + const outstandingSharePercentage = 0.2; + this.maxShares = + Math.round((this.totalShares * outstandingSharePercentage) / 1e5) * 1e5; + } + + /** + * Safely set the stock's second-order forecast to a new value + */ + changeForecastForecast(newff: number): void { + this.otlkMagForecast = newff; + if (this.otlkMagForecast > 100) { + this.otlkMagForecast = 100; + } else if (this.otlkMagForecast < 0) { + this.otlkMagForecast = 0; + } + } + + /** + * Set the stock to a new price. Also updates the stock's previous price tracker + */ + changePrice(newPrice: number): void { + this.lastPrice = this.price; + this.price = newPrice; + } + + /** + * Change the stock's forecast during a stock market 'tick'. + * The way a stock's forecast changes depends on various internal properties, + * but is ultimately determined by RNG + */ + cycleForecast(changeAmt = 0.1): void { + const increaseChance = this.getForecastIncreaseChance(); + + if (Math.random() < increaseChance) { + // Forecast increases + if (this.b) { + this.otlkMag += changeAmt; + } else { + this.otlkMag -= changeAmt; + } + } else { + // Forecast decreases + if (this.b) { + this.otlkMag -= changeAmt; + } else { + this.otlkMag += changeAmt; + } } - /** - * Safely set the stock's second-order forecast to a new value - */ - changeForecastForecast(newff: number): void { - this.otlkMagForecast = newff; - if (this.otlkMagForecast > 100) { - this.otlkMagForecast = 100; - } else if (this.otlkMagForecast < 0) { - this.otlkMagForecast = 0; - } + this.otlkMag = Math.min(this.otlkMag, 50); + if (this.otlkMag < 0) { + this.otlkMag *= -1; + this.b = !this.b; } + } - /** - * Set the stock to a new price. Also updates the stock's previous price tracker - */ - changePrice(newPrice: number): void { - this.lastPrice = this.price; - this.price = newPrice; + /** + * Change's the stock's second-order forecast during a stock market 'tick'. + * The change for the second-order forecast to increase is 50/50 + */ + cycleForecastForecast(changeAmt = 0.1): void { + if (Math.random() < 0.5) { + this.changeForecastForecast(this.otlkMagForecast + changeAmt); + } else { + this.changeForecastForecast(this.otlkMagForecast - changeAmt); } + } - /** - * Change the stock's forecast during a stock market 'tick'. - * The way a stock's forecast changes depends on various internal properties, - * but is ultimately determined by RNG - */ - cycleForecast(changeAmt=0.1): void { - const increaseChance = this.getForecastIncreaseChance(); + /** + * "Flip" the stock's second-order forecast. This can occur during a + * stock market "cycle" (determined by RNG). It is used to simulate + * RL stock market cycles and introduce volatility + */ + flipForecastForecast(): void { + const diff = this.otlkMagForecast - 50; + this.otlkMagForecast = 50 + -1 * diff; + } - if (Math.random() < increaseChance) { - // Forecast increases - if (this.b) { - this.otlkMag += changeAmt; - } else { - this.otlkMag -= changeAmt; - } - } else { - // Forecast decreases - if (this.b) { - this.otlkMag -= changeAmt; - } else { - this.otlkMag += changeAmt; - } - } + /** + * Returns the stock's absolute forecast, which is a number between 0-100 + */ + getAbsoluteForecast(): number { + return this.b ? 50 + this.otlkMag : 50 - this.otlkMag; + } - this.otlkMag = Math.min(this.otlkMag, 50); - if (this.otlkMag < 0) { - this.otlkMag *= -1; - this.b = !this.b; - } + /** + * Return the price at which YOUR stock is bought (market ask price). Accounts for spread + */ + getAskPrice(): number { + return this.price * (1 + this.spreadPerc / 100); + } + + /** + * Return the price at which YOUR stock is sold (market bid price). Accounts for spread + */ + getBidPrice(): number { + return this.price * (1 - this.spreadPerc / 100); + } + + /** + * Returns the chance (0-1 decimal) that a stock has of having its forecast increase + */ + getForecastIncreaseChance(): number { + const diff = this.otlkMagForecast - this.getAbsoluteForecast(); + + return (50 + Math.min(Math.max(diff, -45), 45)) / 100; + } + + /** + * Changes a stock's forecast. This is used when the stock is influenced + * by a transaction. The stock's forecast always goes towards 50, but the + * movement is capped by a certain threshold/limit + */ + influenceForecast(change: number): void { + if (this.otlkMag > StockForecastInfluenceLimit) { + this.otlkMag = Math.max( + StockForecastInfluenceLimit, + this.otlkMag - change, + ); } + } - /** - * Change's the stock's second-order forecast during a stock market 'tick'. - * The change for the second-order forecast to increase is 50/50 - */ - cycleForecastForecast(changeAmt=0.1): void { - if (Math.random() < 0.5) { - this.changeForecastForecast(this.otlkMagForecast + changeAmt); - } else { - this.changeForecastForecast(this.otlkMagForecast - changeAmt); - } + /** + * Changes a stock's second-order forecast. This is used when the stock is + * influenced by a transaction. The stock's second-order forecast always + * goes towards 50. + */ + influenceForecastForecast(change: number): void { + if (this.otlkMagForecast > 50) { + this.otlkMagForecast -= change; + this.otlkMagForecast = Math.max(50, this.otlkMagForecast); + } else if (this.otlkMagForecast < 50) { + this.otlkMagForecast += change; + this.otlkMagForecast = Math.min(50, this.otlkMagForecast); } + } - /** - * "Flip" the stock's second-order forecast. This can occur during a - * stock market "cycle" (determined by RNG). It is used to simulate - * RL stock market cycles and introduce volatility - */ - flipForecastForecast(): void { - const diff = this.otlkMagForecast - 50; - this.otlkMagForecast = 50 + (-1 * diff); - } + /** + * Serialize the Stock to a JSON save state. + */ + toJSON(): any { + return Generic_toJSON("Stock", this); + } - /** - * Returns the stock's absolute forecast, which is a number between 0-100 - */ - getAbsoluteForecast(): number { - return this.b ? 50 + this.otlkMag : 50 - this.otlkMag; - } - - /** - * Return the price at which YOUR stock is bought (market ask price). Accounts for spread - */ - getAskPrice(): number { - return this.price * (1 + (this.spreadPerc / 100)); - } - - /** - * Return the price at which YOUR stock is sold (market bid price). Accounts for spread - */ - getBidPrice(): number { - return this.price * (1 - (this.spreadPerc / 100)); - } - - /** - * Returns the chance (0-1 decimal) that a stock has of having its forecast increase - */ - getForecastIncreaseChance(): number { - const diff = this.otlkMagForecast - this.getAbsoluteForecast(); - - return (50 + Math.min(Math.max(diff, -45), 45)) / 100; - } - - /** - * Changes a stock's forecast. This is used when the stock is influenced - * by a transaction. The stock's forecast always goes towards 50, but the - * movement is capped by a certain threshold/limit - */ - influenceForecast(change: number): void { - if (this.otlkMag > StockForecastInfluenceLimit) { - this.otlkMag = Math.max(StockForecastInfluenceLimit, this.otlkMag - change); - } - } - - /** - * Changes a stock's second-order forecast. This is used when the stock is - * influenced by a transaction. The stock's second-order forecast always - * goes towards 50. - */ - influenceForecastForecast(change: number): void { - if (this.otlkMagForecast > 50) { - this.otlkMagForecast -= change; - this.otlkMagForecast = Math.max(50, this.otlkMagForecast); - } else if (this.otlkMagForecast < 50) { - this.otlkMagForecast += change; - this.otlkMagForecast = Math.min(50, this.otlkMagForecast); - } - } - - /** - * Serialize the Stock to a JSON save state. - */ - toJSON(): any { - return Generic_toJSON("Stock", this); - } - - /** - * Initializes a Stock from a JSON save state - */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): Stock { - return Generic_fromJSON(Stock, value.data); - } + /** + * Initializes a Stock from a JSON save state + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): Stock { + return Generic_fromJSON(Stock, value.data); + } } Reviver.constructors.Stock = Stock; diff --git a/src/StockMarket/StockMarket.tsx b/src/StockMarket/StockMarket.tsx index 3782e4eed..8d6ff4851 100644 --- a/src/StockMarket/StockMarket.tsx +++ b/src/StockMarket/StockMarket.tsx @@ -1,9 +1,4 @@ -import { - buyStock, - sellStock, - shortStock, - sellShort, -} from "./BuyingAndSelling"; +import { buyStock, sellStock, shortStock, sellShort } from "./BuyingAndSelling"; import { IOrderBook } from "./IOrderBook"; import { IStockMarket } from "./IStockMarket"; import { Order } from "./Order"; @@ -34,286 +29,381 @@ import * as ReactDOM from "react-dom"; export let StockMarket: IStockMarket | IMap = {}; // Maps full stock name -> Stock object export const SymbolToStockMap: IMap = {}; // Maps symbol -> Stock object -export function placeOrder(stock: Stock, shares: number, price: number, type: OrderTypes, position: PositionTypes, workerScript: WorkerScript | null=null): boolean { - if (!(stock instanceof Stock)) { - if (workerScript) { - workerScript.log("placeOrder", `Invalid stock: '${stock}'`); - } else { - dialogBoxCreate(`ERROR: Invalid stock passed to placeOrder() function`); - } - return false; +export function placeOrder( + stock: Stock, + shares: number, + price: number, + type: OrderTypes, + position: PositionTypes, + workerScript: WorkerScript | null = null, +): boolean { + if (!(stock instanceof Stock)) { + if (workerScript) { + workerScript.log("placeOrder", `Invalid stock: '${stock}'`); + } else { + dialogBoxCreate(`ERROR: Invalid stock passed to placeOrder() function`); } - if (typeof shares !== "number" || typeof price !== "number") { - if (workerScript) { - workerScript.log("placeOrder", `Invalid arguments: shares='${shares}' price='${price}'`); - } else { - dialogBoxCreate("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument"); - } - return false; + return false; + } + if (typeof shares !== "number" || typeof price !== "number") { + if (workerScript) { + workerScript.log( + "placeOrder", + `Invalid arguments: shares='${shares}' price='${price}'`, + ); + } else { + dialogBoxCreate( + "ERROR: Invalid numeric value provided for either 'shares' or 'price' argument", + ); } + return false; + } - const order = new Order(stock.symbol, shares, price, type, position); - if (StockMarket["Orders"] == null) { - const orders: IOrderBook = {}; - for (const name in StockMarket) { - const stk = StockMarket[name]; - if (!(stk instanceof Stock)) { continue; } - orders[stk.symbol] = []; - } - StockMarket["Orders"] = orders; + const order = new Order(stock.symbol, shares, price, type, position); + if (StockMarket["Orders"] == null) { + const orders: IOrderBook = {}; + for (const name in StockMarket) { + const stk = StockMarket[name]; + if (!(stk instanceof Stock)) { + continue; + } + orders[stk.symbol] = []; } - StockMarket["Orders"][stock.symbol].push(order); + StockMarket["Orders"] = orders; + } + StockMarket["Orders"][stock.symbol].push(order); - // Process to see if it should be executed immediately - const processOrderRefs = { - rerenderFn: displayStockMarketContent, - stockMarket: StockMarket as IStockMarket, - symbolToStockMap: SymbolToStockMap, - } - processOrders(stock, order.type, order.pos, processOrderRefs); - displayStockMarketContent(); + // Process to see if it should be executed immediately + const processOrderRefs = { + rerenderFn: displayStockMarketContent, + stockMarket: StockMarket as IStockMarket, + symbolToStockMap: SymbolToStockMap, + }; + processOrders(stock, order.type, order.pos, processOrderRefs); + displayStockMarketContent(); - return true; + return true; } // Returns true if successfully cancels an order, false otherwise interface ICancelOrderParams { - order?: Order; - pos?: PositionTypes; - price?: number; - shares?: number; - stock?: Stock; - type?: OrderTypes; + order?: Order; + pos?: PositionTypes; + price?: number; + shares?: number; + stock?: Stock; + type?: OrderTypes; } -export function cancelOrder(params: ICancelOrderParams, workerScript: WorkerScript | null=null): boolean { - if (StockMarket["Orders"] == null) {return false;} - if (params.order && params.order instanceof Order) { - const order = params.order; - // An 'Order' object is passed in - const stockOrders = StockMarket["Orders"][order.stockSymbol]; - for (let i = 0; i < stockOrders.length; ++i) { - if (order == stockOrders[i]) { - stockOrders.splice(i, 1); - displayStockMarketContent(); - return true; - } - } - return false; - } else if (params.stock && params.shares && params.price && params.type && - params.pos && params.stock instanceof Stock) { - // Order properties are passed in. Need to look for the order - const stockOrders = StockMarket["Orders"][params.stock.symbol]; - const orderTxt = params.stock.symbol + " - " + params.shares + " @ " + - numeralWrapper.formatMoney(params.price); - for (let i = 0; i < stockOrders.length; ++i) { - const order = stockOrders[i]; - if (params.shares === order.shares && - params.price === order.price && - params.type === order.type && - params.pos === order.pos) { - stockOrders.splice(i, 1); - displayStockMarketContent(); - if (workerScript) { - workerScript.scriptRef.log("Successfully cancelled order: " + orderTxt); - } - return true; - } - } - if (workerScript) { - workerScript.scriptRef.log("Failed to cancel order: " + orderTxt); - } - return false; +export function cancelOrder( + params: ICancelOrderParams, + workerScript: WorkerScript | null = null, +): boolean { + if (StockMarket["Orders"] == null) { + return false; + } + if (params.order && params.order instanceof Order) { + const order = params.order; + // An 'Order' object is passed in + const stockOrders = StockMarket["Orders"][order.stockSymbol]; + for (let i = 0; i < stockOrders.length; ++i) { + if (order == stockOrders[i]) { + stockOrders.splice(i, 1); + displayStockMarketContent(); + return true; + } } return false; + } else if ( + params.stock && + params.shares && + params.price && + params.type && + params.pos && + params.stock instanceof Stock + ) { + // Order properties are passed in. Need to look for the order + const stockOrders = StockMarket["Orders"][params.stock.symbol]; + const orderTxt = + params.stock.symbol + + " - " + + params.shares + + " @ " + + numeralWrapper.formatMoney(params.price); + for (let i = 0; i < stockOrders.length; ++i) { + const order = stockOrders[i]; + if ( + params.shares === order.shares && + params.price === order.price && + params.type === order.type && + params.pos === order.pos + ) { + stockOrders.splice(i, 1); + displayStockMarketContent(); + if (workerScript) { + workerScript.scriptRef.log( + "Successfully cancelled order: " + orderTxt, + ); + } + return true; + } + } + if (workerScript) { + workerScript.scriptRef.log("Failed to cancel order: " + orderTxt); + } + return false; + } + return false; } export function loadStockMarket(saveString: string): void { - if (saveString === "") { - StockMarket = {}; - } else { - StockMarket = JSON.parse(saveString, Reviver); - } + if (saveString === "") { + StockMarket = {}; + } else { + StockMarket = JSON.parse(saveString, Reviver); + } } export function deleteStockMarket(): void { - StockMarket = {}; + StockMarket = {}; } export function initStockMarket(): void { - for (const stk in StockMarket) { - if (StockMarket.hasOwnProperty(stk)) { - delete StockMarket[stk]; - } + for (const stk in StockMarket) { + if (StockMarket.hasOwnProperty(stk)) { + delete StockMarket[stk]; } + } - for (const metadata of InitStockMetadata) { - const name = metadata.name; - StockMarket[name] = new Stock(metadata); + for (const metadata of InitStockMetadata) { + const name = metadata.name; + StockMarket[name] = new Stock(metadata); + } + + const orders: IOrderBook = {}; + for (const name in StockMarket) { + const stock = StockMarket[name]; + if (!(stock instanceof Stock)) { + continue; } + orders[stock.symbol] = []; + } + StockMarket["Orders"] = orders; - const orders: IOrderBook = {}; - for (const name in StockMarket) { - const stock = StockMarket[name]; - if (!(stock instanceof Stock)) { continue; } - orders[stock.symbol] = []; - } - StockMarket["Orders"] = orders; - - StockMarket.storedCycles = 0; - StockMarket.lastUpdate = 0; - StockMarket.ticksUntilCycle = TicksPerCycle; + StockMarket.storedCycles = 0; + StockMarket.lastUpdate = 0; + StockMarket.ticksUntilCycle = TicksPerCycle; } export function initSymbolToStockMap(): void { - for (const name in StockSymbols) { - if (StockSymbols.hasOwnProperty(name)) { - const stock = StockMarket[name]; - if (stock == null) { - console.error(`Could not find Stock for ${name}`); - continue; - } - const symbol = StockSymbols[name]; - SymbolToStockMap[symbol] = stock; - } + for (const name in StockSymbols) { + if (StockSymbols.hasOwnProperty(name)) { + const stock = StockMarket[name]; + if (stock == null) { + console.error(`Could not find Stock for ${name}`); + continue; + } + const symbol = StockSymbols[name]; + SymbolToStockMap[symbol] = stock; } + } } export function stockMarketCycle(): void { - for (const name in StockMarket) { - const stock = StockMarket[name]; - if (!(stock instanceof Stock)) { continue; } - - const roll = Math.random(); - if (roll < 0.45) { - stock.b = !stock.b; - stock.flipForecastForecast(); - } - - StockMarket.ticksUntilCycle = TicksPerCycle; + for (const name in StockMarket) { + const stock = StockMarket[name]; + if (!(stock instanceof Stock)) { + continue; } + + const roll = Math.random(); + if (roll < 0.45) { + stock.b = !stock.b; + stock.flipForecastForecast(); + } + + StockMarket.ticksUntilCycle = TicksPerCycle; + } } // Stock prices updated every 6 seconds const msPerStockUpdate = 6e3; const cyclesPerStockUpdate = msPerStockUpdate / CONSTANTS.MilliPerCycle; -export function processStockPrices(numCycles=1): void { - if (StockMarket.storedCycles == null || isNaN(StockMarket.storedCycles)) { StockMarket.storedCycles = 0; } - StockMarket.storedCycles += numCycles; +export function processStockPrices(numCycles = 1): void { + if (StockMarket.storedCycles == null || isNaN(StockMarket.storedCycles)) { + StockMarket.storedCycles = 0; + } + StockMarket.storedCycles += numCycles; - if (StockMarket.storedCycles < cyclesPerStockUpdate) { return; } + if (StockMarket.storedCycles < cyclesPerStockUpdate) { + return; + } - // We can process the update every 4 seconds as long as there are enough - // stored cycles. This lets us account for offline time - const timeNow = new Date().getTime(); - if (timeNow - StockMarket.lastUpdate < 4e3) { return; } + // We can process the update every 4 seconds as long as there are enough + // stored cycles. This lets us account for offline time + const timeNow = new Date().getTime(); + if (timeNow - StockMarket.lastUpdate < 4e3) { + return; + } - StockMarket.lastUpdate = timeNow; - StockMarket.storedCycles -= cyclesPerStockUpdate; + StockMarket.lastUpdate = timeNow; + StockMarket.storedCycles -= cyclesPerStockUpdate; - // Cycle - if (StockMarket.ticksUntilCycle == null || typeof StockMarket.ticksUntilCycle !== "number") { - StockMarket.ticksUntilCycle = TicksPerCycle; + // Cycle + if ( + StockMarket.ticksUntilCycle == null || + typeof StockMarket.ticksUntilCycle !== "number" + ) { + StockMarket.ticksUntilCycle = TicksPerCycle; + } + --StockMarket.ticksUntilCycle; + if (StockMarket.ticksUntilCycle <= 0) { + stockMarketCycle(); + } + + const v = Math.random(); + for (const name in StockMarket) { + const stock = StockMarket[name]; + if (!(stock instanceof Stock)) { + continue; } - --StockMarket.ticksUntilCycle; - if (StockMarket.ticksUntilCycle <= 0) { - stockMarketCycle(); + let av = (v * stock.mv) / 100; + if (isNaN(av)) { + av = 0.02; } - const v = Math.random(); - for (const name in StockMarket) { - const stock = StockMarket[name]; - if (!(stock instanceof Stock)) { continue; } - let av = (v * stock.mv) / 100; - if (isNaN(av)) { av = .02; } - - let chc = 50; - if (stock.b) { - chc = (chc + stock.otlkMag) / 100; - } else { - chc = (chc - stock.otlkMag) / 100; - } - if (stock.price >= stock.cap) { - chc = 0.1; // "Soft Limit" on stock price. It could still go up but its unlikely - stock.b = false; - } - if (isNaN(chc)) { chc = 0.5; } - - const c = Math.random(); - const processOrderRefs = { - rerenderFn: displayStockMarketContent, - stockMarket: StockMarket as IStockMarket, - symbolToStockMap: SymbolToStockMap, - } - if (c < chc) { - stock.changePrice(stock.price * (1 + av)); - processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short, processOrderRefs); - processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long, processOrderRefs); - processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long, processOrderRefs); - processOrders(stock, OrderTypes.StopSell, PositionTypes.Short, processOrderRefs); - } else { - stock.changePrice(stock.price / (1 + av)); - processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long, processOrderRefs); - processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short, processOrderRefs); - processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short, processOrderRefs); - processOrders(stock, OrderTypes.StopSell, PositionTypes.Long, processOrderRefs); - } - - let otlkMagChange = stock.otlkMag * av; - if (stock.otlkMag < 5) { - if (stock.otlkMag <= 1) { - otlkMagChange = 1; - } else { - otlkMagChange *= 10; - } - } - stock.cycleForecast(otlkMagChange); - stock.cycleForecastForecast(otlkMagChange / 2); - - // Shares required for price movement gradually approaches max over time - stock.shareTxUntilMovement = Math.min(stock.shareTxUntilMovement + 10, stock.shareTxForMovement); + let chc = 50; + if (stock.b) { + chc = (chc + stock.otlkMag) / 100; + } else { + chc = (chc - stock.otlkMag) / 100; + } + if (stock.price >= stock.cap) { + chc = 0.1; // "Soft Limit" on stock price. It could still go up but its unlikely + stock.b = false; + } + if (isNaN(chc)) { + chc = 0.5; } - displayStockMarketContent(); + const c = Math.random(); + const processOrderRefs = { + rerenderFn: displayStockMarketContent, + stockMarket: StockMarket as IStockMarket, + symbolToStockMap: SymbolToStockMap, + }; + if (c < chc) { + stock.changePrice(stock.price * (1 + av)); + processOrders( + stock, + OrderTypes.LimitBuy, + PositionTypes.Short, + processOrderRefs, + ); + processOrders( + stock, + OrderTypes.LimitSell, + PositionTypes.Long, + processOrderRefs, + ); + processOrders( + stock, + OrderTypes.StopBuy, + PositionTypes.Long, + processOrderRefs, + ); + processOrders( + stock, + OrderTypes.StopSell, + PositionTypes.Short, + processOrderRefs, + ); + } else { + stock.changePrice(stock.price / (1 + av)); + processOrders( + stock, + OrderTypes.LimitBuy, + PositionTypes.Long, + processOrderRefs, + ); + processOrders( + stock, + OrderTypes.LimitSell, + PositionTypes.Short, + processOrderRefs, + ); + processOrders( + stock, + OrderTypes.StopBuy, + PositionTypes.Short, + processOrderRefs, + ); + processOrders( + stock, + OrderTypes.StopSell, + PositionTypes.Long, + processOrderRefs, + ); + } + + let otlkMagChange = stock.otlkMag * av; + if (stock.otlkMag < 5) { + if (stock.otlkMag <= 1) { + otlkMagChange = 1; + } else { + otlkMagChange *= 10; + } + } + stock.cycleForecast(otlkMagChange); + stock.cycleForecastForecast(otlkMagChange / 2); + + // Shares required for price movement gradually approaches max over time + stock.shareTxUntilMovement = Math.min( + stock.shareTxUntilMovement + 10, + stock.shareTxForMovement, + ); + } + + displayStockMarketContent(); } let stockMarketContainer: HTMLElement | null = null; function setStockMarketContainer(): void { - stockMarketContainer = document.getElementById("stock-market-container"); - document.removeEventListener("DOMContentLoaded", setStockMarketContainer); + stockMarketContainer = document.getElementById("stock-market-container"); + document.removeEventListener("DOMContentLoaded", setStockMarketContainer); } document.addEventListener("DOMContentLoaded", setStockMarketContainer); function initStockMarketFnForReact(): void { - initStockMarket(); - initSymbolToStockMap(); + initStockMarket(); + initSymbolToStockMap(); } const eventEmitterForUiReset = new EventEmitter(); export function displayStockMarketContent(): void { - if (!routing.isOn(Page.StockMarket)) { - return; - } + if (!routing.isOn(Page.StockMarket)) { + return; + } - eventEmitterForUiReset.emitEvent(); + eventEmitterForUiReset.emitEvent(); - if (stockMarketContainer instanceof HTMLElement) { - const castedStockMarket = StockMarket as IStockMarket; - ReactDOM.render( - , - stockMarketContainer, - ) - } + if (stockMarketContainer instanceof HTMLElement) { + const castedStockMarket = StockMarket as IStockMarket; + ReactDOM.render( + , + stockMarketContainer, + ); + } } diff --git a/src/StockMarket/StockMarketCosts.ts b/src/StockMarket/StockMarketCosts.ts index 699cd2088..a9d54a323 100644 --- a/src/StockMarket/StockMarketCosts.ts +++ b/src/StockMarket/StockMarketCosts.ts @@ -2,17 +2,22 @@ import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { CONSTANTS } from "../Constants"; export function getStockMarketAccountCost(): number { - return CONSTANTS.WSEAccountCost; + return CONSTANTS.WSEAccountCost; } export function getStockMarketTixApiCost(): number { - return CONSTANTS.TIXAPICost; + return CONSTANTS.TIXAPICost; } export function getStockMarket4SDataCost(): number { - return CONSTANTS.MarketData4SCost * BitNodeMultipliers.FourSigmaMarketDataCost; + return ( + CONSTANTS.MarketData4SCost * BitNodeMultipliers.FourSigmaMarketDataCost + ); } export function getStockMarket4STixApiCost(): number { - return CONSTANTS.MarketDataTixApi4SCost * BitNodeMultipliers.FourSigmaMarketDataApiCost; + return ( + CONSTANTS.MarketDataTixApi4SCost * + BitNodeMultipliers.FourSigmaMarketDataApiCost + ); } diff --git a/src/StockMarket/StockMarketHelpers.ts b/src/StockMarket/StockMarketHelpers.ts index 20675398f..5650b9fb4 100644 --- a/src/StockMarket/StockMarketHelpers.ts +++ b/src/StockMarket/StockMarketHelpers.ts @@ -15,21 +15,27 @@ export const forecastChangePerPriceMovement = 0.006; * @param {PositionTypes} posType - Long or short position * @returns {number | null} Total transaction cost. Returns null for an invalid transaction */ -export function getBuyTransactionCost(stock: Stock, shares: number, posType: PositionTypes): number | null { - if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return null; } +export function getBuyTransactionCost( + stock: Stock, + shares: number, + posType: PositionTypes, +): number | null { + if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { + return null; + } - // Cap the 'shares' arg at the stock's maximum shares. This'll prevent - // hanging in the case when a really big number is passed in - shares = Math.min(shares, stock.maxShares); + // Cap the 'shares' arg at the stock's maximum shares. This'll prevent + // hanging in the case when a really big number is passed in + shares = Math.min(shares, stock.maxShares); - const isLong = (posType === PositionTypes.Long); + const isLong = posType === PositionTypes.Long; - // If the number of shares doesn't trigger a price movement, its a simple calculation - if (isLong) { - return (shares * stock.getAskPrice()) + CONSTANTS.StockMarketCommission; - } else { - return (shares * stock.getBidPrice()) + CONSTANTS.StockMarketCommission; - } + // If the number of shares doesn't trigger a price movement, its a simple calculation + if (isLong) { + return shares * stock.getAskPrice() + CONSTANTS.StockMarketCommission; + } else { + return shares * stock.getBidPrice() + CONSTANTS.StockMarketCommission; + } } /** @@ -40,23 +46,31 @@ export function getBuyTransactionCost(stock: Stock, shares: number, posType: Pos * @param {PositionTypes} posType - Long or short position * @returns {number | null} Amount of money gained from transaction. Returns null for an invalid transaction */ -export function getSellTransactionGain(stock: Stock, shares: number, posType: PositionTypes): number | null { - if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return null; } +export function getSellTransactionGain( + stock: Stock, + shares: number, + posType: PositionTypes, +): number | null { + if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { + return null; + } - // Cap the 'shares' arg at the stock's maximum shares. This'll prevent - // hanging in the case when a really big number is passed in - shares = Math.min(shares, stock.maxShares); + // Cap the 'shares' arg at the stock's maximum shares. This'll prevent + // hanging in the case when a really big number is passed in + shares = Math.min(shares, stock.maxShares); - const isLong = (posType === PositionTypes.Long); - if (isLong) { - return (shares * stock.getBidPrice()) - CONSTANTS.StockMarketCommission; - } else { - // Calculating gains for a short position requires calculating the profit made - const origCost = shares * stock.playerAvgShortPx; - const profit = ((stock.playerAvgShortPx - stock.getAskPrice()) * shares) - CONSTANTS.StockMarketCommission; + const isLong = posType === PositionTypes.Long; + if (isLong) { + return shares * stock.getBidPrice() - CONSTANTS.StockMarketCommission; + } else { + // Calculating gains for a short position requires calculating the profit made + const origCost = shares * stock.playerAvgShortPx; + const profit = + (stock.playerAvgShortPx - stock.getAskPrice()) * shares - + CONSTANTS.StockMarketCommission; - return origCost + profit; - } + return origCost + profit; + } } /** @@ -66,42 +80,54 @@ export function getSellTransactionGain(stock: Stock, shares: number, posType: Po * @param {number} shares - Number of sharse being transacted * @param {PositionTypes} posType - Long or short position */ -export function processTransactionForecastMovement(stock: Stock, shares: number): void { - if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { return; } +export function processTransactionForecastMovement( + stock: Stock, + shares: number, +): void { + if (isNaN(shares) || shares <= 0 || !(stock instanceof Stock)) { + return; + } - // Cap the 'shares' arg at the stock's maximum shares. This'll prevent - // hanging in the case when a really big number is passed in - shares = Math.min(shares, stock.maxShares); + // Cap the 'shares' arg at the stock's maximum shares. This'll prevent + // hanging in the case when a really big number is passed in + shares = Math.min(shares, stock.maxShares); - // If there's only going to be one iteration at most - const firstShares = stock.shareTxUntilMovement; - if (shares <= firstShares) { - stock.shareTxUntilMovement -= shares; - if (stock.shareTxUntilMovement <= 0) { - stock.shareTxUntilMovement = stock.shareTxForMovement; - stock.influenceForecast(forecastChangePerPriceMovement); - stock.influenceForecastForecast(forecastChangePerPriceMovement * (stock.mv / 100)); - } - - return; + // If there's only going to be one iteration at most + const firstShares = stock.shareTxUntilMovement; + if (shares <= firstShares) { + stock.shareTxUntilMovement -= shares; + if (stock.shareTxUntilMovement <= 0) { + stock.shareTxUntilMovement = stock.shareTxForMovement; + stock.influenceForecast(forecastChangePerPriceMovement); + stock.influenceForecastForecast( + forecastChangePerPriceMovement * (stock.mv / 100), + ); } - // Calculate how many iterations of price changes we need to account for - const remainingShares = shares - firstShares; - let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement); + return; + } - // If on the offchance we end up perfectly at the next price movement - stock.shareTxUntilMovement = stock.shareTxForMovement - ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement); - if (stock.shareTxUntilMovement === stock.shareTxForMovement || stock.shareTxUntilMovement <= 0) { - ++numIterations; - stock.shareTxUntilMovement = stock.shareTxForMovement; - } + // Calculate how many iterations of price changes we need to account for + const remainingShares = shares - firstShares; + let numIterations = 1 + Math.ceil(remainingShares / stock.shareTxForMovement); - // Forecast always decreases in magnitude - const forecastChange = forecastChangePerPriceMovement * (numIterations - 1); - const forecastForecastChange = forecastChange * (stock.mv / 100); - stock.influenceForecast(forecastChange); - stock.influenceForecastForecast(forecastForecastChange); + // If on the offchance we end up perfectly at the next price movement + stock.shareTxUntilMovement = + stock.shareTxForMovement - + ((shares - stock.shareTxUntilMovement) % stock.shareTxForMovement); + if ( + stock.shareTxUntilMovement === stock.shareTxForMovement || + stock.shareTxUntilMovement <= 0 + ) { + ++numIterations; + stock.shareTxUntilMovement = stock.shareTxForMovement; + } + + // Forecast always decreases in magnitude + const forecastChange = forecastChangePerPriceMovement * (numIterations - 1); + const forecastForecastChange = forecastChange * (stock.mv / 100); + stock.influenceForecast(forecastChange); + stock.influenceForecastForecast(forecastForecastChange); } /** @@ -113,13 +139,19 @@ export function processTransactionForecastMovement(stock: Stock, shares: number) * @param {number} money - Amount of money player has * @returns maximum number of shares that the player can purchase */ -export function calculateBuyMaxAmount(stock: Stock, posType: PositionTypes, money: number): number { - if (!(stock instanceof Stock)) { return 0; } +export function calculateBuyMaxAmount( + stock: Stock, + posType: PositionTypes, + money: number, +): number { + if (!(stock instanceof Stock)) { + return 0; + } - const isLong = (posType === PositionTypes.Long); + const isLong = posType === PositionTypes.Long; - const remainingMoney = money - CONSTANTS.StockMarketCommission; - const currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice(); + const remainingMoney = money - CONSTANTS.StockMarketCommission; + const currPrice = isLong ? stock.getAskPrice() : stock.getBidPrice(); - return Math.floor(remainingMoney / currPrice); + return Math.floor(remainingMoney / currPrice); } diff --git a/src/StockMarket/data/InitStockMetadata.ts b/src/StockMarket/data/InitStockMetadata.ts index 44bad8a9a..8bcef396f 100644 --- a/src/StockMarket/data/InitStockMetadata.ts +++ b/src/StockMarket/data/InitStockMetadata.ts @@ -13,861 +13,861 @@ import { IConstructorParams } from "../Stock"; import { LocationName } from "../../Locations/data/LocationNames"; export const InitStockMetadata: IConstructorParams[] = [ - { - b: true, - initPrice: { - max: 28e3, - min: 17e3, - }, - marketCap: 2.4e12, - mv: { - divisor: 100, - max: 50, - min: 40, - }, - name: LocationName.AevumECorp, - otlkMag: 19, - spreadPerc: { - divisor: 10, - max: 5, - min: 1, - }, - shareTxForMovement: { - max: 90e3, - min: 30e3, - }, - symbol: StockSymbols[LocationName.AevumECorp], + { + b: true, + initPrice: { + max: 28e3, + min: 17e3, }, + marketCap: 2.4e12, + mv: { + divisor: 100, + max: 50, + min: 40, + }, + name: LocationName.AevumECorp, + otlkMag: 19, + spreadPerc: { + divisor: 10, + max: 5, + min: 1, + }, + shareTxForMovement: { + max: 90e3, + min: 30e3, + }, + symbol: StockSymbols[LocationName.AevumECorp], + }, - { - b: true, - initPrice: { - max: 34e3, - min: 24e3, - }, - marketCap: 2.4e12, - mv: { - divisor: 100, - max: 50, - min: 40, - }, - name: LocationName.Sector12MegaCorp, - otlkMag: 19, - spreadPerc: { - divisor: 10, - max: 5, - min: 1, - }, - shareTxForMovement: { - max: 90e3, - min: 30e3, - }, - symbol: StockSymbols[LocationName.Sector12MegaCorp], + { + b: true, + initPrice: { + max: 34e3, + min: 24e3, }, + marketCap: 2.4e12, + mv: { + divisor: 100, + max: 50, + min: 40, + }, + name: LocationName.Sector12MegaCorp, + otlkMag: 19, + spreadPerc: { + divisor: 10, + max: 5, + min: 1, + }, + shareTxForMovement: { + max: 90e3, + min: 30e3, + }, + symbol: StockSymbols[LocationName.Sector12MegaCorp], + }, - { - b: true, - initPrice: { - max: 25e3, - min: 12e3, - }, - marketCap: 1.6e12, - mv: { - divisor: 100, - max: 80, - min: 70, - }, - name: LocationName.Sector12BladeIndustries, - otlkMag: 13, - spreadPerc: { - divisor: 10, - max: 6, - min: 1, - }, - shareTxForMovement: { - max: 90e3, - min: 30e3, - }, - symbol: StockSymbols[LocationName.Sector12BladeIndustries], + { + b: true, + initPrice: { + max: 25e3, + min: 12e3, }, + marketCap: 1.6e12, + mv: { + divisor: 100, + max: 80, + min: 70, + }, + name: LocationName.Sector12BladeIndustries, + otlkMag: 13, + spreadPerc: { + divisor: 10, + max: 6, + min: 1, + }, + shareTxForMovement: { + max: 90e3, + min: 30e3, + }, + symbol: StockSymbols[LocationName.Sector12BladeIndustries], + }, - { - b: true, - initPrice: { - max: 25e3, - min: 10e3, - }, - marketCap: 1.5e12, - mv: { - divisor: 100, - max: 75, - min: 65, - }, - name: LocationName.AevumClarkeIncorporated, - otlkMag: 12, - spreadPerc: { - divisor: 10, - max: 5, - min: 1, - }, - shareTxForMovement: { - max: 90e3, - min: 30e3, - }, - symbol: StockSymbols[LocationName.AevumClarkeIncorporated], + { + b: true, + initPrice: { + max: 25e3, + min: 10e3, }, + marketCap: 1.5e12, + mv: { + divisor: 100, + max: 75, + min: 65, + }, + name: LocationName.AevumClarkeIncorporated, + otlkMag: 12, + spreadPerc: { + divisor: 10, + max: 5, + min: 1, + }, + shareTxForMovement: { + max: 90e3, + min: 30e3, + }, + symbol: StockSymbols[LocationName.AevumClarkeIncorporated], + }, - { - b: true, - initPrice: { - max: 43e3, - min: 32e3, - }, - marketCap: 1.8e12, - mv: { - divisor: 100, - max: 70, - min: 60, - }, - name: LocationName.VolhavenOmniTekIncorporated, - otlkMag: 12, - spreadPerc: { - divisor: 10, - max: 6, - min: 1, - }, - shareTxForMovement: { - max: 90e3, - min: 30e3, - }, - symbol: StockSymbols[LocationName.VolhavenOmniTekIncorporated], + { + b: true, + initPrice: { + max: 43e3, + min: 32e3, }, + marketCap: 1.8e12, + mv: { + divisor: 100, + max: 70, + min: 60, + }, + name: LocationName.VolhavenOmniTekIncorporated, + otlkMag: 12, + spreadPerc: { + divisor: 10, + max: 6, + min: 1, + }, + shareTxForMovement: { + max: 90e3, + min: 30e3, + }, + symbol: StockSymbols[LocationName.VolhavenOmniTekIncorporated], + }, - { - b: true, - initPrice: { - max: 80e3, - min: 50e3, - }, - marketCap: 2e12, - mv: { - divisor: 100, - max: 110, - min: 100, - }, - name: LocationName.Sector12FourSigma, - otlkMag: 17, - spreadPerc: { - divisor: 10, - max: 10, - min: 1, - }, - shareTxForMovement: { - max: 90e3, - min: 30e3, - }, - symbol: StockSymbols[LocationName.Sector12FourSigma], + { + b: true, + initPrice: { + max: 80e3, + min: 50e3, }, + marketCap: 2e12, + mv: { + divisor: 100, + max: 110, + min: 100, + }, + name: LocationName.Sector12FourSigma, + otlkMag: 17, + spreadPerc: { + divisor: 10, + max: 10, + min: 1, + }, + shareTxForMovement: { + max: 90e3, + min: 30e3, + }, + symbol: StockSymbols[LocationName.Sector12FourSigma], + }, - { - b: true, - initPrice: { - max: 28e3, - min: 16e3, - }, - marketCap: 1.9e12, - mv: { - divisor: 100, - max: 85, - min: 75, - }, - name: LocationName.ChongqingKuaiGongInternational, - otlkMag: 10, - spreadPerc: { - divisor: 10, - max: 7, - min: 1, - }, - shareTxForMovement: { - max: 90e3, - min: 30e3, - }, - symbol: StockSymbols[LocationName.ChongqingKuaiGongInternational], + { + b: true, + initPrice: { + max: 28e3, + min: 16e3, }, + marketCap: 1.9e12, + mv: { + divisor: 100, + max: 85, + min: 75, + }, + name: LocationName.ChongqingKuaiGongInternational, + otlkMag: 10, + spreadPerc: { + divisor: 10, + max: 7, + min: 1, + }, + shareTxForMovement: { + max: 90e3, + min: 30e3, + }, + symbol: StockSymbols[LocationName.ChongqingKuaiGongInternational], + }, - { - b: true, - initPrice: { - max: 36e3, - min: 29e3, - }, - marketCap: 2e12, - mv: { - divisor: 100, - max: 130, - min: 120, - }, - name: LocationName.AevumFulcrumTechnologies, - otlkMag: 16, - spreadPerc: { - divisor: 10, - max: 10, - min: 1, - }, - shareTxForMovement: { - max: 90e3, - min: 30e3, - }, - symbol: StockSymbols[LocationName.AevumFulcrumTechnologies], + { + b: true, + initPrice: { + max: 36e3, + min: 29e3, }, + marketCap: 2e12, + mv: { + divisor: 100, + max: 130, + min: 120, + }, + name: LocationName.AevumFulcrumTechnologies, + otlkMag: 16, + spreadPerc: { + divisor: 10, + max: 10, + min: 1, + }, + shareTxForMovement: { + max: 90e3, + min: 30e3, + }, + symbol: StockSymbols[LocationName.AevumFulcrumTechnologies], + }, - { - b: true, - initPrice: { - max: 25e3, - min: 20e3, - }, - marketCap: 1.2e12, - mv: { - divisor: 100, - max: 90, - min: 80, - }, - name: LocationName.IshimaStormTechnologies, - otlkMag: 7, - spreadPerc: { - divisor: 10, - max: 10, - min: 2, - }, - shareTxForMovement: { - max: 108e3, - min: 36e3, - }, - symbol: StockSymbols[LocationName.IshimaStormTechnologies], + { + b: true, + initPrice: { + max: 25e3, + min: 20e3, }, + marketCap: 1.2e12, + mv: { + divisor: 100, + max: 90, + min: 80, + }, + name: LocationName.IshimaStormTechnologies, + otlkMag: 7, + spreadPerc: { + divisor: 10, + max: 10, + min: 2, + }, + shareTxForMovement: { + max: 108e3, + min: 36e3, + }, + symbol: StockSymbols[LocationName.IshimaStormTechnologies], + }, - { - b: true, - initPrice: { - max: 19e3, - min: 6e3, - }, - marketCap: 900e9, - mv: { - divisor: 100, - max: 70, - min: 60, - }, - name: LocationName.NewTokyoDefComm, - otlkMag: 10, - spreadPerc: { - divisor: 10, - max: 10, - min: 2, - }, - shareTxForMovement: { - max: 108e3, - min: 36e3, - }, - symbol: StockSymbols[LocationName.NewTokyoDefComm], + { + b: true, + initPrice: { + max: 19e3, + min: 6e3, }, + marketCap: 900e9, + mv: { + divisor: 100, + max: 70, + min: 60, + }, + name: LocationName.NewTokyoDefComm, + otlkMag: 10, + spreadPerc: { + divisor: 10, + max: 10, + min: 2, + }, + shareTxForMovement: { + max: 108e3, + min: 36e3, + }, + symbol: StockSymbols[LocationName.NewTokyoDefComm], + }, - { - b: true, - initPrice: { - max: 18e3, - min: 10e3, - }, - marketCap: 825e9, - mv: { - divisor: 100, - max: 65, - min: 55, - }, - name: LocationName.VolhavenHeliosLabs, - otlkMag: 9, - spreadPerc: { - divisor: 10, - max: 10, - min: 2, - }, - shareTxForMovement: { - max: 108e3, - min: 36e3, - }, - symbol: StockSymbols[LocationName.VolhavenHeliosLabs], + { + b: true, + initPrice: { + max: 18e3, + min: 10e3, }, + marketCap: 825e9, + mv: { + divisor: 100, + max: 65, + min: 55, + }, + name: LocationName.VolhavenHeliosLabs, + otlkMag: 9, + spreadPerc: { + divisor: 10, + max: 10, + min: 2, + }, + shareTxForMovement: { + max: 108e3, + min: 36e3, + }, + symbol: StockSymbols[LocationName.VolhavenHeliosLabs], + }, - { - b: true, - initPrice: { - max: 14e3, - min: 8e3, - }, - marketCap: 1e12, - mv: { - divisor: 100, - max: 80, - min: 70, - }, - name: LocationName.NewTokyoVitaLife, - otlkMag: 7, - spreadPerc: { - divisor: 10, - max: 10, - min: 2, - }, - shareTxForMovement: { - max: 108e3, - min: 36e3, - }, - symbol: StockSymbols[LocationName.NewTokyoVitaLife], + { + b: true, + initPrice: { + max: 14e3, + min: 8e3, }, + marketCap: 1e12, + mv: { + divisor: 100, + max: 80, + min: 70, + }, + name: LocationName.NewTokyoVitaLife, + otlkMag: 7, + spreadPerc: { + divisor: 10, + max: 10, + min: 2, + }, + shareTxForMovement: { + max: 108e3, + min: 36e3, + }, + symbol: StockSymbols[LocationName.NewTokyoVitaLife], + }, - { - b: true, - initPrice: { - max: 24e3, - min: 12e3, - }, - marketCap: 800e9, - mv: { - divisor: 100, - max: 70, - min: 60, - }, - name: LocationName.Sector12IcarusMicrosystems, - otlkMag: 7.5, - spreadPerc: { - divisor: 10, - max: 10, - min: 3, - }, - shareTxForMovement: { - max: 108e3, - min: 36e3, - }, - symbol: StockSymbols[LocationName.Sector12IcarusMicrosystems], + { + b: true, + initPrice: { + max: 24e3, + min: 12e3, }, + marketCap: 800e9, + mv: { + divisor: 100, + max: 70, + min: 60, + }, + name: LocationName.Sector12IcarusMicrosystems, + otlkMag: 7.5, + spreadPerc: { + divisor: 10, + max: 10, + min: 3, + }, + shareTxForMovement: { + max: 108e3, + min: 36e3, + }, + symbol: StockSymbols[LocationName.Sector12IcarusMicrosystems], + }, - { - b: true, - initPrice: { - max: 29e3, - min: 16e3, - }, - marketCap: 900e9, - mv: { - divisor: 100, - max: 60, - min: 50, - }, - name: LocationName.Sector12UniversalEnergy, - otlkMag: 10, - spreadPerc: { - divisor: 10, - max: 10, - min: 2, - }, - shareTxForMovement: { - max: 108e3, - min: 36e3, - }, - symbol: StockSymbols[LocationName.Sector12UniversalEnergy], + { + b: true, + initPrice: { + max: 29e3, + min: 16e3, }, + marketCap: 900e9, + mv: { + divisor: 100, + max: 60, + min: 50, + }, + name: LocationName.Sector12UniversalEnergy, + otlkMag: 10, + spreadPerc: { + divisor: 10, + max: 10, + min: 2, + }, + shareTxForMovement: { + max: 108e3, + min: 36e3, + }, + symbol: StockSymbols[LocationName.Sector12UniversalEnergy], + }, - { - b: true, - initPrice: { - max: 17e3, - min: 8e3, - }, - marketCap: 640e9, - mv: { - divisor: 100, - max: 65, - min: 55, - }, - name: LocationName.AevumAeroCorp, - otlkMag: 6, - spreadPerc: { - divisor: 10, - max: 10, - min: 3, - }, - shareTxForMovement: { - max: 126e3, - min: 42e3, - }, - symbol: StockSymbols[LocationName.AevumAeroCorp], + { + b: true, + initPrice: { + max: 17e3, + min: 8e3, }, + marketCap: 640e9, + mv: { + divisor: 100, + max: 65, + min: 55, + }, + name: LocationName.AevumAeroCorp, + otlkMag: 6, + spreadPerc: { + divisor: 10, + max: 10, + min: 3, + }, + shareTxForMovement: { + max: 126e3, + min: 42e3, + }, + symbol: StockSymbols[LocationName.AevumAeroCorp], + }, - { - b: true, - initPrice: { - max: 15e3, - min: 6e3, - }, - marketCap: 600e9, - mv: { - divisor: 100, - max: 75, - min: 65, - }, - name: LocationName.VolhavenOmniaCybersystems, - otlkMag: 4.5, - spreadPerc: { - divisor: 10, - max: 11, - min: 4, - }, - shareTxForMovement: { - max: 126e3, - min: 42e3, - }, - symbol: StockSymbols[LocationName.VolhavenOmniaCybersystems], + { + b: true, + initPrice: { + max: 15e3, + min: 6e3, }, + marketCap: 600e9, + mv: { + divisor: 100, + max: 75, + min: 65, + }, + name: LocationName.VolhavenOmniaCybersystems, + otlkMag: 4.5, + spreadPerc: { + divisor: 10, + max: 11, + min: 4, + }, + shareTxForMovement: { + max: 126e3, + min: 42e3, + }, + symbol: StockSymbols[LocationName.VolhavenOmniaCybersystems], + }, - { - b: true, - initPrice: { - max: 28e3, - min: 14e3, - }, - marketCap: 705e9, - mv: { - divisor: 100, - max: 80, - min: 70, - }, - name: LocationName.ChongqingSolarisSpaceSystems, - otlkMag: 8.5, - spreadPerc: { - divisor: 10, - max: 12, - min: 4, - }, - shareTxForMovement: { - max: 126e3, - min: 42e3, - }, - symbol: StockSymbols[LocationName.ChongqingSolarisSpaceSystems], + { + b: true, + initPrice: { + max: 28e3, + min: 14e3, }, + marketCap: 705e9, + mv: { + divisor: 100, + max: 80, + min: 70, + }, + name: LocationName.ChongqingSolarisSpaceSystems, + otlkMag: 8.5, + spreadPerc: { + divisor: 10, + max: 12, + min: 4, + }, + shareTxForMovement: { + max: 126e3, + min: 42e3, + }, + symbol: StockSymbols[LocationName.ChongqingSolarisSpaceSystems], + }, - { - b: true, - initPrice: { - max: 30e3, - min: 12e3, - }, - marketCap: 695e9, - mv: { - divisor: 100, - max: 65, - min: 55, - }, - name: LocationName.NewTokyoGlobalPharmaceuticals, - otlkMag: 10.5, - spreadPerc: { - divisor: 10, - max: 10, - min: 4, - }, - shareTxForMovement: { - max: 126e3, - min: 42e3, - }, - symbol: StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals], + { + b: true, + initPrice: { + max: 30e3, + min: 12e3, }, + marketCap: 695e9, + mv: { + divisor: 100, + max: 65, + min: 55, + }, + name: LocationName.NewTokyoGlobalPharmaceuticals, + otlkMag: 10.5, + spreadPerc: { + divisor: 10, + max: 10, + min: 4, + }, + shareTxForMovement: { + max: 126e3, + min: 42e3, + }, + symbol: StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals], + }, - { - b: true, - initPrice: { - max: 27e3, - min: 15e3, - }, - marketCap: 600e9, - mv: { - divisor: 100, - max: 80, - min: 70, - }, - name: LocationName.IshimaNovaMedical, - otlkMag: 5, - spreadPerc: { - divisor: 10, - max: 11, - min: 4, - }, - shareTxForMovement: { - max: 126e3, - min: 42e3, - }, - symbol: StockSymbols[LocationName.IshimaNovaMedical], + { + b: true, + initPrice: { + max: 27e3, + min: 15e3, }, + marketCap: 600e9, + mv: { + divisor: 100, + max: 80, + min: 70, + }, + name: LocationName.IshimaNovaMedical, + otlkMag: 5, + spreadPerc: { + divisor: 10, + max: 11, + min: 4, + }, + shareTxForMovement: { + max: 126e3, + min: 42e3, + }, + symbol: StockSymbols[LocationName.IshimaNovaMedical], + }, - { - b: true, - initPrice: { - max: 8.5e3, - min: 4e3, - }, - marketCap: 450e9, - mv: { - divisor: 100, - max: 260, - min: 240, - }, - name: LocationName.AevumWatchdogSecurity, - otlkMag: 1.5, - spreadPerc: { - divisor: 10, - max: 12, - min: 5, - }, - shareTxForMovement: { - max: 54e3, - min: 12e3, - }, - symbol: StockSymbols[LocationName.AevumWatchdogSecurity], + { + b: true, + initPrice: { + max: 8.5e3, + min: 4e3, }, + marketCap: 450e9, + mv: { + divisor: 100, + max: 260, + min: 240, + }, + name: LocationName.AevumWatchdogSecurity, + otlkMag: 1.5, + spreadPerc: { + divisor: 10, + max: 12, + min: 5, + }, + shareTxForMovement: { + max: 54e3, + min: 12e3, + }, + symbol: StockSymbols[LocationName.AevumWatchdogSecurity], + }, - { - b: true, - initPrice: { - max: 8e3, - min: 4.5e3, - }, - marketCap: 300e9, - mv: { - divisor: 100, - max: 135, - min: 115, - }, - name: LocationName.VolhavenLexoCorp, - otlkMag: 6, - spreadPerc: { - divisor: 10, - max: 12, - min: 5, - }, - shareTxForMovement: { - max: 108e3, - min: 36e3, - }, - symbol: StockSymbols[LocationName.VolhavenLexoCorp], + { + b: true, + initPrice: { + max: 8e3, + min: 4.5e3, }, + marketCap: 300e9, + mv: { + divisor: 100, + max: 135, + min: 115, + }, + name: LocationName.VolhavenLexoCorp, + otlkMag: 6, + spreadPerc: { + divisor: 10, + max: 12, + min: 5, + }, + shareTxForMovement: { + max: 108e3, + min: 36e3, + }, + symbol: StockSymbols[LocationName.VolhavenLexoCorp], + }, - { - b: true, - initPrice: { - max: 7e3, - min: 2e3, - }, - marketCap: 180e9, - mv: { - divisor: 100, - max: 70, - min: 50, - }, - name: LocationName.AevumRhoConstruction, - otlkMag: 1, - spreadPerc: { - divisor: 10, - max: 10, - min: 3, - }, - shareTxForMovement: { - max: 126e3, - min: 60e3, - }, - symbol: StockSymbols[LocationName.AevumRhoConstruction], + { + b: true, + initPrice: { + max: 7e3, + min: 2e3, }, + marketCap: 180e9, + mv: { + divisor: 100, + max: 70, + min: 50, + }, + name: LocationName.AevumRhoConstruction, + otlkMag: 1, + spreadPerc: { + divisor: 10, + max: 10, + min: 3, + }, + shareTxForMovement: { + max: 126e3, + min: 60e3, + }, + symbol: StockSymbols[LocationName.AevumRhoConstruction], + }, - { - b: true, - initPrice: { - max: 8.5e3, - min: 4e3, - }, - marketCap: 240e9, - mv: { - divisor: 100, - max: 205, - min: 175, - }, - name: LocationName.Sector12AlphaEnterprises, - otlkMag: 10, - spreadPerc: { - divisor: 10, - max: 16, - min: 5, - }, - shareTxForMovement: { - max: 90e3, - min: 30e3, - }, - symbol: StockSymbols[LocationName.Sector12AlphaEnterprises], + { + b: true, + initPrice: { + max: 8.5e3, + min: 4e3, }, + marketCap: 240e9, + mv: { + divisor: 100, + max: 205, + min: 175, + }, + name: LocationName.Sector12AlphaEnterprises, + otlkMag: 10, + spreadPerc: { + divisor: 10, + max: 16, + min: 5, + }, + shareTxForMovement: { + max: 90e3, + min: 30e3, + }, + symbol: StockSymbols[LocationName.Sector12AlphaEnterprises], + }, - { - b: true, - initPrice: { - max: 8e3, - min: 3e3, - }, - marketCap: 200e9, - mv: { - divisor: 100, - max: 170, - min: 150, - }, - name: LocationName.VolhavenSysCoreSecurities, - otlkMag: 3, - spreadPerc: { - divisor: 10, - max: 12, - min: 5, - }, - shareTxForMovement: { - max: 90e3, - min: 15e3, - }, - symbol: StockSymbols[LocationName.VolhavenSysCoreSecurities], + { + b: true, + initPrice: { + max: 8e3, + min: 3e3, }, + marketCap: 200e9, + mv: { + divisor: 100, + max: 170, + min: 150, + }, + name: LocationName.VolhavenSysCoreSecurities, + otlkMag: 3, + spreadPerc: { + divisor: 10, + max: 12, + min: 5, + }, + shareTxForMovement: { + max: 90e3, + min: 15e3, + }, + symbol: StockSymbols[LocationName.VolhavenSysCoreSecurities], + }, - { - b: true, - initPrice: { - max: 6e3, - min: 1e3, - }, - marketCap: 185e9, - mv: { - divisor: 100, - max: 100, - min: 80, - }, - name: LocationName.VolhavenCompuTek, - otlkMag: 4, - spreadPerc: { - divisor: 10, - max: 12, - min: 4, - }, - shareTxForMovement: { - max: 126e3, - min: 60e3, - }, - symbol: StockSymbols[LocationName.VolhavenCompuTek], + { + b: true, + initPrice: { + max: 6e3, + min: 1e3, }, + marketCap: 185e9, + mv: { + divisor: 100, + max: 100, + min: 80, + }, + name: LocationName.VolhavenCompuTek, + otlkMag: 4, + spreadPerc: { + divisor: 10, + max: 12, + min: 4, + }, + shareTxForMovement: { + max: 126e3, + min: 60e3, + }, + symbol: StockSymbols[LocationName.VolhavenCompuTek], + }, - { - b: true, - initPrice: { - max: 5e3, - min: 1e3, - }, - marketCap: 58e9, - mv: { - divisor: 100, - max: 400, - min: 200, - }, - name: LocationName.AevumNetLinkTechnologies, - otlkMag: 1, - spreadPerc: { - divisor: 10, - max: 20, - min: 5, - }, - shareTxForMovement: { - max: 54e3, - min: 18e3, - }, - symbol: StockSymbols[LocationName.AevumNetLinkTechnologies], + { + b: true, + initPrice: { + max: 5e3, + min: 1e3, }, + marketCap: 58e9, + mv: { + divisor: 100, + max: 400, + min: 200, + }, + name: LocationName.AevumNetLinkTechnologies, + otlkMag: 1, + spreadPerc: { + divisor: 10, + max: 20, + min: 5, + }, + shareTxForMovement: { + max: 54e3, + min: 18e3, + }, + symbol: StockSymbols[LocationName.AevumNetLinkTechnologies], + }, - { - b: true, - initPrice: { - max: 8e3, - min: 1e3, - }, - marketCap: 60e9, - mv: { - divisor: 100, - max: 110, - min: 90, - }, - name: LocationName.IshimaOmegaSoftware, - otlkMag: 0.5, - spreadPerc: { - divisor: 10, - max: 13, - min: 4, - }, - shareTxForMovement: { - max: 90e3, - min: 30e3, - }, - symbol: StockSymbols[LocationName.IshimaOmegaSoftware], + { + b: true, + initPrice: { + max: 8e3, + min: 1e3, }, + marketCap: 60e9, + mv: { + divisor: 100, + max: 110, + min: 90, + }, + name: LocationName.IshimaOmegaSoftware, + otlkMag: 0.5, + spreadPerc: { + divisor: 10, + max: 13, + min: 4, + }, + shareTxForMovement: { + max: 90e3, + min: 30e3, + }, + symbol: StockSymbols[LocationName.IshimaOmegaSoftware], + }, - { - b: false, - initPrice: { - max: 4.5e3, - min: 500, - }, - marketCap: 45e9, - mv: { - divisor: 100, - max: 80, - min: 70, - }, - name: LocationName.Sector12FoodNStuff, - otlkMag: 1, - spreadPerc: { - divisor: 10, - max: 10, - min: 6, - }, - shareTxForMovement: { - max: 180e3, - min: 60e3, - }, - symbol: StockSymbols[LocationName.Sector12FoodNStuff], + { + b: false, + initPrice: { + max: 4.5e3, + min: 500, }, + marketCap: 45e9, + mv: { + divisor: 100, + max: 80, + min: 70, + }, + name: LocationName.Sector12FoodNStuff, + otlkMag: 1, + spreadPerc: { + divisor: 10, + max: 10, + min: 6, + }, + shareTxForMovement: { + max: 180e3, + min: 60e3, + }, + symbol: StockSymbols[LocationName.Sector12FoodNStuff], + }, - { - b: true, - initPrice: { - max: 3.5e3, - min: 1.5e3, - }, - marketCap: 30e9, - mv: { - divisor: 100, - max: 275, - min: 100, - }, - name: "Sigma Cosmetics", - otlkMag: 0, - spreadPerc: { - divisor: 10, - max: 14, - min: 6, - }, - shareTxForMovement: { - max: 70e3, - min: 20e3, - }, - symbol: StockSymbols["Sigma Cosmetics"], + { + b: true, + initPrice: { + max: 3.5e3, + min: 1.5e3, }, + marketCap: 30e9, + mv: { + divisor: 100, + max: 275, + min: 100, + }, + name: "Sigma Cosmetics", + otlkMag: 0, + spreadPerc: { + divisor: 10, + max: 14, + min: 6, + }, + shareTxForMovement: { + max: 70e3, + min: 20e3, + }, + symbol: StockSymbols["Sigma Cosmetics"], + }, - { - b: true, - initPrice: { - max: 1.5e3, - min: 250, - }, - marketCap: 42e9, - mv: { - divisor: 100, - max: 350, - min: 200, - }, - name: "Joes Guns", - otlkMag: 1, - spreadPerc: { - divisor: 10, - max: 14, - min: 6, - }, - shareTxForMovement: { - max: 52e3, - min: 15e3, - }, - symbol: StockSymbols["Joes Guns"], + { + b: true, + initPrice: { + max: 1.5e3, + min: 250, }, + marketCap: 42e9, + mv: { + divisor: 100, + max: 350, + min: 200, + }, + name: "Joes Guns", + otlkMag: 1, + spreadPerc: { + divisor: 10, + max: 14, + min: 6, + }, + shareTxForMovement: { + max: 52e3, + min: 15e3, + }, + symbol: StockSymbols["Joes Guns"], + }, - { - b: true, - initPrice: { - max: 1.5e3, - min: 250, - }, - marketCap: 100e9, - mv: { - divisor: 100, - max: 175, - min: 120, - }, - name: "Catalyst Ventures", - otlkMag: 13.5, - spreadPerc: { - divisor: 10, - max: 14, - min: 5, - }, - shareTxForMovement: { - max: 72e3, - min: 24e3, - }, - symbol: StockSymbols["Catalyst Ventures"], + { + b: true, + initPrice: { + max: 1.5e3, + min: 250, }, + marketCap: 100e9, + mv: { + divisor: 100, + max: 175, + min: 120, + }, + name: "Catalyst Ventures", + otlkMag: 13.5, + spreadPerc: { + divisor: 10, + max: 14, + min: 5, + }, + shareTxForMovement: { + max: 72e3, + min: 24e3, + }, + symbol: StockSymbols["Catalyst Ventures"], + }, - { - b: true, - initPrice: { - max: 30e3, - min: 15e3, - }, - marketCap: 360e9, - mv: { - divisor: 100, - max: 80, - min: 70, - }, - name: "Microdyne Technologies", - otlkMag: 8, - spreadPerc: { - divisor: 10, - max: 10, - min: 3, - }, - shareTxForMovement: { - max: 216e3, - min: 90e3, - }, - symbol: StockSymbols["Microdyne Technologies"], + { + b: true, + initPrice: { + max: 30e3, + min: 15e3, }, + marketCap: 360e9, + mv: { + divisor: 100, + max: 80, + min: 70, + }, + name: "Microdyne Technologies", + otlkMag: 8, + spreadPerc: { + divisor: 10, + max: 10, + min: 3, + }, + shareTxForMovement: { + max: 216e3, + min: 90e3, + }, + symbol: StockSymbols["Microdyne Technologies"], + }, - { - b: true, - initPrice: { - max: 24e3, - min: 12e3, - }, - marketCap: 420e9, - mv: { - divisor: 100, - max: 70, - min: 50, - }, - name: "Titan Laboratories", - otlkMag: 11, - spreadPerc: { - divisor: 10, - max: 10, - min: 2, - }, - shareTxForMovement: { - max: 216e3, - min: 90e3, - }, - symbol: StockSymbols["Titan Laboratories"], + { + b: true, + initPrice: { + max: 24e3, + min: 12e3, }, + marketCap: 420e9, + mv: { + divisor: 100, + max: 70, + min: 50, + }, + name: "Titan Laboratories", + otlkMag: 11, + spreadPerc: { + divisor: 10, + max: 10, + min: 2, + }, + shareTxForMovement: { + max: 216e3, + min: 90e3, + }, + symbol: StockSymbols["Titan Laboratories"], + }, ]; diff --git a/src/StockMarket/data/OrderTypes.ts b/src/StockMarket/data/OrderTypes.ts index 0780fb83f..4638b2fa0 100644 --- a/src/StockMarket/data/OrderTypes.ts +++ b/src/StockMarket/data/OrderTypes.ts @@ -1,6 +1,6 @@ export enum OrderTypes { - LimitBuy = "Limit Buy Order", - LimitSell = "Limit Sell Order", - StopBuy = "Stop Buy Order", - StopSell = "Stop Sell Order" + LimitBuy = "Limit Buy Order", + LimitSell = "Limit Sell Order", + StopBuy = "Stop Buy Order", + StopSell = "Stop Sell Order", } diff --git a/src/StockMarket/data/PositionTypes.ts b/src/StockMarket/data/PositionTypes.ts index 4425f85bb..1b34a1cf6 100644 --- a/src/StockMarket/data/PositionTypes.ts +++ b/src/StockMarket/data/PositionTypes.ts @@ -1,4 +1,4 @@ export enum PositionTypes { - Long = "L", - Short = "S" + Long = "L", + Short = "S", } diff --git a/src/StockMarket/data/StockSymbols.ts b/src/StockMarket/data/StockSymbols.ts index 8acb04814..773174282 100644 --- a/src/StockMarket/data/StockSymbols.ts +++ b/src/StockMarket/data/StockSymbols.ts @@ -4,38 +4,38 @@ import { LocationName } from "../../Locations/data/LocationNames"; export const StockSymbols: IMap = {}; // Stocks for companies at which you can work -StockSymbols[LocationName.AevumECorp] = "ECP"; -StockSymbols[LocationName.Sector12MegaCorp] = "MGCP"; -StockSymbols[LocationName.Sector12BladeIndustries] = "BLD"; -StockSymbols[LocationName.AevumClarkeIncorporated] = "CLRK"; -StockSymbols[LocationName.VolhavenOmniTekIncorporated] = "OMTK"; -StockSymbols[LocationName.Sector12FourSigma] = "FSIG"; -StockSymbols[LocationName.ChongqingKuaiGongInternational] = "KGI"; -StockSymbols[LocationName.AevumFulcrumTechnologies] = "FLCM"; -StockSymbols[LocationName.IshimaStormTechnologies] = "STM"; -StockSymbols[LocationName.NewTokyoDefComm] = "DCOMM"; -StockSymbols[LocationName.VolhavenHeliosLabs] = "HLS"; -StockSymbols[LocationName.NewTokyoVitaLife] = "VITA"; -StockSymbols[LocationName.Sector12IcarusMicrosystems] = "ICRS"; -StockSymbols[LocationName.Sector12UniversalEnergy] = "UNV"; -StockSymbols[LocationName.AevumAeroCorp] = "AERO"; -StockSymbols[LocationName.VolhavenOmniaCybersystems] = "OMN"; -StockSymbols[LocationName.ChongqingSolarisSpaceSystems] = "SLRS"; -StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals] = "GPH"; -StockSymbols[LocationName.IshimaNovaMedical] = "NVMD"; -StockSymbols[LocationName.AevumWatchdogSecurity] = "WDS"; -StockSymbols[LocationName.VolhavenLexoCorp] = "LXO"; -StockSymbols[LocationName.AevumRhoConstruction] = "RHOC"; -StockSymbols[LocationName.Sector12AlphaEnterprises] = "APHE"; -StockSymbols[LocationName.VolhavenSysCoreSecurities] = "SYSC"; -StockSymbols[LocationName.VolhavenCompuTek] = "CTK"; -StockSymbols[LocationName.AevumNetLinkTechnologies] = "NTLK"; -StockSymbols[LocationName.IshimaOmegaSoftware] = "OMGA"; -StockSymbols[LocationName.Sector12FoodNStuff] = "FNS"; +StockSymbols[LocationName.AevumECorp] = "ECP"; +StockSymbols[LocationName.Sector12MegaCorp] = "MGCP"; +StockSymbols[LocationName.Sector12BladeIndustries] = "BLD"; +StockSymbols[LocationName.AevumClarkeIncorporated] = "CLRK"; +StockSymbols[LocationName.VolhavenOmniTekIncorporated] = "OMTK"; +StockSymbols[LocationName.Sector12FourSigma] = "FSIG"; +StockSymbols[LocationName.ChongqingKuaiGongInternational] = "KGI"; +StockSymbols[LocationName.AevumFulcrumTechnologies] = "FLCM"; +StockSymbols[LocationName.IshimaStormTechnologies] = "STM"; +StockSymbols[LocationName.NewTokyoDefComm] = "DCOMM"; +StockSymbols[LocationName.VolhavenHeliosLabs] = "HLS"; +StockSymbols[LocationName.NewTokyoVitaLife] = "VITA"; +StockSymbols[LocationName.Sector12IcarusMicrosystems] = "ICRS"; +StockSymbols[LocationName.Sector12UniversalEnergy] = "UNV"; +StockSymbols[LocationName.AevumAeroCorp] = "AERO"; +StockSymbols[LocationName.VolhavenOmniaCybersystems] = "OMN"; +StockSymbols[LocationName.ChongqingSolarisSpaceSystems] = "SLRS"; +StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals] = "GPH"; +StockSymbols[LocationName.IshimaNovaMedical] = "NVMD"; +StockSymbols[LocationName.AevumWatchdogSecurity] = "WDS"; +StockSymbols[LocationName.VolhavenLexoCorp] = "LXO"; +StockSymbols[LocationName.AevumRhoConstruction] = "RHOC"; +StockSymbols[LocationName.Sector12AlphaEnterprises] = "APHE"; +StockSymbols[LocationName.VolhavenSysCoreSecurities] = "SYSC"; +StockSymbols[LocationName.VolhavenCompuTek] = "CTK"; +StockSymbols[LocationName.AevumNetLinkTechnologies] = "NTLK"; +StockSymbols[LocationName.IshimaOmegaSoftware] = "OMGA"; +StockSymbols[LocationName.Sector12FoodNStuff] = "FNS"; // Stocks for other companies -StockSymbols["Sigma Cosmetics"] = "SGC"; -StockSymbols["Joes Guns"] = "JGN"; -StockSymbols["Catalyst Ventures"] = "CTYS"; -StockSymbols["Microdyne Technologies"] = "MDYN"; -StockSymbols["Titan Laboratories"] = "TITN"; +StockSymbols["Sigma Cosmetics"] = "SGC"; +StockSymbols["Joes Guns"] = "JGN"; +StockSymbols["Catalyst Ventures"] = "CTYS"; +StockSymbols["Microdyne Technologies"] = "MDYN"; +StockSymbols["Titan Laboratories"] = "TITN"; diff --git a/src/StockMarket/data/TickerHeaderFormatData.ts b/src/StockMarket/data/TickerHeaderFormatData.ts index e88bf370a..9088d0118 100644 --- a/src/StockMarket/data/TickerHeaderFormatData.ts +++ b/src/StockMarket/data/TickerHeaderFormatData.ts @@ -1,11 +1,17 @@ import { StockSymbols } from "./StockSymbols"; export const TickerHeaderFormatData = { - longestName: 0, - longestSymbol: 0, -} + longestName: 0, + longestSymbol: 0, +}; for (const key in StockSymbols) { - TickerHeaderFormatData.longestName = Math.max(key.length, TickerHeaderFormatData.longestName); - TickerHeaderFormatData.longestSymbol = Math.max(StockSymbols[key].length, TickerHeaderFormatData.longestSymbol); + TickerHeaderFormatData.longestName = Math.max( + key.length, + TickerHeaderFormatData.longestName, + ); + TickerHeaderFormatData.longestSymbol = Math.max( + StockSymbols[key].length, + TickerHeaderFormatData.longestSymbol, + ); } diff --git a/src/StockMarket/ui/InfoAndPurchases.tsx b/src/StockMarket/ui/InfoAndPurchases.tsx index 349959db4..6d8138ee1 100644 --- a/src/StockMarket/ui/InfoAndPurchases.tsx +++ b/src/StockMarket/ui/InfoAndPurchases.tsx @@ -6,8 +6,8 @@ import * as React from "react"; import { - getStockMarket4SDataCost, - getStockMarket4STixApiCost, + getStockMarket4SDataCost, + getStockMarket4STixApiCost, } from "../StockMarketCosts"; import { CONSTANTS } from "../../Constants"; @@ -19,213 +19,263 @@ import { Money } from "../../ui/React/Money"; import { dialogBoxCreate } from "../../../utils/DialogBox"; type IProps = { - initStockMarket: () => void; - p: IPlayer; - rerender: () => void; -} + initStockMarket: () => void; + p: IPlayer; + rerender: () => void; +}; const blockStyleMarkup = { - display: "block", -} + display: "block", +}; export class InfoAndPurchases extends React.Component { - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.handleClick4SMarketDataHelpTip = this.handleClick4SMarketDataHelpTip.bind(this); - this.purchaseWseAccount = this.purchaseWseAccount.bind(this); - this.purchaseTixApiAccess = this.purchaseTixApiAccess.bind(this); - this.purchase4SMarketData = this.purchase4SMarketData.bind(this); - this.purchase4SMarketDataTixApiAccess = this.purchase4SMarketDataTixApiAccess.bind(this); + this.handleClick4SMarketDataHelpTip = + this.handleClick4SMarketDataHelpTip.bind(this); + this.purchaseWseAccount = this.purchaseWseAccount.bind(this); + this.purchaseTixApiAccess = this.purchaseTixApiAccess.bind(this); + this.purchase4SMarketData = this.purchase4SMarketData.bind(this); + this.purchase4SMarketDataTixApiAccess = + this.purchase4SMarketDataTixApiAccess.bind(this); + } + + handleClick4SMarketDataHelpTip(): void { + dialogBoxCreate( + "Access to the 4S Market Data feed will display two additional pieces " + + "of information about each stock: Price Forecast & Volatility

    " + + "Price Forecast indicates the probability the stock has of increasing or " + + "decreasing. A '+' forecast means the stock has a higher chance of increasing " + + "than decreasing, and a '-' means the opposite. The number of '+/-' symbols " + + "is used to illustrate the magnitude of these probabilities. For example, " + + "'+++' means that the stock has a significantly higher chance of increasing " + + "than decreasing, while '+' means that the stock only has a slightly higher chance " + + "of increasing than decreasing.

    " + + "Volatility represents the maximum percentage by which a stock's price " + + "can change every tick (a tick occurs every few seconds while the game " + + "is running).

    " + + "A stock's price forecast can change over time. This is also affected by volatility. " + + "The more volatile a stock is, the more its price forecast will change.", + ); + } + + purchaseWseAccount(): void { + if (this.props.p.hasWseAccount) { + return; } - - handleClick4SMarketDataHelpTip(): void { - dialogBoxCreate( - "Access to the 4S Market Data feed will display two additional pieces " + - "of information about each stock: Price Forecast & Volatility

    " + - "Price Forecast indicates the probability the stock has of increasing or " + - "decreasing. A '+' forecast means the stock has a higher chance of increasing " + - "than decreasing, and a '-' means the opposite. The number of '+/-' symbols " + - "is used to illustrate the magnitude of these probabilities. For example, " + - "'+++' means that the stock has a significantly higher chance of increasing " + - "than decreasing, while '+' means that the stock only has a slightly higher chance " + - "of increasing than decreasing.

    " + - "Volatility represents the maximum percentage by which a stock's price " + - "can change every tick (a tick occurs every few seconds while the game " + - "is running).

    " + - "A stock's price forecast can change over time. This is also affected by volatility. " + - "The more volatile a stock is, the more its price forecast will change.", - ); + if (!this.props.p.canAfford(CONSTANTS.WSEAccountCost)) { + return; } + this.props.p.hasWseAccount = true; + this.props.initStockMarket(); + this.props.p.loseMoney(CONSTANTS.WSEAccountCost); + this.props.rerender(); - purchaseWseAccount(): void { - if (this.props.p.hasWseAccount) { return; } - if (!this.props.p.canAfford(CONSTANTS.WSEAccountCost)) { return; } - this.props.p.hasWseAccount = true; - this.props.initStockMarket(); - this.props.p.loseMoney(CONSTANTS.WSEAccountCost); - this.props.rerender(); - - const worldHeader = document.getElementById("world-menu-header"); - if (worldHeader instanceof HTMLElement) { - worldHeader.click(); worldHeader.click(); - } + const worldHeader = document.getElementById("world-menu-header"); + if (worldHeader instanceof HTMLElement) { + worldHeader.click(); + worldHeader.click(); } + } - purchaseTixApiAccess(): void { - if (this.props.p.hasTixApiAccess) { return; } - if (!this.props.p.canAfford(CONSTANTS.TIXAPICost)) { return; } - this.props.p.hasTixApiAccess = true; - this.props.p.loseMoney(CONSTANTS.TIXAPICost); - this.props.rerender(); + purchaseTixApiAccess(): void { + if (this.props.p.hasTixApiAccess) { + return; } - - purchase4SMarketData(): void { - if (this.props.p.has4SData) { return; } - if (!this.props.p.canAfford(getStockMarket4SDataCost())) { return; } - this.props.p.has4SData = true; - this.props.p.loseMoney(getStockMarket4SDataCost()); - this.props.rerender(); + if (!this.props.p.canAfford(CONSTANTS.TIXAPICost)) { + return; } + this.props.p.hasTixApiAccess = true; + this.props.p.loseMoney(CONSTANTS.TIXAPICost); + this.props.rerender(); + } - purchase4SMarketDataTixApiAccess(): void { - if (this.props.p.has4SDataTixApi) { return; } - if (!this.props.p.canAfford(getStockMarket4STixApiCost())) { return; } - this.props.p.has4SDataTixApi = true; - this.props.p.loseMoney(getStockMarket4STixApiCost()); - this.props.rerender(); + purchase4SMarketData(): void { + if (this.props.p.has4SData) { + return; } - - renderPurchaseWseAccountButton(): React.ReactElement { - if (this.props.p.hasWseAccount) { - return ( - - ) - } else { - const cost = CONSTANTS.WSEAccountCost; - return ( - Buy WSE Account - } - /> - ) - } + if (!this.props.p.canAfford(getStockMarket4SDataCost())) { + return; } + this.props.p.has4SData = true; + this.props.p.loseMoney(getStockMarket4SDataCost()); + this.props.rerender(); + } - renderPurchaseTixApiAccessButton(): React.ReactElement { - if (this.props.p.hasTixApiAccess) { - return ( - - ) - } else { - const cost = CONSTANTS.TIXAPICost; - return ( - Buy Trade Information eXchange (TIX) API Access - } - /> - ) - } + purchase4SMarketDataTixApiAccess(): void { + if (this.props.p.has4SDataTixApi) { + return; } - - renderPurchase4SMarketDataButton(): React.ReactElement { - if (this.props.p.has4SData) { - return ( - - ) - } else { - const cost = getStockMarket4SDataCost(); - return ( - Buy 4S Market Data Access - } - tooltip={"Lets you view additional pricing and volatility information about stocks"} - /> - ) - } + if (!this.props.p.canAfford(getStockMarket4STixApiCost())) { + return; } + this.props.p.has4SDataTixApi = true; + this.props.p.loseMoney(getStockMarket4STixApiCost()); + this.props.rerender(); + } - renderPurchase4SMarketDataTixApiAccessButton(): React.ReactElement { - if (!this.props.p.hasTixApiAccess) { - return ( - - ) - } else if (this.props.p.has4SDataTixApi) { - return ( - - ) - } else { - const cost = getStockMarket4STixApiCost(); - return ( - Buy 4S Market Data TIX API Access - } - tooltip={"Let you access 4S Market Data through Netscript"} - /> - ) - } + renderPurchaseWseAccountButton(): React.ReactElement { + if (this.props.p.hasWseAccount) { + return ; + } else { + const cost = CONSTANTS.WSEAccountCost; + return ( + + Buy WSE Account - + + } + /> + ); } + } - render(): React.ReactNode { - const documentationLink = "https://bitburner.readthedocs.io/en/latest/basicgameplay/stockmarket.html"; - return ( -
    -

    Welcome to the World Stock Exchange (WSE)!

    - -
    -

    - To begin trading, you must first purchase an account: -

    - {this.renderPurchaseWseAccountButton()} - -

    Trade Information eXchange (TIX) API

    -

    - TIX, short for Trade Information eXchange, is the communications protocol - used by the WSE. Purchasing access to the TIX API lets you write code to create - your own algorithmic/automated trading strategies. -

    - {this.renderPurchaseTixApiAccessButton()} -

    Four Sigma (4S) Market Data Feed

    -

    - Four Sigma's (4S) Market Data Feed provides information about stocks that will help - your trading strategies. -

    - {this.renderPurchase4SMarketDataButton()} - - {this.renderPurchase4SMarketDataTixApiAccessButton()} -

    - Commission Fees: Every transaction you make has - a commission fee. -


    -

    - WARNING: When you reset after installing Augmentations, the Stock - Market is reset. You will retain your WSE Account, access to the - TIX API, and 4S Market Data access. However, all of your stock - positions are lost, so make sure to sell your stocks before - installing Augmentations! -

    -
    - ) + renderPurchaseTixApiAccessButton(): React.ReactElement { + if (this.props.p.hasTixApiAccess) { + return ; + } else { + const cost = CONSTANTS.TIXAPICost; + return ( + + Buy Trade Information eXchange (TIX) API Access -{" "} + + + } + /> + ); } + } + + renderPurchase4SMarketDataButton(): React.ReactElement { + if (this.props.p.has4SData) { + return ( + + ); + } else { + const cost = getStockMarket4SDataCost(); + return ( + + Buy 4S Market Data Access -{" "} + + + } + tooltip={ + "Lets you view additional pricing and volatility information about stocks" + } + /> + ); + } + } + + renderPurchase4SMarketDataTixApiAccessButton(): React.ReactElement { + if (!this.props.p.hasTixApiAccess) { + return ( + + ); + } else if (this.props.p.has4SDataTixApi) { + return ( + + ); + } else { + const cost = getStockMarket4STixApiCost(); + return ( + + Buy 4S Market Data TIX API Access -{" "} + + + } + tooltip={"Let you access 4S Market Data through Netscript"} + /> + ); + } + } + + render(): React.ReactNode { + const documentationLink = + "https://bitburner.readthedocs.io/en/latest/basicgameplay/stockmarket.html"; + return ( +
    +

    Welcome to the World Stock Exchange (WSE)!

    + +
    +

    To begin trading, you must first purchase an account:

    + {this.renderPurchaseWseAccountButton()} + +

    Trade Information eXchange (TIX) API

    +

    + TIX, short for Trade Information eXchange, is the communications + protocol used by the WSE. Purchasing access to the TIX API lets you + write code to create your own algorithmic/automated trading + strategies. +

    + {this.renderPurchaseTixApiAccessButton()} +

    Four Sigma (4S) Market Data Feed

    +

    + Four Sigma's (4S) Market Data Feed provides information about stocks + that will help your trading strategies. +

    + {this.renderPurchase4SMarketDataButton()} + + {this.renderPurchase4SMarketDataTixApiAccessButton()} +

    + Commission Fees: Every transaction you make has a{" "} + {" "} + commission fee. +

    +
    +

    + WARNING: When you reset after installing Augmentations, the Stock + Market is reset. You will retain your WSE Account, access to the TIX + API, and 4S Market Data access. However, all of your stock positions + are lost, so make sure to sell your stocks before installing + Augmentations! +

    +
    + ); + } } diff --git a/src/StockMarket/ui/Root.tsx b/src/StockMarket/ui/Root.tsx index 815f9e61b..0e6c32f95 100644 --- a/src/StockMarket/ui/Root.tsx +++ b/src/StockMarket/ui/Root.tsx @@ -15,67 +15,72 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; import { EventEmitter } from "../../utils/EventEmitter"; type txFn = (stock: Stock, shares: number) => boolean; -export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean; +export type placeOrderFn = ( + stock: Stock, + shares: number, + price: number, + ordType: OrderTypes, + posType: PositionTypes, +) => boolean; type IProps = { - buyStockLong: txFn; - buyStockShort: txFn; - cancelOrder: (params: any) => void; - eventEmitterForReset?: EventEmitter; - initStockMarket: () => void; - p: IPlayer; - placeOrder: placeOrderFn; - sellStockLong: txFn; - sellStockShort: txFn; - stockMarket: IStockMarket; -} + buyStockLong: txFn; + buyStockShort: txFn; + cancelOrder: (params: any) => void; + eventEmitterForReset?: EventEmitter; + initStockMarket: () => void; + p: IPlayer; + placeOrder: placeOrderFn; + sellStockLong: txFn; + sellStockShort: txFn; + stockMarket: IStockMarket; +}; type IState = { - rerenderFlag: boolean; -} + rerenderFlag: boolean; +}; export class StockMarketRoot extends React.Component { - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.state = { - rerenderFlag: false, - } + this.state = { + rerenderFlag: false, + }; - this.rerender = this.rerender.bind(this); - } + this.rerender = this.rerender.bind(this); + } - rerender(): void { - this.setState((prevState) => { - return { - rerenderFlag: !prevState.rerenderFlag, - } - }); - } + rerender(): void { + this.setState((prevState) => { + return { + rerenderFlag: !prevState.rerenderFlag, + }; + }); + } - render(): React.ReactNode { - return ( -
    - - { - this.props.p.hasWseAccount && - - } -
    - ) - } + render(): React.ReactNode { + return ( +
    + + {this.props.p.hasWseAccount && ( + + )} +
    + ); + } } diff --git a/src/StockMarket/ui/StockTicker.tsx b/src/StockMarket/ui/StockTicker.tsx index dc6de5280..b81c0ac55 100644 --- a/src/StockMarket/ui/StockTicker.tsx +++ b/src/StockMarket/ui/StockTicker.tsx @@ -11,9 +11,9 @@ import { StockTickerTxButton } from "./StockTickerTxButton"; import { Order } from "../Order"; import { Stock } from "../Stock"; import { - getBuyTransactionCost, - getSellTransactionGain, - calculateBuyMaxAmount, + getBuyTransactionCost, + getSellTransactionGain, + calculateBuyMaxAmount, } from "../StockMarketHelpers"; import { OrderTypes } from "../data/OrderTypes"; import { PositionTypes } from "../data/PositionTypes"; @@ -26,367 +26,463 @@ import { Money } from "../../ui/React/Money"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { - yesNoTxtInpBoxClose, - yesNoTxtInpBoxCreate, - yesNoTxtInpBoxGetInput, - yesNoTxtInpBoxGetNoButton, - yesNoTxtInpBoxGetYesButton, + yesNoTxtInpBoxClose, + yesNoTxtInpBoxCreate, + yesNoTxtInpBoxGetInput, + yesNoTxtInpBoxGetNoButton, + yesNoTxtInpBoxGetYesButton, } from "../../../utils/YesNoBox"; enum SelectorOrderType { - Market = "Market Order", - Limit = "Limit Order", - Stop = "Stop Order", + Market = "Market Order", + Limit = "Limit Order", + Stop = "Stop Order", } export type txFn = (stock: Stock, shares: number) => boolean; -export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean; +export type placeOrderFn = ( + stock: Stock, + shares: number, + price: number, + ordType: OrderTypes, + posType: PositionTypes, +) => boolean; type IProps = { - buyStockLong: txFn; - buyStockShort: txFn; - cancelOrder: (params: any) => void; - orders: Order[]; - p: IPlayer; - placeOrder: placeOrderFn; - rerenderAllTickers: () => void; - sellStockLong: txFn; - sellStockShort: txFn; - stock: Stock; -} + buyStockLong: txFn; + buyStockShort: txFn; + cancelOrder: (params: any) => void; + orders: Order[]; + p: IPlayer; + placeOrder: placeOrderFn; + rerenderAllTickers: () => void; + sellStockLong: txFn; + sellStockShort: txFn; + stock: Stock; +}; type IState = { - orderType: SelectorOrderType; - position: PositionTypes; - qty: string; -} + orderType: SelectorOrderType; + position: PositionTypes; + qty: string; +}; export class StockTicker extends React.Component { - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.state = { - orderType: SelectorOrderType.Market, - position: PositionTypes.Long, - qty: "", - } + this.state = { + orderType: SelectorOrderType.Market, + position: PositionTypes.Long, + qty: "", + }; - this.getBuyTransactionCostContent = this.getBuyTransactionCostContent.bind(this); - this.getSellTransactionCostContent = this.getSellTransactionCostContent.bind(this); - this.handleBuyButtonClick = this.handleBuyButtonClick.bind(this); - this.handleBuyMaxButtonClick = this.handleBuyMaxButtonClick.bind(this); - this.handleHeaderClick = this.handleHeaderClick.bind(this); - this.handleOrderTypeChange = this.handleOrderTypeChange.bind(this); - this.handlePositionTypeChange = this.handlePositionTypeChange.bind(this); - this.handleQuantityChange = this.handleQuantityChange.bind(this); - this.handleSellButtonClick = this.handleSellButtonClick.bind(this); - this.handleSellAllButtonClick = this.handleSellAllButtonClick.bind(this); + this.getBuyTransactionCostContent = + this.getBuyTransactionCostContent.bind(this); + this.getSellTransactionCostContent = + this.getSellTransactionCostContent.bind(this); + this.handleBuyButtonClick = this.handleBuyButtonClick.bind(this); + this.handleBuyMaxButtonClick = this.handleBuyMaxButtonClick.bind(this); + this.handleHeaderClick = this.handleHeaderClick.bind(this); + this.handleOrderTypeChange = this.handleOrderTypeChange.bind(this); + this.handlePositionTypeChange = this.handlePositionTypeChange.bind(this); + this.handleQuantityChange = this.handleQuantityChange.bind(this); + this.handleSellButtonClick = this.handleSellButtonClick.bind(this); + this.handleSellAllButtonClick = this.handleSellAllButtonClick.bind(this); + } + + createPlaceOrderPopupBox( + yesTxt: string, + popupTxt: string, + yesBtnCb: (price: number) => void, + ): void { + const yesBtn = yesNoTxtInpBoxGetYesButton(); + const noBtn = yesNoTxtInpBoxGetNoButton(); + + yesBtn.innerText = yesTxt; + yesBtn.addEventListener("click", () => { + const price = parseFloat(yesNoTxtInpBoxGetInput()); + if (isNaN(price)) { + dialogBoxCreate(`Invalid input for price: ${yesNoTxtInpBoxGetInput()}`); + return false; + } + + yesBtnCb(price); + yesNoTxtInpBoxClose(); + }); + + noBtn.innerText = "Cancel Order"; + noBtn.addEventListener("click", () => { + yesNoTxtInpBoxClose(); + }); + + yesNoTxtInpBoxCreate(popupTxt); + } + + getBuyTransactionCostContent(): JSX.Element | null { + const stock = this.props.stock; + const qty: number = this.getQuantity(); + if (isNaN(qty)) { + return null; } - createPlaceOrderPopupBox(yesTxt: string, popupTxt: string, yesBtnCb: (price: number) => void): void { - const yesBtn = yesNoTxtInpBoxGetYesButton(); - const noBtn = yesNoTxtInpBoxGetNoButton(); - - yesBtn.innerText = yesTxt; - yesBtn.addEventListener("click", () => { - const price = parseFloat(yesNoTxtInpBoxGetInput()); - if (isNaN(price)) { - dialogBoxCreate(`Invalid input for price: ${yesNoTxtInpBoxGetInput()}`); - return false; - } - - yesBtnCb(price); - yesNoTxtInpBoxClose(); - }); - - noBtn.innerText = "Cancel Order"; - noBtn.addEventListener("click", () => { - yesNoTxtInpBoxClose(); - }); - - yesNoTxtInpBoxCreate(popupTxt); + const cost = getBuyTransactionCost(stock, qty, this.state.position); + if (cost == null) { + return null; } - getBuyTransactionCostContent(): JSX.Element | null { - const stock = this.props.stock; - const qty: number = this.getQuantity(); - if (isNaN(qty)) { return null; } + return ( + <> + Purchasing {numeralWrapper.formatShares(qty)} shares ( + {this.state.position === PositionTypes.Long ? "Long" : "Short"}) will + cost . + + ); + } - const cost = getBuyTransactionCost(stock, qty, this.state.position); - if (cost == null) { return null; } + getQuantity(): number { + return Math.round(parseFloat(this.state.qty)); + } - return <>Purchasing {numeralWrapper.formatShares(qty)} shares ({this.state.position === PositionTypes.Long ? "Long" : "Short"}) will cost .; + getSellTransactionCostContent(): JSX.Element | null { + const stock = this.props.stock; + const qty: number = this.getQuantity(); + if (isNaN(qty)) { + return null; } - getQuantity(): number { - return Math.round(parseFloat(this.state.qty)); + if (this.state.position === PositionTypes.Long) { + if (qty > stock.playerShares) { + return <>You do not have this many shares in the Long position; + } + } else { + if (qty > stock.playerShortShares) { + return <>You do not have this many shares in the Short position; + } } - getSellTransactionCostContent(): JSX.Element | null { - const stock = this.props.stock; - const qty: number = this.getQuantity(); - if (isNaN(qty)) { return null; } + const cost = getSellTransactionGain(stock, qty, this.state.position); + if (cost == null) { + return null; + } - if (this.state.position === PositionTypes.Long) { - if (qty > stock.playerShares) { - return <>You do not have this many shares in the Long position; - } + return ( + <> + Selling {numeralWrapper.formatShares(qty)} shares ( + {this.state.position === PositionTypes.Long ? "Long" : "Short"}) will + result in a gain of . + + ); + } + + handleBuyButtonClick(): void { + const shares = this.getQuantity(); + if (isNaN(shares)) { + dialogBoxCreate( + `Invalid input for quantity (number of shares): ${this.state.qty}`, + ); + return; + } + + switch (this.state.orderType) { + case SelectorOrderType.Market: { + if (this.state.position === PositionTypes.Short) { + this.props.buyStockShort(this.props.stock, shares); } else { - if (qty > stock.playerShortShares) { - return <>You do not have this many shares in the Short position; - } + this.props.buyStockLong(this.props.stock, shares); } - - const cost = getSellTransactionGain(stock, qty, this.state.position); - if (cost == null) { return null; } - - return <>Selling {numeralWrapper.formatShares(qty)} shares ({this.state.position === PositionTypes.Long ? "Long" : "Short"}) will result in a gain of .; + this.props.rerenderAllTickers(); + break; + } + case SelectorOrderType.Limit: { + this.createPlaceOrderPopupBox( + "Place Buy Limit Order", + "Enter the price for your Limit Order", + (price: number) => { + this.props.placeOrder( + this.props.stock, + shares, + price, + OrderTypes.LimitBuy, + this.state.position, + ); + }, + ); + break; + } + case SelectorOrderType.Stop: { + this.createPlaceOrderPopupBox( + "Place Buy Stop Order", + "Enter the price for your Stop Order", + (price: number) => { + this.props.placeOrder( + this.props.stock, + shares, + price, + OrderTypes.StopBuy, + this.state.position, + ); + }, + ); + break; + } + default: + break; } + } - handleBuyButtonClick(): void { - const shares = this.getQuantity(); - if (isNaN(shares)) { - dialogBoxCreate(`Invalid input for quantity (number of shares): ${this.state.qty}`); - return; - } + handleBuyMaxButtonClick(): void { + const playerMoney: number = this.props.p.money.toNumber(); - switch (this.state.orderType) { - case SelectorOrderType.Market: { - if (this.state.position === PositionTypes.Short) { - this.props.buyStockShort(this.props.stock, shares); - } else { - this.props.buyStockLong(this.props.stock, shares); - } - this.props.rerenderAllTickers(); - break; - } - case SelectorOrderType.Limit: { - this.createPlaceOrderPopupBox( - "Place Buy Limit Order", - "Enter the price for your Limit Order", - (price: number) => { - this.props.placeOrder(this.props.stock, shares, price, OrderTypes.LimitBuy, this.state.position); - }, - ); - break; - } - case SelectorOrderType.Stop: { - this.createPlaceOrderPopupBox( - "Place Buy Stop Order", - "Enter the price for your Stop Order", - (price: number) => { - this.props.placeOrder(this.props.stock, shares, price, OrderTypes.StopBuy, this.state.position); - }, - ); - break; - } - default: - break; - } - } + const stock = this.props.stock; + let maxShares = calculateBuyMaxAmount( + stock, + this.state.position, + playerMoney, + ); + maxShares = Math.min( + maxShares, + Math.round( + stock.maxShares - stock.playerShares - stock.playerShortShares, + ), + ); - handleBuyMaxButtonClick(): void { - const playerMoney: number = this.props.p.money.toNumber(); - - const stock = this.props.stock; - let maxShares = calculateBuyMaxAmount(stock, this.state.position, playerMoney); - maxShares = Math.min(maxShares, Math.round(stock.maxShares - stock.playerShares - stock.playerShortShares)); - - switch (this.state.orderType) { - case SelectorOrderType.Market: { - if (this.state.position === PositionTypes.Short) { - this.props.buyStockShort(stock, maxShares); - } else { - this.props.buyStockLong(stock, maxShares); - } - this.props.rerenderAllTickers(); - break; - } - default: { - dialogBoxCreate(`ERROR: 'Buy Max' only works for Market Orders`); - break; - } - } - } - - handleHeaderClick(e: React.MouseEvent): void { - const elem = e.currentTarget; - elem.classList.toggle("active"); - - const panel: HTMLElement = elem.nextElementSibling as HTMLElement; - if (panel.style.display === "block") { - panel.style.display = "none"; + switch (this.state.orderType) { + case SelectorOrderType.Market: { + if (this.state.position === PositionTypes.Short) { + this.props.buyStockShort(stock, maxShares); } else { - panel.style.display = "block"; + this.props.buyStockLong(stock, maxShares); } + this.props.rerenderAllTickers(); + break; + } + default: { + dialogBoxCreate(`ERROR: 'Buy Max' only works for Market Orders`); + break; + } } + } - handleOrderTypeChange(e: React.ChangeEvent): void { - const val = e.target.value; + handleHeaderClick(e: React.MouseEvent): void { + const elem = e.currentTarget; + elem.classList.toggle("active"); - // The select value returns a string. Afaik TypeScript doesnt make it easy - // to convert that string back to an enum type so we'll just do this for now - switch (val) { - case SelectorOrderType.Limit: - this.setState({ - orderType: SelectorOrderType.Limit, - }); - break; - case SelectorOrderType.Stop: - this.setState({ - orderType: SelectorOrderType.Stop, - }); - break; - case SelectorOrderType.Market: - default: - this.setState({ - orderType: SelectorOrderType.Market, - }); - } + const panel: HTMLElement = elem.nextElementSibling as HTMLElement; + if (panel.style.display === "block") { + panel.style.display = "none"; + } else { + panel.style.display = "block"; } + } - handlePositionTypeChange(e: React.ChangeEvent): void { - const val = e.target.value; + handleOrderTypeChange(e: React.ChangeEvent): void { + const val = e.target.value; - if (val === PositionTypes.Short) { - this.setState({ - position: PositionTypes.Short, - }); - } else { - this.setState({ - position: PositionTypes.Long, - }); - } - - } - - handleQuantityChange(e: React.ChangeEvent): void { + // The select value returns a string. Afaik TypeScript doesnt make it easy + // to convert that string back to an enum type so we'll just do this for now + switch (val) { + case SelectorOrderType.Limit: this.setState({ - qty: e.target.value, + orderType: SelectorOrderType.Limit, + }); + break; + case SelectorOrderType.Stop: + this.setState({ + orderType: SelectorOrderType.Stop, + }); + break; + case SelectorOrderType.Market: + default: + this.setState({ + orderType: SelectorOrderType.Market, }); } + } - handleSellButtonClick(): void { - const shares = this.getQuantity(); - if (isNaN(shares)) { - dialogBoxCreate(`Invalid input for quantity (number of shares): ${this.state.qty}`); - return; + handlePositionTypeChange(e: React.ChangeEvent): void { + const val = e.target.value; + + if (val === PositionTypes.Short) { + this.setState({ + position: PositionTypes.Short, + }); + } else { + this.setState({ + position: PositionTypes.Long, + }); + } + } + + handleQuantityChange(e: React.ChangeEvent): void { + this.setState({ + qty: e.target.value, + }); + } + + handleSellButtonClick(): void { + const shares = this.getQuantity(); + if (isNaN(shares)) { + dialogBoxCreate( + `Invalid input for quantity (number of shares): ${this.state.qty}`, + ); + return; + } + + switch (this.state.orderType) { + case SelectorOrderType.Market: { + if (this.state.position === PositionTypes.Short) { + this.props.sellStockShort(this.props.stock, shares); + } else { + this.props.sellStockLong(this.props.stock, shares); } + this.props.rerenderAllTickers(); + break; + } + case SelectorOrderType.Limit: { + this.createPlaceOrderPopupBox( + "Place Sell Limit Order", + "Enter the price for your Limit Order", + (price: number) => { + this.props.placeOrder( + this.props.stock, + shares, + price, + OrderTypes.LimitSell, + this.state.position, + ); + }, + ); + break; + } + case SelectorOrderType.Stop: { + this.createPlaceOrderPopupBox( + "Place Sell Stop Order", + "Enter the price for your Stop Order", + (price: number) => { + this.props.placeOrder( + this.props.stock, + shares, + price, + OrderTypes.StopSell, + this.state.position, + ); + }, + ); + break; + } + default: + break; + } + } - switch (this.state.orderType) { - case SelectorOrderType.Market: { - if (this.state.position === PositionTypes.Short) { - this.props.sellStockShort(this.props.stock, shares); - } else { - this.props.sellStockLong(this.props.stock, shares); - } - this.props.rerenderAllTickers(); - break; - } - case SelectorOrderType.Limit: { - this.createPlaceOrderPopupBox( - "Place Sell Limit Order", - "Enter the price for your Limit Order", - (price: number) => { - this.props.placeOrder(this.props.stock, shares, price, OrderTypes.LimitSell, this.state.position); - }, - ); - break; - } - case SelectorOrderType.Stop: { - this.createPlaceOrderPopupBox( - "Place Sell Stop Order", - "Enter the price for your Stop Order", - (price: number) => { - this.props.placeOrder(this.props.stock, shares, price, OrderTypes.StopSell, this.state.position); - }, - ) - break; - } - default: - break; + handleSellAllButtonClick(): void { + const stock = this.props.stock; + + switch (this.state.orderType) { + case SelectorOrderType.Market: { + if (this.state.position === PositionTypes.Short) { + this.props.sellStockShort(stock, stock.playerShortShares); + } else { + this.props.sellStockLong(stock, stock.playerShares); } + this.props.rerenderAllTickers(); + break; + } + default: { + dialogBoxCreate(`ERROR: 'Sell All' only works for Market Orders`); + break; + } } + } - handleSellAllButtonClick(): void { - const stock = this.props.stock; + // Whether the player has access to orders besides market orders (limit/stop) + hasOrderAccess(): boolean { + return this.props.p.bitNodeN === 8 || SourceFileFlags[8] >= 3; + } - switch (this.state.orderType) { - case SelectorOrderType.Market: { - if (this.state.position === PositionTypes.Short) { - this.props.sellStockShort(stock, stock.playerShortShares); - } else { - this.props.sellStockLong(stock, stock.playerShares); - } - this.props.rerenderAllTickers(); - break; - } - default: { - dialogBoxCreate(`ERROR: 'Sell All' only works for Market Orders`); - break; - } - } - } + // Whether the player has access to shorting stocks + hasShortAccess(): boolean { + return this.props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2; + } - // Whether the player has access to orders besides market orders (limit/stop) - hasOrderAccess(): boolean { - return (this.props.p.bitNodeN === 8 || (SourceFileFlags[8] >= 3)); - } + render(): React.ReactNode { + return ( +
  • + + } + panelContent={ +
    + + + - // Whether the player has access to shorting stocks - hasShortAccess(): boolean { - return (this.props.p.bitNodeN === 8 || (SourceFileFlags[8] >= 2)); - } - - render(): React.ReactNode { - return ( -
  • - - } - panelContent={ -
    - - - - - - - - - - -
    - } - /> -
  • - ) - } + + + + + + +
    + } + /> + + ); + } } diff --git a/src/StockMarket/ui/StockTickerHeaderText.tsx b/src/StockMarket/ui/StockTickerHeaderText.tsx index bed2784cb..4ae02b79a 100644 --- a/src/StockMarket/ui/StockTickerHeaderText.tsx +++ b/src/StockMarket/ui/StockTickerHeaderText.tsx @@ -13,44 +13,56 @@ import { Settings } from "../../Settings/Settings"; import { numeralWrapper } from "../../ui/numeralFormat"; type IProps = { - p: IPlayer; - stock: Stock; -} + p: IPlayer; + stock: Stock; +}; -const localesWithLongPriceFormat = [ - "cs", - "lv", - "pl", - "ru", -]; +const localesWithLongPriceFormat = ["cs", "lv", "pl", "ru"]; export function StockTickerHeaderText(props: IProps): React.ReactElement { - const stock = props.stock; + const stock = props.stock; - const stockPriceFormat = numeralWrapper.formatMoney(stock.price); - const spacesAllottedForStockPrice = localesWithLongPriceFormat.includes(Settings.Locale) ? 15 : 12; - const spacesAfterStockName = " ".repeat(1 + TickerHeaderFormatData.longestName - stock.name.length + (TickerHeaderFormatData.longestSymbol - stock.symbol.length)); - const spacesBeforePrice = " ".repeat(spacesAllottedForStockPrice - stockPriceFormat.length); + const stockPriceFormat = numeralWrapper.formatMoney(stock.price); + const spacesAllottedForStockPrice = localesWithLongPriceFormat.includes( + Settings.Locale, + ) + ? 15 + : 12; + const spacesAfterStockName = " ".repeat( + 1 + + TickerHeaderFormatData.longestName - + stock.name.length + + (TickerHeaderFormatData.longestSymbol - stock.symbol.length), + ); + const spacesBeforePrice = " ".repeat( + spacesAllottedForStockPrice - stockPriceFormat.length, + ); - let hdrText = `${stock.name}${spacesAfterStockName}${stock.symbol} -${spacesBeforePrice}${stockPriceFormat}`; - if (props.p.has4SData) { - hdrText += ` - Volatility: ${numeralWrapper.formatPercentage(stock.mv/100)} - Price Forecast: `; - let plusOrMinus = stock.b; // True for "+", false for "-" - if (stock.otlkMag < 0) { plusOrMinus = !plusOrMinus } - hdrText += (plusOrMinus ? "+" : "-").repeat(Math.floor(Math.abs(stock.otlkMag) / 10) + 1); - - // Debugging: - // hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`; + let hdrText = `${stock.name}${spacesAfterStockName}${stock.symbol} -${spacesBeforePrice}${stockPriceFormat}`; + if (props.p.has4SData) { + hdrText += ` - Volatility: ${numeralWrapper.formatPercentage( + stock.mv / 100, + )} - Price Forecast: `; + let plusOrMinus = stock.b; // True for "+", false for "-" + if (stock.otlkMag < 0) { + plusOrMinus = !plusOrMinus; } + hdrText += (plusOrMinus ? "+" : "-").repeat( + Math.floor(Math.abs(stock.otlkMag) / 10) + 1, + ); - const styleMarkup = { - color: "#66ff33", - }; - if (stock.lastPrice === stock.price) { - styleMarkup.color = "white"; - } else if (stock.lastPrice > stock.price) { - styleMarkup.color = "red"; - } + // Debugging: + // hdrText += ` - ${stock.getAbsoluteForecast()} / ${stock.otlkMagForecast}`; + } - return
    {hdrText}
    ; + const styleMarkup = { + color: "#66ff33", + }; + if (stock.lastPrice === stock.price) { + styleMarkup.color = "white"; + } else if (stock.lastPrice > stock.price) { + styleMarkup.color = "red"; + } + + return
    {hdrText}
    ; } diff --git a/src/StockMarket/ui/StockTickerOrder.tsx b/src/StockMarket/ui/StockTickerOrder.tsx index f055f80ce..2508a7c35 100644 --- a/src/StockMarket/ui/StockTickerOrder.tsx +++ b/src/StockMarket/ui/StockTickerOrder.tsx @@ -10,34 +10,43 @@ import { numeralWrapper } from "../../ui/numeralFormat"; import { Money } from "../../ui/React/Money"; type IProps = { - cancelOrder: (params: any) => void; - order: Order; -} + cancelOrder: (params: any) => void; + order: Order; +}; export class StockTickerOrder extends React.Component { - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.handleCancelOrderClick = this.handleCancelOrderClick.bind(this); - } + this.handleCancelOrderClick = this.handleCancelOrderClick.bind(this); + } - handleCancelOrderClick(): void { - this.props.cancelOrder({ order: this.props.order }); - } + handleCancelOrderClick(): void { + this.props.cancelOrder({ order: this.props.order }); + } - render(): React.ReactNode { - const order = this.props.order; + render(): React.ReactNode { + const order = this.props.order; - const posTxt = order.pos === PositionTypes.Long ? "Long Position" : "Short Position"; - const txt = <>{order.type} - {posTxt} - {numeralWrapper.formatShares(order.shares)} @ + const posTxt = + order.pos === PositionTypes.Long ? "Long Position" : "Short Position"; + const txt = ( + <> + {order.type} - {posTxt} - {numeralWrapper.formatShares(order.shares)} @{" "} + + + ); - return ( -
  • - {txt} - -
  • - ) - } + return ( +
  • + {txt} + +
  • + ); + } } diff --git a/src/StockMarket/ui/StockTickerOrderList.tsx b/src/StockMarket/ui/StockTickerOrderList.tsx index 77f239fb4..e997da4a9 100644 --- a/src/StockMarket/ui/StockTickerOrderList.tsx +++ b/src/StockMarket/ui/StockTickerOrderList.tsx @@ -12,22 +12,26 @@ import { Stock } from "../Stock"; import { IPlayer } from "../../PersonObjects/IPlayer"; type IProps = { - cancelOrder: (params: any) => void; - orders: Order[]; - p: IPlayer; - stock: Stock; -} + cancelOrder: (params: any) => void; + orders: Order[]; + p: IPlayer; + stock: Stock; +}; export class StockTickerOrderList extends React.Component { - render(): React.ReactNode { - const orders: React.ReactElement[] = []; - for (let i = 0; i < this.props.orders.length; ++i) { - const o = this.props.orders[i]; - orders.push(); - } - - return ( -
      {orders}
    - ) + render(): React.ReactNode { + const orders: React.ReactElement[] = []; + for (let i = 0; i < this.props.orders.length; ++i) { + const o = this.props.orders[i]; + orders.push( + , + ); } + + return
      {orders}
    ; + } } diff --git a/src/StockMarket/ui/StockTickerPositionText.tsx b/src/StockMarket/ui/StockTickerPositionText.tsx index 06a11b8bb..da801bb8f 100644 --- a/src/StockMarket/ui/StockTickerPositionText.tsx +++ b/src/StockMarket/ui/StockTickerPositionText.tsx @@ -12,104 +12,119 @@ import { Money } from "../../ui/React/Money"; import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; type IProps = { - p: IPlayer; - stock: Stock; -} + p: IPlayer; + stock: Stock; +}; const blockStyleMarkup = { - display: "block", -} + display: "block", +}; export class StockTickerPositionText extends React.Component { - renderLongPosition(): React.ReactElement { - const stock = this.props.stock; + renderLongPosition(): React.ReactElement { + const stock = this.props.stock; - // Caculate total returns - const totalCost = stock.playerShares * stock.playerAvgPx; - const gains = (stock.getBidPrice() - stock.playerAvgPx) * stock.playerShares; - let percentageGains = gains / totalCost; - if (isNaN(percentageGains)) { percentageGains = 0; } - - return ( -
    -

    - Long Position: - - Shares in the long position will increase in value if the price - of the corresponding stock increases - -


    -

    - Shares: {numeralWrapper.formatShares(stock.playerShares)} -


    -

    - Average Price: (Total Cost: -


    -

    - Profit: ({numeralWrapper.formatPercentage(percentageGains)}) -


    -
    - ) + // Caculate total returns + const totalCost = stock.playerShares * stock.playerAvgPx; + const gains = + (stock.getBidPrice() - stock.playerAvgPx) * stock.playerShares; + let percentageGains = gains / totalCost; + if (isNaN(percentageGains)) { + percentageGains = 0; } - renderShortPosition(): React.ReactElement | null { - const stock = this.props.stock; + return ( +
    +

    + Long Position: + + Shares in the long position will increase in value if the price of + the corresponding stock increases + +

    +
    +

    Shares: {numeralWrapper.formatShares(stock.playerShares)}

    +
    +

    + Average Price: (Total Cost:{" "} + +

    +
    +

    + Profit: ( + {numeralWrapper.formatPercentage(percentageGains)}) +

    +
    +
    + ); + } - // Caculate total returns - const totalCost = stock.playerShortShares * stock.playerAvgShortPx; - const gains = (stock.playerAvgShortPx - stock.getAskPrice()) * stock.playerShortShares; - let percentageGains = gains / totalCost; - if (isNaN(percentageGains)) { percentageGains = 0; } + renderShortPosition(): React.ReactElement | null { + const stock = this.props.stock; - if (this.props.p.bitNodeN === 8 || (SourceFileFlags[8] >= 2)) { - return ( -
    -

    - Short Position: - - Shares in the short position will increase in value if the - price of the corresponding stock decreases - -


    -

    - Shares: {numeralWrapper.formatShares(stock.playerShortShares)} -


    -

    - Average Price: (Total Cost: ) -


    -

    - Profit: ({numeralWrapper.formatPercentage(percentageGains)}) -


    -
    - ) - } else { - return null; - } + // Caculate total returns + const totalCost = stock.playerShortShares * stock.playerAvgShortPx; + const gains = + (stock.playerAvgShortPx - stock.getAskPrice()) * stock.playerShortShares; + let percentageGains = gains / totalCost; + if (isNaN(percentageGains)) { + percentageGains = 0; } - render(): React.ReactNode { - const stock = this.props.stock; - - return ( -
    -

    - Max Shares: {numeralWrapper.formatShares(stock.maxShares)} -

    -

    - Ask Price: - - See Investopedia for details on what this is - -


    -

    - Bid Price: - - See Investopedia for details on what this is - -

    - {this.renderLongPosition()} - {this.renderShortPosition()} -
    - ) + if (this.props.p.bitNodeN === 8 || SourceFileFlags[8] >= 2) { + return ( +
    +

    + Short Position: + + Shares in the short position will increase in value if the price + of the corresponding stock decreases + +

    +
    +

    Shares: {numeralWrapper.formatShares(stock.playerShortShares)}

    +
    +

    + Average Price: (Total Cost:{" "} + ) +

    +
    +

    + Profit: ( + {numeralWrapper.formatPercentage(percentageGains)}) +

    +
    +
    + ); + } else { + return null; } + } + + render(): React.ReactNode { + const stock = this.props.stock; + + return ( +
    +

    + Max Shares: {numeralWrapper.formatShares(stock.maxShares)} +

    +

    + Ask Price: + + See Investopedia for details on what this is + +

    +
    +

    + Bid Price: + + See Investopedia for details on what this is + +

    + {this.renderLongPosition()} + {this.renderShortPosition()} +
    + ); + } } diff --git a/src/StockMarket/ui/StockTickerTxButton.tsx b/src/StockMarket/ui/StockTickerTxButton.tsx index 00fadb6b6..a555d37bc 100644 --- a/src/StockMarket/ui/StockTickerTxButton.tsx +++ b/src/StockMarket/ui/StockTickerTxButton.tsx @@ -5,26 +5,25 @@ import * as React from "react"; type IProps = { - onClick: () => void; - text: string; - tooltip?: JSX.Element | null; -} + onClick: () => void; + text: string; + tooltip?: JSX.Element | null; +}; export function StockTickerTxButton(props: IProps): React.ReactElement { - let className = "stock-market-input std-button"; + let className = "stock-market-input std-button"; - const hasTooltip = (props.tooltip != null); - if (hasTooltip) { - className += " tooltip"; - } + const hasTooltip = props.tooltip != null; + if (hasTooltip) { + className += " tooltip"; + } - return ( - - ) + return ( + + ); } diff --git a/src/StockMarket/ui/StockTickers.tsx b/src/StockMarket/ui/StockTickers.tsx index 130c09b67..4dfad7d8b 100644 --- a/src/StockMarket/ui/StockTickers.tsx +++ b/src/StockMarket/ui/StockTickers.tsx @@ -19,179 +19,196 @@ import { EventEmitter } from "../../utils/EventEmitter"; import { ErrorBoundary } from "../../ui/React/ErrorBoundary"; export type txFn = (stock: Stock, shares: number) => boolean; -export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean; +export type placeOrderFn = ( + stock: Stock, + shares: number, + price: number, + ordType: OrderTypes, + posType: PositionTypes, +) => boolean; type IProps = { - buyStockLong: txFn; - buyStockShort: txFn; - cancelOrder: (params: any) => void; - eventEmitterForReset?: EventEmitter; - p: IPlayer; - placeOrder: placeOrderFn; - sellStockLong: txFn; - sellStockShort: txFn; - stockMarket: IStockMarket; -} + buyStockLong: txFn; + buyStockShort: txFn; + cancelOrder: (params: any) => void; + eventEmitterForReset?: EventEmitter; + p: IPlayer; + placeOrder: placeOrderFn; + sellStockLong: txFn; + sellStockShort: txFn; + stockMarket: IStockMarket; +}; type IState = { - rerenderFlag: boolean; - tickerDisplayMode: TickerDisplayMode; - watchlistFilter: string; - watchlistSymbols: string[]; -} + rerenderFlag: boolean; + tickerDisplayMode: TickerDisplayMode; + watchlistFilter: string; + watchlistSymbols: string[]; +}; export class StockTickers extends React.Component { - listRef: React.RefObject; + listRef: React.RefObject; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.state = { - rerenderFlag: false, - tickerDisplayMode: TickerDisplayMode.AllStocks, - watchlistFilter: "", - watchlistSymbols: [], + this.state = { + rerenderFlag: false, + tickerDisplayMode: TickerDisplayMode.AllStocks, + watchlistFilter: "", + watchlistSymbols: [], + }; + + this.changeDisplayMode = this.changeDisplayMode.bind(this); + this.changeWatchlistFilter = this.changeWatchlistFilter.bind(this); + this.collapseAllTickers = this.collapseAllTickers.bind(this); + this.expandAllTickers = this.expandAllTickers.bind(this); + this.rerender = this.rerender.bind(this); + + this.listRef = React.createRef(); + } + + changeDisplayMode(): void { + if (this.state.tickerDisplayMode === TickerDisplayMode.AllStocks) { + this.setState({ + tickerDisplayMode: TickerDisplayMode.Portfolio, + }); + } else { + this.setState({ + tickerDisplayMode: TickerDisplayMode.AllStocks, + }); + } + } + + changeWatchlistFilter(e: React.ChangeEvent): void { + const watchlist = e.target.value; + const sanitizedWatchlist = watchlist.replace(/\s/g, ""); + + this.setState({ + watchlistFilter: watchlist, + }); + + if (sanitizedWatchlist !== "") { + this.setState({ + watchlistSymbols: sanitizedWatchlist.split(","), + }); + } else { + this.setState({ + watchlistSymbols: [], + }); + } + } + + collapseAllTickers(): void { + const ul = this.listRef.current; + if (ul == null) { + return; + } + const tickers = ul.getElementsByClassName("accordion-header"); + for (let i = 0; i < tickers.length; ++i) { + const ticker = tickers[i]; + if (!(ticker instanceof HTMLButtonElement)) { + continue; + } + + if (ticker.classList.contains("active")) { + ticker.click(); + } + } + } + + expandAllTickers(): void { + const ul = this.listRef.current; + if (ul == null) { + return; + } + const tickers = ul.getElementsByClassName("accordion-header"); + for (let i = 0; i < tickers.length; ++i) { + const ticker = tickers[i]; + if (!(ticker instanceof HTMLButtonElement)) { + continue; + } + + if (!ticker.classList.contains("active")) { + ticker.click(); + } + } + } + + rerender(): void { + this.setState((prevState) => { + return { + rerenderFlag: !prevState.rerenderFlag, + }; + }); + } + + render(): React.ReactNode { + const tickers: React.ReactElement[] = []; + for (const stockMarketProp in this.props.stockMarket) { + const val = this.props.stockMarket[stockMarketProp]; + if (val instanceof Stock) { + // Skip if there's a filter and the stock isnt in that filter + if ( + this.state.watchlistSymbols.length > 0 && + !this.state.watchlistSymbols.includes(val.symbol) + ) { + continue; } - this.changeDisplayMode = this.changeDisplayMode.bind(this); - this.changeWatchlistFilter = this.changeWatchlistFilter.bind(this); - this.collapseAllTickers = this.collapseAllTickers.bind(this); - this.expandAllTickers = this.expandAllTickers.bind(this); - this.rerender = this.rerender.bind(this); - - this.listRef = React.createRef(); - } - - changeDisplayMode(): void { - if (this.state.tickerDisplayMode === TickerDisplayMode.AllStocks) { - this.setState({ - tickerDisplayMode: TickerDisplayMode.Portfolio, - }); - } else { - this.setState({ - tickerDisplayMode: TickerDisplayMode.AllStocks, - }); - } - } - - changeWatchlistFilter(e: React.ChangeEvent): void { - const watchlist = e.target.value; - const sanitizedWatchlist = watchlist.replace(/\s/g, ''); - - this.setState({ - watchlistFilter: watchlist, - }); - - if (sanitizedWatchlist !== "") { - this.setState({ - watchlistSymbols: sanitizedWatchlist.split(","), - }); - } else { - this.setState({ - watchlistSymbols: [], - }); - } - } - - collapseAllTickers(): void { - const ul = this.listRef.current; - if (ul == null) { return; } - const tickers = ul.getElementsByClassName("accordion-header"); - for (let i = 0; i < tickers.length; ++i) { - const ticker = tickers[i]; - if (!(ticker instanceof HTMLButtonElement)) { - continue; - } - - if (ticker.classList.contains("active")) { - ticker.click(); - } - } - } - - expandAllTickers(): void { - const ul = this.listRef.current; - if (ul == null) { return; } - const tickers = ul.getElementsByClassName("accordion-header"); - for (let i = 0; i < tickers.length; ++i) { - const ticker = tickers[i]; - if (!(ticker instanceof HTMLButtonElement)) { - continue; - } - - if (!ticker.classList.contains("active")) { - ticker.click(); - } - } - } - - rerender(): void { - this.setState((prevState) => { - return { - rerenderFlag: !prevState.rerenderFlag, - } - }); - } - - render(): React.ReactNode { - const tickers: React.ReactElement[] = []; - for (const stockMarketProp in this.props.stockMarket) { - const val = this.props.stockMarket[stockMarketProp]; - if (val instanceof Stock) { - // Skip if there's a filter and the stock isnt in that filter - if (this.state.watchlistSymbols.length > 0 && !this.state.watchlistSymbols.includes(val.symbol)) { - continue; - } - - let orders = this.props.stockMarket.Orders[val.symbol]; - if (orders == null) { - orders = []; - } - - // Skip if we're in portfolio mode and the player doesnt own this or have any active orders - if (this.state.tickerDisplayMode === TickerDisplayMode.Portfolio) { - if (val.playerShares === 0 && val.playerShortShares === 0 && orders.length === 0) { - continue; - } - } - - tickers.push( - , - ) - } + let orders = this.props.stockMarket.Orders[val.symbol]; + if (orders == null) { + orders = []; } - const errorBoundaryProps = { - eventEmitterForReset: this.props.eventEmitterForReset, - id: "StockTickersErrorBoundary", + // Skip if we're in portfolio mode and the player doesnt own this or have any active orders + if (this.state.tickerDisplayMode === TickerDisplayMode.Portfolio) { + if ( + val.playerShares === 0 && + val.playerShortShares === 0 && + orders.length === 0 + ) { + continue; + } } - return ( - - - -
      - {tickers} -
    -
    - ) + tickers.push( + , + ); + } } + + const errorBoundaryProps = { + eventEmitterForReset: this.props.eventEmitterForReset, + id: "StockTickersErrorBoundary", + }; + + return ( + + + +
      + {tickers} +
    +
    + ); + } } diff --git a/src/StockMarket/ui/StockTickersConfig.tsx b/src/StockMarket/ui/StockTickersConfig.tsx index d4c3144b4..792938f35 100644 --- a/src/StockMarket/ui/StockTickersConfig.tsx +++ b/src/StockMarket/ui/StockTickersConfig.tsx @@ -8,64 +8,64 @@ import * as React from "react"; import { StdButton } from "../../ui/React/StdButton"; export enum TickerDisplayMode { - AllStocks, - Portfolio, + AllStocks, + Portfolio, } type IProps = { - changeDisplayMode: () => void; - changeWatchlistFilter: (e: React.ChangeEvent) => void; - collapseAllTickers: () => void; - expandAllTickers: () => void; - tickerDisplayMode: TickerDisplayMode; -} + changeDisplayMode: () => void; + changeWatchlistFilter: (e: React.ChangeEvent) => void; + collapseAllTickers: () => void; + expandAllTickers: () => void; + tickerDisplayMode: TickerDisplayMode; +}; export class StockTickersConfig extends React.Component { - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); + } + + renderDisplayModeButton(): React.ReactNode { + let txt = ""; + let tooltip = ""; + if (this.props.tickerDisplayMode === TickerDisplayMode.Portfolio) { + txt = "Switch to 'All Stocks' Mode"; + tooltip = "Displays all stocks on the WSE"; + } else { + txt = "Switch to 'Portfolio' Mode"; + tooltip = "Displays only the stocks for which you have shares or orders"; } - renderDisplayModeButton(): React.ReactNode { - let txt = ""; - let tooltip = ""; - if (this.props.tickerDisplayMode === TickerDisplayMode.Portfolio) { - txt = "Switch to 'All Stocks' Mode"; - tooltip = "Displays all stocks on the WSE"; - } else { - txt = "Switch to 'Portfolio' Mode"; - tooltip = "Displays only the stocks for which you have shares or orders"; - } + return ( + + ); + } - return ( - - ) - } + render(): React.ReactNode { + return ( +
    + {this.renderDisplayModeButton()} + + - render(): React.ReactNode { - return ( -
    - {this.renderDisplayModeButton()} - - - - -
    - ) - } + +
    + ); + } } diff --git a/src/Terminal.jsx b/src/Terminal.jsx index 379ff677d..e868bba45 100644 --- a/src/Terminal.jsx +++ b/src/Terminal.jsx @@ -1,11 +1,11 @@ import { - evaluateDirectoryPath, - evaluateFilePath, - getFirstParentDirectory, - isInRootDirectory, - isValidDirectoryPath, - removeLeadingSlash, - removeTrailingSlash, + evaluateDirectoryPath, + evaluateFilePath, + getFirstParentDirectory, + isInRootDirectory, + isValidDirectoryPath, + removeLeadingSlash, + removeTrailingSlash, } from "./Terminal/DirectoryHelpers"; import { determineAllPossibilitiesForTabCompletion } from "./Terminal/determineAllPossibilitiesForTabCompletion"; import { TerminalHelpText, HelpTexts } from "./Terminal/HelpText"; @@ -13,36 +13,34 @@ import { tabCompletion } from "./Terminal/tabCompletion"; import { createFconf } from "./Fconf/Fconf"; import { - parseAliasDeclaration, - printAliases, - removeAlias, - substituteAliases, + parseAliasDeclaration, + printAliases, + removeAlias, + substituteAliases, } from "./Alias"; import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; -import { - CodingContractResult, -} from "./CodingContracts"; +import { CodingContractResult } from "./CodingContracts"; import { CONSTANTS } from "./Constants"; import { Programs } from "./Programs/Programs"; import { - executeDarkwebTerminalCommand, - checkIfConnectedToDarkweb, + executeDarkwebTerminalCommand, + checkIfConnectedToDarkweb, } from "./DarkWeb/DarkWeb"; import { Engine } from "./engine"; import { FconfSettings } from "./Fconf/FconfSettings"; import { - calculateHackingChance, - calculateHackingExpGain, - calculatePercentMoneyHacked, - calculateHackingTime, - calculateGrowTime, - calculateWeakenTime, + calculateHackingChance, + calculateHackingExpGain, + calculatePercentMoneyHacked, + calculateHackingTime, + calculateGrowTime, + calculateWeakenTime, } from "./Hacking"; import { HacknetServer } from "./Hacknet/HacknetServer"; import { - iTutorialNextStep, - iTutorialSteps, - ITutorial, + iTutorialNextStep, + iTutorialSteps, + ITutorial, } from "./InteractiveTutorial"; import { showLiterature } from "./Literature/LiteratureHelpers"; import { Message } from "./Message/Message"; @@ -56,19 +54,19 @@ import { RunningScript } from "./Script/RunningScript"; import { compareArrays } from "../utils/helpers/compareArrays"; import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers"; import { - findRunningScript, - findRunningScriptByPid, + findRunningScript, + findRunningScriptByPid, } from "./Script/ScriptHelpers"; import { isScriptFilename } from "./Script/ScriptHelpersTS"; import { AllServers } from "./Server/AllServers"; import { - GetServerByHostname, - getServer, - getServerOnNetwork, + GetServerByHostname, + getServer, + getServerOnNetwork, } from "./Server/ServerHelpers"; import { - SpecialServerIps, - SpecialServerNames, + SpecialServerIps, + SpecialServerNames, } from "./Server/SpecialServerIps"; import { setTimeoutRef } from "./utils/SetTimeoutRef"; import { Page, routing } from "./ui/navigationTracking"; @@ -78,18 +76,18 @@ import { arrayToString } from "../utils/helpers/arrayToString"; import { getTimestamp } from "../utils/helpers/getTimestamp"; import { logBoxCreate } from "../utils/LogBox"; import { - yesNoBoxCreate, - yesNoBoxGetYesButton, - yesNoBoxGetNoButton, - yesNoBoxClose, + yesNoBoxCreate, + yesNoBoxGetYesButton, + yesNoBoxGetNoButton, + yesNoBoxClose, } from "../utils/YesNoBox"; import { - post, - postElement, - postContent, - postError, - hackProgressBarPost, - hackProgressPost, + post, + postElement, + postContent, + postError, + hackProgressBarPost, + hackProgressPost, } from "./ui/postToTerminal"; import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions"; import { Money } from "./ui/React/Money"; @@ -97,2384 +95,2851 @@ import { Money } from "./ui/React/Money"; import autosize from "autosize"; import * as JSZip from "jszip"; import * as FileSaver from "file-saver"; -import * as libarg from 'arg'; +import * as libarg from "arg"; import React from "react"; -import ReactDOM from 'react-dom'; - +import ReactDOM from "react-dom"; function postNetburnerText() { - post("Bitburner v" + CONSTANTS.Version); + post("Bitburner v" + CONSTANTS.Version); } // Helper function that checks if an argument (which is a string) is a valid number function isNumber(str) { - if (typeof str != "string") { return false; } // Only process strings - return !isNaN(str) && !isNaN(parseFloat(str)); + if (typeof str != "string") { + return false; + } // Only process strings + return !isNaN(str) && !isNaN(parseFloat(str)); } function getTerminalInput() { - return document.getElementById("terminal-input-text-box").value; + return document.getElementById("terminal-input-text-box").value; } // Defines key commands in terminal -$(document).keydown(function(event) { - // Terminal - if (routing.isOn(Page.Terminal)) { - var terminalInput = document.getElementById("terminal-input-text-box"); - if (terminalInput != null && !event.ctrlKey && !event.shiftKey && !Terminal.contractOpen) {terminalInput.focus();} - - if (event.keyCode === KEY.ENTER) { - event.preventDefault(); // Prevent newline from being entered in Script Editor - const command = getTerminalInput(); - const dir = Terminal.currDir; - post( - "[" + - (FconfSettings.ENABLE_TIMESTAMPS ? getTimestamp() + " " : "") + - Player.getCurrentServer().hostname + - ` ~${dir}]> ${command}`, - ); - - if (command.length > 0) { - Terminal.resetTerminalInput(); // Clear input first - Terminal.executeCommands(command); - } - } - - if (event.keyCode === KEY.C && event.ctrlKey) { - if (Engine._actionInProgress) { - // Cancel action - post("Cancelling..."); - Engine._actionInProgress = false; - Terminal.finishAction(true); - } else if (FconfSettings.ENABLE_BASH_HOTKEYS) { - // Dont prevent default so it still copies - Terminal.resetTerminalInput(); // Clear Terminal - } - } - - if (event.keyCode === KEY.L && event.ctrlKey) { - event.preventDefault(); - Terminal.executeCommand("clear"); // Clear screen - } - - // Ctrl p same as up arrow - // Ctrl n same as down arrow - - if (event.keyCode === KEY.UPARROW || - (FconfSettings.ENABLE_BASH_HOTKEYS && event.keyCode === KEY.P && event.ctrlKey)) { - if (FconfSettings.ENABLE_BASH_HOTKEYS) {event.preventDefault();} - // Cycle through past commands - if (terminalInput == null) {return;} - var i = Terminal.commandHistoryIndex; - var len = Terminal.commandHistory.length; - - if (len == 0) {return;} - if (i < 0 || i > len) { - Terminal.commandHistoryIndex = len; - } - - if (i != 0) { - --Terminal.commandHistoryIndex; - } - var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex]; - terminalInput.value = prevCommand; - setTimeoutRef(function(){terminalInput.selectionStart = terminalInput.selectionEnd = 10000; }, 10); - } - - if (event.keyCode === KEY.DOWNARROW || - (FconfSettings.ENABLE_BASH_HOTKEYS && event.keyCode === KEY.M && event.ctrlKey)) { - if (FconfSettings.ENABLE_BASH_HOTKEYS) {event.preventDefault();} - // Cycle through past commands - if (terminalInput == null) {return;} - var i = Terminal.commandHistoryIndex; - var len = Terminal.commandHistory.length; - - if (len == 0) {return;} - if (i < 0 || i > len) { - Terminal.commandHistoryIndex = len; - } - - // Latest command, put nothing - if (i == len || i == len-1) { - Terminal.commandHistoryIndex = len; - terminalInput.value = ""; - } else { - ++Terminal.commandHistoryIndex; - var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex]; - terminalInput.value = prevCommand; - } - } - - if (event.keyCode === KEY.TAB) { - event.preventDefault(); - - // Autocomplete - if (terminalInput == null) {return;} - let input = terminalInput.value; - if (input == "") { return; } - - const semiColonIndex = input.lastIndexOf(";"); - if(semiColonIndex !== -1) { - input = input.slice(semiColonIndex + 1); - } - - input = input.trim(); - input = input.replace(/\s\s+/g, ' '); - - const commandArray = input.split(" "); - let index = commandArray.length - 2; - if (index < -1) { index = 0; } - const allPos = determineAllPossibilitiesForTabCompletion(Player, input, index, Terminal.currDir); - if (allPos.length == 0) {return;} - - let arg = ""; - let command = ""; - if (commandArray.length == 0) {return;} - if (commandArray.length == 1) {command = commandArray[0];} - else if (commandArray.length == 2) { - command = commandArray[0]; - arg = commandArray[1]; - } else if (commandArray.length == 3) { - command = commandArray[0] + " " + commandArray[1]; - arg = commandArray[2]; - } else { - arg = commandArray.pop(); - command = commandArray.join(" "); - } - - tabCompletion(command, arg, allPos); - terminalInput.focus(); - } - - // Extra Bash Emulation Hotkeys, must be enabled through .fconf - if (FconfSettings.ENABLE_BASH_HOTKEYS) { - if (event.keyCode === KEY.A && event.ctrlKey) { - event.preventDefault(); - Terminal.moveTextCursor("home"); - } - - if (event.keyCode === KEY.E && event.ctrlKey) { - event.preventDefault(); - Terminal.moveTextCursor("end"); - } - - if (event.keyCode === KEY.B && event.ctrlKey) { - event.preventDefault(); - Terminal.moveTextCursor("prevchar"); - } - - if (event.keyCode === KEY.B && event.altKey) { - event.preventDefault(); - Terminal.moveTextCursor("prevword"); - } - - if (event.keyCode === KEY.F && event.ctrlKey) { - event.preventDefault(); - Terminal.moveTextCursor("nextchar"); - } - - if (event.keyCode === KEY.F && event.altKey) { - event.preventDefault(); - Terminal.moveTextCursor("nextword"); - } - - - if ((event.keyCode === KEY.H || event.keyCode === KEY.D) && event.ctrlKey) { - Terminal.modifyInput("backspace"); - event.preventDefault(); - } - - // TODO AFTER THIS: - // alt + d deletes word after cursor - // ^w deletes word before cursor - // ^k clears line after cursor - // ^u clears line before cursor - } +$(document).keydown(function (event) { + // Terminal + if (routing.isOn(Page.Terminal)) { + var terminalInput = document.getElementById("terminal-input-text-box"); + if ( + terminalInput != null && + !event.ctrlKey && + !event.shiftKey && + !Terminal.contractOpen + ) { + terminalInput.focus(); } + + if (event.keyCode === KEY.ENTER) { + event.preventDefault(); // Prevent newline from being entered in Script Editor + const command = getTerminalInput(); + const dir = Terminal.currDir; + post( + "[" + + (FconfSettings.ENABLE_TIMESTAMPS ? getTimestamp() + " " : "") + + Player.getCurrentServer().hostname + + ` ~${dir}]> ${command}`, + ); + + if (command.length > 0) { + Terminal.resetTerminalInput(); // Clear input first + Terminal.executeCommands(command); + } + } + + if (event.keyCode === KEY.C && event.ctrlKey) { + if (Engine._actionInProgress) { + // Cancel action + post("Cancelling..."); + Engine._actionInProgress = false; + Terminal.finishAction(true); + } else if (FconfSettings.ENABLE_BASH_HOTKEYS) { + // Dont prevent default so it still copies + Terminal.resetTerminalInput(); // Clear Terminal + } + } + + if (event.keyCode === KEY.L && event.ctrlKey) { + event.preventDefault(); + Terminal.executeCommand("clear"); // Clear screen + } + + // Ctrl p same as up arrow + // Ctrl n same as down arrow + + if ( + event.keyCode === KEY.UPARROW || + (FconfSettings.ENABLE_BASH_HOTKEYS && + event.keyCode === KEY.P && + event.ctrlKey) + ) { + if (FconfSettings.ENABLE_BASH_HOTKEYS) { + event.preventDefault(); + } + // Cycle through past commands + if (terminalInput == null) { + return; + } + var i = Terminal.commandHistoryIndex; + var len = Terminal.commandHistory.length; + + if (len == 0) { + return; + } + if (i < 0 || i > len) { + Terminal.commandHistoryIndex = len; + } + + if (i != 0) { + --Terminal.commandHistoryIndex; + } + var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex]; + terminalInput.value = prevCommand; + setTimeoutRef(function () { + terminalInput.selectionStart = terminalInput.selectionEnd = 10000; + }, 10); + } + + if ( + event.keyCode === KEY.DOWNARROW || + (FconfSettings.ENABLE_BASH_HOTKEYS && + event.keyCode === KEY.M && + event.ctrlKey) + ) { + if (FconfSettings.ENABLE_BASH_HOTKEYS) { + event.preventDefault(); + } + // Cycle through past commands + if (terminalInput == null) { + return; + } + var i = Terminal.commandHistoryIndex; + var len = Terminal.commandHistory.length; + + if (len == 0) { + return; + } + if (i < 0 || i > len) { + Terminal.commandHistoryIndex = len; + } + + // Latest command, put nothing + if (i == len || i == len - 1) { + Terminal.commandHistoryIndex = len; + terminalInput.value = ""; + } else { + ++Terminal.commandHistoryIndex; + var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex]; + terminalInput.value = prevCommand; + } + } + + if (event.keyCode === KEY.TAB) { + event.preventDefault(); + + // Autocomplete + if (terminalInput == null) { + return; + } + let input = terminalInput.value; + if (input == "") { + return; + } + + const semiColonIndex = input.lastIndexOf(";"); + if (semiColonIndex !== -1) { + input = input.slice(semiColonIndex + 1); + } + + input = input.trim(); + input = input.replace(/\s\s+/g, " "); + + const commandArray = input.split(" "); + let index = commandArray.length - 2; + if (index < -1) { + index = 0; + } + const allPos = determineAllPossibilitiesForTabCompletion( + Player, + input, + index, + Terminal.currDir, + ); + if (allPos.length == 0) { + return; + } + + let arg = ""; + let command = ""; + if (commandArray.length == 0) { + return; + } + if (commandArray.length == 1) { + command = commandArray[0]; + } else if (commandArray.length == 2) { + command = commandArray[0]; + arg = commandArray[1]; + } else if (commandArray.length == 3) { + command = commandArray[0] + " " + commandArray[1]; + arg = commandArray[2]; + } else { + arg = commandArray.pop(); + command = commandArray.join(" "); + } + + tabCompletion(command, arg, allPos); + terminalInput.focus(); + } + + // Extra Bash Emulation Hotkeys, must be enabled through .fconf + if (FconfSettings.ENABLE_BASH_HOTKEYS) { + if (event.keyCode === KEY.A && event.ctrlKey) { + event.preventDefault(); + Terminal.moveTextCursor("home"); + } + + if (event.keyCode === KEY.E && event.ctrlKey) { + event.preventDefault(); + Terminal.moveTextCursor("end"); + } + + if (event.keyCode === KEY.B && event.ctrlKey) { + event.preventDefault(); + Terminal.moveTextCursor("prevchar"); + } + + if (event.keyCode === KEY.B && event.altKey) { + event.preventDefault(); + Terminal.moveTextCursor("prevword"); + } + + if (event.keyCode === KEY.F && event.ctrlKey) { + event.preventDefault(); + Terminal.moveTextCursor("nextchar"); + } + + if (event.keyCode === KEY.F && event.altKey) { + event.preventDefault(); + Terminal.moveTextCursor("nextword"); + } + + if ( + (event.keyCode === KEY.H || event.keyCode === KEY.D) && + event.ctrlKey + ) { + Terminal.modifyInput("backspace"); + event.preventDefault(); + } + + // TODO AFTER THIS: + // alt + d deletes word after cursor + // ^w deletes word before cursor + // ^k clears line after cursor + // ^u clears line before cursor + } + } }); // Keep terminal in focus -let terminalCtrlPressed = false, shiftKeyPressed = false; -$(document).ready(function() { - if (routing.isOn(Page.Terminal)) { - $('.terminal-input').focus(); - } +let terminalCtrlPressed = false, + shiftKeyPressed = false; +$(document).ready(function () { + if (routing.isOn(Page.Terminal)) { + $(".terminal-input").focus(); + } }); -$(document).keydown(function(e) { - if (routing.isOn(Page.Terminal)) { - if (e.which == KEY.CTRL) { - terminalCtrlPressed = true; - } else if (e.shiftKey) { - shiftKeyPressed = true; - } else if (terminalCtrlPressed || shiftKeyPressed || Terminal.contractOpen) { - // Don't focus - } else { - var inputTextBox = document.getElementById("terminal-input-text-box"); - if (inputTextBox != null) {inputTextBox.focus();} +$(document).keydown(function (e) { + if (routing.isOn(Page.Terminal)) { + if (e.which == KEY.CTRL) { + terminalCtrlPressed = true; + } else if (e.shiftKey) { + shiftKeyPressed = true; + } else if ( + terminalCtrlPressed || + shiftKeyPressed || + Terminal.contractOpen + ) { + // Don't focus + } else { + var inputTextBox = document.getElementById("terminal-input-text-box"); + if (inputTextBox != null) { + inputTextBox.focus(); + } - terminalCtrlPressed = false; - shiftKeyPressed = false; - } + terminalCtrlPressed = false; + shiftKeyPressed = false; } + } }); -$(document).keyup(function(e) { - if (routing.isOn(Page.Terminal)) { - if (e.which == KEY.CTRL) { - terminalCtrlPressed = false; - } - if (e.shiftKey) { - shiftKeyPressed = false; - } +$(document).keyup(function (e) { + if (routing.isOn(Page.Terminal)) { + if (e.which == KEY.CTRL) { + terminalCtrlPressed = false; } + if (e.shiftKey) { + shiftKeyPressed = false; + } + } }); let Terminal = { - // Flags to determine whether the player is currently running a hack or an analyze - hackFlag: false, - backdoorFlag: false, - analyzeFlag: false, - actionStarted: false, - actionTime: 0, + // Flags to determine whether the player is currently running a hack or an analyze + hackFlag: false, + backdoorFlag: false, + analyzeFlag: false, + actionStarted: false, + actionTime: 0, - commandHistory: [], - commandHistoryIndex: 0, + commandHistory: [], + commandHistoryIndex: 0, - // True if a Coding Contract prompt is opened - contractOpen: false, + // True if a Coding Contract prompt is opened + contractOpen: false, - // Full Path of current directory - // Excludes the trailing forward slash - currDir: "/", + // Full Path of current directory + // Excludes the trailing forward slash + currDir: "/", - resetTerminalInput: function(keepInput=false) { - let input = ""; - if(keepInput) { - input = getTerminalInput(); - } - const dir = Terminal.currDir; - if (FconfSettings.WRAP_INPUT) { - document.getElementById("terminal-input-td").innerHTML = - `
    [${Player.getCurrentServer().hostname} ~${dir}]$
    ` + - ` + -
    +
    - -
    + +
    - - - + + +
    $ - -
    + $ + +
    -
    +
    - -
    + +
    -
    +
    - -
    -

    This page displays a list of all of your scripts that are currently running across every machine. It also - provides information about each script's production. The scripts are categorized by the hostname of the servers on which - they are running.

    -

    Total online production of - Active scripts: $0.000 / sec
    - Total online production since last Aug installation: $0.000 - ($0.000 / sec)

    -
      -
    -
    + +
    +

    + This page displays a list of all of your scripts that are currently + running across every machine. It also provides information about each + script's production. The scripts are categorized by the hostname of + the servers on which they are running. +

    +

    + Total online production of Active scripts: + $0.000 / + sec
    + Total online production since last Aug installation: + $0.000 + ($0.000 + / sec) +

    +
      +
      - -
      + +
      -
      +
      - -
      + +

      - This page displays any programs that you are able to create. Writing the code for a program takes time, which - can vary based on how complex the program is. If you are working on creating a program you can cancel - at any time. Your progress will be saved and you can continue later. + This page displays any programs that you are able to create. Writing + the code for a program takes time, which can vary based on how complex + the program is. If you are working on creating a program you can + cancel at any time. Your progress will be saved and you can continue + later.

        -
        +
        - -
        + +
        - -
        + +
        - -
        + +
        - -
        - - -
        + +
        - - + +
        - -
        -
        + + -
        -
        + +
        -
        +
        + +
        -
        +
        -
        +
        -
        +
        - - - - - - + + + + + +
        + + +
        +

        + + + +
        + + +
        +
        +
        + + +
        + + +
        +
        +

        + + + +
        +
        + + +
        +
        +
        + +
        +
        + + +
        +
        +
        + + +
        +

        +
        + + +
        -
        -
        Loading Bitburner...
        -
        - -

        If the game fails to load, consider killing all scripts

        -
        +
        +
        Loading Bitburner...
        +
        + +

        + If the game fails to load, consider + killing all scripts +

        +
        - - - - - + + + + diff --git a/src/types.ts b/src/types.ts index 09c071767..69a9fa113 100644 --- a/src/types.ts +++ b/src/types.ts @@ -41,8 +41,8 @@ export interface ISelfLoading { * and an optional message */ export interface IReturnStatus { - res: boolean; - msg?: string; + res: boolean; + msg?: string; } /** @@ -51,18 +51,18 @@ export interface IReturnStatus { * It is up to the implementor to ensure max > min. */ export interface IMinMaxRange { - /** - * Value by which the bounds are to be divided for the final range - */ - divisor?: number; - - /** - * The maximum bound of the range. - */ - max: number; + /** + * Value by which the bounds are to be divided for the final range + */ + divisor?: number; - /** - * The minimum bound of the range. - */ - min: number; + /** + * The maximum bound of the range. + */ + max: number; + + /** + * The minimum bound of the range. + */ + min: number; } diff --git a/src/ui/ActiveScripts/Root.tsx b/src/ui/ActiveScripts/Root.tsx index d18dec637..27cfff798 100644 --- a/src/ui/ActiveScripts/Root.tsx +++ b/src/ui/ActiveScripts/Root.tsx @@ -11,28 +11,28 @@ import { WorkerScript } from "../../Netscript/WorkerScript"; import { IPlayer } from "../../PersonObjects/IPlayer"; type IProps = { - p: IPlayer; - workerScripts: Map; -} + p: IPlayer; + workerScripts: Map; +}; export class ActiveScriptsRoot extends React.Component { - constructor(props: IProps) { - super(props); - } + constructor(props: IProps) { + super(props); + } - render(): React.ReactNode { - return ( - <> -

        - This page displays a list of all of your scripts that are currently - running across every machine. It also provides information about each - script's production. The scripts are categorized by the hostname of - the servers on which they are running. -

        + render(): React.ReactNode { + return ( + <> +

        + This page displays a list of all of your scripts that are currently + running across every machine. It also provides information about each + script's production. The scripts are categorized by the hostname of + the servers on which they are running. +

        - - - - ) - } + + + + ); + } } diff --git a/src/ui/ActiveScripts/ScriptProduction.tsx b/src/ui/ActiveScripts/ScriptProduction.tsx index eac508871..c43358c7d 100644 --- a/src/ui/ActiveScripts/ScriptProduction.tsx +++ b/src/ui/ActiveScripts/ScriptProduction.tsx @@ -9,37 +9,42 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; import { Money } from "../React/Money"; type IProps = { - p: IPlayer; - workerScripts: Map; -} + p: IPlayer; + workerScripts: Map; +}; export function ScriptProduction(props: IProps): React.ReactElement { - const prodRateSinceLastAug = props.p.scriptProdSinceLastAug / (props.p.playtimeSinceLastAug / 1000); + const prodRateSinceLastAug = + props.p.scriptProdSinceLastAug / (props.p.playtimeSinceLastAug / 1000); - let onlineProduction = 0; - for (const ws of props.workerScripts.values()) { - onlineProduction += (ws.scriptRef.onlineMoneyMade / ws.scriptRef.onlineRunningTime); - } + let onlineProduction = 0; + for (const ws of props.workerScripts.values()) { + onlineProduction += + ws.scriptRef.onlineMoneyMade / ws.scriptRef.onlineRunningTime; + } - return ( -

        - Total online production of Active scripts:  - - - - / sec -
        - - Total online production since last Aug installation:  - - - - -  ( - - - / sec - ) -

        - ) + return ( +

        + Total online production of Active scripts:  + + + + {" "} + / sec + +
        + Total online production since last Aug installation:  + + + +  ( + + + + {" "} + / sec + + ) +

        + ); } diff --git a/src/ui/ActiveScripts/ServerAccordion.tsx b/src/ui/ActiveScripts/ServerAccordion.tsx index cc6060b1d..e4e87393a 100644 --- a/src/ui/ActiveScripts/ServerAccordion.tsx +++ b/src/ui/ActiveScripts/ServerAccordion.tsx @@ -14,37 +14,37 @@ import { WorkerScript } from "../../Netscript/WorkerScript"; import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; type IProps = { - server: BaseServer; - workerScripts: WorkerScript[]; -} + server: BaseServer; + workerScripts: WorkerScript[]; +}; export function ServerAccordion(props: IProps): React.ReactElement { - const server = props.server; + const server = props.server; - // Accordion's header text - // TODO: calculate the longest hostname length rather than hard coding it - const longestHostnameLength = 18; - const paddedName = `${server.hostname}${" ".repeat(longestHostnameLength)}`.slice(0, Math.max(server.hostname.length, longestHostnameLength)); - const barOptions = { - progress: server.ramUsed / server.maxRam, - totalTicks: 30, - }; - const headerTxt = `${paddedName} ${createProgressBarText(barOptions)}`; - - const scripts = props.workerScripts.map((ws) => { - return ( - - ) - }); + // Accordion's header text + // TODO: calculate the longest hostname length rather than hard coding it + const longestHostnameLength = 18; + const paddedName = `${server.hostname}${" ".repeat( + longestHostnameLength, + )}`.slice(0, Math.max(server.hostname.length, longestHostnameLength)); + const barOptions = { + progress: server.ramUsed / server.maxRam, + totalTicks: 30, + }; + const headerTxt = `${paddedName} ${createProgressBarText(barOptions)}`; + const scripts = props.workerScripts.map((ws) => { return ( - {headerTxt} - } - panelContent={ - - } - /> - ) + + ); + }); + + return ( + {headerTxt}} + panelContent={ + + } + /> + ); } diff --git a/src/ui/ActiveScripts/ServerAccordionContent.tsx b/src/ui/ActiveScripts/ServerAccordionContent.tsx index 307bb9d3b..eed612af0 100644 --- a/src/ui/ActiveScripts/ServerAccordionContent.tsx +++ b/src/ui/ActiveScripts/ServerAccordionContent.tsx @@ -6,78 +6,73 @@ import { AccordionButton } from "../React/AccordionButton"; const pageSize = 20; interface IProps { - workerScripts: WorkerScript[]; - + workerScripts: WorkerScript[]; } export function ServerAccordionContent(props: IProps): React.ReactElement { - if(props.workerScripts.length > pageSize) { - return + if (props.workerScripts.length > pageSize) { + return ( + + ); + } + + const scripts = props.workerScripts.map((ws) => { + return ( + + ); + }); + + return
          {scripts}
        ; +} + +export function ServerAccordionContentPaginated( + props: IProps, +): React.ReactElement { + const [page, setPage] = useState(0); + const scripts: React.ReactElement[] = []; + const maxPage = Math.ceil(props.workerScripts.length / pageSize); + const maxScript = Math.min((page + 1) * pageSize, props.workerScripts.length); + for (let i = page * pageSize; i < maxScript; i++) { + const ws = props.workerScripts[i]; + scripts.push( + , + ); + } + + function capPage(page: number): number { + if (page < 0) { + page = 0; } - const scripts = props.workerScripts.map((ws) => { - return ( - - ) + if (maxPage - 1 < page) { + page = maxPage - 1; + } + + return page; + } + + // in case we're on an invalid page number because scripts were killed. + const capped = capPage(page); + if (capped !== page) setPage(capped); + + function changePage(n: number): void { + setPage((newPage) => { + newPage += n; + newPage = Math.round(newPage); + return capPage(newPage); }); + } - return (
          {scripts}
        ); + return ( + <> +
          {scripts}
        + changePage(-1e99)} text="<<" /> + changePage(-1)} text="<" /> + + {page + 1} / {maxPage} + + changePage(1)} text=">" /> + changePage(1e99)} text=">>" /> + + ); } - - -export function ServerAccordionContentPaginated(props: IProps): React.ReactElement { - const [page, setPage] = useState(0); - const scripts: React.ReactElement[] = []; - const maxPage = Math.ceil(props.workerScripts.length/pageSize); - const maxScript = Math.min((page+1)*pageSize, props.workerScripts.length); - for(let i = page*pageSize; i < maxScript; i++) { - const ws = props.workerScripts[i]; - scripts.push() - } - - function capPage(page: number): number { - if(page < 0) { - page = 0; - } - - if(maxPage-1 < page) { - page = maxPage-1; - } - - return page; - } - - // in case we're on an invalid page number because scripts were killed. - const capped = capPage(page); - if(capped !== page) - setPage(capped); - - function changePage(n: number): void { - setPage(newPage => { - newPage += n; - newPage = Math.round(newPage); - return capPage(newPage); - }) - } - - return (<>
          {scripts}
        - changePage(-1e99)} - text="<<" - /> - changePage(-1)} - text="<" - /> - {page+1} / {maxPage} - changePage(1)} - text=">" - /> - changePage(1e99)} - text=">>" - /> - ); -} - diff --git a/src/ui/ActiveScripts/ServerAccordions.tsx b/src/ui/ActiveScripts/ServerAccordions.tsx index 6368eef89..567cba023 100644 --- a/src/ui/ActiveScripts/ServerAccordions.tsx +++ b/src/ui/ActiveScripts/ServerAccordions.tsx @@ -13,97 +13,96 @@ import { BaseServer } from "../../Server/BaseServer"; // Map of server hostname -> all workerscripts on that server for all active scripts interface IServerData { - server: BaseServer; - workerScripts: WorkerScript[]; + server: BaseServer; + workerScripts: WorkerScript[]; } interface IServerToScriptsMap { - [key: string]: IServerData; + [key: string]: IServerData; } type IProps = { - workerScripts: Map; + workerScripts: Map; }; type IState = { - rerenderFlag: boolean; -} - + rerenderFlag: boolean; +}; const subscriberId = "ActiveScriptsUI"; export class ServerAccordions extends React.Component { - serverToScriptMap: IServerToScriptsMap = {}; + serverToScriptMap: IServerToScriptsMap = {}; - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.state = { - rerenderFlag: false, - } + this.state = { + rerenderFlag: false, + }; - this.updateServerToScriptsMap(); + this.updateServerToScriptsMap(); - this.rerender = this.rerender.bind(this); + this.rerender = this.rerender.bind(this); + } + + componentDidMount(): void { + WorkerScriptStartStopEventEmitter.addSubscriber({ + cb: this.rerender, + id: subscriberId, + }); + } + + componentWillUnmount(): void { + WorkerScriptStartStopEventEmitter.removeSubscriber(subscriberId); + } + + updateServerToScriptsMap(): void { + const map: IServerToScriptsMap = {}; + + for (const ws of this.props.workerScripts.values()) { + const server = getServer(ws.serverIp); + if (server == null) { + console.warn(`WorkerScript has invalid IP address: ${ws.serverIp}`); + continue; + } + + if (map[server.hostname] == null) { + map[server.hostname] = { + server: server, + workerScripts: [], + }; + } + + map[server.hostname].workerScripts.push(ws); } - componentDidMount(): void { - WorkerScriptStartStopEventEmitter.addSubscriber({ - cb: this.rerender, - id: subscriberId, - }) - } + this.serverToScriptMap = map; + } - componentWillUnmount(): void { - WorkerScriptStartStopEventEmitter.removeSubscriber(subscriberId); - } + rerender(): void { + this.updateServerToScriptsMap(); + this.setState((prevState) => { + return { rerenderFlag: !prevState.rerenderFlag }; + }); + } - updateServerToScriptsMap(): void { - const map: IServerToScriptsMap = {}; + render(): React.ReactNode { + const elems = Object.keys(this.serverToScriptMap).map((serverName) => { + const data = this.serverToScriptMap[serverName]; + return ( + + ); + }); - for (const ws of this.props.workerScripts.values()) { - const server = getServer(ws.serverIp); - if (server == null) { - console.warn(`WorkerScript has invalid IP address: ${ws.serverIp}`); - continue; - } - - if (map[server.hostname] == null) { - map[server.hostname] = { - server: server, - workerScripts: [], - }; - } - - map[server.hostname].workerScripts.push(ws); - } - - this.serverToScriptMap = map; - } - - rerender(): void { - this.updateServerToScriptsMap(); - this.setState((prevState) => { - return { rerenderFlag: !prevState.rerenderFlag } - }); - } - - render(): React.ReactNode { - const elems = Object.keys(this.serverToScriptMap).map((serverName) => { - const data = this.serverToScriptMap[serverName]; - return ( - - ) - }); - - return ( -
          - {elems} -
        - ) - } + return ( +
          + {elems} +
        + ); + } } diff --git a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx index 5bb690bd1..babf1d783 100644 --- a/src/ui/ActiveScripts/WorkerScriptAccordion.tsx +++ b/src/ui/ActiveScripts/WorkerScriptAccordion.tsx @@ -19,59 +19,96 @@ import { arrayToString } from "../../../utils/helpers/arrayToString"; import { Money } from "../React/Money"; type IProps = { - workerScript: WorkerScript; -} + workerScript: WorkerScript; +}; export function WorkerScriptAccordion(props: IProps): React.ReactElement { - const workerScript = props.workerScript; - const scriptRef = workerScript.scriptRef; + const workerScript = props.workerScript; + const scriptRef = workerScript.scriptRef; - const logClickHandler = logBoxCreate.bind(null, scriptRef); - const killScript = killWorkerScript.bind(null, scriptRef as any, scriptRef.server); + const logClickHandler = logBoxCreate.bind(null, scriptRef); + const killScript = killWorkerScript.bind( + null, + scriptRef as any, + scriptRef.server, + ); - function killScriptClickHandler(): void { - killScript(); - dialogBoxCreate("Killing script"); - } + function killScriptClickHandler(): void { + killScript(); + dialogBoxCreate("Killing script"); + } - // Calculations for script stats - const onlineMps = scriptRef.onlineMoneyMade / scriptRef.onlineRunningTime; - const onlineEps = scriptRef.onlineExpGained / scriptRef.onlineRunningTime; - const offlineMps = scriptRef.offlineMoneyMade / scriptRef.offlineRunningTime; - const offlineEps = scriptRef.offlineExpGained / scriptRef.offlineRunningTime; + // Calculations for script stats + const onlineMps = scriptRef.onlineMoneyMade / scriptRef.onlineRunningTime; + const onlineEps = scriptRef.onlineExpGained / scriptRef.onlineRunningTime; + const offlineMps = scriptRef.offlineMoneyMade / scriptRef.offlineRunningTime; + const offlineEps = scriptRef.offlineExpGained / scriptRef.offlineRunningTime; - return ( - {props.workerScript.name} - } - panelClass="active-scripts-script-panel" - panelContent={ - <> -
        Threads: {numeralWrapper.formatThreads(props.workerScript.scriptRef.threads)}
        -
        Args: {arrayToString(props.workerScript.args)}
        -
        Online Time: {convertTimeMsToTimeElapsedString(scriptRef.onlineRunningTime * 1e3)}
        -
        Offline Time: {convertTimeMsToTimeElapsedString(scriptRef.offlineRunningTime * 1e3)}
        -
        Total online production: 
        -
        {(Array(26).join(" ") + numeralWrapper.formatExp(scriptRef.onlineExpGained) + " hacking exp")}
        -
        Online production rate:  / second
        -
        {(Array(25).join(" ") + numeralWrapper.formatExp(onlineEps) + " hacking exp / second")}
        -
        Total offline production: 
        -
        {(Array(27).join(" ") + numeralWrapper.formatExp(scriptRef.offlineExpGained) + " hacking exp")}
        -
        Offline production rate:  / second
        -
        {(Array(26).join(" ") + numeralWrapper.formatExp(offlineEps) +  " hacking exp / second")}
        + return ( + {props.workerScript.name}} + panelClass="active-scripts-script-panel" + panelContent={ + <> +
        +            Threads:{" "}
        +            {numeralWrapper.formatThreads(props.workerScript.scriptRef.threads)}
        +          
        +
        Args: {arrayToString(props.workerScript.args)}
        +
        +            Online Time:{" "}
        +            {convertTimeMsToTimeElapsedString(
        +              scriptRef.onlineRunningTime * 1e3,
        +            )}
        +          
        +
        +            Offline Time:{" "}
        +            {convertTimeMsToTimeElapsedString(
        +              scriptRef.offlineRunningTime * 1e3,
        +            )}
        +          
        +
        +            Total online production: 
        +          
        +
        +            {Array(26).join(" ") +
        +              numeralWrapper.formatExp(scriptRef.onlineExpGained) +
        +              " hacking exp"}
        +          
        +
        +            Online production rate:  / second
        +          
        +
        +            {Array(25).join(" ") +
        +              numeralWrapper.formatExp(onlineEps) +
        +              " hacking exp / second"}
        +          
        +
        +            Total offline production:{" "}
        +            
        +          
        +
        +            {Array(27).join(" ") +
        +              numeralWrapper.formatExp(scriptRef.offlineExpGained) +
        +              " hacking exp"}
        +          
        +
        +            Offline production rate:  / second
        +          
        +
        +            {Array(26).join(" ") +
        +              numeralWrapper.formatExp(offlineEps) +
        +              " hacking exp / second"}
        +          
        - - - - } - /> - ) + + + + } + /> + ); } diff --git a/src/ui/CharacterInfo.tsx b/src/ui/CharacterInfo.tsx index 40a452e70..3097fba21 100644 --- a/src/ui/CharacterInfo.tsx +++ b/src/ui/CharacterInfo.tsx @@ -14,249 +14,484 @@ import { StatsTable } from "./React/StatsTable"; import { Money } from "./React/Money"; export function CharacterInfo(p: IPlayer): React.ReactElement { - function LastEmployer(): React.ReactElement { - if (p.companyName) { - return <>Employer at which you last worked: {p.companyName}
        ; - } - return <>; - } - function LastJob(): React.ReactElement { - if (p.companyName !== "") { - return <>Job you last worked: {p.jobs[p.companyName]}
        ; - } - return <>; - } - function Employers(): React.ReactElement { - if (p.jobs && Object.keys(p.jobs).length !== 0) - return <> - All Employers:
        -
          - {Object.keys(p.jobs).map(j =>
        • * {j}
        • )} -


        - - return <>; - } - - function Hacknet(): React.ReactElement { - // Can't import HacknetHelpers for some reason. - if(!(p.bitNodeN === 9 || SourceFileFlags[9] > 0)) { - return <>{`Hacknet Nodes owned: ${p.hacknetNodes.length}`}
        - } else { - return <>{`Hacknet Servers owned: ${p.hacknetNodes.length} / ${HacknetServerConstants.MaxServers}`}
        - } - } - - function convertMoneySourceTrackerToString(src: MoneySourceTracker): React.ReactElement { - const parts: any[][] = [[`Total:`, ]]; - if (src.bladeburner) { parts.push([`Bladeburner:`, ]) } - if (src.codingcontract) { parts.push([`Coding Contracts:`, ]) } - if (src.work) { parts.push([`Company Work:`, ]) } - if (src.class) { parts.push([`Class:`, ]) } - if (src.corporation) { parts.push([`Corporation:`, ]) } - if (src.crime) { parts.push([`Crimes:`, ]) } - if (src.gang) { parts.push([`Gang:`, ]) } - if (src.hacking) { parts.push([`Hacking:`, ]) } - if (src.hacknetnode) { parts.push([`Hacknet Nodes:`, ]) } - if (src.hospitalization) { parts.push([`Hospitalization:`, ]) } - if (src.infiltration) { parts.push([`Infiltration:`, ]) } - if (src.stock) { parts.push([`Stock Market:`, ]) } - if (src.casino) { parts.push([`Casino:`, ]) } - if (src.sleeves) { parts.push([`Sleeves:`, ]) } - - return StatsTable(parts); - } - - function openMoneyModal(): void { - let content = (<> - Money earned since you last installed Augmentations:
        - {convertMoneySourceTrackerToString(p.moneySourceA)} - ); - if (p.sourceFiles.length !== 0) { - content = (<>{content}

        Money earned in this BitNode:
        - {convertMoneySourceTrackerToString(p.moneySourceB)}); - } - - dialogBoxCreate(content, false); - } - - function Intelligence(): React.ReactElement { - if (p.intelligence > 0 && (p.bitNodeN === 5 || SourceFileFlags[5] > 0)) { - return - Intelligence: - {numeralWrapper.formatSkill(p.intelligence)} - ; - } - return <>; - } - - function MultiplierTable(props: any): React.ReactElement { - function bn5Stat(r: any): JSX.Element { - if(SourceFileFlags[5] > 0 && r.length > 2 && r[1] != r[2]) { - return ({numeralWrapper.formatPercentage(r[2])}) - } - return <>; - } - return <> - - - {props.rows.map((r: any) => - - - {bn5Stat(r)} - )} - -
        {`${r[0]} multiplier:`}{numeralWrapper.formatPercentage(r[1])}
        + function LastEmployer(): React.ReactElement { + if (p.companyName) { + return ( + <> + Employer at which you last worked: {p.companyName} +
        + ); + } + return <>; + } + function LastJob(): React.ReactElement { + if (p.companyName !== "") { + return ( + <> + Job you last worked: {p.jobs[p.companyName]} +
        + + ); + } + return <>; + } + function Employers(): React.ReactElement { + if (p.jobs && Object.keys(p.jobs).length !== 0) + return ( + <> + All Employers: +
        +
          + {Object.keys(p.jobs).map((j) => ( +
        • * {j}
        • + ))} +
        +
        +
        + + ); + return <>; + } + + function Hacknet(): React.ReactElement { + // Can't import HacknetHelpers for some reason. + if (!(p.bitNodeN === 9 || SourceFileFlags[9] > 0)) { + return ( + <> + {`Hacknet Nodes owned: ${p.hacknetNodes.length}`} +
        + + ); + } else { + return ( + <> + {`Hacknet Servers owned: ${p.hacknetNodes.length} / ${HacknetServerConstants.MaxServers}`} +
        + + ); + } + } + + function convertMoneySourceTrackerToString( + src: MoneySourceTracker, + ): React.ReactElement { + const parts: any[][] = [[`Total:`, ]]; + if (src.bladeburner) { + parts.push([`Bladeburner:`, ]); + } + if (src.codingcontract) { + parts.push([`Coding Contracts:`, ]); + } + if (src.work) { + parts.push([`Company Work:`, ]); + } + if (src.class) { + parts.push([`Class:`, ]); + } + if (src.corporation) { + parts.push([`Corporation:`, ]); + } + if (src.crime) { + parts.push([`Crimes:`, ]); + } + if (src.gang) { + parts.push([`Gang:`, ]); + } + if (src.hacking) { + parts.push([`Hacking:`, ]); + } + if (src.hacknetnode) { + parts.push([`Hacknet Nodes:`, ]); + } + if (src.hospitalization) { + parts.push([`Hospitalization:`, ]); + } + if (src.infiltration) { + parts.push([`Infiltration:`, ]); + } + if (src.stock) { + parts.push([`Stock Market:`, ]); + } + if (src.casino) { + parts.push([`Casino:`, ]); + } + if (src.sleeves) { + parts.push([`Sleeves:`, ]); } - function BladeburnerMults(): React.ReactElement { - if(!p.canAccessBladeburner()) return (<>); - return (<> -
        - ); + return StatsTable(parts); + } + + function openMoneyModal(): void { + let content = ( + <> + Money earned since you last installed Augmentations: +
        + {convertMoneySourceTrackerToString(p.moneySourceA)} + + ); + if (p.sourceFiles.length !== 0) { + content = ( + <> + {content} +
        +
        + Money earned in this BitNode: +
        + {convertMoneySourceTrackerToString(p.moneySourceB)} + + ); } - function CurrentBitNode(): React.ReactElement { - if(p.sourceFiles.length > 0) { + dialogBoxCreate(content, false); + } - const index = "BitNode" + p.bitNodeN; - return <> - Current BitNode: {p.bitNodeN} ({BitNodes[index].name})

        -
        - {BitNodes[index].info.split("
        ").map((t, i) =>
        - {t}
        -
        )} -
        - - } - - return <> + function Intelligence(): React.ReactElement { + if (p.intelligence > 0 && (p.bitNodeN === 5 || SourceFileFlags[5] > 0)) { + return ( + + Intelligence: + + {numeralWrapper.formatSkill(p.intelligence)} + + + ); } + return <>; + } - const timeRows = [ - ['Time played since last Augmentation:', convertTimeMsToTimeElapsedString(p.playtimeSinceLastAug)], - ] - if(p.sourceFiles.length > 0) { - timeRows.push(['Time played since last Bitnode destroyed:', convertTimeMsToTimeElapsedString(p.playtimeSinceLastBitnode)]); + function MultiplierTable(props: any): React.ReactElement { + function bn5Stat(r: any): JSX.Element { + if (SourceFileFlags[5] > 0 && r.length > 2 && r[1] != r[2]) { + return ( + + {" "} + ({numeralWrapper.formatPercentage(r[2])}) + + ); + } + return <>; } - timeRows.push(['Total Time played:', convertTimeMsToTimeElapsedString(p.totalPlaytime)]) - return ( -
        -            General
        -            

        - Current City: {p.city}
        - - - - Money: -

        - Stats - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        Hacking:{numeralWrapper.formatSkill(p.hacking_skill)}({numeralWrapper.formatExp(p.hacking_exp)} exp)
        Strength:{numeralWrapper.formatSkill(p.strength)}({numeralWrapper.formatExp(p.strength_exp)} exp)
        Defense:{numeralWrapper.formatSkill(p.defense)}({numeralWrapper.formatExp(p.defense_exp)} exp)
        Dexterity:{numeralWrapper.formatSkill(p.dexterity)}({numeralWrapper.formatExp(p.dexterity_exp)} exp)
        Agility:{numeralWrapper.formatSkill(p.agility)}({numeralWrapper.formatExp(p.agility_exp)} exp)
        Charisma:{numeralWrapper.formatSkill(p.charisma)}({numeralWrapper.formatExp(p.charisma_exp)} exp)
        -
        -
        -
        + <> + + + {props.rows.map((r: any) => ( + + + + {bn5Stat(r)} + + ))} + +
        {`${r[0]} multiplier:`} + {numeralWrapper.formatPercentage(r[1])} +
        + + ); + } -
        + function BladeburnerMults(): React.ReactElement { + if (!p.canAccessBladeburner()) return <>; + return ( + <> + +
        + + ); + } -
        + function CurrentBitNode(): React.ReactElement { + if (p.sourceFiles.length > 0) { + const index = "BitNode" + p.bitNodeN; + return ( + <> + + Current BitNode: {p.bitNodeN} ({BitNodes[index].name}) + +
        +
        +
        + {BitNodes[index].info.split("
        ").map((t, i) => ( +
        + + {t} + +
        +
        + ))} +
        + + ); + } -
        + return <>; + } -
        + const timeRows = [ + [ + "Time played since last Augmentation:", + convertTimeMsToTimeElapsedString(p.playtimeSinceLastAug), + ], + ]; + if (p.sourceFiles.length > 0) { + timeRows.push([ + "Time played since last Bitnode destroyed:", + convertTimeMsToTimeElapsedString(p.playtimeSinceLastBitnode), + ]); + } + timeRows.push([ + "Total Time played:", + convertTimeMsToTimeElapsedString(p.totalPlaytime), + ]); -
        + return ( +
        +      General
        +      
        +
        + Current City: {p.city} +
        + + + + + Money: + + +
        +
        + Stats + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        Hacking: + {numeralWrapper.formatSkill(p.hacking_skill)} + + ({numeralWrapper.formatExp(p.hacking_exp)} exp) +
        Strength: + {numeralWrapper.formatSkill(p.strength)} + + ({numeralWrapper.formatExp(p.strength_exp)} exp) +
        Defense: + {numeralWrapper.formatSkill(p.defense)} + + ({numeralWrapper.formatExp(p.defense_exp)} exp) +
        Dexterity: + {numeralWrapper.formatSkill(p.dexterity)} + + ({numeralWrapper.formatExp(p.dexterity_exp)} exp) +
        Agility: + {numeralWrapper.formatSkill(p.agility)} + + ({numeralWrapper.formatExp(p.agility_exp)} exp) +
        Charisma: + {numeralWrapper.formatSkill(p.charisma)} + + ({numeralWrapper.formatExp(p.charisma_exp)} exp) +
        +
        + +
        + +
        -
        + +
        -
        + +
        -
        + +
        -
        + +
        - Misc.

        - {`Servers owned: ${p.purchasedServers.length} / ${getPurchaseServerLimit()}`}
        - - {`Augmentations installed: ${p.augmentations.length}`}

        - {StatsTable(timeRows)} -
        - -
        - ) -} \ No newline at end of file + +
        + + +
        + + +
        + + +
        + + +
        + + Misc. +
        +
        + {`Servers owned: ${ + p.purchasedServers.length + } / ${getPurchaseServerLimit()}`} +
        + + {`Augmentations installed: ${p.augmentations.length}`} +
        +
        + {StatsTable(timeRows)} +
        + +
        + ); +} diff --git a/src/ui/MainMenu/Headers.ts b/src/ui/MainMenu/Headers.ts index 6e076285f..5c8db3e66 100644 --- a/src/ui/MainMenu/Headers.ts +++ b/src/ui/MainMenu/Headers.ts @@ -3,156 +3,208 @@ import { MainMenuLinks } from "./Links"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IMainMenuHeaders { - Hacking: HTMLElement | null; - Character: HTMLElement | null; - World: HTMLElement | null; - Help: HTMLElement | null; + Hacking: HTMLElement | null; + Character: HTMLElement | null; + World: HTMLElement | null; + Help: HTMLElement | null; } export const MainMenuHeaders: IMainMenuHeaders = { - Hacking: null, - Character: null, - World: null, - Help: null, -} + Hacking: null, + Character: null, + World: null, + Help: null, +}; // Implements collapsible toggle feature when a header is clicked -function toggleHeader(open: boolean, elems: HTMLElement[], links: HTMLElement[]): void { - for (let i = 0; i < elems.length; ++i) { - if (open) { - elems[i].style.opacity = "1"; - elems[i].style.maxHeight = elems[i].scrollHeight + "px"; - } else { - elems[i].style.opacity = "0"; - elems[i].style.maxHeight = ""; - } +function toggleHeader( + open: boolean, + elems: HTMLElement[], + links: HTMLElement[], +): void { + for (let i = 0; i < elems.length; ++i) { + if (open) { + elems[i].style.opacity = "1"; + elems[i].style.maxHeight = elems[i].scrollHeight + "px"; + } else { + elems[i].style.opacity = "0"; + elems[i].style.maxHeight = ""; } + } - for (let i = 0; i < links.length; ++i) { - if (open) { - links[i].style.opacity = "1"; - links[i].style.maxHeight = links[i].scrollHeight + "px"; - links[i].style.pointerEvents = "auto"; - } else { - links[i].style.opacity = "0"; - links[i].style.maxHeight = ""; - links[i].style.pointerEvents = "none"; - } + for (let i = 0; i < links.length; ++i) { + if (open) { + links[i].style.opacity = "1"; + links[i].style.maxHeight = links[i].scrollHeight + "px"; + links[i].style.pointerEvents = "auto"; + } else { + links[i].style.opacity = "0"; + links[i].style.maxHeight = ""; + links[i].style.pointerEvents = "none"; } + } } export function initializeMainMenuHeaders(p: IPlayer, dev = false): boolean { - function safeGetElement(id: string): HTMLElement { - const elem: HTMLElement | null = document.getElementById(id); - if (elem == null) { - throw new Error(`Failed to find element with id ${id} in initializeMainMenuHeaders()`); - } - - return elem; + function safeGetElement(id: string): HTMLElement { + const elem: HTMLElement | null = document.getElementById(id); + if (elem == null) { + throw new Error( + `Failed to find element with id ${id} in initializeMainMenuHeaders()`, + ); } - try { - // Get references to the DOM elements - MainMenuHeaders.Hacking = safeGetElement("hacking-menu-header"); - MainMenuHeaders.Character = safeGetElement("character-menu-header"); - MainMenuHeaders.World = safeGetElement("world-menu-header"); - MainMenuHeaders.Help = safeGetElement("help-menu-header"); + return elem; + } - // Set click handlers to turn the headers into collapsibles - MainMenuHeaders.Hacking.onclick = function() { - const terminal: HTMLElement = safeGetElement("terminal-tab"); - const createScript: HTMLElement = safeGetElement("create-script-tab"); - const activeScripts: HTMLElement = safeGetElement("active-scripts-tab"); - const createProgram: HTMLElement = safeGetElement("create-program-tab"); - const createProgramNot: HTMLElement = safeGetElement("create-program-notification"); + try { + // Get references to the DOM elements + MainMenuHeaders.Hacking = safeGetElement("hacking-menu-header"); + MainMenuHeaders.Character = safeGetElement("character-menu-header"); + MainMenuHeaders.World = safeGetElement("world-menu-header"); + MainMenuHeaders.Help = safeGetElement("help-menu-header"); - createProgram.style.display = p.firstProgramAvailable ? "list-item" : "none"; + // Set click handlers to turn the headers into collapsibles + MainMenuHeaders.Hacking.onclick = function () { + const terminal: HTMLElement = safeGetElement("terminal-tab"); + const createScript: HTMLElement = safeGetElement("create-script-tab"); + const activeScripts: HTMLElement = safeGetElement("active-scripts-tab"); + const createProgram: HTMLElement = safeGetElement("create-program-tab"); + const createProgramNot: HTMLElement = safeGetElement( + "create-program-notification", + ); - (this as any).classList.toggle("opened"); + createProgram.style.display = p.firstProgramAvailable + ? "list-item" + : "none"; - const elems: HTMLElement[] = [terminal, createScript, activeScripts, createProgram]; - const links: HTMLElement[] = [MainMenuLinks.Terminal, MainMenuLinks.ScriptEditor, MainMenuLinks.ActiveScripts, MainMenuLinks.CreateProgram]; - if (terminal.style.maxHeight) { - toggleHeader(false, elems, links); - createProgramNot.style.display = "none"; - } else { - toggleHeader(true, elems, links); - createProgramNot.style.display = "block" - } - } + (this as any).classList.toggle("opened"); - MainMenuHeaders.Character.onclick = function() { - const stats: HTMLElement = safeGetElement("stats-tab"); - const factions: HTMLElement = safeGetElement("factions-tab"); - const augmentations: HTMLElement = safeGetElement("augmentations-tab"); - const hacknetnodes: HTMLElement = safeGetElement("hacknet-nodes-tab"); - const sleeves: HTMLElement = safeGetElement("sleeves-tab"); + const elems: HTMLElement[] = [ + terminal, + createScript, + activeScripts, + createProgram, + ]; + const links: HTMLElement[] = [ + MainMenuLinks.Terminal, + MainMenuLinks.ScriptEditor, + MainMenuLinks.ActiveScripts, + MainMenuLinks.CreateProgram, + ]; + if (terminal.style.maxHeight) { + toggleHeader(false, elems, links); + createProgramNot.style.display = "none"; + } else { + toggleHeader(true, elems, links); + createProgramNot.style.display = "block"; + } + }; - sleeves.style.display = p.sleeves.length > 0 ? "list-item" : "none"; + MainMenuHeaders.Character.onclick = function () { + const stats: HTMLElement = safeGetElement("stats-tab"); + const factions: HTMLElement = safeGetElement("factions-tab"); + const augmentations: HTMLElement = safeGetElement("augmentations-tab"); + const hacknetnodes: HTMLElement = safeGetElement("hacknet-nodes-tab"); + const sleeves: HTMLElement = safeGetElement("sleeves-tab"); - (this as any).classList.toggle("opened"); + sleeves.style.display = p.sleeves.length > 0 ? "list-item" : "none"; - const elems: HTMLElement[] = [stats, factions, augmentations, hacknetnodes, sleeves]; - const links: HTMLElement[] = [MainMenuLinks.Stats, MainMenuLinks.Factions, MainMenuLinks.Augmentations, MainMenuLinks.HacknetNodes, MainMenuLinks.Sleeves]; - if (stats.style.maxHeight) { - toggleHeader(false, elems, links); - } else { - toggleHeader(true, elems, links); - } - } + (this as any).classList.toggle("opened"); - MainMenuHeaders.World.onclick = function() { - const city: HTMLElement = safeGetElement("city-tab"); - const travel: HTMLElement = safeGetElement("travel-tab"); - const job: HTMLElement = safeGetElement("job-tab"); - const stockmarket: HTMLElement = safeGetElement("stock-market-tab"); - const bladeburner: HTMLElement = safeGetElement("bladeburner-tab"); - const corporation: HTMLElement = safeGetElement("corporation-tab"); - const gang: HTMLElement = safeGetElement("gang-tab"); + const elems: HTMLElement[] = [ + stats, + factions, + augmentations, + hacknetnodes, + sleeves, + ]; + const links: HTMLElement[] = [ + MainMenuLinks.Stats, + MainMenuLinks.Factions, + MainMenuLinks.Augmentations, + MainMenuLinks.HacknetNodes, + MainMenuLinks.Sleeves, + ]; + if (stats.style.maxHeight) { + toggleHeader(false, elems, links); + } else { + toggleHeader(true, elems, links); + } + }; - // Determine whether certain links should show up - job.style.display = p.companyName !== "" ? "list-item" : "none"; - stockmarket.style.display = p.hasWseAccount ? "list-item" : "none"; - bladeburner.style.display = p.inBladeburner() ? "list-item" : "none"; - corporation.style.display = p.hasCorporation() ? "list-item" : "none"; - gang.style.display = p.inGang() ? "list-item" : "none"; + MainMenuHeaders.World.onclick = function () { + const city: HTMLElement = safeGetElement("city-tab"); + const travel: HTMLElement = safeGetElement("travel-tab"); + const job: HTMLElement = safeGetElement("job-tab"); + const stockmarket: HTMLElement = safeGetElement("stock-market-tab"); + const bladeburner: HTMLElement = safeGetElement("bladeburner-tab"); + const corporation: HTMLElement = safeGetElement("corporation-tab"); + const gang: HTMLElement = safeGetElement("gang-tab"); - (this as any).classList.toggle("opened"); + // Determine whether certain links should show up + job.style.display = p.companyName !== "" ? "list-item" : "none"; + stockmarket.style.display = p.hasWseAccount ? "list-item" : "none"; + bladeburner.style.display = p.inBladeburner() ? "list-item" : "none"; + corporation.style.display = p.hasCorporation() ? "list-item" : "none"; + gang.style.display = p.inGang() ? "list-item" : "none"; - const elems: HTMLElement[] = [city, travel, job, stockmarket, bladeburner, corporation, gang]; - const links: HTMLElement[] = [MainMenuLinks.City, MainMenuLinks.Travel, MainMenuLinks.Job, MainMenuLinks.StockMarket, MainMenuLinks.Bladeburner, MainMenuLinks.Corporation, MainMenuLinks.Gang]; - if (city.style.maxHeight) { - toggleHeader(false, elems, links); - } else { - toggleHeader(true, elems, links); - } - } + (this as any).classList.toggle("opened"); - MainMenuHeaders.Help.onclick = function() { - const milestones: HTMLElement = safeGetElement("milestones-tab"); - const tutorial: HTMLElement = safeGetElement("tutorial-tab"); - const options: HTMLElement = safeGetElement("options-tab"); + const elems: HTMLElement[] = [ + city, + travel, + job, + stockmarket, + bladeburner, + corporation, + gang, + ]; + const links: HTMLElement[] = [ + MainMenuLinks.City, + MainMenuLinks.Travel, + MainMenuLinks.Job, + MainMenuLinks.StockMarket, + MainMenuLinks.Bladeburner, + MainMenuLinks.Corporation, + MainMenuLinks.Gang, + ]; + if (city.style.maxHeight) { + toggleHeader(false, elems, links); + } else { + toggleHeader(true, elems, links); + } + }; - (this as any).classList.toggle("opened"); + MainMenuHeaders.Help.onclick = function () { + const milestones: HTMLElement = safeGetElement("milestones-tab"); + const tutorial: HTMLElement = safeGetElement("tutorial-tab"); + const options: HTMLElement = safeGetElement("options-tab"); - const elems: HTMLElement[] = [milestones, tutorial, options]; - const links: HTMLElement[] = [MainMenuLinks.Milestones, MainMenuLinks.Tutorial, MainMenuLinks.Options]; + (this as any).classList.toggle("opened"); - if (dev) { - elems.push(safeGetElement("dev-tab")); - links.push(safeGetElement("dev-menu-link")); - } + const elems: HTMLElement[] = [milestones, tutorial, options]; + const links: HTMLElement[] = [ + MainMenuLinks.Milestones, + MainMenuLinks.Tutorial, + MainMenuLinks.Options, + ]; - if (tutorial.style.maxHeight) { - toggleHeader(false, elems, links); - } else { - toggleHeader(true, elems, links); - } - } + if (dev) { + elems.push(safeGetElement("dev-tab")); + links.push(safeGetElement("dev-menu-link")); + } - return true; - } catch(e) { - console.error(`Failed to initialize Main Menu Headers: ${e}`); - return false; - } + if (tutorial.style.maxHeight) { + toggleHeader(false, elems, links); + } else { + toggleHeader(true, elems, links); + } + }; + + return true; + } catch (e) { + console.error(`Failed to initialize Main Menu Headers: ${e}`); + return false; + } } diff --git a/src/ui/MainMenu/Links.ts b/src/ui/MainMenu/Links.ts index 890efa405..a56c92a52 100644 --- a/src/ui/MainMenu/Links.ts +++ b/src/ui/MainMenu/Links.ts @@ -3,94 +3,97 @@ import { clearEventListeners } from "../../../utils/uiHelpers/clearEventListeners"; interface IMainMenuLinks { - Terminal: HTMLElement; - ScriptEditor: HTMLElement; - ActiveScripts: HTMLElement; - CreateProgram: HTMLElement; - Stats: HTMLElement; - Factions: HTMLElement; - Augmentations: HTMLElement; - HacknetNodes: HTMLElement; - Sleeves: HTMLElement; - City: HTMLElement; - Travel: HTMLElement; - Job: HTMLElement; - StockMarket: HTMLElement; - Bladeburner: HTMLElement; - Corporation: HTMLElement; - Gang: HTMLElement; - Milestones: HTMLElement; - Tutorial: HTMLElement; - Options: HTMLElement; - DevMenu: HTMLElement; + Terminal: HTMLElement; + ScriptEditor: HTMLElement; + ActiveScripts: HTMLElement; + CreateProgram: HTMLElement; + Stats: HTMLElement; + Factions: HTMLElement; + Augmentations: HTMLElement; + HacknetNodes: HTMLElement; + Sleeves: HTMLElement; + City: HTMLElement; + Travel: HTMLElement; + Job: HTMLElement; + StockMarket: HTMLElement; + Bladeburner: HTMLElement; + Corporation: HTMLElement; + Gang: HTMLElement; + Milestones: HTMLElement; + Tutorial: HTMLElement; + Options: HTMLElement; + DevMenu: HTMLElement; } const emptyElement: HTMLElement = ((): HTMLElement => { - const elem = document.createElement('div'); - if(elem === null) throw new Error("unable to create empty div element"); - return elem; + const elem = document.createElement("div"); + if (elem === null) throw new Error("unable to create empty div element"); + return elem; })(); export const MainMenuLinks: IMainMenuLinks = { - Terminal: emptyElement, - ScriptEditor: emptyElement, - ActiveScripts: emptyElement, - CreateProgram: emptyElement, - Stats: emptyElement, - Factions: emptyElement, - Augmentations: emptyElement, - HacknetNodes: emptyElement, - Sleeves: emptyElement, - City: emptyElement, - Travel: emptyElement, - Job: emptyElement, - StockMarket: emptyElement, - Bladeburner: emptyElement, - Corporation: emptyElement, - Gang: emptyElement, - Milestones: emptyElement, - Tutorial: emptyElement, - Options: emptyElement, - DevMenu: emptyElement, -} + Terminal: emptyElement, + ScriptEditor: emptyElement, + ActiveScripts: emptyElement, + CreateProgram: emptyElement, + Stats: emptyElement, + Factions: emptyElement, + Augmentations: emptyElement, + HacknetNodes: emptyElement, + Sleeves: emptyElement, + City: emptyElement, + Travel: emptyElement, + Job: emptyElement, + StockMarket: emptyElement, + Bladeburner: emptyElement, + Corporation: emptyElement, + Gang: emptyElement, + Milestones: emptyElement, + Tutorial: emptyElement, + Options: emptyElement, + DevMenu: emptyElement, +}; export function initializeMainMenuLinks(): boolean { - try { - function safeGetLink(id: string): HTMLElement { - const elem: HTMLElement | null = clearEventListeners(id); - if (elem == null) { - throw new Error(`clearEventListeners() failed for element with id: ${id}`); - } + try { + function safeGetLink(id: string): HTMLElement { + const elem: HTMLElement | null = clearEventListeners(id); + if (elem == null) { + throw new Error( + `clearEventListeners() failed for element with id: ${id}`, + ); + } - return elem; - } - - MainMenuLinks.Terminal = safeGetLink("terminal-menu-link"); - MainMenuLinks.ScriptEditor = safeGetLink("create-script-menu-link"); - MainMenuLinks.ActiveScripts = safeGetLink("active-scripts-menu-link"); - MainMenuLinks.CreateProgram = safeGetLink("create-program-menu-link"); - MainMenuLinks.Stats = safeGetLink("stats-menu-link"); - MainMenuLinks.Factions = safeGetLink("factions-menu-link"); - MainMenuLinks.Augmentations = safeGetLink("augmentations-menu-link"); - MainMenuLinks.HacknetNodes = safeGetLink("hacknet-nodes-menu-link"); - MainMenuLinks.Sleeves = safeGetLink("sleeves-menu-link"); - MainMenuLinks.City = safeGetLink("city-menu-link"); - MainMenuLinks.Travel = safeGetLink("travel-menu-link"); - MainMenuLinks.Job = safeGetLink("job-menu-link"); - MainMenuLinks.StockMarket = safeGetLink("stock-market-menu-link"); - MainMenuLinks.Bladeburner = safeGetLink("bladeburner-menu-link"); - MainMenuLinks.Corporation = safeGetLink("corporation-menu-link"); - MainMenuLinks.Gang = safeGetLink("gang-menu-link"); - MainMenuLinks.Milestones = safeGetLink("milestones-menu-link"); - MainMenuLinks.Tutorial = safeGetLink("tutorial-menu-link"); - const op: HTMLElement | null = document.getElementById("options-menu-link"); - if(op === null) throw new Error(`Could not find element with id: "options-menu-link"`); - MainMenuLinks.Options = op; // This click listener is already set, so don't clear it - MainMenuLinks.DevMenu = safeGetLink("dev-menu-link"); - - return true; - } catch(e) { - console.error(`Failed to initialize Main Menu Links: ${e}`); - return false; + return elem; } + + MainMenuLinks.Terminal = safeGetLink("terminal-menu-link"); + MainMenuLinks.ScriptEditor = safeGetLink("create-script-menu-link"); + MainMenuLinks.ActiveScripts = safeGetLink("active-scripts-menu-link"); + MainMenuLinks.CreateProgram = safeGetLink("create-program-menu-link"); + MainMenuLinks.Stats = safeGetLink("stats-menu-link"); + MainMenuLinks.Factions = safeGetLink("factions-menu-link"); + MainMenuLinks.Augmentations = safeGetLink("augmentations-menu-link"); + MainMenuLinks.HacknetNodes = safeGetLink("hacknet-nodes-menu-link"); + MainMenuLinks.Sleeves = safeGetLink("sleeves-menu-link"); + MainMenuLinks.City = safeGetLink("city-menu-link"); + MainMenuLinks.Travel = safeGetLink("travel-menu-link"); + MainMenuLinks.Job = safeGetLink("job-menu-link"); + MainMenuLinks.StockMarket = safeGetLink("stock-market-menu-link"); + MainMenuLinks.Bladeburner = safeGetLink("bladeburner-menu-link"); + MainMenuLinks.Corporation = safeGetLink("corporation-menu-link"); + MainMenuLinks.Gang = safeGetLink("gang-menu-link"); + MainMenuLinks.Milestones = safeGetLink("milestones-menu-link"); + MainMenuLinks.Tutorial = safeGetLink("tutorial-menu-link"); + const op: HTMLElement | null = document.getElementById("options-menu-link"); + if (op === null) + throw new Error(`Could not find element with id: "options-menu-link"`); + MainMenuLinks.Options = op; // This click listener is already set, so don't clear it + MainMenuLinks.DevMenu = safeGetLink("dev-menu-link"); + + return true; + } catch (e) { + console.error(`Failed to initialize Main Menu Links: ${e}`); + return false; + } } diff --git a/src/ui/React/Accordion.tsx b/src/ui/React/Accordion.tsx index 3bdc2d658..b897f2eda 100644 --- a/src/ui/React/Accordion.tsx +++ b/src/ui/React/Accordion.tsx @@ -4,82 +4,83 @@ import * as React from "react"; type IProps = { - headerClass?: string; // Override default class - headerContent: React.ReactElement; - panelClass?: string; // Override default class - panelContent: React.ReactElement; - panelInitiallyOpened?: boolean; - style?: string; -} + headerClass?: string; // Override default class + headerContent: React.ReactElement; + panelClass?: string; // Override default class + panelContent: React.ReactElement; + panelInitiallyOpened?: boolean; + style?: string; +}; type IState = { - panelOpened: boolean; -} + panelOpened: boolean; +}; export class Accordion extends React.Component { - constructor(props: IProps) { - super(props); + constructor(props: IProps) { + super(props); - this.handleHeaderClick = this.handleHeaderClick.bind(this); + this.handleHeaderClick = this.handleHeaderClick.bind(this); - this.state = { - panelOpened: props.panelInitiallyOpened ? props.panelInitiallyOpened : false, - } + this.state = { + panelOpened: props.panelInitiallyOpened + ? props.panelInitiallyOpened + : false, + }; + } + + handleHeaderClick(): void { + this.setState({ + panelOpened: !this.state.panelOpened, + }); + } + + render(): React.ReactNode { + let className = "accordion-header"; + if (typeof this.props.headerClass === "string") { + className = this.props.headerClass; } - handleHeaderClick(): void { - this.setState({ - panelOpened: !this.state.panelOpened, - }); - } + if (this.state.panelOpened) className += " active"; - render(): React.ReactNode { - let className = "accordion-header"; - if (typeof this.props.headerClass === "string") { - className = this.props.headerClass; - } - - if(this.state.panelOpened) className += " active" - - return ( - <> - - - - ) - } + return ( + <> + + + + ); + } } type IPanelProps = { - opened: boolean; - panelClass?: string; // Override default class - panelContent: React.ReactElement; -} + opened: boolean; + panelClass?: string; // Override default class + panelContent: React.ReactElement; +}; class AccordionPanel extends React.Component { - shouldComponentUpdate(nextProps: IPanelProps): boolean { - return this.props.opened || nextProps.opened; + shouldComponentUpdate(nextProps: IPanelProps): boolean { + return this.props.opened || nextProps.opened; + } + + render(): React.ReactNode { + let className = "accordion-panel"; + if (typeof this.props.panelClass === "string") { + className = this.props.panelClass; } - render(): React.ReactNode { - let className = "accordion-panel" - if (typeof this.props.panelClass === "string") { - className = this.props.panelClass; - } + if (!this.props.opened) return <>; - if(!this.props.opened) return (<>); - - - return ( -
        - {this.props.panelContent} -
        - ) - } + return ( +
        + {this.props.panelContent} +
        + ); + } } diff --git a/src/ui/React/AccordionButton.tsx b/src/ui/React/AccordionButton.tsx index 722a1c188..daf768334 100644 --- a/src/ui/React/AccordionButton.tsx +++ b/src/ui/React/AccordionButton.tsx @@ -6,44 +6,51 @@ import * as React from "react"; interface IProps { - addClasses?: string; - disabled?: boolean; - id?: string; - onClick?: (e: React.MouseEvent) => any; - style?: any; - text: string; - tooltip?: string; + addClasses?: string; + disabled?: boolean; + id?: string; + onClick?: (e: React.MouseEvent) => any; + style?: any; + text: string; + tooltip?: string; } type IInnerHTMLMarkup = { - __html: string; -} + __html: string; +}; export function AccordionButton(props: IProps): React.ReactElement { - const hasTooltip = props.tooltip != null && props.tooltip !== ""; + const hasTooltip = props.tooltip != null && props.tooltip !== ""; - // TODO Add a disabled class for accordion buttons? - let className = "accordion-button"; - if (hasTooltip) { - className += " tooltip"; - } + // TODO Add a disabled class for accordion buttons? + let className = "accordion-button"; + if (hasTooltip) { + className += " tooltip"; + } - if (typeof props.addClasses === "string") { - className += ` ${props.addClasses}`; - } + if (typeof props.addClasses === "string") { + className += ` ${props.addClasses}`; + } - // Tooltip will be set using inner HTML - const tooltipMarkup: IInnerHTMLMarkup = { - __html: props.tooltip ? props.tooltip : "", - } + // Tooltip will be set using inner HTML + const tooltipMarkup: IInnerHTMLMarkup = { + __html: props.tooltip ? props.tooltip : "", + }; - return ( - - ) + return ( + + ); } diff --git a/src/ui/React/Augmentation.tsx b/src/ui/React/Augmentation.tsx index fddbf7e41..d4b55936e 100644 --- a/src/ui/React/Augmentation.tsx +++ b/src/ui/React/Augmentation.tsx @@ -1,5 +1,9 @@ import * as React from "react"; export function Augmentation(name: string): JSX.Element { - return {name} -} \ No newline at end of file + return ( + + {name} + + ); +} diff --git a/src/ui/React/AugmentationAccordion.tsx b/src/ui/React/AugmentationAccordion.tsx index 95f9f9248..47c3cedfc 100644 --- a/src/ui/React/AugmentationAccordion.tsx +++ b/src/ui/React/AugmentationAccordion.tsx @@ -12,31 +12,45 @@ import { Augmentation } from "../../Augmentation/Augmentation"; import { AugmentationNames } from "../../Augmentation/data/AugmentationNames"; type IProps = { - aug: Augmentation; - level?: number | string | null; -} + aug: Augmentation; + level?: number | string | null; +}; export function AugmentationAccordion(props: IProps): React.ReactElement { - let displayName = props.aug.name; - if (props.level != null) { - if (props.aug.name === AugmentationNames.NeuroFluxGovernor) { - displayName += (` - Level ${props.level}`) - } - } - - if(typeof props.aug.info === 'string') { - return ( - {displayName}} - panelContent={



        {props.aug.stats}

        } - /> - ) + let displayName = props.aug.name; + if (props.level != null) { + if (props.aug.name === AugmentationNames.NeuroFluxGovernor) { + displayName += ` - Level ${props.level}`; } + } + if (typeof props.aug.info === "string") { return ( - {displayName}} - panelContent={

        {props.aug.info}

        {props.aug.stats}

        } - /> - ) + {displayName}} + panelContent={ +

        + +
        +
        + {props.aug.stats} +

        + } + /> + ); + } + + return ( + {displayName}} + panelContent={ +

        + {props.aug.info} +
        +
        + {props.aug.stats} +

        + } + /> + ); } diff --git a/src/ui/React/AutoupdatingParagraph.tsx b/src/ui/React/AutoupdatingParagraph.tsx index 5fd435932..a9bdb74e5 100644 --- a/src/ui/React/AutoupdatingParagraph.tsx +++ b/src/ui/React/AutoupdatingParagraph.tsx @@ -6,65 +6,64 @@ import * as React from "react"; interface IProps { - intervalTime?: number; - style?: any; - getContent: () => JSX.Element; - getTooltip?: () => JSX.Element; + intervalTime?: number; + style?: any; + getContent: () => JSX.Element; + getTooltip?: () => JSX.Element; } interface IState { - i: number; + i: number; } export class AutoupdatingParagraph extends React.Component { - /** - * Timer ID for auto-updating implementation (returned value from setInterval()) - */ - interval = 0; + /** + * Timer ID for auto-updating implementation (returned value from setInterval()) + */ + interval = 0; - constructor(props: IProps) { - super(props); - this.state = { - i: 0, - } - } + constructor(props: IProps) { + super(props); + this.state = { + i: 0, + }; + } - componentDidMount(): void { - const time = this.props.intervalTime ? this.props.intervalTime : 1000; - this.interval = window.setInterval(() => this.tick(), time); - } + componentDidMount(): void { + const time = this.props.intervalTime ? this.props.intervalTime : 1000; + this.interval = window.setInterval(() => this.tick(), time); + } - componentWillUnmount(): void { - clearInterval(this.interval); - } + componentWillUnmount(): void { + clearInterval(this.interval); + } - tick(): void { - this.setState(prevState => ({ - i: prevState.i + 1, - })); - } + tick(): void { + this.setState((prevState) => ({ + i: prevState.i + 1, + })); + } - hasTooltip(): boolean { - if (this.props.getTooltip != null) { - return !!this.props.getTooltip() - } - return true; + hasTooltip(): boolean { + if (this.props.getTooltip != null) { + return !!this.props.getTooltip(); } + return true; + } - tooltip(): JSX.Element { - if(!this.props.getTooltip) return <>; - return this.props.getTooltip(); - } + tooltip(): JSX.Element { + if (!this.props.getTooltip) return <>; + return this.props.getTooltip(); + } - render(): React.ReactNode { - return ( -
        -

        {this.props.getContent()}

        - { - this.hasTooltip() && - {this.tooltip()} - } -
        - ) - } + render(): React.ReactNode { + return ( +
        +

        {this.props.getContent()}

        + {this.hasTooltip() && ( + {this.tooltip()} + )} +
        + ); + } } diff --git a/src/ui/React/AutoupdatingStdButton.tsx b/src/ui/React/AutoupdatingStdButton.tsx index 5ebf9525b..0e57c5f41 100644 --- a/src/ui/React/AutoupdatingStdButton.tsx +++ b/src/ui/React/AutoupdatingStdButton.tsx @@ -7,71 +7,77 @@ import * as React from "react"; interface IProps { - disabled?: boolean; - intervalTime?: number; - onClick?: (e: React.MouseEvent) => any; - style?: any; - text: string | JSX.Element; - tooltip?: string; + disabled?: boolean; + intervalTime?: number; + onClick?: (e: React.MouseEvent) => any; + style?: any; + text: string | JSX.Element; + tooltip?: string; } interface IState { - i: number; + i: number; } type IInnerHTMLMarkup = { - __html: string; -} + __html: string; +}; export class AutoupdatingStdButton extends React.Component { - /** - * Timer ID for auto-updating implementation (returned value from setInterval()) - */ - interval = 0; + /** + * Timer ID for auto-updating implementation (returned value from setInterval()) + */ + interval = 0; - constructor(props: IProps) { - super(props); - this.state = { - i: 0, - } + constructor(props: IProps) { + super(props); + this.state = { + i: 0, + }; + } + + componentDidMount(): void { + const time = this.props.intervalTime ? this.props.intervalTime : 1000; + this.interval = window.setInterval(() => this.tick(), time); + } + + componentWillUnmount(): void { + clearInterval(this.interval); + } + + tick(): void { + this.setState((prevState) => ({ + i: prevState.i + 1, + })); + } + + render(): React.ReactNode { + const hasTooltip = this.props.tooltip != null && this.props.tooltip !== ""; + + let className = this.props.disabled ? "std-button-disabled" : "std-button"; + if (hasTooltip) { + className += " tooltip"; } - componentDidMount(): void { - const time = this.props.intervalTime ? this.props.intervalTime : 1000; - this.interval = window.setInterval(() => this.tick(), time); - } + // Tooltip will eb set using inner HTML + const tooltipMarkup: IInnerHTMLMarkup = { + __html: this.props.tooltip ? this.props.tooltip : "", + }; - componentWillUnmount(): void { - clearInterval(this.interval); - } - - tick(): void { - this.setState(prevState => ({ - i: prevState.i + 1, - })); - } - - render(): React.ReactNode { - const hasTooltip = this.props.tooltip != null && this.props.tooltip !== ""; - - let className = this.props.disabled ? "std-button-disabled" : "std-button"; - if (hasTooltip) { - className += " tooltip" - } - - // Tooltip will eb set using inner HTML - const tooltipMarkup: IInnerHTMLMarkup = { - __html: this.props.tooltip ? this.props.tooltip : "", - } - - return ( - - ) - } + return ( + + ); + } } diff --git a/src/ui/React/CharacterOverview.jsx b/src/ui/React/CharacterOverview.jsx index f0a0274ee..74eb9e6ca 100644 --- a/src/ui/React/CharacterOverview.jsx +++ b/src/ui/React/CharacterOverview.jsx @@ -1,76 +1,138 @@ // Root React Component for the Corporation UI import React from "react"; -import { Player } from "../../Player"; +import { Player } from "../../Player"; import { numeralWrapper } from "../../ui/numeralFormat"; -import { Reputation } from "./Reputation"; +import { Reputation } from "./Reputation"; const Component = React.Component; export class CharacterOverviewComponent extends Component { - render() { - const intelligence = ( - - Int: {numeralWrapper.formatSkill(Player.intelligence)} - - ); + render() { + const intelligence = ( + + Int:  + + {numeralWrapper.formatSkill(Player.intelligence)} + + + ); - /*const work = ( + /*const work = (

        Work progress:

        +{Reputation(Player.workRepGained)} rep

        );*/ - const work = ( - <> - Work progress: - +{Reputation(Player.workRepGained)} rep - - - - - ); + const work = ( + <> + + Work progress: + + + +{Reputation(Player.workRepGained)} rep + + + + + + + + ); - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - { - Player.intelligence >= 1 && - intelligence - } - { - (Player.isWorking && !Player.focus) && - work - } - -
        HP:{numeralWrapper.formatHp(Player.hp) + " / " + numeralWrapper.formatHp(Player.max_hp)}
        Money: {numeralWrapper.formatMoney(Player.money.toNumber())}
        Hack: {numeralWrapper.formatSkill(Player.hacking_skill)}
        Str: {numeralWrapper.formatSkill(Player.strength)}
        Def: {numeralWrapper.formatSkill(Player.defense)}
        Dex: {numeralWrapper.formatSkill(Player.dexterity)}
        Agi: {numeralWrapper.formatSkill(Player.agility)}
        Cha: {numeralWrapper.formatSkill(Player.charisma)}
        - - ) - } -} \ No newline at end of file + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {Player.intelligence >= 1 && intelligence} + {Player.isWorking && !Player.focus && work} + +
        HP: + {numeralWrapper.formatHp(Player.hp) + + " / " + + numeralWrapper.formatHp(Player.max_hp)} +
        Money:  + {numeralWrapper.formatMoney(Player.money.toNumber())} +
        Hack:  + {numeralWrapper.formatSkill(Player.hacking_skill)} +
        Str:  + {numeralWrapper.formatSkill(Player.strength)} +
        Def:  + {numeralWrapper.formatSkill(Player.defense)} +
        Dex:  + {numeralWrapper.formatSkill(Player.dexterity)} +
        Agi:  + {numeralWrapper.formatSkill(Player.agility)} +
        Cha:  + {numeralWrapper.formatSkill(Player.charisma)} +
        + + ); + } +} diff --git a/src/ui/React/CodingContractPopup.tsx b/src/ui/React/CodingContractPopup.tsx index 8ae991835..fa02640bf 100644 --- a/src/ui/React/CodingContractPopup.tsx +++ b/src/ui/React/CodingContractPopup.tsx @@ -1,67 +1,87 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { KEY } from "../../../utils/helpers/keyCodes"; -import { CodingContract, CodingContractType, CodingContractTypes } from "../../CodingContracts"; +import { + CodingContract, + CodingContractType, + CodingContractTypes, +} from "../../CodingContracts"; import { ClickableTag, CopyableText } from "./CopyableText"; import { PopupCloseButton } from "./PopupCloseButton"; type IProps = { - c: CodingContract; - popupId: string; - onClose: () => void; - onAttempt: (answer: string) => void; -} + c: CodingContract; + popupId: string; + onClose: () => void; + onAttempt: (answer: string) => void; +}; export function CodingContractPopup(props: IProps): React.ReactElement { - const [answer, setAnswer] = useState(""); + const [answer, setAnswer] = useState(""); - function onChange(event: React.ChangeEvent): void { - setAnswer(event.target.value); + function onChange(event: React.ChangeEvent): void { + setAnswer(event.target.value); + } + + function onKeyDown(event: React.KeyboardEvent): void { + // React just won't cooperate on this one. + // "React.KeyboardEvent" seems like the right type but + // whatever ... + const value = (event.target as any).value; + + if (event.keyCode === KEY.ENTER && value !== "") { + event.preventDefault(); + props.onAttempt(answer); + } else if (event.keyCode === KEY.ESC) { + event.preventDefault(); + props.onClose(); } + } - function onKeyDown(event: React.KeyboardEvent): void { - // React just won't cooperate on this one. - // "React.KeyboardEvent" seems like the right type but - // whatever ... - const value = (event.target as any).value; - - if (event.keyCode === KEY.ENTER && value !== "") { - event.preventDefault(); - props.onAttempt(answer); - } else if (event.keyCode === KEY.ESC) { - event.preventDefault(); - props.onClose(); - } - } - - const contractType: CodingContractType = CodingContractTypes[props.c.type]; - const description = []; - for (const [i, value] of contractType.desc(props.c.data).split('\n').entries()) - description.push('}}>); - return ( -
        - -

        -

        You are attempting to solve a Coding Contract. You have {props.c.getMaxNumTries() - props.c.tries} tries remaining, after which the contract will self-destruct.

        -
        -

        {description}

        -
        - - props.onAttempt(answer)} - text={"Solve"} /> - -
        - ) -} \ No newline at end of file + const contractType: CodingContractType = CodingContractTypes[props.c.type]; + const description = []; + for (const [i, value] of contractType + .desc(props.c.data) + .split("\n") + .entries()) + description.push( + " }} + >, + ); + return ( +
        + +
        +
        +

        + You are attempting to solve a Coding Contract. You have{" "} + {props.c.getMaxNumTries() - props.c.tries} tries remaining, after which + the contract will self-destruct. +

        +
        +

        {description}

        +
        + + props.onAttempt(answer)} + text={"Solve"} + /> + +
        + ); +} diff --git a/src/ui/React/CopyableText.tsx b/src/ui/React/CopyableText.tsx index 137998fe8..7e47e882a 100644 --- a/src/ui/React/CopyableText.tsx +++ b/src/ui/React/CopyableText.tsx @@ -1,82 +1,83 @@ import * as React from "react"; -export enum ClickableTag{ - Tag_span, - Tag_h1 +export enum ClickableTag { + Tag_span, + Tag_h1, } type IProps = { - value: string; - tag: ClickableTag; -} + value: string; + tag: ClickableTag; +}; type IState = { - tooltipVisible: boolean; -} + tooltipVisible: boolean; +}; export class CopyableText extends React.Component { - public static defaultProps = { - //Default span to prevent destroying current clickables - tag: ClickableTag.Tag_span, + public static defaultProps = { + //Default span to prevent destroying current clickables + tag: ClickableTag.Tag_span, + }; + + constructor(props: IProps) { + super(props); + + this.copy = this.copy.bind(this); + this.tooltipClasses = this.tooltipClasses.bind(this); + this.textClasses = this.textClasses.bind(this); + + this.state = { + tooltipVisible: false, }; - - constructor(props: IProps) { - super(props); + } - this.copy = this.copy.bind(this); - this.tooltipClasses = this.tooltipClasses.bind(this); - this.textClasses = this.textClasses.bind(this); + copy(): void { + const copyText = document.createElement("textarea"); + copyText.value = this.props.value; + document.body.appendChild(copyText); + copyText.select(); + copyText.setSelectionRange(0, 1e10); + document.execCommand("copy"); + document.body.removeChild(copyText); + this.setState({ tooltipVisible: true }); + setTimeout(() => this.setState({ tooltipVisible: false }), 1000); + } - this.state = { - tooltipVisible: false, - } + tooltipClasses(): string { + let classes = "copy_tooltip_text"; + if (this.state.tooltipVisible) { + classes += " copy_tooltip_text_visible"; } - copy(): void { - const copyText = document.createElement("textarea"); - copyText.value = this.props.value; - document.body.appendChild(copyText); - copyText.select(); - copyText.setSelectionRange(0, 1e10); - document.execCommand("copy"); - document.body.removeChild(copyText); - this.setState({tooltipVisible: true}); - setTimeout(() => this.setState({tooltipVisible: false}), 1000); + return classes; + } + + textClasses(): string { + let classes = "copy_tooltip noselect text"; + if (this.state.tooltipVisible) { + classes += " copy_tooltip_copied"; } - tooltipClasses(): string { - let classes = "copy_tooltip_text"; - if(this.state.tooltipVisible) { - classes += " copy_tooltip_text_visible"; - } + return classes; + } - return classes; + render(): React.ReactNode { + switch (this.props.tag) { + case ClickableTag.Tag_h1: + return ( +

        + {this.props.value} + Copied! +

        + ); + case ClickableTag.Tag_span: + return ( + + {this.props.value} + Copied! + + ); } - - textClasses(): string { - let classes = "copy_tooltip noselect text"; - if(this.state.tooltipVisible) { - classes += " copy_tooltip_copied"; - } - - return classes; - } - - - render(): React.ReactNode { - switch (this.props.tag) { - case ClickableTag.Tag_h1: - return ( -

        - {this.props.value} - Copied! -

        ) - case ClickableTag.Tag_span: - return ( - - {this.props.value} - Copied! - ) - } - } -} \ No newline at end of file + } +} diff --git a/src/ui/React/CorruptableText.tsx b/src/ui/React/CorruptableText.tsx index 926e1ece6..99143199d 100644 --- a/src/ui/React/CorruptableText.tsx +++ b/src/ui/React/CorruptableText.tsx @@ -1,53 +1,53 @@ import React, { useEffect, useState } from "react"; function replace(str: string, i: number, char: string): string { - return str.substring(0, i) + char + str.substring(i + 1); + return str.substring(0, i) + char + str.substring(i + 1); } interface IProps { - content: string; + content: string; } function randomize(char: string): string { - const randFrom = (str: string): string => str[Math.floor(Math.random()*str.length)]; - const classes = [ - "abcdefghijklmnopqrstuvwxyz", - "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - "1234567890", - " _", - "()[]{}<>", - ]; - const other = `!@#$%^&*()_+|\\';"/.,?\`~`; + const randFrom = (str: string): string => + str[Math.floor(Math.random() * str.length)]; + const classes = [ + "abcdefghijklmnopqrstuvwxyz", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "1234567890", + " _", + "()[]{}<>", + ]; + const other = `!@#$%^&*()_+|\\';"/.,?\`~`; - for(const c of classes) { - if (c.includes(char)) return randFrom(c); - } + for (const c of classes) { + if (c.includes(char)) return randFrom(c); + } - return randFrom(other); + return randFrom(other); } export function CorruptableText(props: IProps): JSX.Element { - const [content, setContent] = useState(props.content); + const [content, setContent] = useState(props.content); - useEffect(() => { - let counter = 5; - const id = setInterval(() => { - counter--; - if (counter > 0) - return; - counter = Math.random() * 5; - const index = Math.random() * content.length; - const letter = content.charAt(index); - setContent(replace(content, index, randomize(letter))); - setTimeout(() => { - setContent(content); - }, 50); - }, 100); + useEffect(() => { + let counter = 5; + const id = setInterval(() => { + counter--; + if (counter > 0) return; + counter = Math.random() * 5; + const index = Math.random() * content.length; + const letter = content.charAt(index); + setContent(replace(content, index, randomize(letter))); + setTimeout(() => { + setContent(content); + }, 50); + }, 100); - return () => { - clearInterval(id); - }; - }, []); + return () => { + clearInterval(id); + }; + }, []); - return {content} -} \ No newline at end of file + return {content}; +} diff --git a/src/ui/React/ErrorBoundary.tsx b/src/ui/React/ErrorBoundary.tsx index ee65679b8..4fdae86da 100644 --- a/src/ui/React/ErrorBoundary.tsx +++ b/src/ui/React/ErrorBoundary.tsx @@ -7,99 +7,93 @@ import * as React from "react"; import { EventEmitter } from "../../utils/EventEmitter"; type IProps = { - eventEmitterForReset?: EventEmitter; - id?: string; + eventEmitterForReset?: EventEmitter; + id?: string; }; type IState = { - errorInfo: string; - hasError: boolean; -} + errorInfo: string; + hasError: boolean; +}; type IErrorInfo = { - componentStack: string; -} + componentStack: string; +}; // TODO: Move this out to a css file const styleMarkup = { - border: "1px solid red", - display: "inline-block", - margin: "4px", - padding: "4px", -} + border: "1px solid red", + display: "inline-block", + margin: "4px", + padding: "4px", +}; export class ErrorBoundary extends React.Component { - constructor(props: IProps) { - super(props); - this.state = { - errorInfo: "", - hasError: false, - } + constructor(props: IProps) { + super(props); + this.state = { + errorInfo: "", + hasError: false, + }; + } + + componentDidCatch(error: Error, info: IErrorInfo): void { + console.error(`Caught error in React ErrorBoundary. Component stack:`); + console.error(info.componentStack); + } + + componentDidMount(): void { + const cb = (): void => { + this.setState({ + hasError: false, + }); + }; + + if (this.hasEventEmitter()) { + (this.props.eventEmitterForReset as EventEmitter).addSubscriber({ + cb: cb, + id: this.props.id as string, + }); + } + } + + componentWillUnmount(): void { + if (this.hasEventEmitter()) { + (this.props.eventEmitterForReset as EventEmitter).removeSubscriber( + this.props.id as string, + ); + } + } + + hasEventEmitter(): boolean { + return ( + this.props.eventEmitterForReset != null && + this.props.eventEmitterForReset instanceof EventEmitter && + this.props.id != null && + typeof this.props.id === "string" + ); + } + + render(): React.ReactNode { + if (this.state.hasError) { + return ( +
        +

        + {`Error rendering UI. This is (probably) a bug. Please report to game developer.`} +

        +

        {`In the meantime, try refreshing the game WITHOUT saving.`}

        +

        {`Error info: ${this.state.errorInfo}`}

        +
        + ); } - componentDidCatch(error: Error, info: IErrorInfo): void { - console.error(`Caught error in React ErrorBoundary. Component stack:`); - console.error(info.componentStack); - } + return this.props.children; + } - componentDidMount(): void { - const cb = (): void => { - this.setState({ - hasError: false, - }); - } - - if (this.hasEventEmitter()) { - (this.props.eventEmitterForReset as EventEmitter).addSubscriber({ - cb: cb, - id: (this.props.id as string), - }); - } - } - - componentWillUnmount(): void { - if (this.hasEventEmitter()) { - (this.props.eventEmitterForReset as EventEmitter).removeSubscriber((this.props.id as string)); - } - } - - hasEventEmitter(): boolean { - return this.props.eventEmitterForReset != null && - this.props.eventEmitterForReset instanceof EventEmitter && - this.props.id != null && - typeof this.props.id === "string"; - } - - render(): React.ReactNode { - if (this.state.hasError) { - return ( -
        -

        - { - `Error rendering UI. This is (probably) a bug. Please report to game developer.` - } -

        -

        - { - `In the meantime, try refreshing the game WITHOUT saving.` - } -

        -

        - { - `Error info: ${this.state.errorInfo}` - } -

        -
        - ) - } - - return this.props.children; - } - - static getDerivedStateFromError(error: Error): IState { - return { - errorInfo: error.message, - hasError: true, - }; - } + static getDerivedStateFromError(error: Error): IState { + return { + errorInfo: error.message, + hasError: true, + }; + } } diff --git a/src/ui/React/Favor.tsx b/src/ui/React/Favor.tsx index 065f8538f..cee804849 100644 --- a/src/ui/React/Favor.tsx +++ b/src/ui/React/Favor.tsx @@ -2,5 +2,9 @@ import * as React from "react"; import { numeralWrapper } from "../../ui/numeralFormat"; export function Favor(favor: number | string): JSX.Element { - return {typeof favor === 'number' ? numeralWrapper.formatFavor(favor) : favor} -} \ No newline at end of file + return ( + + {typeof favor === "number" ? numeralWrapper.formatFavor(favor) : favor} + + ); +} diff --git a/src/ui/React/HashRate.tsx b/src/ui/React/HashRate.tsx index 88785f3dc..f368ce66c 100644 --- a/src/ui/React/HashRate.tsx +++ b/src/ui/React/HashRate.tsx @@ -2,5 +2,5 @@ import { numeralWrapper } from "../../ui/numeralFormat"; import { Hashes } from "../../ui/React/Hashes"; export function HashRate(hashes: number): JSX.Element { - return Hashes(`${numeralWrapper.formatHashes(hashes)} / sec`); -} \ No newline at end of file + return Hashes(`${numeralWrapper.formatHashes(hashes)} / sec`); +} diff --git a/src/ui/React/Hashes.tsx b/src/ui/React/Hashes.tsx index b1b221e6a..304fc72d3 100644 --- a/src/ui/React/Hashes.tsx +++ b/src/ui/React/Hashes.tsx @@ -2,5 +2,11 @@ import * as React from "react"; import { numeralWrapper } from "../../ui/numeralFormat"; export function Hashes(hashes: number | string): JSX.Element { - return {typeof hashes === 'number' ? numeralWrapper.formatHashes(hashes) : hashes} -} \ No newline at end of file + return ( + + {typeof hashes === "number" + ? numeralWrapper.formatHashes(hashes) + : hashes} + + ); +} diff --git a/src/ui/React/Money.tsx b/src/ui/React/Money.tsx index 4dd49c09d..59986c851 100644 --- a/src/ui/React/Money.tsx +++ b/src/ui/React/Money.tsx @@ -3,14 +3,27 @@ import { numeralWrapper } from "../../ui/numeralFormat"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - money: number | string; - player?: IPlayer; + money: number | string; + player?: IPlayer; } export function Money(props: IProps): JSX.Element { - if(props.player !== undefined) { - if(typeof props.money !== 'number') throw new Error('if player if provided, money should be number, contact dev'); - if(!props.player.canAfford(props.money)) - return {numeralWrapper.formatMoney(props.money)} - } - return {typeof props.money === 'number' ? numeralWrapper.formatMoney(props.money) : props.money} -} \ No newline at end of file + if (props.player !== undefined) { + if (typeof props.money !== "number") + throw new Error( + "if player if provided, money should be number, contact dev", + ); + if (!props.player.canAfford(props.money)) + return ( + + {numeralWrapper.formatMoney(props.money)} + + ); + } + return ( + + {typeof props.money === "number" + ? numeralWrapper.formatMoney(props.money) + : props.money} + + ); +} diff --git a/src/ui/React/MoneyRate.tsx b/src/ui/React/MoneyRate.tsx index 611a94fe1..4c7b54551 100644 --- a/src/ui/React/MoneyRate.tsx +++ b/src/ui/React/MoneyRate.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React from "react"; import { numeralWrapper } from "../../ui/numeralFormat"; import { Money } from "../../ui/React/Money"; export function MoneyRate(money: number): JSX.Element { - return ; -} \ No newline at end of file + return ; +} diff --git a/src/ui/React/MuiButton.tsx b/src/ui/React/MuiButton.tsx index 7670bf823..78a6e5c7c 100644 --- a/src/ui/React/MuiButton.tsx +++ b/src/ui/React/MuiButton.tsx @@ -1,5 +1,5 @@ /** - * Wrapper around material-ui's Button component that styles it with + * Wrapper around material-ui's Button component that styles it with * Bitburner's UI theme */ @@ -7,37 +7,38 @@ import React from "react"; import { Button, ButtonProps, makeStyles } from "@material-ui/core"; const useStyles = makeStyles({ - // Tries to emulate StdButton in buttons.scss - root: { - backgroundColor: "#555", - border: "1px solid #333", - color: "white", - margin: "5px", - padding: "3px 5px", - "&:hover": { - backgroundColor: "#666", - }, - }, - textPrimary: { - color: "rgb( 144, 202, 249)", - }, - textSecondary: { - color: "rgb(244, 143, 177)", - }, - disabled: { - backgroundColor: "#333", - color: "#fff", - cursor: "default", + // Tries to emulate StdButton in buttons.scss + root: { + backgroundColor: "#555", + border: "1px solid #333", + color: "white", + margin: "5px", + padding: "3px 5px", + "&:hover": { + backgroundColor: "#666", }, + }, + textPrimary: { + color: "rgb( 144, 202, 249)", + }, + textSecondary: { + color: "rgb(244, 143, 177)", + }, + disabled: { + backgroundColor: "#333", + color: "#fff", + cursor: "default", + }, }); - export const MuiButton: React.FC = (props: ButtonProps) => { - return ( - - ) + keyListener(e: KeyboardEvent): void { + //This doesn't really make sense, a button doesnt have to listen to escape IMO + //Too affraid to remove it since im not sure what it will break.. But yuck.. + if (e.keyCode === KEY.ESC) { + this.handleClick(); } -} \ No newline at end of file + } + + render(): React.ReactNode { + const className = this.props.class ? this.props.class : "std-button"; + + return ( + + ); + } +} diff --git a/src/ui/React/PopupCloseButton.tsx b/src/ui/React/PopupCloseButton.tsx index 8230ff5ae..9fdd5ef0c 100644 --- a/src/ui/React/PopupCloseButton.tsx +++ b/src/ui/React/PopupCloseButton.tsx @@ -5,56 +5,56 @@ * Should only be used in other React components, otherwise it may not be properly * unmounted */ -import * as React from "react"; -import * as ReactDOM from "react-dom"; +import * as React from "react"; +import * as ReactDOM from "react-dom"; import { removeElement } from "../../../utils/uiHelpers/removeElement"; import { IPopupButtonProps, PopupButton } from "./PopupButton"; export interface IPopupCloseButtonProps extends IPopupButtonProps { - class?: string; - popup: HTMLElement | string; - style?: any; - text: string; - onClose: () => void; + class?: string; + popup: HTMLElement | string; + style?: any; + text: string; + onClose: () => void; } export class PopupCloseButton extends PopupButton { - constructor(props: IPopupCloseButtonProps) { - super(props); + constructor(props: IPopupCloseButtonProps) { + super(props); - this.closePopup = this.closePopup.bind(this); + this.closePopup = this.closePopup.bind(this); + } + + closePopup(): void { + if (this.props.onClose) this.props.onClose(); + let popup: HTMLElement | null; + if (typeof this.props.popup === "string") { + popup = document.getElementById(this.props.popup); + } else { + popup = this.props.popup; } - closePopup(): void { - if(this.props.onClose) - this.props.onClose(); - let popup: HTMLElement | null; - if (typeof this.props.popup === "string") { - popup = document.getElementById(this.props.popup); - } else { - popup = this.props.popup; - } - - // TODO Check if this is okay? This is essentially calling to unmount a - // parent component - if (popup instanceof HTMLElement) { - // Removes everything inside the wrapper container - ReactDOM.unmountComponentAtNode(popup); - removeElement(popup); // Removes the wrapper container - } + // TODO Check if this is okay? This is essentially calling to unmount a + // parent component + if (popup instanceof HTMLElement) { + // Removes everything inside the wrapper container + ReactDOM.unmountComponentAtNode(popup); + removeElement(popup); // Removes the wrapper container } + } - render(): React.ReactNode { - const className = this.props.class ? this.props.class : "std-button"; + render(): React.ReactNode { + const className = this.props.class ? this.props.class : "std-button"; - return ( - - ) - } + return ( + + ); + } } diff --git a/src/ui/React/Reputation.tsx b/src/ui/React/Reputation.tsx index 760d62b3e..da25d02fc 100644 --- a/src/ui/React/Reputation.tsx +++ b/src/ui/React/Reputation.tsx @@ -2,5 +2,11 @@ import * as React from "react"; import { numeralWrapper } from "../../ui/numeralFormat"; export function Reputation(reputation: number | string): JSX.Element { - return {typeof reputation === 'number' ? numeralWrapper.formatReputation(reputation) : reputation} -} \ No newline at end of file + return ( + + {typeof reputation === "number" + ? numeralWrapper.formatReputation(reputation) + : reputation} + + ); +} diff --git a/src/ui/React/ReputationRate.tsx b/src/ui/React/ReputationRate.tsx index 49bde1a09..c3a283398 100644 --- a/src/ui/React/ReputationRate.tsx +++ b/src/ui/React/ReputationRate.tsx @@ -2,5 +2,5 @@ import { numeralWrapper } from "../../ui/numeralFormat"; import { Reputation } from "../../ui/React/Reputation"; export function ReputationRate(reputation: number): JSX.Element { - return Reputation(`${numeralWrapper.formatReputation(reputation)} / sec`); -} \ No newline at end of file + return Reputation(`${numeralWrapper.formatReputation(reputation)} / sec`); +} diff --git a/src/ui/React/ServerDropdown.jsx b/src/ui/React/ServerDropdown.jsx index 81c25d3b6..1368e2e12 100644 --- a/src/ui/React/ServerDropdown.jsx +++ b/src/ui/React/ServerDropdown.jsx @@ -10,56 +10,68 @@ import { HacknetServer } from "../../Hacknet/HacknetServer"; // TODO make this an enum when this gets converted to TypeScript export const ServerType = { - All: 0, - Foreign: 1, // Hackable, non-owned servers - Owned: 2, // Home Computer, Purchased Servers, and Hacknet Servers - Purchased: 3, // Everything from Owned except home computer -} + All: 0, + Foreign: 1, // Hackable, non-owned servers + Owned: 2, // Home Computer, Purchased Servers, and Hacknet Servers + Purchased: 3, // Everything from Owned except home computer +}; export class ServerDropdown extends React.Component { - /** - * Checks if the server should be shown in the dropdown menu, based on the - * 'serverType' property - */ - isValidServer(s) { - const type = this.props.serverType; - switch (type) { - case ServerType.All: - return true; - case ServerType.Foreign: - return (s.hostname !== "home" && !s.purchasedByPlayer); - case ServerType.Owned: - return s.purchasedByPlayer || (s instanceof HacknetServer) || s.hostname === "home"; - case ServerType.Purchased: - return s.purchasedByPlayer || (s instanceof HacknetServer); - default: - console.warn(`Invalid ServerType specified for ServerDropdown component: ${type}`); - return false; - } - } - - /** - * Given a Server object, creates a Option element - */ - renderOption(s) { + /** + * Checks if the server should be shown in the dropdown menu, based on the + * 'serverType' property + */ + isValidServer(s) { + const type = this.props.serverType; + switch (type) { + case ServerType.All: + return true; + case ServerType.Foreign: + return s.hostname !== "home" && !s.purchasedByPlayer; + case ServerType.Owned: return ( - - ) + s.purchasedByPlayer || + s instanceof HacknetServer || + s.hostname === "home" + ); + case ServerType.Purchased: + return s.purchasedByPlayer || s instanceof HacknetServer; + default: + console.warn( + `Invalid ServerType specified for ServerDropdown component: ${type}`, + ); + return false; + } + } + + /** + * Given a Server object, creates a Option element + */ + renderOption(s) { + return ( + + ); + } + + render() { + const servers = []; + for (const serverName in AllServers) { + const server = AllServers[serverName]; + if (this.isValidServer(server)) { + servers.push(this.renderOption(server)); + } } - render() { - const servers = []; - for (const serverName in AllServers) { - const server = AllServers[serverName]; - if (this.isValidServer(server)) { - servers.push(this.renderOption(server)); - } - } - - return ( - - ) - } + return ( + + ); + } } diff --git a/src/ui/React/SourceFileAccordion.tsx b/src/ui/React/SourceFileAccordion.tsx index 040258c88..57ccc73ef 100644 --- a/src/ui/React/SourceFileAccordion.tsx +++ b/src/ui/React/SourceFileAccordion.tsx @@ -11,25 +11,23 @@ import { Accordion } from "./Accordion"; import { SourceFile } from "../../SourceFile/SourceFile"; type IProps = { - level: number; - sf: SourceFile; -} + level: number; + sf: SourceFile; +}; export function SourceFileAccordion(props: IProps): React.ReactElement { - const maxLevel = props.sf.n === 12 ? "∞" : "3"; + const maxLevel = props.sf.n === 12 ? "∞" : "3"; - return ( - - {props.sf.name} -
        - {`Level ${props.level} / ${maxLevel}`} - - } - panelContent={ -

        - } - /> - ) + return ( + + {props.sf.name} +
        + {`Level ${props.level} / ${maxLevel}`} + + } + panelContent={

        } + /> + ); } diff --git a/src/ui/React/StatsTable.tsx b/src/ui/React/StatsTable.tsx index b2db010e6..2ccfc17f0 100644 --- a/src/ui/React/StatsTable.tsx +++ b/src/ui/React/StatsTable.tsx @@ -1,24 +1,40 @@ import * as React from "react"; export function StatsTable(rows: any[][], title?: string): React.ReactElement { - let titleElem = <> - if (title) { - titleElem = <>

        {title}


        ; - } - return (<> - {titleElem} - - - {rows.map((row: any[]) => { - return - {row.map((elem: any, i: number) => { - let style = {}; - if (i !== 0) style = {textAlign: 'right', paddingLeft: '.25em'}; - return - })} - + let titleElem = <>; + if (title) { + titleElem = ( + <> +

        + {title} +

        +
        + + ); + } + return ( + <> + {titleElem} +
        {elem}
        + + {rows.map((row: any[]) => { + return ( + + {row.map((elem: any, i: number) => { + let style = {}; + if (i !== 0) + style = { textAlign: "right", paddingLeft: ".25em" }; + return ( + + ); })} - -
        + {elem} +
        - ); -} \ No newline at end of file + + ); + })} + + + + ); +} diff --git a/src/ui/React/StdButton.tsx b/src/ui/React/StdButton.tsx index 6cfe37a8f..07ef86ee6 100644 --- a/src/ui/React/StdButton.tsx +++ b/src/ui/React/StdButton.tsx @@ -5,50 +5,57 @@ import * as React from "react"; interface IStdButtonProps { - addClasses?: string; - disabled?: boolean; - id?: string; - onClick?: (e: React.MouseEvent) => any; - style?: any; - text: string | JSX.Element; - tooltip?: string | JSX.Element; + addClasses?: string; + disabled?: boolean; + id?: string; + onClick?: (e: React.MouseEvent) => any; + style?: any; + text: string | JSX.Element; + tooltip?: string | JSX.Element; } type IInnerHTMLMarkup = { - __html: string; -} + __html: string; +}; export function StdButton(props: IStdButtonProps): React.ReactElement { - const hasTooltip = props.tooltip != null && props.tooltip !== ""; - let className = props.disabled ? "std-button-disabled" : "std-button"; - if (hasTooltip) { - className += " tooltip"; - } + const hasTooltip = props.tooltip != null && props.tooltip !== ""; + let className = props.disabled ? "std-button-disabled" : "std-button"; + if (hasTooltip) { + className += " tooltip"; + } - if (typeof props.addClasses === "string") { - className += ` ${props.addClasses}`; - } + if (typeof props.addClasses === "string") { + className += ` ${props.addClasses}`; + } - // Tooltip will be set using inner HTML - let tooltip; - if (hasTooltip) { - if(typeof props.tooltip === 'string') { - const tooltipMarkup: IInnerHTMLMarkup = { - __html: props.tooltip, - } - tooltip = - } else { - tooltip = {props.tooltip} - } + // Tooltip will be set using inner HTML + let tooltip; + if (hasTooltip) { + if (typeof props.tooltip === "string") { + const tooltipMarkup: IInnerHTMLMarkup = { + __html: props.tooltip, + }; + tooltip = ( + + ); + } else { + tooltip = {props.tooltip}; } + } - return ( - - ) + return ( + + ); } diff --git a/src/ui/React/StdButtonPurchased.tsx b/src/ui/React/StdButtonPurchased.tsx index afdab4cba..cc374c1f7 100644 --- a/src/ui/React/StdButtonPurchased.tsx +++ b/src/ui/React/StdButtonPurchased.tsx @@ -4,57 +4,65 @@ import * as React from "react"; interface IStdButtonPurchasedProps { - onClick?: (e: React.MouseEvent) => any; - style?: any; - text: string; - tooltip?: string; + onClick?: (e: React.MouseEvent) => any; + style?: any; + text: string; + tooltip?: string; } type IInnerHTMLMarkup = { - __html: string; -} - -export class StdButtonPurchased extends React.Component { - - constructor(props: IStdButtonPurchasedProps) { - super(props); - this.hasTooltip = this.hasTooltip.bind(this); - this.tooltip = this.tooltip.bind(this); - } - - hasTooltip(): boolean { - return this.props.tooltip != null && this.props.tooltip !== ""; - } - - tooltip(): string { - if(!this.props.tooltip) return ""; - return this.props.tooltip; - } - - render(): React.ReactNode { - let className = "std-button-bought"; - if (this.hasTooltip()) { - className += " tooltip"; - } - - // Tooltip will be set using inner HTML - let tooltipMarkup: IInnerHTMLMarkup = { - __html: "", - } - if (this.hasTooltip()) { - tooltipMarkup = { - __html: this.tooltip(), - } - } - - return ( - - ) - } + __html: string; +}; + +export class StdButtonPurchased extends React.Component< + IStdButtonPurchasedProps, + any +> { + constructor(props: IStdButtonPurchasedProps) { + super(props); + this.hasTooltip = this.hasTooltip.bind(this); + this.tooltip = this.tooltip.bind(this); + } + + hasTooltip(): boolean { + return this.props.tooltip != null && this.props.tooltip !== ""; + } + + tooltip(): string { + if (!this.props.tooltip) return ""; + return this.props.tooltip; + } + + render(): React.ReactNode { + let className = "std-button-bought"; + if (this.hasTooltip()) { + className += " tooltip"; + } + + // Tooltip will be set using inner HTML + let tooltipMarkup: IInnerHTMLMarkup = { + __html: "", + }; + if (this.hasTooltip()) { + tooltipMarkup = { + __html: this.tooltip(), + }; + } + + return ( + + ); + } } diff --git a/src/ui/React/createPopup.tsx b/src/ui/React/createPopup.tsx index 167681f2e..6d4d79d1e 100644 --- a/src/ui/React/createPopup.tsx +++ b/src/ui/React/createPopup.tsx @@ -6,8 +6,8 @@ * @param id The (hopefully) unique identifier for the popup container * @param rootComponent Root React Component for the content (NOT the popup containers themselves) */ -import * as React from "react"; -import * as ReactDOM from "react-dom"; +import * as React from "react"; +import * as ReactDOM from "react-dom"; import { Popup } from "./Popup"; @@ -16,18 +16,18 @@ import { removeElementById } from "../../../utils/uiHelpers/removeElementById"; let gameContainer: HTMLElement; -(function() { - function getGameContainer(): void { - const container = document.getElementById("entire-game-container"); - if (container == null) { - throw new Error(`Failed to find game container DOM element`) - } - - gameContainer = container; - document.removeEventListener("DOMContentLoaded", getGameContainer); +(function () { + function getGameContainer(): void { + const container = document.getElementById("entire-game-container"); + if (container == null) { + throw new Error(`Failed to find game container DOM element`); } - document.addEventListener("DOMContentLoaded", getGameContainer); + gameContainer = container; + document.removeEventListener("DOMContentLoaded", getGameContainer); + } + + document.addEventListener("DOMContentLoaded", getGameContainer); })(); // This variable is used to avoid setting the semi-transparent background @@ -35,45 +35,57 @@ let gameContainer: HTMLElement; let deepestPopupId = ""; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function createPopup(id: string, rootComponent: (props: T) => React.ReactElement, props: T): HTMLElement | null { - - let container = document.getElementById(id); - if (container == null) { - function onClick(this: HTMLElement, event: MouseEvent): any { - if(!event.srcElement) return; - if(!(event.srcElement instanceof HTMLElement)) return; - const clickedId = (event.srcElement as HTMLElement).id; - if(clickedId !== id) return; - removePopup(id); - } - const backgroundColor = deepestPopupId === "" ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0)'; - container = createElement("div", { - class: "popup-box-container", - display: "flex", - id: id, - backgroundColor: backgroundColor, - clickListener: onClick, - }); - - gameContainer.appendChild(container); +export function createPopup( + id: string, + rootComponent: (props: T) => React.ReactElement, + props: T, +): HTMLElement | null { + let container = document.getElementById(id); + if (container == null) { + function onClick(this: HTMLElement, event: MouseEvent): any { + if (!event.srcElement) return; + if (!(event.srcElement instanceof HTMLElement)) return; + const clickedId = (event.srcElement as HTMLElement).id; + if (clickedId !== id) return; + removePopup(id); } + const backgroundColor = + deepestPopupId === "" ? "rgba(0,0,0,0.5)" : "rgba(0,0,0,0)"; + container = createElement("div", { + class: "popup-box-container", + display: "flex", + id: id, + backgroundColor: backgroundColor, + clickListener: onClick, + }); - if(deepestPopupId === "") deepestPopupId = id; - ReactDOM.render(, container); + gameContainer.appendChild(container); + } - return container; + if (deepestPopupId === "") deepestPopupId = id; + ReactDOM.render( + , + container, + ); + + return container; } /** * Closes a popup created with the createPopup() function above */ export function removePopup(id: string): void { - const content = document.getElementById(`${id}`); - if (content == null) return; + const content = document.getElementById(`${id}`); + if (content == null) return; - ReactDOM.unmountComponentAtNode(content); + ReactDOM.unmountComponentAtNode(content); - removeElementById(id); - removeElementById(`${id}-close`); - if(id === deepestPopupId) deepestPopupId = ""; + removeElementById(id); + removeElementById(`${id}-close`); + if (id === deepestPopupId) deepestPopupId = ""; } diff --git a/src/ui/createStatusText.ts b/src/ui/createStatusText.ts index 58ee30947..72fc08a6b 100644 --- a/src/ui/createStatusText.ts +++ b/src/ui/createStatusText.ts @@ -10,21 +10,21 @@ let x: number | undefined; * @param text The status text to display */ export function createStatusText(text: string): void { - if (x !== undefined) { - clearTimeout(x); - // Likely not needed due to clearTimeout, but just in case... - x = undefined; - } + if (x !== undefined) { + clearTimeout(x); + // Likely not needed due to clearTimeout, but just in case... + x = undefined; + } - const statusElement: HTMLElement = getElementById("status-text"); - statusElement.style.display = "block"; - statusElement.classList.add("status-text"); - statusElement.innerText = text; - const handler: Action = () => { - statusElement.innerText = ""; - statusElement.style.display = "none"; - statusElement.classList.remove("status-text"); - }; + const statusElement: HTMLElement = getElementById("status-text"); + statusElement.style.display = "block"; + statusElement.classList.add("status-text"); + statusElement.innerText = text; + const handler: Action = () => { + statusElement.innerText = ""; + statusElement.style.display = "none"; + statusElement.classList.remove("status-text"); + }; - x = setTimeoutRef(handler, threeSeconds); + x = setTimeoutRef(handler, threeSeconds); } diff --git a/src/ui/navigationTracking.ts b/src/ui/navigationTracking.ts index 6005c7b56..2e4abd51f 100644 --- a/src/ui/navigationTracking.ts +++ b/src/ui/navigationTracking.ts @@ -3,151 +3,151 @@ * These pages are mutually exclusive. */ export enum Page { - /** - * (Default) The terminal is where the player issues all commands, executes scripts, etc. - */ - Terminal = "Terminal", + /** + * (Default) The terminal is where the player issues all commands, executes scripts, etc. + */ + Terminal = "Terminal", - /** - * Displays most of the statistics about the player. - */ - CharacterInfo = "CharacterInfo", + /** + * Displays most of the statistics about the player. + */ + CharacterInfo = "CharacterInfo", - /** - * The console for editing Netscript files. - */ - ScriptEditor = "ScriptEditor", + /** + * The console for editing Netscript files. + */ + ScriptEditor = "ScriptEditor", - /** - * Monitor the scripts currently executing across the servers. - */ - ActiveScripts = "ActiveScripts", + /** + * Monitor the scripts currently executing across the servers. + */ + ActiveScripts = "ActiveScripts", - /** - * View, purchase, and upgrade Hacknet nodes. - */ - HacknetNodes = "HacknetNodes", + /** + * View, purchase, and upgrade Hacknet nodes. + */ + HacknetNodes = "HacknetNodes", - /** - * The list of programs the player could potentially build. - */ - CreateProgram = "CreateProgram", + /** + * The list of programs the player could potentially build. + */ + CreateProgram = "CreateProgram", - /** - * The list of all factions, and invites, available to the player. - */ - Factions = "Factions", + /** + * The list of all factions, and invites, available to the player. + */ + Factions = "Factions", - /** - * Information about a specific faction. - */ - Faction = "Faction", + /** + * Information about a specific faction. + */ + Faction = "Faction", - /** - * The list of installed, and yet-to-be installed, augmentations the player has purchased. - */ - Augmentations = "Augmentations", + /** + * The list of installed, and yet-to-be installed, augmentations the player has purchased. + */ + Augmentations = "Augmentations", - /** - * List of milestones that players should follow. - */ - Milestones = "Milestones", + /** + * List of milestones that players should follow. + */ + Milestones = "Milestones", - /** - * A collection of in-game material to learn about the game. - */ - Tutorial = "Tutorial", + /** + * A collection of in-game material to learn about the game. + */ + Tutorial = "Tutorial", - /** - * A collection of items to manipulate the state of the game. Useful for development. - */ - DevMenu = "Dev Menu", + /** + * A collection of items to manipulate the state of the game. Useful for development. + */ + DevMenu = "Dev Menu", - /** - * Visiting a location in the world - */ - Location = "Location", + /** + * Visiting a location in the world + */ + Location = "Location", - /** - * A blocking page to show the player they are currently doing some action (building a program, working, etc.). - */ - workInProgress = "WorkInProgress", + /** + * A blocking page to show the player they are currently doing some action (building a program, working, etc.). + */ + workInProgress = "WorkInProgress", - /** - * A special screen to show the player they've reached a certain point in the game. - */ - RedPill = "RedPill", + /** + * A special screen to show the player they've reached a certain point in the game. + */ + RedPill = "RedPill", - /** - * A special screen to show the player they've reached a certain point in the game. - */ - CinematicText = "CinematicText", + /** + * A special screen to show the player they've reached a certain point in the game. + */ + CinematicText = "CinematicText", - /** - * Mini-game to infiltrate a company, gaining experience from successful progress. - */ - Infiltration = "Infiltration", + /** + * Mini-game to infiltrate a company, gaining experience from successful progress. + */ + Infiltration = "Infiltration", - /** - * View the in-game stock market. - */ - StockMarket = "StockMarket", + /** + * View the in-game stock market. + */ + StockMarket = "StockMarket", - /** - * Manage gang actions and members. - */ - Gang = "Gang", + /** + * Manage gang actions and members. + */ + Gang = "Gang", - /** - * Perform missions for a Faction. - */ - Mission = "Mission", + /** + * Perform missions for a Faction. + */ + Mission = "Mission", - /** - * Manage a corporation. - */ - Corporation = "Corporation", + /** + * Manage a corporation. + */ + Corporation = "Corporation", - /** - * Manage special Bladeburner activities. - */ - Bladeburner = "Bladeburner", + /** + * Manage special Bladeburner activities. + */ + Bladeburner = "Bladeburner", - /** - * Manage your Sleeves - */ - Sleeves = "Sleeves", + /** + * Manage your Sleeves + */ + Sleeves = "Sleeves", - /** - * Purchase Resleeves - */ - Resleeves = "Re-sleeving", + /** + * Purchase Resleeves + */ + Resleeves = "Re-sleeving", } /** * This class keeps track of player navigation/routing within the game. */ class Routing { - /** - * Tracking the what page the user is currently on. - */ - private currentPage: Page | null = null; + /** + * Tracking the what page the user is currently on. + */ + private currentPage: Page | null = null; - /** - * Determines if the player is currently on the specified page. - * @param page The page to compare against the current state. - */ - isOn(page: Page): boolean { - return this.currentPage === page; - } + /** + * Determines if the player is currently on the specified page. + * @param page The page to compare against the current state. + */ + isOn(page: Page): boolean { + return this.currentPage === page; + } - /** - * Routes the player to the appropriate page. - * @param page The page to navigate to. - */ - navigateTo(page: Page): void { - this.currentPage = page; - } + /** + * Routes the player to the appropriate page. + * @param page The page to navigate to. + */ + navigateTo(page: Page): void { + this.currentPage = page; + } } /** diff --git a/src/ui/numeralFormat.ts b/src/ui/numeralFormat.ts index 426641ec7..a7bdad48c 100644 --- a/src/ui/numeralFormat.ts +++ b/src/ui/numeralFormat.ts @@ -1,184 +1,189 @@ -import numeral from 'numeral'; -import 'numeral/locales/bg'; -import 'numeral/locales/cs'; -import 'numeral/locales/da-dk'; -import 'numeral/locales/de'; -import 'numeral/locales/en-au'; -import 'numeral/locales/en-gb'; -import 'numeral/locales/es'; -import 'numeral/locales/fr'; -import 'numeral/locales/hu'; -import 'numeral/locales/it'; -import 'numeral/locales/lv'; -import 'numeral/locales/no'; -import 'numeral/locales/pl'; -import 'numeral/locales/ru'; +import numeral from "numeral"; +import "numeral/locales/bg"; +import "numeral/locales/cs"; +import "numeral/locales/da-dk"; +import "numeral/locales/de"; +import "numeral/locales/en-au"; +import "numeral/locales/en-gb"; +import "numeral/locales/es"; +import "numeral/locales/fr"; +import "numeral/locales/hu"; +import "numeral/locales/it"; +import "numeral/locales/lv"; +import "numeral/locales/no"; +import "numeral/locales/pl"; +import "numeral/locales/ru"; /* eslint-disable class-methods-use-this */ const extraFormats = [1e15, 1e18, 1e21, 1e24, 1e27, 1e30]; -const extraNotations = ['q', 'Q', 's', 'S', 'o', 'n']; - +const extraNotations = ["q", "Q", "s", "S", "o", "n"]; class NumeralFormatter { - // Default Locale - defaultLocale = "en"; + // Default Locale + defaultLocale = "en"; - constructor() { - this.defaultLocale = 'en'; + constructor() { + this.defaultLocale = "en"; + } + + updateLocale(l: string): boolean { + if (numeral.locale(l) == null) { + console.warn(`Invalid locale for numeral: ${l}`); + + numeral.locale(this.defaultLocale); + return false; } + return true; + } - updateLocale(l: string): boolean { - if (numeral.locale(l) == null) { - console.warn(`Invalid locale for numeral: ${l}`); - - numeral.locale(this.defaultLocale); - return false; - } - return true; + format(n: number, format: string): string { + // numeraljs doesnt properly format numbers that are too big or too small + if (Math.abs(n) < 1e-6) { + n = 0; } - - format(n: number, format: string): string { - // numeraljs doesnt properly format numbers that are too big or too small - if (Math.abs(n) < 1e-6) { n = 0; } - const answer = numeral(n).format(format); - if (answer === 'NaN') { - return `${n}`; - } - return answer; + const answer = numeral(n).format(format); + if (answer === "NaN") { + return `${n}`; } + return answer; + } - formatBigNumber(n: number): string { - return this.format(n, "0.000a"); - } + formatBigNumber(n: number): string { + return this.format(n, "0.000a"); + } - // TODO: leverage numeral.js to do it. This function also implies you can - // use this format in some text field but you can't. ( "1t" will parse but - // "1s" will not) - formatReallyBigNumber(n: number, decimalPlaces = 3): string { - if(n === Infinity) return "∞"; - for(let i = 0; i < extraFormats.length; i++) { - if(extraFormats[i] < n && n <= extraFormats[i]*1000) { - return this.format(n/extraFormats[i], '0.'+'0'.repeat(decimalPlaces))+extraNotations[i]; - } - } - if(Math.abs(n) < 1000) { - return this.format(n, '0.'+'0'.repeat(decimalPlaces)); - } - const str = this.format(n, '0.'+'0'.repeat(decimalPlaces) + 'a'); - if(str === "NaNt") return this.format(n, '0.' + ' '.repeat(decimalPlaces) + 'e+0'); - return str; + // TODO: leverage numeral.js to do it. This function also implies you can + // use this format in some text field but you can't. ( "1t" will parse but + // "1s" will not) + formatReallyBigNumber(n: number, decimalPlaces = 3): string { + if (n === Infinity) return "∞"; + for (let i = 0; i < extraFormats.length; i++) { + if (extraFormats[i] < n && n <= extraFormats[i] * 1000) { + return ( + this.format(n / extraFormats[i], "0." + "0".repeat(decimalPlaces)) + + extraNotations[i] + ); + } } + if (Math.abs(n) < 1000) { + return this.format(n, "0." + "0".repeat(decimalPlaces)); + } + const str = this.format(n, "0." + "0".repeat(decimalPlaces) + "a"); + if (str === "NaNt") + return this.format(n, "0." + " ".repeat(decimalPlaces) + "e+0"); + return str; + } - formatHp(n: number): string { - if(n < 1e6){ - return this.format(n, "0,0"); - } - return this.formatReallyBigNumber(n); + formatHp(n: number): string { + if (n < 1e6) { + return this.format(n, "0,0"); } + return this.formatReallyBigNumber(n); + } - formatMoney(n: number): string { - return "$" + this.formatReallyBigNumber(n); - } + formatMoney(n: number): string { + return "$" + this.formatReallyBigNumber(n); + } - formatSkill(n: number): string { - if(n < 1e15){ - return this.format(n, "0,0"); - } - return this.formatReallyBigNumber(n); + formatSkill(n: number): string { + if (n < 1e15) { + return this.format(n, "0,0"); } + return this.formatReallyBigNumber(n); + } - formatExp(n: number): string { - return this.formatReallyBigNumber(n); - } + formatExp(n: number): string { + return this.formatReallyBigNumber(n); + } - formatHashes(n: number): string { - return this.formatReallyBigNumber(n); - } + formatHashes(n: number): string { + return this.formatReallyBigNumber(n); + } - formatReputation(n: number): string { - return this.formatReallyBigNumber(n); - } + formatReputation(n: number): string { + return this.formatReallyBigNumber(n); + } - formatFavor(n: number): string { - return this.format(n, "0,0"); - } + formatFavor(n: number): string { + return this.format(n, "0,0"); + } - formatRAM(n: number): string { - return this.format(n, "0.00")+"GB"; - } + formatRAM(n: number): string { + return this.format(n, "0.00") + "GB"; + } - formatPercentage(n: number, decimalPlaces = 2): string { - const formatter: string = "0." + "0".repeat(decimalPlaces) + "%"; - return this.format(n, formatter); - } + formatPercentage(n: number, decimalPlaces = 2): string { + const formatter: string = "0." + "0".repeat(decimalPlaces) + "%"; + return this.format(n, formatter); + } - formatServerSecurity(n: number): string { - return this.format(n, "0,0.000"); - } + formatServerSecurity(n: number): string { + return this.format(n, "0,0.000"); + } - formatRespect(n: number): string { - return this.formatReallyBigNumber(n, 5); - } + formatRespect(n: number): string { + return this.formatReallyBigNumber(n, 5); + } - formatWanted(n: number): string { - return this.formatReallyBigNumber(n, 5); - } + formatWanted(n: number): string { + return this.formatReallyBigNumber(n, 5); + } - formatMultiplier(n: number): string { - return this.format(n, "0,0.00"); - } + formatMultiplier(n: number): string { + return this.format(n, "0,0.00"); + } - formatSleeveShock(n: number): string { - return this.format(n, "0,0.000"); - } + formatSleeveShock(n: number): string { + return this.format(n, "0,0.000"); + } - formatSleeveSynchro(n: number): string { - return this.format(n, "0,0.000"); - } + formatSleeveSynchro(n: number): string { + return this.format(n, "0,0.000"); + } - formatSleeveMemory(n: number): string { - return this.format(n, "0"); - } + formatSleeveMemory(n: number): string { + return this.format(n, "0"); + } - formatPopulation(n: number): string { - return this.format(n, "0.000a"); - } + formatPopulation(n: number): string { + return this.format(n, "0.000a"); + } - formatStamina(n: number): string { - return this.format(n, "0.0"); - } + formatStamina(n: number): string { + return this.format(n, "0.0"); + } - formatShares(n: number): string { - if (n < 1000) { - return this.format(n, "0"); - } - return this.formatReallyBigNumber(n); + formatShares(n: number): string { + if (n < 1000) { + return this.format(n, "0"); } + return this.formatReallyBigNumber(n); + } - formatInfiltrationSecurity(n: number): string { - return this.formatReallyBigNumber(n); - } + formatInfiltrationSecurity(n: number): string { + return this.formatReallyBigNumber(n); + } - formatThreads(n: number): string { - return this.format(n, "0,0"); - } + formatThreads(n: number): string { + return this.format(n, "0,0"); + } - parseMoney(s: string): number { - // numeral library does not handle formats like 1e10 well (returns 110), - // so if both return a valid number, return the biggest one - const numeralValue = numeral(s).value(); - const parsed = parseFloat(s); - if (isNaN(parsed) && numeralValue === null) { - return NaN; - } else if (isNaN(parsed)) { - return numeralValue; - } else if (numeralValue === null) { - return parsed; - } else { - return Math.max(numeralValue, parsed); - } + parseMoney(s: string): number { + // numeral library does not handle formats like 1e10 well (returns 110), + // so if both return a valid number, return the biggest one + const numeralValue = numeral(s).value(); + const parsed = parseFloat(s); + if (isNaN(parsed) && numeralValue === null) { + return NaN; + } else if (isNaN(parsed)) { + return numeralValue; + } else if (numeralValue === null) { + return parsed; + } else { + return Math.max(numeralValue, parsed); } + } } export const numeralWrapper = new NumeralFormatter(); diff --git a/src/ui/postToTerminal.tsx b/src/ui/postToTerminal.tsx index 7f06e03ff..a842154e5 100644 --- a/src/ui/postToTerminal.tsx +++ b/src/ui/postToTerminal.tsx @@ -1,4 +1,4 @@ -import { renderToStaticMarkup } from "react-dom/server" +import { renderToStaticMarkup } from "react-dom/server"; import { getElementById } from "../../utils/uiHelpers/getElementById"; /** @@ -6,11 +6,11 @@ import { getElementById } from "../../utils/uiHelpers/getElementById"; * @param input Text or HTML to output to the terminal */ export function post(input: string): void { - postContent(input); + postContent(input); } export function postError(input: string): void { - postContent(`ERROR: ${input}`, { color: "#ff2929" }); + postContent(`ERROR: ${input}`, { color: "#ff2929" }); } /** @@ -18,7 +18,7 @@ export function postError(input: string): void { * @param input Text or HTML to output to the terminal */ export function hackProgressBarPost(input: string): void { - postContent(input, { id: "hack-progress-bar" }); + postContent(input, { id: "hack-progress-bar" }); } /** @@ -26,29 +26,38 @@ export function hackProgressBarPost(input: string): void { * @param input Text or HTML to output to the terminal */ export function hackProgressPost(input: string): void { - postContent(input, { id: "hack-progress" }); + postContent(input, { id: "hack-progress" }); } interface IPostContentConfig { - id?: string; // Replaces class, if specified - color?: string; // Additional class for terminal-line. Does NOT replace + id?: string; // Replaces class, if specified + color?: string; // Additional class for terminal-line. Does NOT replace } export function postElement(element: JSX.Element): void { - postContent(renderToStaticMarkup(element)); + postContent(renderToStaticMarkup(element)); } -export function postContent(input: string, config: IPostContentConfig = {}): void { - // tslint:disable-next-line:max-line-length - const style = `color: ${config.color != null ? config.color : "var(--my-font-color)"}; background-color:var(--my-background-color);${config.id === undefined ? " white-space:pre-wrap;" : ""}`; - // tslint:disable-next-line:max-line-length - const content = `${input}`; - const inputElement: HTMLElement = getElementById("terminal-input"); - inputElement.insertAdjacentHTML("beforebegin", content); - scrollTerminalToBottom(); +export function postContent( + input: string, + config: IPostContentConfig = {}, +): void { + // tslint:disable-next-line:max-line-length + const style = `color: ${ + config.color != null ? config.color : "var(--my-font-color)" + }; background-color:var(--my-background-color);${ + config.id === undefined ? " white-space:pre-wrap;" : "" + }`; + // tslint:disable-next-line:max-line-length + const content = `${input}`; + const inputElement: HTMLElement = getElementById("terminal-input"); + inputElement.insertAdjacentHTML("beforebegin", content); + scrollTerminalToBottom(); } function scrollTerminalToBottom(): void { - const container: HTMLElement = getElementById("terminal-container"); - container.scrollTop = container.scrollHeight; + const container: HTMLElement = getElementById("terminal-container"); + container.scrollTop = container.scrollHeight; } diff --git a/src/ui/setSettingsLabels.js b/src/ui/setSettingsLabels.js index bf8ba0426..9c0ef74df 100644 --- a/src/ui/setSettingsLabels.js +++ b/src/ui/setSettingsLabels.js @@ -1,133 +1,149 @@ -import {Engine} from "../engine"; -import {Settings} from "../Settings/Settings"; -import {numeralWrapper} from "./numeralFormat"; - +import { Engine } from "../engine"; +import { Settings } from "../Settings/Settings"; +import { numeralWrapper } from "./numeralFormat"; function setSettingsLabels() { - function setAutosaveLabel(elem) { - if(Settings.AutosaveInterval === 0) { - elem.innerHTML = `disabled`; - } else { - elem.innerHTML = `every ${Settings.AutosaveInterval}s`; - } + function setAutosaveLabel(elem) { + if (Settings.AutosaveInterval === 0) { + elem.innerHTML = `disabled`; + } else { + elem.innerHTML = `every ${Settings.AutosaveInterval}s`; } + } - const nsExecTime = document.getElementById("settingsNSExecTimeRangeValLabel"); - const nsLogLimit = document.getElementById("settingsNSLogRangeValLabel"); - const nsPortLimit = document.getElementById("settingsNSPortRangeValLabel"); - const suppressMsgs = document.getElementById("settingsSuppressMessages"); - const suppressFactionInv = document.getElementById("settingsSuppressFactionInvites") - const suppressTravelConfirmation = document.getElementById("settingsSuppressTravelConfirmation"); - const suppressBuyAugmentationConfirmation = document.getElementById("settingsSuppressBuyAugmentationConfirmation"); - const suppressHospitalizationPopup = document.getElementById("settingsSuppressHospitalizationPopup"); - const suppressBladeburnerPopup = document.getElementById("settingsSuppressBladeburnerPopup"); - const autosaveInterval = document.getElementById("settingsAutosaveIntervalValLabel"); - const disableHotkeys = document.getElementById("settingsDisableHotkeys"); - const disableASCIIArt = document.getElementById("settingsDisableASCIIArt"); - const disableTextEffects = document.getElementById("settingsDisableTextEffects"); - const locale = document.getElementById("settingsLocale"); + const nsExecTime = document.getElementById("settingsNSExecTimeRangeValLabel"); + const nsLogLimit = document.getElementById("settingsNSLogRangeValLabel"); + const nsPortLimit = document.getElementById("settingsNSPortRangeValLabel"); + const suppressMsgs = document.getElementById("settingsSuppressMessages"); + const suppressFactionInv = document.getElementById( + "settingsSuppressFactionInvites", + ); + const suppressTravelConfirmation = document.getElementById( + "settingsSuppressTravelConfirmation", + ); + const suppressBuyAugmentationConfirmation = document.getElementById( + "settingsSuppressBuyAugmentationConfirmation", + ); + const suppressHospitalizationPopup = document.getElementById( + "settingsSuppressHospitalizationPopup", + ); + const suppressBladeburnerPopup = document.getElementById( + "settingsSuppressBladeburnerPopup", + ); + const autosaveInterval = document.getElementById( + "settingsAutosaveIntervalValLabel", + ); + const disableHotkeys = document.getElementById("settingsDisableHotkeys"); + const disableASCIIArt = document.getElementById("settingsDisableASCIIArt"); + const disableTextEffects = document.getElementById( + "settingsDisableTextEffects", + ); + const locale = document.getElementById("settingsLocale"); - //Initialize values on labels - nsExecTime.innerHTML = Settings.CodeInstructionRunTime + "ms"; - nsLogLimit.innerHTML = Settings.MaxLogCapacity; - nsPortLimit.innerHTML = Settings.MaxPortCapacity; - suppressMsgs.checked = Settings.SuppressMessages; - suppressFactionInv.checked = Settings.SuppressFactionInvites; - suppressTravelConfirmation.checked = Settings.SuppressTravelConfirmation; - suppressBuyAugmentationConfirmation.checked = Settings.SuppressBuyAugmentationConfirmation; - suppressHospitalizationPopup.checked = Settings.SuppressHospitalizationPopup; - suppressBladeburnerPopup.checked = Settings.SuppressBladeburnerPopup; + //Initialize values on labels + nsExecTime.innerHTML = Settings.CodeInstructionRunTime + "ms"; + nsLogLimit.innerHTML = Settings.MaxLogCapacity; + nsPortLimit.innerHTML = Settings.MaxPortCapacity; + suppressMsgs.checked = Settings.SuppressMessages; + suppressFactionInv.checked = Settings.SuppressFactionInvites; + suppressTravelConfirmation.checked = Settings.SuppressTravelConfirmation; + suppressBuyAugmentationConfirmation.checked = + Settings.SuppressBuyAugmentationConfirmation; + suppressHospitalizationPopup.checked = Settings.SuppressHospitalizationPopup; + suppressBladeburnerPopup.checked = Settings.SuppressBladeburnerPopup; + setAutosaveLabel(autosaveInterval); + disableHotkeys.checked = Settings.DisableHotkeys; + disableASCIIArt.checked = Settings.CityListView; + disableTextEffects.checked = Settings.DisableTextEffects; + locale.value = Settings.Locale; + numeralWrapper.updateLocale(Settings.Locale); //Initialize locale + + //Set handlers for when input changes for sliders + const nsExecTimeInput = document.getElementById("settingsNSExecTimeRangeVal"); + const nsLogRangeInput = document.getElementById("settingsNSLogRangeVal"); + const nsPortRangeInput = document.getElementById("settingsNSPortRangeVal"); + const nsAutosaveIntervalInput = document.getElementById( + "settingsAutosaveIntervalVal", + ); + nsExecTimeInput.value = Settings.CodeInstructionRunTime; + nsLogRangeInput.value = Settings.MaxLogCapacity; + nsPortRangeInput.value = Settings.MaxPortCapacity; + nsAutosaveIntervalInput.value = Settings.AutosaveInterval; + + nsExecTimeInput.oninput = function () { + nsExecTime.innerHTML = this.value + "ms"; + Settings.CodeInstructionRunTime = this.value; + }; + + nsLogRangeInput.oninput = function () { + nsLogLimit.innerHTML = this.value; + Settings.MaxLogCapacity = this.value; + }; + + nsPortRangeInput.oninput = function () { + nsPortLimit.innerHTML = this.value; + Settings.MaxPortCapacity = this.value; + }; + + nsAutosaveIntervalInput.oninput = function () { + Settings.AutosaveInterval = Number(this.value); setAutosaveLabel(autosaveInterval); - disableHotkeys.checked = Settings.DisableHotkeys; - disableASCIIArt.checked = Settings.CityListView; - disableTextEffects.checked = Settings.DisableTextEffects; - locale.value = Settings.Locale; - numeralWrapper.updateLocale(Settings.Locale); //Initialize locale - - //Set handlers for when input changes for sliders - const nsExecTimeInput = document.getElementById("settingsNSExecTimeRangeVal"); - const nsLogRangeInput = document.getElementById("settingsNSLogRangeVal"); - const nsPortRangeInput = document.getElementById("settingsNSPortRangeVal"); - const nsAutosaveIntervalInput = document.getElementById("settingsAutosaveIntervalVal"); - nsExecTimeInput.value = Settings.CodeInstructionRunTime; - nsLogRangeInput.value = Settings.MaxLogCapacity; - nsPortRangeInput.value = Settings.MaxPortCapacity; - nsAutosaveIntervalInput.value = Settings.AutosaveInterval; - - nsExecTimeInput.oninput = function() { - nsExecTime.innerHTML = this.value + 'ms'; - Settings.CodeInstructionRunTime = this.value; - }; - - nsLogRangeInput.oninput = function() { - nsLogLimit.innerHTML = this.value; - Settings.MaxLogCapacity = this.value; - }; - - nsPortRangeInput.oninput = function() { - nsPortLimit.innerHTML = this.value; - Settings.MaxPortCapacity = this.value; - }; - - nsAutosaveIntervalInput.oninput = function() { - Settings.AutosaveInterval = Number(this.value); - setAutosaveLabel(autosaveInterval) - if (Number(this.value) === 0) { - Engine.Counters.autoSaveCounter = Infinity; - } else { - Engine.Counters.autoSaveCounter = Number(this.value) * 5; - } - }; - - //Set handlers for when settings change on checkboxes - suppressMsgs.onclick = function() { - Settings.SuppressMessages = this.checked; - }; - - suppressFactionInv.onclick = function() { - Settings.SuppressFactionInvites = this.checked; - }; - - suppressTravelConfirmation.onclick = function() { - Settings.SuppressTravelConfirmation = this.checked; - }; - - suppressBuyAugmentationConfirmation.onclick = function() { - Settings.SuppressBuyAugmentationConfirmation = this.checked; - }; - - suppressHospitalizationPopup.onclick = function() { - Settings.SuppressHospitalizationPopup = this.checked; + if (Number(this.value) === 0) { + Engine.Counters.autoSaveCounter = Infinity; + } else { + Engine.Counters.autoSaveCounter = Number(this.value) * 5; } + }; - suppressBladeburnerPopup.onclick = function() { - Settings.SuppressBladeburnerPopup = this.checked; - } - - disableHotkeys.onclick = function() { - Settings.DisableHotkeys = this.checked; - } - - disableASCIIArt.onclick = function() { - Settings.DisableASCIIArt = this.checked; - } - - disableTextEffects.onclick = function() { - Settings.DisableTextEffects = this.checked; - } - - //Locale selector - locale.onchange = function() { - if (!numeralWrapper.updateLocale(locale.value)) { - console.warn(`Invalid locale for numeral: ${locale.value}`); - - let defaultValue = 'en'; - Settings.Locale = defaultValue; - locale.value = defaultValue; - return; - } - Settings.Locale = locale.value; + //Set handlers for when settings change on checkboxes + suppressMsgs.onclick = function () { + Settings.SuppressMessages = this.checked; + }; + + suppressFactionInv.onclick = function () { + Settings.SuppressFactionInvites = this.checked; + }; + + suppressTravelConfirmation.onclick = function () { + Settings.SuppressTravelConfirmation = this.checked; + }; + + suppressBuyAugmentationConfirmation.onclick = function () { + Settings.SuppressBuyAugmentationConfirmation = this.checked; + }; + + suppressHospitalizationPopup.onclick = function () { + Settings.SuppressHospitalizationPopup = this.checked; + }; + + suppressBladeburnerPopup.onclick = function () { + Settings.SuppressBladeburnerPopup = this.checked; + }; + + disableHotkeys.onclick = function () { + Settings.DisableHotkeys = this.checked; + }; + + disableASCIIArt.onclick = function () { + Settings.DisableASCIIArt = this.checked; + }; + + disableTextEffects.onclick = function () { + Settings.DisableTextEffects = this.checked; + }; + + //Locale selector + locale.onchange = function () { + if (!numeralWrapper.updateLocale(locale.value)) { + console.warn(`Invalid locale for numeral: ${locale.value}`); + + let defaultValue = "en"; + Settings.Locale = defaultValue; + locale.value = defaultValue; + return; } + Settings.Locale = locale.value; + }; } export { setSettingsLabels }; diff --git a/src/utils/EventEmitter.ts b/src/utils/EventEmitter.ts index 5064ac05f..e339d31f2 100644 --- a/src/utils/EventEmitter.ts +++ b/src/utils/EventEmitter.ts @@ -6,45 +6,44 @@ import { IMap } from "../types"; type cbFn = (...args: any[]) => any; export interface ISubscriber { - /** - * Callback function that will be run when an event is emitted - */ - cb: cbFn; + /** + * Callback function that will be run when an event is emitted + */ + cb: cbFn; - /** - * Name/identifier for this subscriber - */ - id: string; + /** + * Name/identifier for this subscriber + */ + id: string; } export class EventEmitter { - /** - * Map of Subscriber name -> Callback function - */ - subscribers: IMap = {}; + /** + * Map of Subscriber name -> Callback function + */ + subscribers: IMap = {}; - constructor(subs?: ISubscriber[]) { - if (Array.isArray(subs)) { - for (const s of subs) { - this.addSubscriber(s); - } - } + constructor(subs?: ISubscriber[]) { + if (Array.isArray(subs)) { + for (const s of subs) { + this.addSubscriber(s); + } } + } - addSubscriber(s: ISubscriber): void { - this.subscribers[s.id] = s.cb; - } - - emitEvent(...args: any[]): void { - for (const s in this.subscribers) { - const sub = this.subscribers[s]; - - sub(args); - } - } - - removeSubscriber(id: string): void { - delete this.subscribers[id]; + addSubscriber(s: ISubscriber): void { + this.subscribers[s.id] = s.cb; + } + + emitEvent(...args: any[]): void { + for (const s in this.subscribers) { + const sub = this.subscribers[s]; + + sub(args); } + } + removeSubscriber(id: string): void { + delete this.subscribers[id]; + } } diff --git a/src/utils/MoneySourceTracker.ts b/src/utils/MoneySourceTracker.ts index e74ef221f..b8493fa7b 100644 --- a/src/utils/MoneySourceTracker.ts +++ b/src/utils/MoneySourceTracker.ts @@ -2,59 +2,65 @@ * This is an object that is used to keep track of where all of the player's * money is coming from (or going to) */ -import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { + Generic_fromJSON, + Generic_toJSON, + Reviver, +} from "../../utils/JSONReviver"; export class MoneySourceTracker { - // eslint-disable-next-line @typescript-eslint/ban-types - [key: string]: number | Function; - - bladeburner = 0; - casino = 0; - class = 0; - codingcontract = 0; - corporation = 0; - crime = 0; - gang = 0; - hacking = 0; - hacknetnode = 0; - hospitalization = 0; - infiltration = 0; - sleeves = 0; - stock = 0; - total = 0; - work = 0; + // eslint-disable-next-line @typescript-eslint/ban-types + [key: string]: number | Function; - // Record money earned - record(amt: number, source: string): void { - const sanitizedSource = source.toLowerCase(); - if (typeof this[sanitizedSource] !== "number") { - console.warn(`MoneySourceTracker.record() called with invalid source: ${source}`); - return; - } + bladeburner = 0; + casino = 0; + class = 0; + codingcontract = 0; + corporation = 0; + crime = 0; + gang = 0; + hacking = 0; + hacknetnode = 0; + hospitalization = 0; + infiltration = 0; + sleeves = 0; + stock = 0; + total = 0; + work = 0; - ( this[sanitizedSource]) += amt; - this.total += amt; + // Record money earned + record(amt: number, source: string): void { + const sanitizedSource = source.toLowerCase(); + if (typeof this[sanitizedSource] !== "number") { + console.warn( + `MoneySourceTracker.record() called with invalid source: ${source}`, + ); + return; } - // Reset the money tracker by setting all stats to 0 - reset(): void { - for (const prop in this) { - if (typeof this[prop] === "number") { - (this[prop] as number) = 0; - } - } - } + (this[sanitizedSource]) += amt; + this.total += amt; + } - // Serialize the current object to a JSON save state. - toJSON(): any { - return Generic_toJSON("MoneySourceTracker", this); + // Reset the money tracker by setting all stats to 0 + reset(): void { + for (const prop in this) { + if (typeof this[prop] === "number") { + (this[prop] as number) = 0; + } } + } - // Initiatizes a MoneySourceTracker object from a JSON save state. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - static fromJSON(value: any): MoneySourceTracker { - return Generic_fromJSON(MoneySourceTracker, value.data); - } + // Serialize the current object to a JSON save state. + toJSON(): any { + return Generic_toJSON("MoneySourceTracker", this); + } + + // Initiatizes a MoneySourceTracker object from a JSON save state. + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromJSON(value: any): MoneySourceTracker { + return Generic_fromJSON(MoneySourceTracker, value.data); + } } Reviver.constructors.MoneySourceTracker = MoneySourceTracker; diff --git a/src/utils/calculateEffectWithFactors.ts b/src/utils/calculateEffectWithFactors.ts index fc394ff3c..52ed536e4 100644 --- a/src/utils/calculateEffectWithFactors.ts +++ b/src/utils/calculateEffectWithFactors.ts @@ -16,30 +16,38 @@ * can be called with the stat and the exponential/linear factors. The other is a * class where the exponential and linear factors are defined upon construction. */ -export function calculateEffectWithFactors(n: number, expFac: number, linearFac: number): number { - if (expFac <= 0 || expFac >= 1) { - console.warn(`Exponential factor is ${expFac}. This is not an intended value for it`); - } - if (linearFac < 1) { - console.warn(`Linear factor is ${linearFac}. This is not an intended value for it`); - } +export function calculateEffectWithFactors( + n: number, + expFac: number, + linearFac: number, +): number { + if (expFac <= 0 || expFac >= 1) { + console.warn( + `Exponential factor is ${expFac}. This is not an intended value for it`, + ); + } + if (linearFac < 1) { + console.warn( + `Linear factor is ${linearFac}. This is not an intended value for it`, + ); + } - return (Math.pow(n, expFac)) + (n / linearFac); + return Math.pow(n, expFac) + n / linearFac; } export class EffectWithFactors { - // Exponential factor - private expFac: number; + // Exponential factor + private expFac: number; - // Linear Factor - private linearFac: number; + // Linear Factor + private linearFac: number; - constructor(expFac: number, linearFac: number) { - this.expFac = expFac; - this.linearFac = linearFac; - } + constructor(expFac: number, linearFac: number) { + this.expFac = expFac; + this.linearFac = linearFac; + } - calculate(n: number): number { - return calculateEffectWithFactors(n, this.expFac, this.linearFac); - } + calculate(n: number): number { + return calculateEffectWithFactors(n, this.expFac, this.linearFac); + } } diff --git a/src/utils/helpers/createRandomString.ts b/src/utils/helpers/createRandomString.ts index 97261a919..22ab439bc 100644 --- a/src/utils/helpers/createRandomString.ts +++ b/src/utils/helpers/createRandomString.ts @@ -2,11 +2,11 @@ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; export function createRandomString(n: number): string { - let str = ""; + let str = ""; - for (let i = 0; i < n; ++i) { - str += chars.charAt(Math.floor(Math.random() * chars.length)); - } + for (let i = 0; i < n; ++i) { + str += chars.charAt(Math.floor(Math.random() * chars.length)); + } - return str; + return str; } diff --git a/src/utils/helpers/is2DArray.ts b/src/utils/helpers/is2DArray.ts index f8763921b..7475dac83 100644 --- a/src/utils/helpers/is2DArray.ts +++ b/src/utils/helpers/is2DArray.ts @@ -2,7 +2,11 @@ // For this, a 2D array is an array which contains only other arrays. // If one element in the array is a number or string, it is NOT a 2D array export function is2DArray(arr: any[]): boolean { - if (arr.constructor !== Array) { return false; } + if (arr.constructor !== Array) { + return false; + } - return arr.every((e) => { return e.constructor === Array; }); + return arr.every((e) => { + return e.constructor === Array; + }); } diff --git a/src/utils/helpers/isValidNumber.ts b/src/utils/helpers/isValidNumber.ts index a501a3aeb..ac0fff824 100644 --- a/src/utils/helpers/isValidNumber.ts +++ b/src/utils/helpers/isValidNumber.ts @@ -3,5 +3,5 @@ * must be a "number" type and cannot be NaN */ export function isValidNumber(n: number): boolean { - return (typeof n === "number") && !isNaN(n); + return typeof n === "number" && !isNaN(n); } diff --git a/stylelint.config.js b/stylelint.config.js index d662a5936..71ebf7ea8 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -1,172 +1,172 @@ /* eslint-disable spaced-comment */ module.exports = { - plugins: [ - "stylelint-declaration-use-variable", - "stylelint-order", /*, - "stylelint-scss" */ + plugins: [ + "stylelint-declaration-use-variable", + "stylelint-order" /*, + "stylelint-scss" */, + ], + rules: { + "at-rule-blacklist": [], + // "at-rule-empty-line-before": [ + // "always", + // { + // except: [ + // "inside-block", "blockless-after-same-name-blockless" + // ], + // ignore: [ + // "after-comment" + // ] + // } + // ], + "at-rule-name-case": "lower", + "at-rule-name-newline-after": "always-multi-line", + "at-rule-name-space-after": "always", + // "at-rule-no-vendor-prefix": true, + "at-rule-semicolon-newline-after": "always", + "at-rule-semicolon-space-before": "never", + // "at-rule-whitelist": [ + // "content", + // "else", + // "function", + // "if", + // "import", + // "include", + // "keyframes", + // "mixin", + // "return" + // ], + "block-closing-brace-empty-line-before": "never", + "block-closing-brace-newline-after": "always", + "block-closing-brace-newline-before": "always-multi-line", + "block-no-empty": true, + // "block-opening-brace-newline-after": "always", + // "block-opening-brace-newline-before": "never-single-line", + // "block-opening-brace-space-before": "always", + "color-hex-case": "lower", + "color-hex-length": "short", + // "color-named": "never", + //"color-no-hex": true, + "color-no-invalid-hex": true, + // "comment-empty-line-before": "always", + "comment-no-empty": true, + "comment-whitespace-inside": "always", + "comment-word-blacklist": [], + "custom-media-pattern": ".+", + "custom-property-empty-line-before": "never", + "custom-property-pattern": "my-.+", + "declaration-bang-space-after": "never", + "declaration-bang-space-before": "always", + // "declaration-block-no-duplicate-properties": true, + "declaration-block-no-redundant-longhand-properties": true, + "declaration-block-no-shorthand-property-overrides": true, + // "declaration-block-semicolon-newline-after": "always", + "declaration-block-semicolon-newline-before": "never-multi-line", + "declaration-block-semicolon-space-before": "never", + // "declaration-block-single-line-max-declarations": 1, + "declaration-block-trailing-semicolon": "always", + "declaration-colon-newline-after": "always-multi-line", + "declaration-colon-space-after": "always-single-line", + "declaration-colon-space-before": "never", + // "declaration-empty-line-before": "never", + //"declaration-no-important": true, + "declaration-property-unit-blacklist": {}, + "declaration-property-unit-whitelist": {}, + "declaration-property-value-blacklist": {}, + "declaration-property-value-whitelist": {}, + // "font-family-name-quotes": "always-where-recommended", + "font-family-no-duplicate-names": true, + "font-family-no-missing-generic-family-keyword": true, + // "font-weight-notation": "numeric", + "function-blacklist": [], + "function-calc-no-unspaced-operator": true, + "function-comma-newline-after": "always-multi-line", + "function-comma-newline-before": "never-multi-line", + "function-comma-space-after": "always-single-line", + "function-comma-space-before": "never", + "function-linear-gradient-no-nonstandard-direction": true, + "function-max-empty-lines": 1, + "function-name-case": "lower", + "function-parentheses-newline-inside": "never-multi-line", + "function-parentheses-space-inside": "never", + "function-url-no-scheme-relative": true, + "function-url-quotes": "always", + "function-url-scheme-blacklist": [], + "function-url-scheme-whitelist": [], + // "function-whitelist": [ + // "box-shadow-args", + // "map-get", + // "rgba", + // "skew", + // "var" + // ], + "keyframe-declaration-no-important": true, + "length-zero-no-unit": true, + "max-empty-lines": 1, + "max-line-length": 160, + "max-nesting-depth": 99, + "media-feature-colon-space-after": "always", + "media-feature-colon-space-before": "never", + "media-feature-name-blacklist": [], + "media-feature-name-case": "lower", + "media-feature-name-no-unknown": true, + "media-feature-name-no-vendor-prefix": true, + "media-feature-name-whitelist": [], + "media-feature-parentheses-space-inside": "never", + "media-feature-range-operator-space-after": "always", + "media-feature-range-operator-space-before": "always", + "media-query-list-comma-newline-after": "always-multi-line", + "media-query-list-comma-newline-before": "never-multi-line", + "media-query-list-comma-space-after": "always-single-line", + "media-query-list-comma-space-before": "never", + // "no-descending-specificity": true, + "no-duplicate-at-import-rules": true, + // "no-duplicate-selectors": true, + "no-empty-source": true, + "no-eol-whitespace": true, + "no-extra-semicolons": true, + "no-invalid-double-slash-comments": true, + "no-missing-end-of-source-newline": true, + "no-unknown-animations": true, + "number-leading-zero": "always", + "number-max-precision": [4, { ignoreUnits: ["%"] }], + // "number-no-trailing-zeros": true, + "order/order": [ + [ + "dollar-variables", + "at-variables", + "custom-properties", + { + type: "at-rule", + name: "extend", + }, + { + type: "at-rule", + name: "include", + }, + "declarations", + "rules", + "at-rules", + "less-mixins", + ], + { + unspecified: "bottom", + }, ], - rules: { - "at-rule-blacklist": [], -// "at-rule-empty-line-before": [ -// "always", -// { -// except: [ -// "inside-block", "blockless-after-same-name-blockless" -// ], -// ignore: [ -// "after-comment" -// ] -// } -// ], - "at-rule-name-case": "lower", - "at-rule-name-newline-after": "always-multi-line", - "at-rule-name-space-after": "always", -// "at-rule-no-vendor-prefix": true, - "at-rule-semicolon-newline-after": "always", - "at-rule-semicolon-space-before": "never", -// "at-rule-whitelist": [ -// "content", -// "else", -// "function", -// "if", -// "import", -// "include", -// "keyframes", -// "mixin", -// "return" -// ], - "block-closing-brace-empty-line-before": "never", - "block-closing-brace-newline-after": "always", - "block-closing-brace-newline-before": "always-multi-line", - "block-no-empty": true, -// "block-opening-brace-newline-after": "always", -// "block-opening-brace-newline-before": "never-single-line", -// "block-opening-brace-space-before": "always", - "color-hex-case": "lower", - "color-hex-length": "short", -// "color-named": "never", - //"color-no-hex": true, - "color-no-invalid-hex": true, -// "comment-empty-line-before": "always", - "comment-no-empty": true, - "comment-whitespace-inside": "always", - "comment-word-blacklist": [], - "custom-media-pattern": ".+", - "custom-property-empty-line-before": "never", - "custom-property-pattern": "my-.+", - "declaration-bang-space-after": "never", - "declaration-bang-space-before": "always", -// "declaration-block-no-duplicate-properties": true, - "declaration-block-no-redundant-longhand-properties": true, - "declaration-block-no-shorthand-property-overrides": true, -// "declaration-block-semicolon-newline-after": "always", - "declaration-block-semicolon-newline-before": "never-multi-line", - "declaration-block-semicolon-space-before": "never", -// "declaration-block-single-line-max-declarations": 1, - "declaration-block-trailing-semicolon": "always", - "declaration-colon-newline-after": "always-multi-line", - "declaration-colon-space-after": "always-single-line", - "declaration-colon-space-before": "never", -// "declaration-empty-line-before": "never", - //"declaration-no-important": true, - "declaration-property-unit-blacklist": {}, - "declaration-property-unit-whitelist": {}, - "declaration-property-value-blacklist": {}, - "declaration-property-value-whitelist": {}, -// "font-family-name-quotes": "always-where-recommended", - "font-family-no-duplicate-names": true, - "font-family-no-missing-generic-family-keyword": true, -// "font-weight-notation": "numeric", - "function-blacklist": [], - "function-calc-no-unspaced-operator": true, - "function-comma-newline-after": "always-multi-line", - "function-comma-newline-before": "never-multi-line", - "function-comma-space-after": "always-single-line", - "function-comma-space-before": "never", - "function-linear-gradient-no-nonstandard-direction": true, - "function-max-empty-lines": 1, - "function-name-case": "lower", - "function-parentheses-newline-inside": "never-multi-line", - "function-parentheses-space-inside": "never", - "function-url-no-scheme-relative": true, - "function-url-quotes": "always", - "function-url-scheme-blacklist": [], - "function-url-scheme-whitelist": [], -// "function-whitelist": [ -// "box-shadow-args", -// "map-get", -// "rgba", -// "skew", -// "var" -// ], - "keyframe-declaration-no-important": true, - "length-zero-no-unit": true, - "max-empty-lines": 1, - "max-line-length": 160, - "max-nesting-depth": 99, - "media-feature-colon-space-after": "always", - "media-feature-colon-space-before": "never", - "media-feature-name-blacklist": [], - "media-feature-name-case": "lower", - "media-feature-name-no-unknown": true, - "media-feature-name-no-vendor-prefix": true, - "media-feature-name-whitelist": [], - "media-feature-parentheses-space-inside": "never", - "media-feature-range-operator-space-after": "always", - "media-feature-range-operator-space-before": "always", - "media-query-list-comma-newline-after": "always-multi-line", - "media-query-list-comma-newline-before": "never-multi-line", - "media-query-list-comma-space-after": "always-single-line", - "media-query-list-comma-space-before": "never", -// "no-descending-specificity": true, - "no-duplicate-at-import-rules": true, -// "no-duplicate-selectors": true, - "no-empty-source": true, - "no-eol-whitespace": true, - "no-extra-semicolons": true, - "no-invalid-double-slash-comments": true, - "no-missing-end-of-source-newline": true, - "no-unknown-animations": true, - "number-leading-zero": "always", - "number-max-precision": [4, { ignoreUnits: [ "%" ] }], -// "number-no-trailing-zeros": true, - "order/order": [ - [ - "dollar-variables", - "at-variables", - "custom-properties", - { - type: "at-rule", - name: "extend", - }, - { - type: "at-rule", - name: "include", - }, - "declarations", - "rules", - "at-rules", - "less-mixins", - ], - { - unspecified: "bottom", - }, - ], -// "order/properties-order": [ -// [] -// ], -// "order/properties-alphabetical-order": true, - "property-blacklist": [ - "grid-area", - "grid-template", - "grid-column", - "grid-row", - ], - "property-case": "lower", - "property-no-unknown": true, -// "property-no-vendor-prefix": true, + // "order/properties-order": [ + // [] + // ], + // "order/properties-alphabetical-order": true, + "property-blacklist": [ + "grid-area", + "grid-template", + "grid-column", + "grid-row", + ], + "property-case": "lower", + "property-no-unknown": true, + // "property-no-vendor-prefix": true, - /*"property-whitelist": [ + /*"property-whitelist": [ "/animation$/", "/box-shadow$/", "/keyframes$/", @@ -174,8 +174,8 @@ module.exports = { "display", "font-size" ], */ -// "rule-empty-line-before": ["always", { except: [ "after-single-line-comment" ] }], - /*"scss/at-else-closing-brace-newline-after": "always-last-in-chain", + // "rule-empty-line-before": ["always", { except: [ "after-single-line-comment" ] }], + /*"scss/at-else-closing-brace-newline-after": "always-last-in-chain", "scss/at-else-empty-line-before": "never", "scss/at-else-if-parentheses-space-before": "always", "scss/at-extend-no-missing-placeholder": true, @@ -229,95 +229,82 @@ module.exports = { "scss/operator-no-unspaced": true, "scss/partial-no-import": true, "scss/selector-no-redundant-nesting-selector": true,*/ - "selector-attribute-brackets-space-inside": "never", - "selector-attribute-operator-blacklist": [], - "selector-attribute-operator-space-after": "never", - "selector-attribute-operator-space-before": "never", - "selector-attribute-operator-whitelist": [ - "=", - ], - "selector-attribute-quotes": "always", - "selector-class-pattern": ".+", - //"selector-combinator-blacklist": [], - "selector-combinator-space-after": "always", - "selector-combinator-space-before": "always", - //"selector-combinator-whitelist": [], - "selector-descendant-combinator-no-non-space": true, - "selector-id-pattern": ".+", - "selector-list-comma-newline-after": "always-multi-line", - "selector-list-comma-newline-before": "never-multi-line", - "selector-list-comma-space-after": "always-single-line", - "selector-list-comma-space-before": "never", - "selector-max-attribute": 99, - "selector-max-class": 99, - "selector-max-combinators": 99, - "selector-max-compound-selectors": 99, - "selector-max-empty-lines": 1, - "selector-max-id": 1, - //"selector-max-specificity": "0,0,0", - "selector-max-type": 99, - "selector-max-universal": 1, - "selector-nested-pattern": ".+", - "selector-no-qualifying-type": [ - true, - { - ignore: [ - "attribute", "class", - ], - }, - ], - "selector-no-vendor-prefix": true, - "selector-pseudo-class-blacklist": [], - "selector-pseudo-class-case": "lower", - "selector-pseudo-class-no-unknown": true, - "selector-pseudo-class-parentheses-space-inside": "never", - "selector-pseudo-class-whitelist": [ - "active", - "after", - "before", - "checked", - "disabled", - "focus", - "hover", - "link", - "not", - "last-child", - "root", - "target", - "visited", - ], - //"selector-pseudo-element-blacklist": [], - "selector-pseudo-element-case": "lower", -// "selector-pseudo-element-colon-notation": "double", - "selector-pseudo-element-no-unknown": true, - //"selector-pseudo-element-whitelist": [], - "selector-type-case": "lower", - "selector-type-no-unknown": true, -// "shorthand-property-no-redundant-values": true, -// "sh-waqar/declaration-use-variable": [ -// [ -// "color", -// "background-color", -// "font-family" -// ] -// ], -// "string-quotes": "double", - "time-min-milliseconds": 50, - "unit-blacklist": [], - "unit-case": "lower", - "unit-no-unknown": true, - "unit-whitelist": [ - "deg", - "fr", - "px", - "rem", - "ms", - "s", - "vw", - "%", - ], -// "value-keyword-case": "lower", - "value-list-max-empty-lines": 0, - "value-no-vendor-prefix": true, - }, + "selector-attribute-brackets-space-inside": "never", + "selector-attribute-operator-blacklist": [], + "selector-attribute-operator-space-after": "never", + "selector-attribute-operator-space-before": "never", + "selector-attribute-operator-whitelist": ["="], + "selector-attribute-quotes": "always", + "selector-class-pattern": ".+", + //"selector-combinator-blacklist": [], + "selector-combinator-space-after": "always", + "selector-combinator-space-before": "always", + //"selector-combinator-whitelist": [], + "selector-descendant-combinator-no-non-space": true, + "selector-id-pattern": ".+", + "selector-list-comma-newline-after": "always-multi-line", + "selector-list-comma-newline-before": "never-multi-line", + "selector-list-comma-space-after": "always-single-line", + "selector-list-comma-space-before": "never", + "selector-max-attribute": 99, + "selector-max-class": 99, + "selector-max-combinators": 99, + "selector-max-compound-selectors": 99, + "selector-max-empty-lines": 1, + "selector-max-id": 1, + //"selector-max-specificity": "0,0,0", + "selector-max-type": 99, + "selector-max-universal": 1, + "selector-nested-pattern": ".+", + "selector-no-qualifying-type": [ + true, + { + ignore: ["attribute", "class"], + }, + ], + "selector-no-vendor-prefix": true, + "selector-pseudo-class-blacklist": [], + "selector-pseudo-class-case": "lower", + "selector-pseudo-class-no-unknown": true, + "selector-pseudo-class-parentheses-space-inside": "never", + "selector-pseudo-class-whitelist": [ + "active", + "after", + "before", + "checked", + "disabled", + "focus", + "hover", + "link", + "not", + "last-child", + "root", + "target", + "visited", + ], + //"selector-pseudo-element-blacklist": [], + "selector-pseudo-element-case": "lower", + // "selector-pseudo-element-colon-notation": "double", + "selector-pseudo-element-no-unknown": true, + //"selector-pseudo-element-whitelist": [], + "selector-type-case": "lower", + "selector-type-no-unknown": true, + // "shorthand-property-no-redundant-values": true, + // "sh-waqar/declaration-use-variable": [ + // [ + // "color", + // "background-color", + // "font-family" + // ] + // ], + // "string-quotes": "double", + "time-min-milliseconds": 50, + "unit-blacklist": [], + "unit-case": "lower", + "unit-no-unknown": true, + "unit-whitelist": ["deg", "fr", "px", "rem", "ms", "s", "vw", "%"], + // "value-keyword-case": "lower", + "value-list-max-empty-lines": 0, + "value-no-vendor-prefix": true, + }, }; diff --git a/test/Netscript/DynamicRamCalculation.test.js b/test/Netscript/DynamicRamCalculation.test.js index 9a5fe1a03..ea437bc96 100644 --- a/test/Netscript/DynamicRamCalculation.test.js +++ b/test/Netscript/DynamicRamCalculation.test.js @@ -1,5 +1,8 @@ import { NetscriptFunctions } from "../../src/NetscriptFunctions"; -import { getRamCost, RamCostConstants } from "../../src/Netscript/RamCostGenerator"; +import { + getRamCost, + RamCostConstants, +} from "../../src/Netscript/RamCostGenerator"; import { Environment } from "../../src/Netscript/Environment"; import { RunningScript } from "../../src/Script/RunningScript"; import { Script } from "../../src/Script/Script"; @@ -7,1180 +10,1184 @@ import { SourceFileFlags } from "../../src/SourceFile/SourceFileFlags"; const ScriptBaseCost = RamCostConstants.ScriptBaseRamCost; -describe("Netscript Dynamic RAM Calculation/Generation Tests", function() { - // Creates a mock RunningScript object - async function createRunningScript(code) { - const script = new Script(); - script.code = code; - await script.updateRamUsage([]); +describe("Netscript Dynamic RAM Calculation/Generation Tests", function () { + // Creates a mock RunningScript object + async function createRunningScript(code) { + const script = new Script(); + script.code = code; + await script.updateRamUsage([]); - const runningScript = new RunningScript(script); + const runningScript = new RunningScript(script); - return runningScript; + return runningScript; + } + + // Tests numeric equality, allowing for floating point imprecision + function testEquality(val, expected) { + expect(val).toBeGreaterThanOrEqual(expected - 100 * Number.EPSILON); + expect(val).toBeLessThanOrEqual(expected + 100 * Number.EPSILON); + } + + // Runs a Netscript function and properly catches it if it returns promise + function runPotentiallyAsyncFunction(fn) { + const res = fn(); + if (res instanceof Promise) { + res.catch(() => undefined); + } + } + + /** + * Tests that: + * 1. A function has non-zero RAM cost + * 2. Running the function properly updates the MockWorkerScript's dynamic RAM calculation + * 3. Running multiple calls of the function does not result in additional RAM cost + * @param {string[]} fnDesc - describes the name of the function being tested, + * including the namespace(s). e.g. ["gang", "getMemberNames"] + */ + async function testNonzeroDynamicRamCost(fnDesc) { + if (!Array.isArray(fnDesc)) { + throw new Error("Non-array passed to testNonzeroDynamicRamCost()"); + } + const expected = getRamCost(...fnDesc); + expect(expected).toBeGreaterThan(0); + + const code = `${fnDesc.join(".")}();`; + + const runningScript = await createRunningScript(code); + + // We don't need a real WorkerScript + const workerScript = { + args: [], + code: code, + dynamicLoadedFns: {}, + dynamicRamUsage: RamCostConstants.ScriptBaseRamCost, + env: new Environment(null), + ramUsage: runningScript.ramUsage, + scriptRef: runningScript, + }; + workerScript.env.vars = NetscriptFunctions(workerScript); + + // Run the function through the workerscript's args + const scope = workerScript.env.vars; + let curr = scope[fnDesc[0]]; + for (let i = 1; i < fnDesc.length; ++i) { + if (curr == null) { + throw new Error(`Invalid function specified: [${fnDesc}]`); + } + + if (typeof curr === "function") { + break; + } + + curr = curr[fnDesc[i]]; } - // Tests numeric equality, allowing for floating point imprecision - function testEquality(val, expected) { - expect(val).toBeGreaterThanOrEqual(expected - 100 * Number.EPSILON); - expect(val).toBeLessThanOrEqual(expected + 100 * Number.EPSILON); + if (typeof curr === "function") { + // We use a try/catch because the function will probably fail since the game isn't + // actually initialized. Call the fn multiple times to test that repeated calls + // do not incur extra RAM costs. + try { + runPotentiallyAsyncFunction(curr); + runPotentiallyAsyncFunction(curr); + runPotentiallyAsyncFunction(curr); + } catch (e) {} + } else { + throw new Error(`Invalid function specified: [${fnDesc}]`); } - // Runs a Netscript function and properly catches it if it returns promise - function runPotentiallyAsyncFunction(fn) { - const res = fn(); - if (res instanceof Promise) { - res.catch(() => undefined); - } + const fnName = fnDesc[fnDesc.length - 1]; + testEquality(workerScript.dynamicRamUsage - ScriptBaseCost, expected); + testEquality(workerScript.dynamicRamUsage, runningScript.ramUsage); + expect(workerScript.dynamicLoadedFns).toHaveProperty(fnName); + } + + /** + * Tests that: + * 1. A function has zero RAM cost + * 2. Running the function does NOT update the MockWorkerScript's dynamic RAM calculation + * 3. Running multiple calls of the function does not result in dynamic RAM calculation + * @param {string[]} fnDesc - describes the name of the function being tested, + * including the namespace(s). e.g. ["gang", "getMemberNames"] + */ + async function testZeroDynamicRamCost(fnDesc) { + if (!Array.isArray(fnDesc)) { + throw new Error("Non-array passed to testZeroDynamicRamCost()"); + } + const expected = getRamCost(...fnDesc); + expect(expected).toEqual(0); + + const code = `${fnDesc.join(".")}();`; + + const runningScript = await createRunningScript(code); + + // We don't need a real WorkerScript + const workerScript = { + args: [], + code: code, + dynamicLoadedFns: {}, + dynamicRamUsage: RamCostConstants.ScriptBaseRamCost, + env: new Environment(null), + ramUsage: runningScript.ramUsage, + scriptRef: runningScript, + }; + workerScript.env.vars = NetscriptFunctions(workerScript); + + // Run the function through the workerscript's args + const scope = workerScript.env.vars; + let curr = scope[fnDesc[0]]; + for (let i = 1; i < fnDesc.length; ++i) { + if (curr == null) { + throw new Error(`Invalid function specified: [${fnDesc}]`); + } + + if (typeof curr === "function") { + break; + } + + curr = curr[fnDesc[i]]; } - /** - * Tests that: - * 1. A function has non-zero RAM cost - * 2. Running the function properly updates the MockWorkerScript's dynamic RAM calculation - * 3. Running multiple calls of the function does not result in additional RAM cost - * @param {string[]} fnDesc - describes the name of the function being tested, - * including the namespace(s). e.g. ["gang", "getMemberNames"] - */ - async function testNonzeroDynamicRamCost(fnDesc) { - if (!Array.isArray(fnDesc)) { throw new Error("Non-array passed to testNonzeroDynamicRamCost()"); } - const expected = getRamCost(...fnDesc); - expect(expected).toBeGreaterThan(0); - - const code = `${fnDesc.join(".")}();` - - const runningScript = await createRunningScript(code); - - // We don't need a real WorkerScript - const workerScript = { - args: [], - code: code, - dynamicLoadedFns: {}, - dynamicRamUsage: RamCostConstants.ScriptBaseRamCost, - env: new Environment(null), - ramUsage: runningScript.ramUsage, - scriptRef: runningScript, - } - workerScript.env.vars = NetscriptFunctions(workerScript); - - // Run the function through the workerscript's args - const scope = workerScript.env.vars; - let curr = scope[fnDesc[0]]; - for (let i = 1; i < fnDesc.length; ++i) { - if (curr == null) { - throw new Error(`Invalid function specified: [${fnDesc}]`); - } - - if (typeof curr === "function") { - break; - } - - curr = curr[fnDesc[i]]; - } - - if (typeof curr === "function") { - // We use a try/catch because the function will probably fail since the game isn't - // actually initialized. Call the fn multiple times to test that repeated calls - // do not incur extra RAM costs. - try { - runPotentiallyAsyncFunction(curr); - runPotentiallyAsyncFunction(curr); - runPotentiallyAsyncFunction(curr); - } catch(e) {} - } else { - throw new Error(`Invalid function specified: [${fnDesc}]`); - } - - const fnName = fnDesc[fnDesc.length - 1]; - testEquality(workerScript.dynamicRamUsage - ScriptBaseCost, expected); - testEquality(workerScript.dynamicRamUsage, runningScript.ramUsage); - expect(workerScript.dynamicLoadedFns).toHaveProperty(fnName); + if (typeof curr === "function") { + // We use a try/catch because the function will probably fail since the game isn't + // actually initialized. Call the fn multiple times to test that repeated calls + // do not incur extra RAM costs. + try { + runPotentiallyAsyncFunction(curr); + runPotentiallyAsyncFunction(curr); + runPotentiallyAsyncFunction(curr); + } catch (e) {} + } else { + throw new Error(`Invalid function specified: [${fndesc}]`); } - /** - * Tests that: - * 1. A function has zero RAM cost - * 2. Running the function does NOT update the MockWorkerScript's dynamic RAM calculation - * 3. Running multiple calls of the function does not result in dynamic RAM calculation - * @param {string[]} fnDesc - describes the name of the function being tested, - * including the namespace(s). e.g. ["gang", "getMemberNames"] - */ - async function testZeroDynamicRamCost(fnDesc) { - if (!Array.isArray(fnDesc)) { throw new Error("Non-array passed to testZeroDynamicRamCost()"); } - const expected = getRamCost(...fnDesc); - expect(expected).toEqual(0); + testEquality(workerScript.dynamicRamUsage, ScriptBaseCost); + testEquality(workerScript.dynamicRamUsage, runningScript.ramUsage); + } - const code = `${fnDesc.join(".")}();` - - const runningScript = await createRunningScript(code); - - // We don't need a real WorkerScript - const workerScript = { - args: [], - code: code, - dynamicLoadedFns: {}, - dynamicRamUsage: RamCostConstants.ScriptBaseRamCost, - env: new Environment(null), - ramUsage: runningScript.ramUsage, - scriptRef: runningScript, - } - workerScript.env.vars = NetscriptFunctions(workerScript); - - // Run the function through the workerscript's args - const scope = workerScript.env.vars; - let curr = scope[fnDesc[0]]; - for (let i = 1; i < fnDesc.length; ++i) { - if (curr == null) { - throw new Error(`Invalid function specified: [${fnDesc}]`); - } - - if (typeof curr === "function") { - break; - } - - curr = curr[fnDesc[i]]; - } - - if (typeof curr === "function") { - // We use a try/catch because the function will probably fail since the game isn't - // actually initialized. Call the fn multiple times to test that repeated calls - // do not incur extra RAM costs. - try { - runPotentiallyAsyncFunction(curr); - runPotentiallyAsyncFunction(curr); - runPotentiallyAsyncFunction(curr); - } catch(e) {} - } else { - throw new Error(`Invalid function specified: [${fndesc}]`); - } - - testEquality(workerScript.dynamicRamUsage, ScriptBaseCost); - testEquality(workerScript.dynamicRamUsage, runningScript.ramUsage); + beforeEach(function () { + for (let i = 0; i < SourceFileFlags.length; ++i) { + SourceFileFlags[i] = 3; } + }); - beforeEach(function() { - for (let i = 0; i < SourceFileFlags.length; ++i) { - SourceFileFlags[i] = 3; - } + describe("Basic Functions", function () { + it("hack()", async function () { + const f = ["hack"]; + await testNonzeroDynamicRamCost(f); }); - describe("Basic Functions", function() { - it("hack()", async function() { - const f = ["hack"]; - await testNonzeroDynamicRamCost(f); - }); - - it("grow()", async function() { - const f = ["grow"]; - await testNonzeroDynamicRamCost(f); - }); - - it("weaken()", async function() { - const f = ["weaken"]; - await testNonzeroDynamicRamCost(f); - }); - - it("hackAnalyzeThreads()", async function() { - const f = ["hackAnalyzeThreads"]; - await testNonzeroDynamicRamCost(f); - }); - - it("hackAnalyzePercent()", async function() { - const f = ["hackAnalyzePercent"]; - await testNonzeroDynamicRamCost(f); - }); - - it("hackChance()", async function() { - const f = ["hackChance"]; - await testNonzeroDynamicRamCost(f); - }); - - it("growthAnalyze()", async function() { - const f = ["growthAnalyze"]; - await testNonzeroDynamicRamCost(f); - }); - - it("sleep()", async function() { - const f = ["sleep"]; - await testZeroDynamicRamCost(f); - }); - - it("print()", async function() { - const f = ["print"]; - await testZeroDynamicRamCost(f); - }); - - it("tprint()", async function() { - const f = ["tprint"]; - await testZeroDynamicRamCost(f); - }); - - it("clearLog()", async function() { - const f = ["clearLog"]; - await testZeroDynamicRamCost(f); - }); - - it("disableLog()", async function() { - const f = ["disableLog"]; - await testZeroDynamicRamCost(f); - }); - - it("enableLog()", async function() { - const f = ["enableLog"]; - await testZeroDynamicRamCost(f); - }); - - it("isLogEnabled()", async function() { - const f = ["isLogEnabled"]; - await testZeroDynamicRamCost(f); - }); - - it("getScriptLogs()", async function() { - const f = ["getScriptLogs"]; - await testZeroDynamicRamCost(f); - }); - - it("scan()", async function() { - const f = ["scan"]; - await testNonzeroDynamicRamCost(f); - }); - - it("nuke()", async function() { - const f = ["nuke"]; - await testNonzeroDynamicRamCost(f); - }); - - it("brutessh()", async function() { - const f = ["brutessh"]; - await testNonzeroDynamicRamCost(f); - }); - - it("ftpcrack()", async function() { - const f = ["ftpcrack"]; - await testNonzeroDynamicRamCost(f); - }); - - it("relaysmtp()", async function() { - const f = ["relaysmtp"]; - await testNonzeroDynamicRamCost(f); - }); - - it("httpworm()", async function() { - const f = ["httpworm"]; - await testNonzeroDynamicRamCost(f); - }); - - it("sqlinject()", async function() { - const f = ["sqlinject"]; - await testNonzeroDynamicRamCost(f); - }); - - it("run()", async function() { - const f = ["run"]; - await testNonzeroDynamicRamCost(f); - }); - - it("exec()", async function() { - const f = ["exec"]; - await testNonzeroDynamicRamCost(f); - }); - - it("spawn()", async function() { - const f = ["spawn"]; - await testNonzeroDynamicRamCost(f); - }); - - it("kill()", async function() { - const f = ["kill"]; - await testNonzeroDynamicRamCost(f); - }); - - it("killall()", async function() { - const f = ["killall"]; - await testNonzeroDynamicRamCost(f); - }); - - it("exit()", async function() { - const f = ["exit"]; - await testZeroDynamicRamCost(f); - }); - - it("scp()", async function() { - const f = ["scp"]; - await testNonzeroDynamicRamCost(f); - }); - - it("ls()", async function() { - const f = ["ls"]; - await testNonzeroDynamicRamCost(f); - }); - - it("ps()", async function() { - const f = ["ps"]; - await testNonzeroDynamicRamCost(f); - }); - - it("hasRootAccess()", async function() { - const f = ["hasRootAccess"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getHostname()", async function() { - const f = ["getHostname"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getHackingLevel()", async function() { - const f = ["getHackingLevel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getHackingMultipliers()", async function() { - const f = ["getHackingMultipliers"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getHacknetMultipliers()", async function() { - const f = ["getHacknetMultipliers"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getServerMoneyAvailable()", async function() { - const f = ["getServerMoneyAvailable"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getServerMaxMoney()", async function() { - const f = ["getServerMaxMoney"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getServerGrowth()", async function() { - const f = ["getServerGrowth"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getServerSecurityLevel()", async function() { - const f = ["getServerSecurityLevel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getServerBaseSecurityLevel()", async function() { - const f = ["getServerBaseSecurityLevel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getServerMinSecurityLevel()", async function() { - const f = ["getServerMinSecurityLevel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getServerRequiredHackingLevel()", async function() { - const f = ["getServerRequiredHackingLevel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getServerNumPortsRequired()", async function() { - const f = ["getServerNumPortsRequired"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getServerRam()", async function() { - const f = ["getServerRam"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getServerMaxRam()", async function() { - const f = ["getServerMaxRam"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getServerUsedRam()", async function() { - const f = ["getServerUsedRam"]; - await testNonzeroDynamicRamCost(f); - }); - - it("serverExists()", async function() { - const f = ["serverExists"]; - await testNonzeroDynamicRamCost(f); - }); - - it("fileExists()", async function() { - const f = ["fileExists"]; - await testNonzeroDynamicRamCost(f); - }); - - it("isRunning()", async function() { - const f = ["isRunning"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getPurchasedServerCost()", async function() { - const f = ["getPurchasedServerCost"]; - await testNonzeroDynamicRamCost(f); - }); - - it("purchaseServer()", async function() { - const f = ["purchaseServer"]; - await testNonzeroDynamicRamCost(f); - }); - - it("deleteServer()", async function() { - const f = ["deleteServer"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getPurchasedServers()", async function() { - const f = ["getPurchasedServers"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getPurchasedServerLimit()", async function() { - const f = ["getPurchasedServerLimit"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getPurchasedServerMaxRam()", async function() { - const f = ["getPurchasedServerMaxRam"]; - await testNonzeroDynamicRamCost(f); - }); - - it("write()", async function() { - const f = ["write"]; - await testNonzeroDynamicRamCost(f); - }); - - it("tryWrite()", async function() { - const f = ["tryWrite"]; - await testNonzeroDynamicRamCost(f); - }); - - it("read()", async function() { - const f = ["read"]; - await testNonzeroDynamicRamCost(f); - }); - - it("peek()", async function() { - const f = ["peek"]; - await testNonzeroDynamicRamCost(f); - }); - - it("clear()", async function() { - const f = ["clear"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getPortHandle()", async function() { - const f = ["getPortHandle"]; - await testNonzeroDynamicRamCost(f); - }); - - it("rm()", async function() { - const f = ["rm"]; - await testNonzeroDynamicRamCost(f); - }); - - it("scriptRunning()", async function() { - const f = ["scriptRunning"]; - await testNonzeroDynamicRamCost(f); - }); - - it("scriptKill()", async function() { - const f = ["scriptKill"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getScriptName()", async function() { - const f = ["getScriptName"]; - await testZeroDynamicRamCost(f); - }); - - it("getScriptRam()", async function() { - const f = ["getScriptRam"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getHackTime()", async function() { - const f = ["getHackTime"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getGrowTime()", async function() { - const f = ["getGrowTime"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getWeakenTime()", async function() { - const f = ["getWeakenTime"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getScriptIncome()", async function() { - const f = ["getScriptIncome"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getScriptExpGain()", async function() { - const f = ["getScriptExpGain"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getRunningScript()", async function() { - const f = ["getRunningScript"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getTimeSinceLastAug()", async function() { - const f = ["getTimeSinceLastAug"]; - await testNonzeroDynamicRamCost(f); - }); - - it("sprintf()", async function() { - const f = ["sprintf"]; - await testZeroDynamicRamCost(f); - }); - - it("vsprintf()", async function() { - const f = ["vsprintf"]; - await testZeroDynamicRamCost(f); - }); - - it("nFormat()", async function() { - const f = ["nFormat"]; - await testZeroDynamicRamCost(f); - }); - - it("prompt()", async function() { - const f = ["prompt"]; - await testZeroDynamicRamCost(f); - }); - - it("wget()", async function() { - const f = ["wget"]; - await testZeroDynamicRamCost(f); - }); - - it("getFavorToDonate()", async function() { - const f = ["getFavorToDonate"]; - await testNonzeroDynamicRamCost(f); - }); + it("grow()", async function () { + const f = ["grow"]; + await testNonzeroDynamicRamCost(f); }); - describe("Advanced Functions", function() { - it("getBitNodeMultipliers()", async function() { - const f = ["getBitNodeMultipliers"]; - await testNonzeroDynamicRamCost(f); - }); + it("weaken()", async function () { + const f = ["weaken"]; + await testNonzeroDynamicRamCost(f); }); - describe("TIX API", function() { - it("getStockSymbols()", async function() { - const f = ["getStockSymbols"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getStockPrice()", async function() { - const f = ["getStockPrice"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getStockAskPrice()", async function() { - const f = ["getStockAskPrice"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getStockBidPrice()", async function() { - const f = ["getStockBidPrice"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getStockPosition()", async function() { - const f = ["getStockPosition"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getStockMaxShares()", async function() { - const f = ["getStockMaxShares"]; - await testNonzeroDynamicRamCost(f); - }); - - it("buyStock()", async function() { - const f = ["buyStock"]; - await testNonzeroDynamicRamCost(f); - }); - - it("sellStock()", async function() { - const f = ["sellStock"]; - await testNonzeroDynamicRamCost(f); - }); - - it("shortStock()", async function() { - const f = ["shortStock"]; - await testNonzeroDynamicRamCost(f); - }); - - it("sellShort()", async function() { - const f = ["sellShort"]; - await testNonzeroDynamicRamCost(f); - }); - - it("placeOrder()", async function() { - const f = ["placeOrder"]; - await testNonzeroDynamicRamCost(f); - }); - - it("cancelOrder()", async function() { - const f = ["cancelOrder"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getOrders()", async function() { - const f = ["getOrders"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getStockVolatility()", async function() { - const f = ["getStockVolatility"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getStockForecast()", async function() { - const f = ["getStockForecast"]; - await testNonzeroDynamicRamCost(f); - }); - - it("purchase4SMarketData()", async function() { - const f = ["purchase4SMarketData"]; - await testNonzeroDynamicRamCost(f); - }); - - it("purchase4SMarketDataTixApi()", async function() { - const f = ["purchase4SMarketDataTixApi"]; - await testNonzeroDynamicRamCost(f); - }); + it("hackAnalyzeThreads()", async function () { + const f = ["hackAnalyzeThreads"]; + await testNonzeroDynamicRamCost(f); }); - describe("Singularity Functions", function() { - it("universityCourse()", async function() { - const f = ["universityCourse"]; - await testNonzeroDynamicRamCost(f); - }); - - it("gymWorkout()", async function() { - const f = ["gymWorkout"]; - await testNonzeroDynamicRamCost(f); - }); - - it("travelToCity()", async function() { - const f = ["travelToCity"]; - await testNonzeroDynamicRamCost(f); - }); - - it("purchaseTor()", async function() { - const f = ["purchaseTor"]; - await testNonzeroDynamicRamCost(f); - }); - - it("purchaseProgram()", async function() { - const f = ["purchaseProgram"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getStats()", async function() { - const f = ["getStats"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getCharacterInformation()", async function() { - const f = ["getCharacterInformation"]; - await testNonzeroDynamicRamCost(f); - }); - - it("isBusy()", async function() { - const f = ["isBusy"]; - await testNonzeroDynamicRamCost(f); - }); - - it("stopAction()", async function() { - const f = ["stopAction"]; - await testNonzeroDynamicRamCost(f); - }); - - it("upgradeHomeRam()", async function() { - const f = ["upgradeHomeRam"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getUpgradeHomeRamCost()", async function() { - const f = ["getUpgradeHomeRamCost"]; - await testNonzeroDynamicRamCost(f); - }); - - it("workForCompany()", async function() { - const f = ["workForCompany"]; - await testNonzeroDynamicRamCost(f); - }); - - it("applyToCompany()", async function() { - const f = ["applyToCompany"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getCompanyRep()", async function() { - const f = ["getCompanyRep"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getCompanyFavor()", async function() { - const f = ["getCompanyFavor"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getCompanyFavorGain()", async function() { - const f = ["getCompanyFavorGain"]; - await testNonzeroDynamicRamCost(f); - }); - - it("checkFactionInvitations()", async function() { - const f = ["checkFactionInvitations"]; - await testNonzeroDynamicRamCost(f); - }); - - it("joinFaction()", async function() { - const f = ["joinFaction"]; - await testNonzeroDynamicRamCost(f); - }); - - it("workForFaction()", async function() { - const f = ["workForFaction"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getFactionRep()", async function() { - const f = ["getFactionRep"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getFactionFavor()", async function() { - const f = ["getFactionFavor"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getFactionFavorGain()", async function() { - const f = ["getFactionFavorGain"]; - await testNonzeroDynamicRamCost(f); - }); - - it("donateToFaction()", async function() { - const f = ["donateToFaction"]; - await testNonzeroDynamicRamCost(f); - }); - - it("createProgram()", async function() { - const f = ["createProgram"]; - await testNonzeroDynamicRamCost(f); - }); - - it("commitCrime()", async function() { - const f = ["commitCrime"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getCrimeChance()", async function() { - const f = ["getCrimeChance"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getOwnedAugmentations()", async function() { - const f = ["getOwnedAugmentations"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getOwnedSourceFiles()", async function() { - const f = ["getOwnedSourceFiles"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getAugmentationsFromFaction()", async function() { - const f = ["getAugmentationsFromFaction"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getAugmentationPrereq()", async function() { - const f = ["getAugmentationPrereq"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getAugmentationCost()", async function() { - const f = ["getAugmentationCost"]; - await testNonzeroDynamicRamCost(f); - }); - - it("purchaseAugmentation()", async function() { - const f = ["purchaseAugmentation"]; - await testNonzeroDynamicRamCost(f); - }); - - it("installAugmentations()", async function() { - const f = ["installAugmentations"]; - await testNonzeroDynamicRamCost(f); - }); + it("hackAnalyzePercent()", async function () { + const f = ["hackAnalyzePercent"]; + await testNonzeroDynamicRamCost(f); }); - describe("Bladeburner API", function() { - it("getContractNames()", async function() { - const f = ["bladeburner", "getContractNames"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getOperationNames()", async function() { - const f = ["bladeburner", "getOperationNames"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getBlackOpNames()", async function() { - const f = ["bladeburner", "getBlackOpNames"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getGeneralActionNames()", async function() { - const f = ["bladeburner", "getGeneralActionNames"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getSkillNames()", async function() { - const f = ["bladeburner", "getSkillNames"]; - await testNonzeroDynamicRamCost(f); - }); - - it("startAction()", async function() { - const f = ["bladeburner", "startAction"]; - await testNonzeroDynamicRamCost(f); - }); - - it("stopBladeburnerAction()", async function() { - const f = ["bladeburner", "stopBladeburnerAction"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getCurrentAction()", async function() { - const f = ["bladeburner", "getCurrentAction"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getActionTime()", async function() { - const f = ["bladeburner", "getActionTime"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getActionEstimatedSuccessChance()", async function() { - const f = ["bladeburner", "getActionEstimatedSuccessChance"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getActionRepGain()", async function() { - const f = ["bladeburner", "getActionRepGain"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getActionCountRemaining()", async function() { - const f = ["bladeburner", "getActionCountRemaining"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getActionMaxLevel()", async function() { - const f = ["bladeburner", "getActionMaxLevel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getActionCurrentLevel()", async function() { - const f = ["bladeburner", "getActionCurrentLevel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getActionAutolevel()", async function() { - const f = ["bladeburner", "getActionAutolevel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setActionAutolevel()", async function() { - const f = ["bladeburner", "setActionAutolevel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setActionLevel()", async function() { - const f = ["bladeburner", "setActionLevel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getRank()", async function() { - const f = ["bladeburner", "getRank"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getBlackOpRank()", async function() { - const f = ["bladeburner", "getBlackOpRank"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getSkillPoints()", async function() { - const f = ["bladeburner", "getSkillPoints"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getSkillLevel()", async function() { - const f = ["bladeburner", "getSkillLevel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getSkillUpgradeCost()", async function() { - const f = ["bladeburner", "getSkillUpgradeCost"]; - await testNonzeroDynamicRamCost(f); - }); - - it("upgradeSkill()", async function() { - const f = ["bladeburner", "upgradeSkill"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getTeamSize()", async function() { - const f = ["bladeburner", "getTeamSize"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setTeamSize()", async function() { - const f = ["bladeburner", "setTeamSize"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getCityEstimatedPopulation()", async function() { - const f = ["bladeburner", "getCityEstimatedPopulation"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getCityEstimatedCommunities()", async function() { - const f = ["bladeburner", "getCityEstimatedCommunities"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getCityChaos()", async function() { - const f = ["bladeburner", "getCityChaos"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getCity()", async function() { - const f = ["bladeburner", "getCity"]; - await testNonzeroDynamicRamCost(f); - }); - - it("switchCity()", async function() { - const f = ["bladeburner", "switchCity"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getStamina()", async function() { - const f = ["bladeburner", "getStamina"]; - await testNonzeroDynamicRamCost(f); - }); - - it("joinBladeburnerFaction()", async function() { - const f = ["bladeburner", "joinBladeburnerFaction"]; - await testNonzeroDynamicRamCost(f); - }); - - it("joinBladeburnerDivision()", async function() { - const f = ["bladeburner", "joinBladeburnerDivision"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getBonusTime()", async function() { - const f = ["bladeburner", "getBonusTime"]; - await testZeroDynamicRamCost(f); - }); + it("hackChance()", async function () { + const f = ["hackChance"]; + await testNonzeroDynamicRamCost(f); }); - describe("Gang API", function() { - it("getMemberNames()", async function() { - const f = ["gang", "getMemberNames"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getGangInformation()", async function() { - const f = ["gang", "getGangInformation"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getOtherGangInformation()", async function() { - const f = ["gang", "getOtherGangInformation"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getMemberInformation()", async function() { - const f = ["gang", "getMemberInformation"]; - await testNonzeroDynamicRamCost(f); - }); - - it("canRecruitMember()", async function() { - const f = ["gang", "canRecruitMember"]; - await testNonzeroDynamicRamCost(f); - }); - - it("recruitMember()", async function() { - const f = ["gang", "recruitMember"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getTaskNames()", async function() { - const f = ["gang", "getTaskNames"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setMemberTask()", async function() { - const f = ["gang", "setMemberTask"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getEquipmentNames()", async function() { - const f = ["gang", "getEquipmentNames"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getEquipmentCost()", async function() { - const f = ["gang", "getEquipmentCost"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getEquipmentType()", async function() { - const f = ["gang", "getEquipmentType"]; - await testNonzeroDynamicRamCost(f); - }); - - it("purchaseEquipment()", async function() { - const f = ["gang", "purchaseEquipment"]; - await testNonzeroDynamicRamCost(f); - }); - - it("ascendMember()", async function() { - const f = ["gang", "ascendMember"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setTerritoryWarfare()", async function() { - const f = ["gang", "setTerritoryWarfare"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getChanceToWinClash()", async function() { - const f = ["gang", "getChanceToWinClash"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getBonusTime()", async function() { - const f = ["gang", "getBonusTime"]; - await testZeroDynamicRamCost(f); - }); + it("growthAnalyze()", async function () { + const f = ["growthAnalyze"]; + await testNonzeroDynamicRamCost(f); }); - describe("Coding Contract API", function() { - it("attempt()", async function() { - const f = ["codingcontract", "attempt"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getContractType()", async function() { - const f = ["codingcontract", "getContractType"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getDescription()", async function() { - const f = ["codingcontract", "getDescription"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getData()", async function() { - const f = ["codingcontract", "getData"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getNumTriesRemaining()", async function() { - const f = ["codingcontract", "getNumTriesRemaining"]; - await testNonzeroDynamicRamCost(f); - }); + it("sleep()", async function () { + const f = ["sleep"]; + await testZeroDynamicRamCost(f); }); - describe("Sleeve API", function() { - it("getNumSleeves()", async function() { - const f = ["sleeve", "getNumSleeves"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getSleeveStats()", async function() { - const f = ["sleeve", "getSleeveStats"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getInformation()", async function() { - const f = ["sleeve", "getInformation"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getTask()", async function() { - const f = ["sleeve", "getTask"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setToShockRecovery()", async function() { - const f = ["sleeve", "setToShockRecovery"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setToSynchronize()", async function() { - const f = ["sleeve", "setToSynchronize"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setToCommitCrime()", async function() { - const f = ["sleeve", "setToCommitCrime"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setToFactionWork()", async function() { - const f = ["sleeve", "setToFactionWork"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setToCompanyWork()", async function() { - const f = ["sleeve", "setToCompanyWork"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setToUniversityCourse()", async function() { - const f = ["sleeve", "setToUniversityCourse"]; - await testNonzeroDynamicRamCost(f); - }); - - it("setToGymWorkout()", async function() { - const f = ["sleeve", "setToGymWorkout"]; - await testNonzeroDynamicRamCost(f); - }); - - it("travel()", async function() { - const f = ["sleeve", "travel"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getSleeveAugmentations()", async function() { - const f = ["sleeve", "getSleeveAugmentations"]; - await testNonzeroDynamicRamCost(f); - }); - - it("getSleevePurchasableAugs()", async function() { - const f = ["sleeve", "getSleevePurchasableAugs"]; - await testNonzeroDynamicRamCost(f); - }); - - it("purchaseSleeveAug()", async function() { - const f = ["sleeve", "purchaseSleeveAug"]; - await testNonzeroDynamicRamCost(f); - }); + it("print()", async function () { + const f = ["print"]; + await testZeroDynamicRamCost(f); }); + + it("tprint()", async function () { + const f = ["tprint"]; + await testZeroDynamicRamCost(f); + }); + + it("clearLog()", async function () { + const f = ["clearLog"]; + await testZeroDynamicRamCost(f); + }); + + it("disableLog()", async function () { + const f = ["disableLog"]; + await testZeroDynamicRamCost(f); + }); + + it("enableLog()", async function () { + const f = ["enableLog"]; + await testZeroDynamicRamCost(f); + }); + + it("isLogEnabled()", async function () { + const f = ["isLogEnabled"]; + await testZeroDynamicRamCost(f); + }); + + it("getScriptLogs()", async function () { + const f = ["getScriptLogs"]; + await testZeroDynamicRamCost(f); + }); + + it("scan()", async function () { + const f = ["scan"]; + await testNonzeroDynamicRamCost(f); + }); + + it("nuke()", async function () { + const f = ["nuke"]; + await testNonzeroDynamicRamCost(f); + }); + + it("brutessh()", async function () { + const f = ["brutessh"]; + await testNonzeroDynamicRamCost(f); + }); + + it("ftpcrack()", async function () { + const f = ["ftpcrack"]; + await testNonzeroDynamicRamCost(f); + }); + + it("relaysmtp()", async function () { + const f = ["relaysmtp"]; + await testNonzeroDynamicRamCost(f); + }); + + it("httpworm()", async function () { + const f = ["httpworm"]; + await testNonzeroDynamicRamCost(f); + }); + + it("sqlinject()", async function () { + const f = ["sqlinject"]; + await testNonzeroDynamicRamCost(f); + }); + + it("run()", async function () { + const f = ["run"]; + await testNonzeroDynamicRamCost(f); + }); + + it("exec()", async function () { + const f = ["exec"]; + await testNonzeroDynamicRamCost(f); + }); + + it("spawn()", async function () { + const f = ["spawn"]; + await testNonzeroDynamicRamCost(f); + }); + + it("kill()", async function () { + const f = ["kill"]; + await testNonzeroDynamicRamCost(f); + }); + + it("killall()", async function () { + const f = ["killall"]; + await testNonzeroDynamicRamCost(f); + }); + + it("exit()", async function () { + const f = ["exit"]; + await testZeroDynamicRamCost(f); + }); + + it("scp()", async function () { + const f = ["scp"]; + await testNonzeroDynamicRamCost(f); + }); + + it("ls()", async function () { + const f = ["ls"]; + await testNonzeroDynamicRamCost(f); + }); + + it("ps()", async function () { + const f = ["ps"]; + await testNonzeroDynamicRamCost(f); + }); + + it("hasRootAccess()", async function () { + const f = ["hasRootAccess"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getHostname()", async function () { + const f = ["getHostname"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getHackingLevel()", async function () { + const f = ["getHackingLevel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getHackingMultipliers()", async function () { + const f = ["getHackingMultipliers"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getHacknetMultipliers()", async function () { + const f = ["getHacknetMultipliers"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getServerMoneyAvailable()", async function () { + const f = ["getServerMoneyAvailable"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getServerMaxMoney()", async function () { + const f = ["getServerMaxMoney"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getServerGrowth()", async function () { + const f = ["getServerGrowth"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getServerSecurityLevel()", async function () { + const f = ["getServerSecurityLevel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getServerBaseSecurityLevel()", async function () { + const f = ["getServerBaseSecurityLevel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getServerMinSecurityLevel()", async function () { + const f = ["getServerMinSecurityLevel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getServerRequiredHackingLevel()", async function () { + const f = ["getServerRequiredHackingLevel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getServerNumPortsRequired()", async function () { + const f = ["getServerNumPortsRequired"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getServerRam()", async function () { + const f = ["getServerRam"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getServerMaxRam()", async function () { + const f = ["getServerMaxRam"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getServerUsedRam()", async function () { + const f = ["getServerUsedRam"]; + await testNonzeroDynamicRamCost(f); + }); + + it("serverExists()", async function () { + const f = ["serverExists"]; + await testNonzeroDynamicRamCost(f); + }); + + it("fileExists()", async function () { + const f = ["fileExists"]; + await testNonzeroDynamicRamCost(f); + }); + + it("isRunning()", async function () { + const f = ["isRunning"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getPurchasedServerCost()", async function () { + const f = ["getPurchasedServerCost"]; + await testNonzeroDynamicRamCost(f); + }); + + it("purchaseServer()", async function () { + const f = ["purchaseServer"]; + await testNonzeroDynamicRamCost(f); + }); + + it("deleteServer()", async function () { + const f = ["deleteServer"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getPurchasedServers()", async function () { + const f = ["getPurchasedServers"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getPurchasedServerLimit()", async function () { + const f = ["getPurchasedServerLimit"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getPurchasedServerMaxRam()", async function () { + const f = ["getPurchasedServerMaxRam"]; + await testNonzeroDynamicRamCost(f); + }); + + it("write()", async function () { + const f = ["write"]; + await testNonzeroDynamicRamCost(f); + }); + + it("tryWrite()", async function () { + const f = ["tryWrite"]; + await testNonzeroDynamicRamCost(f); + }); + + it("read()", async function () { + const f = ["read"]; + await testNonzeroDynamicRamCost(f); + }); + + it("peek()", async function () { + const f = ["peek"]; + await testNonzeroDynamicRamCost(f); + }); + + it("clear()", async function () { + const f = ["clear"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getPortHandle()", async function () { + const f = ["getPortHandle"]; + await testNonzeroDynamicRamCost(f); + }); + + it("rm()", async function () { + const f = ["rm"]; + await testNonzeroDynamicRamCost(f); + }); + + it("scriptRunning()", async function () { + const f = ["scriptRunning"]; + await testNonzeroDynamicRamCost(f); + }); + + it("scriptKill()", async function () { + const f = ["scriptKill"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getScriptName()", async function () { + const f = ["getScriptName"]; + await testZeroDynamicRamCost(f); + }); + + it("getScriptRam()", async function () { + const f = ["getScriptRam"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getHackTime()", async function () { + const f = ["getHackTime"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getGrowTime()", async function () { + const f = ["getGrowTime"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getWeakenTime()", async function () { + const f = ["getWeakenTime"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getScriptIncome()", async function () { + const f = ["getScriptIncome"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getScriptExpGain()", async function () { + const f = ["getScriptExpGain"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getRunningScript()", async function () { + const f = ["getRunningScript"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getTimeSinceLastAug()", async function () { + const f = ["getTimeSinceLastAug"]; + await testNonzeroDynamicRamCost(f); + }); + + it("sprintf()", async function () { + const f = ["sprintf"]; + await testZeroDynamicRamCost(f); + }); + + it("vsprintf()", async function () { + const f = ["vsprintf"]; + await testZeroDynamicRamCost(f); + }); + + it("nFormat()", async function () { + const f = ["nFormat"]; + await testZeroDynamicRamCost(f); + }); + + it("prompt()", async function () { + const f = ["prompt"]; + await testZeroDynamicRamCost(f); + }); + + it("wget()", async function () { + const f = ["wget"]; + await testZeroDynamicRamCost(f); + }); + + it("getFavorToDonate()", async function () { + const f = ["getFavorToDonate"]; + await testNonzeroDynamicRamCost(f); + }); + }); + + describe("Advanced Functions", function () { + it("getBitNodeMultipliers()", async function () { + const f = ["getBitNodeMultipliers"]; + await testNonzeroDynamicRamCost(f); + }); + }); + + describe("TIX API", function () { + it("getStockSymbols()", async function () { + const f = ["getStockSymbols"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getStockPrice()", async function () { + const f = ["getStockPrice"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getStockAskPrice()", async function () { + const f = ["getStockAskPrice"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getStockBidPrice()", async function () { + const f = ["getStockBidPrice"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getStockPosition()", async function () { + const f = ["getStockPosition"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getStockMaxShares()", async function () { + const f = ["getStockMaxShares"]; + await testNonzeroDynamicRamCost(f); + }); + + it("buyStock()", async function () { + const f = ["buyStock"]; + await testNonzeroDynamicRamCost(f); + }); + + it("sellStock()", async function () { + const f = ["sellStock"]; + await testNonzeroDynamicRamCost(f); + }); + + it("shortStock()", async function () { + const f = ["shortStock"]; + await testNonzeroDynamicRamCost(f); + }); + + it("sellShort()", async function () { + const f = ["sellShort"]; + await testNonzeroDynamicRamCost(f); + }); + + it("placeOrder()", async function () { + const f = ["placeOrder"]; + await testNonzeroDynamicRamCost(f); + }); + + it("cancelOrder()", async function () { + const f = ["cancelOrder"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getOrders()", async function () { + const f = ["getOrders"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getStockVolatility()", async function () { + const f = ["getStockVolatility"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getStockForecast()", async function () { + const f = ["getStockForecast"]; + await testNonzeroDynamicRamCost(f); + }); + + it("purchase4SMarketData()", async function () { + const f = ["purchase4SMarketData"]; + await testNonzeroDynamicRamCost(f); + }); + + it("purchase4SMarketDataTixApi()", async function () { + const f = ["purchase4SMarketDataTixApi"]; + await testNonzeroDynamicRamCost(f); + }); + }); + + describe("Singularity Functions", function () { + it("universityCourse()", async function () { + const f = ["universityCourse"]; + await testNonzeroDynamicRamCost(f); + }); + + it("gymWorkout()", async function () { + const f = ["gymWorkout"]; + await testNonzeroDynamicRamCost(f); + }); + + it("travelToCity()", async function () { + const f = ["travelToCity"]; + await testNonzeroDynamicRamCost(f); + }); + + it("purchaseTor()", async function () { + const f = ["purchaseTor"]; + await testNonzeroDynamicRamCost(f); + }); + + it("purchaseProgram()", async function () { + const f = ["purchaseProgram"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getStats()", async function () { + const f = ["getStats"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getCharacterInformation()", async function () { + const f = ["getCharacterInformation"]; + await testNonzeroDynamicRamCost(f); + }); + + it("isBusy()", async function () { + const f = ["isBusy"]; + await testNonzeroDynamicRamCost(f); + }); + + it("stopAction()", async function () { + const f = ["stopAction"]; + await testNonzeroDynamicRamCost(f); + }); + + it("upgradeHomeRam()", async function () { + const f = ["upgradeHomeRam"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getUpgradeHomeRamCost()", async function () { + const f = ["getUpgradeHomeRamCost"]; + await testNonzeroDynamicRamCost(f); + }); + + it("workForCompany()", async function () { + const f = ["workForCompany"]; + await testNonzeroDynamicRamCost(f); + }); + + it("applyToCompany()", async function () { + const f = ["applyToCompany"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getCompanyRep()", async function () { + const f = ["getCompanyRep"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getCompanyFavor()", async function () { + const f = ["getCompanyFavor"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getCompanyFavorGain()", async function () { + const f = ["getCompanyFavorGain"]; + await testNonzeroDynamicRamCost(f); + }); + + it("checkFactionInvitations()", async function () { + const f = ["checkFactionInvitations"]; + await testNonzeroDynamicRamCost(f); + }); + + it("joinFaction()", async function () { + const f = ["joinFaction"]; + await testNonzeroDynamicRamCost(f); + }); + + it("workForFaction()", async function () { + const f = ["workForFaction"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getFactionRep()", async function () { + const f = ["getFactionRep"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getFactionFavor()", async function () { + const f = ["getFactionFavor"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getFactionFavorGain()", async function () { + const f = ["getFactionFavorGain"]; + await testNonzeroDynamicRamCost(f); + }); + + it("donateToFaction()", async function () { + const f = ["donateToFaction"]; + await testNonzeroDynamicRamCost(f); + }); + + it("createProgram()", async function () { + const f = ["createProgram"]; + await testNonzeroDynamicRamCost(f); + }); + + it("commitCrime()", async function () { + const f = ["commitCrime"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getCrimeChance()", async function () { + const f = ["getCrimeChance"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getOwnedAugmentations()", async function () { + const f = ["getOwnedAugmentations"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getOwnedSourceFiles()", async function () { + const f = ["getOwnedSourceFiles"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getAugmentationsFromFaction()", async function () { + const f = ["getAugmentationsFromFaction"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getAugmentationPrereq()", async function () { + const f = ["getAugmentationPrereq"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getAugmentationCost()", async function () { + const f = ["getAugmentationCost"]; + await testNonzeroDynamicRamCost(f); + }); + + it("purchaseAugmentation()", async function () { + const f = ["purchaseAugmentation"]; + await testNonzeroDynamicRamCost(f); + }); + + it("installAugmentations()", async function () { + const f = ["installAugmentations"]; + await testNonzeroDynamicRamCost(f); + }); + }); + + describe("Bladeburner API", function () { + it("getContractNames()", async function () { + const f = ["bladeburner", "getContractNames"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getOperationNames()", async function () { + const f = ["bladeburner", "getOperationNames"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getBlackOpNames()", async function () { + const f = ["bladeburner", "getBlackOpNames"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getGeneralActionNames()", async function () { + const f = ["bladeburner", "getGeneralActionNames"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getSkillNames()", async function () { + const f = ["bladeburner", "getSkillNames"]; + await testNonzeroDynamicRamCost(f); + }); + + it("startAction()", async function () { + const f = ["bladeburner", "startAction"]; + await testNonzeroDynamicRamCost(f); + }); + + it("stopBladeburnerAction()", async function () { + const f = ["bladeburner", "stopBladeburnerAction"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getCurrentAction()", async function () { + const f = ["bladeburner", "getCurrentAction"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getActionTime()", async function () { + const f = ["bladeburner", "getActionTime"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getActionEstimatedSuccessChance()", async function () { + const f = ["bladeburner", "getActionEstimatedSuccessChance"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getActionRepGain()", async function () { + const f = ["bladeburner", "getActionRepGain"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getActionCountRemaining()", async function () { + const f = ["bladeburner", "getActionCountRemaining"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getActionMaxLevel()", async function () { + const f = ["bladeburner", "getActionMaxLevel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getActionCurrentLevel()", async function () { + const f = ["bladeburner", "getActionCurrentLevel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getActionAutolevel()", async function () { + const f = ["bladeburner", "getActionAutolevel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setActionAutolevel()", async function () { + const f = ["bladeburner", "setActionAutolevel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setActionLevel()", async function () { + const f = ["bladeburner", "setActionLevel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getRank()", async function () { + const f = ["bladeburner", "getRank"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getBlackOpRank()", async function () { + const f = ["bladeburner", "getBlackOpRank"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getSkillPoints()", async function () { + const f = ["bladeburner", "getSkillPoints"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getSkillLevel()", async function () { + const f = ["bladeburner", "getSkillLevel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getSkillUpgradeCost()", async function () { + const f = ["bladeburner", "getSkillUpgradeCost"]; + await testNonzeroDynamicRamCost(f); + }); + + it("upgradeSkill()", async function () { + const f = ["bladeburner", "upgradeSkill"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getTeamSize()", async function () { + const f = ["bladeburner", "getTeamSize"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setTeamSize()", async function () { + const f = ["bladeburner", "setTeamSize"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getCityEstimatedPopulation()", async function () { + const f = ["bladeburner", "getCityEstimatedPopulation"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getCityEstimatedCommunities()", async function () { + const f = ["bladeburner", "getCityEstimatedCommunities"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getCityChaos()", async function () { + const f = ["bladeburner", "getCityChaos"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getCity()", async function () { + const f = ["bladeburner", "getCity"]; + await testNonzeroDynamicRamCost(f); + }); + + it("switchCity()", async function () { + const f = ["bladeburner", "switchCity"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getStamina()", async function () { + const f = ["bladeburner", "getStamina"]; + await testNonzeroDynamicRamCost(f); + }); + + it("joinBladeburnerFaction()", async function () { + const f = ["bladeburner", "joinBladeburnerFaction"]; + await testNonzeroDynamicRamCost(f); + }); + + it("joinBladeburnerDivision()", async function () { + const f = ["bladeburner", "joinBladeburnerDivision"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getBonusTime()", async function () { + const f = ["bladeburner", "getBonusTime"]; + await testZeroDynamicRamCost(f); + }); + }); + + describe("Gang API", function () { + it("getMemberNames()", async function () { + const f = ["gang", "getMemberNames"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getGangInformation()", async function () { + const f = ["gang", "getGangInformation"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getOtherGangInformation()", async function () { + const f = ["gang", "getOtherGangInformation"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getMemberInformation()", async function () { + const f = ["gang", "getMemberInformation"]; + await testNonzeroDynamicRamCost(f); + }); + + it("canRecruitMember()", async function () { + const f = ["gang", "canRecruitMember"]; + await testNonzeroDynamicRamCost(f); + }); + + it("recruitMember()", async function () { + const f = ["gang", "recruitMember"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getTaskNames()", async function () { + const f = ["gang", "getTaskNames"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setMemberTask()", async function () { + const f = ["gang", "setMemberTask"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getEquipmentNames()", async function () { + const f = ["gang", "getEquipmentNames"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getEquipmentCost()", async function () { + const f = ["gang", "getEquipmentCost"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getEquipmentType()", async function () { + const f = ["gang", "getEquipmentType"]; + await testNonzeroDynamicRamCost(f); + }); + + it("purchaseEquipment()", async function () { + const f = ["gang", "purchaseEquipment"]; + await testNonzeroDynamicRamCost(f); + }); + + it("ascendMember()", async function () { + const f = ["gang", "ascendMember"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setTerritoryWarfare()", async function () { + const f = ["gang", "setTerritoryWarfare"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getChanceToWinClash()", async function () { + const f = ["gang", "getChanceToWinClash"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getBonusTime()", async function () { + const f = ["gang", "getBonusTime"]; + await testZeroDynamicRamCost(f); + }); + }); + + describe("Coding Contract API", function () { + it("attempt()", async function () { + const f = ["codingcontract", "attempt"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getContractType()", async function () { + const f = ["codingcontract", "getContractType"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getDescription()", async function () { + const f = ["codingcontract", "getDescription"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getData()", async function () { + const f = ["codingcontract", "getData"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getNumTriesRemaining()", async function () { + const f = ["codingcontract", "getNumTriesRemaining"]; + await testNonzeroDynamicRamCost(f); + }); + }); + + describe("Sleeve API", function () { + it("getNumSleeves()", async function () { + const f = ["sleeve", "getNumSleeves"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getSleeveStats()", async function () { + const f = ["sleeve", "getSleeveStats"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getInformation()", async function () { + const f = ["sleeve", "getInformation"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getTask()", async function () { + const f = ["sleeve", "getTask"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setToShockRecovery()", async function () { + const f = ["sleeve", "setToShockRecovery"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setToSynchronize()", async function () { + const f = ["sleeve", "setToSynchronize"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setToCommitCrime()", async function () { + const f = ["sleeve", "setToCommitCrime"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setToFactionWork()", async function () { + const f = ["sleeve", "setToFactionWork"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setToCompanyWork()", async function () { + const f = ["sleeve", "setToCompanyWork"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setToUniversityCourse()", async function () { + const f = ["sleeve", "setToUniversityCourse"]; + await testNonzeroDynamicRamCost(f); + }); + + it("setToGymWorkout()", async function () { + const f = ["sleeve", "setToGymWorkout"]; + await testNonzeroDynamicRamCost(f); + }); + + it("travel()", async function () { + const f = ["sleeve", "travel"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getSleeveAugmentations()", async function () { + const f = ["sleeve", "getSleeveAugmentations"]; + await testNonzeroDynamicRamCost(f); + }); + + it("getSleevePurchasableAugs()", async function () { + const f = ["sleeve", "getSleevePurchasableAugs"]; + await testNonzeroDynamicRamCost(f); + }); + + it("purchaseSleeveAug()", async function () { + const f = ["sleeve", "purchaseSleeveAug"]; + await testNonzeroDynamicRamCost(f); + }); + }); }); diff --git a/test/Netscript/StaticRamCalculation.test.js b/test/Netscript/StaticRamCalculation.test.js index cee319f4a..917936dd7 100644 --- a/test/Netscript/StaticRamCalculation.test.js +++ b/test/Netscript/StaticRamCalculation.test.js @@ -1,1127 +1,1137 @@ -import { getRamCost, RamCostConstants } from "../../src/Netscript/RamCostGenerator"; -import { calculateRamUsage } from "../../src/Script/RamCalculations" +import { + getRamCost, + RamCostConstants, +} from "../../src/Netscript/RamCostGenerator"; +import { calculateRamUsage } from "../../src/Script/RamCalculations"; const ScriptBaseCost = RamCostConstants.ScriptBaseRamCost; const HacknetNamespaceCost = RamCostConstants.ScriptHacknetNodesRamCost; -describe("Netscript Static RAM Calculation/Generation Tests", function() { - // Tests numeric equality, allowing for floating point imprecision - function testEquality(val, expected) { - expect(val).toBeGreaterThanOrEqual(expected - 100 * Number.EPSILON); - expect(val).toBeLessThanOrEqual(expected + 100 * Number.EPSILON); +describe("Netscript Static RAM Calculation/Generation Tests", function () { + // Tests numeric equality, allowing for floating point imprecision + function testEquality(val, expected) { + expect(val).toBeGreaterThanOrEqual(expected - 100 * Number.EPSILON); + expect(val).toBeLessThanOrEqual(expected + 100 * Number.EPSILON); + } + + /** + * Tests that: + * 1. A function has non-zero RAM cost + * 2. The calculator and the generator result in equal values + * 3. Running multiple calls of the function does not result in additional RAM cost + * @param {string[]} fnDesc - describes the name of the function being tested, + * including the namespace(s). e.g. ["gang", "getMemberNames"] + */ + async function expectNonZeroRamCost(fnDesc) { + if (!Array.isArray(fnDesc)) { + expect.fail("Non-array passed to expectNonZeroRamCost()"); } + const expected = getRamCost(...fnDesc); + expect(expected).toBeGreaterThan(0); - /** - * Tests that: - * 1. A function has non-zero RAM cost - * 2. The calculator and the generator result in equal values - * 3. Running multiple calls of the function does not result in additional RAM cost - * @param {string[]} fnDesc - describes the name of the function being tested, - * including the namespace(s). e.g. ["gang", "getMemberNames"] - */ - async function expectNonZeroRamCost(fnDesc) { - if (!Array.isArray(fnDesc)) { expect.fail("Non-array passed to expectNonZeroRamCost()"); } - const expected = getRamCost(...fnDesc); - expect(expected).toBeGreaterThan(0); + const code = fnDesc.join(".") + "(); "; - const code = fnDesc.join(".") + "(); "; + const calculated = await calculateRamUsage(code, []); + testEquality(calculated, expected + ScriptBaseCost); - const calculated = await calculateRamUsage(code, []); - testEquality(calculated, expected + ScriptBaseCost); + const multipleCallsCode = code.repeat(3); + const multipleCallsCalculated = await calculateRamUsage( + multipleCallsCode, + [], + ); + expect(multipleCallsCalculated).toEqual(calculated); + } - const multipleCallsCode = code.repeat(3); - const multipleCallsCalculated = await calculateRamUsage(multipleCallsCode, []); - expect(multipleCallsCalculated).toEqual(calculated); + /** + * Tests that: + * 1. A function has zero RAM cost + * 2. The calculator and the generator result in equal values + * 3. Running multiple calls of the function does not result in additional RAM cost + * @param {string[]} fnDesc - describes the name of the function being tested, + * including the namespace(s). e.g. ["gang", "getMemberNames"] + */ + async function expectZeroRamCost(fnDesc) { + if (!Array.isArray(fnDesc)) { + expect.fail("Non-array passed to expectZeroRamCost()"); } + const expected = getRamCost(...fnDesc); + expect(expected).toEqual(0); - /** - * Tests that: - * 1. A function has zero RAM cost - * 2. The calculator and the generator result in equal values - * 3. Running multiple calls of the function does not result in additional RAM cost - * @param {string[]} fnDesc - describes the name of the function being tested, - * including the namespace(s). e.g. ["gang", "getMemberNames"] - */ - async function expectZeroRamCost(fnDesc) { - if (!Array.isArray(fnDesc)) { expect.fail("Non-array passed to expectZeroRamCost()"); } - const expected = getRamCost(...fnDesc); - expect(expected).toEqual(0); + const code = fnDesc.join(".") + "(); "; - const code = fnDesc.join(".") + "(); "; + const calculated = await calculateRamUsage(code, []); + testEquality(calculated, ScriptBaseCost); - const calculated = await calculateRamUsage(code, []); - testEquality(calculated, ScriptBaseCost); + const multipleCallsCalculated = await calculateRamUsage(code, []); + expect(multipleCallsCalculated).toEqual(ScriptBaseCost); + } - const multipleCallsCalculated = await calculateRamUsage(code, []); - expect(multipleCallsCalculated).toEqual(ScriptBaseCost); - } - - describe("Basic Functions", function() { - it("hack()", async function() { - const f = ["hack"]; - await expectNonZeroRamCost(f); - }); - - it("grow()", async function() { - const f = ["grow"]; - await expectNonZeroRamCost(f); - }); - - it("weaken()", async function() { - const f = ["weaken"]; - await expectNonZeroRamCost(f); - }); - - it("hackAnalyzeThreads()", async function() { - const f = ["hackAnalyzeThreads"]; - await expectNonZeroRamCost(f); - }); - - it("hackAnalyzePercent()", async function() { - const f = ["hackAnalyzePercent"]; - await expectNonZeroRamCost(f); - }); - - it("hackChance()", async function() { - const f = ["hackChance"]; - await expectNonZeroRamCost(f); - }); - - it("growthAnalyze()", async function() { - const f = ["growthAnalyze"]; - await expectNonZeroRamCost(f); - }); - - it("sleep()", async function() { - const f = ["sleep"]; - await expectZeroRamCost(f); - }); - - it("print()", async function() { - const f = ["print"]; - await expectZeroRamCost(f); - }); - - it("tprint()", async function() { - const f = ["tprint"]; - await expectZeroRamCost(f); - }); - - it("clearLog()", async function() { - const f = ["clearLog"]; - await expectZeroRamCost(f); - }); - - it("disableLog()", async function() { - const f = ["disableLog"]; - await expectZeroRamCost(f); - }); - - it("enableLog()", async function() { - const f = ["enableLog"]; - await expectZeroRamCost(f); - }); - - it("isLogEnabled()", async function() { - const f = ["isLogEnabled"]; - await expectZeroRamCost(f); - }); - - it("getScriptLogs()", async function() { - const f = ["getScriptLogs"]; - await expectZeroRamCost(f); - }); - - it("scan()", async function() { - const f = ["scan"]; - await expectNonZeroRamCost(f); - }); - - it("nuke()", async function() { - const f = ["nuke"]; - await expectNonZeroRamCost(f); - }); - - it("brutessh()", async function() { - const f = ["brutessh"]; - await expectNonZeroRamCost(f); - }); - - it("ftpcrack()", async function() { - const f = ["ftpcrack"]; - await expectNonZeroRamCost(f); - }); - - it("relaysmtp()", async function() { - const f = ["relaysmtp"]; - await expectNonZeroRamCost(f); - }); - - it("httpworm()", async function() { - const f = ["httpworm"]; - await expectNonZeroRamCost(f); - }); - - it("sqlinject()", async function() { - const f = ["sqlinject"]; - await expectNonZeroRamCost(f); - }); - - it("run()", async function() { - const f = ["run"]; - await expectNonZeroRamCost(f); - }); - - it("exec()", async function() { - const f = ["exec"]; - await expectNonZeroRamCost(f); - }); - - it("spawn()", async function() { - const f = ["spawn"]; - await expectNonZeroRamCost(f); - }); - - it("kill()", async function() { - const f = ["kill"]; - await expectNonZeroRamCost(f); - }); - - it("killall()", async function() { - const f = ["killall"]; - await expectNonZeroRamCost(f); - }); - - it("exit()", async function() { - const f = ["exit"]; - await expectZeroRamCost(f); - }); - - it("scp()", async function() { - const f = ["scp"]; - await expectNonZeroRamCost(f); - }); - - it("ls()", async function() { - const f = ["ls"]; - await expectNonZeroRamCost(f); - }); - - it("ps()", async function() { - const f = ["ps"]; - await expectNonZeroRamCost(f); - }); - - it("hasRootAccess()", async function() { - const f = ["hasRootAccess"]; - await expectNonZeroRamCost(f); - }); - - it("getHostname()", async function() { - const f = ["getHostname"]; - await expectNonZeroRamCost(f); - }); - - it("getHackingLevel()", async function() { - const f = ["getHackingLevel"]; - await expectNonZeroRamCost(f); - }); - - it("getHackingMultipliers()", async function() { - const f = ["getHackingMultipliers"]; - await expectNonZeroRamCost(f); - }); - - it("getHacknetMultipliers()", async function() { - const f = ["getHacknetMultipliers"]; - await expectNonZeroRamCost(f); - }); - - it("getServerMoneyAvailable()", async function() { - const f = ["getServerMoneyAvailable"]; - await expectNonZeroRamCost(f); - }); - - it("getServerMaxMoney()", async function() { - const f = ["getServerMaxMoney"]; - await expectNonZeroRamCost(f); - }); - - it("getServerGrowth()", async function() { - const f = ["getServerGrowth"]; - await expectNonZeroRamCost(f); - }); - - it("getServerSecurityLevel()", async function() { - const f = ["getServerSecurityLevel"]; - await expectNonZeroRamCost(f); - }); - - it("getServerBaseSecurityLevel()", async function() { - const f = ["getServerBaseSecurityLevel"]; - await expectNonZeroRamCost(f); - }); - - it("getServerMinSecurityLevel()", async function() { - const f = ["getServerMinSecurityLevel"]; - await expectNonZeroRamCost(f); - }); - - it("getServerRequiredHackingLevel()", async function() { - const f = ["getServerRequiredHackingLevel"]; - await expectNonZeroRamCost(f); - }); - - it("getServerNumPortsRequired()", async function() { - const f = ["getServerNumPortsRequired"]; - await expectNonZeroRamCost(f); - }); - - it("getServerRam()", async function() { - const f = ["getServerRam"]; - await expectNonZeroRamCost(f); - }); - - it("getServerMaxRam()", async function() { - const f = ["getServerMaxRam"]; - await expectNonZeroRamCost(f); - }); - - it("getServerUsedRam()", async function() { - const f = ["getServerUsedRam"]; - await expectNonZeroRamCost(f); - }); - - it("serverExists()", async function() { - const f = ["serverExists"]; - await expectNonZeroRamCost(f); - }); - - it("fileExists()", async function() { - const f = ["fileExists"]; - await expectNonZeroRamCost(f); - }); - - it("isRunning()", async function() { - const f = ["isRunning"]; - await expectNonZeroRamCost(f); - }); - - it("getPurchasedServerCost()", async function() { - const f = ["getPurchasedServerCost"]; - await expectNonZeroRamCost(f); - }); - - it("purchaseServer()", async function() { - const f = ["purchaseServer"]; - await expectNonZeroRamCost(f); - }); - - it("deleteServer()", async function() { - const f = ["deleteServer"]; - await expectNonZeroRamCost(f); - }); - - it("getPurchasedServers()", async function() { - const f = ["getPurchasedServers"]; - await expectNonZeroRamCost(f); - }); - - it("getPurchasedServerLimit()", async function() { - const f = ["getPurchasedServerLimit"]; - await expectNonZeroRamCost(f); - }); - - it("getPurchasedServerMaxRam()", async function() { - const f = ["getPurchasedServerMaxRam"]; - await expectNonZeroRamCost(f); - }); - - it("write()", async function() { - const f = ["write"]; - await expectNonZeroRamCost(f); - }); - - it("tryWrite()", async function() { - const f = ["tryWrite"]; - await expectNonZeroRamCost(f); - }); - - it("read()", async function() { - const f = ["read"]; - await expectNonZeroRamCost(f); - }); - - it("peek()", async function() { - const f = ["peek"]; - await expectNonZeroRamCost(f); - }); - - it("clear()", async function() { - const f = ["clear"]; - await expectNonZeroRamCost(f); - }); - - it("getPortHandle()", async function() { - const f = ["getPortHandle"]; - await expectNonZeroRamCost(f); - }); - - it("rm()", async function() { - const f = ["rm"]; - await expectNonZeroRamCost(f); - }); - - it("scriptRunning()", async function() { - const f = ["scriptRunning"]; - await expectNonZeroRamCost(f); - }); - - it("scriptKill()", async function() { - const f = ["scriptKill"]; - await expectNonZeroRamCost(f); - }); - - it("getScriptName()", async function() { - const f = ["getScriptName"]; - await expectZeroRamCost(f); - }); - - it("getScriptRam()", async function() { - const f = ["getScriptRam"]; - await expectNonZeroRamCost(f); - }); - - it("getHackTime()", async function() { - const f = ["getHackTime"]; - await expectNonZeroRamCost(f); - }); - - it("getGrowTime()", async function() { - const f = ["getGrowTime"]; - await expectNonZeroRamCost(f); - }); - - it("getWeakenTime()", async function() { - const f = ["getWeakenTime"]; - await expectNonZeroRamCost(f); - }); - - it("getScriptIncome()", async function() { - const f = ["getScriptIncome"]; - await expectNonZeroRamCost(f); - }); - - it("getScriptExpGain()", async function() { - const f = ["getScriptExpGain"]; - await expectNonZeroRamCost(f); - }); - - it("getRunningScript()", async function() { - const f = ["getRunningScript"]; - await expectNonZeroRamCost(f); - }); - - it("getTimeSinceLastAug()", async function() { - const f = ["getTimeSinceLastAug"]; - await expectNonZeroRamCost(f); - }); - - it("sprintf()", async function() { - const f = ["sprintf"]; - await expectZeroRamCost(f); - }); - - it("vsprintf()", async function() { - const f = ["vsprintf"]; - await expectZeroRamCost(f); - }); - - it("nFormat()", async function() { - const f = ["nFormat"]; - await expectZeroRamCost(f); - }); - - it("prompt()", async function() { - const f = ["prompt"]; - await expectZeroRamCost(f); - }); - - it("wget()", async function() { - const f = ["wget"]; - await expectZeroRamCost(f); - }); - - it("getFavorToDonate()", async function() { - const f = ["getFavorToDonate"]; - await expectNonZeroRamCost(f); - }); + describe("Basic Functions", function () { + it("hack()", async function () { + const f = ["hack"]; + await expectNonZeroRamCost(f); }); - describe("Advanced Functions", function() { - it("getBitNodeMultipliers()", async function() { - const f = ["getBitNodeMultipliers"]; - await expectNonZeroRamCost(f); - }); + it("grow()", async function () { + const f = ["grow"]; + await expectNonZeroRamCost(f); }); - describe("Hacknet Node API", function() { - // The Hacknet Node API RAM cost is a bit different because - // it's just a one-time cost to access the 'hacknet' namespace. - // Otherwise, all functions cost 0 RAM - const apiFunctions = [ - "numNodes", - "purchaseNode", - "getPurchaseNodeCost", - "getNodeStats", - "upgradeLevel", - "upgradeRam", - "upgradeCore", - "upgradeCache", - "getLevelUpgradeCost", - "getRamUpgradeCost", - "getCoreUpgradeCost", - "getCacheUpgradeCost", - "numHashes", - "hashCost", - "spendHashes", - ] - it("should have zero RAM cost for all functions", function() { - for (const fn of apiFunctions) { - expect(getRamCost("hacknet", fn)).toEqual(0); - } - }); - - it("should incur a one time cost of for accesing the namespace", async function() { - let code = ""; - for (const fn of apiFunctions) { - code += ("hacknet." + fn + "(); "); - } - - const calculated = await calculateRamUsage(code, []); - testEquality(calculated, ScriptBaseCost + HacknetNamespaceCost); - }); + it("weaken()", async function () { + const f = ["weaken"]; + await expectNonZeroRamCost(f); }); - describe("TIX API", function() { - it("getStockSymbols()", async function() { - const f = ["getStockSymbols"]; - await expectNonZeroRamCost(f); - }); - - it("getStockPrice()", async function() { - const f = ["getStockPrice"]; - await expectNonZeroRamCost(f); - }); - - it("getStockAskPrice()", async function() { - const f = ["getStockAskPrice"]; - await expectNonZeroRamCost(f); - }); - - it("getStockBidPrice()", async function() { - const f = ["getStockBidPrice"]; - await expectNonZeroRamCost(f); - }); - - it("getStockPosition()", async function() { - const f = ["getStockPosition"]; - await expectNonZeroRamCost(f); - }); - - it("getStockMaxShares()", async function() { - const f = ["getStockMaxShares"]; - await expectNonZeroRamCost(f); - }); - - it("getStockPurchaseCost()", async function() { - const f = ["getStockPurchaseCost"]; - await expectNonZeroRamCost(f); - }); - - it("getStockSaleGain()", async function() { - const f = ["getStockSaleGain"]; - await expectNonZeroRamCost(f); - }); - - it("buyStock()", async function() { - const f = ["buyStock"]; - await expectNonZeroRamCost(f); - }); - - it("sellStock()", async function() { - const f = ["sellStock"]; - await expectNonZeroRamCost(f); - }); - - it("shortStock()", async function() { - const f = ["shortStock"]; - await expectNonZeroRamCost(f); - }); - - it("sellShort()", async function() { - const f = ["sellShort"]; - await expectNonZeroRamCost(f); - }); - - it("placeOrder()", async function() { - const f = ["placeOrder"]; - await expectNonZeroRamCost(f); - }); - - it("cancelOrder()", async function() { - const f = ["cancelOrder"]; - await expectNonZeroRamCost(f); - }); - - it("getOrders()", async function() { - const f = ["getOrders"]; - await expectNonZeroRamCost(f); - }); - - it("getStockVolatility()", async function() { - const f = ["getStockVolatility"]; - await expectNonZeroRamCost(f); - }); - - it("getStockForecast()", async function() { - const f = ["getStockForecast"]; - await expectNonZeroRamCost(f); - }); - - it("purchase4SMarketData()", async function() { - const f = ["purchase4SMarketData"]; - await expectNonZeroRamCost(f); - }); - - it("purchase4SMarketDataTixApi()", async function() { - const f = ["purchase4SMarketDataTixApi"]; - await expectNonZeroRamCost(f); - }); + it("hackAnalyzeThreads()", async function () { + const f = ["hackAnalyzeThreads"]; + await expectNonZeroRamCost(f); }); - describe("Singularity Functions", function() { - it("universityCourse()", async function() { - const f = ["universityCourse"]; - await expectNonZeroRamCost(f); - }); - - it("gymWorkout()", async function() { - const f = ["gymWorkout"]; - await expectNonZeroRamCost(f); - }); - - it("travelToCity()", async function() { - const f = ["travelToCity"]; - await expectNonZeroRamCost(f); - }); - - it("purchaseTor()", async function() { - const f = ["purchaseTor"]; - await expectNonZeroRamCost(f); - }); - - it("purchaseProgram()", async function() { - const f = ["purchaseProgram"]; - await expectNonZeroRamCost(f); - }); - - it("getStats()", async function() { - const f = ["getStats"]; - await expectNonZeroRamCost(f); - }); - - it("getCharacterInformation()", async function() { - const f = ["getCharacterInformation"]; - await expectNonZeroRamCost(f); - }); - - it("isBusy()", async function() { - const f = ["isBusy"]; - await expectNonZeroRamCost(f); - }); - - it("stopAction()", async function() { - const f = ["stopAction"]; - await expectNonZeroRamCost(f); - }); - - it("upgradeHomeRam()", async function() { - const f = ["upgradeHomeRam"]; - await expectNonZeroRamCost(f); - }); - - it("getUpgradeHomeRamCost()", async function() { - const f = ["getUpgradeHomeRamCost"]; - await expectNonZeroRamCost(f); - }); - - it("workForCompany()", async function() { - const f = ["workForCompany"]; - await expectNonZeroRamCost(f); - }); - - it("applyToCompany()", async function() { - const f = ["applyToCompany"]; - await expectNonZeroRamCost(f); - }); - - it("getCompanyRep()", async function() { - const f = ["getCompanyRep"]; - await expectNonZeroRamCost(f); - }); - - it("getCompanyFavor()", async function() { - const f = ["getCompanyFavor"]; - await expectNonZeroRamCost(f); - }); - - it("getCompanyFavorGain()", async function() { - const f = ["getCompanyFavorGain"]; - await expectNonZeroRamCost(f); - }); - - it("checkFactionInvitations()", async function() { - const f = ["checkFactionInvitations"]; - await expectNonZeroRamCost(f); - }); - - it("joinFaction()", async function() { - const f = ["joinFaction"]; - await expectNonZeroRamCost(f); - }); - - it("workForFaction()", async function() { - const f = ["workForFaction"]; - await expectNonZeroRamCost(f); - }); - - it("getFactionRep()", async function() { - const f = ["getFactionRep"]; - await expectNonZeroRamCost(f); - }); - - it("getFactionFavor()", async function() { - const f = ["getFactionFavor"]; - await expectNonZeroRamCost(f); - }); - - it("getFactionFavorGain()", async function() { - const f = ["getFactionFavorGain"]; - await expectNonZeroRamCost(f); - }); - - it("donateToFaction()", async function() { - const f = ["donateToFaction"]; - await expectNonZeroRamCost(f); - }); - - it("createProgram()", async function() { - const f = ["createProgram"]; - await expectNonZeroRamCost(f); - }); - - it("commitCrime()", async function() { - const f = ["commitCrime"]; - await expectNonZeroRamCost(f); - }); - - it("getCrimeChance()", async function() { - const f = ["getCrimeChance"]; - await expectNonZeroRamCost(f); - }); - - it("getOwnedAugmentations()", async function() { - const f = ["getOwnedAugmentations"]; - await expectNonZeroRamCost(f); - }); - - it("getOwnedSourceFiles()", async function() { - const f = ["getOwnedSourceFiles"]; - await expectNonZeroRamCost(f); - }); - - it("getAugmentationsFromFaction()", async function() { - const f = ["getAugmentationsFromFaction"]; - await expectNonZeroRamCost(f); - }); - - it("getAugmentationPrereq()", async function() { - const f = ["getAugmentationPrereq"]; - await expectNonZeroRamCost(f); - }); - - it("getAugmentationCost()", async function() { - const f = ["getAugmentationCost"]; - await expectNonZeroRamCost(f); - }); - - it("purchaseAugmentation()", async function() { - const f = ["purchaseAugmentation"]; - await expectNonZeroRamCost(f); - }); - - it("installAugmentations()", async function() { - const f = ["installAugmentations"]; - await expectNonZeroRamCost(f); - }); + it("hackAnalyzePercent()", async function () { + const f = ["hackAnalyzePercent"]; + await expectNonZeroRamCost(f); }); - describe("Bladeburner API", function() { - it("getContractNames()", async function() { - const f = ["bladeburner", "getContractNames"]; - await expectNonZeroRamCost(f); - }); - - it("getOperationNames()", async function() { - const f = ["bladeburner", "getOperationNames"]; - await expectNonZeroRamCost(f); - }); - - it("getBlackOpNames()", async function() { - const f = ["bladeburner", "getBlackOpNames"]; - await expectNonZeroRamCost(f); - }); - - it("getGeneralActionNames()", async function() { - const f = ["bladeburner", "getGeneralActionNames"]; - await expectNonZeroRamCost(f); - }); - - it("getSkillNames()", async function() { - const f = ["bladeburner", "getSkillNames"]; - await expectNonZeroRamCost(f); - }); - - it("startAction()", async function() { - const f = ["bladeburner", "startAction"]; - await expectNonZeroRamCost(f); - }); - - it("stopBladeburnerAction()", async function() { - const f = ["bladeburner", "stopBladeburnerAction"]; - await expectNonZeroRamCost(f); - }); - - it("getCurrentAction()", async function() { - const f = ["bladeburner", "getCurrentAction"]; - await expectNonZeroRamCost(f); - }); - - it("getActionTime()", async function() { - const f = ["bladeburner", "getActionTime"]; - await expectNonZeroRamCost(f); - }); - - it("getActionEstimatedSuccessChance()", async function() { - const f = ["bladeburner", "getActionEstimatedSuccessChance"]; - await expectNonZeroRamCost(f); - }); - - it("getActionRepGain()", async function() { - const f = ["bladeburner", "getActionRepGain"]; - await expectNonZeroRamCost(f); - }); - - it("getActionCountRemaining()", async function() { - const f = ["bladeburner", "getActionCountRemaining"]; - await expectNonZeroRamCost(f); - }); - - it("getActionMaxLevel()", async function() { - const f = ["bladeburner", "getActionMaxLevel"]; - await expectNonZeroRamCost(f); - }); - - it("getActionCurrentLevel()", async function() { - const f = ["bladeburner", "getActionCurrentLevel"]; - await expectNonZeroRamCost(f); - }); - - it("getActionAutolevel()", async function() { - const f = ["bladeburner", "getActionAutolevel"]; - await expectNonZeroRamCost(f); - }); - - it("setActionAutolevel()", async function() { - const f = ["bladeburner", "setActionAutolevel"]; - await expectNonZeroRamCost(f); - }); - - it("setActionLevel()", async function() { - const f = ["bladeburner", "setActionLevel"]; - await expectNonZeroRamCost(f); - }); - - it("getRank()", async function() { - const f = ["bladeburner", "getRank"]; - await expectNonZeroRamCost(f); - }); - - it("getBlackOpRank()", async function() { - const f = ["bladeburner", "getBlackOpRank"]; - await expectNonZeroRamCost(f); - }); - - it("getSkillPoints()", async function() { - const f = ["bladeburner", "getSkillPoints"]; - await expectNonZeroRamCost(f); - }); - - it("getSkillLevel()", async function() { - const f = ["bladeburner", "getSkillLevel"]; - await expectNonZeroRamCost(f); - }); - - it("getSkillUpgradeCost()", async function() { - const f = ["bladeburner", "getSkillUpgradeCost"]; - await expectNonZeroRamCost(f); - }); - - it("upgradeSkill()", async function() { - const f = ["bladeburner", "upgradeSkill"]; - await expectNonZeroRamCost(f); - }); - - it("getTeamSize()", async function() { - const f = ["bladeburner", "getTeamSize"]; - await expectNonZeroRamCost(f); - }); - - it("setTeamSize()", async function() { - const f = ["bladeburner", "setTeamSize"]; - await expectNonZeroRamCost(f); - }); - - it("getCityEstimatedPopulation()", async function() { - const f = ["bladeburner", "getCityEstimatedPopulation"]; - await expectNonZeroRamCost(f); - }); - - it("getCityEstimatedCommunities()", async function() { - const f = ["bladeburner", "getCityEstimatedCommunities"]; - await expectNonZeroRamCost(f); - }); - - it("getCityChaos()", async function() { - const f = ["bladeburner", "getCityChaos"]; - await expectNonZeroRamCost(f); - }); - - it("getCity()", async function() { - const f = ["bladeburner", "getCity"]; - await expectNonZeroRamCost(f); - }); - - it("switchCity()", async function() { - const f = ["bladeburner", "switchCity"]; - await expectNonZeroRamCost(f); - }); - - it("getStamina()", async function() { - const f = ["bladeburner", "getStamina"]; - await expectNonZeroRamCost(f); - }); - - it("joinBladeburnerFaction()", async function() { - const f = ["bladeburner", "joinBladeburnerFaction"]; - await expectNonZeroRamCost(f); - }); - - it("joinBladeburnerDivision()", async function() { - const f = ["bladeburner", "joinBladeburnerDivision"]; - await expectNonZeroRamCost(f); - }); - - it("getBonusTime()", async function() { - const f = ["bladeburner", "getBonusTime"]; - await expectZeroRamCost(f); - }); + it("hackChance()", async function () { + const f = ["hackChance"]; + await expectNonZeroRamCost(f); }); - describe("Gang API", function() { - it("getMemberNames()", async function() { - const f = ["gang", "getMemberNames"]; - await expectNonZeroRamCost(f); - }); - - it("getGangInformation()", async function() { - const f = ["gang", "getGangInformation"]; - await expectNonZeroRamCost(f); - }); - - it("getOtherGangInformation()", async function() { - const f = ["gang", "getOtherGangInformation"]; - await expectNonZeroRamCost(f); - }); - - it("getMemberInformation()", async function() { - const f = ["gang", "getMemberInformation"]; - await expectNonZeroRamCost(f); - }); - - it("canRecruitMember()", async function() { - const f = ["gang", "canRecruitMember"]; - await expectNonZeroRamCost(f); - }); - - it("recruitMember()", async function() { - const f = ["gang", "recruitMember"]; - await expectNonZeroRamCost(f); - }); - - it("getTaskNames()", async function() { - const f = ["gang", "getTaskNames"]; - await expectNonZeroRamCost(f); - }); - - it("setMemberTask()", async function() { - const f = ["gang", "setMemberTask"]; - await expectNonZeroRamCost(f); - }); - - it("getEquipmentNames()", async function() { - const f = ["gang", "getEquipmentNames"]; - await expectNonZeroRamCost(f); - }); - - it("getEquipmentCost()", async function() { - const f = ["gang", "getEquipmentCost"]; - await expectNonZeroRamCost(f); - }); - - it("getEquipmentType()", async function() { - const f = ["gang", "getEquipmentType"]; - await expectNonZeroRamCost(f); - }); - - it("purchaseEquipment()", async function() { - const f = ["gang", "purchaseEquipment"]; - await expectNonZeroRamCost(f); - }); - - it("ascendMember()", async function() { - const f = ["gang", "ascendMember"]; - await expectNonZeroRamCost(f); - }); - - it("setTerritoryWarfare()", async function() { - const f = ["gang", "setTerritoryWarfare"]; - await expectNonZeroRamCost(f); - }); - - it("getChanceToWinClash()", async function() { - const f = ["gang", "getChanceToWinClash"]; - await expectNonZeroRamCost(f); - }); - - it("getBonusTime()", async function() { - const f = ["gang", "getBonusTime"]; - await expectZeroRamCost(f); - }); + it("growthAnalyze()", async function () { + const f = ["growthAnalyze"]; + await expectNonZeroRamCost(f); }); - describe("Coding Contract API", function() { - it("attempt()", async function() { - const f = ["codingcontract", "attempt"]; - await expectNonZeroRamCost(f); - }); - - it("getContractType()", async function() { - const f = ["codingcontract", "getContractType"]; - await expectNonZeroRamCost(f); - }); - - it("getDescription()", async function() { - const f = ["codingcontract", "getDescription"]; - await expectNonZeroRamCost(f); - }); - - it("getData()", async function() { - const f = ["codingcontract", "getData"]; - await expectNonZeroRamCost(f); - }); - - it("getNumTriesRemaining()", async function() { - const f = ["codingcontract", "getNumTriesRemaining"]; - await expectNonZeroRamCost(f); - }); + it("sleep()", async function () { + const f = ["sleep"]; + await expectZeroRamCost(f); }); - describe("Sleeve API", function() { - it("getNumSleeves()", async function() { - const f = ["sleeve", "getNumSleeves"]; - await expectNonZeroRamCost(f); - }); - - it("getSleeveStats()", async function() { - const f = ["sleeve", "getSleeveStats"]; - await expectNonZeroRamCost(f); - }); - - it("getInformation()", async function() { - const f = ["sleeve", "getInformation"]; - await expectNonZeroRamCost(f); - }); - - it("getTask()", async function() { - const f = ["sleeve", "getTask"]; - await expectNonZeroRamCost(f); - }); - - it("setToShockRecovery()", async function() { - const f = ["sleeve", "setToShockRecovery"]; - await expectNonZeroRamCost(f); - }); - - it("setToSynchronize()", async function() { - const f = ["sleeve", "setToSynchronize"]; - await expectNonZeroRamCost(f); - }); - - it("setToCommitCrime()", async function() { - const f = ["sleeve", "setToCommitCrime"]; - await expectNonZeroRamCost(f); - }); - - it("setToFactionWork()", async function() { - const f = ["sleeve", "setToFactionWork"]; - await expectNonZeroRamCost(f); - }); - - it("setToCompanyWork()", async function() { - const f = ["sleeve", "setToCompanyWork"]; - await expectNonZeroRamCost(f); - }); - - it("setToUniversityCourse()", async function() { - const f = ["sleeve", "setToUniversityCourse"]; - await expectNonZeroRamCost(f); - }); - - it("setToGymWorkout()", async function() { - const f = ["sleeve", "setToGymWorkout"]; - await expectNonZeroRamCost(f); - }); - - it("travel()", async function() { - const f = ["sleeve", "travel"]; - await expectNonZeroRamCost(f); - }); - - it("getSleeveAugmentations()", async function() { - const f = ["sleeve", "getSleeveAugmentations"]; - await expectNonZeroRamCost(f); - }); - - it("getSleevePurchasableAugs()", async function() { - const f = ["sleeve", "getSleevePurchasableAugs"]; - await expectNonZeroRamCost(f); - }); - - it("purchaseSleeveAug()", async function() { - const f = ["sleeve", "purchaseSleeveAug"]; - await expectNonZeroRamCost(f); - }); + it("print()", async function () { + const f = ["print"]; + await expectZeroRamCost(f); }); + + it("tprint()", async function () { + const f = ["tprint"]; + await expectZeroRamCost(f); + }); + + it("clearLog()", async function () { + const f = ["clearLog"]; + await expectZeroRamCost(f); + }); + + it("disableLog()", async function () { + const f = ["disableLog"]; + await expectZeroRamCost(f); + }); + + it("enableLog()", async function () { + const f = ["enableLog"]; + await expectZeroRamCost(f); + }); + + it("isLogEnabled()", async function () { + const f = ["isLogEnabled"]; + await expectZeroRamCost(f); + }); + + it("getScriptLogs()", async function () { + const f = ["getScriptLogs"]; + await expectZeroRamCost(f); + }); + + it("scan()", async function () { + const f = ["scan"]; + await expectNonZeroRamCost(f); + }); + + it("nuke()", async function () { + const f = ["nuke"]; + await expectNonZeroRamCost(f); + }); + + it("brutessh()", async function () { + const f = ["brutessh"]; + await expectNonZeroRamCost(f); + }); + + it("ftpcrack()", async function () { + const f = ["ftpcrack"]; + await expectNonZeroRamCost(f); + }); + + it("relaysmtp()", async function () { + const f = ["relaysmtp"]; + await expectNonZeroRamCost(f); + }); + + it("httpworm()", async function () { + const f = ["httpworm"]; + await expectNonZeroRamCost(f); + }); + + it("sqlinject()", async function () { + const f = ["sqlinject"]; + await expectNonZeroRamCost(f); + }); + + it("run()", async function () { + const f = ["run"]; + await expectNonZeroRamCost(f); + }); + + it("exec()", async function () { + const f = ["exec"]; + await expectNonZeroRamCost(f); + }); + + it("spawn()", async function () { + const f = ["spawn"]; + await expectNonZeroRamCost(f); + }); + + it("kill()", async function () { + const f = ["kill"]; + await expectNonZeroRamCost(f); + }); + + it("killall()", async function () { + const f = ["killall"]; + await expectNonZeroRamCost(f); + }); + + it("exit()", async function () { + const f = ["exit"]; + await expectZeroRamCost(f); + }); + + it("scp()", async function () { + const f = ["scp"]; + await expectNonZeroRamCost(f); + }); + + it("ls()", async function () { + const f = ["ls"]; + await expectNonZeroRamCost(f); + }); + + it("ps()", async function () { + const f = ["ps"]; + await expectNonZeroRamCost(f); + }); + + it("hasRootAccess()", async function () { + const f = ["hasRootAccess"]; + await expectNonZeroRamCost(f); + }); + + it("getHostname()", async function () { + const f = ["getHostname"]; + await expectNonZeroRamCost(f); + }); + + it("getHackingLevel()", async function () { + const f = ["getHackingLevel"]; + await expectNonZeroRamCost(f); + }); + + it("getHackingMultipliers()", async function () { + const f = ["getHackingMultipliers"]; + await expectNonZeroRamCost(f); + }); + + it("getHacknetMultipliers()", async function () { + const f = ["getHacknetMultipliers"]; + await expectNonZeroRamCost(f); + }); + + it("getServerMoneyAvailable()", async function () { + const f = ["getServerMoneyAvailable"]; + await expectNonZeroRamCost(f); + }); + + it("getServerMaxMoney()", async function () { + const f = ["getServerMaxMoney"]; + await expectNonZeroRamCost(f); + }); + + it("getServerGrowth()", async function () { + const f = ["getServerGrowth"]; + await expectNonZeroRamCost(f); + }); + + it("getServerSecurityLevel()", async function () { + const f = ["getServerSecurityLevel"]; + await expectNonZeroRamCost(f); + }); + + it("getServerBaseSecurityLevel()", async function () { + const f = ["getServerBaseSecurityLevel"]; + await expectNonZeroRamCost(f); + }); + + it("getServerMinSecurityLevel()", async function () { + const f = ["getServerMinSecurityLevel"]; + await expectNonZeroRamCost(f); + }); + + it("getServerRequiredHackingLevel()", async function () { + const f = ["getServerRequiredHackingLevel"]; + await expectNonZeroRamCost(f); + }); + + it("getServerNumPortsRequired()", async function () { + const f = ["getServerNumPortsRequired"]; + await expectNonZeroRamCost(f); + }); + + it("getServerRam()", async function () { + const f = ["getServerRam"]; + await expectNonZeroRamCost(f); + }); + + it("getServerMaxRam()", async function () { + const f = ["getServerMaxRam"]; + await expectNonZeroRamCost(f); + }); + + it("getServerUsedRam()", async function () { + const f = ["getServerUsedRam"]; + await expectNonZeroRamCost(f); + }); + + it("serverExists()", async function () { + const f = ["serverExists"]; + await expectNonZeroRamCost(f); + }); + + it("fileExists()", async function () { + const f = ["fileExists"]; + await expectNonZeroRamCost(f); + }); + + it("isRunning()", async function () { + const f = ["isRunning"]; + await expectNonZeroRamCost(f); + }); + + it("getPurchasedServerCost()", async function () { + const f = ["getPurchasedServerCost"]; + await expectNonZeroRamCost(f); + }); + + it("purchaseServer()", async function () { + const f = ["purchaseServer"]; + await expectNonZeroRamCost(f); + }); + + it("deleteServer()", async function () { + const f = ["deleteServer"]; + await expectNonZeroRamCost(f); + }); + + it("getPurchasedServers()", async function () { + const f = ["getPurchasedServers"]; + await expectNonZeroRamCost(f); + }); + + it("getPurchasedServerLimit()", async function () { + const f = ["getPurchasedServerLimit"]; + await expectNonZeroRamCost(f); + }); + + it("getPurchasedServerMaxRam()", async function () { + const f = ["getPurchasedServerMaxRam"]; + await expectNonZeroRamCost(f); + }); + + it("write()", async function () { + const f = ["write"]; + await expectNonZeroRamCost(f); + }); + + it("tryWrite()", async function () { + const f = ["tryWrite"]; + await expectNonZeroRamCost(f); + }); + + it("read()", async function () { + const f = ["read"]; + await expectNonZeroRamCost(f); + }); + + it("peek()", async function () { + const f = ["peek"]; + await expectNonZeroRamCost(f); + }); + + it("clear()", async function () { + const f = ["clear"]; + await expectNonZeroRamCost(f); + }); + + it("getPortHandle()", async function () { + const f = ["getPortHandle"]; + await expectNonZeroRamCost(f); + }); + + it("rm()", async function () { + const f = ["rm"]; + await expectNonZeroRamCost(f); + }); + + it("scriptRunning()", async function () { + const f = ["scriptRunning"]; + await expectNonZeroRamCost(f); + }); + + it("scriptKill()", async function () { + const f = ["scriptKill"]; + await expectNonZeroRamCost(f); + }); + + it("getScriptName()", async function () { + const f = ["getScriptName"]; + await expectZeroRamCost(f); + }); + + it("getScriptRam()", async function () { + const f = ["getScriptRam"]; + await expectNonZeroRamCost(f); + }); + + it("getHackTime()", async function () { + const f = ["getHackTime"]; + await expectNonZeroRamCost(f); + }); + + it("getGrowTime()", async function () { + const f = ["getGrowTime"]; + await expectNonZeroRamCost(f); + }); + + it("getWeakenTime()", async function () { + const f = ["getWeakenTime"]; + await expectNonZeroRamCost(f); + }); + + it("getScriptIncome()", async function () { + const f = ["getScriptIncome"]; + await expectNonZeroRamCost(f); + }); + + it("getScriptExpGain()", async function () { + const f = ["getScriptExpGain"]; + await expectNonZeroRamCost(f); + }); + + it("getRunningScript()", async function () { + const f = ["getRunningScript"]; + await expectNonZeroRamCost(f); + }); + + it("getTimeSinceLastAug()", async function () { + const f = ["getTimeSinceLastAug"]; + await expectNonZeroRamCost(f); + }); + + it("sprintf()", async function () { + const f = ["sprintf"]; + await expectZeroRamCost(f); + }); + + it("vsprintf()", async function () { + const f = ["vsprintf"]; + await expectZeroRamCost(f); + }); + + it("nFormat()", async function () { + const f = ["nFormat"]; + await expectZeroRamCost(f); + }); + + it("prompt()", async function () { + const f = ["prompt"]; + await expectZeroRamCost(f); + }); + + it("wget()", async function () { + const f = ["wget"]; + await expectZeroRamCost(f); + }); + + it("getFavorToDonate()", async function () { + const f = ["getFavorToDonate"]; + await expectNonZeroRamCost(f); + }); + }); + + describe("Advanced Functions", function () { + it("getBitNodeMultipliers()", async function () { + const f = ["getBitNodeMultipliers"]; + await expectNonZeroRamCost(f); + }); + }); + + describe("Hacknet Node API", function () { + // The Hacknet Node API RAM cost is a bit different because + // it's just a one-time cost to access the 'hacknet' namespace. + // Otherwise, all functions cost 0 RAM + const apiFunctions = [ + "numNodes", + "purchaseNode", + "getPurchaseNodeCost", + "getNodeStats", + "upgradeLevel", + "upgradeRam", + "upgradeCore", + "upgradeCache", + "getLevelUpgradeCost", + "getRamUpgradeCost", + "getCoreUpgradeCost", + "getCacheUpgradeCost", + "numHashes", + "hashCost", + "spendHashes", + ]; + it("should have zero RAM cost for all functions", function () { + for (const fn of apiFunctions) { + expect(getRamCost("hacknet", fn)).toEqual(0); + } + }); + + it("should incur a one time cost of for accesing the namespace", async function () { + let code = ""; + for (const fn of apiFunctions) { + code += "hacknet." + fn + "(); "; + } + + const calculated = await calculateRamUsage(code, []); + testEquality(calculated, ScriptBaseCost + HacknetNamespaceCost); + }); + }); + + describe("TIX API", function () { + it("getStockSymbols()", async function () { + const f = ["getStockSymbols"]; + await expectNonZeroRamCost(f); + }); + + it("getStockPrice()", async function () { + const f = ["getStockPrice"]; + await expectNonZeroRamCost(f); + }); + + it("getStockAskPrice()", async function () { + const f = ["getStockAskPrice"]; + await expectNonZeroRamCost(f); + }); + + it("getStockBidPrice()", async function () { + const f = ["getStockBidPrice"]; + await expectNonZeroRamCost(f); + }); + + it("getStockPosition()", async function () { + const f = ["getStockPosition"]; + await expectNonZeroRamCost(f); + }); + + it("getStockMaxShares()", async function () { + const f = ["getStockMaxShares"]; + await expectNonZeroRamCost(f); + }); + + it("getStockPurchaseCost()", async function () { + const f = ["getStockPurchaseCost"]; + await expectNonZeroRamCost(f); + }); + + it("getStockSaleGain()", async function () { + const f = ["getStockSaleGain"]; + await expectNonZeroRamCost(f); + }); + + it("buyStock()", async function () { + const f = ["buyStock"]; + await expectNonZeroRamCost(f); + }); + + it("sellStock()", async function () { + const f = ["sellStock"]; + await expectNonZeroRamCost(f); + }); + + it("shortStock()", async function () { + const f = ["shortStock"]; + await expectNonZeroRamCost(f); + }); + + it("sellShort()", async function () { + const f = ["sellShort"]; + await expectNonZeroRamCost(f); + }); + + it("placeOrder()", async function () { + const f = ["placeOrder"]; + await expectNonZeroRamCost(f); + }); + + it("cancelOrder()", async function () { + const f = ["cancelOrder"]; + await expectNonZeroRamCost(f); + }); + + it("getOrders()", async function () { + const f = ["getOrders"]; + await expectNonZeroRamCost(f); + }); + + it("getStockVolatility()", async function () { + const f = ["getStockVolatility"]; + await expectNonZeroRamCost(f); + }); + + it("getStockForecast()", async function () { + const f = ["getStockForecast"]; + await expectNonZeroRamCost(f); + }); + + it("purchase4SMarketData()", async function () { + const f = ["purchase4SMarketData"]; + await expectNonZeroRamCost(f); + }); + + it("purchase4SMarketDataTixApi()", async function () { + const f = ["purchase4SMarketDataTixApi"]; + await expectNonZeroRamCost(f); + }); + }); + + describe("Singularity Functions", function () { + it("universityCourse()", async function () { + const f = ["universityCourse"]; + await expectNonZeroRamCost(f); + }); + + it("gymWorkout()", async function () { + const f = ["gymWorkout"]; + await expectNonZeroRamCost(f); + }); + + it("travelToCity()", async function () { + const f = ["travelToCity"]; + await expectNonZeroRamCost(f); + }); + + it("purchaseTor()", async function () { + const f = ["purchaseTor"]; + await expectNonZeroRamCost(f); + }); + + it("purchaseProgram()", async function () { + const f = ["purchaseProgram"]; + await expectNonZeroRamCost(f); + }); + + it("getStats()", async function () { + const f = ["getStats"]; + await expectNonZeroRamCost(f); + }); + + it("getCharacterInformation()", async function () { + const f = ["getCharacterInformation"]; + await expectNonZeroRamCost(f); + }); + + it("isBusy()", async function () { + const f = ["isBusy"]; + await expectNonZeroRamCost(f); + }); + + it("stopAction()", async function () { + const f = ["stopAction"]; + await expectNonZeroRamCost(f); + }); + + it("upgradeHomeRam()", async function () { + const f = ["upgradeHomeRam"]; + await expectNonZeroRamCost(f); + }); + + it("getUpgradeHomeRamCost()", async function () { + const f = ["getUpgradeHomeRamCost"]; + await expectNonZeroRamCost(f); + }); + + it("workForCompany()", async function () { + const f = ["workForCompany"]; + await expectNonZeroRamCost(f); + }); + + it("applyToCompany()", async function () { + const f = ["applyToCompany"]; + await expectNonZeroRamCost(f); + }); + + it("getCompanyRep()", async function () { + const f = ["getCompanyRep"]; + await expectNonZeroRamCost(f); + }); + + it("getCompanyFavor()", async function () { + const f = ["getCompanyFavor"]; + await expectNonZeroRamCost(f); + }); + + it("getCompanyFavorGain()", async function () { + const f = ["getCompanyFavorGain"]; + await expectNonZeroRamCost(f); + }); + + it("checkFactionInvitations()", async function () { + const f = ["checkFactionInvitations"]; + await expectNonZeroRamCost(f); + }); + + it("joinFaction()", async function () { + const f = ["joinFaction"]; + await expectNonZeroRamCost(f); + }); + + it("workForFaction()", async function () { + const f = ["workForFaction"]; + await expectNonZeroRamCost(f); + }); + + it("getFactionRep()", async function () { + const f = ["getFactionRep"]; + await expectNonZeroRamCost(f); + }); + + it("getFactionFavor()", async function () { + const f = ["getFactionFavor"]; + await expectNonZeroRamCost(f); + }); + + it("getFactionFavorGain()", async function () { + const f = ["getFactionFavorGain"]; + await expectNonZeroRamCost(f); + }); + + it("donateToFaction()", async function () { + const f = ["donateToFaction"]; + await expectNonZeroRamCost(f); + }); + + it("createProgram()", async function () { + const f = ["createProgram"]; + await expectNonZeroRamCost(f); + }); + + it("commitCrime()", async function () { + const f = ["commitCrime"]; + await expectNonZeroRamCost(f); + }); + + it("getCrimeChance()", async function () { + const f = ["getCrimeChance"]; + await expectNonZeroRamCost(f); + }); + + it("getOwnedAugmentations()", async function () { + const f = ["getOwnedAugmentations"]; + await expectNonZeroRamCost(f); + }); + + it("getOwnedSourceFiles()", async function () { + const f = ["getOwnedSourceFiles"]; + await expectNonZeroRamCost(f); + }); + + it("getAugmentationsFromFaction()", async function () { + const f = ["getAugmentationsFromFaction"]; + await expectNonZeroRamCost(f); + }); + + it("getAugmentationPrereq()", async function () { + const f = ["getAugmentationPrereq"]; + await expectNonZeroRamCost(f); + }); + + it("getAugmentationCost()", async function () { + const f = ["getAugmentationCost"]; + await expectNonZeroRamCost(f); + }); + + it("purchaseAugmentation()", async function () { + const f = ["purchaseAugmentation"]; + await expectNonZeroRamCost(f); + }); + + it("installAugmentations()", async function () { + const f = ["installAugmentations"]; + await expectNonZeroRamCost(f); + }); + }); + + describe("Bladeburner API", function () { + it("getContractNames()", async function () { + const f = ["bladeburner", "getContractNames"]; + await expectNonZeroRamCost(f); + }); + + it("getOperationNames()", async function () { + const f = ["bladeburner", "getOperationNames"]; + await expectNonZeroRamCost(f); + }); + + it("getBlackOpNames()", async function () { + const f = ["bladeburner", "getBlackOpNames"]; + await expectNonZeroRamCost(f); + }); + + it("getGeneralActionNames()", async function () { + const f = ["bladeburner", "getGeneralActionNames"]; + await expectNonZeroRamCost(f); + }); + + it("getSkillNames()", async function () { + const f = ["bladeburner", "getSkillNames"]; + await expectNonZeroRamCost(f); + }); + + it("startAction()", async function () { + const f = ["bladeburner", "startAction"]; + await expectNonZeroRamCost(f); + }); + + it("stopBladeburnerAction()", async function () { + const f = ["bladeburner", "stopBladeburnerAction"]; + await expectNonZeroRamCost(f); + }); + + it("getCurrentAction()", async function () { + const f = ["bladeburner", "getCurrentAction"]; + await expectNonZeroRamCost(f); + }); + + it("getActionTime()", async function () { + const f = ["bladeburner", "getActionTime"]; + await expectNonZeroRamCost(f); + }); + + it("getActionEstimatedSuccessChance()", async function () { + const f = ["bladeburner", "getActionEstimatedSuccessChance"]; + await expectNonZeroRamCost(f); + }); + + it("getActionRepGain()", async function () { + const f = ["bladeburner", "getActionRepGain"]; + await expectNonZeroRamCost(f); + }); + + it("getActionCountRemaining()", async function () { + const f = ["bladeburner", "getActionCountRemaining"]; + await expectNonZeroRamCost(f); + }); + + it("getActionMaxLevel()", async function () { + const f = ["bladeburner", "getActionMaxLevel"]; + await expectNonZeroRamCost(f); + }); + + it("getActionCurrentLevel()", async function () { + const f = ["bladeburner", "getActionCurrentLevel"]; + await expectNonZeroRamCost(f); + }); + + it("getActionAutolevel()", async function () { + const f = ["bladeburner", "getActionAutolevel"]; + await expectNonZeroRamCost(f); + }); + + it("setActionAutolevel()", async function () { + const f = ["bladeburner", "setActionAutolevel"]; + await expectNonZeroRamCost(f); + }); + + it("setActionLevel()", async function () { + const f = ["bladeburner", "setActionLevel"]; + await expectNonZeroRamCost(f); + }); + + it("getRank()", async function () { + const f = ["bladeburner", "getRank"]; + await expectNonZeroRamCost(f); + }); + + it("getBlackOpRank()", async function () { + const f = ["bladeburner", "getBlackOpRank"]; + await expectNonZeroRamCost(f); + }); + + it("getSkillPoints()", async function () { + const f = ["bladeburner", "getSkillPoints"]; + await expectNonZeroRamCost(f); + }); + + it("getSkillLevel()", async function () { + const f = ["bladeburner", "getSkillLevel"]; + await expectNonZeroRamCost(f); + }); + + it("getSkillUpgradeCost()", async function () { + const f = ["bladeburner", "getSkillUpgradeCost"]; + await expectNonZeroRamCost(f); + }); + + it("upgradeSkill()", async function () { + const f = ["bladeburner", "upgradeSkill"]; + await expectNonZeroRamCost(f); + }); + + it("getTeamSize()", async function () { + const f = ["bladeburner", "getTeamSize"]; + await expectNonZeroRamCost(f); + }); + + it("setTeamSize()", async function () { + const f = ["bladeburner", "setTeamSize"]; + await expectNonZeroRamCost(f); + }); + + it("getCityEstimatedPopulation()", async function () { + const f = ["bladeburner", "getCityEstimatedPopulation"]; + await expectNonZeroRamCost(f); + }); + + it("getCityEstimatedCommunities()", async function () { + const f = ["bladeburner", "getCityEstimatedCommunities"]; + await expectNonZeroRamCost(f); + }); + + it("getCityChaos()", async function () { + const f = ["bladeburner", "getCityChaos"]; + await expectNonZeroRamCost(f); + }); + + it("getCity()", async function () { + const f = ["bladeburner", "getCity"]; + await expectNonZeroRamCost(f); + }); + + it("switchCity()", async function () { + const f = ["bladeburner", "switchCity"]; + await expectNonZeroRamCost(f); + }); + + it("getStamina()", async function () { + const f = ["bladeburner", "getStamina"]; + await expectNonZeroRamCost(f); + }); + + it("joinBladeburnerFaction()", async function () { + const f = ["bladeburner", "joinBladeburnerFaction"]; + await expectNonZeroRamCost(f); + }); + + it("joinBladeburnerDivision()", async function () { + const f = ["bladeburner", "joinBladeburnerDivision"]; + await expectNonZeroRamCost(f); + }); + + it("getBonusTime()", async function () { + const f = ["bladeburner", "getBonusTime"]; + await expectZeroRamCost(f); + }); + }); + + describe("Gang API", function () { + it("getMemberNames()", async function () { + const f = ["gang", "getMemberNames"]; + await expectNonZeroRamCost(f); + }); + + it("getGangInformation()", async function () { + const f = ["gang", "getGangInformation"]; + await expectNonZeroRamCost(f); + }); + + it("getOtherGangInformation()", async function () { + const f = ["gang", "getOtherGangInformation"]; + await expectNonZeroRamCost(f); + }); + + it("getMemberInformation()", async function () { + const f = ["gang", "getMemberInformation"]; + await expectNonZeroRamCost(f); + }); + + it("canRecruitMember()", async function () { + const f = ["gang", "canRecruitMember"]; + await expectNonZeroRamCost(f); + }); + + it("recruitMember()", async function () { + const f = ["gang", "recruitMember"]; + await expectNonZeroRamCost(f); + }); + + it("getTaskNames()", async function () { + const f = ["gang", "getTaskNames"]; + await expectNonZeroRamCost(f); + }); + + it("setMemberTask()", async function () { + const f = ["gang", "setMemberTask"]; + await expectNonZeroRamCost(f); + }); + + it("getEquipmentNames()", async function () { + const f = ["gang", "getEquipmentNames"]; + await expectNonZeroRamCost(f); + }); + + it("getEquipmentCost()", async function () { + const f = ["gang", "getEquipmentCost"]; + await expectNonZeroRamCost(f); + }); + + it("getEquipmentType()", async function () { + const f = ["gang", "getEquipmentType"]; + await expectNonZeroRamCost(f); + }); + + it("purchaseEquipment()", async function () { + const f = ["gang", "purchaseEquipment"]; + await expectNonZeroRamCost(f); + }); + + it("ascendMember()", async function () { + const f = ["gang", "ascendMember"]; + await expectNonZeroRamCost(f); + }); + + it("setTerritoryWarfare()", async function () { + const f = ["gang", "setTerritoryWarfare"]; + await expectNonZeroRamCost(f); + }); + + it("getChanceToWinClash()", async function () { + const f = ["gang", "getChanceToWinClash"]; + await expectNonZeroRamCost(f); + }); + + it("getBonusTime()", async function () { + const f = ["gang", "getBonusTime"]; + await expectZeroRamCost(f); + }); + }); + + describe("Coding Contract API", function () { + it("attempt()", async function () { + const f = ["codingcontract", "attempt"]; + await expectNonZeroRamCost(f); + }); + + it("getContractType()", async function () { + const f = ["codingcontract", "getContractType"]; + await expectNonZeroRamCost(f); + }); + + it("getDescription()", async function () { + const f = ["codingcontract", "getDescription"]; + await expectNonZeroRamCost(f); + }); + + it("getData()", async function () { + const f = ["codingcontract", "getData"]; + await expectNonZeroRamCost(f); + }); + + it("getNumTriesRemaining()", async function () { + const f = ["codingcontract", "getNumTriesRemaining"]; + await expectNonZeroRamCost(f); + }); + }); + + describe("Sleeve API", function () { + it("getNumSleeves()", async function () { + const f = ["sleeve", "getNumSleeves"]; + await expectNonZeroRamCost(f); + }); + + it("getSleeveStats()", async function () { + const f = ["sleeve", "getSleeveStats"]; + await expectNonZeroRamCost(f); + }); + + it("getInformation()", async function () { + const f = ["sleeve", "getInformation"]; + await expectNonZeroRamCost(f); + }); + + it("getTask()", async function () { + const f = ["sleeve", "getTask"]; + await expectNonZeroRamCost(f); + }); + + it("setToShockRecovery()", async function () { + const f = ["sleeve", "setToShockRecovery"]; + await expectNonZeroRamCost(f); + }); + + it("setToSynchronize()", async function () { + const f = ["sleeve", "setToSynchronize"]; + await expectNonZeroRamCost(f); + }); + + it("setToCommitCrime()", async function () { + const f = ["sleeve", "setToCommitCrime"]; + await expectNonZeroRamCost(f); + }); + + it("setToFactionWork()", async function () { + const f = ["sleeve", "setToFactionWork"]; + await expectNonZeroRamCost(f); + }); + + it("setToCompanyWork()", async function () { + const f = ["sleeve", "setToCompanyWork"]; + await expectNonZeroRamCost(f); + }); + + it("setToUniversityCourse()", async function () { + const f = ["sleeve", "setToUniversityCourse"]; + await expectNonZeroRamCost(f); + }); + + it("setToGymWorkout()", async function () { + const f = ["sleeve", "setToGymWorkout"]; + await expectNonZeroRamCost(f); + }); + + it("travel()", async function () { + const f = ["sleeve", "travel"]; + await expectNonZeroRamCost(f); + }); + + it("getSleeveAugmentations()", async function () { + const f = ["sleeve", "getSleeveAugmentations"]; + await expectNonZeroRamCost(f); + }); + + it("getSleevePurchasableAugs()", async function () { + const f = ["sleeve", "getSleevePurchasableAugs"]; + await expectNonZeroRamCost(f); + }); + + it("purchaseSleeveAug()", async function () { + const f = ["sleeve", "purchaseSleeveAug"]; + await expectNonZeroRamCost(f); + }); + }); }); diff --git a/test/README.md b/test/README.md index b9cbb732f..3ce83dbef 100644 --- a/test/README.md +++ b/test/README.md @@ -1,4 +1,5 @@ # Unit Tests + This directory contains unit tests for Bitburner. Unit tests use jest. diff --git a/test/StockMarket.test.ts b/test/StockMarket.test.ts index 08eafab80..7c6f32f8f 100644 --- a/test/StockMarket.test.ts +++ b/test/StockMarket.test.ts @@ -6,1307 +6,1606 @@ import { Company } from "../src/Company/Company"; import { Server } from "../src/Server/Server"; import { - buyStock, - sellStock, - shortStock, - sellShort, + buyStock, + sellStock, + shortStock, + sellShort, } from "../src/StockMarket/BuyingAndSelling"; import { IStockMarket } from "../src/StockMarket/IStockMarket"; import { Order } from "../src/StockMarket/Order"; import { - forecastForecastChangeFromCompanyWork, - forecastForecastChangeFromHack, - influenceStockThroughCompanyWork, - influenceStockThroughServerGrow, - influenceStockThroughServerHack, + forecastForecastChangeFromCompanyWork, + forecastForecastChangeFromHack, + influenceStockThroughCompanyWork, + influenceStockThroughServerGrow, + influenceStockThroughServerHack, } from "../src/StockMarket/PlayerInfluencing"; -import { processOrders, IProcessOrderRefs } from "../src/StockMarket/OrderProcessing"; -import { Stock , StockForecastInfluenceLimit } from "../src/StockMarket/Stock"; import { - cancelOrder, - deleteStockMarket, - initStockMarket, - initSymbolToStockMap, - placeOrder, - processStockPrices, - StockMarket, - SymbolToStockMap, + processOrders, + IProcessOrderRefs, +} from "../src/StockMarket/OrderProcessing"; +import { Stock, StockForecastInfluenceLimit } from "../src/StockMarket/Stock"; +import { + cancelOrder, + deleteStockMarket, + initStockMarket, + initSymbolToStockMap, + placeOrder, + processStockPrices, + StockMarket, + SymbolToStockMap, } from "../src/StockMarket/StockMarket"; import { - forecastChangePerPriceMovement, - getBuyTransactionCost, - getSellTransactionGain, - processTransactionForecastMovement, + forecastChangePerPriceMovement, + getBuyTransactionCost, + getSellTransactionGain, + processTransactionForecastMovement, } from "../src/StockMarket/StockMarketHelpers"; -import { OrderTypes } from "../src/StockMarket/data/OrderTypes" +import { OrderTypes } from "../src/StockMarket/data/OrderTypes"; import { PositionTypes } from "../src/StockMarket/data/PositionTypes"; jest.mock("../src/ui/React/createPopup.tsx", () => ({ - createPopup: jest.fn(), -})) + createPopup: jest.fn(), +})); -describe("Stock Market Tests", function() { - const commission = CONSTANTS.StockMarketCommission; +describe("Stock Market Tests", function () { + const commission = CONSTANTS.StockMarketCommission; - // Generic Stock object that can be used by each test - let stock: Stock; - const ctorParams = { - b: true, - initPrice: 10e3, - marketCap: 5e9, - mv: 2, - name: "MockStock", - otlkMag: 20, - spreadPerc: 1, - shareTxForMovement: 5e3, - symbol: "mock", - }; + // Generic Stock object that can be used by each test + let stock: Stock; + const ctorParams = { + b: true, + initPrice: 10e3, + marketCap: 5e9, + mv: 2, + name: "MockStock", + otlkMag: 20, + spreadPerc: 1, + shareTxForMovement: 5e3, + symbol: "mock", + }; - beforeEach(function() { + beforeEach(function () { + function construct(): void { + stock = new Stock(ctorParams); + } + + expect(construct).not.toThrow(); + }); + + describe("Stock Class", function () { + describe("constructor", function () { + it("should have default parameters", function () { function construct(): void { - stock = new Stock(ctorParams); + new Stock(); // eslint-disable-line no-new + } + expect(construct).not.toThrow(); + + const defaultStock = new Stock(); + expect(defaultStock).not.toEqual(null); + expect(defaultStock.name).toEqual(""); + }); + + it("should properly initialize props from parameters", function () { + expect(stock.name).toEqual(ctorParams.name); + expect(stock.symbol).toEqual(ctorParams.symbol); + expect(stock.price).toEqual(ctorParams.initPrice); + expect(stock.lastPrice).toEqual(ctorParams.initPrice); + expect(stock.b).toEqual(ctorParams.b); + expect(stock.mv).toEqual(ctorParams.mv); + expect(stock.shareTxForMovement).toEqual(ctorParams.shareTxForMovement); + expect(stock.shareTxUntilMovement).toEqual( + ctorParams.shareTxForMovement, + ); + expect(stock.maxShares).toBeLessThan(stock.totalShares); + expect(stock.spreadPerc).toEqual(ctorParams.spreadPerc); + expect(stock.otlkMag).toEqual(ctorParams.otlkMag); + expect(stock.otlkMagForecast).toEqual( + ctorParams.b ? 50 + ctorParams.otlkMag : 50 - ctorParams.otlkMag, + ); + }); + + it("should properly initialize props from range-values", function () { + const params = { + b: true, + initPrice: { + max: 10e3, + min: 1e3, + }, + marketCap: 5e9, + mv: { + divisor: 100, + max: 150, + min: 50, + }, + name: "MockStock", + otlkMag: 10, + spreadPerc: { + divisor: 10, + max: 10, + min: 1, + }, + shareTxForMovement: { + max: 10e3, + min: 5e3, + }, + symbol: "mock", + }; + + function construct(): void { + new Stock(params); // eslint-disable-line no-new } expect(construct).not.toThrow(); + + const stock = new Stock(params); + expect(stock).not.toEqual(null); + expect(stock.price).toBeGreaterThanOrEqual(params.initPrice.min); + expect(stock.price).toBeLessThanOrEqual(params.initPrice.max); + expect(stock.mv).toBeGreaterThanOrEqual( + params.mv.min / params.mv.divisor, + ); + expect(stock.mv).toBeLessThanOrEqual(params.mv.max / params.mv.divisor); + expect(stock.spreadPerc).toBeGreaterThanOrEqual( + params.spreadPerc.min / params.spreadPerc.divisor, + ); + expect(stock.spreadPerc).toBeLessThanOrEqual( + params.spreadPerc.max / params.spreadPerc.divisor, + ); + expect(stock.shareTxForMovement).toBeGreaterThanOrEqual( + params.shareTxForMovement.min, + ); + expect(stock.shareTxForMovement).toBeLessThanOrEqual( + params.shareTxForMovement.max, + ); + }); + + it("should round the 'totalShare' prop to the nearest 100k", function () { + expect(stock.totalShares % 100e3).toEqual(0); + }); }); - describe("Stock Class", function() { - describe("constructor", function() { - it("should have default parameters", function() { - function construct(): void { - new Stock(); // eslint-disable-line no-new - } - expect(construct).not.toThrow(); + describe("#changeForecastForecast()", function () { + it("should get the stock's second-order forecast property", function () { + stock.changeForecastForecast(99); + expect(stock.otlkMagForecast).toEqual(99); + stock.changeForecastForecast(1); + expect(stock.otlkMagForecast).toEqual(1); + }); - const defaultStock = new Stock(); - expect(defaultStock).not.toEqual(null); - expect(defaultStock.name).toEqual(""); - }); - - it("should properly initialize props from parameters", function() { - expect(stock.name).toEqual(ctorParams.name); - expect(stock.symbol).toEqual(ctorParams.symbol); - expect(stock.price).toEqual(ctorParams.initPrice); - expect(stock.lastPrice).toEqual(ctorParams.initPrice); - expect(stock.b).toEqual(ctorParams.b); - expect(stock.mv).toEqual(ctorParams.mv); - expect(stock.shareTxForMovement).toEqual(ctorParams.shareTxForMovement); - expect(stock.shareTxUntilMovement).toEqual(ctorParams.shareTxForMovement); - expect(stock.maxShares).toBeLessThan(stock.totalShares); - expect(stock.spreadPerc).toEqual(ctorParams.spreadPerc); - expect(stock.otlkMag).toEqual(ctorParams.otlkMag); - expect(stock.otlkMagForecast).toEqual(ctorParams.b ? 50 + ctorParams.otlkMag : 50 - ctorParams.otlkMag); - }); - - it ("should properly initialize props from range-values", function() { - const params = { - b: true, - initPrice: { - max: 10e3, - min: 1e3, - }, - marketCap: 5e9, - mv: { - divisor: 100, - max: 150, - min: 50, - }, - name: "MockStock", - otlkMag: 10, - spreadPerc: { - divisor: 10, - max: 10, - min: 1, - }, - shareTxForMovement: { - max: 10e3, - min: 5e3, - }, - symbol: "mock", - }; - - function construct(): void { - new Stock(params); // eslint-disable-line no-new - } - - expect(construct).not.toThrow(); - - const stock = new Stock(params); - expect(stock).not.toEqual(null); - expect(stock.price).toBeGreaterThanOrEqual(params.initPrice.min); - expect(stock.price).toBeLessThanOrEqual(params.initPrice.max); - expect(stock.mv).toBeGreaterThanOrEqual(params.mv.min / params.mv.divisor); - expect(stock.mv).toBeLessThanOrEqual(params.mv.max / params.mv.divisor); - expect(stock.spreadPerc).toBeGreaterThanOrEqual(params.spreadPerc.min / params.spreadPerc.divisor); - expect(stock.spreadPerc).toBeLessThanOrEqual(params.spreadPerc.max / params.spreadPerc.divisor); - expect(stock.shareTxForMovement).toBeGreaterThanOrEqual(params.shareTxForMovement.min); - expect(stock.shareTxForMovement).toBeLessThanOrEqual(params.shareTxForMovement.max); - }); - - it("should round the 'totalShare' prop to the nearest 100k", function() { - expect(stock.totalShares % 100e3).toEqual(0); - }); - }); - - describe("#changeForecastForecast()", function() { - it("should get the stock's second-order forecast property", function() { - stock.changeForecastForecast(99); - expect(stock.otlkMagForecast).toEqual(99); - stock.changeForecastForecast(1); - expect(stock.otlkMagForecast).toEqual(1); - }); - - it("should prevent values outside of 0-100", function() { - stock.changeForecastForecast(101); - expect(stock.otlkMagForecast).toEqual(100); - stock.changeForecastForecast(-1); - expect(stock.otlkMagForecast).toEqual(0); - }); - }); - - describe("#changePrice()", function() { - it("should set both the last price and current price properties", function() { - const newPrice = 20e3; - stock.changePrice(newPrice); - expect(stock.lastPrice).toEqual(ctorParams.initPrice); - expect(stock.price).toEqual(newPrice); - }); - }); - - describe("#cycleForecast()", function() { - it("should appropriately change the otlkMag by the given amount when b=true", function() { - stock.getForecastIncreaseChance = () => { return 1; } - stock.cycleForecast(5); - expect(stock.otlkMag).toEqual(ctorParams.otlkMag + 5); - - stock.getForecastIncreaseChance = () => { return 0; } - stock.cycleForecast(10); - expect(stock.otlkMag).toEqual(ctorParams.otlkMag - 5); - }); - - it("should NOT(!) the stock's 'b' property if it causes 'otlkMag' to go below 0", function() { - stock.getForecastIncreaseChance = () => { return 0; } - stock.cycleForecast(25); - expect(stock.otlkMag).toEqual(5); - expect(stock.b).toEqual(false); - }); - }); - - describe("#cycleForecastForecast()", function() { - it("should increase the stock's second-order forecast by a given amount", function() { - const expected = [65, 75]; - stock.cycleForecastForecast(5); - expect(expected).toContain(stock.otlkMagForecast); - }); - }); - - describe("#flipForecastForecast()", function() { - it("should flip the 'otlkMagForecast' property around 50", function() { - stock.otlkMagForecast = 50; - stock.flipForecastForecast(); - expect(stock.otlkMagForecast).toEqual(50); - - stock.otlkMagForecast = 60; - stock.flipForecastForecast(); - expect(stock.otlkMagForecast).toEqual(40); - - stock.otlkMagForecast = 90; - stock.flipForecastForecast(); - expect(stock.otlkMagForecast).toEqual(10); - - stock.otlkMagForecast = 100; - stock.flipForecastForecast(); - expect(stock.otlkMagForecast).toEqual(0); - - stock.otlkMagForecast = 40; - stock.flipForecastForecast(); - expect(stock.otlkMagForecast).toEqual(60); - - stock.otlkMagForecast = 0; - stock.flipForecastForecast(); - expect(stock.otlkMagForecast).toEqual(100); - - stock.otlkMagForecast = 25; - stock.flipForecastForecast(); - expect(stock.otlkMagForecast).toEqual(75); - }) - }); - - describe("#getAbsoluteForecast()", function() { - it("should return the absolute forecast on a 1-100 scale", function() { - stock.b = true; - stock.otlkMag = 10; - expect(stock.getAbsoluteForecast()).toEqual(60); - - stock.b = false; - expect(stock.getAbsoluteForecast()).toEqual(40); - - stock.otlkMag = 30; - expect(stock.getAbsoluteForecast()).toEqual(20); - - stock.b = true; - expect(stock.getAbsoluteForecast()).toEqual(80); - - stock.otlkMag = 0; - expect(stock.getAbsoluteForecast()).toEqual(50); - - stock.b = false; - expect(stock.getAbsoluteForecast()).toEqual(50); - }); - }); - - describe("#getAskPrice()", function() { - it("should return the price increased by spread percentage", function() { - const perc = stock.spreadPerc / 100; - expect(perc).toBeLessThanOrEqual(1); - expect(perc).toBeGreaterThanOrEqual(0); - - const expected = stock.price * (1 + perc); - expect(stock.getAskPrice()).toEqual(expected); - }); - }); - - describe("#getBidPrice()", function() { - it("should return the price decreased by spread percentage", function() { - const perc = stock.spreadPerc / 100; - expect(perc).toBeLessThanOrEqual(1); - expect(perc).toBeGreaterThanOrEqual(0); - - const expected = stock.price * (1 - perc); - expect(stock.getBidPrice()).toEqual(expected); - }); - }); - - describe("#getForecastIncreaseChance()", function() { - it("should return the chance that the stock has of increasing in decimal form", function() { - stock.b = true; - - stock.otlkMagForecast = 90; - stock.otlkMag = 20; // Absolute forecast of 70 - expect(stock.getForecastIncreaseChance()).toEqual(0.7); - - stock.otlkMag = 25; // Absolute forecast of 75 - expect(stock.getForecastIncreaseChance()).toEqual(0.65); - - stock.otlkMagForecast = 100; - stock.otlkMag = 0; // Absolute forecast of 50 - expect(stock.getForecastIncreaseChance()).toEqual(0.95); - - stock.otlkMagForecast = 60; - stock.otlkMag = 25; // Absolute forecast of 75 - expect(stock.getForecastIncreaseChance()).toEqual(0.35); - - stock.otlkMagForecast = 10; - expect(stock.getForecastIncreaseChance()).toEqual(0.05); - - stock.b = false; - - stock.otlkMagForecast = 90; - stock.otlkMag = 20; // Absolute forecast of 30 - expect(stock.getForecastIncreaseChance()).toEqual(0.95); - - stock.otlkMagForecast = 50; - stock.otlkMag = 25; // Absolute forecast of 25 - expect(stock.getForecastIncreaseChance()).toEqual(0.75); - - stock.otlkMagForecast = 100; - stock.otlkMag = 0; // Absolute forecast of 50 - expect(stock.getForecastIncreaseChance()).toEqual(0.95); - - stock.otlkMagForecast = 5; - stock.otlkMag = 25; // Absolute forecast of 25 - expect(stock.getForecastIncreaseChance()).toEqual(0.3); - - stock.otlkMagForecast = 10; - expect(stock.getForecastIncreaseChance()).toEqual(0.35); - - stock.otlkMagForecast = 50; - stock.otlkMag = 0; - expect(stock.getForecastIncreaseChance()).toEqual(0.5); - - stock.otlkMagForecast = 25; - stock.otlkMag = 5; // Asolute forecast of 45 - expect(stock.getForecastIncreaseChance()).toEqual(0.3); - }); - }); - - describe("#influenceForecast()", function() { - beforeEach(function() { - stock.otlkMag = 10; - }); - - it("should change the forecast's value towards 50", function() { - stock.influenceForecast(2); - expect(stock.otlkMag).toEqual(8); - }); - - it("should not care about whether the stock is in bull or bear mode", function() { - stock.b = true; - stock.influenceForecast(1); - expect(stock.otlkMag).toEqual(9); - - stock.b = false; - stock.influenceForecast(2); - expect(stock.otlkMag).toEqual(7); - }); - - it("should not influence the forecast beyond the limit", function() { - stock.influenceForecast(10); - expect(stock.otlkMag).toEqual(StockForecastInfluenceLimit); - - stock.influenceForecast(10); - expect(stock.otlkMag).toEqual(StockForecastInfluenceLimit); - }); - }); - - describe("#influenceForecastForecast()", function() { - it("should change the second-order forecast's value towards 50", function() { - stock.otlkMagForecast = 75; - stock.influenceForecastForecast(15); - expect(stock.otlkMagForecast).toEqual(60); - - stock.otlkMagForecast = 25; - stock.influenceForecastForecast(15); - expect(stock.otlkMagForecast).toEqual(40); - }); - - it("should not change the second-order forecast past 50", function() { - stock.otlkMagForecast = 40; - stock.influenceForecastForecast(20); - expect(stock.otlkMagForecast).toEqual(50); - - stock.otlkMagForecast = 60; - stock.influenceForecastForecast(20); - expect(stock.otlkMagForecast).toEqual(50); - }); - }); + it("should prevent values outside of 0-100", function () { + stock.changeForecastForecast(101); + expect(stock.otlkMagForecast).toEqual(100); + stock.changeForecastForecast(-1); + expect(stock.otlkMagForecast).toEqual(0); + }); }); - describe("StockMarket object", function() { - describe("Initialization", function() { - // Keeps track of initialized stocks. Contains their symbols - const stocks: string[]= []; - - beforeEach(function() { - expect(initStockMarket).not.toThrow(); - expect(initSymbolToStockMap).not.toThrow(); - }); - - it("should have Stock objects", function() { - for (const prop in StockMarket) { - const stock = StockMarket[prop]; - if (stock instanceof Stock) { - stocks.push(stock.symbol); - } - } - - // We'll just check that there are some stocks - expect(stocks.length).toBeGreaterThanOrEqual(1); - }); - - it("should have an order book in the 'Orders' property", function() { - expect(StockMarket).toHaveProperty("Orders"); - - const orderbook = StockMarket["Orders"]; - for (const symbol of stocks) { - const ordersForStock = orderbook[symbol]; - expect(ordersForStock).toEqual([]); - expect(ordersForStock.length).toEqual(0); - } - }); - - it("should have properties for managing game cycles", function() { - expect(StockMarket).toHaveProperty("storedCycles"); - expect(StockMarket["storedCycles"]).toEqual(0); - expect(StockMarket).toHaveProperty("lastUpdate"); - expect(StockMarket["lastUpdate"]).toEqual(0); - expect(StockMarket).toHaveProperty("ticksUntilCycle"); - expect(typeof StockMarket["ticksUntilCycle"]).toBe("number"); - }); - }); - - describe("Deletion", function() { - it("should set StockMarket to be an empty object", function() { - expect(StockMarket).not.toEqual({}); - deleteStockMarket(); - expect(StockMarket).toEqual({}); - }); - }); - - describe("processStockPrices()", function() { - beforeEach(function() { - deleteStockMarket(); - initStockMarket(); - initSymbolToStockMap(); - }); - - it("should store cycles until it actually processes", function() { - expect(StockMarket["storedCycles"]).toEqual(0); - processStockPrices(10); - expect(StockMarket["storedCycles"]).toEqual(10); - }); - - it("should trigger a price update when it has enough cycles", function() { - // Get the initial prices - const initialValues: IMap = {}; - for (const stockName in StockMarket) { - const stock = StockMarket[stockName]; - if (!(stock instanceof Stock)) { continue; } - initialValues[stock.symbol] = { - b: stock.b, - otlkMag: stock.otlkMag, - price: stock.price, - } - } - - // Don't know or care how many exact cycles are required - StockMarket.lastUpdate = new Date().getTime() - 5e3; - processStockPrices(1e9); - - // Both price and 'otlkMag' should be different - for (const stockName in StockMarket) { - const stock = StockMarket[stockName]; - if (!(stock instanceof Stock)) { continue; } - const initValue = initialValues[stock.symbol]; - expect(initValue.price).not.toEqual(stock.price); - if ((initValue.otlkMag === stock.otlkMag) && (initValue.b === stock.b)) { - throw new Error("expected either price or otlkMag to be different") - } - } - }); - }); + describe("#changePrice()", function () { + it("should set both the last price and current price properties", function () { + const newPrice = 20e3; + stock.changePrice(newPrice); + expect(stock.lastPrice).toEqual(ctorParams.initPrice); + expect(stock.price).toEqual(newPrice); + }); }); - describe("StockToSymbolMap", function() { - beforeEach(function() { - deleteStockMarket(); - initStockMarket(); - initSymbolToStockMap(); - }); + describe("#cycleForecast()", function () { + it("should appropriately change the otlkMag by the given amount when b=true", function () { + stock.getForecastIncreaseChance = () => { + return 1; + }; + stock.cycleForecast(5); + expect(stock.otlkMag).toEqual(ctorParams.otlkMag + 5); - it("should map stock symbols to their corresponding Stock Objects", function() { - for (const stockName in StockMarket) { - const stock = StockMarket[stockName]; - if (!(stock instanceof Stock)) { continue; } + stock.getForecastIncreaseChance = () => { + return 0; + }; + stock.cycleForecast(10); + expect(stock.otlkMag).toEqual(ctorParams.otlkMag - 5); + }); - expect(SymbolToStockMap[stock.symbol]).toEqual(stock); - } - }); + it("should NOT(!) the stock's 'b' property if it causes 'otlkMag' to go below 0", function () { + stock.getForecastIncreaseChance = () => { + return 0; + }; + stock.cycleForecast(25); + expect(stock.otlkMag).toEqual(5); + expect(stock.b).toEqual(false); + }); }); - describe("Transaction Cost Calculator Functions", function() { - describe("getBuyTransactionCost()", function() { - it("should fail on invalid 'stock' argument", function() { - const res = getBuyTransactionCost({} as Stock, 10, PositionTypes.Long); - expect(res).toEqual(null); - }); - - it("should fail on invalid 'shares' arg", function() { - let res = getBuyTransactionCost(stock, NaN, PositionTypes.Long); - expect(res).toEqual(null); - - res = getBuyTransactionCost(stock, -1, PositionTypes.Long); - expect(res).toEqual(null); - }); - - it("should properly evaluate LONG transactions", function() { - const shares = ctorParams.shareTxForMovement / 2; - const res = getBuyTransactionCost(stock, shares, PositionTypes.Long); - expect(res).toEqual(shares * stock.getAskPrice() + commission); - }); - - it("should properly evaluate SHORT transactions", function() { - const shares = ctorParams.shareTxForMovement / 2; - const res = getBuyTransactionCost(stock, shares, PositionTypes.Short); - expect(res).toEqual(shares * stock.getBidPrice() + commission); - }); - - it("should cap the 'shares' argument at the stock's maximum number of shares", function() { - const maxRes = getBuyTransactionCost(stock, stock.maxShares, PositionTypes.Long); - const exceedRes = getBuyTransactionCost(stock, stock.maxShares * 10, PositionTypes.Long); - expect(maxRes).toEqual(exceedRes); - }); - }); - - describe("getSellTransactionGain()", function() { - it("should fail on invalid 'stock' argument", function() { - const res = getSellTransactionGain({} as Stock, 10, PositionTypes.Long); - expect(res).toEqual(null); - }); - - it("should fail on invalid 'shares' arg", function() { - let res = getSellTransactionGain(stock, NaN, PositionTypes.Long); - expect(res).toEqual(null); - - res = getSellTransactionGain(stock, -1, PositionTypes.Long); - expect(res).toEqual(null); - }); - - it("should properly evaluate LONG transactionst", function() { - const shares = ctorParams.shareTxForMovement / 2; - const res = getSellTransactionGain(stock, shares, PositionTypes.Long); - const expected = shares * stock.getBidPrice() - commission; - expect(res).toEqual(expected); - }); - - it("should properly evaluate SHORT transactions", function() { - // We need to set this property in order to calculate gains from short position - stock.playerAvgShortPx = stock.price * 2; - - const shares = ctorParams.shareTxForMovement / 2; - const res = getSellTransactionGain(stock, shares, PositionTypes.Short); - const expected = (shares * stock.playerAvgShortPx) + (shares * (stock.playerAvgShortPx - stock.getAskPrice())) - commission; - expect(res).toEqual(expected); - }); - - it("should cap the 'shares' argument at the stock's maximum number of shares", function() { - const maxRes = getSellTransactionGain(stock, stock.maxShares, PositionTypes.Long); - const exceedRes = getSellTransactionGain(stock, stock.maxShares * 10, PositionTypes.Long); - expect(maxRes).toEqual(exceedRes); - }); - }); + describe("#cycleForecastForecast()", function () { + it("should increase the stock's second-order forecast by a given amount", function () { + const expected = [65, 75]; + stock.cycleForecastForecast(5); + expect(expected).toContain(stock.otlkMagForecast); + }); }); - describe("Forecast Movement Processor Function", function() { - // N = 1 is the original forecast - function getNthForecast(origForecast: number, n: number): number { - return origForecast - forecastChangePerPriceMovement * (n - 1); + describe("#flipForecastForecast()", function () { + it("should flip the 'otlkMagForecast' property around 50", function () { + stock.otlkMagForecast = 50; + stock.flipForecastForecast(); + expect(stock.otlkMagForecast).toEqual(50); + + stock.otlkMagForecast = 60; + stock.flipForecastForecast(); + expect(stock.otlkMagForecast).toEqual(40); + + stock.otlkMagForecast = 90; + stock.flipForecastForecast(); + expect(stock.otlkMagForecast).toEqual(10); + + stock.otlkMagForecast = 100; + stock.flipForecastForecast(); + expect(stock.otlkMagForecast).toEqual(0); + + stock.otlkMagForecast = 40; + stock.flipForecastForecast(); + expect(stock.otlkMagForecast).toEqual(60); + + stock.otlkMagForecast = 0; + stock.flipForecastForecast(); + expect(stock.otlkMagForecast).toEqual(100); + + stock.otlkMagForecast = 25; + stock.flipForecastForecast(); + expect(stock.otlkMagForecast).toEqual(75); + }); + }); + + describe("#getAbsoluteForecast()", function () { + it("should return the absolute forecast on a 1-100 scale", function () { + stock.b = true; + stock.otlkMag = 10; + expect(stock.getAbsoluteForecast()).toEqual(60); + + stock.b = false; + expect(stock.getAbsoluteForecast()).toEqual(40); + + stock.otlkMag = 30; + expect(stock.getAbsoluteForecast()).toEqual(20); + + stock.b = true; + expect(stock.getAbsoluteForecast()).toEqual(80); + + stock.otlkMag = 0; + expect(stock.getAbsoluteForecast()).toEqual(50); + + stock.b = false; + expect(stock.getAbsoluteForecast()).toEqual(50); + }); + }); + + describe("#getAskPrice()", function () { + it("should return the price increased by spread percentage", function () { + const perc = stock.spreadPerc / 100; + expect(perc).toBeLessThanOrEqual(1); + expect(perc).toBeGreaterThanOrEqual(0); + + const expected = stock.price * (1 + perc); + expect(stock.getAskPrice()).toEqual(expected); + }); + }); + + describe("#getBidPrice()", function () { + it("should return the price decreased by spread percentage", function () { + const perc = stock.spreadPerc / 100; + expect(perc).toBeLessThanOrEqual(1); + expect(perc).toBeGreaterThanOrEqual(0); + + const expected = stock.price * (1 - perc); + expect(stock.getBidPrice()).toEqual(expected); + }); + }); + + describe("#getForecastIncreaseChance()", function () { + it("should return the chance that the stock has of increasing in decimal form", function () { + stock.b = true; + + stock.otlkMagForecast = 90; + stock.otlkMag = 20; // Absolute forecast of 70 + expect(stock.getForecastIncreaseChance()).toEqual(0.7); + + stock.otlkMag = 25; // Absolute forecast of 75 + expect(stock.getForecastIncreaseChance()).toEqual(0.65); + + stock.otlkMagForecast = 100; + stock.otlkMag = 0; // Absolute forecast of 50 + expect(stock.getForecastIncreaseChance()).toEqual(0.95); + + stock.otlkMagForecast = 60; + stock.otlkMag = 25; // Absolute forecast of 75 + expect(stock.getForecastIncreaseChance()).toEqual(0.35); + + stock.otlkMagForecast = 10; + expect(stock.getForecastIncreaseChance()).toEqual(0.05); + + stock.b = false; + + stock.otlkMagForecast = 90; + stock.otlkMag = 20; // Absolute forecast of 30 + expect(stock.getForecastIncreaseChance()).toEqual(0.95); + + stock.otlkMagForecast = 50; + stock.otlkMag = 25; // Absolute forecast of 25 + expect(stock.getForecastIncreaseChance()).toEqual(0.75); + + stock.otlkMagForecast = 100; + stock.otlkMag = 0; // Absolute forecast of 50 + expect(stock.getForecastIncreaseChance()).toEqual(0.95); + + stock.otlkMagForecast = 5; + stock.otlkMag = 25; // Absolute forecast of 25 + expect(stock.getForecastIncreaseChance()).toEqual(0.3); + + stock.otlkMagForecast = 10; + expect(stock.getForecastIncreaseChance()).toEqual(0.35); + + stock.otlkMagForecast = 50; + stock.otlkMag = 0; + expect(stock.getForecastIncreaseChance()).toEqual(0.5); + + stock.otlkMagForecast = 25; + stock.otlkMag = 5; // Asolute forecast of 45 + expect(stock.getForecastIncreaseChance()).toEqual(0.3); + }); + }); + + describe("#influenceForecast()", function () { + beforeEach(function () { + stock.otlkMag = 10; + }); + + it("should change the forecast's value towards 50", function () { + stock.influenceForecast(2); + expect(stock.otlkMag).toEqual(8); + }); + + it("should not care about whether the stock is in bull or bear mode", function () { + stock.b = true; + stock.influenceForecast(1); + expect(stock.otlkMag).toEqual(9); + + stock.b = false; + stock.influenceForecast(2); + expect(stock.otlkMag).toEqual(7); + }); + + it("should not influence the forecast beyond the limit", function () { + stock.influenceForecast(10); + expect(stock.otlkMag).toEqual(StockForecastInfluenceLimit); + + stock.influenceForecast(10); + expect(stock.otlkMag).toEqual(StockForecastInfluenceLimit); + }); + }); + + describe("#influenceForecastForecast()", function () { + it("should change the second-order forecast's value towards 50", function () { + stock.otlkMagForecast = 75; + stock.influenceForecastForecast(15); + expect(stock.otlkMagForecast).toEqual(60); + + stock.otlkMagForecast = 25; + stock.influenceForecastForecast(15); + expect(stock.otlkMagForecast).toEqual(40); + }); + + it("should not change the second-order forecast past 50", function () { + stock.otlkMagForecast = 40; + stock.influenceForecastForecast(20); + expect(stock.otlkMagForecast).toEqual(50); + + stock.otlkMagForecast = 60; + stock.influenceForecastForecast(20); + expect(stock.otlkMagForecast).toEqual(50); + }); + }); + }); + + describe("StockMarket object", function () { + describe("Initialization", function () { + // Keeps track of initialized stocks. Contains their symbols + const stocks: string[] = []; + + beforeEach(function () { + expect(initStockMarket).not.toThrow(); + expect(initSymbolToStockMap).not.toThrow(); + }); + + it("should have Stock objects", function () { + for (const prop in StockMarket) { + const stock = StockMarket[prop]; + if (stock instanceof Stock) { + stocks.push(stock.symbol); + } } - function getNthForecastForecast(origForecastForecast: number, n: number): number { - if (stock.otlkMagForecast > 50) { - const expected = origForecastForecast - (forecastChangePerPriceMovement * (n - 1) * (stock.mv / 100)); - return expected < 50 ? 50 : expected; - } else if (stock.otlkMagForecast < 50) { - const expected = origForecastForecast + (forecastChangePerPriceMovement * (n - 1) * (stock.mv / 100)); - return expected > 50 ? 50 : expected; - } else { - return 50; - } + // We'll just check that there are some stocks + expect(stocks.length).toBeGreaterThanOrEqual(1); + }); + + it("should have an order book in the 'Orders' property", function () { + expect(StockMarket).toHaveProperty("Orders"); + + const orderbook = StockMarket["Orders"]; + for (const symbol of stocks) { + const ordersForStock = orderbook[symbol]; + expect(ordersForStock).toEqual([]); + expect(ordersForStock.length).toEqual(0); + } + }); + + it("should have properties for managing game cycles", function () { + expect(StockMarket).toHaveProperty("storedCycles"); + expect(StockMarket["storedCycles"]).toEqual(0); + expect(StockMarket).toHaveProperty("lastUpdate"); + expect(StockMarket["lastUpdate"]).toEqual(0); + expect(StockMarket).toHaveProperty("ticksUntilCycle"); + expect(typeof StockMarket["ticksUntilCycle"]).toBe("number"); + }); + }); + + describe("Deletion", function () { + it("should set StockMarket to be an empty object", function () { + expect(StockMarket).not.toEqual({}); + deleteStockMarket(); + expect(StockMarket).toEqual({}); + }); + }); + + describe("processStockPrices()", function () { + beforeEach(function () { + deleteStockMarket(); + initStockMarket(); + initSymbolToStockMap(); + }); + + it("should store cycles until it actually processes", function () { + expect(StockMarket["storedCycles"]).toEqual(0); + processStockPrices(10); + expect(StockMarket["storedCycles"]).toEqual(10); + }); + + it("should trigger a price update when it has enough cycles", function () { + // Get the initial prices + const initialValues: IMap = {}; + for (const stockName in StockMarket) { + const stock = StockMarket[stockName]; + if (!(stock instanceof Stock)) { + continue; + } + initialValues[stock.symbol] = { + b: stock.b, + otlkMag: stock.otlkMag, + price: stock.price, + }; } - describe("processTransactionForecastMovement() for buy transactions", function() { - const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2); - const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares; + // Don't know or care how many exact cycles are required + StockMarket.lastUpdate = new Date().getTime() - 5e3; + processStockPrices(1e9); - it("should do nothing on invalid 'stock' argument", function() { - const oldTracker = stock.shareTxUntilMovement; + // Both price and 'otlkMag' should be different + for (const stockName in StockMarket) { + const stock = StockMarket[stockName]; + if (!(stock instanceof Stock)) { + continue; + } + const initValue = initialValues[stock.symbol]; + expect(initValue.price).not.toEqual(stock.price); + if (initValue.otlkMag === stock.otlkMag && initValue.b === stock.b) { + throw new Error("expected either price or otlkMag to be different"); + } + } + }); + }); + }); - processTransactionForecastMovement({} as Stock, mvmtShares); - expect(stock.shareTxUntilMovement).toEqual(oldTracker); - }); - - it("should do nothing on invalid 'shares' arg", function() { - const oldTracker = stock.shareTxUntilMovement; - - processTransactionForecastMovement(stock, NaN); - expect(stock.shareTxUntilMovement).toEqual(oldTracker); - - processTransactionForecastMovement(stock, -1); - expect(stock.shareTxUntilMovement).toEqual(oldTracker); - }); - - it("should properly evaluate a LONG transaction that doesn't trigger a forecast movement", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, noMvmtShares); - expect(stock.otlkMag).toEqual(oldForecast); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement - noMvmtShares); - }); - - it("should properly evaluate a SHORT transaction that doesn't trigger a forecast movement", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, noMvmtShares); - expect(stock.otlkMag).toEqual(oldForecast); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement - noMvmtShares); - }); - - it("should properly evaluate LONG transactions that triggers forecast movements", function() { - const oldForecast = stock.otlkMag; - const oldForecastForecast = stock.otlkMagForecast; - - processTransactionForecastMovement(stock, mvmtShares); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); - expect(stock.otlkMagForecast).toEqual(getNthForecastForecast(oldForecastForecast, 4)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement - noMvmtShares); - }); - - it("should properly evaluate SHORT transactions that triggers forecast movements", function() { - const oldForecast = stock.otlkMag; - const oldForecastForecast = stock.otlkMagForecast; - - processTransactionForecastMovement(stock, mvmtShares); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); - expect(stock.otlkMagForecast).toEqual(getNthForecastForecast(oldForecastForecast, 4)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement - noMvmtShares); - }); - - it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - const oldForecastForecast = stock.otlkMagForecast; - - processTransactionForecastMovement(stock, stock.shareTxForMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); - expect(stock.otlkMagForecast).toEqual(getNthForecastForecast(oldForecastForecast, 2)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - - it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - const oldForecastForecast = stock.otlkMagForecast; - - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); - expect(stock.shareTxUntilMovement).toBeLessThan(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); - expect(stock.otlkMagForecast).toEqual(getNthForecastForecast(oldForecastForecast, 2)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - - it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - const oldForecastForecast = stock.otlkMagForecast; - - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); - expect(stock.otlkMagForecast).toEqual(getNthForecastForecast(oldForecastForecast, 4)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - - it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - const oldForecastForecast = stock.otlkMagForecast; - - processTransactionForecastMovement(stock, stock.shareTxForMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); - expect(stock.otlkMagForecast).toEqual(getNthForecastForecast(oldForecastForecast, 2)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - - it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - const oldForecastForecast = stock.otlkMagForecast; - - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); - expect(stock.shareTxUntilMovement).toBeLessThan(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); - expect(stock.otlkMagForecast).toEqual(getNthForecastForecast(oldForecastForecast, 2)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - - it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - const oldForecastForecast = stock.otlkMagForecast; - - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); - expect(stock.otlkMagForecast).toEqual(getNthForecastForecast(oldForecastForecast, 4)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - }); - - describe("processTransactionForecastMovement() for sell transactions", function() { - const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2); - const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares; - - it("should do nothing on invalid 'stock' argument", function() { - const oldTracker = stock.shareTxUntilMovement; - - processTransactionForecastMovement({} as Stock, mvmtShares); - expect(stock.shareTxUntilMovement).toEqual(oldTracker); - }); - - it("should do nothing on invalid 'shares' arg", function() { - const oldTracker = stock.shareTxUntilMovement; - - processTransactionForecastMovement(stock, NaN); - expect(stock.shareTxUntilMovement).toEqual(oldTracker); - - processTransactionForecastMovement(stock, -1); - expect(stock.shareTxUntilMovement).toEqual(oldTracker); - }); - - it("should properly evaluate a LONG transaction that doesn't trigger a price movement", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, noMvmtShares); - expect(stock.otlkMag).toEqual(oldForecast); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement - noMvmtShares); - }); - - it("should properly evaluate a SHORT transaction that doesn't trigger a price movement", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, noMvmtShares); - expect(stock.otlkMag).toEqual(oldForecast); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement - noMvmtShares); - }); - - it("should properly evaluate LONG transactions that trigger price movements", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, mvmtShares); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement - noMvmtShares); - }); - - it("should properly evaluate SHORT transactions that trigger price movements", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, mvmtShares); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement - noMvmtShares); - }); - - it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, stock.shareTxForMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - - it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); - expect(stock.shareTxUntilMovement).toBeLessThan(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - - it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - - it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, stock.shareTxForMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - - it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, Math.round(stock.shareTxForMovement / 2)); - expect(stock.shareTxUntilMovement).toBeLessThan(stock.shareTxForMovement); - processTransactionForecastMovement(stock, stock.shareTxUntilMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - - it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function() { - const oldForecast = stock.otlkMag; - - processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); - expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); - expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); - }); - }); + describe("StockToSymbolMap", function () { + beforeEach(function () { + deleteStockMarket(); + initStockMarket(); + initSymbolToStockMap(); }); - describe("Transaction (Buy/Sell) Functions", function() { - const suppressDialogOpt = { suppressDialog: true }; + it("should map stock symbols to their corresponding Stock Objects", function () { + for (const stockName in StockMarket) { + const stock = StockMarket[stockName]; + if (!(stock instanceof Stock)) { + continue; + } - describe("buyStock()", function() { - it("should fail for invalid arguments", function() { - expect(buyStock({} as Stock, 1, null, suppressDialogOpt)).toEqual(false); - expect(buyStock(stock, 0, null, suppressDialogOpt)).toEqual(false); - expect(buyStock(stock, -1, null, suppressDialogOpt)).toEqual(false); - expect(buyStock(stock, NaN, null, suppressDialogOpt)).toEqual(false); - }); + expect(SymbolToStockMap[stock.symbol]).toEqual(stock); + } + }); + }); - it("should fail if player doesn't have enough money", function() { - Player.setMoney(0); - expect(buyStock(stock, 1, null, suppressDialogOpt)).toEqual(false); - }); + describe("Transaction Cost Calculator Functions", function () { + describe("getBuyTransactionCost()", function () { + it("should fail on invalid 'stock' argument", function () { + const res = getBuyTransactionCost({} as Stock, 10, PositionTypes.Long); + expect(res).toEqual(null); + }); - it("should not allow for transactions that exceed the maximum shares", function() { - const maxShares = stock.maxShares; - expect(buyStock(stock, maxShares + 1, null, suppressDialogOpt)).toEqual(false); - }); + it("should fail on invalid 'shares' arg", function () { + let res = getBuyTransactionCost(stock, NaN, PositionTypes.Long); + expect(res).toEqual(null); - it("should return true and properly update stock properties for successful transactions", function() { - const shares = 1e3; - const cost = getBuyTransactionCost(stock, shares, PositionTypes.Long); - expect(cost).not.toBeNull() + res = getBuyTransactionCost(stock, -1, PositionTypes.Long); + expect(res).toEqual(null); + }); - // Checked above - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - Player.setMoney(cost!); + it("should properly evaluate LONG transactions", function () { + const shares = ctorParams.shareTxForMovement / 2; + const res = getBuyTransactionCost(stock, shares, PositionTypes.Long); + expect(res).toEqual(shares * stock.getAskPrice() + commission); + }); - expect(buyStock(stock, shares, null, suppressDialogOpt)).toEqual(true); - expect(stock.playerShares).toEqual(shares); - expect(stock.playerAvgPx).toBeGreaterThan(0); - expect(Player.money.toNumber()).toEqual(0); - }); - }); + it("should properly evaluate SHORT transactions", function () { + const shares = ctorParams.shareTxForMovement / 2; + const res = getBuyTransactionCost(stock, shares, PositionTypes.Short); + expect(res).toEqual(shares * stock.getBidPrice() + commission); + }); - describe("sellStock()", function() { - it("should fail for invalid arguments", function() { - expect(sellStock({} as Stock, 1, null, suppressDialogOpt)).toEqual(false); - expect(sellStock(stock, 0, null, suppressDialogOpt)).toEqual(false); - expect(sellStock(stock, -1, null, suppressDialogOpt)).toEqual(false); - expect(sellStock(stock, NaN, null, suppressDialogOpt)).toEqual(false); - }); - - it("should fail if player doesn't have any shares", function() { - Player.setMoney(0); - expect(sellStock(stock, 1, null, suppressDialogOpt)).toEqual(false); - }); - - it("should not allow for transactions that exceed the maximum shares", function() { - const maxShares = stock.maxShares; - expect(sellStock(stock, maxShares + 1, null, suppressDialogOpt)).toEqual(false); - }); - - it("should return true and properly update stock properties for successful transactions", function() { - const shares = 1e3; - stock.playerShares = shares; - stock.playerAvgPx = stock.price; - const gain = getSellTransactionGain(stock, shares, PositionTypes.Long); - Player.setMoney(0); - - expect(sellStock(stock, shares, null, suppressDialogOpt)).toEqual(true); - expect(stock.playerShares).toEqual(0); - expect(stock.playerAvgPx).toEqual(0); - expect(Player.money.toNumber()).toEqual(gain); - }); - - it("should cap the number of sharse sold to however many the player owns", function() { - const attemptedShares = 2e3; - const actualShares = 1e3; - stock.playerShares = actualShares; - stock.playerAvgPx = stock.price; - const gain = getSellTransactionGain(stock, actualShares, PositionTypes.Long); - Player.setMoney(0); - - expect(sellStock(stock, attemptedShares, null, suppressDialogOpt)).toEqual(true); - expect(stock.playerShares).toEqual(0); - expect(stock.playerAvgPx).toEqual(0); - expect(Player.money.toNumber()).toEqual(gain); - }); - - it("should properly update stock properties for partial transactions", function() { - const shares = 1e3; - const origPrice = stock.price; - stock.playerShares = 2 * shares; - stock.playerAvgPx = origPrice; - const gain = getSellTransactionGain(stock, shares, PositionTypes.Long); - Player.setMoney(0); - - expect(sellStock(stock, shares, null, suppressDialogOpt)).toEqual(true); - expect(stock.playerShares).toEqual(shares); - expect(stock.playerAvgPx).toEqual(origPrice); - expect(Player.money.toNumber()).toEqual(gain); - }); - }); - - describe("shortStock()", function() { - it("should fail for invalid arguments", function() { - expect(shortStock({} as Stock, 1, null, suppressDialogOpt)).toEqual(false); - expect(shortStock(stock, 0, null, suppressDialogOpt)).toEqual(false); - expect(shortStock(stock, -1, null, suppressDialogOpt)).toEqual(false); - expect(shortStock(stock, NaN, null, suppressDialogOpt)).toEqual(false); - }); - - it("should fail if player doesn't have enough money", function() { - Player.setMoney(0); - expect(shortStock(stock, 1, null, suppressDialogOpt)).toEqual(false); - }); - - it("should not allow for transactions that exceed the maximum shares", function() { - const maxShares = stock.maxShares; - expect(shortStock(stock, maxShares + 1, null, suppressDialogOpt)).toEqual(false); - }); - - it("should return true and properly update stock properties for successful transactions", function() { - const shares = 1e3; - const cost = getBuyTransactionCost(stock, shares, PositionTypes.Short); - expect(cost).not.toBeNull() - - // Checked above - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - Player.setMoney(cost!); - - expect(shortStock(stock, shares, null, suppressDialogOpt)).toEqual(true); - expect(stock.playerShortShares).toEqual(shares); - expect(stock.playerAvgShortPx).toBeGreaterThan(0); - expect(Player.money.toNumber()).toEqual(0); - }); - }); - - describe("sellShort()", function() { - it("should fail for invalid arguments", function() { - expect(sellShort({} as Stock, 1, null, suppressDialogOpt)).toEqual(false); - expect(sellShort(stock, 0, null, suppressDialogOpt)).toEqual(false); - expect(sellShort(stock, -1, null, suppressDialogOpt)).toEqual(false); - expect(sellShort(stock, NaN, null, suppressDialogOpt)).toEqual(false); - }); - - it("should fail if player doesn't have any shares", function() { - Player.setMoney(0); - expect(sellShort(stock, 1, null, suppressDialogOpt)).toEqual(false); - }); - - it("should not allow for transactions that exceed the maximum shares", function() { - const maxShares = stock.maxShares; - expect(sellShort(stock, maxShares + 1, null, suppressDialogOpt)).toEqual(false); - }); - - it("should return true and properly update stock properties for successful transactions", function() { - const shares = 1e3; - stock.playerShortShares = shares; - stock.playerAvgShortPx = stock.price; - const gain = getSellTransactionGain(stock, shares, PositionTypes.Short); - Player.setMoney(0); - - expect(sellShort(stock, shares, null, suppressDialogOpt)).toEqual(true); - expect(stock.playerShortShares).toEqual(0); - expect(stock.playerAvgShortPx).toEqual(0); - expect(Player.money.toNumber()).toEqual(gain); - }); - - it("should cap the number of sharse sold to however many the player owns", function() { - const attemptedShares = 2e3; - const actualShares = 1e3; - stock.playerShortShares = actualShares; - stock.playerAvgShortPx = stock.price; - const gain = getSellTransactionGain(stock, actualShares, PositionTypes.Short); - Player.setMoney(0); - - expect(sellShort(stock, attemptedShares, null, suppressDialogOpt)).toEqual(true); - expect(stock.playerShortShares).toEqual(0); - expect(stock.playerAvgShortPx).toEqual(0); - expect(Player.money.toNumber()).toEqual(gain); - }); - - it("should properly update stock properties for partial transactions", function() { - const shares = 1e3; - const origPrice = stock.price; - stock.playerShortShares = 2 * shares; - stock.playerAvgShortPx = origPrice; - const gain = getSellTransactionGain(stock, shares, PositionTypes.Short); - Player.setMoney(0); - - expect(sellShort(stock, shares, null, suppressDialogOpt)).toEqual(true); - expect(stock.playerShortShares).toEqual(shares); - expect(stock.playerAvgShortPx).toEqual(origPrice); - expect(Player.money.toNumber()).toEqual(gain); - }); - }); + it("should cap the 'shares' argument at the stock's maximum number of shares", function () { + const maxRes = getBuyTransactionCost( + stock, + stock.maxShares, + PositionTypes.Long, + ); + const exceedRes = getBuyTransactionCost( + stock, + stock.maxShares * 10, + PositionTypes.Long, + ); + expect(maxRes).toEqual(exceedRes); + }); }); - describe("Order Class", function() { - it("should throw on invalid arguments", function() { - function invalid1(): Order { - return new Order({} as string, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long); - } - function invalid2(): Order { - return new Order("FOO", "z" as any as number, 0, OrderTypes.LimitBuy, PositionTypes.Short); - } - function invalid3(): Order { - return new Order("FOO", 1, {} as number, OrderTypes.LimitBuy, PositionTypes.Short); - } - function invalid4(): Order { - return new Order("FOO", 1, NaN, OrderTypes.LimitBuy, PositionTypes.Short); - } - function invalid5(): Order { - return new Order("FOO", NaN, 0, OrderTypes.LimitBuy, PositionTypes.Short); - } + describe("getSellTransactionGain()", function () { + it("should fail on invalid 'stock' argument", function () { + const res = getSellTransactionGain({} as Stock, 10, PositionTypes.Long); + expect(res).toEqual(null); + }); - expect(invalid1).toThrow(); - expect(invalid2).toThrow(); - expect(invalid3).toThrow(); - expect(invalid4).toThrow(); - expect(invalid5).toThrow(); - }); + it("should fail on invalid 'shares' arg", function () { + let res = getSellTransactionGain(stock, NaN, PositionTypes.Long); + expect(res).toEqual(null); + + res = getSellTransactionGain(stock, -1, PositionTypes.Long); + expect(res).toEqual(null); + }); + + it("should properly evaluate LONG transactionst", function () { + const shares = ctorParams.shareTxForMovement / 2; + const res = getSellTransactionGain(stock, shares, PositionTypes.Long); + const expected = shares * stock.getBidPrice() - commission; + expect(res).toEqual(expected); + }); + + it("should properly evaluate SHORT transactions", function () { + // We need to set this property in order to calculate gains from short position + stock.playerAvgShortPx = stock.price * 2; + + const shares = ctorParams.shareTxForMovement / 2; + const res = getSellTransactionGain(stock, shares, PositionTypes.Short); + const expected = + shares * stock.playerAvgShortPx + + shares * (stock.playerAvgShortPx - stock.getAskPrice()) - + commission; + expect(res).toEqual(expected); + }); + + it("should cap the 'shares' argument at the stock's maximum number of shares", function () { + const maxRes = getSellTransactionGain( + stock, + stock.maxShares, + PositionTypes.Long, + ); + const exceedRes = getSellTransactionGain( + stock, + stock.maxShares * 10, + PositionTypes.Long, + ); + expect(maxRes).toEqual(exceedRes); + }); + }); + }); + + describe("Forecast Movement Processor Function", function () { + // N = 1 is the original forecast + function getNthForecast(origForecast: number, n: number): number { + return origForecast - forecastChangePerPriceMovement * (n - 1); + } + + function getNthForecastForecast( + origForecastForecast: number, + n: number, + ): number { + if (stock.otlkMagForecast > 50) { + const expected = + origForecastForecast - + forecastChangePerPriceMovement * (n - 1) * (stock.mv / 100); + return expected < 50 ? 50 : expected; + } else if (stock.otlkMagForecast < 50) { + const expected = + origForecastForecast + + forecastChangePerPriceMovement * (n - 1) * (stock.mv / 100); + return expected > 50 ? 50 : expected; + } else { + return 50; + } + } + + describe("processTransactionForecastMovement() for buy transactions", function () { + const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2); + const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares; + + it("should do nothing on invalid 'stock' argument", function () { + const oldTracker = stock.shareTxUntilMovement; + + processTransactionForecastMovement({} as Stock, mvmtShares); + expect(stock.shareTxUntilMovement).toEqual(oldTracker); + }); + + it("should do nothing on invalid 'shares' arg", function () { + const oldTracker = stock.shareTxUntilMovement; + + processTransactionForecastMovement(stock, NaN); + expect(stock.shareTxUntilMovement).toEqual(oldTracker); + + processTransactionForecastMovement(stock, -1); + expect(stock.shareTxUntilMovement).toEqual(oldTracker); + }); + + it("should properly evaluate a LONG transaction that doesn't trigger a forecast movement", function () { + const oldForecast = stock.otlkMag; + + processTransactionForecastMovement(stock, noMvmtShares); + expect(stock.otlkMag).toEqual(oldForecast); + expect(stock.shareTxUntilMovement).toEqual( + stock.shareTxForMovement - noMvmtShares, + ); + }); + + it("should properly evaluate a SHORT transaction that doesn't trigger a forecast movement", function () { + const oldForecast = stock.otlkMag; + + processTransactionForecastMovement(stock, noMvmtShares); + expect(stock.otlkMag).toEqual(oldForecast); + expect(stock.shareTxUntilMovement).toEqual( + stock.shareTxForMovement - noMvmtShares, + ); + }); + + it("should properly evaluate LONG transactions that triggers forecast movements", function () { + const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; + + processTransactionForecastMovement(stock, mvmtShares); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); + expect(stock.otlkMagForecast).toEqual( + getNthForecastForecast(oldForecastForecast, 4), + ); + expect(stock.shareTxUntilMovement).toEqual( + stock.shareTxForMovement - noMvmtShares, + ); + }); + + it("should properly evaluate SHORT transactions that triggers forecast movements", function () { + const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; + + processTransactionForecastMovement(stock, mvmtShares); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); + expect(stock.otlkMagForecast).toEqual( + getNthForecastForecast(oldForecastForecast, 4), + ); + expect(stock.shareTxUntilMovement).toEqual( + stock.shareTxForMovement - noMvmtShares, + ); + }); + + it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; + + processTransactionForecastMovement(stock, stock.shareTxForMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); + expect(stock.otlkMagForecast).toEqual( + getNthForecastForecast(oldForecastForecast, 2), + ); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); + + it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; + + processTransactionForecastMovement( + stock, + Math.round(stock.shareTxForMovement / 2), + ); + expect(stock.shareTxUntilMovement).toBeLessThan( + stock.shareTxForMovement, + ); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); + expect(stock.otlkMagForecast).toEqual( + getNthForecastForecast(oldForecastForecast, 2), + ); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); + + it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; + + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); + expect(stock.otlkMagForecast).toEqual( + getNthForecastForecast(oldForecastForecast, 4), + ); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); + + it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; + + processTransactionForecastMovement(stock, stock.shareTxForMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); + expect(stock.otlkMagForecast).toEqual( + getNthForecastForecast(oldForecastForecast, 2), + ); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); + + it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; + + processTransactionForecastMovement( + stock, + Math.round(stock.shareTxForMovement / 2), + ); + expect(stock.shareTxUntilMovement).toBeLessThan( + stock.shareTxForMovement, + ); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); + expect(stock.otlkMagForecast).toEqual( + getNthForecastForecast(oldForecastForecast, 2), + ); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); + + it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; + const oldForecastForecast = stock.otlkMagForecast; + + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); + expect(stock.otlkMagForecast).toEqual( + getNthForecastForecast(oldForecastForecast, 4), + ); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); }); - describe("Order Placing & Processing", function() { - beforeEach(function() { - expect(initStockMarket).not.toThrow(); - expect(initSymbolToStockMap).not.toThrow(); + describe("processTransactionForecastMovement() for sell transactions", function () { + const noMvmtShares = Math.round(ctorParams.shareTxForMovement / 2.2); + const mvmtShares = ctorParams.shareTxForMovement * 3 + noMvmtShares; - // Create an order book for our mock stock - StockMarket["Orders"][stock.symbol] = []; - }); + it("should do nothing on invalid 'stock' argument", function () { + const oldTracker = stock.shareTxUntilMovement; - describe("placeOrder()", function() { - it("should return false when it's called with invalid arguments", function() { - const invalid1 = placeOrder({} as Stock, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long); - const invalid2 = placeOrder(stock, "foo" as any as number, 2, OrderTypes.LimitBuy, PositionTypes.Long); - const invalid3 = placeOrder(stock, 1, "foo" as any as number, OrderTypes.LimitBuy, PositionTypes.Long); + processTransactionForecastMovement({} as Stock, mvmtShares); + expect(stock.shareTxUntilMovement).toEqual(oldTracker); + }); - expect(invalid1).toEqual(false); - expect(invalid2).toEqual(false); - expect(invalid3).toEqual(false); + it("should do nothing on invalid 'shares' arg", function () { + const oldTracker = stock.shareTxUntilMovement; - expect(StockMarket["Orders"][stock.symbol]).toEqual([]); - }); + processTransactionForecastMovement(stock, NaN); + expect(stock.shareTxUntilMovement).toEqual(oldTracker); - it("should return true and update the order book for valid arguments", function() { - const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); - expect(res).toEqual(true); + processTransactionForecastMovement(stock, -1); + expect(stock.shareTxUntilMovement).toEqual(oldTracker); + }); - expect(StockMarket["Orders"][stock.symbol]).toHaveLength(1); - const order = StockMarket["Orders"][stock.symbol][0]; - expect(order).toBeInstanceOf(Order); - expect(order.stockSymbol).toEqual(ctorParams.symbol); - expect(order.shares).toEqual(1e3); - expect(order.price).toEqual(9e3); - expect(order.type).toEqual(OrderTypes.LimitBuy); - expect(order.pos).toEqual(PositionTypes.Long); - }); - }); + it("should properly evaluate a LONG transaction that doesn't trigger a price movement", function () { + const oldForecast = stock.otlkMag; - describe("cancelOrder()", function() { - beforeEach(function() { - StockMarket["Orders"][stock.symbol] = []; + processTransactionForecastMovement(stock, noMvmtShares); + expect(stock.otlkMag).toEqual(oldForecast); + expect(stock.shareTxUntilMovement).toEqual( + stock.shareTxForMovement - noMvmtShares, + ); + }); - const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); - expect(res).toEqual(true); - expect(StockMarket["Orders"][stock.symbol]).toHaveLength(1); - }); + it("should properly evaluate a SHORT transaction that doesn't trigger a price movement", function () { + const oldForecast = stock.otlkMag; - it("returns true & removes an Order from the order book", function() { - const order = StockMarket["Orders"][stock.symbol][0]; - const res = cancelOrder({ order }); - expect(res).toEqual(true); - expect(StockMarket["Orders"][stock.symbol]).toHaveLength(0); - }); + processTransactionForecastMovement(stock, noMvmtShares); + expect(stock.otlkMag).toEqual(oldForecast); + expect(stock.shareTxUntilMovement).toEqual( + stock.shareTxForMovement - noMvmtShares, + ); + }); - it("should also work when passing in order parameters separately", function() { - const res = cancelOrder({ - stock, - shares: 1e3, - price: 9e3, - type: OrderTypes.LimitBuy, - pos: PositionTypes.Long, - }); - expect(res).toEqual(true); - expect(StockMarket["Orders"][stock.symbol]).toHaveLength(0); - }); + it("should properly evaluate LONG transactions that trigger price movements", function () { + const oldForecast = stock.otlkMag; - it("should return false and do nothing when the specified order doesn't exist", function() { - // Same parameters, but its a different object - const order = new Order(stock.symbol, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); - const res = cancelOrder({ order }); - expect(res).toEqual(false); - expect(StockMarket["Orders"][stock.symbol]).toHaveLength(1); + processTransactionForecastMovement(stock, mvmtShares); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); + expect(stock.shareTxUntilMovement).toEqual( + stock.shareTxForMovement - noMvmtShares, + ); + }); - const res2 = cancelOrder({ - stock, - shares: 999, - price: 9e3, - type: OrderTypes.LimitBuy, - pos: PositionTypes.Long, - }); - expect(res2).toEqual(false); - expect(StockMarket["Orders"][stock.symbol]).toHaveLength(1); - }); - }); + it("should properly evaluate SHORT transactions that trigger price movements", function () { + const oldForecast = stock.otlkMag; - describe("processOrders()", function() { - let processOrdersRefs: IProcessOrderRefs; + processTransactionForecastMovement(stock, mvmtShares); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); + expect(stock.shareTxUntilMovement).toEqual( + stock.shareTxForMovement - noMvmtShares, + ); + }); - beforeEach(function() { - expect(initStockMarket).not.toThrow(); - expect(initSymbolToStockMap).not.toThrow(); + it("should properly evaluate LONG transactions of exactly 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; - StockMarket[stock.name] = stock; - SymbolToStockMap[stock.symbol] = stock; - StockMarket["Orders"][stock.symbol] = []; + processTransactionForecastMovement(stock, stock.shareTxForMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); - stock.playerShares = 1e3; - stock.playerShortShares = 1e3; - Player.setMoney(100e9); + it("should properly evaluate LONG transactions that total to 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; - processOrdersRefs = { - rerenderFn: () => undefined, - stockMarket: StockMarket as IStockMarket, - symbolToStockMap: SymbolToStockMap, - }; - }); + processTransactionForecastMovement( + stock, + Math.round(stock.shareTxForMovement / 2), + ); + expect(stock.shareTxUntilMovement).toBeLessThan( + stock.shareTxForMovement, + ); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); - function checkThatOrderExists(placeOrderRes?: boolean): void { - if (typeof placeOrderRes === "boolean") { - expect(placeOrderRes).toEqual(true); - } - expect(StockMarket["Orders"][stock.symbol]).toHaveLength(1); - } + it("should properly evaluate LONG transactions that are a multiple of 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; - function checkThatOrderExecuted(): void { - expect(StockMarket["Orders"][stock.symbol]).toHaveLength(0); - } + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); - it("should execute LONG Limit Buy orders when price <= order price", function() { - const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitBuy, PositionTypes.Long); - checkThatOrderExists(res); + it("should properly evaluate SHORT transactions of exactly 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; - stock.changePrice(9e3); - processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long, processOrdersRefs); - checkThatOrderExecuted(); - expect(stock.playerShares).toEqual(2e3); - }); + processTransactionForecastMovement(stock, stock.shareTxForMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); - it("should execute SHORT Limit Buy Orders when price >= order price", function() { - const res = placeOrder(stock, 1e3, 11e3, OrderTypes.LimitBuy, PositionTypes.Short); - checkThatOrderExists(res); + it("should properly evaluate SHORT transactions that total to 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; - stock.changePrice(11e3); - processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short, processOrdersRefs); - checkThatOrderExecuted(); - expect(stock.playerShortShares).toEqual(2e3); - }); + processTransactionForecastMovement( + stock, + Math.round(stock.shareTxForMovement / 2), + ); + expect(stock.shareTxUntilMovement).toBeLessThan( + stock.shareTxForMovement, + ); + processTransactionForecastMovement(stock, stock.shareTxUntilMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 2)); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); - it("should execute LONG Limit Sell Orders when price >= order price", function() { - const res = placeOrder(stock, 1e3, 11e3, OrderTypes.LimitSell, PositionTypes.Long); - checkThatOrderExists(res); + it("should properly evaluate SHORT transactions that are a multiple of 'shareTxForMovement' shares", function () { + const oldForecast = stock.otlkMag; - stock.changePrice(11e3); - processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long, processOrdersRefs); - checkThatOrderExecuted(); - expect(stock.playerShares).toEqual(0); - }); + processTransactionForecastMovement(stock, 3 * stock.shareTxForMovement); + expect(stock.otlkMag).toEqual(getNthForecast(oldForecast, 4)); + expect(stock.shareTxUntilMovement).toEqual(stock.shareTxForMovement); + }); + }); + }); - it("should execute SHORT Limit Sell Orders when price <= order price", function() { - const res = placeOrder(stock, 1e3, 9e3, OrderTypes.LimitSell, PositionTypes.Short); - checkThatOrderExists(res); + describe("Transaction (Buy/Sell) Functions", function () { + const suppressDialogOpt = { suppressDialog: true }; - stock.changePrice(9e3); - processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short, processOrdersRefs); - checkThatOrderExecuted(); - expect(stock.playerShortShares).toEqual(0); - }); + describe("buyStock()", function () { + it("should fail for invalid arguments", function () { + expect(buyStock({} as Stock, 1, null, suppressDialogOpt)).toEqual( + false, + ); + expect(buyStock(stock, 0, null, suppressDialogOpt)).toEqual(false); + expect(buyStock(stock, -1, null, suppressDialogOpt)).toEqual(false); + expect(buyStock(stock, NaN, null, suppressDialogOpt)).toEqual(false); + }); - it("should execute LONG Stop Buy Orders when price >= order price", function() { - const res = placeOrder(stock, 1e3, 11e3, OrderTypes.StopBuy, PositionTypes.Long); - checkThatOrderExists(res); + it("should fail if player doesn't have enough money", function () { + Player.setMoney(0); + expect(buyStock(stock, 1, null, suppressDialogOpt)).toEqual(false); + }); - stock.changePrice(11e3); - processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long, processOrdersRefs); - checkThatOrderExecuted(); - expect(stock.playerShares).toEqual(2e3); - }); + it("should not allow for transactions that exceed the maximum shares", function () { + const maxShares = stock.maxShares; + expect(buyStock(stock, maxShares + 1, null, suppressDialogOpt)).toEqual( + false, + ); + }); - it("should execute SHORT Stop Buy Orders when price <= order price", function() { - const res = placeOrder(stock, 1e3, 9e3, OrderTypes.StopBuy, PositionTypes.Short); - checkThatOrderExists(res); + it("should return true and properly update stock properties for successful transactions", function () { + const shares = 1e3; + const cost = getBuyTransactionCost(stock, shares, PositionTypes.Long); + expect(cost).not.toBeNull(); - stock.changePrice(9e3); - processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short, processOrdersRefs); - checkThatOrderExecuted(); - expect(stock.playerShortShares).toEqual(2e3); - }); + // Checked above + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + Player.setMoney(cost!); - it("should execute LONG Stop Sell Orders when price <= order price", function() { - const res = placeOrder(stock, 1e3, 9e3, OrderTypes.StopSell, PositionTypes.Long); - checkThatOrderExists(res); - - stock.changePrice(9e3); - processOrders(stock, OrderTypes.StopSell, PositionTypes.Long, processOrdersRefs); - checkThatOrderExecuted(); - expect(stock.playerShares).toEqual(0); - }); - - it("should execute SHORT Stop Sell Orders when price >= order price", function() { - const res = placeOrder(stock, 1e3, 11e3, OrderTypes.StopSell, PositionTypes.Short); - checkThatOrderExists(res); - - stock.changePrice(11e3); - processOrders(stock, OrderTypes.StopSell, PositionTypes.Short, processOrdersRefs); - checkThatOrderExecuted(); - expect(stock.playerShortShares).toEqual(0); - }); - - it("should execute immediately if their conditions are satisfied", function() { - placeOrder(stock, 1e3, 11e3, OrderTypes.LimitBuy, PositionTypes.Long); - checkThatOrderExecuted(); - expect(stock.playerShares).toEqual(2e3); - }); - }); + expect(buyStock(stock, shares, null, suppressDialogOpt)).toEqual(true); + expect(stock.playerShares).toEqual(shares); + expect(stock.playerAvgPx).toBeGreaterThan(0); + expect(Player.money.toNumber()).toEqual(0); + }); }); - // TODO - describe("Player Influencing", function() { - const server = new Server({ - hostname: "mockserver", - moneyAvailable: 1e6, - organizationName: "MockStock", - }); + describe("sellStock()", function () { + it("should fail for invalid arguments", function () { + expect(sellStock({} as Stock, 1, null, suppressDialogOpt)).toEqual( + false, + ); + expect(sellStock(stock, 0, null, suppressDialogOpt)).toEqual(false); + expect(sellStock(stock, -1, null, suppressDialogOpt)).toEqual(false); + expect(sellStock(stock, NaN, null, suppressDialogOpt)).toEqual(false); + }); - const company = new Company({ - name: "MockStock", - info: "", - companyPositions: {}, - expMultiplier: 1, - salaryMultiplier: 1, - jobStatReqOffset: 1, - }) + it("should fail if player doesn't have any shares", function () { + Player.setMoney(0); + expect(sellStock(stock, 1, null, suppressDialogOpt)).toEqual(false); + }); - beforeEach(function() { - expect(initStockMarket).not.toThrow(); - expect(initSymbolToStockMap).not.toThrow(); + it("should not allow for transactions that exceed the maximum shares", function () { + const maxShares = stock.maxShares; + expect( + sellStock(stock, maxShares + 1, null, suppressDialogOpt), + ).toEqual(false); + }); - StockMarket[stock.name] = stock; - }); + it("should return true and properly update stock properties for successful transactions", function () { + const shares = 1e3; + stock.playerShares = shares; + stock.playerAvgPx = stock.price; + const gain = getSellTransactionGain(stock, shares, PositionTypes.Long); + Player.setMoney(0); - describe("influenceStockThroughServerHack()", function() { - it("should decrease a stock's second-order forecast when all of its money is hacked", function() { - const oldSecondOrderForecast = stock.otlkMagForecast; - influenceStockThroughServerHack(server, server.moneyMax); - expect(stock.otlkMagForecast).toEqual(oldSecondOrderForecast - forecastForecastChangeFromHack); - }); + expect(sellStock(stock, shares, null, suppressDialogOpt)).toEqual(true); + expect(stock.playerShares).toEqual(0); + expect(stock.playerAvgPx).toEqual(0); + expect(Player.money.toNumber()).toEqual(gain); + }); - it("should not decrease the stock's second-order forecast when no money is stolen", function() { - const oldSecondOrderForecast = stock.otlkMagForecast; - influenceStockThroughServerHack(server, 0); - expect(stock.otlkMagForecast).toEqual(oldSecondOrderForecast); - }); - }); + it("should cap the number of sharse sold to however many the player owns", function () { + const attemptedShares = 2e3; + const actualShares = 1e3; + stock.playerShares = actualShares; + stock.playerAvgPx = stock.price; + const gain = getSellTransactionGain( + stock, + actualShares, + PositionTypes.Long, + ); + Player.setMoney(0); - describe("influenceStockThroughServerGrow()", function() { - it("should increase a stock's second-order forecast when all of its money is grown", function() { - const oldSecondOrderForecast = stock.otlkMagForecast; - influenceStockThroughServerGrow(server, server.moneyMax); - expect(stock.otlkMagForecast).toEqual(oldSecondOrderForecast + forecastForecastChangeFromHack); - }); + expect( + sellStock(stock, attemptedShares, null, suppressDialogOpt), + ).toEqual(true); + expect(stock.playerShares).toEqual(0); + expect(stock.playerAvgPx).toEqual(0); + expect(Player.money.toNumber()).toEqual(gain); + }); - it("should not increase the stock's second-order forecast when no money is grown", function() { - const oldSecondOrderForecast = stock.otlkMagForecast; - influenceStockThroughServerGrow(server, 0); - expect(stock.otlkMagForecast).toEqual(oldSecondOrderForecast); - }); - }); + it("should properly update stock properties for partial transactions", function () { + const shares = 1e3; + const origPrice = stock.price; + stock.playerShares = 2 * shares; + stock.playerAvgPx = origPrice; + const gain = getSellTransactionGain(stock, shares, PositionTypes.Long); + Player.setMoney(0); - describe("influenceStockThroughCompanyWork()", function() { - it("should increase the server's second order forecast", function() { - const oldSecondOrderForecast = stock.otlkMagForecast; - - // Use 1e3 for numCycles to force a change - // (This may break later if numbers are rebalanced); - influenceStockThroughCompanyWork(company, 1, 500); - expect(stock.otlkMagForecast).toEqual(oldSecondOrderForecast + forecastForecastChangeFromCompanyWork); - }); - - it("should be affected by performanceMult", function() { - const oldSecondOrderForecast = stock.otlkMagForecast; - - // Use 1e3 for numCycles to force a change - // (This may break later if numbers are rebalanced); - influenceStockThroughCompanyWork(company, 4, 1e3); - expect(stock.otlkMagForecast).toEqual(oldSecondOrderForecast + 4 * forecastForecastChangeFromCompanyWork); - }); - }); + expect(sellStock(stock, shares, null, suppressDialogOpt)).toEqual(true); + expect(stock.playerShares).toEqual(shares); + expect(stock.playerAvgPx).toEqual(origPrice); + expect(Player.money.toNumber()).toEqual(gain); + }); }); + + describe("shortStock()", function () { + it("should fail for invalid arguments", function () { + expect(shortStock({} as Stock, 1, null, suppressDialogOpt)).toEqual( + false, + ); + expect(shortStock(stock, 0, null, suppressDialogOpt)).toEqual(false); + expect(shortStock(stock, -1, null, suppressDialogOpt)).toEqual(false); + expect(shortStock(stock, NaN, null, suppressDialogOpt)).toEqual(false); + }); + + it("should fail if player doesn't have enough money", function () { + Player.setMoney(0); + expect(shortStock(stock, 1, null, suppressDialogOpt)).toEqual(false); + }); + + it("should not allow for transactions that exceed the maximum shares", function () { + const maxShares = stock.maxShares; + expect( + shortStock(stock, maxShares + 1, null, suppressDialogOpt), + ).toEqual(false); + }); + + it("should return true and properly update stock properties for successful transactions", function () { + const shares = 1e3; + const cost = getBuyTransactionCost(stock, shares, PositionTypes.Short); + expect(cost).not.toBeNull(); + + // Checked above + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + Player.setMoney(cost!); + + expect(shortStock(stock, shares, null, suppressDialogOpt)).toEqual( + true, + ); + expect(stock.playerShortShares).toEqual(shares); + expect(stock.playerAvgShortPx).toBeGreaterThan(0); + expect(Player.money.toNumber()).toEqual(0); + }); + }); + + describe("sellShort()", function () { + it("should fail for invalid arguments", function () { + expect(sellShort({} as Stock, 1, null, suppressDialogOpt)).toEqual( + false, + ); + expect(sellShort(stock, 0, null, suppressDialogOpt)).toEqual(false); + expect(sellShort(stock, -1, null, suppressDialogOpt)).toEqual(false); + expect(sellShort(stock, NaN, null, suppressDialogOpt)).toEqual(false); + }); + + it("should fail if player doesn't have any shares", function () { + Player.setMoney(0); + expect(sellShort(stock, 1, null, suppressDialogOpt)).toEqual(false); + }); + + it("should not allow for transactions that exceed the maximum shares", function () { + const maxShares = stock.maxShares; + expect( + sellShort(stock, maxShares + 1, null, suppressDialogOpt), + ).toEqual(false); + }); + + it("should return true and properly update stock properties for successful transactions", function () { + const shares = 1e3; + stock.playerShortShares = shares; + stock.playerAvgShortPx = stock.price; + const gain = getSellTransactionGain(stock, shares, PositionTypes.Short); + Player.setMoney(0); + + expect(sellShort(stock, shares, null, suppressDialogOpt)).toEqual(true); + expect(stock.playerShortShares).toEqual(0); + expect(stock.playerAvgShortPx).toEqual(0); + expect(Player.money.toNumber()).toEqual(gain); + }); + + it("should cap the number of sharse sold to however many the player owns", function () { + const attemptedShares = 2e3; + const actualShares = 1e3; + stock.playerShortShares = actualShares; + stock.playerAvgShortPx = stock.price; + const gain = getSellTransactionGain( + stock, + actualShares, + PositionTypes.Short, + ); + Player.setMoney(0); + + expect( + sellShort(stock, attemptedShares, null, suppressDialogOpt), + ).toEqual(true); + expect(stock.playerShortShares).toEqual(0); + expect(stock.playerAvgShortPx).toEqual(0); + expect(Player.money.toNumber()).toEqual(gain); + }); + + it("should properly update stock properties for partial transactions", function () { + const shares = 1e3; + const origPrice = stock.price; + stock.playerShortShares = 2 * shares; + stock.playerAvgShortPx = origPrice; + const gain = getSellTransactionGain(stock, shares, PositionTypes.Short); + Player.setMoney(0); + + expect(sellShort(stock, shares, null, suppressDialogOpt)).toEqual(true); + expect(stock.playerShortShares).toEqual(shares); + expect(stock.playerAvgShortPx).toEqual(origPrice); + expect(Player.money.toNumber()).toEqual(gain); + }); + }); + }); + + describe("Order Class", function () { + it("should throw on invalid arguments", function () { + function invalid1(): Order { + return new Order( + {} as string, + 1, + 1, + OrderTypes.LimitBuy, + PositionTypes.Long, + ); + } + function invalid2(): Order { + return new Order( + "FOO", + "z" as any as number, + 0, + OrderTypes.LimitBuy, + PositionTypes.Short, + ); + } + function invalid3(): Order { + return new Order( + "FOO", + 1, + {} as number, + OrderTypes.LimitBuy, + PositionTypes.Short, + ); + } + function invalid4(): Order { + return new Order( + "FOO", + 1, + NaN, + OrderTypes.LimitBuy, + PositionTypes.Short, + ); + } + function invalid5(): Order { + return new Order( + "FOO", + NaN, + 0, + OrderTypes.LimitBuy, + PositionTypes.Short, + ); + } + + expect(invalid1).toThrow(); + expect(invalid2).toThrow(); + expect(invalid3).toThrow(); + expect(invalid4).toThrow(); + expect(invalid5).toThrow(); + }); + }); + + describe("Order Placing & Processing", function () { + beforeEach(function () { + expect(initStockMarket).not.toThrow(); + expect(initSymbolToStockMap).not.toThrow(); + + // Create an order book for our mock stock + StockMarket["Orders"][stock.symbol] = []; + }); + + describe("placeOrder()", function () { + it("should return false when it's called with invalid arguments", function () { + const invalid1 = placeOrder( + {} as Stock, + 1, + 1, + OrderTypes.LimitBuy, + PositionTypes.Long, + ); + const invalid2 = placeOrder( + stock, + "foo" as any as number, + 2, + OrderTypes.LimitBuy, + PositionTypes.Long, + ); + const invalid3 = placeOrder( + stock, + 1, + "foo" as any as number, + OrderTypes.LimitBuy, + PositionTypes.Long, + ); + + expect(invalid1).toEqual(false); + expect(invalid2).toEqual(false); + expect(invalid3).toEqual(false); + + expect(StockMarket["Orders"][stock.symbol]).toEqual([]); + }); + + it("should return true and update the order book for valid arguments", function () { + const res = placeOrder( + stock, + 1e3, + 9e3, + OrderTypes.LimitBuy, + PositionTypes.Long, + ); + expect(res).toEqual(true); + + expect(StockMarket["Orders"][stock.symbol]).toHaveLength(1); + const order = StockMarket["Orders"][stock.symbol][0]; + expect(order).toBeInstanceOf(Order); + expect(order.stockSymbol).toEqual(ctorParams.symbol); + expect(order.shares).toEqual(1e3); + expect(order.price).toEqual(9e3); + expect(order.type).toEqual(OrderTypes.LimitBuy); + expect(order.pos).toEqual(PositionTypes.Long); + }); + }); + + describe("cancelOrder()", function () { + beforeEach(function () { + StockMarket["Orders"][stock.symbol] = []; + + const res = placeOrder( + stock, + 1e3, + 9e3, + OrderTypes.LimitBuy, + PositionTypes.Long, + ); + expect(res).toEqual(true); + expect(StockMarket["Orders"][stock.symbol]).toHaveLength(1); + }); + + it("returns true & removes an Order from the order book", function () { + const order = StockMarket["Orders"][stock.symbol][0]; + const res = cancelOrder({ order }); + expect(res).toEqual(true); + expect(StockMarket["Orders"][stock.symbol]).toHaveLength(0); + }); + + it("should also work when passing in order parameters separately", function () { + const res = cancelOrder({ + stock, + shares: 1e3, + price: 9e3, + type: OrderTypes.LimitBuy, + pos: PositionTypes.Long, + }); + expect(res).toEqual(true); + expect(StockMarket["Orders"][stock.symbol]).toHaveLength(0); + }); + + it("should return false and do nothing when the specified order doesn't exist", function () { + // Same parameters, but its a different object + const order = new Order( + stock.symbol, + 1e3, + 9e3, + OrderTypes.LimitBuy, + PositionTypes.Long, + ); + const res = cancelOrder({ order }); + expect(res).toEqual(false); + expect(StockMarket["Orders"][stock.symbol]).toHaveLength(1); + + const res2 = cancelOrder({ + stock, + shares: 999, + price: 9e3, + type: OrderTypes.LimitBuy, + pos: PositionTypes.Long, + }); + expect(res2).toEqual(false); + expect(StockMarket["Orders"][stock.symbol]).toHaveLength(1); + }); + }); + + describe("processOrders()", function () { + let processOrdersRefs: IProcessOrderRefs; + + beforeEach(function () { + expect(initStockMarket).not.toThrow(); + expect(initSymbolToStockMap).not.toThrow(); + + StockMarket[stock.name] = stock; + SymbolToStockMap[stock.symbol] = stock; + StockMarket["Orders"][stock.symbol] = []; + + stock.playerShares = 1e3; + stock.playerShortShares = 1e3; + Player.setMoney(100e9); + + processOrdersRefs = { + rerenderFn: () => undefined, + stockMarket: StockMarket as IStockMarket, + symbolToStockMap: SymbolToStockMap, + }; + }); + + function checkThatOrderExists(placeOrderRes?: boolean): void { + if (typeof placeOrderRes === "boolean") { + expect(placeOrderRes).toEqual(true); + } + expect(StockMarket["Orders"][stock.symbol]).toHaveLength(1); + } + + function checkThatOrderExecuted(): void { + expect(StockMarket["Orders"][stock.symbol]).toHaveLength(0); + } + + it("should execute LONG Limit Buy orders when price <= order price", function () { + const res = placeOrder( + stock, + 1e3, + 9e3, + OrderTypes.LimitBuy, + PositionTypes.Long, + ); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders( + stock, + OrderTypes.LimitBuy, + PositionTypes.Long, + processOrdersRefs, + ); + checkThatOrderExecuted(); + expect(stock.playerShares).toEqual(2e3); + }); + + it("should execute SHORT Limit Buy Orders when price >= order price", function () { + const res = placeOrder( + stock, + 1e3, + 11e3, + OrderTypes.LimitBuy, + PositionTypes.Short, + ); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders( + stock, + OrderTypes.LimitBuy, + PositionTypes.Short, + processOrdersRefs, + ); + checkThatOrderExecuted(); + expect(stock.playerShortShares).toEqual(2e3); + }); + + it("should execute LONG Limit Sell Orders when price >= order price", function () { + const res = placeOrder( + stock, + 1e3, + 11e3, + OrderTypes.LimitSell, + PositionTypes.Long, + ); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders( + stock, + OrderTypes.LimitSell, + PositionTypes.Long, + processOrdersRefs, + ); + checkThatOrderExecuted(); + expect(stock.playerShares).toEqual(0); + }); + + it("should execute SHORT Limit Sell Orders when price <= order price", function () { + const res = placeOrder( + stock, + 1e3, + 9e3, + OrderTypes.LimitSell, + PositionTypes.Short, + ); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders( + stock, + OrderTypes.LimitSell, + PositionTypes.Short, + processOrdersRefs, + ); + checkThatOrderExecuted(); + expect(stock.playerShortShares).toEqual(0); + }); + + it("should execute LONG Stop Buy Orders when price >= order price", function () { + const res = placeOrder( + stock, + 1e3, + 11e3, + OrderTypes.StopBuy, + PositionTypes.Long, + ); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders( + stock, + OrderTypes.StopBuy, + PositionTypes.Long, + processOrdersRefs, + ); + checkThatOrderExecuted(); + expect(stock.playerShares).toEqual(2e3); + }); + + it("should execute SHORT Stop Buy Orders when price <= order price", function () { + const res = placeOrder( + stock, + 1e3, + 9e3, + OrderTypes.StopBuy, + PositionTypes.Short, + ); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders( + stock, + OrderTypes.StopBuy, + PositionTypes.Short, + processOrdersRefs, + ); + checkThatOrderExecuted(); + expect(stock.playerShortShares).toEqual(2e3); + }); + + it("should execute LONG Stop Sell Orders when price <= order price", function () { + const res = placeOrder( + stock, + 1e3, + 9e3, + OrderTypes.StopSell, + PositionTypes.Long, + ); + checkThatOrderExists(res); + + stock.changePrice(9e3); + processOrders( + stock, + OrderTypes.StopSell, + PositionTypes.Long, + processOrdersRefs, + ); + checkThatOrderExecuted(); + expect(stock.playerShares).toEqual(0); + }); + + it("should execute SHORT Stop Sell Orders when price >= order price", function () { + const res = placeOrder( + stock, + 1e3, + 11e3, + OrderTypes.StopSell, + PositionTypes.Short, + ); + checkThatOrderExists(res); + + stock.changePrice(11e3); + processOrders( + stock, + OrderTypes.StopSell, + PositionTypes.Short, + processOrdersRefs, + ); + checkThatOrderExecuted(); + expect(stock.playerShortShares).toEqual(0); + }); + + it("should execute immediately if their conditions are satisfied", function () { + placeOrder(stock, 1e3, 11e3, OrderTypes.LimitBuy, PositionTypes.Long); + checkThatOrderExecuted(); + expect(stock.playerShares).toEqual(2e3); + }); + }); + }); + + // TODO + describe("Player Influencing", function () { + const server = new Server({ + hostname: "mockserver", + moneyAvailable: 1e6, + organizationName: "MockStock", + }); + + const company = new Company({ + name: "MockStock", + info: "", + companyPositions: {}, + expMultiplier: 1, + salaryMultiplier: 1, + jobStatReqOffset: 1, + }); + + beforeEach(function () { + expect(initStockMarket).not.toThrow(); + expect(initSymbolToStockMap).not.toThrow(); + + StockMarket[stock.name] = stock; + }); + + describe("influenceStockThroughServerHack()", function () { + it("should decrease a stock's second-order forecast when all of its money is hacked", function () { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerHack(server, server.moneyMax); + expect(stock.otlkMagForecast).toEqual( + oldSecondOrderForecast - forecastForecastChangeFromHack, + ); + }); + + it("should not decrease the stock's second-order forecast when no money is stolen", function () { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerHack(server, 0); + expect(stock.otlkMagForecast).toEqual(oldSecondOrderForecast); + }); + }); + + describe("influenceStockThroughServerGrow()", function () { + it("should increase a stock's second-order forecast when all of its money is grown", function () { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerGrow(server, server.moneyMax); + expect(stock.otlkMagForecast).toEqual( + oldSecondOrderForecast + forecastForecastChangeFromHack, + ); + }); + + it("should not increase the stock's second-order forecast when no money is grown", function () { + const oldSecondOrderForecast = stock.otlkMagForecast; + influenceStockThroughServerGrow(server, 0); + expect(stock.otlkMagForecast).toEqual(oldSecondOrderForecast); + }); + }); + + describe("influenceStockThroughCompanyWork()", function () { + it("should increase the server's second order forecast", function () { + const oldSecondOrderForecast = stock.otlkMagForecast; + + // Use 1e3 for numCycles to force a change + // (This may break later if numbers are rebalanced); + influenceStockThroughCompanyWork(company, 1, 500); + expect(stock.otlkMagForecast).toEqual( + oldSecondOrderForecast + forecastForecastChangeFromCompanyWork, + ); + }); + + it("should be affected by performanceMult", function () { + const oldSecondOrderForecast = stock.otlkMagForecast; + + // Use 1e3 for numCycles to force a change + // (This may break later if numbers are rebalanced); + influenceStockThroughCompanyWork(company, 4, 1e3); + expect(stock.otlkMagForecast).toEqual( + oldSecondOrderForecast + 4 * forecastForecastChangeFromCompanyWork, + ); + }); + }); + }); }); diff --git a/test/StringHelperFunctions.test.ts b/test/StringHelperFunctions.test.ts index f4effabfe..3fe1db48f 100644 --- a/test/StringHelperFunctions.test.ts +++ b/test/StringHelperFunctions.test.ts @@ -1,33 +1,33 @@ import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions"; describe("StringHelperFunctions Tests", function () { - it("transforms strings", () => { - expect(convertTimeMsToTimeElapsedString(1000)).toEqual("1 seconds"); - expect(convertTimeMsToTimeElapsedString(5 * 60 * 1000 + 34 * 1000)).toEqual( - "5 minutes 34 seconds", - ); - expect( - convertTimeMsToTimeElapsedString( - 2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000, - ), - ).toEqual("2 days 5 minutes 34 seconds"); - expect( - convertTimeMsToTimeElapsedString( - 2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000, - true, - ), - ).toEqual("2 days 5 minutes 34.000 seconds"); - expect( - convertTimeMsToTimeElapsedString( - 2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123, - true, - ), - ).toEqual("2 days 5 minutes 34.123 seconds"); - expect( - convertTimeMsToTimeElapsedString( - 2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123.888, - true, - ), - ).toEqual("2 days 5 minutes 34.123 seconds"); - }) + it("transforms strings", () => { + expect(convertTimeMsToTimeElapsedString(1000)).toEqual("1 seconds"); + expect(convertTimeMsToTimeElapsedString(5 * 60 * 1000 + 34 * 1000)).toEqual( + "5 minutes 34 seconds", + ); + expect( + convertTimeMsToTimeElapsedString( + 2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000, + ), + ).toEqual("2 days 5 minutes 34 seconds"); + expect( + convertTimeMsToTimeElapsedString( + 2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000, + true, + ), + ).toEqual("2 days 5 minutes 34.000 seconds"); + expect( + convertTimeMsToTimeElapsedString( + 2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123, + true, + ), + ).toEqual("2 days 5 minutes 34.123 seconds"); + expect( + convertTimeMsToTimeElapsedString( + 2 * 60 * 60 * 24 * 1000 + 5 * 60 * 1000 + 34 * 1000 + 123.888, + true, + ), + ).toEqual("2 days 5 minutes 34.123 seconds"); + }); }); diff --git a/test/Terminal/Directory.test.js b/test/Terminal/Directory.test.js index 8a3ff8df5..cd3403f0a 100644 --- a/test/Terminal/Directory.test.js +++ b/test/Terminal/Directory.test.js @@ -1,295 +1,303 @@ import * as dirHelpers from "../../src/Terminal/DirectoryHelpers"; -describe("Terminal Directory Tests", function() { - describe("removeLeadingSlash()", function() { - const removeLeadingSlash = dirHelpers.removeLeadingSlash; +describe("Terminal Directory Tests", function () { + describe("removeLeadingSlash()", function () { + const removeLeadingSlash = dirHelpers.removeLeadingSlash; - it("should remove first slash in a string", function() { - expect(removeLeadingSlash("/")).toEqual(""); - expect(removeLeadingSlash("/foo.txt")).toEqual("foo.txt"); - expect(removeLeadingSlash("/foo/file.txt")).toEqual("foo/file.txt"); - }); - - it("should only remove one slash", function() { - expect(removeLeadingSlash("///")).toEqual("//"); - expect(removeLeadingSlash("//foo")).toEqual("/foo"); - }); - - it("should do nothing for a string that doesn't start with a slash", function() { - expect(removeLeadingSlash("foo.txt")).toEqual("foo.txt"); - expect(removeLeadingSlash("foo/test.txt")).toEqual("foo/test.txt"); - }); - - it("should not fail on an empty string", function() { - expect(removeLeadingSlash.bind(null, "")).not.toThrow(); - expect(removeLeadingSlash("")).toEqual(""); - }); + it("should remove first slash in a string", function () { + expect(removeLeadingSlash("/")).toEqual(""); + expect(removeLeadingSlash("/foo.txt")).toEqual("foo.txt"); + expect(removeLeadingSlash("/foo/file.txt")).toEqual("foo/file.txt"); }); - describe("removeTrailingSlash()", function() { - const removeTrailingSlash = dirHelpers.removeTrailingSlash; - - it("should remove last slash in a string", function() { - expect(removeTrailingSlash("/")).toEqual(""); - expect(removeTrailingSlash("foo.txt/")).toEqual("foo.txt"); - expect(removeTrailingSlash("foo/file.txt/")).toEqual("foo/file.txt"); - }); - - it("should only remove one slash", function() { - expect(removeTrailingSlash("///")).toEqual("//"); - expect(removeTrailingSlash("foo//")).toEqual("foo/"); - }); - - it("should do nothing for a string that doesn't end with a slash", function() { - expect(removeTrailingSlash("foo.txt")).toEqual("foo.txt"); - expect(removeTrailingSlash("foo/test.txt")).toEqual("foo/test.txt"); - }); - - it("should not fail on an empty string", function() { - expect(removeTrailingSlash.bind(null, "")).not.toThrow(); - expect(removeTrailingSlash("")).toEqual(""); - }); + it("should only remove one slash", function () { + expect(removeLeadingSlash("///")).toEqual("//"); + expect(removeLeadingSlash("//foo")).toEqual("/foo"); }); - describe("isValidFilename()", function() { - const isValidFilename = dirHelpers.isValidFilename; - - it("should return true for valid filenames", function() { - expect(isValidFilename("test.txt")).toEqual(true); - expect(isValidFilename("123.script")).toEqual(true); - expect(isValidFilename("foo123.b")).toEqual(true); - expect(isValidFilename("my_script.script")).toEqual(true); - expect(isValidFilename("my-script.script")).toEqual(true); - expect(isValidFilename("_foo.lit")).toEqual(true); - expect(isValidFilename("mult.periods.script")).toEqual(true); - expect(isValidFilename("mult.per-iods.again.script")).toEqual(true); - expect(isValidFilename("BruteSSH.exe-50%-INC")).toEqual(true); - expect(isValidFilename("DeepscanV1.exe-1.01%-INC")).toEqual(true); - expect(isValidFilename("DeepscanV2.exe-1.00%-INC")).toEqual(true); - expect(isValidFilename("AutoLink.exe-1.%-INC")).toEqual(true); - }); - - it("should return false for invalid filenames", function() { - expect(isValidFilename("foo")).toEqual(false); - expect(isValidFilename("my script.script")).toEqual(false); - expect(isValidFilename("a^.txt")).toEqual(false); - expect(isValidFilename("b#.lit")).toEqual(false); - expect(isValidFilename("lib().js")).toEqual(false); - expect(isValidFilename("foo.script_")).toEqual(false); - expect(isValidFilename("foo._script")).toEqual(false); - expect(isValidFilename("foo.hyphened-ext")).toEqual(false); - expect(isValidFilename("")).toEqual(false); - expect(isValidFilename("AutoLink-1.%-INC.exe")).toEqual(false); - expect(isValidFilename("AutoLink.exe-1.%-INC.exe")).toEqual(false); - expect(isValidFilename("foo%.exe")).toEqual(false); - expect(isValidFilename("-1.00%-INC")).toEqual(false); - }); + it("should do nothing for a string that doesn't start with a slash", function () { + expect(removeLeadingSlash("foo.txt")).toEqual("foo.txt"); + expect(removeLeadingSlash("foo/test.txt")).toEqual("foo/test.txt"); }); - describe("isValidDirectoryName()", function() { - const isValidDirectoryName = dirHelpers.isValidDirectoryName; + it("should not fail on an empty string", function () { + expect(removeLeadingSlash.bind(null, "")).not.toThrow(); + expect(removeLeadingSlash("")).toEqual(""); + }); + }); - it("should return true for valid directory names", function() { - expect(isValidDirectoryName("a")).toEqual(true); - expect(isValidDirectoryName("foo")).toEqual(true); - expect(isValidDirectoryName("foo-dir")).toEqual(true); - expect(isValidDirectoryName("foo_dir")).toEqual(true); - expect(isValidDirectoryName(".a")).toEqual(true); - expect(isValidDirectoryName("1")).toEqual(true); - expect(isValidDirectoryName("a1")).toEqual(true); - expect(isValidDirectoryName(".a1")).toEqual(true); - expect(isValidDirectoryName("._foo")).toEqual(true); - expect(isValidDirectoryName("_foo")).toEqual(true); - }); + describe("removeTrailingSlash()", function () { + const removeTrailingSlash = dirHelpers.removeTrailingSlash; - it("should return false for invalid directory names", function() { - expect(isValidDirectoryName("")).toEqual(false); - expect(isValidDirectoryName("foo.dir")).toEqual(false); - expect(isValidDirectoryName("1.")).toEqual(false); - expect(isValidDirectoryName("foo.")).toEqual(false); - expect(isValidDirectoryName("dir#")).toEqual(false); - expect(isValidDirectoryName("dir!")).toEqual(false); - expect(isValidDirectoryName("dir*")).toEqual(false); - expect(isValidDirectoryName(".")).toEqual(false); - }); + it("should remove last slash in a string", function () { + expect(removeTrailingSlash("/")).toEqual(""); + expect(removeTrailingSlash("foo.txt/")).toEqual("foo.txt"); + expect(removeTrailingSlash("foo/file.txt/")).toEqual("foo/file.txt"); }); - describe("isValidDirectoryPath()", function() { - const isValidDirectoryPath = dirHelpers.isValidDirectoryPath; - - it("should return false for empty strings", function() { - expect(isValidDirectoryPath("")).toEqual(false); - }); - - it("should return true only for the forward slash if the string has length 1", function() { - expect(isValidDirectoryPath("/")).toEqual(true); - expect(isValidDirectoryPath(" ")).toEqual(false); - expect(isValidDirectoryPath(".")).toEqual(false); - expect(isValidDirectoryPath("a")).toEqual(false); - }); - - it("should return true for valid directory paths", function() { - expect(isValidDirectoryPath("/a")).toEqual(true); - expect(isValidDirectoryPath("/dir/a")).toEqual(true); - expect(isValidDirectoryPath("/dir/foo")).toEqual(true); - expect(isValidDirectoryPath("/.dir/foo-dir")).toEqual(true); - expect(isValidDirectoryPath("/.dir/foo_dir")).toEqual(true); - expect(isValidDirectoryPath("/.dir/.a")).toEqual(true); - expect(isValidDirectoryPath("/dir1/1")).toEqual(true); - expect(isValidDirectoryPath("/dir1/a1")).toEqual(true); - expect(isValidDirectoryPath("/dir1/.a1")).toEqual(true); - expect(isValidDirectoryPath("/dir_/._foo")).toEqual(true); - expect(isValidDirectoryPath("/dir-/_foo")).toEqual(true); - }); - - it("should return false if the path does not have a leading slash", function() { - expect(isValidDirectoryPath("a")).toEqual(false); - expect(isValidDirectoryPath("dir/a")).toEqual(false); - expect(isValidDirectoryPath("dir/foo")).toEqual(false); - expect(isValidDirectoryPath(".dir/foo-dir")).toEqual(false); - expect(isValidDirectoryPath(".dir/foo_dir")).toEqual(false); - expect(isValidDirectoryPath(".dir/.a")).toEqual(false); - expect(isValidDirectoryPath("dir1/1")).toEqual(false); - expect(isValidDirectoryPath("dir1/a1")).toEqual(false); - expect(isValidDirectoryPath("dir1/.a1")).toEqual(false); - expect(isValidDirectoryPath("dir_/._foo")).toEqual(false); - expect(isValidDirectoryPath("dir-/_foo")).toEqual(false); - }); - - it("should accept dot notation", function() { - expect(isValidDirectoryPath("/dir/./a")).toEqual(true); - expect(isValidDirectoryPath("/dir/../foo")).toEqual(true); - expect(isValidDirectoryPath("/.dir/./foo-dir")).toEqual(true); - expect(isValidDirectoryPath("/.dir/../foo_dir")).toEqual(true); - expect(isValidDirectoryPath("/.dir/./.a")).toEqual(true); - expect(isValidDirectoryPath("/dir1/1/.")).toEqual(true); - expect(isValidDirectoryPath("/dir1/a1/..")).toEqual(true); - expect(isValidDirectoryPath("/dir1/.a1/..")).toEqual(true); - expect(isValidDirectoryPath("/dir_/._foo/.")).toEqual(true); - expect(isValidDirectoryPath("/./dir-/_foo")).toEqual(true); - expect(isValidDirectoryPath("/../dir-/_foo")).toEqual(true); - }); + it("should only remove one slash", function () { + expect(removeTrailingSlash("///")).toEqual("//"); + expect(removeTrailingSlash("foo//")).toEqual("foo/"); }); - describe("isValidFilePath()", function() { - const isValidFilePath = dirHelpers.isValidFilePath; - - it("should return false for strings that are too short", function() { - expect(isValidFilePath("/a")).toEqual(false); - expect(isValidFilePath("a.")).toEqual(false); - expect(isValidFilePath(".a")).toEqual(false); - expect(isValidFilePath("/.")).toEqual(false); - }); - - it("should return true for arguments that are just filenames", function() { - expect(isValidFilePath("test.txt")).toEqual(true); - expect(isValidFilePath("123.script")).toEqual(true); - expect(isValidFilePath("foo123.b")).toEqual(true); - expect(isValidFilePath("my_script.script")).toEqual(true); - expect(isValidFilePath("my-script.script")).toEqual(true); - expect(isValidFilePath("_foo.lit")).toEqual(true); - expect(isValidFilePath("mult.periods.script")).toEqual(true); - expect(isValidFilePath("mult.per-iods.again.script")).toEqual(true); - }); - - it("should return true for valid filepaths", function() { - expect(isValidFilePath("/foo/test.txt")).toEqual(true); - expect(isValidFilePath("/../123.script")).toEqual(true); - expect(isValidFilePath("/./foo123.b")).toEqual(true); - expect(isValidFilePath("/dir/my_script.script")).toEqual(true); - expect(isValidFilePath("/dir1/dir2/dir3/my-script.script")).toEqual(true); - expect(isValidFilePath("/dir1/dir2/././../_foo.lit")).toEqual(true); - expect(isValidFilePath("/.dir1/./../.dir2/mult.periods.script")).toEqual(true); - expect(isValidFilePath("/_dir/../dir2/mult.per-iods.again.script")).toEqual(true); - }); - - it("should return false for strings that end with a slash", function() { - expect(isValidFilePath("/foo/")).toEqual(false); - expect(isValidFilePath("foo.txt/")).toEqual(false); - expect(isValidFilePath("/")).toEqual(false); - expect(isValidFilePath("/_dir/")).toEqual(false); - }); - - it("should return false for invalid arguments", function() { - expect(isValidFilePath(null)).toEqual(false); - expect(isValidFilePath()).toEqual(false); - expect(isValidFilePath(5)).toEqual(false); - expect(isValidFilePath({})).toEqual(false); - }) + it("should do nothing for a string that doesn't end with a slash", function () { + expect(removeTrailingSlash("foo.txt")).toEqual("foo.txt"); + expect(removeTrailingSlash("foo/test.txt")).toEqual("foo/test.txt"); }); - describe("getFirstParentDirectory()", function() { - const getFirstParentDirectory = dirHelpers.getFirstParentDirectory; + it("should not fail on an empty string", function () { + expect(removeTrailingSlash.bind(null, "")).not.toThrow(); + expect(removeTrailingSlash("")).toEqual(""); + }); + }); - it("should return the first parent directory in a filepath", function() { - expect(getFirstParentDirectory("/dir1/foo.txt")).toEqual("dir1/"); - expect(getFirstParentDirectory("/dir1/dir2/dir3/dir4/foo.txt")).toEqual("dir1/"); - expect(getFirstParentDirectory("/_dir1/dir2/foo.js")).toEqual("_dir1/"); - }); + describe("isValidFilename()", function () { + const isValidFilename = dirHelpers.isValidFilename; - it("should return '/' if there is no first parent directory", function() { - expect(getFirstParentDirectory("")).toEqual("/"); - expect(getFirstParentDirectory(" ")).toEqual("/"); - expect(getFirstParentDirectory("/")).toEqual("/"); - expect(getFirstParentDirectory("//")).toEqual("/"); - expect(getFirstParentDirectory("foo.script")).toEqual("/"); - expect(getFirstParentDirectory("/foo.txt")).toEqual("/"); - }); + it("should return true for valid filenames", function () { + expect(isValidFilename("test.txt")).toEqual(true); + expect(isValidFilename("123.script")).toEqual(true); + expect(isValidFilename("foo123.b")).toEqual(true); + expect(isValidFilename("my_script.script")).toEqual(true); + expect(isValidFilename("my-script.script")).toEqual(true); + expect(isValidFilename("_foo.lit")).toEqual(true); + expect(isValidFilename("mult.periods.script")).toEqual(true); + expect(isValidFilename("mult.per-iods.again.script")).toEqual(true); + expect(isValidFilename("BruteSSH.exe-50%-INC")).toEqual(true); + expect(isValidFilename("DeepscanV1.exe-1.01%-INC")).toEqual(true); + expect(isValidFilename("DeepscanV2.exe-1.00%-INC")).toEqual(true); + expect(isValidFilename("AutoLink.exe-1.%-INC")).toEqual(true); }); - describe("getAllParentDirectories()", function() { - const getAllParentDirectories = dirHelpers.getAllParentDirectories; + it("should return false for invalid filenames", function () { + expect(isValidFilename("foo")).toEqual(false); + expect(isValidFilename("my script.script")).toEqual(false); + expect(isValidFilename("a^.txt")).toEqual(false); + expect(isValidFilename("b#.lit")).toEqual(false); + expect(isValidFilename("lib().js")).toEqual(false); + expect(isValidFilename("foo.script_")).toEqual(false); + expect(isValidFilename("foo._script")).toEqual(false); + expect(isValidFilename("foo.hyphened-ext")).toEqual(false); + expect(isValidFilename("")).toEqual(false); + expect(isValidFilename("AutoLink-1.%-INC.exe")).toEqual(false); + expect(isValidFilename("AutoLink.exe-1.%-INC.exe")).toEqual(false); + expect(isValidFilename("foo%.exe")).toEqual(false); + expect(isValidFilename("-1.00%-INC")).toEqual(false); + }); + }); - it("should return all parent directories in a filepath", function() { - expect(getAllParentDirectories("/")).toEqual("/"); - expect(getAllParentDirectories("/home/var/foo.txt")).toEqual("/home/var/"); - expect(getAllParentDirectories("/home/var/")).toEqual("/home/var/"); - expect(getAllParentDirectories("/home/var/test/")).toEqual("/home/var/test/"); - }); + describe("isValidDirectoryName()", function () { + const isValidDirectoryName = dirHelpers.isValidDirectoryName; - it("should return an empty string if there are no parent directories", function() { - expect(getAllParentDirectories("foo.txt")).toEqual(""); - }); + it("should return true for valid directory names", function () { + expect(isValidDirectoryName("a")).toEqual(true); + expect(isValidDirectoryName("foo")).toEqual(true); + expect(isValidDirectoryName("foo-dir")).toEqual(true); + expect(isValidDirectoryName("foo_dir")).toEqual(true); + expect(isValidDirectoryName(".a")).toEqual(true); + expect(isValidDirectoryName("1")).toEqual(true); + expect(isValidDirectoryName("a1")).toEqual(true); + expect(isValidDirectoryName(".a1")).toEqual(true); + expect(isValidDirectoryName("._foo")).toEqual(true); + expect(isValidDirectoryName("_foo")).toEqual(true); }); - describe("isInRootDirectory()", function() { - const isInRootDirectory = dirHelpers.isInRootDirectory; + it("should return false for invalid directory names", function () { + expect(isValidDirectoryName("")).toEqual(false); + expect(isValidDirectoryName("foo.dir")).toEqual(false); + expect(isValidDirectoryName("1.")).toEqual(false); + expect(isValidDirectoryName("foo.")).toEqual(false); + expect(isValidDirectoryName("dir#")).toEqual(false); + expect(isValidDirectoryName("dir!")).toEqual(false); + expect(isValidDirectoryName("dir*")).toEqual(false); + expect(isValidDirectoryName(".")).toEqual(false); + }); + }); - it("should return true for filepaths that refer to a file in the root directory", function() { - expect(isInRootDirectory("a.b")).toEqual(true); - expect(isInRootDirectory("foo.txt")).toEqual(true); - expect(isInRootDirectory("/foo.txt")).toEqual(true); - }); + describe("isValidDirectoryPath()", function () { + const isValidDirectoryPath = dirHelpers.isValidDirectoryPath; - it("should return false for filepaths that refer to a file that's NOT in the root directory", function() { - expect(isInRootDirectory("/dir/foo.txt")).toEqual(false); - expect(isInRootDirectory("dir/foo.txt")).toEqual(false); - expect(isInRootDirectory("/./foo.js")).toEqual(false); - expect(isInRootDirectory("../foo.js")).toEqual(false); - expect(isInRootDirectory("/dir1/dir2/dir3/foo.txt")).toEqual(false); - }); - - it("should return false for invalid inputs (inputs that aren't filepaths)", function() { - expect(isInRootDirectory(null)).toEqual(false); - expect(isInRootDirectory(undefined)).toEqual(false); - expect(isInRootDirectory("")).toEqual(false); - expect(isInRootDirectory(" ")).toEqual(false); - expect(isInRootDirectory("a")).toEqual(false); - expect(isInRootDirectory("/dir")).toEqual(false); - expect(isInRootDirectory("/dir/")).toEqual(false); - expect(isInRootDirectory("/dir/foo")).toEqual(false); - }); + it("should return false for empty strings", function () { + expect(isValidDirectoryPath("")).toEqual(false); }); - describe("evaluateDirectoryPath()", function() { - //const evaluateDirectoryPath = dirHelpers.evaluateDirectoryPath; - - // TODO + it("should return true only for the forward slash if the string has length 1", function () { + expect(isValidDirectoryPath("/")).toEqual(true); + expect(isValidDirectoryPath(" ")).toEqual(false); + expect(isValidDirectoryPath(".")).toEqual(false); + expect(isValidDirectoryPath("a")).toEqual(false); }); - describe("evaluateFilePath()", function() { - //const evaluateFilePath = dirHelpers.evaluateFilePath; + it("should return true for valid directory paths", function () { + expect(isValidDirectoryPath("/a")).toEqual(true); + expect(isValidDirectoryPath("/dir/a")).toEqual(true); + expect(isValidDirectoryPath("/dir/foo")).toEqual(true); + expect(isValidDirectoryPath("/.dir/foo-dir")).toEqual(true); + expect(isValidDirectoryPath("/.dir/foo_dir")).toEqual(true); + expect(isValidDirectoryPath("/.dir/.a")).toEqual(true); + expect(isValidDirectoryPath("/dir1/1")).toEqual(true); + expect(isValidDirectoryPath("/dir1/a1")).toEqual(true); + expect(isValidDirectoryPath("/dir1/.a1")).toEqual(true); + expect(isValidDirectoryPath("/dir_/._foo")).toEqual(true); + expect(isValidDirectoryPath("/dir-/_foo")).toEqual(true); + }); - // TODO - }) + it("should return false if the path does not have a leading slash", function () { + expect(isValidDirectoryPath("a")).toEqual(false); + expect(isValidDirectoryPath("dir/a")).toEqual(false); + expect(isValidDirectoryPath("dir/foo")).toEqual(false); + expect(isValidDirectoryPath(".dir/foo-dir")).toEqual(false); + expect(isValidDirectoryPath(".dir/foo_dir")).toEqual(false); + expect(isValidDirectoryPath(".dir/.a")).toEqual(false); + expect(isValidDirectoryPath("dir1/1")).toEqual(false); + expect(isValidDirectoryPath("dir1/a1")).toEqual(false); + expect(isValidDirectoryPath("dir1/.a1")).toEqual(false); + expect(isValidDirectoryPath("dir_/._foo")).toEqual(false); + expect(isValidDirectoryPath("dir-/_foo")).toEqual(false); + }); + + it("should accept dot notation", function () { + expect(isValidDirectoryPath("/dir/./a")).toEqual(true); + expect(isValidDirectoryPath("/dir/../foo")).toEqual(true); + expect(isValidDirectoryPath("/.dir/./foo-dir")).toEqual(true); + expect(isValidDirectoryPath("/.dir/../foo_dir")).toEqual(true); + expect(isValidDirectoryPath("/.dir/./.a")).toEqual(true); + expect(isValidDirectoryPath("/dir1/1/.")).toEqual(true); + expect(isValidDirectoryPath("/dir1/a1/..")).toEqual(true); + expect(isValidDirectoryPath("/dir1/.a1/..")).toEqual(true); + expect(isValidDirectoryPath("/dir_/._foo/.")).toEqual(true); + expect(isValidDirectoryPath("/./dir-/_foo")).toEqual(true); + expect(isValidDirectoryPath("/../dir-/_foo")).toEqual(true); + }); + }); + + describe("isValidFilePath()", function () { + const isValidFilePath = dirHelpers.isValidFilePath; + + it("should return false for strings that are too short", function () { + expect(isValidFilePath("/a")).toEqual(false); + expect(isValidFilePath("a.")).toEqual(false); + expect(isValidFilePath(".a")).toEqual(false); + expect(isValidFilePath("/.")).toEqual(false); + }); + + it("should return true for arguments that are just filenames", function () { + expect(isValidFilePath("test.txt")).toEqual(true); + expect(isValidFilePath("123.script")).toEqual(true); + expect(isValidFilePath("foo123.b")).toEqual(true); + expect(isValidFilePath("my_script.script")).toEqual(true); + expect(isValidFilePath("my-script.script")).toEqual(true); + expect(isValidFilePath("_foo.lit")).toEqual(true); + expect(isValidFilePath("mult.periods.script")).toEqual(true); + expect(isValidFilePath("mult.per-iods.again.script")).toEqual(true); + }); + + it("should return true for valid filepaths", function () { + expect(isValidFilePath("/foo/test.txt")).toEqual(true); + expect(isValidFilePath("/../123.script")).toEqual(true); + expect(isValidFilePath("/./foo123.b")).toEqual(true); + expect(isValidFilePath("/dir/my_script.script")).toEqual(true); + expect(isValidFilePath("/dir1/dir2/dir3/my-script.script")).toEqual(true); + expect(isValidFilePath("/dir1/dir2/././../_foo.lit")).toEqual(true); + expect(isValidFilePath("/.dir1/./../.dir2/mult.periods.script")).toEqual( + true, + ); + expect( + isValidFilePath("/_dir/../dir2/mult.per-iods.again.script"), + ).toEqual(true); + }); + + it("should return false for strings that end with a slash", function () { + expect(isValidFilePath("/foo/")).toEqual(false); + expect(isValidFilePath("foo.txt/")).toEqual(false); + expect(isValidFilePath("/")).toEqual(false); + expect(isValidFilePath("/_dir/")).toEqual(false); + }); + + it("should return false for invalid arguments", function () { + expect(isValidFilePath(null)).toEqual(false); + expect(isValidFilePath()).toEqual(false); + expect(isValidFilePath(5)).toEqual(false); + expect(isValidFilePath({})).toEqual(false); + }); + }); + + describe("getFirstParentDirectory()", function () { + const getFirstParentDirectory = dirHelpers.getFirstParentDirectory; + + it("should return the first parent directory in a filepath", function () { + expect(getFirstParentDirectory("/dir1/foo.txt")).toEqual("dir1/"); + expect(getFirstParentDirectory("/dir1/dir2/dir3/dir4/foo.txt")).toEqual( + "dir1/", + ); + expect(getFirstParentDirectory("/_dir1/dir2/foo.js")).toEqual("_dir1/"); + }); + + it("should return '/' if there is no first parent directory", function () { + expect(getFirstParentDirectory("")).toEqual("/"); + expect(getFirstParentDirectory(" ")).toEqual("/"); + expect(getFirstParentDirectory("/")).toEqual("/"); + expect(getFirstParentDirectory("//")).toEqual("/"); + expect(getFirstParentDirectory("foo.script")).toEqual("/"); + expect(getFirstParentDirectory("/foo.txt")).toEqual("/"); + }); + }); + + describe("getAllParentDirectories()", function () { + const getAllParentDirectories = dirHelpers.getAllParentDirectories; + + it("should return all parent directories in a filepath", function () { + expect(getAllParentDirectories("/")).toEqual("/"); + expect(getAllParentDirectories("/home/var/foo.txt")).toEqual( + "/home/var/", + ); + expect(getAllParentDirectories("/home/var/")).toEqual("/home/var/"); + expect(getAllParentDirectories("/home/var/test/")).toEqual( + "/home/var/test/", + ); + }); + + it("should return an empty string if there are no parent directories", function () { + expect(getAllParentDirectories("foo.txt")).toEqual(""); + }); + }); + + describe("isInRootDirectory()", function () { + const isInRootDirectory = dirHelpers.isInRootDirectory; + + it("should return true for filepaths that refer to a file in the root directory", function () { + expect(isInRootDirectory("a.b")).toEqual(true); + expect(isInRootDirectory("foo.txt")).toEqual(true); + expect(isInRootDirectory("/foo.txt")).toEqual(true); + }); + + it("should return false for filepaths that refer to a file that's NOT in the root directory", function () { + expect(isInRootDirectory("/dir/foo.txt")).toEqual(false); + expect(isInRootDirectory("dir/foo.txt")).toEqual(false); + expect(isInRootDirectory("/./foo.js")).toEqual(false); + expect(isInRootDirectory("../foo.js")).toEqual(false); + expect(isInRootDirectory("/dir1/dir2/dir3/foo.txt")).toEqual(false); + }); + + it("should return false for invalid inputs (inputs that aren't filepaths)", function () { + expect(isInRootDirectory(null)).toEqual(false); + expect(isInRootDirectory(undefined)).toEqual(false); + expect(isInRootDirectory("")).toEqual(false); + expect(isInRootDirectory(" ")).toEqual(false); + expect(isInRootDirectory("a")).toEqual(false); + expect(isInRootDirectory("/dir")).toEqual(false); + expect(isInRootDirectory("/dir/")).toEqual(false); + expect(isInRootDirectory("/dir/foo")).toEqual(false); + }); + }); + + describe("evaluateDirectoryPath()", function () { + //const evaluateDirectoryPath = dirHelpers.evaluateDirectoryPath; + // TODO + }); + + describe("evaluateFilePath()", function () { + //const evaluateFilePath = dirHelpers.evaluateFilePath; + // TODO + }); }); diff --git a/test/Terminal/determineAllPossibilitiesForTabCompletion.test.ts b/test/Terminal/determineAllPossibilitiesForTabCompletion.test.ts index 2e60f52d8..56974f577 100644 --- a/test/Terminal/determineAllPossibilitiesForTabCompletion.test.ts +++ b/test/Terminal/determineAllPossibilitiesForTabCompletion.test.ts @@ -2,236 +2,185 @@ import { Player } from "../../src/Player"; import { determineAllPossibilitiesForTabCompletion } from "../../src/Terminal/determineAllPossibilitiesForTabCompletion"; import { Server } from "../../src/Server/Server"; import { - AddToAllServers, - prestigeAllServers, + AddToAllServers, + prestigeAllServers, } from "../../src/Server/AllServers"; import { LocationName } from "../../src/Locations/data/LocationNames"; import { Message } from "../../src/Message/Message"; import { CodingContract } from "../../src/CodingContracts"; describe("determineAllPossibilitiesForTabCompletion", function () { - let closeServer: Server; - let farServer: Server; + let closeServer: Server; + let farServer: Server; - beforeEach(() => { - prestigeAllServers(); - Player.init(); + beforeEach(() => { + prestigeAllServers(); + Player.init(); - closeServer = new Server({ - ip: "8.8.8.8", - hostname: "near", - hackDifficulty: 1, - moneyAvailable: 70000, - numOpenPortsRequired: 0, - organizationName: LocationName.NewTokyoNoodleBar, - requiredHackingSkill: 1, - serverGrowth: 3000, - }); - farServer = new Server({ - ip: "4.4.4.4", - hostname: "far", - hackDifficulty: 1, - moneyAvailable: 70000, - numOpenPortsRequired: 0, - organizationName: LocationName.Aevum, - requiredHackingSkill: 1, - serverGrowth: 3000, - }); - Player.getHomeComputer().serversOnNetwork.push(closeServer.ip); - closeServer.serversOnNetwork.push(Player.getHomeComputer().ip); - closeServer.serversOnNetwork.push(farServer.ip); - farServer.serversOnNetwork.push(closeServer.ip); - AddToAllServers(closeServer); - AddToAllServers(farServer); + closeServer = new Server({ + ip: "8.8.8.8", + hostname: "near", + hackDifficulty: 1, + moneyAvailable: 70000, + numOpenPortsRequired: 0, + organizationName: LocationName.NewTokyoNoodleBar, + requiredHackingSkill: 1, + serverGrowth: 3000, }); - - it("completes the connect command", () => { - const options = determineAllPossibilitiesForTabCompletion( - Player, - "connect ", - 0, - ); - expect(options).toEqual(["8.8.8.8", "near"]); + farServer = new Server({ + ip: "4.4.4.4", + hostname: "far", + hackDifficulty: 1, + moneyAvailable: 70000, + numOpenPortsRequired: 0, + organizationName: LocationName.Aevum, + requiredHackingSkill: 1, + serverGrowth: 3000, }); + Player.getHomeComputer().serversOnNetwork.push(closeServer.ip); + closeServer.serversOnNetwork.push(Player.getHomeComputer().ip); + closeServer.serversOnNetwork.push(farServer.ip); + farServer.serversOnNetwork.push(closeServer.ip); + AddToAllServers(closeServer); + AddToAllServers(farServer); + }); - it("completes the buy command", () => { - const options = determineAllPossibilitiesForTabCompletion( - Player, - "buy ", - 0, - ); - expect(options).toEqual([ - "BruteSSH.exe", - "FTPCrack.exe", - "relaySMTP.exe", - "HTTPWorm.exe", - "SQLInject.exe", - "DeepscanV1.exe", - "DeepscanV2.exe", - "AutoLink.exe", - "ServerProfiler.exe", - ]); - }); + it("completes the connect command", () => { + const options = determineAllPossibilitiesForTabCompletion( + Player, + "connect ", + 0, + ); + expect(options).toEqual(["8.8.8.8", "near"]); + }); - it("completes the scp command", () => { - Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark"); - Player.getHomeComputer().messages.push("af.lit"); - Player.getHomeComputer().writeToScriptFile( - "/www/script.js", - "oh hai mark", - ); - const options1 = determineAllPossibilitiesForTabCompletion( - Player, - "scp ", - 0, - ); - expect(options1).toEqual([ - "/www/script.js", - "af.lit", - "note.txt", - "www/", - ]); + it("completes the buy command", () => { + const options = determineAllPossibilitiesForTabCompletion( + Player, + "buy ", + 0, + ); + expect(options).toEqual([ + "BruteSSH.exe", + "FTPCrack.exe", + "relaySMTP.exe", + "HTTPWorm.exe", + "SQLInject.exe", + "DeepscanV1.exe", + "DeepscanV2.exe", + "AutoLink.exe", + "ServerProfiler.exe", + ]); + }); - const options2 = determineAllPossibilitiesForTabCompletion( - Player, - "scp note.txt ", - 1, - ); - expect(options2).toEqual([ - Player.getHomeComputer().ip, - "home", - "8.8.8.8", - "near", - "4.4.4.4", - "far", - ]); - }); + it("completes the scp command", () => { + Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark"); + Player.getHomeComputer().messages.push("af.lit"); + Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark"); + const options1 = determineAllPossibilitiesForTabCompletion( + Player, + "scp ", + 0, + ); + expect(options1).toEqual(["/www/script.js", "af.lit", "note.txt", "www/"]); - it("completes the kill, tail, mem, and check commands", () => { - Player.getHomeComputer().writeToScriptFile( - "/www/script.js", - "oh hai mark", - ); - for (const command of ["kill", "tail", "mem", "check"]) { - expect( - determineAllPossibilitiesForTabCompletion( - Player, - `${command} `, - 0, - ), - ).toEqual(["/www/script.js", "www/"]); - } - }); + const options2 = determineAllPossibilitiesForTabCompletion( + Player, + "scp note.txt ", + 1, + ); + expect(options2).toEqual([ + Player.getHomeComputer().ip, + "home", + "8.8.8.8", + "near", + "4.4.4.4", + "far", + ]); + }); - it("completes the nano commands", () => { - Player.getHomeComputer().writeToScriptFile( - "/www/script.js", - "oh hai mark", - ); - Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark"); - expect( - determineAllPossibilitiesForTabCompletion(Player, "nano ", 0), - ).toEqual(["/www/script.js", "note.txt", ".fconf", "www/"]); - }); + it("completes the kill, tail, mem, and check commands", () => { + Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark"); + for (const command of ["kill", "tail", "mem", "check"]) { + expect( + determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0), + ).toEqual(["/www/script.js", "www/"]); + } + }); - it("completes the rm command", () => { - Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark"); - Player.getHomeComputer().writeToScriptFile( - "/www/script.js", - "oh hai mark", - ); - Player.getHomeComputer().contracts.push( - new CodingContract("linklist.cct"), - ); - Player.getHomeComputer().messages.push(new Message("asl.msg")); - Player.getHomeComputer().messages.push("af.lit"); - expect( - determineAllPossibilitiesForTabCompletion(Player, "rm ", 0), - ).toEqual([ - "/www/script.js", - "NUKE.exe", - "af.lit", - "note.txt", - "linklist.cct", - "www/", - ]); - }); + it("completes the nano commands", () => { + Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark"); + Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark"); + expect( + determineAllPossibilitiesForTabCompletion(Player, "nano ", 0), + ).toEqual(["/www/script.js", "note.txt", ".fconf", "www/"]); + }); - it("completes the run command", () => { - Player.getHomeComputer().writeToScriptFile( - "/www/script.js", - "oh hai mark", - ); - Player.getHomeComputer().contracts.push( - new CodingContract("linklist.cct"), - ); - expect( - determineAllPossibilitiesForTabCompletion(Player, "run ", 0), - ).toEqual(["/www/script.js", "NUKE.exe", "linklist.cct", "www/"]); - }); + it("completes the rm command", () => { + Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark"); + Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark"); + Player.getHomeComputer().contracts.push(new CodingContract("linklist.cct")); + Player.getHomeComputer().messages.push(new Message("asl.msg")); + Player.getHomeComputer().messages.push("af.lit"); + expect(determineAllPossibilitiesForTabCompletion(Player, "rm ", 0)).toEqual( + [ + "/www/script.js", + "NUKE.exe", + "af.lit", + "note.txt", + "linklist.cct", + "www/", + ], + ); + }); - it("completes the cat command", () => { - Player.getHomeComputer().writeToTextFile( - "/www/note.txt", - "oh hai mark", - ); - Player.getHomeComputer().messages.push(new Message("asl.msg")); - Player.getHomeComputer().messages.push("af.lit"); - expect( - determineAllPossibilitiesForTabCompletion(Player, "cat ", 0), - ).toEqual(["asl.msg", "af.lit", "/www/note.txt", "www/"]); - }); + it("completes the run command", () => { + Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark"); + Player.getHomeComputer().contracts.push(new CodingContract("linklist.cct")); + expect( + determineAllPossibilitiesForTabCompletion(Player, "run ", 0), + ).toEqual(["/www/script.js", "NUKE.exe", "linklist.cct", "www/"]); + }); - it("completes the download and mv commands", () => { - Player.getHomeComputer().writeToScriptFile( - "/www/script.js", - "oh hai mark", - ); - Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark"); - for (const command of ["download", "mv"]) { - expect( - determineAllPossibilitiesForTabCompletion( - Player, - `${command} `, - 0, - ), - ).toEqual(["/www/script.js", "note.txt", "www/"]); - } - }); + it("completes the cat command", () => { + Player.getHomeComputer().writeToTextFile("/www/note.txt", "oh hai mark"); + Player.getHomeComputer().messages.push(new Message("asl.msg")); + Player.getHomeComputer().messages.push("af.lit"); + expect( + determineAllPossibilitiesForTabCompletion(Player, "cat ", 0), + ).toEqual(["asl.msg", "af.lit", "/www/note.txt", "www/"]); + }); - it("completes the cd command", () => { - Player.getHomeComputer().writeToScriptFile( - "/www/script.js", - "oh hai mark", - ); - expect( - determineAllPossibilitiesForTabCompletion(Player, "cd ", 0), - ).toEqual(["www/"]); - }); + it("completes the download and mv commands", () => { + Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark"); + Player.getHomeComputer().writeToTextFile("note.txt", "oh hai mark"); + for (const command of ["download", "mv"]) { + expect( + determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0), + ).toEqual(["/www/script.js", "note.txt", "www/"]); + } + }); - it("completes the ls and cd commands", () => { - Player.getHomeComputer().writeToScriptFile( - "/www/script.js", - "oh hai mark", - ); - for (const command of ["ls", "cd"]) { - expect( - determineAllPossibilitiesForTabCompletion( - Player, - `${command} `, - 0, - ), - ).toEqual(["www/"]); - } - }); + it("completes the cd command", () => { + Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark"); + expect(determineAllPossibilitiesForTabCompletion(Player, "cd ", 0)).toEqual( + ["www/"], + ); + }); - it("completes commands starting with ./", () => { - Player.getHomeComputer().writeToScriptFile( - "/www/script.js", - "oh hai mark", - ); - expect( - determineAllPossibilitiesForTabCompletion(Player, "run ./", 0), - ).toEqual([".//www/script.js", "NUKE.exe", "./www/"]); - }); + it("completes the ls and cd commands", () => { + Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark"); + for (const command of ["ls", "cd"]) { + expect( + determineAllPossibilitiesForTabCompletion(Player, `${command} `, 0), + ).toEqual(["www/"]); + } + }); + + it("completes commands starting with ./", () => { + Player.getHomeComputer().writeToScriptFile("/www/script.js", "oh hai mark"); + expect( + determineAllPossibilitiesForTabCompletion(Player, "run ./", 0), + ).toEqual([".//www/script.js", "NUKE.exe", "./www/"]); + }); }); diff --git a/tsconfig.json b/tsconfig.json index b22593fab..f0db6e4d3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,13 @@ { - "compilerOptions": { - "baseUrl" : ".", - "esModuleInterop": true, - "jsx": "react", - "lib" : ["es2016", "dom", "es2017.object", "es2019"], - "module": "commonjs", - "target": "es6", - "sourceMap": true, - "strict": true - }, - "exclude": [ - "node_modules" - ] + "compilerOptions": { + "baseUrl": ".", + "esModuleInterop": true, + "jsx": "react", + "lib": ["es2016", "dom", "es2017.object", "es2019"], + "module": "commonjs", + "target": "es6", + "sourceMap": true, + "strict": true + }, + "exclude": ["node_modules"] } diff --git a/utils/DialogBox.tsx b/utils/DialogBox.tsx index 2e9800be4..8217f0c78 100644 --- a/utils/DialogBox.tsx +++ b/utils/DialogBox.tsx @@ -4,32 +4,45 @@ import { getRandomInt } from "./helpers/getRandomInt"; import React from "react"; interface IProps { - content: JSX.Element; + content: JSX.Element; } export function MessagePopup(props: IProps): React.ReactElement { - return (<>{props.content}); + return <>{props.content}; } -function dialogBoxCreate(txt: string | JSX.Element, preformatted = false): void { - const popupId = `popup-`+(Array.from(Array(16))).map(() => `${getRandomInt(0, 9)}`).join(''); - if (typeof txt === 'string') { - if (preformatted) { - // For text files as they are often computed data that - // shouldn't be wrapped and should retain tabstops. - createPopup(popupId, MessagePopup, { - content: (
        ),
        -            });
        -        } else {
        -            createPopup(popupId, MessagePopup, {
        -                content: (

        ') }} />), - }); - } +function dialogBoxCreate( + txt: string | JSX.Element, + preformatted = false, +): void { + const popupId = + `popup-` + + Array.from(Array(16)) + .map(() => `${getRandomInt(0, 9)}`) + .join(""); + if (typeof txt === "string") { + if (preformatted) { + // For text files as they are often computed data that + // shouldn't be wrapped and should retain tabstops. + createPopup(popupId, MessagePopup, { + content:

        ,
        +      });
             } else {
        -        createPopup(popupId, MessagePopup, {
        -            content: txt,
        -        });
        +      createPopup(popupId, MessagePopup, {
        +        content: (
        +          

        "), + }} + /> + ), + }); } + } else { + createPopup(popupId, MessagePopup, { + content: txt, + }); + } } -export {dialogBoxCreate}; +export { dialogBoxCreate }; diff --git a/utils/FactionInvitationBox.js b/utils/FactionInvitationBox.js index 2d0a9b975..7ddf716b2 100644 --- a/utils/FactionInvitationBox.js +++ b/utils/FactionInvitationBox.js @@ -1,57 +1,65 @@ -import {joinFaction} from "../src/Faction/FactionHelpers"; -import {Engine} from "../src/engine"; -import {Player} from "../src/Player"; -import {clearEventListeners} from "./uiHelpers/clearEventListeners"; -import {Page, routing} from "../src/ui/navigationTracking"; +import { joinFaction } from "../src/Faction/FactionHelpers"; +import { Engine } from "../src/engine"; +import { Player } from "../src/Player"; +import { clearEventListeners } from "./uiHelpers/clearEventListeners"; +import { Page, routing } from "../src/ui/navigationTracking"; /* Faction Invitation Pop-up box */ function factionInvitationBoxClose() { - var factionInvitationBox = document.getElementById("faction-invitation-box-container"); - factionInvitationBox.style.display = "none"; + var factionInvitationBox = document.getElementById( + "faction-invitation-box-container", + ); + factionInvitationBox.style.display = "none"; } function factionInvitationBoxOpen() { - var factionInvitationBox = document.getElementById("faction-invitation-box-container"); - factionInvitationBox.style.display = "flex"; + var factionInvitationBox = document.getElementById( + "faction-invitation-box-container", + ); + factionInvitationBox.style.display = "flex"; } function factionInvitationSetText(txt) { - var textBox = document.getElementById("faction-invitation-box-text"); - textBox.innerHTML = txt; + var textBox = document.getElementById("faction-invitation-box-text"); + textBox.innerHTML = txt; } //ram argument is in GB function factionInvitationBoxCreate(faction) { - factionInvitationSetText("You have received a faction invitation from " + faction.name); - faction.alreadyInvited = true; - Player.factionInvitations.push(faction.name); + factionInvitationSetText( + "You have received a faction invitation from " + faction.name, + ); + faction.alreadyInvited = true; + Player.factionInvitations.push(faction.name); - if (routing.isOn(Page.Factions)) { - Engine.loadFactionsContent(); + if (routing.isOn(Page.Factions)) { + Engine.loadFactionsContent(); + } + + var newYesButton = clearEventListeners("faction-invitation-box-yes"); + newYesButton.addEventListener("click", function () { + //Remove from invited factions + var i = Player.factionInvitations.findIndex((facName) => { + return facName === faction.name; + }); + if (i === -1) { + console.error("Could not find faction in Player.factionInvitations"); } + joinFaction(faction); + factionInvitationBoxClose(); + if (routing.isOn(Page.Factions)) { + Engine.loadFactionsContent(); + } + return false; + }); - var newYesButton = clearEventListeners("faction-invitation-box-yes"); - newYesButton.addEventListener("click", function() { - //Remove from invited factions - var i = Player.factionInvitations.findIndex((facName)=>{return facName === faction.name}); - if (i === -1) { - console.error("Could not find faction in Player.factionInvitations"); - } - joinFaction(faction); - factionInvitationBoxClose(); - if (routing.isOn(Page.Factions)) { - Engine.loadFactionsContent(); - } - return false; - }); + var noButton = clearEventListeners("faction-invitation-box-no"); + noButton.addEventListener("click", function () { + factionInvitationBoxClose(); + return false; + }); - var noButton = clearEventListeners("faction-invitation-box-no"); - noButton.addEventListener("click", function() { - factionInvitationBoxClose(); - return false; - }); - - factionInvitationBoxOpen(); + factionInvitationBoxOpen(); } -export {factionInvitationBoxCreate}; +export { factionInvitationBoxCreate }; diff --git a/utils/GameOptions.js b/utils/GameOptions.js index 47110eb2d..e9cdd4614 100644 --- a/utils/GameOptions.js +++ b/utils/GameOptions.js @@ -2,50 +2,53 @@ import { Player } from "../src/Player"; //Close box when clicking outside -$(document).click(function(event) { - if (gameOptionsOpened) { - if ( $(event.target).closest(".game-options-box").get(0) == null ) { - gameOptionsBoxClose(); - } +$(document).click(function (event) { + if (gameOptionsOpened) { + if ($(event.target).closest(".game-options-box").get(0) == null) { + gameOptionsBoxClose(); } + } }); var gameOptionsOpened = false; function gameOptionsBoxInit() { - //Menu link button - document.getElementById("options-menu-link").addEventListener("click", function() { - gameOptionsBoxOpen(); - return false; + //Menu link button + document + .getElementById("options-menu-link") + .addEventListener("click", function () { + gameOptionsBoxOpen(); + return false; }); - //Close button - var closeButton = document.getElementById("game-options-close-button"); - closeButton.addEventListener("click", function() { - gameOptionsBoxClose(); - return false; - }); + //Close button + var closeButton = document.getElementById("game-options-close-button"); + closeButton.addEventListener("click", function () { + gameOptionsBoxClose(); + return false; + }); } document.addEventListener("DOMContentLoaded", gameOptionsBoxInit, false); function gameOptionsBoxClose() { - gameOptionsOpened = false; - var box = document.getElementById("game-options-container"); - box.style.display = "none"; + gameOptionsOpened = false; + var box = document.getElementById("game-options-container"); + box.style.display = "none"; } function gameOptionsBoxOpen() { - var box = document.getElementById("game-options-container"); - box.style.display = "flex"; - - // special exception for bladeburner popup because it's only visible later. - document.getElementById("settingsSuppressBladeburnerPopup") - .closest('fieldset').style.display = - Player.canAccessBladeburner() ? 'block' : 'none'; - setTimeout(function() { - gameOptionsOpened = true; - }, 500); + var box = document.getElementById("game-options-container"); + box.style.display = "flex"; + // special exception for bladeburner popup because it's only visible later. + document + .getElementById("settingsSuppressBladeburnerPopup") + .closest("fieldset").style.display = Player.canAccessBladeburner() + ? "block" + : "none"; + setTimeout(function () { + gameOptionsOpened = true; + }, 500); } -export {gameOptionsBoxOpen, gameOptionsBoxClose}; +export { gameOptionsBoxOpen, gameOptionsBoxClose }; diff --git a/utils/IPAddress.ts b/utils/IPAddress.ts index 241239fff..45df5d4bb 100644 --- a/utils/IPAddress.ts +++ b/utils/IPAddress.ts @@ -1,14 +1,18 @@ -import { getRandomByte } from "./helpers/getRandomByte"; +import { getRandomByte } from "./helpers/getRandomByte"; /** * Generate a random IP address * Does not check to see if the IP already exists in the game */ export function createRandomIp(): string { - const ip: string = getRandomByte(99) + '.' + - getRandomByte(9) + '.' + - getRandomByte(9) + '.' + - getRandomByte(9); + const ip: string = + getRandomByte(99) + + "." + + getRandomByte(9) + + "." + + getRandomByte(9) + + "." + + getRandomByte(9); - return ip; + return ip; } diff --git a/utils/JSONReviver.d.ts b/utils/JSONReviver.d.ts index 237eb0364..192e177ba 100644 --- a/utils/JSONReviver.d.ts +++ b/utils/JSONReviver.d.ts @@ -1,10 +1,14 @@ interface IReviverValue { - ctor: string; - data: any; + ctor: string; + data: any; } export function Generic_fromJSON(ctor: new () => T, data: any): T; -export function Generic_toJSON(ctorName: string, obj: any, keys?: string[]): string; +export function Generic_toJSON( + ctorName: string, + obj: any, + keys?: string[], +): string; export function Reviver(key, value: IReviverValue); export namespace Reviver { - export let constructors: any; + export let constructors: any; } diff --git a/utils/JSONReviver.js b/utils/JSONReviver.js index b23656b86..c06823ade 100644 --- a/utils/JSONReviver.js +++ b/utils/JSONReviver.js @@ -6,31 +6,33 @@ // constructor that has a `fromJSON` property on it, it hands // off to that `fromJSON` fuunction, passing in the value. function Reviver(key, value) { - var ctor; - if (value == null) { - console.log("Reviver WRONGLY called with key: " + key + ", and value: " + value); - return 0; + var ctor; + if (value == null) { + console.log( + "Reviver WRONGLY called with key: " + key + ", and value: " + value, + ); + return 0; + } + + if ( + typeof value === "object" && + typeof value.ctor === "string" && + typeof value.data !== "undefined" + ) { + // Compatibility for version v0.43.1 + // TODO Remove this eventually + if (value.ctor === "AllServersMap") { + console.log("Converting AllServersMap for v0.43.1"); + return value.data; } - if (typeof value === "object" && - typeof value.ctor === "string" && - typeof value.data !== "undefined") { - // Compatibility for version v0.43.1 - // TODO Remove this eventually - if (value.ctor === "AllServersMap") { - console.log('Converting AllServersMap for v0.43.1'); - return value.data; - } + ctor = Reviver.constructors[value.ctor] || window[value.ctor]; - ctor = Reviver.constructors[value.ctor] || window[value.ctor]; - - if (typeof ctor === "function" && - typeof ctor.fromJSON === "function") { - - return ctor.fromJSON(value); - } - } - return value; + if (typeof ctor === "function" && typeof ctor.fromJSON === "function") { + return ctor.fromJSON(value); + } + } + return value; } Reviver.constructors = {}; // A list of constructors the smart reviver should know about @@ -57,7 +59,7 @@ function Generic_toJSON(ctorName, obj, keys) { key = keys[index]; data[key] = obj[key]; } - return {ctor: ctorName, data: data}; + return { ctor: ctorName, data: data }; } // A generic "fromJSON" function for use with Reviver: Just calls the @@ -77,4 +79,4 @@ function Generic_fromJSON(ctor, data) { return obj; } -export {Reviver, Generic_toJSON, Generic_fromJSON}; +export { Reviver, Generic_toJSON, Generic_fromJSON }; diff --git a/utils/LogBox.tsx b/utils/LogBox.tsx index 2108d8fd0..01ce45838 100644 --- a/utils/LogBox.tsx +++ b/utils/LogBox.tsx @@ -8,112 +8,135 @@ import { removeElementById } from "./uiHelpers/removeElementById"; let gameContainer: HTMLElement; -(function() { - function getGameContainer(): void { - const container = document.getElementById("entire-game-container"); - if (container == null) { - throw new Error(`Failed to find game container DOM element`) - } - - gameContainer = container; - document.removeEventListener("DOMContentLoaded", getGameContainer); +(function () { + function getGameContainer(): void { + const container = document.getElementById("entire-game-container"); + if (container == null) { + throw new Error(`Failed to find game container DOM element`); } - document.addEventListener("DOMContentLoaded", getGameContainer); + gameContainer = container; + document.removeEventListener("DOMContentLoaded", getGameContainer); + } + + document.addEventListener("DOMContentLoaded", getGameContainer); })(); interface IProps { - script: RunningScript; - container: HTMLElement; - id: string; + script: RunningScript; + container: HTMLElement; + id: string; } function ScriptLogPopup(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; + const setRerender = useState(false)[1]; - function rerender(): void { - setRerender(old => !old); - } + function rerender(): void { + setRerender((old) => !old); + } - useEffect(() => { - const id = setInterval(rerender, 1000); - return () => clearInterval(id); - }, []); + useEffect(() => { + const id = setInterval(rerender, 1000); + return () => clearInterval(id); + }, []); + function close(): void { + const content = document.getElementById(props.id); + if (content == null) return; + ReactDOM.unmountComponentAtNode(content); + removeElementById(props.id); + } - function close(): void { - const content = document.getElementById(props.id); - if (content == null) return; - ReactDOM.unmountComponentAtNode(content); - removeElementById(props.id); - } - - useEffect(() => { - function closeHandler(event: KeyboardEvent) { - if(event.keyCode === 27) { - close(); - } - } - - document.addEventListener('keydown', closeHandler); - - return () => { - document.removeEventListener('keydown', closeHandler); - } - }, []); - - function kill(): void { - killWorkerScript(props.script, props.script.server, true); + useEffect(() => { + function closeHandler(event: KeyboardEvent) { + if (event.keyCode === 27) { close(); + } } - function drag(event: React.MouseEvent): void { - event.preventDefault(); - let x = event.clientX; - let y = event.clientY; - let left = props.container.offsetLeft+props.container.clientWidth/2; - let top = props.container.offsetTop+props.container.clientWidth/5; - function mouseMove(event: MouseEvent): void { - left+=event.clientX-x; - top+=event.clientY-y; - props.container.style.left=left+'px'; - props.container.style.top=top+'px'; - // reset right and bottom to avoid the window stretching - props.container.style.right=''; - props.container.style.bottom=''; - x=event.clientX; - y=event.clientY; - } - function mouseUp(): void { - document.removeEventListener('mouseup', mouseUp) - document.removeEventListener('mousemove', mouseMove) - } - document.addEventListener('mouseup', mouseUp) - document.addEventListener('mousemove', mouseMove) - } + document.addEventListener("keydown", closeHandler); - return (<> -

        -

        {props.script.filename} {props.script.args.map((x: any): string => `${x}`).join(' ')}

        -
        - - -
        + return () => { + document.removeEventListener("keydown", closeHandler); + }; + }, []); + + function kill(): void { + killWorkerScript(props.script, props.script.server, true); + close(); + } + + function drag(event: React.MouseEvent): void { + event.preventDefault(); + let x = event.clientX; + let y = event.clientY; + let left = props.container.offsetLeft + props.container.clientWidth / 2; + let top = props.container.offsetTop + props.container.clientWidth / 5; + function mouseMove(event: MouseEvent): void { + left += event.clientX - x; + top += event.clientY - y; + props.container.style.left = left + "px"; + props.container.style.top = top + "px"; + // reset right and bottom to avoid the window stretching + props.container.style.right = ""; + props.container.style.bottom = ""; + x = event.clientX; + y = event.clientY; + } + function mouseUp(): void { + document.removeEventListener("mouseup", mouseUp); + document.removeEventListener("mousemove", mouseMove); + } + document.addEventListener("mouseup", mouseUp); + document.addEventListener("mousemove", mouseMove); + } + + return ( + <> +
        +

        + {props.script.filename}{" "} + {props.script.args.map((x: any): string => `${x}`).join(" ")} +

        +
        + +
        -
        -

        {props.script.logs.map((line: string, i: number): JSX.Element => {line}
        )}

        -
        - ); +
        +
        +

        + {props.script.logs.map( + (line: string, i: number): JSX.Element => ( + + {line} +
        +
        + ), + )} +

        +
        + + ); } export function logBoxCreate(script: RunningScript): void { - const id = script.server+"-"+script.filename+script.args.map((x: any): string => `${x}`).join('-'); - if(document.getElementById(id) !== null) return; - const container = createElement("div", { - class: "log-box-container", - id: id, - }); - gameContainer.appendChild(container); - ReactDOM.render(, container); + const id = + script.server + + "-" + + script.filename + + script.args.map((x: any): string => `${x}`).join("-"); + if (document.getElementById(id) !== null) return; + const container = createElement("div", { + class: "log-box-container", + id: id, + }); + gameContainer.appendChild(container); + ReactDOM.render( + , + container, + ); } - diff --git a/utils/StringHelperFunctions.ts b/utils/StringHelperFunctions.ts index 4eb88058a..476c54ae9 100644 --- a/utils/StringHelperFunctions.ts +++ b/utils/StringHelperFunctions.ts @@ -5,7 +5,9 @@ import { isString } from "./helpers/isString"; // Replaces the character at an index with a new character function replaceAt(base: string, index: number, character: string): string { - return base.substr(0, index) + character + base.substr(index + character.length); + return ( + base.substr(0, index) + character + base.substr(index + character.length) + ); } /* @@ -13,102 +15,126 @@ Converts a date representing time in milliseconds to a string with the format H e.g. 10000 -> "10 seconds" 120000 -> "2 minutes and 0 seconds" */ -function convertTimeMsToTimeElapsedString(time: number, showMilli=false): string { - time = Math.floor(time); - const millisecondsPerSecond = 1000; - const secondPerMinute = 60; - const minutesPerHours = 60; - const secondPerHours: number = secondPerMinute * minutesPerHours; - const hoursPerDays = 24; - const secondPerDay: number = secondPerHours * hoursPerDays; +function convertTimeMsToTimeElapsedString( + time: number, + showMilli = false, +): string { + time = Math.floor(time); + const millisecondsPerSecond = 1000; + const secondPerMinute = 60; + const minutesPerHours = 60; + const secondPerHours: number = secondPerMinute * minutesPerHours; + const hoursPerDays = 24; + const secondPerDay: number = secondPerHours * hoursPerDays; - // Convert ms to seconds, since we only have second-level precision - const totalSeconds: number = Math.floor(time / millisecondsPerSecond); + // Convert ms to seconds, since we only have second-level precision + const totalSeconds: number = Math.floor(time / millisecondsPerSecond); - const days: number = Math.floor(totalSeconds / secondPerDay); - const secTruncDays: number = totalSeconds % secondPerDay; + const days: number = Math.floor(totalSeconds / secondPerDay); + const secTruncDays: number = totalSeconds % secondPerDay; - const hours: number = Math.floor(secTruncDays / secondPerHours); - const secTruncHours: number = secTruncDays % secondPerHours; + const hours: number = Math.floor(secTruncDays / secondPerHours); + const secTruncHours: number = secTruncDays % secondPerHours; - const minutes: number = Math.floor(secTruncHours / secondPerMinute); - const secTruncMinutes: number = secTruncHours % secondPerMinute; + const minutes: number = Math.floor(secTruncHours / secondPerMinute); + const secTruncMinutes: number = secTruncHours % secondPerMinute; - const milliTruncSec: string = (() => { - let str = `${time % millisecondsPerSecond}`; - while(str.length < 3) str = "0"+str; - return str; - })() + const milliTruncSec: string = (() => { + let str = `${time % millisecondsPerSecond}`; + while (str.length < 3) str = "0" + str; + return str; + })(); - const seconds: string = showMilli ? `${secTruncMinutes}.${milliTruncSec}` : `${secTruncMinutes}`; + const seconds: string = showMilli + ? `${secTruncMinutes}.${milliTruncSec}` + : `${secTruncMinutes}`; - let res = ""; - if (days > 0) {res += `${days} days `; } - if (hours > 0) {res += `${hours} hours `; } - if (minutes > 0) {res += `${minutes} minutes `; } - res += `${seconds} seconds`; + let res = ""; + if (days > 0) { + res += `${days} days `; + } + if (hours > 0) { + res += `${hours} hours `; + } + if (minutes > 0) { + res += `${minutes} minutes `; + } + res += `${seconds} seconds`; - return res; + return res; } // Finds the longest common starting substring in a set of strings function longestCommonStart(strings: string[]): string { - if (!containsAllStrings(strings)) {return ""; } - if (strings.length === 0) {return ""; } + if (!containsAllStrings(strings)) { + return ""; + } + if (strings.length === 0) { + return ""; + } - const A: string[] = strings.concat() - .sort(); - const a1: string = A[0]; - const a2: string = A[A.length - 1]; - const L: number = a1.length; - let i = 0; - const areEqualCaseInsensitive: EqualityFunc = (a: string, b: string) => a.toUpperCase() === b.toUpperCase(); - while (i < L && areEqualCaseInsensitive(a1.charAt(i), a2.charAt(i))) { - i++; - } + const A: string[] = strings.concat().sort(); + const a1: string = A[0]; + const a2: string = A[A.length - 1]; + const L: number = a1.length; + let i = 0; + const areEqualCaseInsensitive: EqualityFunc = ( + a: string, + b: string, + ) => a.toUpperCase() === b.toUpperCase(); + while (i < L && areEqualCaseInsensitive(a1.charAt(i), a2.charAt(i))) { + i++; + } - return a1.substring(0, i); + return a1.substring(0, i); } // Returns whether an array contains entirely of string objects function containsAllStrings(arr: string[]): boolean { - return arr.every(isString); + return arr.every(isString); } // Formats a number with commas and a specific number of decimal digits function formatNumber(num: number, numFractionDigits = 0): string { - return num.toLocaleString(undefined, { - maximumFractionDigits: numFractionDigits, - minimumFractionDigits: numFractionDigits, - }); + return num.toLocaleString(undefined, { + maximumFractionDigits: numFractionDigits, + minimumFractionDigits: numFractionDigits, + }); } // Checks if a string contains HTML elements function isHTML(str: string): boolean { - const element: HTMLDivElement = document.createElement("div"); - element.innerHTML = str; - const c: NodeListOf = element.childNodes; - for (let i: number = c.length - 1; i >= 0; i--) { - if (c[i].nodeType === 1) { - return true; - } + const element: HTMLDivElement = document.createElement("div"); + element.innerHTML = str; + const c: NodeListOf = element.childNodes; + for (let i: number = c.length - 1; i >= 0; i--) { + if (c[i].nodeType === 1) { + return true; } + } - return false; + return false; } // Generates a random alphanumeric string with N characters function generateRandomString(n: number): string { - let str = ""; - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let str = ""; + const chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (let i = 0; i < n; i++) { - str += chars.charAt(Math.floor(Math.random() * chars.length)); - } + for (let i = 0; i < n; i++) { + str += chars.charAt(Math.floor(Math.random() * chars.length)); + } - return str; + return str; } -export {convertTimeMsToTimeElapsedString, longestCommonStart, - containsAllStrings, formatNumber, - isHTML, generateRandomString, replaceAt}; +export { + convertTimeMsToTimeElapsedString, + longestCommonStart, + containsAllStrings, + formatNumber, + isHTML, + generateRandomString, + replaceAt, +}; diff --git a/utils/YesNoBox.ts b/utils/YesNoBox.ts index bbac42b1e..31aa67e7c 100644 --- a/utils/YesNoBox.ts +++ b/utils/YesNoBox.ts @@ -12,154 +12,174 @@ import * as ReactDOM from "react-dom"; export let yesNoBoxOpen = false; -const yesNoBoxContainer: HTMLElement | null = document.getElementById("yes-no-box-container"); -const yesNoBoxTextElement: HTMLElement | null = document.getElementById("yes-no-box-text"); +const yesNoBoxContainer: HTMLElement | null = document.getElementById( + "yes-no-box-container", +); +const yesNoBoxTextElement: HTMLElement | null = + document.getElementById("yes-no-box-text"); function yesNoBoxHotkeyHandler(e: KeyboardEvent): void { - if (e.keyCode === KEY.ESC) { - yesNoBoxClose(); - } else if (e.keyCode === KEY.ENTER) { - const yesBtn: HTMLElement | null = document.getElementById("yes-no-box-yes"); - if (yesBtn) { - yesBtn.click(); - } else { - console.error(`Could not find YesNoBox Yes button DOM element`) - } + if (e.keyCode === KEY.ESC) { + yesNoBoxClose(); + } else if (e.keyCode === KEY.ENTER) { + const yesBtn: HTMLElement | null = + document.getElementById("yes-no-box-yes"); + if (yesBtn) { + yesBtn.click(); + } else { + console.error(`Could not find YesNoBox Yes button DOM element`); } + } } export function yesNoBoxClose(): boolean { - if (yesNoBoxContainer) { - yesNoBoxContainer.style.display = "none"; - } else { - console.error("Container not found for YesNoBox"); - } - yesNoBoxOpen = false; + if (yesNoBoxContainer) { + yesNoBoxContainer.style.display = "none"; + } else { + console.error("Container not found for YesNoBox"); + } + yesNoBoxOpen = false; - // Remove hotkey handler - document.removeEventListener("keydown", yesNoBoxHotkeyHandler); + // Remove hotkey handler + document.removeEventListener("keydown", yesNoBoxHotkeyHandler); - return false; //So that 'return yesNoBoxClose()' is return false in event listeners + return false; //So that 'return yesNoBoxClose()' is return false in event listeners } export function yesNoBoxGetYesButton(): HTMLElement | null { - return clearEventListeners("yes-no-box-yes"); + return clearEventListeners("yes-no-box-yes"); } export function yesNoBoxGetNoButton(): HTMLElement | null { - return clearEventListeners("yes-no-box-no"); + return clearEventListeners("yes-no-box-no"); } export function yesNoBoxCreate(txt: string | JSX.Element): boolean { - if (yesNoBoxOpen) { return false; } //Already open - yesNoBoxOpen = true; + if (yesNoBoxOpen) { + return false; + } //Already open + yesNoBoxOpen = true; - if (yesNoBoxTextElement) { - ReactDOM.unmountComponentAtNode(yesNoBoxTextElement); - yesNoBoxTextElement.innerHTML = ''; - if(typeof txt === 'string') { - yesNoBoxTextElement.innerHTML = txt as string; - } else { - ReactDOM.render(txt, yesNoBoxTextElement); - } + if (yesNoBoxTextElement) { + ReactDOM.unmountComponentAtNode(yesNoBoxTextElement); + yesNoBoxTextElement.innerHTML = ""; + if (typeof txt === "string") { + yesNoBoxTextElement.innerHTML = txt as string; } else { - console.error(`Text element not found for YesNoBox`); + ReactDOM.render(txt, yesNoBoxTextElement); } + } else { + console.error(`Text element not found for YesNoBox`); + } - if (yesNoBoxContainer) { - yesNoBoxContainer.style.display = "flex"; - } else { - console.error("Container not found for YesNoBox"); - } + if (yesNoBoxContainer) { + yesNoBoxContainer.style.display = "flex"; + } else { + console.error("Container not found for YesNoBox"); + } - // Add event listener for Esc and Enter hotkeys - document.addEventListener("keydown", yesNoBoxHotkeyHandler); + // Add event listener for Esc and Enter hotkeys + document.addEventListener("keydown", yesNoBoxHotkeyHandler); - return true; + return true; } /** * Yes-No pop up box with text input field */ -const yesNoTextInputBoxContainer: HTMLElement | null = document.getElementById("yes-no-text-input-box-container"); -const yesNoTextInputBoxInput: HTMLInputElement | null = document.getElementById("yes-no-text-input-box-input") as HTMLInputElement; -const yesNoTextInputBoxTextElement: HTMLElement | null = document.getElementById("yes-no-text-input-box-text"); +const yesNoTextInputBoxContainer: HTMLElement | null = document.getElementById( + "yes-no-text-input-box-container", +); +const yesNoTextInputBoxInput: HTMLInputElement | null = document.getElementById( + "yes-no-text-input-box-input", +) as HTMLInputElement; +const yesNoTextInputBoxTextElement: HTMLElement | null = + document.getElementById("yes-no-text-input-box-text"); export function yesNoTxtInpBoxHotkeyHandler(e: KeyboardEvent): void { - if (e.keyCode === KEY.ESC) { - yesNoTxtInpBoxClose(); - } else if (e.keyCode === KEY.ENTER) { - const yesBtn: HTMLElement | null = document.getElementById("yes-no-text-input-box-yes"); - if (yesBtn) { - yesBtn.click(); - } else { - console.error(`Could not find YesNoTxtInputBox Yes button DOM element`) - } + if (e.keyCode === KEY.ESC) { + yesNoTxtInpBoxClose(); + } else if (e.keyCode === KEY.ENTER) { + const yesBtn: HTMLElement | null = document.getElementById( + "yes-no-text-input-box-yes", + ); + if (yesBtn) { + yesBtn.click(); + } else { + console.error(`Could not find YesNoTxtInputBox Yes button DOM element`); } + } } export function yesNoTxtInpBoxClose(): boolean { - if (yesNoTextInputBoxContainer != null) { - yesNoTextInputBoxContainer.style.display = "none"; - } else { - console.error("Container not found for YesNoTextInputBox"); - return false; - } - if(!yesNoTextInputBoxInput) throw new Error("yesNoTextInputBoxInput was not set"); - yesNoBoxOpen = false; - yesNoTextInputBoxInput.value = ""; - - // Remove hotkey handler - document.removeEventListener("keydown", yesNoTxtInpBoxHotkeyHandler); - + if (yesNoTextInputBoxContainer != null) { + yesNoTextInputBoxContainer.style.display = "none"; + } else { + console.error("Container not found for YesNoTextInputBox"); return false; + } + if (!yesNoTextInputBoxInput) + throw new Error("yesNoTextInputBoxInput was not set"); + yesNoBoxOpen = false; + yesNoTextInputBoxInput.value = ""; + + // Remove hotkey handler + document.removeEventListener("keydown", yesNoTxtInpBoxHotkeyHandler); + + return false; } export function yesNoTxtInpBoxGetYesButton(): HTMLElement { - const elem = clearEventListeners("yes-no-text-input-box-yes"); - if(elem === null) throw new Error("Could not find element with id: 'yes-no-text-input-box-yes'"); - return elem; + const elem = clearEventListeners("yes-no-text-input-box-yes"); + if (elem === null) + throw new Error( + "Could not find element with id: 'yes-no-text-input-box-yes'", + ); + return elem; } export function yesNoTxtInpBoxGetNoButton(): HTMLElement { - const elem = clearEventListeners("yes-no-text-input-box-no"); - if(elem === null) throw new Error("Could not find element with id: 'yes-no-text-input-box-no'"); - return elem; + const elem = clearEventListeners("yes-no-text-input-box-no"); + if (elem === null) + throw new Error( + "Could not find element with id: 'yes-no-text-input-box-no'", + ); + return elem; } export function yesNoTxtInpBoxGetInput(): string { - if (!yesNoTextInputBoxInput) { - console.error("Could not find YesNoTextInputBox input element"); - return ""; - } - let val: string = yesNoTextInputBoxInput.value; - val = val.replace(/\s+/g, ''); - return val; + if (!yesNoTextInputBoxInput) { + console.error("Could not find YesNoTextInputBox input element"); + return ""; + } + let val: string = yesNoTextInputBoxInput.value; + val = val.replace(/\s+/g, ""); + return val; } export function yesNoTxtInpBoxCreate(txt: string | JSX.Element): void { - yesNoBoxOpen = true; + yesNoBoxOpen = true; - - if (yesNoTextInputBoxTextElement) { - ReactDOM.unmountComponentAtNode(yesNoTextInputBoxTextElement); - yesNoTextInputBoxTextElement.innerHTML = ''; - if(typeof txt === 'string') { - yesNoTextInputBoxTextElement.innerHTML = txt; - } else { - ReactDOM.render(txt, yesNoTextInputBoxTextElement); - } - } - - if (yesNoTextInputBoxContainer) { - yesNoTextInputBoxContainer.style.display = "flex"; + if (yesNoTextInputBoxTextElement) { + ReactDOM.unmountComponentAtNode(yesNoTextInputBoxTextElement); + yesNoTextInputBoxTextElement.innerHTML = ""; + if (typeof txt === "string") { + yesNoTextInputBoxTextElement.innerHTML = txt; } else { - console.error("Container not found for YesNoTextInputBox"); + ReactDOM.render(txt, yesNoTextInputBoxTextElement); } + } - // Add event listener for Esc and Enter hotkeys - document.addEventListener("keydown", yesNoTxtInpBoxHotkeyHandler); + if (yesNoTextInputBoxContainer) { + yesNoTextInputBoxContainer.style.display = "flex"; + } else { + console.error("Container not found for YesNoTextInputBox"); + } - if(!yesNoTextInputBoxInput) throw new Error("yesNoTextInputBoxInput was not set"); - yesNoTextInputBoxInput.focus(); + // Add event listener for Esc and Enter hotkeys + document.addEventListener("keydown", yesNoTxtInpBoxHotkeyHandler); + + if (!yesNoTextInputBoxInput) + throw new Error("yesNoTextInputBoxInput was not set"); + yesNoTextInputBoxInput.focus(); } diff --git a/utils/helpers/addOffset.ts b/utils/helpers/addOffset.ts index 2463a8aff..a379fa13d 100644 --- a/utils/helpers/addOffset.ts +++ b/utils/helpers/addOffset.ts @@ -10,14 +10,14 @@ * @param percentage The percentage (in a range of 0-100) to offset */ export function addOffset(midpoint: number, percentage: number): number { - const maxPercent = 100; - if (percentage < 0 || percentage > maxPercent) { - return midpoint; - } + const maxPercent = 100; + if (percentage < 0 || percentage > maxPercent) { + return midpoint; + } - const offset: number = midpoint * (percentage / maxPercent); + const offset: number = midpoint * (percentage / maxPercent); - // Double the range to account for both sides of the midpoint. - // tslint:disable-next-line:no-magic-numbers - return midpoint + ((Math.random() * (offset * 2)) - offset); + // Double the range to account for both sides of the midpoint. + // tslint:disable-next-line:no-magic-numbers + return midpoint + (Math.random() * (offset * 2) - offset); } diff --git a/utils/helpers/arrayToString.ts b/utils/helpers/arrayToString.ts index f301c6563..405adb216 100644 --- a/utils/helpers/arrayToString.ts +++ b/utils/helpers/arrayToString.ts @@ -6,16 +6,16 @@ * - Adds quotation marks around strings */ export function arrayToString(a: T[]): string { - const vals: any[] = []; - for (let i = 0; i < a.length; ++i) { - let elem: any = a[i]; - if (Array.isArray(elem)) { - elem = arrayToString(elem); - } else if (typeof elem === "string") { - elem = `"${elem}"`; - } - vals.push(elem); + const vals: any[] = []; + for (let i = 0; i < a.length; ++i) { + let elem: any = a[i]; + if (Array.isArray(elem)) { + elem = arrayToString(elem); + } else if (typeof elem === "string") { + elem = `"${elem}"`; } + vals.push(elem); + } - return `[${vals.join(", ")}]`; + return `[${vals.join(", ")}]`; } diff --git a/utils/helpers/clearObject.ts b/utils/helpers/clearObject.ts index 57a92b6f4..e3184d901 100644 --- a/utils/helpers/clearObject.ts +++ b/utils/helpers/clearObject.ts @@ -4,12 +4,12 @@ * @deprecated Look into using `Map` or `Set` rather than manipulating properties on an Object. * @param obj the object to clear all properties */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function clearObject(obj: any): void { - for (const key in obj) { - if (obj.hasOwnProperty(key)) { - // tslint:disable-next-line:no-dynamic-delete - delete obj[key]; - } + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + // tslint:disable-next-line:no-dynamic-delete + delete obj[key]; } + } } diff --git a/utils/helpers/compareArrays.ts b/utils/helpers/compareArrays.ts index 8b88caf13..b9b016b31 100644 --- a/utils/helpers/compareArrays.ts +++ b/utils/helpers/compareArrays.ts @@ -1,30 +1,29 @@ - /** * Does a shallow compare of two arrays to determine if they are equal. * @param a1 The first array * @param a2 The second array */ export function compareArrays(a1: T[], a2: T[]): boolean { - if (a1.length !== a2.length) { + if (a1.length !== a2.length) { + return false; + } + + for (let i = 0; i < a1.length; ++i) { + if (Array.isArray(a1[i])) { + // If the other element is not an array, then these cannot be equal + if (!Array.isArray(a2[i])) { return false; + } + + const elem1 = (a1[i]); + const elem2 = (a2[i]); + if (!compareArrays(elem1, elem2)) { + return false; + } + } else if (a1[i] !== a2[i]) { + return false; } + } - for (let i = 0; i < a1.length; ++i) { - if (Array.isArray(a1[i])) { - // If the other element is not an array, then these cannot be equal - if (!Array.isArray(a2[i])) { - return false; - } - - const elem1 = a1[i]; - const elem2 = a2[i]; - if (!compareArrays(elem1, elem2)) { - return false; - } - } else if (a1[i] !== a2[i]) { - return false; - } - } - - return true; + return true; } diff --git a/utils/helpers/createProgressBarText.ts b/utils/helpers/createProgressBarText.ts index 2107de5eb..56bc5d585 100644 --- a/utils/helpers/createProgressBarText.ts +++ b/utils/helpers/createProgressBarText.ts @@ -2,23 +2,24 @@ * Represents the possible configuration values that can be provided when creating the progress bar text. */ interface IProgressBarConfiguration { - /** - * Current progress, taken as a decimal (i.e. '0.6' to represent '60%') - */ - progress?: number; + /** + * Current progress, taken as a decimal (i.e. '0.6' to represent '60%') + */ + progress?: number; - /** - * Total number of ticks in progress bar. Preferably a factor of 100. - */ - totalTicks?: number; + /** + * Total number of ticks in progress bar. Preferably a factor of 100. + */ + totalTicks?: number; } /** * Represents concrete configuration values when creating the progress bar text. */ -interface IProgressBarConfigurationMaterialized extends IProgressBarConfiguration { - progress: number; - totalTicks: number; +interface IProgressBarConfigurationMaterialized + extends IProgressBarConfiguration { + progress: number; + totalTicks: number; } /** @@ -26,22 +27,31 @@ interface IProgressBarConfigurationMaterialized extends IProgressBarConfiguratio * e.g.: [||||---------------] * @param params The configuration parameters for the progress bar */ -export function createProgressBarText(params: IProgressBarConfiguration): string { - // Default values - const defaultParams: IProgressBarConfigurationMaterialized = { - progress: 0, - totalTicks: 20, - }; +export function createProgressBarText( + params: IProgressBarConfiguration, +): string { + // Default values + const defaultParams: IProgressBarConfigurationMaterialized = { + progress: 0, + totalTicks: 20, + }; - // tslint:disable-next-line:prefer-object-spread - const derived: IProgressBarConfigurationMaterialized = Object.assign({}, defaultParams, params); - // Ensure it is 0..1 - derived.progress = Math.max(Math.min(derived.progress, 1), 0); + // tslint:disable-next-line:prefer-object-spread + const derived: IProgressBarConfigurationMaterialized = Object.assign( + {}, + defaultParams, + params, + ); + // Ensure it is 0..1 + derived.progress = Math.max(Math.min(derived.progress, 1), 0); - // This way there is always at least one bar filled in... - const bars: number = Math.max(Math.floor(derived.progress / (1 / derived.totalTicks)), 1); - const dashes: number = Math.max(derived.totalTicks - bars, 0); + // This way there is always at least one bar filled in... + const bars: number = Math.max( + Math.floor(derived.progress / (1 / derived.totalTicks)), + 1, + ); + const dashes: number = Math.max(derived.totalTicks - bars, 0); - // String.prototype.repeat isn't completley supported, but good enough for our purposes - return `[${"|".repeat(bars)}${"-".repeat(dashes)}]`; + // String.prototype.repeat isn't completley supported, but good enough for our purposes + return `[${"|".repeat(bars)}${"-".repeat(dashes)}]`; } diff --git a/utils/helpers/exceptionAlert.ts b/utils/helpers/exceptionAlert.ts index 5130b2fb5..1c8ab60a6 100644 --- a/utils/helpers/exceptionAlert.ts +++ b/utils/helpers/exceptionAlert.ts @@ -1,17 +1,26 @@ import { dialogBoxCreate } from "../DialogBox"; interface IError { - fileName?: string; - lineNumber?: number; + fileName?: string; + lineNumber?: number; } export function exceptionAlert(e: IError): void { - console.error(e); - dialogBoxCreate("Caught an exception: " + e + "

        " + - "Filename: " + (e.fileName || "UNKNOWN FILE NAME") + "

        " + - "Line Number: " + (e.lineNumber || "UNKNOWN LINE NUMBER") + "

        " + - "This is a bug, please report to game developer with this " + - "message as well as details about how to reproduce the bug.

        " + - "If you want to be safe, I suggest refreshing the game WITHOUT saving so that your " + - "safe doesn't get corrupted", false); + console.error(e); + dialogBoxCreate( + "Caught an exception: " + + e + + "

        " + + "Filename: " + + (e.fileName || "UNKNOWN FILE NAME") + + "

        " + + "Line Number: " + + (e.lineNumber || "UNKNOWN LINE NUMBER") + + "

        " + + "This is a bug, please report to game developer with this " + + "message as well as details about how to reproduce the bug.

        " + + "If you want to be safe, I suggest refreshing the game WITHOUT saving so that your " + + "safe doesn't get corrupted", + false, + ); } diff --git a/utils/helpers/getRandomByte.ts b/utils/helpers/getRandomByte.ts index 954d45d83..46d91839b 100644 --- a/utils/helpers/getRandomByte.ts +++ b/utils/helpers/getRandomByte.ts @@ -5,9 +5,9 @@ import { getRandomInt } from "./getRandomInt"; * @param max The maximum value (up to 255). */ export function getRandomByte(max: number): number { - // Technically 2^8 is 256, but the values are 0-255, not 1-256. - const byteMaximum = 255; - const upper: number = Math.max(Math.min(max, byteMaximum), 0); + // Technically 2^8 is 256, but the values are 0-255, not 1-256. + const byteMaximum = 255; + const upper: number = Math.max(Math.min(max, byteMaximum), 0); - return getRandomInt(0, upper); + return getRandomInt(0, upper); } diff --git a/utils/helpers/getRandomInt.ts b/utils/helpers/getRandomInt.ts index 1dc4956f1..23e42ed6b 100644 --- a/utils/helpers/getRandomInt.ts +++ b/utils/helpers/getRandomInt.ts @@ -4,8 +4,8 @@ * @param max The maximum value in the range. */ export function getRandomInt(min: number, max: number): number { - const lower: number = Math.min(min, max); - const upper: number = Math.max(min, max); + const lower: number = Math.min(min, max); + const upper: number = Math.max(min, max); - return Math.floor(Math.random() * (upper - lower + 1)) + lower; + return Math.floor(Math.random() * (upper - lower + 1)) + lower; } diff --git a/utils/helpers/getTimestamp.ts b/utils/helpers/getTimestamp.ts index bd523f727..fce825b96 100644 --- a/utils/helpers/getTimestamp.ts +++ b/utils/helpers/getTimestamp.ts @@ -2,11 +2,13 @@ * Returns a MM/DD HH:MM timestamp for the current time */ export function getTimestamp(): string { - const d: Date = new Date(); - // A negative slice value takes from the end of the string rather than the beginning. - const stringWidth = -2; - const formattedHours: string = `0${d.getHours()}`.slice(stringWidth); - const formattedMinutes: string = `0${d.getMinutes()}`.slice(stringWidth); + const d: Date = new Date(); + // A negative slice value takes from the end of the string rather than the beginning. + const stringWidth = -2; + const formattedHours: string = `0${d.getHours()}`.slice(stringWidth); + const formattedMinutes: string = `0${d.getMinutes()}`.slice(stringWidth); - return `${d.getMonth() + 1}/${d.getDate()} ${formattedHours}:${formattedMinutes}`; + return `${ + d.getMonth() + 1 + }/${d.getDate()} ${formattedHours}:${formattedMinutes}`; } diff --git a/utils/helpers/isPowerOfTwo.ts b/utils/helpers/isPowerOfTwo.ts index c0822c704..004620a87 100644 --- a/utils/helpers/isPowerOfTwo.ts +++ b/utils/helpers/isPowerOfTwo.ts @@ -3,15 +3,15 @@ * @param n The number to check. */ export function isPowerOfTwo(n: number): boolean { - if (isNaN(n)) { - return false; - } + if (isNaN(n)) { + return false; + } - if (n === 0) { - return false; - } + if (n === 0) { + return false; + } - // Disabiling the bitwise rule because it's honestly the most effecient way to check for this. - // tslint:disable-next-line:no-bitwise - return (n & (n - 1)) === 0; + // Disabiling the bitwise rule because it's honestly the most effecient way to check for this. + // tslint:disable-next-line:no-bitwise + return (n & (n - 1)) === 0; } diff --git a/utils/helpers/isString.ts b/utils/helpers/isString.ts index 204d42427..ec1159aca 100644 --- a/utils/helpers/isString.ts +++ b/utils/helpers/isString.ts @@ -4,5 +4,5 @@ */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export function isString(value: any): boolean { - return (typeof value === "string" || value instanceof String); + return typeof value === "string" || value instanceof String; } diff --git a/utils/helpers/isValidIPAddress.ts b/utils/helpers/isValidIPAddress.ts index 47afdbb43..6d8395c03 100644 --- a/utils/helpers/isValidIPAddress.ts +++ b/utils/helpers/isValidIPAddress.ts @@ -1,12 +1,11 @@ - /** * Checks whether a IP Address string is valid. * @param ipaddress A string representing a potential IP Address */ export function isValidIPAddress(ipaddress: string): boolean { - const byteRange = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"; - const regexStr = `^${byteRange}\.${byteRange}\.${byteRange}\.${byteRange}$`; - const ipAddressRegex = new RegExp(regexStr); + const byteRange = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"; + const regexStr = `^${byteRange}\.${byteRange}\.${byteRange}\.${byteRange}$`; + const ipAddressRegex = new RegExp(regexStr); - return ipAddressRegex.test(ipaddress); + return ipAddressRegex.test(ipaddress); } diff --git a/utils/helpers/keyCodes.ts b/utils/helpers/keyCodes.ts index 445235ea3..1c939dbc2 100644 --- a/utils/helpers/keyCodes.ts +++ b/utils/helpers/keyCodes.ts @@ -4,48 +4,48 @@ import { IMap } from "../../src/types"; * Keyboard key codes */ export const KEY: IMap = { - CTRL: 17, - DOWNARROW: 40, - ENTER: 13, - ESC: 27, - TAB: 9, - UPARROW: 38, + CTRL: 17, + DOWNARROW: 40, + ENTER: 13, + ESC: 27, + TAB: 9, + UPARROW: 38, - "0": 48, - "1": 49, - "2": 50, - "3": 51, - "4": 52, - "5": 53, - "6": 54, - "7": 55, - "8": 56, - "9": 57, + "0": 48, + "1": 49, + "2": 50, + "3": 51, + "4": 52, + "5": 53, + "6": 54, + "7": 55, + "8": 56, + "9": 57, - A: 65, - B: 66, - C: 67, - D: 68, - E: 69, - F: 70, - G: 71, - H: 72, - I: 73, - J: 74, - K: 75, - L: 76, - M: 77, - N: 78, - O: 79, - P: 80, - Q: 81, - R: 82, - S: 83, - T: 84, - U: 85, - V: 86, - W: 87, - X: 88, - Y: 89, - Z: 90, + A: 65, + B: 66, + C: 67, + D: 68, + E: 69, + F: 70, + G: 71, + H: 72, + I: 73, + J: 74, + K: 75, + L: 76, + M: 77, + N: 78, + O: 79, + P: 80, + Q: 81, + R: 82, + S: 83, + T: 84, + U: 85, + V: 86, + W: 87, + X: 88, + Y: 89, + Z: 90, }; diff --git a/utils/helpers/roundToTwo.ts b/utils/helpers/roundToTwo.ts index a7fa1c2fb..be5d9ec56 100644 --- a/utils/helpers/roundToTwo.ts +++ b/utils/helpers/roundToTwo.ts @@ -3,7 +3,7 @@ * @param decimal A decimal value to trim to two places. */ export function roundToTwo(decimal: number): number { - const leftShift: number = Math.round(parseFloat(`${decimal}e+2`)); + const leftShift: number = Math.round(parseFloat(`${decimal}e+2`)); - return +(`${leftShift}e-2`); + return +`${leftShift}e-2`; } diff --git a/utils/ui/DialogBox.tsx b/utils/ui/DialogBox.tsx index 9155ef809..17d58cc42 100644 --- a/utils/ui/DialogBox.tsx +++ b/utils/ui/DialogBox.tsx @@ -1,8 +1,10 @@ import * as React from "react"; export function DialogBox(content: HTMLElement): React.ReactElement { - return (
        - × - {content} -
        ); + return ( +
        + × + {content} +
        + ); } diff --git a/utils/uiHelpers/appendLineBreaks.ts b/utils/uiHelpers/appendLineBreaks.ts index 02ac2ca1e..bb6c70217 100644 --- a/utils/uiHelpers/appendLineBreaks.ts +++ b/utils/uiHelpers/appendLineBreaks.ts @@ -6,7 +6,7 @@ import { createElement } from "./createElement"; * @param n The number of breaks to add. */ export function appendLineBreaks(el: HTMLElement, n: number): void { - for (let i = 0; i < n; ++i) { - el.appendChild(createElement("br")); - } + for (let i = 0; i < n; ++i) { + el.appendChild(createElement("br")); + } } diff --git a/utils/uiHelpers/clearEventListeners.ts b/utils/uiHelpers/clearEventListeners.ts index f94f3be8c..c8bd8748d 100644 --- a/utils/uiHelpers/clearEventListeners.ts +++ b/utils/uiHelpers/clearEventListeners.ts @@ -5,25 +5,27 @@ import { getElementById } from "./getElementById"; * replacing. Then returns the new cloned element. * @param elemId The HTML ID to retrieve the element by. */ -export function clearEventListeners(elemId: string | HTMLElement): HTMLElement | null { - try { - let elem: HTMLElement; - if (typeof elemId === "string") { - elem = getElementById(elemId); - } else { - elem = elemId; - } - - const newElem: HTMLElement = elem.cloneNode(true) as HTMLElement; - if (elem.parentNode !== null) { - elem.parentNode.replaceChild(newElem, elem); - } - - return newElem; - } catch (e) { - // tslint:disable-next-line:no-console - console.error(e); - - return null; +export function clearEventListeners( + elemId: string | HTMLElement, +): HTMLElement | null { + try { + let elem: HTMLElement; + if (typeof elemId === "string") { + elem = getElementById(elemId); + } else { + elem = elemId; } + + const newElem: HTMLElement = elem.cloneNode(true) as HTMLElement; + if (elem.parentNode !== null) { + elem.parentNode.replaceChild(newElem, elem); + } + + return newElem; + } catch (e) { + // tslint:disable-next-line:no-console + console.error(e); + + return null; + } } diff --git a/utils/uiHelpers/clearSelector.ts b/utils/uiHelpers/clearSelector.ts index 887f72a53..c51f49129 100644 --- a/utils/uiHelpers/clearSelector.ts +++ b/utils/uiHelpers/clearSelector.ts @@ -3,7 +3,7 @@ * @param selector The ") element. */ interface ICreateElementInputOptions { - checked?: boolean; - max?: string; - maxLength?: number; - min?: string; - name?: string; - pattern?: string; - placeholder?: string; - step?: string; - type?: string; - value?: string; + checked?: boolean; + max?: string; + maxLength?: number; + min?: string; + name?: string; + pattern?: string; + placeholder?: string; + step?: string; + type?: string; + value?: string; } /** * Options specific to creating a label ("