bitburner-src/src/Hacknet/HacknetHelpers.jsx

740 lines
19 KiB
React
Raw Normal View History

2019-05-18 00:51:28 +02:00
/**
* Generic helper/utility functions for the Hacknet mechanic:
* - Purchase nodes/upgrades
* - Calculating maximum number of upgrades
* - Processing Hacknet earnings
* - Updating Hash Manager capacity
* - Purchasing hash upgrades
*
* TODO Should probably split the different types of functions into their own modules
*/
import { HacknetNode } from "./HacknetNode";
import { calculateNodeCost } from "./formulas/HacknetNodes";
import { calculateServerCost } from "./formulas/HacknetServers";
import { HacknetNodeConstants, HacknetServerConstants } from "./data/Constants";
import { HacknetServer } from "./HacknetServer";
import { HashManager } from "./HashManager";
import { HashUpgrades } from "./HashUpgrades";
import { generateRandomContract } from "../CodingContractGenerator";
import {
2021-09-05 01:09:30 +02:00
iTutorialSteps,
iTutorialNextStep,
ITutorial,
} from "../InteractiveTutorial";
import { Player } from "../Player";
2019-05-18 00:51:28 +02:00
import { AllServers } from "../Server/AllServers";
import { GetServerByHostname } from "../Server/ServerHelpers";
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";
2019-03-25 04:03:24 +01:00
let hacknetNodesDiv;
function hacknetNodesInit() {
2021-09-05 01:09:30 +02:00
hacknetNodesDiv = document.getElementById("hacknet-nodes-container");
document.removeEventListener("DOMContentLoaded", hacknetNodesInit);
2019-03-25 04:03:24 +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() {
2021-09-05 01:09:30 +02:00
return Player.bitNodeN === 9 || SourceFileFlags[9] > 0;
2019-03-25 04:03:24 +01:00
}
export function purchaseHacknet() {
2021-09-05 01:09:30 +02:00
/* INTERACTIVE TUTORIAL */
if (ITutorial.isRunning) {
if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) {
iTutorialNextStep();
} else {
return;
2019-03-25 04:03:24 +01:00
}
2021-09-05 01:09:30 +02:00
}
/* END INTERACTIVE TUTORIAL */
2021-09-05 01:09:30 +02:00
const numOwned = Player.hacknetNodes.length;
if (hasHacknetServers()) {
const cost = getCostOfNextHacknetServer();
if (isNaN(cost)) {
throw new Error(`Calculated cost of purchasing HacknetServer is NaN`);
}
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
if (!Player.canAfford(cost)) {
return -1;
}
Player.loseMoney(cost);
Player.createHacknetServer();
updateHashManagerCapacity();
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
return numOwned;
} else {
const cost = getCostOfNextHacknetNode();
if (isNaN(cost)) {
throw new Error(`Calculated cost of purchasing HacknetNode is NaN`);
}
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
if (!Player.canAfford(cost)) {
return -1;
}
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
// Auto generate a name for the Node
const name = "hacknet-node-" + numOwned;
const node = new HacknetNode(name, Player.hacknet_node_money_mult);
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
Player.loseMoney(cost);
Player.hacknetNodes.push(node);
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
return numOwned;
}
2019-03-25 04:03:24 +01:00
}
export function hasMaxNumberHacknetServers() {
2021-09-05 01:09:30 +02:00
return (
hasHacknetServers() &&
Player.hacknetNodes.length >= HacknetServerConstants.MaxServers
);
2019-03-25 04:03:24 +01:00
}
export function getCostOfNextHacknetNode() {
2021-09-05 01:09:30 +02:00
return calculateNodeCost(
Player.hacknetNodes.length + 1,
Player.hacknet_node_purchase_cost_mult,
);
2019-03-25 04:03:24 +01:00
}
export function getCostOfNextHacknetServer() {
2021-09-05 01:09:30 +02:00
return calculateServerCost(
Player.hacknetNodes.length + 1,
Player.hacknet_node_purchase_cost_mult,
);
2019-03-25 04:03:24 +01:00
}
2019-05-18 00:51:28 +02:00
// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's level
2019-03-25 04:03:24 +01:00
export function getMaxNumberLevelUpgrades(nodeObj, maxLevel) {
2021-09-05 01:09:30 +02:00
if (maxLevel == null) {
throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`);
}
if (
Player.money.lt(
nodeObj.calculateLevelUpgradeCost(1, Player.hacknet_node_level_cost_mult),
)
) {
2019-03-25 04:03:24 +01:00
return 0;
2021-09-05 01:09:30 +02:00
}
let min = 1;
let max = maxLevel - 1;
let levelsToMax = maxLevel - nodeObj.level;
if (
Player.money.gt(
nodeObj.calculateLevelUpgradeCost(
levelsToMax,
Player.hacknet_node_level_cost_mult,
),
)
) {
return levelsToMax;
}
while (min <= max) {
var curr = ((min + max) / 2) | 0;
if (
curr !== maxLevel &&
Player.money.gt(
nodeObj.calculateLevelUpgradeCost(
curr,
Player.hacknet_node_level_cost_mult,
),
) &&
Player.money.lt(
nodeObj.calculateLevelUpgradeCost(
curr + 1,
Player.hacknet_node_level_cost_mult,
),
)
) {
return Math.min(levelsToMax, curr);
} else if (
Player.money.lt(
nodeObj.calculateLevelUpgradeCost(
curr,
Player.hacknet_node_level_cost_mult,
),
)
) {
max = curr - 1;
} else if (
Player.money.gt(
nodeObj.calculateLevelUpgradeCost(
curr,
Player.hacknet_node_level_cost_mult,
),
)
) {
min = curr + 1;
} else {
return Math.min(levelsToMax, curr);
}
}
return 0;
2019-03-25 04:03:24 +01:00
}
2019-05-18 00:51:28 +02:00
// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's RAM
2019-03-25 04:03:24 +01:00
export function getMaxNumberRamUpgrades(nodeObj, maxLevel) {
2021-09-05 01:09:30 +02:00
if (maxLevel == null) {
throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`);
}
if (
Player.money.lt(
nodeObj.calculateRamUpgradeCost(1, Player.hacknet_node_ram_cost_mult),
)
) {
2019-03-25 04:03:24 +01:00
return 0;
2021-09-05 01:09:30 +02:00
}
let levelsToMax;
if (nodeObj instanceof HacknetServer) {
levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.maxRam));
} else {
levelsToMax = Math.round(Math.log2(maxLevel / nodeObj.ram));
}
if (
Player.money.gt(
nodeObj.calculateRamUpgradeCost(
levelsToMax,
Player.hacknet_node_ram_cost_mult,
),
)
) {
return levelsToMax;
}
//We'll just loop until we find the max
for (let i = levelsToMax - 1; i >= 0; --i) {
if (
Player.money.gt(
nodeObj.calculateRamUpgradeCost(i, Player.hacknet_node_ram_cost_mult),
)
) {
return i;
}
}
return 0;
2019-03-25 04:03:24 +01:00
}
2019-05-18 00:51:28 +02:00
// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cores
2019-03-25 04:03:24 +01:00
export function getMaxNumberCoreUpgrades(nodeObj, maxLevel) {
2021-09-05 01:09:30 +02:00
if (maxLevel == null) {
throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`);
}
if (
Player.money.lt(
nodeObj.calculateCoreUpgradeCost(1, Player.hacknet_node_core_cost_mult),
)
) {
return 0;
}
let min = 1;
let max = maxLevel - 1;
const levelsToMax = maxLevel - nodeObj.cores;
if (
Player.money.gt(
nodeObj.calculateCoreUpgradeCost(
levelsToMax,
Player.hacknet_node_core_cost_mult,
),
)
) {
return levelsToMax;
}
// Use a binary search to find the max possible number of upgrades
while (min <= max) {
let curr = ((min + max) / 2) | 0;
if (
curr != maxLevel &&
Player.money.gt(
nodeObj.calculateCoreUpgradeCost(
curr,
Player.hacknet_node_core_cost_mult,
),
) &&
Player.money.lt(
nodeObj.calculateCoreUpgradeCost(
curr + 1,
Player.hacknet_node_core_cost_mult,
),
)
) {
return Math.min(levelsToMax, curr);
} else if (
Player.money.lt(
nodeObj.calculateCoreUpgradeCost(
curr,
Player.hacknet_node_core_cost_mult,
),
)
) {
max = curr - 1;
} else if (
Player.money.gt(
nodeObj.calculateCoreUpgradeCost(
curr,
Player.hacknet_node_core_cost_mult,
),
)
) {
min = curr + 1;
} else {
return Math.min(levelsToMax, curr);
2019-03-25 04:03:24 +01:00
}
2021-09-05 01:09:30 +02:00
}
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
return 0;
2019-03-25 04:03:24 +01:00
}
2019-05-18 00:51:28 +02:00
// Calculate the maximum number of times the Player can afford to upgrade a Hacknet Node's cache
2019-03-25 04:03:24 +01:00
export function getMaxNumberCacheUpgrades(nodeObj, maxLevel) {
2021-09-05 01:09:30 +02:00
if (maxLevel == null) {
throw new Error(`getMaxNumberCacheUpgrades() called without maxLevel arg`);
}
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
if (!Player.canAfford(nodeObj.calculateCacheUpgradeCost(1))) {
2019-03-25 04:03:24 +01:00
return 0;
2021-09-05 01:09:30 +02:00
}
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);
}
2021-09-05 01:09:30 +02:00
}
2021-09-05 01:09:30 +02:00
return 0;
}
2021-09-05 01:09:30 +02:00
export function purchaseLevelUpgrade(node, levels = 1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateLevelUpgradeCost(
sanitizedLevels,
Player.hacknet_node_level_cost_mult,
);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
2021-09-05 01:09:30 +02:00
const isServer = node instanceof HacknetServer;
2021-09-05 01:09:30 +02:00
// If we're at max level, return false
if (
node.level >=
(isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel)
) {
return false;
}
// If the number of specified upgrades would exceed the max level, calculate
// the maximum number of upgrades and use that
if (
node.level + sanitizedLevels >
(isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel)
) {
const diff = Math.max(
0,
(isServer
? HacknetServerConstants.MaxLevel
: HacknetNodeConstants.MaxLevel) - node.level,
);
return purchaseLevelUpgrade(node, diff);
}
if (!Player.canAfford(cost)) {
return false;
}
2021-09-05 01:09:30 +02:00
Player.loseMoney(cost);
node.upgradeLevel(sanitizedLevels, Player.hacknet_node_money_mult);
2021-09-05 01:09:30 +02:00
return true;
}
2021-09-05 01:09:30 +02:00
export function purchaseRamUpgrade(node, levels = 1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateRamUpgradeCost(
sanitizedLevels,
Player.hacknet_node_ram_cost_mult,
);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
2021-09-05 01:09:30 +02:00
const isServer = node instanceof HacknetServer;
2021-09-05 01:09:30 +02:00
// Fail if we're already at max
if (
node.ram >=
(isServer ? HacknetServerConstants.MaxRam : HacknetNodeConstants.MaxRam)
) {
return false;
}
// If the number of specified upgrades would exceed the max RAM, calculate the
// max possible number of upgrades and use that
if (isServer) {
if (
node.maxRam * Math.pow(2, sanitizedLevels) >
HacknetServerConstants.MaxRam
) {
const diff = Math.max(
0,
Math.log2(Math.round(HacknetServerConstants.MaxRam / node.maxRam)),
);
return purchaseRamUpgrade(node, diff);
}
} else {
if (node.ram * Math.pow(2, sanitizedLevels) > HacknetNodeConstants.MaxRam) {
const diff = Math.max(
0,
Math.log2(Math.round(HacknetNodeConstants.MaxRam / node.ram)),
);
return purchaseRamUpgrade(node, diff);
}
}
if (!Player.canAfford(cost)) {
return false;
}
2021-09-05 01:09:30 +02:00
Player.loseMoney(cost);
node.upgradeRam(sanitizedLevels, Player.hacknet_node_money_mult);
2021-09-05 01:09:30 +02:00
return true;
}
2021-09-05 01:09:30 +02:00
export function purchaseCoreUpgrade(node, levels = 1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateCoreUpgradeCost(
sanitizedLevels,
Player.hacknet_node_core_cost_mult,
);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
2021-09-05 01:09:30 +02:00
const isServer = node instanceof HacknetServer;
2021-09-05 01:09:30 +02:00
// Fail if we're already at max
if (
node.cores >=
(isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores)
) {
return false;
}
// If the specified number of upgrades would exceed the max Cores, calculate
// the max possible number of upgrades and use that
if (
node.cores + sanitizedLevels >
(isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores)
) {
const diff = Math.max(
0,
(isServer
? HacknetServerConstants.MaxCores
: HacknetNodeConstants.MaxCores) - node.cores,
);
return purchaseCoreUpgrade(node, diff);
}
if (!Player.canAfford(cost)) {
return false;
}
2021-09-05 01:09:30 +02:00
Player.loseMoney(cost);
node.upgradeCore(sanitizedLevels, Player.hacknet_node_money_mult);
2021-09-05 01:09:30 +02:00
return true;
}
2021-09-05 01:09:30 +02:00
export function purchaseCacheUpgrade(node, levels = 1) {
const sanitizedLevels = Math.round(levels);
const cost = node.calculateCacheUpgradeCost(sanitizedLevels);
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
return false;
}
2021-09-05 01:09:30 +02:00
if (!(node instanceof HacknetServer)) {
console.warn(`purchaseCacheUpgrade() called for a non-HacknetNode`);
return false;
}
2021-09-05 01:09:30 +02:00
// Fail if we're already at max
if (node.cache + sanitizedLevels > HacknetServerConstants.MaxCache) {
const diff = Math.max(0, HacknetServerConstants.MaxCache - node.cache);
return purchaseCacheUpgrade(node, diff);
}
2021-09-05 01:09:30 +02:00
if (!Player.canAfford(cost)) {
return false;
}
2021-09-05 01:09:30 +02:00
Player.loseMoney(cost);
node.upgradeCache(sanitizedLevels);
2021-09-05 01:09:30 +02:00
return true;
}
// Create/Refresh Hacknet Nodes UI
2019-03-25 04:03:24 +01:00
export function renderHacknetNodesUI() {
2021-09-05 01:09:30 +02:00
if (!routing.isOn(Page.HacknetNodes)) {
return;
}
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
ReactDOM.render(<HacknetRoot />, hacknetNodesDiv);
2019-03-25 04:03:24 +01:00
}
export function clearHacknetNodesUI() {
2021-09-05 01:09:30 +02:00
if (hacknetNodesDiv instanceof HTMLElement) {
ReactDOM.unmountComponentAtNode(hacknetNodesDiv);
}
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
hacknetNodesDiv.style.display = "none";
2019-03-25 04:03:24 +01:00
}
export function processHacknetEarnings(numCycles) {
2021-09-05 01:09:30 +02:00
// Determine if player has Hacknet Nodes or Hacknet Servers, then
// call the appropriate function
if (Player.hacknetNodes.length === 0) {
return 0;
}
if (hasHacknetServers()) {
return processAllHacknetServerEarnings(numCycles);
} else if (Player.hacknetNodes[0] instanceof HacknetNode) {
return processAllHacknetNodeEarnings(numCycles);
} else {
return 0;
}
2019-03-25 04:03:24 +01:00
}
function processAllHacknetNodeEarnings(numCycles) {
2021-09-05 01:09:30 +02:00
let total = 0;
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
total += processSingleHacknetNodeEarnings(
numCycles,
Player.hacknetNodes[i],
);
}
return total;
2019-03-25 04:03:24 +01:00
}
function processSingleHacknetNodeEarnings(numCycles, nodeObj) {
2021-09-05 01:09:30 +02:00
const totalEarnings = nodeObj.process(numCycles);
Player.gainMoney(totalEarnings);
Player.recordMoneySource(totalEarnings, "hacknetnode");
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
return totalEarnings;
2019-03-25 04:03:24 +01:00
}
function processAllHacknetServerEarnings(numCycles) {
2021-09-05 01:09:30 +02:00
if (!(Player.hashManager instanceof HashManager)) {
throw new Error(
`Player does not have a HashManager (should be in 'hashManager' prop)`,
);
}
let hashes = 0;
for (let i = 0; i < Player.hacknetNodes.length; ++i) {
// hacknetNodes array only contains the IP addresses of the servers.
// Also, update the hash rate before processing
const hserver = AllServers[Player.hacknetNodes[i]];
hserver.updateHashRate(Player.hacknet_node_money_mult);
const h = hserver.process(numCycles);
hserver.totalHashesGenerated += h;
hashes += h;
}
Player.hashManager.storeHashes(hashes);
return hashes;
2019-03-25 04:03:24 +01:00
}
export function updateHashManagerCapacity() {
2021-09-05 01:09:30 +02:00
if (!(Player.hashManager instanceof HashManager)) {
console.error(`Player does not have a HashManager`);
return;
}
2021-09-05 01:09:30 +02:00
const nodes = Player.hacknetNodes;
if (nodes.length === 0) {
Player.hashManager.updateCapacity(0);
return;
}
2021-09-05 01:09:30 +02:00
let total = 0;
for (let i = 0; i < nodes.length; ++i) {
if (typeof nodes[i] !== "string") {
Player.hashManager.updateCapacity(0);
return;
}
2021-09-05 01:09:30 +02:00
const h = AllServers[nodes[i]];
if (!(h instanceof HacknetServer)) {
Player.hashManager.updateCapacity(0);
return;
}
2021-09-05 01:09:30 +02:00
total += h.hashCapacity;
}
Player.hashManager.updateCapacity(total);
}
2019-03-25 04:03:24 +01:00
export function purchaseHashUpgrade(upgName, upgTarget) {
2021-09-05 01:09:30 +02:00
if (!(Player.hashManager instanceof HashManager)) {
console.error(`Player does not have a HashManager`);
return false;
}
// HashManager handles the transaction. This just needs to actually implement
// the effects of the upgrade
if (Player.hashManager.upgrade(upgName)) {
const upg = HashUpgrades[upgName];
switch (upgName) {
case "Sell for Money": {
Player.gainMoney(upg.value);
Player.recordMoneySource(upg.value, "hacknetnode");
break;
}
case "Sell for Corporation Funds": {
// This will throw if player doesn't have a corporation
try {
Player.corporation.funds = Player.corporation.funds.plus(upg.value);
} catch (e) {
Player.hashManager.refundUpgrade(upgName);
return false;
}
break;
}
case "Reduce Minimum Security": {
try {
const target = GetServerByHostname(upgTarget);
if (target == null) {
console.error(
`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`,
);
return false;
}
target.changeMinimumSecurity(upg.value, true);
} catch (e) {
Player.hashManager.refundUpgrade(upgName);
return false;
}
break;
}
case "Increase Maximum Money": {
try {
const target = GetServerByHostname(upgTarget);
if (target == null) {
console.error(
`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`,
);
return false;
}
target.changeMaximumMoney(upg.value, true);
} catch (e) {
Player.hashManager.refundUpgrade(upgName);
return false;
}
break;
}
case "Improve Studying": {
// Multiplier handled by HashManager
break;
}
case "Improve Gym Training": {
// Multiplier handled by HashManager
break;
}
case "Exchange for Corporation Research": {
// This will throw if player doesn't have a corporation
try {
for (const division of Player.corporation.divisions) {
division.sciResearch.qty += upg.value;
}
} catch (e) {
Player.hashManager.refundUpgrade(upgName);
return false;
}
break;
}
case "Exchange for Bladeburner Rank": {
// This will throw if player isnt in Bladeburner
try {
Player.bladeburner.changeRank(Player, upg.value);
} catch (e) {
Player.hashManager.refundUpgrade(upgName);
return false;
}
break;
}
case "Exchange for Bladeburner SP": {
// This will throw if player isn't in Bladeburner
try {
// As long as we don't change `Bladeburner.totalSkillPoints`, this
// shouldn't affect anything else
Player.bladeburner.skillPoints += upg.value;
} catch (e) {
Player.hashManager.refundUpgrade(upgName);
return false;
}
break;
}
case "Generate Coding Contract": {
generateRandomContract();
break;
}
default:
console.warn(
`Unrecognized upgrade name ${upgName}. Upgrade has no effect`,
);
Player.hashManager.refundUpgrade(upgName);
2019-03-25 04:03:24 +01:00
return false;
}
2021-09-05 01:09:30 +02:00
return true;
}
2019-03-25 04:03:24 +01:00
2021-09-05 01:09:30 +02:00
return false;
2019-03-25 04:03:24 +01:00
}