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
|
|
|
|
*/
|
2021-03-31 06:45:21 +02:00
|
|
|
import { HacknetNode } from "./HacknetNode";
|
|
|
|
import { calculateNodeCost } from "./formulas/HacknetNodes";
|
|
|
|
import { calculateServerCost } from "./formulas/HacknetServers";
|
|
|
|
import { HacknetNodeConstants, HacknetServerConstants } from "./data/Constants";
|
|
|
|
import { HacknetServer } from "./HacknetServer";
|
2019-05-05 06:03:40 +02:00
|
|
|
import { HashManager } from "./HashManager";
|
|
|
|
import { HashUpgrades } from "./HashUpgrades";
|
|
|
|
|
|
|
|
import { generateRandomContract } from "../CodingContractGenerator";
|
2021-09-09 05:47:34 +02:00
|
|
|
import { iTutorialSteps, iTutorialNextStep, ITutorial } from "../InteractiveTutorial";
|
2021-09-09 09:17:01 +02:00
|
|
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
2019-05-18 00:51:28 +02:00
|
|
|
import { AllServers } from "../Server/AllServers";
|
2019-05-05 06:03:40 +02:00
|
|
|
import { GetServerByHostname } from "../Server/ServerHelpers";
|
2021-09-09 09:17:01 +02:00
|
|
|
import { Server } from "../Server/Server";
|
2019-05-05 06:03:40 +02:00
|
|
|
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
2019-03-25 04:03:24 +01:00
|
|
|
|
|
|
|
// Returns a boolean indicating whether the player has Hacknet Servers
|
|
|
|
// (the upgraded form of Hacknet Nodes)
|
2021-09-09 09:17:01 +02:00
|
|
|
export function hasHacknetServers(player: IPlayer): boolean {
|
|
|
|
return player.bitNodeN === 9 || SourceFileFlags[9] > 0;
|
2019-03-25 04:03:24 +01:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
export function purchaseHacknet(player: IPlayer): number {
|
2021-09-05 01:09:30 +02:00
|
|
|
/* INTERACTIVE TUTORIAL */
|
|
|
|
if (ITutorial.isRunning) {
|
|
|
|
if (ITutorial.currStep === iTutorialSteps.HacknetNodesIntroduction) {
|
|
|
|
iTutorialNextStep();
|
|
|
|
} else {
|
2021-09-09 09:17:01 +02:00
|
|
|
return -1;
|
2019-03-25 04:03:24 +01:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
/* END INTERACTIVE TUTORIAL */
|
2019-05-14 10:35:37 +02:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
const numOwned = player.hacknetNodes.length;
|
|
|
|
if (hasHacknetServers(player)) {
|
|
|
|
const cost = getCostOfNextHacknetServer(player);
|
2021-09-05 01:09:30 +02:00
|
|
|
if (isNaN(cost)) {
|
|
|
|
throw new Error(`Calculated cost of purchasing HacknetServer is NaN`);
|
|
|
|
}
|
2019-03-25 04:03:24 +01:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
if (!player.canAfford(cost)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2021-09-09 09:17:01 +02:00
|
|
|
player.loseMoney(cost);
|
|
|
|
player.createHacknetServer();
|
|
|
|
updateHashManagerCapacity(player);
|
2019-03-25 04:03:24 +01:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
return numOwned;
|
|
|
|
} else {
|
2021-09-09 09:17:01 +02:00
|
|
|
const cost = getCostOfNextHacknetNode(player);
|
2021-09-05 01:09:30 +02:00
|
|
|
if (isNaN(cost)) {
|
|
|
|
throw new Error(`Calculated cost of purchasing HacknetNode is NaN`);
|
|
|
|
}
|
2019-03-25 04:03:24 +01:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
if (!player.canAfford(cost)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
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;
|
2021-09-09 09:17:01 +02:00
|
|
|
const node = new HacknetNode(name, player.hacknet_node_money_mult);
|
2019-03-25 04:03:24 +01:00
|
|
|
|
2021-09-09 09:17:01 +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
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
export function hasMaxNumberHacknetServers(player: IPlayer): boolean {
|
|
|
|
return hasHacknetServers(player) && player.hacknetNodes.length >= HacknetServerConstants.MaxServers;
|
2019-03-25 04:03:24 +01:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
export function getCostOfNextHacknetNode(player: IPlayer): number {
|
|
|
|
return calculateNodeCost(player.hacknetNodes.length + 1, player.hacknet_node_purchase_cost_mult);
|
2019-03-25 04:03:24 +01:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
export function getCostOfNextHacknetServer(player: IPlayer): number {
|
|
|
|
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
|
2021-09-09 09:17:01 +02:00
|
|
|
export function getMaxNumberLevelUpgrades(
|
|
|
|
player: IPlayer,
|
|
|
|
nodeObj: HacknetNode | HacknetServer,
|
|
|
|
maxLevel: number,
|
|
|
|
): number {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (maxLevel == null) {
|
|
|
|
throw new Error(`getMaxNumberLevelUpgrades() called without maxLevel arg`);
|
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
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;
|
2021-09-09 09:17:01 +02:00
|
|
|
const levelsToMax = maxLevel - nodeObj.level;
|
|
|
|
if (player.money.gt(nodeObj.calculateLevelUpgradeCost(levelsToMax, player.hacknet_node_level_cost_mult))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return levelsToMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (min <= max) {
|
2021-09-09 09:17:01 +02:00
|
|
|
const curr = ((min + max) / 2) | 0;
|
2021-09-05 01:09:30 +02:00
|
|
|
if (
|
|
|
|
curr !== maxLevel &&
|
2021-09-09 09:17:01 +02:00
|
|
|
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))
|
2021-09-05 01:09:30 +02:00
|
|
|
) {
|
|
|
|
return Math.min(levelsToMax, curr);
|
2021-09-09 09:17:01 +02:00
|
|
|
} else if (player.money.lt(nodeObj.calculateLevelUpgradeCost(curr, player.hacknet_node_level_cost_mult))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
max = curr - 1;
|
2021-09-09 09:17:01 +02:00
|
|
|
} else if (player.money.gt(nodeObj.calculateLevelUpgradeCost(curr, player.hacknet_node_level_cost_mult))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
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
|
2021-09-09 09:17:01 +02:00
|
|
|
export function getMaxNumberRamUpgrades(
|
|
|
|
player: IPlayer,
|
|
|
|
nodeObj: HacknetNode | HacknetServer,
|
|
|
|
maxLevel: number,
|
|
|
|
): number {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (maxLevel == null) {
|
|
|
|
throw new Error(`getMaxNumberRamUpgrades() called without maxLevel arg`);
|
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
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));
|
|
|
|
}
|
2021-09-09 09:17:01 +02:00
|
|
|
if (player.money.gt(nodeObj.calculateRamUpgradeCost(levelsToMax, player.hacknet_node_ram_cost_mult))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return levelsToMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
//We'll just loop until we find the max
|
|
|
|
for (let i = levelsToMax - 1; i >= 0; --i) {
|
2021-09-09 09:17:01 +02:00
|
|
|
if (player.money.gt(nodeObj.calculateRamUpgradeCost(i, player.hacknet_node_ram_cost_mult))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
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
|
2021-09-09 09:17:01 +02:00
|
|
|
export function getMaxNumberCoreUpgrades(
|
|
|
|
player: IPlayer,
|
|
|
|
nodeObj: HacknetNode | HacknetServer,
|
|
|
|
maxLevel: number,
|
|
|
|
): number {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (maxLevel == null) {
|
|
|
|
throw new Error(`getMaxNumberCoreUpgrades() called without maxLevel arg`);
|
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
if (player.money.lt(nodeObj.calculateCoreUpgradeCost(1, player.hacknet_node_core_cost_mult))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let min = 1;
|
|
|
|
let max = maxLevel - 1;
|
|
|
|
const levelsToMax = maxLevel - nodeObj.cores;
|
2021-09-09 09:17:01 +02:00
|
|
|
if (player.money.gt(nodeObj.calculateCoreUpgradeCost(levelsToMax, player.hacknet_node_core_cost_mult))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return levelsToMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use a binary search to find the max possible number of upgrades
|
|
|
|
while (min <= max) {
|
2021-09-09 09:17:01 +02:00
|
|
|
const curr = ((min + max) / 2) | 0;
|
2021-09-05 01:09:30 +02:00
|
|
|
if (
|
|
|
|
curr != maxLevel &&
|
2021-09-09 09:17:01 +02:00
|
|
|
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))
|
2021-09-05 01:09:30 +02:00
|
|
|
) {
|
|
|
|
return Math.min(levelsToMax, curr);
|
2021-09-09 09:17:01 +02:00
|
|
|
} else if (player.money.lt(nodeObj.calculateCoreUpgradeCost(curr, player.hacknet_node_core_cost_mult))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
max = curr - 1;
|
2021-09-09 09:17:01 +02:00
|
|
|
} else if (player.money.gt(nodeObj.calculateCoreUpgradeCost(curr, player.hacknet_node_core_cost_mult))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
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
|
2021-09-09 09:17:01 +02:00
|
|
|
export function getMaxNumberCacheUpgrades(player: IPlayer, nodeObj: HacknetServer, maxLevel: number): number {
|
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-09 09:17:01 +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;
|
2021-09-09 09:17:01 +02:00
|
|
|
if (player.canAfford(nodeObj.calculateCacheUpgradeCost(levelsToMax))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return levelsToMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use a binary search to find the max possible number of upgrades
|
|
|
|
while (min <= max) {
|
2021-09-09 09:17:01 +02:00
|
|
|
const curr = ((min + max) / 2) | 0;
|
2021-09-05 01:09:30 +02:00
|
|
|
if (
|
|
|
|
curr != maxLevel &&
|
2021-09-09 09:17:01 +02:00
|
|
|
player.canAfford(nodeObj.calculateCacheUpgradeCost(curr)) &&
|
|
|
|
!player.canAfford(nodeObj.calculateCacheUpgradeCost(curr + 1))
|
2021-09-05 01:09:30 +02:00
|
|
|
) {
|
|
|
|
return Math.min(levelsToMax, curr);
|
2021-09-09 09:17:01 +02:00
|
|
|
} else if (!player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
max = curr - 1;
|
2021-09-09 09:17:01 +02:00
|
|
|
} else if (player.canAfford(nodeObj.calculateCacheUpgradeCost(curr))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
min = curr + 1;
|
|
|
|
} else {
|
|
|
|
return Math.min(levelsToMax, curr);
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
export function purchaseLevelUpgrade(player: IPlayer, node: HacknetNode | HacknetServer, levels = 1): boolean {
|
2021-09-05 01:09:30 +02:00
|
|
|
const sanitizedLevels = Math.round(levels);
|
2021-09-09 09:17:01 +02:00
|
|
|
const cost = node.calculateLevelUpgradeCost(sanitizedLevels, player.hacknet_node_level_cost_mult);
|
2021-09-05 01:09:30 +02:00
|
|
|
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
const isServer = node instanceof HacknetServer;
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
// If we're at max level, return false
|
2021-09-09 05:47:34 +02:00
|
|
|
if (node.level >= (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the number of specified upgrades would exceed the max level, calculate
|
|
|
|
// the maximum number of upgrades and use that
|
2021-09-09 05:47:34 +02:00
|
|
|
if (node.level + sanitizedLevels > (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel)) {
|
|
|
|
const diff = Math.max(0, (isServer ? HacknetServerConstants.MaxLevel : HacknetNodeConstants.MaxLevel) - node.level);
|
2021-09-09 09:17:01 +02:00
|
|
|
return purchaseLevelUpgrade(player, node, diff);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
if (!player.canAfford(cost)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
player.loseMoney(cost);
|
|
|
|
node.upgradeLevel(sanitizedLevels, player.hacknet_node_money_mult);
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
return true;
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
export function purchaseRamUpgrade(player: IPlayer, node: HacknetNode | HacknetServer, levels = 1): boolean {
|
2021-09-05 01:09:30 +02:00
|
|
|
const sanitizedLevels = Math.round(levels);
|
2021-09-09 09:17:01 +02:00
|
|
|
const cost = node.calculateRamUpgradeCost(sanitizedLevels, player.hacknet_node_ram_cost_mult);
|
2021-09-05 01:09:30 +02:00
|
|
|
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
if (node instanceof HacknetServer && node.maxRam >= HacknetServerConstants.MaxRam) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
if (node instanceof HacknetNode && node.ram >= HacknetNodeConstants.MaxRam) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the number of specified upgrades would exceed the max RAM, calculate the
|
|
|
|
// max possible number of upgrades and use that
|
2021-09-09 09:17:01 +02:00
|
|
|
if (node instanceof HacknetServer) {
|
2021-09-09 05:47:34 +02:00
|
|
|
if (node.maxRam * Math.pow(2, sanitizedLevels) > HacknetServerConstants.MaxRam) {
|
|
|
|
const diff = Math.max(0, Math.log2(Math.round(HacknetServerConstants.MaxRam / node.maxRam)));
|
2021-09-09 09:17:01 +02:00
|
|
|
return purchaseRamUpgrade(player, node, diff);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-09-09 09:17:01 +02:00
|
|
|
} else if (node instanceof HacknetNode) {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (node.ram * Math.pow(2, sanitizedLevels) > HacknetNodeConstants.MaxRam) {
|
2021-09-09 05:47:34 +02:00
|
|
|
const diff = Math.max(0, Math.log2(Math.round(HacknetNodeConstants.MaxRam / node.ram)));
|
2021-09-09 09:17:01 +02:00
|
|
|
return purchaseRamUpgrade(player, node, diff);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
if (!player.canAfford(cost)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
player.loseMoney(cost);
|
|
|
|
node.upgradeRam(sanitizedLevels, player.hacknet_node_money_mult);
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
return true;
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
export function purchaseCoreUpgrade(player: IPlayer, node: HacknetNode | HacknetServer, levels = 1): boolean {
|
2021-09-05 01:09:30 +02:00
|
|
|
const sanitizedLevels = Math.round(levels);
|
2021-09-09 09:17:01 +02:00
|
|
|
const cost = node.calculateCoreUpgradeCost(sanitizedLevels, player.hacknet_node_core_cost_mult);
|
2021-09-05 01:09:30 +02:00
|
|
|
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
const isServer = node instanceof HacknetServer;
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
// Fail if we're already at max
|
2021-09-09 05:47:34 +02:00
|
|
|
if (node.cores >= (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the specified number of upgrades would exceed the max Cores, calculate
|
|
|
|
// the max possible number of upgrades and use that
|
2021-09-09 05:47:34 +02:00
|
|
|
if (node.cores + sanitizedLevels > (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores)) {
|
|
|
|
const diff = Math.max(0, (isServer ? HacknetServerConstants.MaxCores : HacknetNodeConstants.MaxCores) - node.cores);
|
2021-09-09 09:17:01 +02:00
|
|
|
return purchaseCoreUpgrade(player, node, diff);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
if (!player.canAfford(cost)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
player.loseMoney(cost);
|
|
|
|
node.upgradeCore(sanitizedLevels, player.hacknet_node_money_mult);
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
return true;
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
export function purchaseCacheUpgrade(player: IPlayer, node: HacknetServer, levels = 1): boolean {
|
2021-09-05 01:09:30 +02:00
|
|
|
const sanitizedLevels = Math.round(levels);
|
|
|
|
const cost = node.calculateCacheUpgradeCost(sanitizedLevels);
|
|
|
|
if (isNaN(cost) || cost <= 0 || sanitizedLevels < 0) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
if (!(node instanceof HacknetServer)) {
|
|
|
|
console.warn(`purchaseCacheUpgrade() called for a non-HacknetNode`);
|
|
|
|
return false;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
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);
|
2021-09-09 09:17:01 +02:00
|
|
|
return purchaseCacheUpgrade(player, node, diff);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
if (!player.canAfford(cost)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
player.loseMoney(cost);
|
2021-09-05 01:09:30 +02:00
|
|
|
node.upgradeCache(sanitizedLevels);
|
2019-05-17 08:55:21 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
return true;
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
export function processHacknetEarnings(player: IPlayer, numCycles: number): number {
|
2021-09-05 01:09:30 +02:00
|
|
|
// Determine if player has Hacknet Nodes or Hacknet Servers, then
|
|
|
|
// call the appropriate function
|
2021-09-09 09:17:01 +02:00
|
|
|
if (player.hacknetNodes.length === 0) {
|
2021-09-05 01:09:30 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2021-09-09 09:17:01 +02:00
|
|
|
if (hasHacknetServers(player)) {
|
|
|
|
return processAllHacknetServerEarnings(player, numCycles);
|
|
|
|
} else if (player.hacknetNodes[0] instanceof HacknetNode) {
|
|
|
|
return processAllHacknetNodeEarnings(player, numCycles);
|
2021-09-05 01:09:30 +02:00
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
2019-03-25 04:03:24 +01:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
function processAllHacknetNodeEarnings(player: IPlayer, numCycles: number): number {
|
2021-09-05 01:09:30 +02:00
|
|
|
let total = 0;
|
2021-09-09 09:17:01 +02:00
|
|
|
for (let i = 0; i < player.hacknetNodes.length; ++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);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return total;
|
2019-03-25 04:03:24 +01:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
function processSingleHacknetNodeEarnings(player: IPlayer, numCycles: number, nodeObj: HacknetNode): number {
|
2021-09-05 01:09:30 +02:00
|
|
|
const totalEarnings = nodeObj.process(numCycles);
|
2021-09-09 09:17:01 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
function processAllHacknetServerEarnings(player: IPlayer, numCycles: number): number {
|
|
|
|
if (!(player.hashManager instanceof HashManager)) {
|
2021-09-09 05:47:34 +02:00
|
|
|
throw new Error(`Player does not have a HashManager (should be in 'hashManager' prop)`);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
let hashes = 0;
|
2021-09-09 09:17:01 +02:00
|
|
|
for (let i = 0; i < player.hacknetNodes.length; ++i) {
|
2021-09-05 01:09:30 +02:00
|
|
|
// hacknetNodes array only contains the IP addresses of the servers.
|
|
|
|
// Also, update the hash rate before processing
|
2021-09-09 09:17:01 +02:00
|
|
|
const ip = player.hacknetNodes[i];
|
|
|
|
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);
|
2021-09-05 01:09:30 +02:00
|
|
|
const h = hserver.process(numCycles);
|
|
|
|
hashes += h;
|
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
player.hashManager.storeHashes(hashes);
|
2021-09-05 01:09:30 +02:00
|
|
|
|
|
|
|
return hashes;
|
2019-03-25 04:03:24 +01:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
export function updateHashManagerCapacity(player: IPlayer): void {
|
|
|
|
if (!(player.hashManager instanceof HashManager)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
console.error(`Player does not have a HashManager`);
|
|
|
|
return;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
const nodes = player.hacknetNodes;
|
2021-09-05 01:09:30 +02:00
|
|
|
if (nodes.length === 0) {
|
2021-09-09 09:17:01 +02:00
|
|
|
player.hashManager.updateCapacity(0);
|
2021-09-05 01:09:30 +02:00
|
|
|
return;
|
|
|
|
}
|
2019-05-05 06:03:40 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
let total = 0;
|
|
|
|
for (let i = 0; i < nodes.length; ++i) {
|
|
|
|
if (typeof nodes[i] !== "string") {
|
2021-09-09 09:17:01 +02:00
|
|
|
player.hashManager.updateCapacity(0);
|
2021-09-05 01:09:30 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-09-09 09:17:01 +02:00
|
|
|
const ip = nodes[i];
|
|
|
|
if (ip instanceof HacknetNode) throw new Error(`player nodes should be string but isn't`);
|
|
|
|
const h = AllServers[ip];
|
2021-09-05 01:09:30 +02:00
|
|
|
if (!(h instanceof HacknetServer)) {
|
2021-09-09 09:17:01 +02:00
|
|
|
player.hashManager.updateCapacity(0);
|
2021-09-05 01:09:30 +02:00
|
|
|
return;
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
total += h.hashCapacity;
|
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
player.hashManager.updateCapacity(total);
|
2019-05-05 06:03:40 +02:00
|
|
|
}
|
|
|
|
|
2021-09-09 09:17:01 +02:00
|
|
|
export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget: string): boolean {
|
|
|
|
if (!(player.hashManager instanceof HashManager)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
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
|
2021-09-09 09:17:01 +02:00
|
|
|
if (player.hashManager.upgrade(upgName)) {
|
2021-09-05 01:09:30 +02:00
|
|
|
const upg = HashUpgrades[upgName];
|
|
|
|
|
|
|
|
switch (upgName) {
|
|
|
|
case "Sell for Money": {
|
2021-09-09 09:17:01 +02:00
|
|
|
player.gainMoney(upg.value);
|
|
|
|
player.recordMoneySource(upg.value, "hacknetnode");
|
2021-09-05 01:09:30 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Sell for Corporation Funds": {
|
2021-09-24 00:47:43 +02:00
|
|
|
const corp = player.corporation;
|
|
|
|
if (corp === null) {
|
2021-09-09 09:17:01 +02:00
|
|
|
player.hashManager.refundUpgrade(upgName);
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
2021-09-24 00:47:43 +02:00
|
|
|
corp.funds = corp.funds.plus(upg.value);
|
2021-09-05 01:09:30 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Reduce Minimum Security": {
|
|
|
|
try {
|
|
|
|
const target = GetServerByHostname(upgTarget);
|
|
|
|
if (target == null) {
|
2021-09-09 05:47:34 +02:00
|
|
|
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
2021-09-09 09:17:01 +02:00
|
|
|
if (!(target instanceof Server)) throw new Error(`'${upgTarget}' is not a normal server.`);
|
2021-09-05 01:09:30 +02:00
|
|
|
|
|
|
|
target.changeMinimumSecurity(upg.value, true);
|
|
|
|
} catch (e) {
|
2021-09-09 09:17:01 +02:00
|
|
|
player.hashManager.refundUpgrade(upgName);
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Increase Maximum Money": {
|
|
|
|
try {
|
|
|
|
const target = GetServerByHostname(upgTarget);
|
|
|
|
if (target == null) {
|
2021-09-09 05:47:34 +02:00
|
|
|
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
2021-09-09 09:17:01 +02:00
|
|
|
if (!(target instanceof Server)) throw new Error(`'${upgTarget}' is not a normal server.`);
|
2021-09-05 01:09:30 +02:00
|
|
|
|
|
|
|
target.changeMaximumMoney(upg.value, true);
|
|
|
|
} catch (e) {
|
2021-09-09 09:17:01 +02:00
|
|
|
player.hashManager.refundUpgrade(upgName);
|
2021-09-05 01:09:30 +02:00
|
|
|
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
|
2021-09-24 00:47:43 +02:00
|
|
|
const corp = player.corporation;
|
|
|
|
if (corp === null) {
|
2021-09-09 09:17:01 +02:00
|
|
|
player.hashManager.refundUpgrade(upgName);
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
2021-09-24 00:47:43 +02:00
|
|
|
for (const division of corp.divisions) {
|
|
|
|
division.sciResearch.qty += upg.value;
|
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Exchange for Bladeburner Rank": {
|
|
|
|
// This will throw if player isnt in Bladeburner
|
2021-09-24 00:47:43 +02:00
|
|
|
const bladeburner = player.bladeburner;
|
|
|
|
if (bladeburner === null) {
|
2021-09-09 09:17:01 +02:00
|
|
|
player.hashManager.refundUpgrade(upgName);
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
2021-09-24 00:47:43 +02:00
|
|
|
bladeburner.changeRank(player, upg.value);
|
2021-09-05 01:09:30 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Exchange for Bladeburner SP": {
|
2021-09-24 00:47:43 +02:00
|
|
|
// This will throw if player isnt in Bladeburner
|
|
|
|
const bladeburner = player.bladeburner;
|
|
|
|
if (bladeburner === null) {
|
2021-09-09 09:17:01 +02:00
|
|
|
player.hashManager.refundUpgrade(upgName);
|
2021-09-05 01:09:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
2021-09-24 00:47:43 +02:00
|
|
|
|
|
|
|
bladeburner.skillPoints += upg.value;
|
2021-09-05 01:09:30 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Generate Coding Contract": {
|
|
|
|
generateRandomContract();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
2021-09-09 05:47:34 +02:00
|
|
|
console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`);
|
2021-09-09 09:17:01 +02:00
|
|
|
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
|
|
|
}
|