convert all hacknet to ts

This commit is contained in:
Olivier Gagnon 2021-09-09 03:17:01 -04:00
parent c97fece747
commit b7e07bc7f2
41 changed files with 1947 additions and 1930 deletions

@ -2,25 +2,25 @@ const numSpaces = 4;
const maxLineLength = 160; const maxLineLength = 160;
module.exports = { module.exports = {
env: { "env": {
es6: true, "es6": true,
node: true, "node": true
}, },
extends: "eslint:recommended", "extends": "eslint:recommended",
parserOptions: { "parserOptions": {
ecmaFeatures: { "ecmaFeatures": {
experimentalObjectRestSpread: true, "experimentalObjectRestSpread": true
}, },
ecmaVersion: 8, "ecmaVersion": 8,
sourceType: "module", "sourceType": "module"
}, },
rules: { "rules": {
"accessor-pairs": [ "accessor-pairs": [
"error", "error",
{ {
getWithoutSet: false, "getWithoutSet": false,
setWithoutGet: true, "setWithoutGet": true
}, }
], ],
"array-bracket-newline": ["error"], "array-bracket-newline": ["error"],
"array-bracket-spacing": ["error"], "array-bracket-spacing": ["error"],
@ -33,35 +33,50 @@ module.exports = {
"block-spacing": ["error"], "block-spacing": ["error"],
"brace-style": ["error"], "brace-style": ["error"],
"callback-return": ["error"], "callback-return": ["error"],
camelcase: ["error"], "camelcase": ["error"],
"capitalized-comments": ["error"], "capitalized-comments": ["error"],
"class-methods-use-this": ["error"], "class-methods-use-this": ["error"],
"comma-dangle": ["error"], "comma-dangle": ["error"],
"comma-spacing": ["error"], "comma-spacing": ["error"],
"comma-style": ["error", "last"], "comma-style": [
complexity: ["error"], "error",
"computed-property-spacing": ["error", "never"], "last"
],
"complexity": ["error"],
"computed-property-spacing": [
"error",
"never"
],
"consistent-return": ["error"], "consistent-return": ["error"],
"consistent-this": ["error"], "consistent-this": ["error"],
"constructor-super": ["error"], "constructor-super": ["error"],
curly: ["error"], "curly": ["error"],
"default-case": ["error"], "default-case": ["error"],
"dot-location": ["error", "property"], "dot-location": [
"error",
"property"
],
"dot-notation": ["error"], "dot-notation": ["error"],
"eol-last": ["error"], "eol-last": ["error"],
eqeqeq: ["error"], "eqeqeq": ["error"],
"for-direction": ["error"], "for-direction": ["error"],
"func-call-spacing": ["error"], "func-call-spacing": ["error"],
"func-name-matching": ["error"], "func-name-matching": ["error"],
"func-names": ["error", "never"], "func-names": [
"error",
"never"
],
"func-style": ["error"], "func-style": ["error"],
"function-paren-newline": ["error"], "function-paren-newline": ["error"],
"generator-star-spacing": ["error", "before"], "generator-star-spacing": [
"error",
"before"
],
"getter-return": [ "getter-return": [
"error", "error",
{ {
allowImplicit: false, "allowImplicit": false
}, }
], ],
"global-require": ["error"], "global-require": ["error"],
"guard-for-in": ["error"], "guard-for-in": ["error"],
@ -69,37 +84,52 @@ module.exports = {
"id-blacklist": ["error"], "id-blacklist": ["error"],
"id-length": ["error"], "id-length": ["error"],
"id-match": ["error"], "id-match": ["error"],
"implicit-arrow-linebreak": ["error", "beside"], "implicit-arrow-linebreak": [
indent: [ "error",
"beside"
],
"indent": [
"error", "error",
numSpaces, numSpaces,
{ {
SwitchCase: 1, "SwitchCase": 1
}, }
], ],
"init-declarations": ["error"], "init-declarations": ["error"],
"jsx-quotes": ["error"], "jsx-quotes": ["error"],
"key-spacing": ["error"], "key-spacing": ["error"],
"keyword-spacing": ["error"], "keyword-spacing": ["error"],
"line-comment-position": ["error"], "line-comment-position": ["error"],
"linebreak-style": ["error", "windows"], "linebreak-style": [
"error",
"windows"
],
"lines-around-comment": ["error"], "lines-around-comment": ["error"],
"lines-between-class-members": ["error"], "lines-between-class-members": ["error"],
"max-depth": ["error"], "max-depth": ["error"],
"max-len": ["error", maxLineLength], "max-len": [
"error",
maxLineLength
],
"max-lines": [ "max-lines": [
"error", "error",
{ {
skipBlankLines: true, "skipBlankLines": true,
skipComments: true, "skipComments": true
}, }
], ],
"max-nested-callbacks": ["error"], "max-nested-callbacks": ["error"],
"max-params": ["error"], "max-params": ["error"],
"max-statements": ["error"], "max-statements": ["error"],
"max-statements-per-line": ["error"], "max-statements-per-line": ["error"],
"multiline-comment-style": ["off", "starred-block"], "multiline-comment-style": [
"multiline-ternary": ["error", "never"], "off",
"starred-block"
],
"multiline-ternary": [
"error",
"never"
],
"new-cap": ["error"], "new-cap": ["error"],
"new-parens": ["error"], "new-parens": ["error"],
// TODO: configure this... // TODO: configure this...
@ -115,15 +145,18 @@ module.exports = {
"no-catch-shadow": ["error"], "no-catch-shadow": ["error"],
"no-class-assign": ["error"], "no-class-assign": ["error"],
"no-compare-neg-zero": ["error"], "no-compare-neg-zero": ["error"],
"no-cond-assign": ["error", "except-parens"], "no-cond-assign": [
"error",
"except-parens"
],
"no-confusing-arrow": ["error"], "no-confusing-arrow": ["error"],
"no-console": ["error"], "no-console": ["error"],
"no-const-assign": ["error"], "no-const-assign": ["error"],
"no-constant-condition": [ "no-constant-condition": [
"error", "error",
{ {
checkLoops: false, "checkLoops": false
}, }
], ],
"no-continue": ["off"], "no-continue": ["off"],
"no-control-regex": ["error"], "no-control-regex": ["error"],
@ -137,15 +170,15 @@ module.exports = {
"no-duplicate-imports": [ "no-duplicate-imports": [
"error", "error",
{ {
includeExports: true, "includeExports": true
}, }
], ],
"no-else-return": ["error"], "no-else-return": ["error"],
"no-empty": [ "no-empty": [
"error", "error",
{ {
allowEmptyCatch: false, "allowEmptyCatch": false
}, }
], ],
"no-empty-character-class": ["error"], "no-empty-character-class": ["error"],
"no-empty-function": ["error"], "no-empty-function": ["error"],
@ -161,8 +194,8 @@ module.exports = {
"error", "error",
"all", "all",
{ {
conditionalAssign: false, "conditionalAssign": false
}, }
], ],
"no-extra-semi": ["error"], "no-extra-semi": ["error"],
"no-fallthrough": ["error"], "no-fallthrough": ["error"],
@ -173,17 +206,20 @@ module.exports = {
"no-implicit-globals": ["error"], "no-implicit-globals": ["error"],
"no-implied-eval": ["error"], "no-implied-eval": ["error"],
"no-inline-comments": ["error"], "no-inline-comments": ["error"],
"no-inner-declarations": ["error", "both"], "no-inner-declarations": [
"error",
"both"
],
"no-invalid-regexp": ["error"], "no-invalid-regexp": ["error"],
"no-invalid-this": ["error"], "no-invalid-this": ["error"],
"no-irregular-whitespace": [ "no-irregular-whitespace": [
"error", "error",
{ {
skipComments: false, "skipComments": false,
skipRegExps: false, "skipRegExps": false,
skipStrings: false, "skipStrings": false,
skipTemplates: false, "skipTemplates": false
}, }
], ],
"no-iterator": ["error"], "no-iterator": ["error"],
"no-label-var": ["error"], "no-label-var": ["error"],
@ -194,9 +230,13 @@ module.exports = {
"no-magic-numbers": [ "no-magic-numbers": [
"error", "error",
{ {
ignore: [-1, 0, 1], "ignore": [
ignoreArrayIndexes: true, -1,
}, 0,
1
],
"ignoreArrayIndexes": true
}
], ],
"no-mixed-operators": ["error"], "no-mixed-operators": ["error"],
"no-mixed-requires": ["error"], "no-mixed-requires": ["error"],
@ -207,8 +247,8 @@ module.exports = {
"no-multiple-empty-lines": [ "no-multiple-empty-lines": [
"error", "error",
{ {
max: 1, "max": 1
}, }
], ],
"no-native-reassign": ["error"], "no-native-reassign": ["error"],
"no-negated-condition": ["error"], "no-negated-condition": ["error"],
@ -228,8 +268,8 @@ module.exports = {
"no-plusplus": [ "no-plusplus": [
"error", "error",
{ {
allowForLoopAfterthoughts: true, "allowForLoopAfterthoughts": true
}, }
], ],
"no-process-env": ["error"], "no-process-env": ["error"],
"no-process-exit": ["error"], "no-process-exit": ["error"],
@ -243,10 +283,10 @@ module.exports = {
"no-restricted-properties": [ "no-restricted-properties": [
"error", "error",
{ {
message: "'log' is too general, use an appropriate level when logging.", "message": "'log' is too general, use an appropriate level when logging.",
object: "console", "object": "console",
property: "log", "property": "log"
}, }
], ],
"no-restricted-syntax": ["error"], "no-restricted-syntax": ["error"],
"no-return-assign": ["error"], "no-return-assign": ["error"],
@ -255,8 +295,8 @@ module.exports = {
"no-self-assign": [ "no-self-assign": [
"error", "error",
{ {
props: false, "props": false
}, }
], ],
"no-self-compare": ["error"], "no-self-compare": ["error"],
"no-sequences": ["error"], "no-sequences": ["error"],
@ -293,10 +333,10 @@ module.exports = {
"no-useless-rename": [ "no-useless-rename": [
"error", "error",
{ {
ignoreDestructuring: false, "ignoreDestructuring": false,
ignoreExport: false, "ignoreExport": false,
ignoreImport: false, "ignoreImport": false
}, }
], ],
"no-useless-return": ["error"], "no-useless-return": ["error"],
"no-var": ["error"], "no-var": ["error"],
@ -304,7 +344,10 @@ module.exports = {
"no-warning-comments": ["error"], "no-warning-comments": ["error"],
"no-whitespace-before-property": ["error"], "no-whitespace-before-property": ["error"],
"no-with": ["error"], "no-with": ["error"],
"nonblock-statement-body-position": ["error", "below"], "nonblock-statement-body-position": [
"error",
"below"
],
"object-curly-newline": ["error"], "object-curly-newline": ["error"],
"object-curly-spacing": ["error"], "object-curly-spacing": ["error"],
"object-property-newline": ["error"], "object-property-newline": ["error"],
@ -312,7 +355,10 @@ module.exports = {
"one-var": ["off"], "one-var": ["off"],
"one-var-declaration-per-line": ["error"], "one-var-declaration-per-line": ["error"],
"operator-assignment": ["error"], "operator-assignment": ["error"],
"operator-linebreak": ["error", "none"], "operator-linebreak": [
"error",
"none"
],
"padded-blocks": ["off"], "padded-blocks": ["off"],
"padding-line-between-statements": ["error"], "padding-line-between-statements": ["error"],
"prefer-arrow-callback": ["error"], "prefer-arrow-callback": ["error"],
@ -325,15 +371,24 @@ module.exports = {
"prefer-spread": ["error"], "prefer-spread": ["error"],
"prefer-template": ["error"], "prefer-template": ["error"],
"quote-props": ["error"], "quote-props": ["error"],
quotes: ["error"], "quotes": ["error"],
radix: ["error", "as-needed"], "radix": [
"error",
"as-needed"
],
"require-await": ["error"], "require-await": ["error"],
"require-jsdoc": ["off"], "require-jsdoc": ["off"],
"require-yield": ["error"], "require-yield": ["error"],
"rest-spread-spacing": ["error", "never"], "rest-spread-spacing": [
semi: ["error"], "error",
"never"
],
"semi": ["error"],
"semi-spacing": ["error"], "semi-spacing": ["error"],
"semi-style": ["error", "last"], "semi-style": [
"error",
"last"
],
"sort-imports": ["error"], "sort-imports": ["error"],
"sort-keys": ["error"], "sort-keys": ["error"],
"sort-vars": ["error"], "sort-vars": ["error"],
@ -343,25 +398,37 @@ module.exports = {
"space-infix-ops": ["error"], "space-infix-ops": ["error"],
"space-unary-ops": ["error"], "space-unary-ops": ["error"],
"spaced-comment": ["error"], "spaced-comment": ["error"],
strict: ["error"], "strict": ["error"],
"switch-colon-spacing": [ "switch-colon-spacing": [
"error", "error",
{ {
after: true, "after": true,
before: false, "before": false
}, }
], ],
"symbol-description": ["error"], "symbol-description": ["error"],
"template-curly-spacing": ["error"], "template-curly-spacing": ["error"],
"template-tag-spacing": ["error"], "template-tag-spacing": ["error"],
"unicode-bom": ["error", "never"], "unicode-bom": [
"error",
"never"
],
"use-isnan": ["error"], "use-isnan": ["error"],
"valid-jsdoc": ["error"], "valid-jsdoc": ["error"],
"valid-typeof": ["error"], "valid-typeof": ["error"],
"vars-on-top": ["error"], "vars-on-top": ["error"],
"wrap-iife": ["error", "any"], "wrap-iife": [
"error",
"any"
],
"wrap-regex": ["error"], "wrap-regex": ["error"],
"yield-star-spacing": ["error", "before"], "yield-star-spacing": [
yoda: ["error", "never"], "error",
}, "before"
],
"yoda": [
"error",
"never"
]
}
}; };

@ -8,8 +8,7 @@ const path = require("path");
const exec = require("child_process").exec; const exec = require("child_process").exec;
const semver = require("./semver"); const semver = require("./semver");
const getPackageJson = () => const getPackageJson = () => new Promise((resolve, reject) => {
new Promise((resolve, reject) => {
try { try {
/* eslint-disable-next-line global-require */ /* eslint-disable-next-line global-require */
resolve(require(path.resolve(process.cwd(), "package.json"))); resolve(require(path.resolve(process.cwd(), "package.json")));
@ -18,8 +17,7 @@ const getPackageJson = () =>
} }
}); });
const getEngines = (data) => const getEngines = (data) => new Promise((resolve, reject) => {
new Promise((resolve, reject) => {
let versions = null; let versions = null;
if (data.engines) { if (data.engines) {
@ -33,8 +31,7 @@ const getEngines = (data) =>
} }
}); });
const checkNpmVersion = (engines) => const checkNpmVersion = (engines) => new Promise((resolve, reject) => {
new Promise((resolve, reject) => {
exec("npm -v", (error, stdout, stderr) => { exec("npm -v", (error, stdout, stderr) => {
if (error) { if (error) {
reject(`Unable to find NPM version\n${stderr}`); reject(`Unable to find NPM version\n${stderr}`);
@ -46,23 +43,18 @@ const checkNpmVersion = (engines) =>
if (semver.satisfies(npmVersion, engineVersion)) { if (semver.satisfies(npmVersion, engineVersion)) {
resolve(); resolve();
} else { } else {
reject( reject(`Incorrect npm version\n'package.json' specifies "${engineVersion}", you are currently running "${npmVersion}".`);
`Incorrect npm version\n'package.json' specifies "${engineVersion}", you are currently running "${npmVersion}".`,
);
} }
}); });
}); });
const checkNodeVersion = (engines) => const checkNodeVersion = (engines) => new Promise((resolve, reject) => {
new Promise((resolve, reject) => {
const nodeVersion = process.version.substring(1); const nodeVersion = process.version.substring(1);
if (semver.satisfies(nodeVersion, engines.node)) { if (semver.satisfies(nodeVersion, engines.node)) {
resolve(engines); resolve(engines);
} else { } else {
reject( reject(`Incorrect node version\n'package.json' specifies "${engines.node}", you are currently running "${process.version}".`);
`Incorrect node version\n'package.json' specifies "${engines.node}", you are currently running "${process.version}".`,
);
} }
}); });
@ -77,5 +69,5 @@ getPackageJson()
/* eslint-disable no-console, no-process-exit */ /* eslint-disable no-console, no-process-exit */
console.error(error); console.error(error);
process.exit(1); process.exit(1);
}, }
); );

@ -444,6 +444,7 @@ function parseComparator(comp, loose) {
} }
class SemVer { class SemVer {
/** /**
* A semantic version. * A semantic version.
* @param {string} version The version. * @param {string} version The version.
@ -487,7 +488,7 @@ class SemVer {
// Numberify any prerelease numeric ids // Numberify any prerelease numeric ids
if (matches[4]) { if (matches[4]) {
this.prerelease = matches[4].split(".").map((id) => { this.prerelease = matches[4].split(".").map((id) => {
if (/^[0-9]+$/.test(id)) { if ((/^[0-9]+$/).test(id)) {
const num = Number(id); const num = Number(id);
if (num >= 0 && num < MAX_SAFE_INTEGER) { if (num >= 0 && num < MAX_SAFE_INTEGER) {
return num; return num;
@ -531,9 +532,7 @@ class SemVer {
} }
return ( return (
compareIdentifiers(this.major, other.major) || compareIdentifiers(this.major, other.major) || compareIdentifiers(this.minor, other.minor) || compareIdentifiers(this.patch, other.patch)
compareIdentifiers(this.minor, other.minor) ||
compareIdentifiers(this.patch, other.patch)
); );
} }
@ -573,8 +572,7 @@ class SemVer {
} }
} }
const compare = (leftVersion, rightVersion, loose) => const compare = (leftVersion, rightVersion, loose) => new SemVer(leftVersion, loose).compare(new SemVer(rightVersion, loose));
new SemVer(leftVersion, loose).compare(new SemVer(rightVersion, loose));
const gt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) > 0; const gt = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) > 0;
const lt = (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 eq = (leftVersion, rightVersion, loose) => compare(leftVersion, rightVersion, loose) === 0;

@ -1928,7 +1928,7 @@ export class Bladeburner implements IBladeburner {
// Count increase for contracts/operations // Count increase for contracts/operations
for (const contract of Object.values(this.contracts) as Contract[]) { for (const contract of Object.values(this.contracts) as Contract[]) {
let growthF = Growths[contract.name]; const growthF = Growths[contract.name];
if (growthF === undefined) throw new Error(`growth formula for action '${contract.name}' is undefined`); if (growthF === undefined) throw new Error(`growth formula for action '${contract.name}' is undefined`);
contract.count += (seconds * growthF()) / BladeburnerConstants.ActionCountGrowthPeriod; contract.count += (seconds * growthF()) / BladeburnerConstants.ActionCountGrowthPeriod;
} }

@ -22,8 +22,7 @@ export function BlackOpList(props: IProps): React.ReactElement {
}); });
blackops = blackops.filter( blackops = blackops.filter(
(blackop: BlackOperation, i: number) => (blackop: BlackOperation, i: number) => !(
!(
props.bladeburner.blackops[blackops[i].name] == null && props.bladeburner.blackops[blackops[i].name] == null &&
i !== 0 && i !== 0 &&
props.bladeburner.blackops[blackops[i - 1].name] == null props.bladeburner.blackops[blackops[i - 1].name] == null

@ -61,8 +61,7 @@ export function CityTabs(props: IProps): React.ReactElement {
return ( return (
<> <>
{Object.values(props.division.offices).map( {Object.values(props.division.offices).map(
(office: OfficeSpace | 0) => (office: OfficeSpace | 0) => office !== 0 && (
office !== 0 && (
<CityTab <CityTab
current={city === office.loc} current={city === office.loc}
key={office.loc} key={office.loc}

@ -1,6 +1,5 @@
import React, { useRef } from "react"; import React, { useRef } from "react";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../IIndustry";
import { numeralWrapper } from "../../ui/numeralFormat";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";
import { removePopup } from "../../ui/React/createPopup"; import { removePopup } from "../../ui/React/createPopup";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";

@ -20,8 +20,7 @@ function ExpandButton(props: IExpandButtonProps): React.ReactElement {
const allIndustries = Object.keys(Industries).sort(); const allIndustries = Object.keys(Industries).sort();
const possibleIndustries = allIndustries const possibleIndustries = allIndustries
.filter( .filter(
(industryType: string) => (industryType: string) => props.corp.divisions.find((division: IIndustry) => division.type === industryType) === undefined,
props.corp.divisions.find((division: IIndustry) => division.type === industryType) === undefined,
) )
.sort(); .sort();
if (possibleIndustries.length === 0) return <></>; if (possibleIndustries.length === 0) return <></>;

@ -424,7 +424,6 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
)} )}
<br /> <br />
{/* TODO: add flashing here */}
<button className={`std-button${shouldFlash() ? " flashing-button" : ""}`} onClick={openSellMaterialPopup}> <button className={`std-button${shouldFlash() ? " flashing-button" : ""}`} onClick={openSellMaterialPopup}>
{sellButtonText} {sellButtonText}
</button> </button>
@ -481,17 +480,6 @@ export function IndustryWarehouse(props: IProps): React.ReactElement {
}); });
} }
// Industry material Requirements
let generalReqsText = "This Industry uses [" + Object.keys(props.division.reqMats).join(", ") + "] in order to ";
if (props.division.prodMats.length > 0) {
generalReqsText += "produce [" + props.division.prodMats.join(", ") + "] ";
if (props.division.makesProducts) {
generalReqsText += " and " + props.division.getProductDescriptionText();
}
} else if (props.division.makesProducts) {
generalReqsText += props.division.getProductDescriptionText() + ".";
}
const ratioLines = []; const ratioLines = [];
for (const matName in props.division.reqMats) { for (const matName in props.division.reqMats) {
if (props.division.reqMats.hasOwnProperty(matName)) { if (props.division.reqMats.hasOwnProperty(matName)) {
@ -504,16 +492,6 @@ export function IndustryWarehouse(props: IProps): React.ReactElement {
} }
} }
let createdItemsText = "in order to create ";
if (props.division.prodMats.length > 0) {
createdItemsText += "one of each produced Material (" + props.division.prodMats.join(", ") + ") ";
if (props.division.makesProducts) {
createdItemsText += "or to create one of its Products";
}
} else if (props.division.makesProducts) {
createdItemsText += "one of its Products";
}
// Current State: // Current State:
let stateText; let stateText;
switch (props.division.state) { switch (props.division.state) {

@ -17,8 +17,7 @@ export function NewIndustryPopup(props: IProps): React.ReactElement {
const allIndustries = Object.keys(Industries).sort(); const allIndustries = Object.keys(Industries).sort();
const possibleIndustries = allIndustries const possibleIndustries = allIndustries
.filter( .filter(
(industryType: string) => (industryType: string) => props.corp.divisions.find((division: IIndustry) => division.type === industryType) === undefined,
props.corp.divisions.find((division: IIndustry) => division.type === industryType) === undefined,
) )
.sort(); .sort();
const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : ""); const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : "");

@ -5,7 +5,6 @@ import { ICorporation } from "../ICorporation";
import { IIndustry } from "../IIndustry"; import { IIndustry } from "../IIndustry";
import { SetSmartSupply } from "../Actions"; import { SetSmartSupply } from "../Actions";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { isRelevantMaterial } from "./Helpers";
import { Material } from "../Material"; import { Material } from "../Material";
interface ILeftoverProps { interface ILeftoverProps {
@ -43,7 +42,7 @@ interface IProps {
export function SmartSupplyPopup(props: IProps): React.ReactElement { export function SmartSupplyPopup(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender() { function rerender(): void {
setRerender((old) => !old); setRerender((old) => !old);
} }
// Smart Supply Checkbox // Smart Supply Checkbox

@ -737,8 +737,7 @@ class DevMenuComponent extends Component {
} }
let sourceFiles = []; let sourceFiles = [];
validSFN.forEach((i) => validSFN.forEach((i) => sourceFiles.push(
sourceFiles.push(
<tr key={"sf-" + i}> <tr key={"sf-" + i}>
<td> <td>
<span className="text">SF-{i}:</span> <span className="text">SF-{i}:</span>

@ -123,8 +123,7 @@ export class AugmentationsPage extends React.Component<IProps, IState> {
render(): React.ReactNode { render(): React.ReactNode {
const augs = this.getAugsSorted(); const augs = this.getAugsSorted();
const purchasable = augs.filter( const purchasable = augs.filter(
(aug: string) => (aug: string) => aug === AugmentationNames.NeuroFluxGovernor ||
aug === AugmentationNames.NeuroFluxGovernor ||
(!this.props.p.augmentations.some((a) => a.name === aug) && (!this.props.p.augmentations.some((a) => a.name === aug) &&
!this.props.p.queuedAugmentations.some((a) => a.name === aug)), !this.props.p.queuedAugmentations.some((a) => a.name === aug)),
); );

@ -18,117 +18,109 @@ import { HashUpgrades } from "./HashUpgrades";
import { generateRandomContract } from "../CodingContractGenerator"; import { generateRandomContract } from "../CodingContractGenerator";
import { iTutorialSteps, iTutorialNextStep, ITutorial } from "../InteractiveTutorial"; import { iTutorialSteps, iTutorialNextStep, ITutorial } from "../InteractiveTutorial";
import { Player } from "../Player"; import { IPlayer } from "../PersonObjects/IPlayer";
import { AllServers } from "../Server/AllServers"; import { AllServers } from "../Server/AllServers";
import { GetServerByHostname } from "../Server/ServerHelpers"; import { GetServerByHostname } from "../Server/ServerHelpers";
import { Server } from "../Server/Server";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags"; import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { Page, routing } from "../ui/navigationTracking";
import React from "react";
import ReactDOM from "react-dom";
import { HacknetRoot } from "./ui/Root";
let hacknetNodesDiv;
function hacknetNodesInit() {
hacknetNodesDiv = document.getElementById("hacknet-nodes-container");
document.removeEventListener("DOMContentLoaded", hacknetNodesInit);
}
document.addEventListener("DOMContentLoaded", hacknetNodesInit);
// Returns a boolean indicating whether the player has Hacknet Servers // Returns a boolean indicating whether the player has Hacknet Servers
// (the upgraded form of Hacknet Nodes) // (the upgraded form of Hacknet Nodes)
export function hasHacknetServers() { export function hasHacknetServers(player: IPlayer): boolean {
return Player.bitNodeN === 9 || SourceFileFlags[9] > 0; return player.bitNodeN === 9 || SourceFileFlags[9] > 0;
} }
export function purchaseHacknet() { export function purchaseHacknet(player: IPlayer): number {
/* INTERACTIVE TUTORIAL */ /* INTERACTIVE TUTORIAL */
if (ITutorial.isRunning) { if (ITutorial.isRunning) {
if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) { if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) {
iTutorialNextStep(); iTutorialNextStep();
} else { } else {
return; return -1;
} }
} }
/* END INTERACTIVE TUTORIAL */ /* END INTERACTIVE TUTORIAL */
const numOwned = Player.hacknetNodes.length; const numOwned = player.hacknetNodes.length;
if (hasHacknetServers()) { if (hasHacknetServers(player)) {
const cost = getCostOfNextHacknetServer(); const cost = getCostOfNextHacknetServer(player);
if (isNaN(cost)) { if (isNaN(cost)) {
throw new Error(`Calculated cost of purchasing HacknetServer is NaN`); throw new Error(`Calculated cost of purchasing HacknetServer is NaN`);
} }
if (!Player.canAfford(cost)) { if (!player.canAfford(cost)) {
return -1; return -1;
} }
Player.loseMoney(cost); player.loseMoney(cost);
Player.createHacknetServer(); player.createHacknetServer();
updateHashManagerCapacity(); updateHashManagerCapacity(player);
return numOwned; return numOwned;
} else { } else {
const cost = getCostOfNextHacknetNode(); const cost = getCostOfNextHacknetNode(player);
if (isNaN(cost)) { if (isNaN(cost)) {
throw new Error(`Calculated cost of purchasing HacknetNode is NaN`); throw new Error(`Calculated cost of purchasing HacknetNode is NaN`);
} }
if (!Player.canAfford(cost)) { if (!player.canAfford(cost)) {
return -1; return -1;
} }
// Auto generate a name for the Node // Auto generate a name for the Node
const name = "hacknet-node-" + numOwned; const name = "hacknet-node-" + numOwned;
const node = new HacknetNode(name, Player.hacknet_node_money_mult); const node = new HacknetNode(name, player.hacknet_node_money_mult);
Player.loseMoney(cost); player.loseMoney(cost);
Player.hacknetNodes.push(node); player.hacknetNodes.push(node);
return numOwned; return numOwned;
} }
} }
export function hasMaxNumberHacknetServers() { export function hasMaxNumberHacknetServers(player: IPlayer): boolean {
return hasHacknetServers() && Player.hacknetNodes.length >= HacknetServerConstants.MaxServers; return hasHacknetServers(player) && player.hacknetNodes.length >= HacknetServerConstants.MaxServers;
} }
export function getCostOfNextHacknetNode() { export function getCostOfNextHacknetNode(player: IPlayer): number {
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() { export function getCostOfNextHacknetServer(player: IPlayer): number {
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 // Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's level
export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) { export function getMaxNumberLevelUpgrades(
player: IPlayer,
nodeObj: HacknetNode | HacknetServer,
maxLevel: number,
): number {
if (maxLevel == null) { if (maxLevel == null) {
throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`); throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`);
} }
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1, Player.hacknet_node_level_cost_mult))) { if (player.money.lt(nodeObj.calculateLevelUpgradeCost(1, player.hacknet_node_level_cost_mult))) {
return 0; return 0;
} }
let min = 1; let min = 1;
let max = maxLevel - 1; let max = maxLevel - 1;
let levelsToMax = maxLevel - nodeObj.level; const levelsToMax = maxLevel - nodeObj.level;
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, Player.hacknet_node_level_cost_mult))) { if (player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, player.hacknet_node_level_cost_mult))) {
return levelsToMax; return levelsToMax;
} }
while (min <= max) { while (min <= max) {
var curr = ((min + max) / 2) | 0; const curr = ((min + max) / 2) | 0;
if ( if (
curr !== maxLevel && curr !== maxLevel &&
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult)) && 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)) player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, player.hacknet_node_level_cost_mult))
) { ) {
return Math.min(levelsToMax, curr); return Math.min(levelsToMax, curr);
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult))) { } else if (player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, player.hacknet_node_level_cost_mult))) {
max = curr - 1; max = curr - 1;
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player.hacknet_node_level_cost_mult))) { } else if (player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, player.hacknet_node_level_cost_mult))) {
min = curr + 1; min = curr + 1;
} else { } else {
return Math.min(levelsToMax, curr); return Math.min(levelsToMax, curr);
@ -138,12 +130,16 @@ export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) {
} }
// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's RAM // Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's RAM
export function getMaxNumberRamUpgrades(nodeObj, maxLevel) { export function getMaxNumberRamUpgrades(
player: IPlayer,
nodeObj: HacknetNode | HacknetServer,
maxLevel: number,
): number {
if (maxLevel == null) { if (maxLevel == null) {
throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`); throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`);
} }
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1, Player.hacknet_node_ram_cost_mult))) { if (player.money.lt(nodeObj.calculateRamUpgradeCost(1, player.hacknet_node_ram_cost_mult))) {
return 0; return 0;
} }
@ -153,13 +149,13 @@ export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
} else { } else {
levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram)); levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram));
} }
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, Player.hacknet_node_ram_cost_mult))) { if (player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, player.hacknet_node_ram_cost_mult))) {
return levelsToMax; return levelsToMax;
} }
//We'll just loop until we find the max //We'll just loop until we find the max
for (let i = levelsToMax - 1; i >= 0; --i) { for (let i = levelsToMax - 1; i >= 0; --i) {
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i, Player.hacknet_node_ram_cost_mult))) { if (player.money.gt(nodeObj.calculateRamUpgradeCost(i, player.hacknet_node_ram_cost_mult))) {
return i; return i;
} }
} }
@ -167,34 +163,38 @@ export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
} }
// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cores // Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cores
export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) { export function getMaxNumberCoreUpgrades(
player: IPlayer,
nodeObj: HacknetNode | HacknetServer,
maxLevel: number,
): number {
if (maxLevel == null) { if (maxLevel == null) {
throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`); throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`);
} }
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1, Player.hacknet_node_core_cost_mult))) { if (player.money.lt(nodeObj.calculateCoreUpgradeCost(1, player.hacknet_node_core_cost_mult))) {
return 0; return 0;
} }
let min = 1; let min = 1;
let max = maxLevel - 1; let max = maxLevel - 1;
const levelsToMax = maxLevel - nodeObj.cores; const levelsToMax = maxLevel - nodeObj.cores;
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, Player.hacknet_node_core_cost_mult))) { if (player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, player.hacknet_node_core_cost_mult))) {
return levelsToMax; return levelsToMax;
} }
// Use a binary search to find the max possible number of upgrades // Use a binary search to find the max possible number of upgrades
while (min <= max) { while (min <= max) {
let curr = ((min + max) / 2) | 0; const curr = ((min + max) / 2) | 0;
if ( if (
curr != maxLevel && curr != maxLevel &&
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult)) && 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)) player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, player.hacknet_node_core_cost_mult))
) { ) {
return Math.min(levelsToMax, curr); return Math.min(levelsToMax, curr);
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult))) { } else if (player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, player.hacknet_node_core_cost_mult))) {
max = curr - 1; max = curr - 1;
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player.hacknet_node_core_cost_mult))) { } else if (player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, player.hacknet_node_core_cost_mult))) {
min = curr + 1; min = curr + 1;
} else { } else {
return Math.min(levelsToMax, curr); return Math.min(levelsToMax, curr);
@ -205,34 +205,34 @@ export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
} }
// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cache // Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cache
export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) { export function getMaxNumberCacheUpgrades(player: IPlayer, nodeObj: HacknetServer, maxLevel: number): number {
if (maxLevel == null) { if (maxLevel == null) {
throw new Error(`getMaxNumberCacheUpgrades() called without maxLevel arg`); throw new Error(`getMaxNumberCacheUpgrades() called without maxLevel arg`);
} }
if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(1))) { if (!player.canAfford(nodeObj.calculateCacheUpgradeCost(1))) {
return 0; return 0;
} }
let min = 1; let min = 1;
let max = maxLevel - 1; let max = maxLevel - 1;
const levelsToMax = maxLevel - nodeObj.cache; const levelsToMax = maxLevel - nodeObj.cache;
if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(levelsToMax))) { if (player.canAfford(nodeObj.calculateCacheUpgradeCost(levelsToMax))) {
return levelsToMax; return levelsToMax;
} }
// Use a binary search to find the max possible number of upgrades // Use a binary search to find the max possible number of upgrades
while (min <= max) { while (min <= max) {
let curr = ((min + max) / 2) | 0; const curr = ((min + max) / 2) | 0;
if ( if (
curr != maxLevel && curr != maxLevel &&
Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr)) && player.canAfford(nodeObj.calculateCacheUpgradeCost(curr)) &&
!Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr + 1)) !player.canAfford(nodeObj.calculateCacheUpgradeCost(curr + 1))
) { ) {
return Math.min(levelsToMax, curr); return Math.min(levelsToMax, curr);
} else if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) { } else if (!player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) {
max = curr - 1; max = curr - 1;
} else if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) { } else if (player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) {
min = curr + 1; min = curr + 1;
} else { } else {
return Math.min(levelsToMax, curr); return Math.min(levelsToMax, curr);
@ -242,9 +242,9 @@ export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) {
return 0; return 0;
} }
export function purchaseLevelUpgrade(node, levels = 1) { export function purchaseLevelUpgrade(player: IPlayer, node: HacknetNode | HacknetServer, levels = 1): boolean {
const sanitizedLevels = Math.round(levels); const sanitizedLevels = Math.round(levels);
const cost = node.calculateLevelUpgradeCost(sanitizedLevels, Player.hacknet_node_level_cost_mult); const cost = node.calculateLevelUpgradeCost(sanitizedLevels, player.hacknet_node_level_cost_mult);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false; return false;
} }
@ -260,60 +260,61 @@ export function purchaseLevelUpgrade(node, levels = 1) {
// the maximum number of upgrades and use that // the maximum number of upgrades and use that
if (node.level + sanitizedLevels > (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel)) { if (node.level + sanitizedLevels > (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel)) {
const diff = Math.max(0, (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel) - node.level); const diff = Math.max(0, (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel) - node.level);
return purchaseLevelUpgrade(node, diff); return purchaseLevelUpgrade(player, node, diff);
} }
if (!Player.canAfford(cost)) { if (!player.canAfford(cost)) {
return false; return false;
} }
Player.loseMoney(cost); player.loseMoney(cost);
node.upgradeLevel(sanitizedLevels, Player.hacknet_node_money_mult); node.upgradeLevel(sanitizedLevels, player.hacknet_node_money_mult);
return true; return true;
} }
export function purchaseRamUpgrade(node, levels = 1) { export function purchaseRamUpgrade(player: IPlayer, node: HacknetNode | HacknetServer, levels = 1): boolean {
const sanitizedLevels = Math.round(levels); const sanitizedLevels = Math.round(levels);
const cost = node.calculateRamUpgradeCost(sanitizedLevels, Player.hacknet_node_ram_cost_mult); const cost = node.calculateRamUpgradeCost(sanitizedLevels, player.hacknet_node_ram_cost_mult);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false; return false;
} }
const isServer = node instanceof HacknetServer; if (node instanceof HacknetServer && node.maxRam >= HacknetServerConstants.MaxRam) {
return false;
}
// Fail if we're already at max if (node instanceof HacknetNode && node.ram >= HacknetNodeConstants.MaxRam) {
if (node.ram >= (isServer ? HacknetServerConstants.MaxRam : HacknetNodeConstants.MaxRam)) {
return false; return false;
} }
// If the number of specified upgrades would exceed the max RAM, calculate the // If the number of specified upgrades would exceed the max RAM, calculate the
// max possible number of upgrades and use that // max possible number of upgrades and use that
if (isServer) { if (node instanceof HacknetServer) {
if (node.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerConstants.MaxRam) { if (node.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerConstants.MaxRam) {
const diff = Math.max(0, Math.log2(Math.round(HacknetServerConstants.MaxRam / node.maxRam))); const diff = Math.max(0, Math.log2(Math.round(HacknetServerConstants.MaxRam / node.maxRam)));
return purchaseRamUpgrade(node, diff); return purchaseRamUpgrade(player, node, diff);
} }
} else { } else if (node instanceof HacknetNode) {
if (node.ram * Math.pow(2, sanitizedLevels) > HacknetNodeConstants.MaxRam) { if (node.ram * Math.pow(2, sanitizedLevels) > HacknetNodeConstants.MaxRam) {
const diff = Math.max(0, Math.log2(Math.round(HacknetNodeConstants.MaxRam / node.ram))); const diff = Math.max(0, Math.log2(Math.round(HacknetNodeConstants.MaxRam / node.ram)));
return purchaseRamUpgrade(node, diff); return purchaseRamUpgrade(player, node, diff);
} }
} }
if (!Player.canAfford(cost)) { if (!player.canAfford(cost)) {
return false; return false;
} }
Player.loseMoney(cost); player.loseMoney(cost);
node.upgradeRam(sanitizedLevels, Player.hacknet_node_money_mult); node.upgradeRam(sanitizedLevels, player.hacknet_node_money_mult);
return true; return true;
} }
export function purchaseCoreUpgrade(node, levels = 1) { export function purchaseCoreUpgrade(player: IPlayer, node: HacknetNode | HacknetServer, levels = 1): boolean {
const sanitizedLevels = Math.round(levels); const sanitizedLevels = Math.round(levels);
const cost = node.calculateCoreUpgradeCost(sanitizedLevels, Player.hacknet_node_core_cost_mult); const cost = node.calculateCoreUpgradeCost(sanitizedLevels, player.hacknet_node_core_cost_mult);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false; return false;
} }
@ -329,20 +330,20 @@ export function purchaseCoreUpgrade(node, levels = 1) {
// the max possible number of upgrades and use that // the max possible number of upgrades and use that
if (node.cores + sanitizedLevels > (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores)) { if (node.cores + sanitizedLevels > (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores)) {
const diff = Math.max(0, (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores) - node.cores); const diff = Math.max(0, (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores) - node.cores);
return purchaseCoreUpgrade(node, diff); return purchaseCoreUpgrade(player, node, diff);
} }
if (!Player.canAfford(cost)) { if (!player.canAfford(cost)) {
return false; return false;
} }
Player.loseMoney(cost); player.loseMoney(cost);
node.upgradeCore(sanitizedLevels, Player.hacknet_node_money_mult); node.upgradeCore(sanitizedLevels, player.hacknet_node_money_mult);
return true; return true;
} }
export function purchaseCacheUpgrade(node, levels = 1) { export function purchaseCacheUpgrade(player: IPlayer, node: HacknetServer, levels = 1): boolean {
const sanitizedLevels = Math.round(levels); const sanitizedLevels = Math.round(levels);
const cost = node.calculateCacheUpgradeCost(sanitizedLevels); const cost = node.calculateCacheUpgradeCost(sanitizedLevels);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) { if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
@ -357,143 +358,132 @@ export function purchaseCacheUpgrade(node, levels = 1) {
// Fail if we're already at max // Fail if we're already at max
if (node.cache + sanitizedLevels > HacknetServerConstants.MaxCache) { if (node.cache + sanitizedLevels > HacknetServerConstants.MaxCache) {
const diff = Math.max(0, HacknetServerConstants.MaxCache - node.cache); const diff = Math.max(0, HacknetServerConstants.MaxCache - node.cache);
return purchaseCacheUpgrade(node, diff); return purchaseCacheUpgrade(player, node, diff);
} }
if (!Player.canAfford(cost)) { if (!player.canAfford(cost)) {
return false; return false;
} }
Player.loseMoney(cost); player.loseMoney(cost);
node.upgradeCache(sanitizedLevels); node.upgradeCache(sanitizedLevels);
return true; return true;
} }
// Create/Refresh Hacknet Nodes UI export function processHacknetEarnings(player: IPlayer, numCycles: number): number {
export function renderHacknetNodesUI() {
if (!routing.isOn(Page.HacknetNodes)) {
return;
}
ReactDOM.render(<HacknetRoot />, hacknetNodesDiv);
}
export function clearHacknetNodesUI() {
if (hacknetNodesDiv instanceof HTMLElement) {
ReactDOM.unmountComponentAtNode(hacknetNodesDiv);
}
hacknetNodesDiv.style.display = "none";
}
export function processHacknetEarnings(numCycles) {
// Determine if player has Hacknet Nodes or Hacknet Servers, then // Determine if player has Hacknet Nodes or Hacknet Servers, then
// call the appropriate function // call the appropriate function
if (Player.hacknetNodes.length === 0) { if (player.hacknetNodes.length === 0) {
return 0; return 0;
} }
if (hasHacknetServers()) { if (hasHacknetServers(player)) {
return processAllHacknetServerEarnings(numCycles); return processAllHacknetServerEarnings(player, numCycles);
} else if (Player.hacknetNodes[0] instanceof HacknetNode) { } else if (player.hacknetNodes[0] instanceof HacknetNode) {
return processAllHacknetNodeEarnings(numCycles); return processAllHacknetNodeEarnings(player, numCycles);
} else { } else {
return 0; return 0;
} }
} }
function processAllHacknetNodeEarnings(numCycles) { function processAllHacknetNodeEarnings(player: IPlayer, numCycles: number): number {
let total = 0; let total = 0;
for (let i = 0; i < Player.hacknetNodes.length; ++i) { for (let i = 0; i < player.hacknetNodes.length; ++i) {
total += processSingleHacknetNodeEarnings(numCycles, Player.hacknetNodes[i]); const node = player.hacknetNodes[i];
if (typeof node === "string") throw new Error("player node should not be ip string");
total += processSingleHacknetNodeEarnings(player, numCycles, node);
} }
return total; return total;
} }
function processSingleHacknetNodeEarnings(numCycles, nodeObj) { function processSingleHacknetNodeEarnings(player: IPlayer, numCycles: number, nodeObj: HacknetNode): number {
const totalEarnings = nodeObj.process(numCycles); const totalEarnings = nodeObj.process(numCycles);
Player.gainMoney(totalEarnings); player.gainMoney(totalEarnings);
Player.recordMoneySource(totalEarnings, "hacknetnode"); player.recordMoneySource(totalEarnings, "hacknetnode");
return totalEarnings; return totalEarnings;
} }
function processAllHacknetServerEarnings(numCycles) { function processAllHacknetServerEarnings(player: IPlayer, numCycles: number): number {
if (!(Player.hashManager instanceof HashManager)) { if (!(player.hashManager instanceof HashManager)) {
throw new Error(`Player does not have a HashManager (should be in 'hashManager' prop)`); throw new Error(`Player does not have a HashManager (should be in 'hashManager' prop)`);
} }
let hashes = 0; let hashes = 0;
for (let i = 0; i < Player.hacknetNodes.length; ++i) { for (let i = 0; i < player.hacknetNodes.length; ++i) {
// hacknetNodes array only contains the IP addresses of the servers. // hacknetNodes array only contains the IP addresses of the servers.
// Also, update the hash rate before processing // Also, update the hash rate before processing
const hserver = AllServers[Player.hacknetNodes[i]]; const ip = player.hacknetNodes[i];
hserver.updateHashRate(Player.hacknet_node_money_mult); if (ip instanceof HacknetNode) throw new Error(`player nodes should not be HacketNode`);
const hserver = AllServers[ip];
if (hserver instanceof Server) throw new Error(`player nodes shoud not be Server`);
hserver.updateHashRate(player.hacknet_node_money_mult);
const h = hserver.process(numCycles); const h = hserver.process(numCycles);
hserver.totalHashesGenerated += h; hserver.totalHashesGenerated += h;
hashes += h; hashes += h;
} }
Player.hashManager.storeHashes(hashes); player.hashManager.storeHashes(hashes);
return hashes; return hashes;
} }
export function updateHashManagerCapacity() { export function updateHashManagerCapacity(player: IPlayer): void {
if (!(Player.hashManager instanceof HashManager)) { if (!(player.hashManager instanceof HashManager)) {
console.error(`Player does not have a HashManager`); console.error(`Player does not have a HashManager`);
return; return;
} }
const nodes = Player.hacknetNodes; const nodes = player.hacknetNodes;
if (nodes.length === 0) { if (nodes.length === 0) {
Player.hashManager.updateCapacity(0); player.hashManager.updateCapacity(0);
return; return;
} }
let total = 0; let total = 0;
for (let i = 0; i < nodes.length; ++i) { for (let i = 0; i < nodes.length; ++i) {
if (typeof nodes[i] !== "string") { if (typeof nodes[i] !== "string") {
Player.hashManager.updateCapacity(0); player.hashManager.updateCapacity(0);
return; return;
} }
const ip = nodes[i];
const h = AllServers[nodes[i]]; if (ip instanceof HacknetNode) throw new Error(`player nodes should be string but isn't`);
const h = AllServers[ip];
if (!(h instanceof HacknetServer)) { if (!(h instanceof HacknetServer)) {
Player.hashManager.updateCapacity(0); player.hashManager.updateCapacity(0);
return; return;
} }
total += h.hashCapacity; total += h.hashCapacity;
} }
Player.hashManager.updateCapacity(total); player.hashManager.updateCapacity(total);
} }
export function purchaseHashUpgrade(upgName, upgTarget) { export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget: string): boolean {
if (!(Player.hashManager instanceof HashManager)) { if (!(player.hashManager instanceof HashManager)) {
console.error(`Player does not have a HashManager`); console.error(`Player does not have a HashManager`);
return false; return false;
} }
// HashManager handles the transaction. This just needs to actually implement // HashManager handles the transaction. This just needs to actually implement
// the effects of the upgrade // the effects of the upgrade
if (Player.hashManager.upgrade(upgName)) { if (player.hashManager.upgrade(upgName)) {
const upg = HashUpgrades[upgName]; const upg = HashUpgrades[upgName];
switch (upgName) { switch (upgName) {
case "Sell for Money": { case "Sell for Money": {
Player.gainMoney(upg.value); player.gainMoney(upg.value);
Player.recordMoneySource(upg.value, "hacknetnode"); player.recordMoneySource(upg.value, "hacknetnode");
break; break;
} }
case "Sell for Corporation Funds": { case "Sell for Corporation Funds": {
// This will throw if player doesn't have a corporation // This will throw if player doesn't have a corporation
try { try {
Player.corporation.funds = Player.corporation.funds.plus(upg.value); player.corporation.funds = player.corporation.funds.plus(upg.value);
} catch (e) { } catch (e) {
Player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName);
return false; return false;
} }
break; break;
@ -505,10 +495,11 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`); console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
return false; return false;
} }
if (!(target instanceof Server)) throw new Error(`'${upgTarget}' is not a normal server.`);
target.changeMinimumSecurity(upg.value, true); target.changeMinimumSecurity(upg.value, true);
} catch (e) { } catch (e) {
Player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName);
return false; return false;
} }
break; break;
@ -520,10 +511,11 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`); console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
return false; return false;
} }
if (!(target instanceof Server)) throw new Error(`'${upgTarget}' is not a normal server.`);
target.changeMaximumMoney(upg.value, true); target.changeMaximumMoney(upg.value, true);
} catch (e) { } catch (e) {
Player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName);
return false; return false;
} }
break; break;
@ -539,11 +531,11 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
case "Exchange for Corporation Research": { case "Exchange for Corporation Research": {
// This will throw if player doesn't have a corporation // This will throw if player doesn't have a corporation
try { try {
for (const division of Player.corporation.divisions) { for (const division of player.corporation.divisions) {
division.sciResearch.qty += upg.value; division.sciResearch.qty += upg.value;
} }
} catch (e) { } catch (e) {
Player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName);
return false; return false;
} }
break; break;
@ -551,9 +543,9 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
case "Exchange for Bladeburner Rank": { case "Exchange for Bladeburner Rank": {
// This will throw if player isnt in Bladeburner // This will throw if player isnt in Bladeburner
try { try {
Player.bladeburner.changeRank(Player, upg.value); player.bladeburner.changeRank(player, upg.value);
} catch (e) { } catch (e) {
Player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName);
return false; return false;
} }
break; break;
@ -563,9 +555,9 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
try { try {
// As long as we don't change `Bladeburner.totalSkillPoints`, this // As long as we don't change `Bladeburner.totalSkillPoints`, this
// shouldn't affect anything else // shouldn't affect anything else
Player.bladeburner.skillPoints += upg.value; player.bladeburner.skillPoints += upg.value;
} catch (e) { } catch (e) {
Player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName);
return false; return false;
} }
break; break;
@ -576,7 +568,7 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
} }
default: default:
console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`); console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`);
Player.hashManager.refundUpgrade(upgName); player.hashManager.refundUpgrade(upgName);
return false; return false;
} }

@ -35,6 +35,19 @@ export const HacknetNodeConstants: {
MaxCores: 16, MaxCores: 16,
}; };
export const PurchaseMultipliers: {
[key: string]: number | string | undefined;
x1: number;
x5: number;
x10: number;
MAX: string;
} = {
x1: 1,
x5: 5,
x10: 10,
MAX: "MAX",
};
export const HacknetServerConstants: { export const HacknetServerConstants: {
// Constants for Hacknet Server stats/production // Constants for Hacknet Server stats/production
HashesPerLevel: number; HashesPerLevel: number;

@ -54,7 +54,7 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
"Use hashes to improve the experience earned when studying at a university by 20%. " + "Use hashes to improve the experience earned when studying at a university by 20%. " +
"This effect persists until you install Augmentations", "This effect persists until you install Augmentations",
name: "Improve Studying", name: "Improve Studying",
//effectText: (level: number) => JSX.Element | null = <>Improves studying by ${level*20}%</>, effectText: (level: number): JSX.Element | null => <>Improves studying by {level * 20}%</>,
value: 20, // Improves studying by value% value: 20, // Improves studying by value%
}, },
{ {
@ -63,7 +63,7 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
"Use hashes to improve the experience earned when training at the gym by 20%. This effect " + "Use hashes to improve the experience earned when training at the gym by 20%. This effect " +
"persists until you install Augmentations", "persists until you install Augmentations",
name: "Improve Gym Training", name: "Improve Gym Training",
effectText: (level: number): JSX.Element | null => <>Improves training by ${level * 20}%</>, effectText: (level: number): JSX.Element | null => <>Improves training by {level * 20}%</>,
value: 20, // Improves training by value% value: 20, // Improves training by value%
}, },
{ {

@ -1,57 +0,0 @@
/**
* React Component for the Hacknet Node UI
*
* Displays general information about Hacknet Nodes
*/
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.`
);
}
}
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 (
<div>
<p className={"hacknet-general-info"}>
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.
</p>
<p className={"hacknet-general-info"}>{this.getSecondParagraph()}</p>
<p className={"hacknet-general-info"}>{this.getThirdParagraph()}</p>
</div>
);
}
}

@ -0,0 +1,50 @@
/**
* React Component for the Hacknet Node UI
*
* Displays general information about Hacknet Nodes
*/
import React from "react";
interface IProps {
hasHacknetServers: boolean;
}
export function GeneralInfo(props: IProps): React.ReactElement {
return (
<div>
<p className={"hacknet-general-info"}>
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.
</p>
{!props.hasHacknetServers ? (
<>
<p className={"hacknet-general-info"}>
{`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.`}
</p>
<p className={"hacknet-general-info"}>
{`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.`}
</p>
</>
) : (
<>
<p className={"hacknet-general-info"}>
{`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.`}
</p>
<p className={"hacknet-general-info"}>
{`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.`}
</p>
</>
)}
</div>
);
}

@ -1,171 +0,0 @@
/**
* React Component for the Hacknet Node UI.
* This Component displays the panel for a single Hacknet Node
*/
import React from "react";
import { HacknetNodeConstants } from "../data/Constants";
import {
getMaxNumberLevelUpgrades,
getMaxNumberRamUpgrades,
getMaxNumberCoreUpgrades,
purchaseLevelUpgrade,
purchaseRamUpgrade,
purchaseCoreUpgrade,
} from "../HacknetHelpers";
import { Player } from "../../Player";
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;
// 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} - <Money money={upgradeLevelCost} player={Player} />
</>
);
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} - <Money money={upgradeRamCost} player={Player} />
</>
);
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} - <Money money={upgradeCoreCost} player={Player} />
</>
);
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 (
<li className={"hacknet-node"}>
<div className={"hacknet-node-container"}>
<div className={"row"}>
<h1 style={{ fontSize: "1em" }}>{node.name}</h1>
</div>
<div className={"row"}>
<p>Production:</p>
<span className={"text money-gold"}>
<Money money={node.totalMoneyGenerated} player={Player} /> ({MoneyRate(node.moneyGainRatePerSecond)})
</span>
</div>
<div className={"row"}>
<p>Level:</p>
<span className={"text upgradable-info"}>{node.level}</span>
<button className={upgradeLevelClass} onClick={upgradeLevelOnClick}>
{upgradeLevelContent}
</button>
</div>
<div className={"row"}>
<p>RAM:</p>
<span className={"text upgradable-info"}>{node.ram}GB</span>
<button className={upgradeRamClass} onClick={upgradeRamOnClick}>
{upgradeRamContent}
</button>
</div>
<div className={"row"}>
<p>Cores:</p>
<span className={"text upgradable-info"}>{node.cores}</span>
<button className={upgradeCoresClass} onClick={upgradeCoresOnClick}>
{upgradeCoresContent}
</button>
</div>
</div>
</li>
);
}
}

@ -0,0 +1,174 @@
/**
* React Component for the Hacknet Node UI.
* This Component displays the panel for a single Hacknet Node
*/
import React from "react";
import { HacknetNodeConstants } from "../data/Constants";
import {
getMaxNumberLevelUpgrades,
getMaxNumberRamUpgrades,
getMaxNumberCoreUpgrades,
purchaseLevelUpgrade,
purchaseRamUpgrade,
purchaseCoreUpgrade,
} from "../HacknetHelpers";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { HacknetNode } from "../HacknetNode";
import { Money } from "../../ui/React/Money";
import { MoneyRate } from "../../ui/React/MoneyRate";
interface IProps {
node: HacknetNode;
purchaseMultiplier: number | string;
rerender: () => void;
player: IPlayer;
}
export function HacknetNodeElem(props: IProps): React.ReactElement {
const node = props.node;
const purchaseMult = props.purchaseMultiplier;
const rerender = props.rerender;
// 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(props.player, node, HacknetNodeConstants.MaxLevel);
} else {
const levelsToMax = HacknetNodeConstants.MaxLevel - node.level;
multiplier = Math.min(levelsToMax, purchaseMult as number);
}
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, props.player.hacknet_node_level_cost_mult);
upgradeLevelContent = (
<>
Upgrade x{multiplier} - <Money money={upgradeLevelCost} player={props.player} />
</>
);
if (props.player.money.lt(upgradeLevelCost)) {
upgradeLevelClass = "std-button-disabled";
} else {
upgradeLevelClass = "std-button";
}
}
function upgradeLevelOnClick(): void {
let numUpgrades = purchaseMult;
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberLevelUpgrades(props.player, node, HacknetNodeConstants.MaxLevel);
}
purchaseLevelUpgrade(props.player, node, numUpgrades as number);
rerender();
}
let upgradeRamContent, upgradeRamClass;
if (node.ram >= HacknetNodeConstants.MaxRam) {
upgradeRamContent = <>MAX RAM</>;
upgradeRamClass = "std-button-disabled";
} else {
let multiplier = 0;
if (purchaseMult === "MAX") {
multiplier = getMaxNumberRamUpgrades(props.player, node, HacknetNodeConstants.MaxRam);
} else {
const levelsToMax = Math.round(Math.log2(HacknetNodeConstants.MaxRam / node.ram));
multiplier = Math.min(levelsToMax, purchaseMult as number);
}
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, props.player.hacknet_node_ram_cost_mult);
upgradeRamContent = (
<>
Upgrade x{multiplier} - <Money money={upgradeRamCost} player={props.player} />
</>
);
if (props.player.money.lt(upgradeRamCost)) {
upgradeRamClass = "std-button-disabled";
} else {
upgradeRamClass = "std-button";
}
}
function upgradeRamOnClick(): void {
let numUpgrades = purchaseMult;
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberRamUpgrades(props.player, node, HacknetNodeConstants.MaxRam);
}
purchaseRamUpgrade(props.player, node, numUpgrades as number);
rerender();
}
let upgradeCoresContent, upgradeCoresClass;
if (node.cores >= HacknetNodeConstants.MaxCores) {
upgradeCoresContent = <>MAX CORES</>;
upgradeCoresClass = "std-button-disabled";
} else {
let multiplier = 0;
if (purchaseMult === "MAX") {
multiplier = getMaxNumberCoreUpgrades(props.player, node, HacknetNodeConstants.MaxCores);
} else {
const levelsToMax = HacknetNodeConstants.MaxCores - node.cores;
multiplier = Math.min(levelsToMax, purchaseMult as number);
}
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, props.player.hacknet_node_core_cost_mult);
upgradeCoresContent = (
<>
Upgrade x{multiplier} - <Money money={upgradeCoreCost} player={props.player} />
</>
);
if (props.player.money.lt(upgradeCoreCost)) {
upgradeCoresClass = "std-button-disabled";
} else {
upgradeCoresClass = "std-button";
}
}
function upgradeCoresOnClick(): void {
let numUpgrades = purchaseMult;
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberCoreUpgrades(props.player, node, HacknetNodeConstants.MaxCores);
}
purchaseCoreUpgrade(props.player, node, numUpgrades as number);
rerender();
}
return (
<li className={"hacknet-node"}>
<div className={"hacknet-node-container"}>
<div className={"row"}>
<h1 style={{ fontSize: "1em" }}>{node.name}</h1>
</div>
<div className={"row"}>
<p>Production:</p>
<span className={"text money-gold"}>
<Money money={node.totalMoneyGenerated} player={props.player} /> ({MoneyRate(node.moneyGainRatePerSecond)})
</span>
</div>
<div className={"row"}>
<p>Level:</p>
<span className={"text upgradable-info"}>{node.level}</span>
<button className={upgradeLevelClass} onClick={upgradeLevelOnClick}>
{upgradeLevelContent}
</button>
</div>
<div className={"row"}>
<p>RAM:</p>
<span className={"text upgradable-info"}>{node.ram}GB</span>
<button className={upgradeRamClass} onClick={upgradeRamOnClick}>
{upgradeRamContent}
</button>
</div>
<div className={"row"}>
<p>Cores:</p>
<span className={"text upgradable-info"}>{node.cores}</span>
<button className={upgradeCoresClass} onClick={upgradeCoresOnClick}>
{upgradeCoresContent}
</button>
</div>
</div>
</li>
);
}

@ -0,0 +1,145 @@
/**
* Root React Component for the Hacknet Node UI
*/
import React, { useState, useEffect } from "react";
import { GeneralInfo } from "./GeneralInfo";
import { HacknetNodeElem } from "./HacknetNodeElem";
import { HacknetServerElem } from "./HacknetServerElem";
import { HacknetNode } from "../HacknetNode";
import { HashUpgradePopup } from "./HashUpgradePopup";
import { MultiplierButtons } from "./MultiplierButtons";
import { PlayerInfo } from "./PlayerInfo";
import { PurchaseButton } from "./PurchaseButton";
import { PurchaseMultipliers } from "../data/Constants";
import {
getCostOfNextHacknetNode,
getCostOfNextHacknetServer,
hasHacknetServers,
purchaseHacknet,
} from "../HacknetHelpers";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { AllServers } from "../../Server/AllServers";
import { Server } from "../../Server/Server";
import { createPopup } from "../../ui/React/createPopup";
interface IProps {
player: IPlayer;
}
export function HacknetRoot(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
const [purchaseMultiplier, setPurchaseMultiplier] = useState<number | string>(PurchaseMultipliers.x1);
useEffect(() => {
const id = setInterval(rerender, 1000);
return () => clearInterval(id);
}, []);
function createHashUpgradesPopup(): void {
const id = "hacknet-server-hash-upgrades-popup";
createPopup(id, HashUpgradePopup, {
player: props.player,
});
}
let totalProduction = 0;
for (let i = 0; i < props.player.hacknetNodes.length; ++i) {
const node = props.player.hacknetNodes[i];
if (hasHacknetServers(props.player)) {
if (node instanceof HacknetNode) throw new Error("node was hacknet node"); // should never happen
const hserver = AllServers[node];
if (hserver instanceof Server) throw new Error("node was a normal server"); // should never happen
if (hserver) {
totalProduction += hserver.hashRate;
} else {
console.warn(`Could not find Hacknet Server object in AllServers map (i=${i})`);
}
} else {
if (typeof node === "string") throw new Error("node was ip string"); // should never happen
totalProduction += node.moneyGainRatePerSecond;
}
}
function handlePurchaseButtonClick(): void {
purchaseHacknet(props.player);
rerender();
}
// Cost to purchase a new Hacknet Node
let purchaseCost;
if (hasHacknetServers(props.player)) {
purchaseCost = getCostOfNextHacknetServer(props.player);
} else {
purchaseCost = getCostOfNextHacknetNode(props.player);
}
// onClick event handlers for purchase multiplier buttons
const purchaseMultiplierOnClicks = [
() => setPurchaseMultiplier(PurchaseMultipliers.x1),
() => setPurchaseMultiplier(PurchaseMultipliers.x5),
() => setPurchaseMultiplier(PurchaseMultipliers.x10),
() => setPurchaseMultiplier(PurchaseMultipliers.MAX),
];
// HacknetNode components
const nodes = props.player.hacknetNodes.map((node: string | HacknetNode) => {
if (hasHacknetServers(props.player)) {
if (node instanceof HacknetNode) throw new Error("node was hacknet node"); // should never happen
const hserver = AllServers[node];
if (hserver == null) {
throw new Error(`Could not find Hacknet Server object in AllServers map for IP: ${node}`);
}
if (hserver instanceof Server) throw new Error("node was normal server"); // should never happen
return (
<HacknetServerElem
player={props.player}
key={hserver.hostname}
node={hserver}
purchaseMultiplier={purchaseMultiplier}
rerender={rerender}
/>
);
} else {
if (typeof node === "string") throw new Error("node was ip string"); // should never happen
return (
<HacknetNodeElem
player={props.player}
key={node.name}
node={node}
purchaseMultiplier={purchaseMultiplier}
rerender={rerender}
/>
);
}
});
return (
<div>
<h1>Hacknet {hasHacknetServers(props.player) ? "Servers" : "Nodes"}</h1>
<GeneralInfo hasHacknetServers={hasHacknetServers(props.player)} />
<PurchaseButton cost={purchaseCost} multiplier={purchaseMultiplier} onClick={handlePurchaseButtonClick} />
<br />
<div id={"hacknet-nodes-money-multipliers-div"}>
<PlayerInfo totalProduction={totalProduction} player={props.player} />
<MultiplierButtons onClicks={purchaseMultiplierOnClicks} purchaseMultiplier={purchaseMultiplier} />
</div>
{hasHacknetServers(props.player) && (
<button className={"std-button"} onClick={createHashUpgradesPopup} style={{ display: "block" }}>
{"Spend Hashes on Upgrades"}
</button>
)}
<ul id={"hacknet-nodes-list"}>{nodes}</ul>
</div>
);
}

@ -1,225 +0,0 @@
/**
* React Component for the Hacknet Node UI.
* This Component displays the panel for a single Hacknet Node
*/
import React from "react";
import { HacknetServerConstants } from "../data/Constants";
import {
getMaxNumberLevelUpgrades,
getMaxNumberRamUpgrades,
getMaxNumberCoreUpgrades,
getMaxNumberCacheUpgrades,
purchaseLevelUpgrade,
purchaseRamUpgrade,
purchaseCoreUpgrade,
purchaseCacheUpgrade,
updateHashManagerCapacity,
} from "../HacknetHelpers";
import { Player } from "../../Player";
import { Money } from "../../ui/React/Money";
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;
// 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} - <Money money={upgradeLevelCost} player={Player} />
</>
);
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} - <Money money={upgradeRamCost} player={Player} />
</>
);
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} - <Money money={upgradeCoreCost} player={Player} />
</>
);
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} - <Money money={upgradeCacheCost} player={Player} />
</>
);
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 (
<li className={"hacknet-node"}>
<div className={"hacknet-node-container"}>
<div className={"row"}>
<h1 style={{ fontSize: "1em" }}>{node.hostname}</h1>
</div>
<div className={"row"}>
<p>Production:</p>
<span className={"text money-gold"}>
{Hashes(node.totalHashesGenerated)} ({HashRate(node.hashRate)})
</span>
</div>
<div className={"row"}>
<p>Hash Capacity:</p>
<span className={"text"}>{Hashes(node.hashCapacity)}</span>
</div>
<div className={"row"}>
<p>Level:</p>
<span className={"text upgradable-info"}>{node.level}</span>
<button className={upgradeLevelClass} onClick={upgradeLevelOnClick}>
{upgradeLevelContent}
</button>
</div>
<div className={"row"}>
<p>RAM:</p>
<span className={"text upgradable-info"}>{node.maxRam}GB</span>
<button className={upgradeRamClass} onClick={upgradeRamOnClick}>
{upgradeRamContent}
</button>
</div>
<div className={"row"}>
<p>Cores:</p>
<span className={"text upgradable-info"}>{node.cores}</span>
<button className={upgradeCoresClass} onClick={upgradeCoresOnClick}>
{upgradeCoresContent}
</button>
</div>
<div className={"row"}>
<p>Cache Level:</p>
<span className={"text upgradable-info"}>{node.cache}</span>
<button className={upgradeCacheClass} onClick={upgradeCacheOnClick}>
{upgradeCacheContent}
</button>
</div>
</div>
</li>
);
}
}

@ -0,0 +1,227 @@
/**
* React Component for the Hacknet Node UI.
* This Component displays the panel for a single Hacknet Node
*/
import React from "react";
import { HacknetServerConstants } from "../data/Constants";
import {
getMaxNumberLevelUpgrades,
getMaxNumberRamUpgrades,
getMaxNumberCoreUpgrades,
getMaxNumberCacheUpgrades,
purchaseLevelUpgrade,
purchaseRamUpgrade,
purchaseCoreUpgrade,
purchaseCacheUpgrade,
updateHashManagerCapacity,
} from "../HacknetHelpers";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { HacknetServer } from "../HacknetServer";
import { Money } from "../../ui/React/Money";
import { Hashes } from "../../ui/React/Hashes";
import { HashRate } from "../../ui/React/HashRate";
interface IProps {
node: HacknetServer;
purchaseMultiplier: number | string;
rerender: () => void;
player: IPlayer;
}
export function HacknetServerElem(props: IProps): React.ReactElement {
const node = props.node;
const purchaseMult = props.purchaseMultiplier;
const rerender = props.rerender;
// 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(props.player, node, HacknetServerConstants.MaxLevel);
} else {
const levelsToMax = HacknetServerConstants.MaxLevel - node.level;
multiplier = Math.min(levelsToMax, purchaseMult as number);
}
const upgradeLevelCost = node.calculateLevelUpgradeCost(multiplier, props.player.hacknet_node_level_cost_mult);
upgradeLevelContent = (
<>
Upgrade x{multiplier} - <Money money={upgradeLevelCost} player={props.player} />
</>
);
if (props.player.money.lt(upgradeLevelCost)) {
upgradeLevelClass = "std-button-disabled";
} else {
upgradeLevelClass = "std-button";
}
}
function upgradeLevelOnClick(): void {
let numUpgrades = purchaseMult;
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberLevelUpgrades(props.player, node, HacknetServerConstants.MaxLevel);
}
purchaseLevelUpgrade(props.player, node, numUpgrades as number);
rerender();
}
// 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(props.player, node, HacknetServerConstants.MaxRam);
} else {
const levelsToMax = Math.round(Math.log2(HacknetServerConstants.MaxRam / node.maxRam));
multiplier = Math.min(levelsToMax, purchaseMult as number);
}
const upgradeRamCost = node.calculateRamUpgradeCost(multiplier, props.player.hacknet_node_ram_cost_mult);
upgradeRamContent = (
<>
Upgrade x{multiplier} - <Money money={upgradeRamCost} player={props.player} />
</>
);
if (props.player.money.lt(upgradeRamCost)) {
upgradeRamClass = "std-button-disabled";
} else {
upgradeRamClass = "std-button";
}
}
function upgradeRamOnClick(): void {
let numUpgrades = purchaseMult;
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberRamUpgrades(props.player, node, HacknetServerConstants.MaxRam);
}
purchaseRamUpgrade(props.player, node, numUpgrades as number);
rerender();
}
// 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(props.player, node, HacknetServerConstants.MaxCores);
} else {
const levelsToMax = HacknetServerConstants.MaxCores - node.cores;
multiplier = Math.min(levelsToMax, purchaseMult as number);
}
const upgradeCoreCost = node.calculateCoreUpgradeCost(multiplier, props.player.hacknet_node_core_cost_mult);
upgradeCoresContent = (
<>
Upgrade x{multiplier} - <Money money={upgradeCoreCost} player={props.player} />
</>
);
if (props.player.money.lt(upgradeCoreCost)) {
upgradeCoresClass = "std-button-disabled";
} else {
upgradeCoresClass = "std-button";
}
}
function upgradeCoresOnClick(): void {
let numUpgrades = purchaseMult;
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberCoreUpgrades(props.player, node, HacknetServerConstants.MaxCores);
}
purchaseCoreUpgrade(props.player, node, numUpgrades as number);
rerender();
}
// 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(props.player, node, HacknetServerConstants.MaxCache);
} else {
const levelsToMax = HacknetServerConstants.MaxCache - node.cache;
multiplier = Math.min(levelsToMax, purchaseMult as number);
}
const upgradeCacheCost = node.calculateCacheUpgradeCost(multiplier);
upgradeCacheContent = (
<>
Upgrade x{multiplier} - <Money money={upgradeCacheCost} player={props.player} />
</>
);
if (props.player.money.lt(upgradeCacheCost)) {
upgradeCacheClass = "std-button-disabled";
} else {
upgradeCacheClass = "std-button";
}
}
function upgradeCacheOnClick(): void {
let numUpgrades = purchaseMult;
if (purchaseMult === "MAX") {
numUpgrades = getMaxNumberCacheUpgrades(props.player, node, HacknetServerConstants.MaxCache);
}
purchaseCacheUpgrade(props.player, node, numUpgrades as number);
rerender();
updateHashManagerCapacity(props.player);
}
return (
<li className={"hacknet-node"}>
<div className={"hacknet-node-container"}>
<div className={"row"}>
<h1 style={{ fontSize: "1em" }}>{node.hostname}</h1>
</div>
<div className={"row"}>
<p>Production:</p>
<span className={"text money-gold"}>
{Hashes(node.totalHashesGenerated)} ({HashRate(node.hashRate)})
</span>
</div>
<div className={"row"}>
<p>Hash Capacity:</p>
<span className={"text"}>{Hashes(node.hashCapacity)}</span>
</div>
<div className={"row"}>
<p>Level:</p>
<span className={"text upgradable-info"}>{node.level}</span>
<button className={upgradeLevelClass} onClick={upgradeLevelOnClick}>
{upgradeLevelContent}
</button>
</div>
<div className={"row"}>
<p>RAM:</p>
<span className={"text upgradable-info"}>{node.maxRam}GB</span>
<button className={upgradeRamClass} onClick={upgradeRamOnClick}>
{upgradeRamContent}
</button>
</div>
<div className={"row"}>
<p>Cores:</p>
<span className={"text upgradable-info"}>{node.cores}</span>
<button className={upgradeCoresClass} onClick={upgradeCoresOnClick}>
{upgradeCoresContent}
</button>
</div>
<div className={"row"}>
<p>Cache Level:</p>
<span className={"text upgradable-info"}>{node.cache}</span>
<button className={upgradeCacheClass} onClick={upgradeCacheOnClick}>
{upgradeCacheContent}
</button>
</div>
</div>
</li>
);
}

@ -0,0 +1,70 @@
import React, { useState } from "react";
import { purchaseHashUpgrade } from "../HacknetHelpers";
import { HashManager } from "../HashManager";
import { HashUpgrade } from "../HashUpgrade";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { ServerDropdown, ServerType } from "../../ui/React/ServerDropdown";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { CopyableText } from "../../ui/React/CopyableText";
import { Hashes } from "../../ui/React/Hashes";
interface IProps {
player: IPlayer;
hashManager: HashManager;
upg: HashUpgrade;
rerender: () => void;
}
export function HacknetUpgradeElem(props: IProps): React.ReactElement {
const [selectedServer, setSelectedServer] = useState("ecorp");
function changeTargetServer(event: React.ChangeEvent<HTMLSelectElement>): void {
setSelectedServer(event.target.value);
}
function purchase(): void {
const canPurchase = props.hashManager.hashes >= props.hashManager.getUpgradeCost(props.upg.name);
if (canPurchase) {
const res = purchaseHashUpgrade(props.player, props.upg.name, selectedServer);
if (!res) {
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 upgrade affects.",
);
}
props.rerender();
}
}
const hashManager = props.hashManager;
const upg = props.upg;
const cost = hashManager.getUpgradeCost(upg.name);
const level = hashManager.upgrades[upg.name];
const effect = upg.effectText(level);
// Purchase button
const canPurchase = hashManager.hashes >= cost;
const btnClass = canPurchase ? "std-button" : "std-button-disabled";
// We'll reuse a Bladeburner css class
return (
<div className={"bladeburner-action"}>
<CopyableText value={upg.name} />
<p>
Cost: {Hashes(cost)}, Bought: {level} times
</p>
<p>{upg.desc}</p>
<button className={btnClass} onClick={purchase}>
Purchase
</button>
{level > 0 && effect && <p>{effect}</p>}
{upg.hasTargetServer && (
<ServerDropdown serverType={ServerType.Foreign} onChange={changeTargetServer} style={{ margin: "5px" }} />
)}
</div>
);
}

@ -1,133 +0,0 @@
/**
* Create the pop-up for purchasing upgrades with hashes
*/
import React from "react";
import { purchaseHashUpgrade } from "../HacknetHelpers";
import { HashManager } from "../HashManager";
import { HashUpgrades } from "../HashUpgrades";
import { Player } from "../../Player";
import { numeralWrapper } from "../../ui/numeralFormat";
import { ServerDropdown, ServerType } from "../../ui/React/ServerDropdown";
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);
this.state = {
selectedServer: "ecorp",
};
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.",
);
}
}
}
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 button
const canPurchase = hashManager.hashes >= cost;
const btnClass = canPurchase ? "std-button" : "std-button-disabled";
// We'll reuse a Bladeburner css class
return (
<div className={"bladeburner-action"}>
<CopyableText value={upg.name} />
<p>
Cost: {Hashes(cost)}, Bought: {level} times
</p>
<p>{upg.desc}</p>
<button className={btnClass} onClick={this.purchase}>
Purchase
</button>
{level > 0 && effect && <p>{effect}</p>}
{upg.hasTargetServer && (
<ServerDropdown
serverType={ServerType.Foreign}
onChange={this.changeTargetServer}
style={{ margin: "5px" }}
/>
)}
</div>
);
}
}
export class HashUpgradePopup extends React.Component {
constructor(props) {
super(props);
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)`);
}
const upgradeElems = Object.keys(HashUpgrades).map((upgName) => {
const upg = HashUpgrades[upgName];
return <HashUpgrade upg={upg} hashManager={hashManager} key={upg.name} rerender={rerender} />;
});
return (
<div>
<p>Spend your hashes on a variety of different upgrades</p>
<p>Hashes: {numeralWrapper.formatHashes(this.state.totalHashes)}</p>
{upgradeElems}
</div>
);
}
}

@ -0,0 +1,54 @@
/**
* Create the pop-up for purchasing upgrades with hashes
*/
import React, { useState, useEffect } from "react";
import { HashManager } from "../HashManager";
import { HashUpgrades } from "../HashUpgrades";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Hashes } from "../../ui/React/Hashes";
import { HacknetUpgradeElem } from "./HacknetUpgradeElem";
interface IProps {
player: IPlayer;
}
export function HashUpgradePopup(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(() => setRerender((old) => !old), 1000);
return () => clearInterval(id);
}, []);
const hashManager = props.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 (
<HacknetUpgradeElem
player={props.player}
upg={upg}
hashManager={hashManager}
key={upg.name}
rerender={rerender}
/>
);
});
return (
<div>
<p>Spend your hashes on a variety of different upgrades</p>
<p>Hashes: {Hashes(props.player.hashManager.hashes)}</p>
{upgradeElems}
</div>
);
}

@ -5,9 +5,16 @@
*/ */
import React from "react"; import React from "react";
import { PurchaseMultipliers } from "./Root"; import { PurchaseMultipliers } from "../data/Constants";
function MultiplierButton(props) { interface IMultiplierProps {
className: string;
key: string;
onClick: () => void;
text: string;
}
function MultiplierButton(props: IMultiplierProps): React.ReactElement {
return ( return (
<button className={props.className} onClick={props.onClick}> <button className={props.className} onClick={props.onClick}>
{props.text} {props.text}
@ -15,7 +22,12 @@ function MultiplierButton(props) {
); );
} }
export function MultiplierButtons(props) { interface IProps {
purchaseMultiplier: number | string;
onClicks: (() => void)[];
}
export function MultiplierButtons(props: IProps): React.ReactElement {
if (props.purchaseMultiplier == null) { if (props.purchaseMultiplier == null) {
throw new Error(`MultiplierButtons constructed without required props`); throw new Error(`MultiplierButtons constructed without required props`);
} }

@ -7,14 +7,19 @@
import React from "react"; import React from "react";
import { hasHacknetServers } from "../HacknetHelpers"; import { hasHacknetServers } from "../HacknetHelpers";
import { Player } from "../../Player"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { MoneyRate } from "../../ui/React/MoneyRate"; import { MoneyRate } from "../../ui/React/MoneyRate";
import { HashRate } from "../../ui/React/HashRate"; import { HashRate } from "../../ui/React/HashRate";
import { Hashes } from "../../ui/React/Hashes"; import { Hashes } from "../../ui/React/Hashes";
export function PlayerInfo(props) { interface IProps {
const hasServers = hasHacknetServers(); totalProduction: number;
player: IPlayer;
}
export function PlayerInfo(props: IProps): React.ReactElement {
const hasServers = hasHacknetServers(props.player);
let prod; let prod;
if (hasServers) { if (hasServers) {
@ -26,13 +31,13 @@ export function PlayerInfo(props) {
return ( return (
<p id={"hacknet-nodes-money"}> <p id={"hacknet-nodes-money"}>
<span>Money: </span> <span>Money: </span>
<Money money={Player.money.toNumber()} /> <Money money={props.player.money.toNumber()} />
<br /> <br />
{hasServers && ( {hasServers && (
<> <>
<span> <span>
Hashes: {Hashes(Player.hashManager.hashes)} / {Hashes(Player.hashManager.capacity)} Hashes: {Hashes(props.player.hashManager.hashes)} / {Hashes(props.player.hashManager.capacity)}
</span> </span>
<br /> <br />
</> </>

@ -7,17 +7,19 @@ import { hasHacknetServers, hasMaxNumberHacknetServers } from "../HacknetHelpers
import { Player } from "../../Player"; import { Player } from "../../Player";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
export function PurchaseButton(props) { interface IProps {
if (props.multiplier == null || props.onClick == null) { multiplier: number | string;
throw new Error(`PurchaseButton constructed without required props`); onClick: () => void;
cost: number;
} }
export function PurchaseButton(props: IProps): React.ReactElement {
const cost = props.cost; const cost = props.cost;
let className = Player.canAfford(cost) ? "std-button" : "std-button-disabled"; let className = Player.canAfford(cost) ? "std-button" : "std-button-disabled";
let text; let text;
let style = null; let style = {};
if (hasHacknetServers()) { if (hasHacknetServers(Player)) {
if (hasMaxNumberHacknetServers()) { if (hasMaxNumberHacknetServers(Player)) {
className = "std-button-disabled"; className = "std-button-disabled";
text = <>Hacknet Server limit reached</>; text = <>Hacknet Server limit reached</>;
style = { color: "red" }; style = { color: "red" };

@ -1,162 +0,0 @@
/**
* Root React Component for the Hacknet Node UI
*/
import React from "react";
import { GeneralInfo } from "./GeneralInfo";
import { HacknetNode } from "./HacknetNode";
import { HacknetServer } from "./HacknetServer";
import { HashUpgradePopup } from "./HashUpgradePopup";
import { MultiplierButtons } from "./MultiplierButtons";
import { PlayerInfo } from "./PlayerInfo";
import { PurchaseButton } from "./PurchaseButton";
import {
getCostOfNextHacknetNode,
getCostOfNextHacknetServer,
hasHacknetServers,
purchaseHacknet,
} from "../HacknetHelpers";
import { Player } from "../../Player";
import { AllServers } from "../../Server/AllServers";
import { createPopup } from "../../ui/React/createPopup";
export const PurchaseMultipliers = Object.freeze({
x1: 1,
x5: 5,
x10: 10,
MAX: "MAX",
});
export class HacknetRoot extends React.Component {
constructor(props) {
super(props);
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);
}
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();
} 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 (
<HacknetServer
key={hserver.hostname}
node={hserver}
purchaseMultiplier={this.state.purchaseMultiplier}
recalculate={this.recalculateTotalProduction}
/>
);
} else {
return (
<HacknetNode
key={node.name}
node={node}
purchaseMultiplier={this.state.purchaseMultiplier}
recalculate={this.recalculateTotalProduction}
/>
);
}
});
return (
<div>
<h1>Hacknet {hasHacknetServers() ? "Servers" : "Nodes"}</h1>
<GeneralInfo />
<PurchaseButton
cost={purchaseCost}
multiplier={this.state.purchaseMultiplier}
onClick={this.handlePurchaseButtonClick}
/>
<br />
<div id={"hacknet-nodes-money-multipliers-div"}>
<PlayerInfo totalProduction={this.state.totalProduction} />
<MultiplierButtons onClicks={purchaseMultiplierOnClicks} purchaseMultiplier={this.state.purchaseMultiplier} />
</div>
{hasHacknetServers() && (
<button className={"std-button"} onClick={this.createHashUpgradesPopup} style={{ display: "block" }}>
{"Spend Hashes on Upgrades"}
</button>
)}
<ul id={"hacknet-nodes-list"}>{nodes}</ul>
</div>
);
}
}

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React from "react";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React from "react";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";

@ -24,7 +24,7 @@ type IProps = {
export function TechVendorLocation(props: IProps): React.ReactElement { export function TechVendorLocation(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender() { function rerender(): void {
setRerender((old) => !old); setRerender((old) => !old);
} }
const btnStyle = { display: "block" }; const btnStyle = { display: "block" };

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React from "react";
import { purchaseTorRouter } from "../LocationsHelpers"; import { purchaseTorRouter } from "../LocationsHelpers";

@ -351,7 +351,7 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeErrorMsg(callingFn, "Index specified for Hacknet Node is out-of-bounds: " + i); throw makeRuntimeErrorMsg(callingFn, "Index specified for Hacknet Node is out-of-bounds: " + i);
} }
if (hasHacknetServers()) { if (hasHacknetServers(Player)) {
const hserver = AllServers[Player.hacknetNodes[i]]; const hserver = AllServers[Player.hacknetNodes[i]];
if (hserver == null) { if (hserver == null) {
throw makeRuntimeErrorMsg( throw makeRuntimeErrorMsg(
@ -719,24 +719,24 @@ function NetscriptFunctions(workerScript) {
return Player.hacknetNodes.length; return Player.hacknetNodes.length;
}, },
maxNumNodes: function () { maxNumNodes: function () {
if (hasHacknetServers()) { if (hasHacknetServers(Player)) {
return HacknetServerConstants.MaxServers; return HacknetServerConstants.MaxServers;
} }
return Infinity; return Infinity;
}, },
purchaseNode: function () { purchaseNode: function () {
return purchaseHacknet(); return purchaseHacknet(Player);
}, },
getPurchaseNodeCost: function () { getPurchaseNodeCost: function () {
if (hasHacknetServers()) { if (hasHacknetServers(Player)) {
return getCostOfNextHacknetServer(); return getCostOfNextHacknetServer(Player);
} else { } else {
return getCostOfNextHacknetNode(); return getCostOfNextHacknetNode(Player);
} }
}, },
getNodeStats: function (i) { getNodeStats: function (i) {
const node = getHacknetNode(i, "getNodeStats"); const node = getHacknetNode(i, "getNodeStats");
const hasUpgraded = hasHacknetServers(); const hasUpgraded = hasHacknetServers(Player);
const res = { const res = {
name: hasUpgraded ? node.hostname : node.name, name: hasUpgraded ? node.hostname : node.name,
level: node.level, level: node.level,
@ -756,24 +756,24 @@ function NetscriptFunctions(workerScript) {
}, },
upgradeLevel: function (i, n) { upgradeLevel: function (i, n) {
const node = getHacknetNode(i, "upgradeLevel"); const node = getHacknetNode(i, "upgradeLevel");
return purchaseLevelUpgrade(node, n); return purchaseLevelUpgrade(Player, node, n);
}, },
upgradeRam: function (i, n) { upgradeRam: function (i, n) {
const node = getHacknetNode(i, "upgradeRam"); const node = getHacknetNode(i, "upgradeRam");
return purchaseRamUpgrade(node, n); return purchaseRamUpgrade(Player, node, n);
}, },
upgradeCore: function (i, n) { upgradeCore: function (i, n) {
const node = getHacknetNode(i, "upgradeCore"); const node = getHacknetNode(i, "upgradeCore");
return purchaseCoreUpgrade(node, n); return purchaseCoreUpgrade(Player, node, n);
}, },
upgradeCache: function (i, n) { upgradeCache: function (i, n) {
if (!hasHacknetServers()) { if (!hasHacknetServers(Player)) {
return false; return false;
} }
const node = getHacknetNode(i, "upgradeCache"); const node = getHacknetNode(i, "upgradeCache");
const res = purchaseCacheUpgrade(node, n); const res = purchaseCacheUpgrade(Player, node, n);
if (res) { if (res) {
updateHashManagerCapacity(); updateHashManagerCapacity(Player);
} }
return res; return res;
}, },
@ -790,36 +790,36 @@ function NetscriptFunctions(workerScript) {
return node.calculateCoreUpgradeCost(n, Player.hacknet_node_core_cost_mult); return node.calculateCoreUpgradeCost(n, Player.hacknet_node_core_cost_mult);
}, },
getCacheUpgradeCost: function (i, n) { getCacheUpgradeCost: function (i, n) {
if (!hasHacknetServers()) { if (!hasHacknetServers(Player)) {
return Infinity; return Infinity;
} }
const node = getHacknetNode(i, "upgradeCache"); const node = getHacknetNode(i, "upgradeCache");
return node.calculateCacheUpgradeCost(n); return node.calculateCacheUpgradeCost(n);
}, },
numHashes: function () { numHashes: function () {
if (!hasHacknetServers()) { if (!hasHacknetServers(Player)) {
return 0; return 0;
} }
return Player.hashManager.hashes; return Player.hashManager.hashes;
}, },
hashCapacity: function () { hashCapacity: function () {
if (!hasHacknetServers()) { if (!hasHacknetServers(Player)) {
return 0; return 0;
} }
return Player.hashManager.capacity; return Player.hashManager.capacity;
}, },
hashCost: function (upgName) { hashCost: function (upgName) {
if (!hasHacknetServers()) { if (!hasHacknetServers(Player)) {
return Infinity; return Infinity;
} }
return Player.hashManager.getUpgradeCost(upgName); return Player.hashManager.getUpgradeCost(upgName);
}, },
spendHashes: function (upgName, upgTarget) { spendHashes: function (upgName, upgTarget) {
if (!hasHacknetServers()) { if (!hasHacknetServers(Player)) {
return false; return false;
} }
return purchaseHashUpgrade(upgName, upgTarget); return purchaseHashUpgrade(Player, upgName, upgTarget);
}, },
getHashUpgradeLevel: function (upgName) { getHashUpgradeLevel: function (upgName) {
const level = Player.hashManager.upgrades[upgName]; const level = Player.hashManager.upgrades[upgName];
@ -829,13 +829,13 @@ function NetscriptFunctions(workerScript) {
return level; return level;
}, },
getStudyMult: function () { getStudyMult: function () {
if (!hasHacknetServers()) { if (!hasHacknetServers(Player)) {
return false; return false;
} }
return Player.hashManager.getStudyMult(); return Player.hashManager.getStudyMult();
}, },
getTrainingMult: function () { getTrainingMult: function () {
if (!hasHacknetServers()) { if (!hasHacknetServers(Player)) {
return false; return false;
} }
return Player.hashManager.getTrainingMult(); return Player.hashManager.getTrainingMult();

@ -191,4 +191,5 @@ export interface IPlayer {
getIntelligenceBonus(weight: number): number; getIntelligenceBonus(weight: number): number;
getCasinoWinnings(): number; getCasinoWinnings(): number;
quitJob(company: string): void; quitJob(company: string): void;
createHacknetServer(): void;
} }

@ -2518,7 +2518,6 @@ export function checkForFactionInvitations() {
//BitRunners //BitRunners
var bitrunnersFac = Factions["BitRunners"]; var bitrunnersFac = Factions["BitRunners"];
var homeComp = this.getHomeComputer();
var bitrunnersServer = AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]]; var bitrunnersServer = AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]];
if (bitrunnersServer == null) { if (bitrunnersServer == null) {
console.error("Could not find BitRunners Server"); console.error("Could not find BitRunners Server");
@ -2743,7 +2742,7 @@ export function checkForFactionInvitations() {
var totalHacknetCores = 0; var totalHacknetCores = 0;
var totalHacknetLevels = 0; var totalHacknetLevels = 0;
for (let i = 0; i < this.hacknetNodes.length; ++i) { for (let i = 0; i < this.hacknetNodes.length; ++i) {
if (hasHacknetServers()) { if (hasHacknetServers(this)) {
const hserver = AllServers[this.hacknetNodes[i]]; const hserver = AllServers[this.hacknetNodes[i]];
if (hserver) { if (hserver) {
totalHacknetLevels += hserver.level; totalHacknetLevels += hserver.level;

@ -339,7 +339,7 @@ function prestigeSourceFile(flume) {
hserver.cache = 5; hserver.cache = 5;
hserver.updateHashRate(Player.hacknet_node_money_mult); hserver.updateHashRate(Player.hacknet_node_money_mult);
hserver.updateHashCapacity(); hserver.updateHashCapacity();
updateHashManagerCapacity(); updateHashManagerCapacity(Player);
} }
// Refresh Main Menu (the 'World' menu, specifically) // Refresh Main Menu (the 'World' menu, specifically)

@ -157,8 +157,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
d += "&nbsp;&nbsp;&nbsp;&nbsp;[\n"; d += "&nbsp;&nbsp;&nbsp;&nbsp;[\n";
d += n d += n
.map( .map(
(line: number[]) => (line: number[]) => "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[" +
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[" +
line.map((x: number) => `${x}`.padStart(2, " ")).join(",") + line.map((x: number) => `${x}`.padStart(2, " ")).join(",") +
"]", "]",
) )

@ -28,12 +28,8 @@ import {
getFactionFieldWorkRepGain, getFactionFieldWorkRepGain,
} from "./PersonObjects/formulas/reputation"; } from "./PersonObjects/formulas/reputation";
import { FconfSettings } from "./Fconf/FconfSettings"; import { FconfSettings } from "./Fconf/FconfSettings";
import { import { hasHacknetServers, processHacknetEarnings } from "./Hacknet/HacknetHelpers";
hasHacknetServers, import { HacknetRoot } from "./Hacknet/ui/HacknetRoot";
renderHacknetNodesUI,
clearHacknetNodesUI,
processHacknetEarnings,
} from "./Hacknet/HacknetHelpers";
import { iTutorialStart } from "./InteractiveTutorial"; import { iTutorialStart } from "./InteractiveTutorial";
import { LocationName } from "./Locations/data/LocationNames"; import { LocationName } from "./Locations/data/LocationNames";
import { LocationRoot } from "./Locations/ui/Root"; import { LocationRoot } from "./Locations/ui/Root";
@ -251,7 +247,7 @@ const Engine = {
Engine.hideAllContent(); Engine.hideAllContent();
Engine.Display.hacknetNodesContent.style.display = "block"; Engine.Display.hacknetNodesContent.style.display = "block";
routing.navigateTo(Page.HacknetNodes); routing.navigateTo(Page.HacknetNodes);
renderHacknetNodesUI(); ReactDOM.render(<HacknetRoot player={Player} />, Engine.Display.hacknetNodesContent);
MainMenuLinks.HacknetNodes.classList.add("active"); MainMenuLinks.HacknetNodes.classList.add("active");
}, },
@ -462,7 +458,9 @@ const Engine = {
Engine.Display.infiltrationContent.style.display = "none"; Engine.Display.infiltrationContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.infiltrationContent); ReactDOM.unmountComponentAtNode(Engine.Display.infiltrationContent);
clearHacknetNodesUI(); Engine.Display.hacknetNodesContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.hacknetNodesContent);
Engine.Display.createProgramContent.style.display = "none"; Engine.Display.createProgramContent.style.display = "none";
Engine.Display.factionsContent.style.display = "none"; Engine.Display.factionsContent.style.display = "none";
@ -670,7 +668,7 @@ const Engine = {
updateOnlineScriptTimes(numCycles); updateOnlineScriptTimes(numCycles);
// Hacknet Nodes // Hacknet Nodes
processHacknetEarnings(numCycles); processHacknetEarnings(Player, numCycles);
}, },
/** /**
@ -737,9 +735,7 @@ const Engine = {
if (Engine.Counters.updateDisplays <= 0) { if (Engine.Counters.updateDisplays <= 0) {
Engine.displayCharacterOverviewInfo(); Engine.displayCharacterOverviewInfo();
if (routing.isOn(Page.HacknetNodes)) { if (routing.isOn(Page.CreateProgram)) {
renderHacknetNodesUI();
} else if (routing.isOn(Page.CreateProgram)) {
displayCreateProgramContent(); displayCreateProgramContent();
} else if (routing.isOn(Page.Sleeves)) { } else if (routing.isOn(Page.Sleeves)) {
updateSleevesPage(); updateSleevesPage();
@ -1020,8 +1016,8 @@ const Engine = {
} }
// Hacknet Nodes offline progress // Hacknet Nodes offline progress
var offlineProductionFromHacknetNodes = processHacknetEarnings(numCyclesOffline); var offlineProductionFromHacknetNodes = processHacknetEarnings(Player, numCyclesOffline);
const hacknetProdInfo = hasHacknetServers() ? ( const hacknetProdInfo = hasHacknetServers(Player) ? (
<>{Hashes(offlineProductionFromHacknetNodes)} hashes</> <>{Hashes(offlineProductionFromHacknetNodes)} hashes</>
) : ( ) : (
<Money money={offlineProductionFromHacknetNodes} /> <Money money={offlineProductionFromHacknetNodes} />

@ -5,6 +5,7 @@
*/ */
import React from "react"; import React from "react";
import { AllServers } from "../../Server/AllServers"; import { AllServers } from "../../Server/AllServers";
import { Server } from "../../Server/Server";
import { HacknetServer } from "../../Hacknet/HacknetServer"; import { HacknetServer } from "../../Hacknet/HacknetServer";
@ -16,52 +17,50 @@ export const ServerType = {
Purchased: 3, // Everything from Owned except home computer Purchased: 3, // Everything from Owned except home computer
}; };
export class ServerDropdown extends React.Component { interface IProps {
serverType: number;
onChange: (event: React.ChangeEvent<HTMLSelectElement>) => void;
style: any;
}
export function ServerDropdown(props: IProps): React.ReactElement {
/** /**
* Checks if the server should be shown in the dropdown menu, based on the * Checks if the server should be shown in the dropdown menu, based on the
* 'serverType' property * 'serverType' property
*/ */
isValidServer(s) { function isValidServer(s: Server | HacknetServer): boolean {
const type = this.props.serverType; const purchased = s instanceof Server && s.purchasedByPlayer;
const type = props.serverType;
switch (type) { switch (type) {
case ServerType.All: case ServerType.All:
return true; return true;
case ServerType.Foreign: case ServerType.Foreign:
return s.hostname !== "home" && !s.purchasedByPlayer; return s.hostname !== "home" && !purchased;
case ServerType.Owned: case ServerType.Owned:
return s.purchasedByPlayer || s instanceof HacknetServer || s.hostname === "home"; return purchased || s instanceof HacknetServer || s.hostname === "home";
case ServerType.Purchased: case ServerType.Purchased:
return s.purchasedByPlayer || s instanceof HacknetServer; return purchased || s instanceof HacknetServer;
default: default:
console.warn(`Invalid ServerType specified for ServerDropdown component: ${type}`); console.warn(`Invalid ServerType specified for ServerDropdown component: ${type}`);
return false; return false;
} }
} }
/**
* Given a Server object, creates a Option element
*/
renderOption(s) {
return (
<option key={s.hostname} value={s.hostname}>
{s.hostname}
</option>
);
}
render() {
const servers = []; const servers = [];
for (const serverName in AllServers) { for (const serverName in AllServers) {
const server = AllServers[serverName]; const server = AllServers[serverName];
if (this.isValidServer(server)) { if (isValidServer(server)) {
servers.push(this.renderOption(server)); servers.push(
<option key={server.hostname} value={server.hostname}>
{server.hostname}
</option>,
);
} }
} }
return ( return (
<select className={"dropdown"} onChange={this.props.onChange} style={this.props.style}> <select className={"dropdown"} onChange={props.onChange} style={props.style}>
{servers} {servers}
</select> </select>
); );
} }
}