mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-10-23 10:13:13 +02:00
286 lines
9.6 KiB
TypeScript
286 lines
9.6 KiB
TypeScript
|
/**
|
||
|
* Hacknet Node Class
|
||
|
*
|
||
|
* Hacknet Nodes are specialized machines that passively earn the player money over time.
|
||
|
* They can be upgraded to increase their production
|
||
|
*/
|
||
|
import { IHacknetNode } from "./IHacknetNode";
|
||
|
|
||
|
import { CONSTANTS } from "../Constants";
|
||
|
|
||
|
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||
|
|
||
|
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||
|
import { Generic_fromJSON,
|
||
|
Generic_toJSON,
|
||
|
Reviver } from "../../utils/JSONReviver";
|
||
|
|
||
|
// Constants for Hacknet Node production
|
||
|
export const HacknetNodeMoneyGainPerLevel: number = 1.6; // Base production per level
|
||
|
|
||
|
// Constants for Hacknet Node purchase/upgrade costs
|
||
|
export const BaseCostForHacknetNode: number = 1000;
|
||
|
export const BaseCostFor1GBOfRamHacknetNode: number = 30e3;
|
||
|
export const BaseCostForHacknetNodeCore: number = 500e3;
|
||
|
export const HacknetNodePurchaseNextMult: number = 1.85; // Multiplier when purchasing an additional hacknet node
|
||
|
export const HacknetNodeUpgradeLevelMult: number = 1.04; // Multiplier for cost when upgrading level
|
||
|
export const HacknetNodeUpgradeRamMult: number = 1.28; // Multiplier for cost when upgrading RAM
|
||
|
export const HacknetNodeUpgradeCoreMult: number = 1.48; // Multiplier for cost when buying another core
|
||
|
|
||
|
// Constants for max upgrade levels for Hacknet Nodes
|
||
|
export const HacknetNodeMaxLevel: number = 200;
|
||
|
export const HacknetNodeMaxRam: number = 64;
|
||
|
export const HacknetNodeMaxCores: number = 16;
|
||
|
|
||
|
export class HacknetNode implements IHacknetNode {
|
||
|
/**
|
||
|
* Initiatizes a HacknetNode object from a JSON save state.
|
||
|
*/
|
||
|
static fromJSON(value: any): HacknetNode {
|
||
|
return Generic_fromJSON(HacknetNode, value.data);
|
||
|
}
|
||
|
|
||
|
// Node's number of cores
|
||
|
cores: number = 1;
|
||
|
|
||
|
// Node's Level
|
||
|
level: number = 1;
|
||
|
|
||
|
// Node's production per second
|
||
|
moneyGainRatePerSecond: number = 0;
|
||
|
|
||
|
// Identifier for Node. Includes the full "name" (hacknet-node-N)
|
||
|
name: string;
|
||
|
|
||
|
// How long this Node has existed, in seconds
|
||
|
onlineTimeSeconds: number = 0;
|
||
|
|
||
|
// Node's RAM (GB)
|
||
|
ram: number = 1;
|
||
|
|
||
|
// Total money earned by this Node
|
||
|
totalMoneyGenerated: number = 0;
|
||
|
|
||
|
constructor(name: string="") {
|
||
|
this.name = name;
|
||
|
}
|
||
|
|
||
|
// Get the cost to upgrade this Node's number of cores
|
||
|
calculateCoreUpgradeCost(levels: number=1, p: IPlayer): number {
|
||
|
const sanitizedLevels = Math.round(levels);
|
||
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (this.cores >= HacknetNodeMaxCores) {
|
||
|
return Infinity;
|
||
|
}
|
||
|
|
||
|
const coreBaseCost = BaseCostForHacknetNodeCore;
|
||
|
const mult = HacknetNodeUpgradeCoreMult;
|
||
|
let totalCost = 0;
|
||
|
let currentCores = this.cores;
|
||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||
|
totalCost += (coreBaseCost * Math.pow(mult, currentCores-1));
|
||
|
++currentCores;
|
||
|
}
|
||
|
|
||
|
totalCost *= p.hacknet_node_core_cost_mult;
|
||
|
|
||
|
return totalCost;
|
||
|
}
|
||
|
|
||
|
// Get the cost to upgrade this Node's level
|
||
|
calculateLevelUpgradeCost(levels: number=1, p: IPlayer): number {
|
||
|
const sanitizedLevels = Math.round(levels);
|
||
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (this.level >= HacknetNodeMaxLevel) {
|
||
|
return Infinity;
|
||
|
}
|
||
|
|
||
|
const mult = HacknetNodeUpgradeLevelMult;
|
||
|
let totalMultiplier = 0;
|
||
|
let currLevel = this.level;
|
||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||
|
totalMultiplier += Math.pow(mult, currLevel);
|
||
|
++currLevel;
|
||
|
}
|
||
|
|
||
|
return BaseCostForHacknetNode / 2 * totalMultiplier * p.hacknet_node_level_cost_mult;
|
||
|
}
|
||
|
|
||
|
// Get the cost to upgrade this Node's RAM
|
||
|
calculateRamUpgradeCost(levels: number=1, p: IPlayer): number {
|
||
|
const sanitizedLevels = Math.round(levels);
|
||
|
if (isNaN(sanitizedLevels) || sanitizedLevels < 1) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (this.ram >= HacknetNodeMaxRam) {
|
||
|
return Infinity;
|
||
|
}
|
||
|
|
||
|
let totalCost = 0;
|
||
|
let numUpgrades = Math.round(Math.log2(this.ram));
|
||
|
let currentRam = this.ram;
|
||
|
|
||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||
|
let baseCost = currentRam * BaseCostFor1GBOfRamHacknetNode;
|
||
|
let mult = Math.pow(HacknetNodeUpgradeRamMult, numUpgrades);
|
||
|
|
||
|
totalCost += (baseCost * mult);
|
||
|
|
||
|
currentRam *= 2;
|
||
|
++numUpgrades;
|
||
|
}
|
||
|
|
||
|
totalCost *= p.hacknet_node_ram_cost_mult;
|
||
|
|
||
|
return totalCost;
|
||
|
}
|
||
|
|
||
|
// Process this Hacknet Node in the game loop.
|
||
|
// Returns the amount of money generated
|
||
|
process(numCycles: number=1): number {
|
||
|
const seconds = numCycles * CONSTANTS.MilliPerCycle / 1000;
|
||
|
let gain = this.moneyGainRatePerSecond * seconds;
|
||
|
if (isNaN(gain)) {
|
||
|
console.error(`Hacknet Node ${this.name} calculated earnings of NaN`);
|
||
|
gain = 0;
|
||
|
}
|
||
|
|
||
|
this.totalMoneyGenerated += gain;
|
||
|
this.onlineTimeSeconds += seconds;
|
||
|
|
||
|
return gain;
|
||
|
}
|
||
|
|
||
|
// Upgrade this Node's number of cores, if possible
|
||
|
// Returns a boolean indicating whether new cores were successfully bought
|
||
|
purchaseCoreUpgrade(levels: number=1, p: IPlayer): boolean {
|
||
|
const sanitizedLevels = Math.round(levels);
|
||
|
const cost = this.calculateCoreUpgradeCost(sanitizedLevels, p);
|
||
|
if (isNaN(cost) || sanitizedLevels < 0) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Fail if we're already at max
|
||
|
if (this.cores >= HacknetNodeMaxCores) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If the specified number of upgrades would exceed the max Cores, calculate
|
||
|
// the max possible number of upgrades and use that
|
||
|
if (this.cores + sanitizedLevels > HacknetNodeMaxCores) {
|
||
|
const diff = Math.max(0, HacknetNodeMaxCores - this.cores);
|
||
|
return this.purchaseCoreUpgrade(diff, p);
|
||
|
}
|
||
|
|
||
|
if (!p.canAfford(cost)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
p.loseMoney(cost);
|
||
|
this.cores = Math.round(this.cores + sanitizedLevels); // Just in case of floating point imprecision
|
||
|
this.updateMoneyGainRate(p);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Upgrade this Node's level, if possible
|
||
|
// Returns a boolean indicating whether the level was successfully updated
|
||
|
purchaseLevelUpgrade(levels: number=1, p: IPlayer): boolean {
|
||
|
const sanitizedLevels = Math.round(levels);
|
||
|
const cost = this.calculateLevelUpgradeCost(sanitizedLevels, p);
|
||
|
if (isNaN(cost) || sanitizedLevels < 0) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If we're at max level, return false
|
||
|
if (this.level >= HacknetNodeMaxLevel) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If the number of specified upgrades would exceed the max level, calculate
|
||
|
// the maximum number of upgrades and use that
|
||
|
if (this.level + sanitizedLevels > HacknetNodeMaxLevel) {
|
||
|
var diff = Math.max(0, HacknetNodeMaxLevel - this.level);
|
||
|
return this.purchaseLevelUpgrade(diff, p);
|
||
|
}
|
||
|
|
||
|
if (!p.canAfford(cost)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
p.loseMoney(cost);
|
||
|
this.level = Math.round(this.level + sanitizedLevels); // Just in case of floating point imprecision
|
||
|
this.updateMoneyGainRate(p);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Upgrade this Node's RAM, if possible
|
||
|
// Returns a boolean indicating whether the RAM was successfully upgraded
|
||
|
purchaseRamUpgrade(levels: number=1, p: IPlayer): boolean {
|
||
|
const sanitizedLevels = Math.round(levels);
|
||
|
const cost = this.calculateRamUpgradeCost(sanitizedLevels, p);
|
||
|
if (isNaN(cost) || sanitizedLevels < 0) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Fail if we're already at max
|
||
|
if (this.ram >= HacknetNodeMaxRam) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If the number of specified upgrades would exceed the max RAM, calculate the
|
||
|
// max possible number of upgrades and use that
|
||
|
if (this.ram * Math.pow(2, sanitizedLevels) > HacknetNodeMaxRam) {
|
||
|
var diff = Math.max(0, Math.log2(Math.round(HacknetNodeMaxRam / this.ram)));
|
||
|
return this.purchaseRamUpgrade(diff, p);
|
||
|
}
|
||
|
|
||
|
if (!p.canAfford(cost)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
p.loseMoney(cost);
|
||
|
for (let i = 0; i < sanitizedLevels; ++i) {
|
||
|
this.ram *= 2; // Ram is always doubled
|
||
|
}
|
||
|
this.ram = Math.round(this.ram); // Handle any floating point precision issues
|
||
|
this.updateMoneyGainRate(p);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Re-calculate this Node's production and update the moneyGainRatePerSecond prop
|
||
|
updateMoneyGainRate(p: IPlayer): void {
|
||
|
//How much extra $/s is gained per level
|
||
|
var gainPerLevel = HacknetNodeMoneyGainPerLevel;
|
||
|
|
||
|
this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
|
||
|
Math.pow(1.035, this.ram - 1) *
|
||
|
((this.cores + 5) / 6) *
|
||
|
p.hacknet_node_money_mult *
|
||
|
BitNodeMultipliers.HacknetNodeMoney;
|
||
|
if (isNaN(this.moneyGainRatePerSecond)) {
|
||
|
this.moneyGainRatePerSecond = 0;
|
||
|
dialogBoxCreate("Error in calculating Hacknet Node production. Please report to game developer", false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Serialize the current object to a JSON save state.
|
||
|
*/
|
||
|
toJSON(): any {
|
||
|
return Generic_toJSON("HacknetNode", this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Reviver.constructors.HacknetNode = HacknetNode;
|