2019-03-25 04:03:24 +01:00
|
|
|
import { HacknetNode,
|
|
|
|
BaseCostForHacknetNode,
|
|
|
|
HacknetNodePurchaseNextMult,
|
|
|
|
HacknetNodeMaxLevel,
|
|
|
|
HacknetNodeMaxRam,
|
|
|
|
HacknetNodeMaxCores } from "./HacknetNode";
|
|
|
|
import { HacknetServer,
|
|
|
|
BaseCostForHacknetServer,
|
|
|
|
HacknetServerPurchaseMult,
|
|
|
|
HacknetServerMaxLevel,
|
|
|
|
HacknetServerMaxRam,
|
|
|
|
HacknetServerMaxCores,
|
|
|
|
HacknetServerMaxCache,
|
|
|
|
MaxNumberHacknetServers } from "./HacknetServer";
|
|
|
|
import { HashManager } from "./HashManager";
|
|
|
|
import { HashUpgrades } from "./HashUpgrades";
|
|
|
|
|
2019-04-05 11:08:41 +02:00
|
|
|
import { generateRandomContract } from "../CodingContractGenerator";
|
2019-03-25 04:03:24 +01:00
|
|
|
import { iTutorialSteps, iTutorialNextStep,
|
|
|
|
ITutorial} from "../InteractiveTutorial";
|
|
|
|
import { Player } from "../Player";
|
2019-03-27 09:36:14 +01:00
|
|
|
import { AddToAllServers,
|
|
|
|
AllServers } from "../Server/AllServers";
|
2019-03-25 04:03:24 +01:00
|
|
|
import { GetServerByHostname } from "../Server/ServerHelpers";
|
|
|
|
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
|
|
|
import { Page, routing } from "../ui/navigationTracking";
|
|
|
|
|
|
|
|
import {getElementById} from "../../utils/uiHelpers/getElementById";
|
|
|
|
|
|
|
|
import React from "react";
|
|
|
|
import ReactDOM from "react-dom";
|
|
|
|
import { HacknetRoot } from "./ui/Root";
|
|
|
|
|
|
|
|
let hacknetNodesDiv;
|
|
|
|
function hacknetNodesInit() {
|
|
|
|
hacknetNodesDiv = document.getElementById("hacknet-nodes-container");
|
2019-03-27 09:36:14 +01:00
|
|
|
document.removeEventListener("DOMContentLoaded", hacknetNodesInit);
|
2019-03-25 04:03:24 +01:00
|
|
|
}
|
|
|
|
|
2019-03-27 09:36:14 +01:00
|
|
|
document.addEventListener("DOMContentLoaded", hacknetNodesInit);
|
2019-03-25 04:03:24 +01:00
|
|
|
|
|
|
|
// Returns a boolean indicating whether the player has Hacknet Servers
|
|
|
|
// (the upgraded form of Hacknet Nodes)
|
|
|
|
export function hasHacknetServers() {
|
|
|
|
return (Player.bitNodeN === 9 || SourceFileFlags[9] > 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function purchaseHacknet() {
|
|
|
|
/* INTERACTIVE TUTORIAL */
|
|
|
|
if (ITutorial.isRunning) {
|
|
|
|
if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) {
|
|
|
|
iTutorialNextStep();
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* END INTERACTIVE TUTORIAL */
|
|
|
|
|
2019-03-31 01:39:37 +01:00
|
|
|
const numOwned = Player.hacknetNodes.length;
|
2019-03-25 04:03:24 +01:00
|
|
|
if (hasHacknetServers()) {
|
|
|
|
const cost = getCostOfNextHacknetServer();
|
|
|
|
if (isNaN(cost)) {
|
|
|
|
throw new Error(`Calculated cost of purchasing HacknetServer is NaN`)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Player.canAfford(cost)) { return -1; }
|
|
|
|
Player.loseMoney(cost);
|
2019-04-05 11:08:41 +02:00
|
|
|
const server = Player.createHacknetServer();
|
2019-03-31 01:39:37 +01:00
|
|
|
Player.hashManager.updateCapacity(Player);
|
2019-03-25 04:03:24 +01:00
|
|
|
|
|
|
|
return numOwned;
|
|
|
|
} else {
|
|
|
|
const cost = getCostOfNextHacknetNode();
|
|
|
|
if (isNaN(cost)) {
|
|
|
|
throw new Error(`Calculated cost of purchasing HacknetNode is NaN`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Player.canAfford(cost)) { return -1; }
|
|
|
|
|
|
|
|
// Auto generate a name for the Node
|
|
|
|
const name = "hacknet-node-" + numOwned;
|
|
|
|
const node = new HacknetNode(name);
|
|
|
|
node.updateMoneyGainRate(Player);
|
|
|
|
|
|
|
|
Player.loseMoney(cost);
|
|
|
|
Player.hacknetNodes.push(node);
|
|
|
|
|
|
|
|
return numOwned;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function hasMaxNumberHacknetServers() {
|
|
|
|
return hasHacknetServers() && Player.hacknetNodes.length >= MaxNumberHacknetServers;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getCostOfNextHacknetNode() {
|
|
|
|
// Cost increases exponentially based on how many you own
|
|
|
|
const numOwned = Player.hacknetNodes.length;
|
|
|
|
const mult = HacknetNodePurchaseNextMult;
|
|
|
|
|
|
|
|
return BaseCostForHacknetNode * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getCostOfNextHacknetServer() {
|
|
|
|
const numOwned = Player.hacknetNodes.length;
|
|
|
|
const mult = HacknetServerPurchaseMult;
|
|
|
|
|
|
|
|
if (numOwned > MaxNumberHacknetServers) { return Infinity; }
|
|
|
|
|
|
|
|
return BaseCostForHacknetServer * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node
|
|
|
|
export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) {
|
|
|
|
if (maxLevel == null) {
|
|
|
|
throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(1, Player))) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let min = 1;
|
|
|
|
let max = maxLevel - 1;
|
|
|
|
let levelsToMax = maxLevel - nodeObj.level;
|
|
|
|
if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, Player))) {
|
|
|
|
return levelsToMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (min <= max) {
|
|
|
|
var curr = (min + max) / 2 | 0;
|
|
|
|
if (curr !== maxLevel &&
|
|
|
|
Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player)) &&
|
|
|
|
Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr + 1, Player))) {
|
|
|
|
return Math.min(levelsToMax, curr);
|
|
|
|
} else if (Player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
|
|
|
|
max = curr - 1;
|
|
|
|
} else if (Player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, Player))) {
|
|
|
|
min = curr + 1;
|
|
|
|
} else {
|
|
|
|
return Math.min(levelsToMax, curr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
|
|
|
|
if (maxLevel == null) {
|
|
|
|
throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Player.money.lt(nodeObj.calculateRamUpgradeCost(1, Player))) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let levelsToMax;
|
|
|
|
if (nodeObj instanceof HacknetServer) {
|
|
|
|
levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.maxRam));
|
|
|
|
} else {
|
|
|
|
levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram));
|
|
|
|
}
|
|
|
|
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, Player))) {
|
|
|
|
return levelsToMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
//We'll just loop until we find the max
|
|
|
|
for (let i = levelsToMax-1; i >= 0; --i) {
|
|
|
|
if (Player.money.gt(nodeObj.calculateRamUpgradeCost(i, Player))) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
|
|
|
|
if (maxLevel == null) {
|
|
|
|
throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(1, Player))) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let min = 1;
|
|
|
|
let max = maxLevel - 1;
|
|
|
|
const levelsToMax = maxLevel - nodeObj.cores;
|
|
|
|
if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, Player))) {
|
|
|
|
return levelsToMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Use a binary search to find the max possible number of upgrades
|
|
|
|
while (min <= max) {
|
|
|
|
let curr = (min + max) / 2 | 0;
|
|
|
|
if (curr != maxLevel &&
|
|
|
|
Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player)) &&
|
|
|
|
Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr + 1, Player))) {
|
|
|
|
return Math.min(levelsToMax, curr);
|
|
|
|
} else if (Player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
|
|
|
|
max = curr - 1;
|
|
|
|
} else if (Player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, Player))) {
|
|
|
|
min = curr + 1;
|
|
|
|
} else {
|
|
|
|
return Math.min(levelsToMax, curr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) {
|
|
|
|
if (maxLevel == null) {
|
|
|
|
throw new Error(`getMaxNumberCacheUpgrades() called without maxLevel arg`);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(1))) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let min = 1;
|
|
|
|
let max = maxLevel - 1;
|
|
|
|
const levelsToMax = maxLevel - nodeObj.cache;
|
|
|
|
if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(levelsToMax))) {
|
|
|
|
return levelsToMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use a binary search to find the max possible number of upgrades
|
|
|
|
while (min <= max) {
|
|
|
|
let curr = (min + max) / 2 | 0;
|
|
|
|
if (curr != maxLevel &&
|
|
|
|
Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr)) &&
|
|
|
|
!Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr + 1))) {
|
|
|
|
return Math.min(levelsToMax, curr);
|
|
|
|
} else if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) {
|
|
|
|
max = curr -1 ;
|
|
|
|
} else if (Player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) {
|
|
|
|
min = curr + 1;
|
|
|
|
} else {
|
|
|
|
return Math.min(levelsToMax, curr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initial construction of Hacknet Nodes UI
|
|
|
|
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
|
|
|
|
// call the appropriate function
|
|
|
|
if (Player.hacknetNodes.length === 0) { return 0; }
|
|
|
|
if (hasHacknetServers()) {
|
2019-04-05 11:08:41 +02:00
|
|
|
return processAllHacknetServerEarnings(numCycles);
|
2019-03-25 04:03:24 +01:00
|
|
|
} else if (Player.hacknetNodes[0] instanceof HacknetNode) {
|
2019-04-05 11:08:41 +02:00
|
|
|
return processAllHacknetNodeEarnings(numCycles);
|
2019-03-25 04:03:24 +01:00
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function processAllHacknetNodeEarnings(numCycles) {
|
|
|
|
let total = 0;
|
|
|
|
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
|
|
|
|
total += processSingleHacknetNodeEarnings(numCycles, Player.hacknetNodes[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
|
|
|
|
function processSingleHacknetNodeEarnings(numCycles, nodeObj) {
|
|
|
|
const totalEarnings = nodeObj.process(numCycles);
|
|
|
|
Player.gainMoney(totalEarnings);
|
|
|
|
Player.recordMoneySource(totalEarnings, "hacknetnode");
|
|
|
|
|
|
|
|
return totalEarnings;
|
|
|
|
}
|
|
|
|
|
|
|
|
function processAllHacknetServerEarnings(numCycles) {
|
|
|
|
if (!(Player.hashManager instanceof HashManager)) {
|
|
|
|
throw new Error(`Player does not have a HashManager (should be in 'hashManager' prop)`)
|
|
|
|
}
|
|
|
|
|
|
|
|
let hashes = 0;
|
|
|
|
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
|
2019-03-27 09:36:14 +01:00
|
|
|
const hserver = AllServers[Player.hacknetNodes[i]]; // hacknetNodes array only contains the IP addresses
|
|
|
|
hashes += hserver.process(numCycles);
|
2019-03-25 04:03:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Player.hashManager.storeHashes(hashes);
|
|
|
|
|
|
|
|
return hashes;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function purchaseHashUpgrade(upgName, upgTarget) {
|
|
|
|
if (!(Player.hashManager instanceof HashManager)) {
|
|
|
|
console.error(`Player does not have a HashManager`);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// HashManager handles the transaction. This just needs to actually implement
|
|
|
|
// the effects of the upgrade
|
|
|
|
if (Player.hashManager.upgrade(upgName)) {
|
|
|
|
const upg = HashUpgrades[upgName];
|
|
|
|
|
|
|
|
switch (upgName) {
|
|
|
|
case "Sell for Money": {
|
|
|
|
Player.gainMoney(upg.value);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Sell for Corporation Funds": {
|
|
|
|
// This will throw if player doesn't have a corporation
|
|
|
|
try {
|
|
|
|
Player.corporation.funds = Player.corporation.funds.plus(upg.value);
|
|
|
|
} catch(e) {
|
|
|
|
Player.hashManager.refundUpgrade(upgName);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Reduce Minimum Security": {
|
|
|
|
try {
|
|
|
|
const target = GetServerByHostname(upgTarget);
|
|
|
|
if (target == null) {
|
|
|
|
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
target.changeMinimumSecurity(upg.value, true);
|
|
|
|
} catch(e) {
|
|
|
|
Player.hashManager.refundUpgrade(upgName);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Increase Maximum Money": {
|
|
|
|
try {
|
|
|
|
const target = GetServerByHostname(upgTarget);
|
|
|
|
if (target == null) {
|
|
|
|
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
target.changeMaximumMoney(upg.value, true);
|
|
|
|
} catch(e) {
|
|
|
|
Player.hashManager.refundUpgrade(upgName);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Improve Studying": {
|
|
|
|
// Multiplier handled by HashManager
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Improve Gym Training": {
|
|
|
|
// Multiplier handled by HashManager
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Exchange for Corporation Research": {
|
|
|
|
// This will throw if player doesn't have a corporation
|
|
|
|
try {
|
|
|
|
for (const division of Player.corporation.divisions) {
|
|
|
|
division.sciResearch.qty += upg.value;
|
|
|
|
}
|
|
|
|
} catch(e) {
|
|
|
|
Player.hashManager.refundUpgrade(upgName);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Exchange for Bladeburner Rank": {
|
2019-03-30 00:14:32 +01:00
|
|
|
// This will throw if player isnt in Bladeburner
|
2019-03-25 04:03:24 +01:00
|
|
|
try {
|
2019-03-30 00:14:32 +01:00
|
|
|
Player.bladeburner.changeRank(upg.value);
|
|
|
|
} catch(e) {
|
|
|
|
Player.hashManager.refundUpgrade(upgName);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Exchange for Bladeburner SP": {
|
|
|
|
// This will throw if player isn't in Bladeburner
|
|
|
|
try {
|
|
|
|
// As long as we don't change `Bladeburner.totalSkillPoints`, this
|
|
|
|
// shouldn't affect anything else
|
|
|
|
Player.bladeburner.skillPoints += upg.value;
|
2019-03-25 04:03:24 +01:00
|
|
|
} catch(e) {
|
|
|
|
Player.hashManager.refundUpgrade(upgName);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Generate Coding Contract": {
|
2019-04-05 11:08:41 +02:00
|
|
|
generateRandomContract();
|
2019-03-25 04:03:24 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`)
|
2019-03-30 00:14:32 +01:00
|
|
|
Player.hashManager.refundUpgrade(upgName);
|
2019-03-25 04:03:24 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|