Rebalanced new Hacknet Node mechanics. Adjusted Hacknet API so that it'll work with hacknet Servers. Fixed Corporation bug with Issuing new Shares

This commit is contained in:
danielyxie 2019-03-29 16:14:32 -07:00
parent 5592a8bc96
commit 18a3f061b4
14 changed files with 225 additions and 98 deletions

@ -350,21 +350,23 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.CodingContractMoney = 0;
break;
case 9: // Hacktocracy
BitNodeMultipliers.HackingLevelMultiplier = 0.4;
BitNodeMultipliers.StrengthLevelMultiplier = 0.45;
BitNodeMultipliers.DefenseLevelMultiplier = 0.45;
BitNodeMultipliers.DexterityLevelMultiplier = 0.45;
BitNodeMultipliers.AgilityLevelMultiplier = 0.45;
BitNodeMultipliers.CharismaLevelMultiplier = 0.45;
BitNodeMultipliers.PurchasedServerLimit = 0;
BitNodeMultipliers.HomeComputerRamCost = 5;
BitNodeMultipliers.CrimeMoney = 0.5;
BitNodeMultipliers.ScriptHackMoney = 0.1;
BitNodeMultipliers.HackExpGain = 0.1;
BitNodeMultipliers.ServerStartingMoney = 0.1;
BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingSecurity = 2.5;
BitNodeMultipliers.CorporationValuation = 0.5;
BitNodeMultipliers.HackingLevelMultiplier = 0.4;
BitNodeMultipliers.StrengthLevelMultiplier = 0.45;
BitNodeMultipliers.DefenseLevelMultiplier = 0.45;
BitNodeMultipliers.DexterityLevelMultiplier = 0.45;
BitNodeMultipliers.AgilityLevelMultiplier = 0.45;
BitNodeMultipliers.CharismaLevelMultiplier = 0.45;
BitNodeMultipliers.PurchasedServerLimit = 0;
BitNodeMultipliers.HomeComputerRamCost = 5;
BitNodeMultipliers.CrimeMoney = 0.5;
BitNodeMultipliers.ScriptHackMoney = 0.1;
BitNodeMultipliers.HackExpGain = 0.1;
BitNodeMultipliers.ServerStartingMoney = 0.1;
BitNodeMultipliers.ServerMaxMoney = 0.1;
BitNodeMultipliers.ServerStartingSecurity = 2.5;
BitNodeMultipliers.CorporationValuation = 0.5;
BitNodeMultipliers.FourSigmaMarketDataCost = 5;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
break;
case 10: // Digital Carbon
BitNodeMultipliers.HackingLevelMultiplier = 0.2;

@ -285,6 +285,7 @@ export let CONSTANTS: IMap<any> = {
* Corporation Changes:
** 'Demand' value of products decreases more slowly
** Bug Fix: Fixed a Corporation issue that broke the Market-TA2 Research
** Bug Fix: Issuing New Shares now works properly
* Bug Fix: Money Statistics tracker was incorrectly recording profits when selling stocks manually
* Bug Fix: Fixed an issue with the job requirement tooltip for security jobs

@ -31,6 +31,7 @@ import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { getRandomInt } from "../../../utils/helpers/getRandomInt";
import { KEY } from "../../../utils/helpers/keyCodes";
import { clearSelector } from "../../../utils/uiHelpers/clearSelector";

1
src/Hacking/README.md Normal file

@ -0,0 +1 @@
Implementation of underlying Hacking mechanics

@ -0,0 +1,53 @@
/**
* Functions used to determine whether the target can be hacked (or grown/weakened).
* Meant to be used for Netscript implementation
*
* The returned status object's message should be used for logging in Netscript
*/
import { IReturnStatus } from "../types";
import { HacknetServer } from "../Hacknet/HacknetServer";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Server } from "../Server/Server";
function baseCheck(server: Server | HacknetServer, fnName: string): IReturnStatus {
if (server instanceof HacknetServer) {
return {
res: false,
msg: `Cannot ${fnName} ${server.hostname} server because it is a Hacknet Node`
}
}
if (server.hasAdminRights === false) {
return {
res: false,
msg: `Cannot ${fnName} ${server.hostname} server because you do not have root access`,
}
}
return { res: true }
}
export function netscriptCanHack(server: Server | HacknetServer, p: IPlayer): IReturnStatus {
const initialCheck = baseCheck(server, "hack");
if (!initialCheck.res) { return initialCheck; }
let s = <Server>server;
if (s.requiredHackingSkill > p.hacking_skill) {
return {
res: false,
msg: `Cannot hack ${server.hostname} server because your hacking skill is not high enough`,
}
}
return { res: true }
}
export function netscriptCanGrow(server: Server | HacknetServer): IReturnStatus {
return baseCheck(server, "grow");
}
export function netscriptCanWeaken(server: Server | HacknetServer): IReturnStatus {
return baseCheck(server, "weaken");
}

@ -54,7 +54,6 @@ export function purchaseHacknet() {
return;
}
}
/* END INTERACTIVE TUTORIAL */
if (hasHacknetServers()) {
@ -396,11 +395,21 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
break;
}
case "Exchange for Bladeburner Rank": {
// This will throw if player doesn't have a corporation
// This will throw if player isnt in Bladeburner
try {
for (const division of Player.corporation.divisions) {
division.sciResearch.qty += upg.value;
}
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;
} catch(e) {
Player.hashManager.refundUpgrade(upgName);
return false;
@ -413,6 +422,7 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
}
default:
console.warn(`Unrecognized upgrade name ${upgName}. Upgrade has no effect`)
Player.hashManager.refundUpgrade(upgName);
return false;
}

@ -327,16 +327,17 @@ export class HacknetServer extends BaseServer implements IHacknetNode {
updateHashRate(p: IPlayer): void {
const baseGain = HacknetServerHashesPerLevel * this.level;
const coreMultiplier = Math.pow(1.1, this.cores - 1);
const ramMultiplier = Math.pow(1.07, Math.log2(this.maxRam));
const coreMultiplier = 1 + (this.cores - 1) / 5;
const ramRatio = (1 - this.ramUsed / this.maxRam);
const hashRate = baseGain * coreMultiplier * ramRatio;
const hashRate = baseGain * ramMultiplier * coreMultiplier * ramRatio;
this.hashRate = hashRate * p.hacknet_node_money_mult * BitNodeMultipliers.HacknetNodeMoney;
if (isNaN(this.hashRate)) {
this.hashRate = 0;
dialogBoxCreate(`Error calculating Hacknet Server hash production. This is a bug. Please report to game dev`, false);
console.error(`Error calculating Hacknet Server hash production. This is a bug. Please report to game dev`, false);
}
}

@ -84,9 +84,14 @@ export class HashManager {
return upg.getCost(currLevel);
}
storeHashes(numHashes: number): void {
this.hashes += numHashes;
this.hashes = Math.min(this.hashes, this.capacity);
prestige(p: IPlayer): void {
for (const name in HashUpgrades) {
this.upgrades[name] = 0;
}
this.hashes = 0;
if (p != null) {
this.updateCapacity(p);
}
}
/**
@ -106,6 +111,11 @@ export class HashManager {
this.hashes += cost;
}
storeHashes(numHashes: number): void {
this.hashes += numHashes;
this.hashes = Math.min(this.hashes, this.capacity);
}
updateCapacity(p: IPlayer): void {
if (p.hacknetNodes.length <= 0) {
this.capacity = 0;

@ -3,7 +3,7 @@ import { IConstructorParams } from "../HashUpgrade";
export const HashUpgradesMetadata: IConstructorParams[] = [
{
costPerLevel: 2,
costPerLevel: 1,
desc: "Sell hashes for $1m",
name: "Sell for Money",
value: 1e6,
@ -15,36 +15,36 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
value: 1e9,
},
{
costPerLevel: 100,
desc: "Use hashes to decrease the minimum security of a single server by 5%. " +
costPerLevel: 50,
desc: "Use hashes to decrease the minimum security of a single server by 2%. " +
"Note that a server's minimum security cannot go below 1.",
hasTargetServer: true,
name: "Reduce Minimum Security",
value: 0.95,
value: 0.98,
},
{
costPerLevel: 100,
desc: "Use hashes to increase the maximum amount of money on a single server by 5%",
costPerLevel: 50,
desc: "Use hashes to increase the maximum amount of money on a single server by 2%",
hasTargetServer: true,
name: "Increase Maximum Money",
value: 1.05,
value: 1.02,
},
{
costPerLevel: 100,
desc: "Use hashes to improve the experience earned when studying at a university. " +
costPerLevel: 50,
desc: "Use hashes to improve the experience earned when studying at a university by 20%. " +
"This effect persists until you install Augmentations",
name: "Improve Studying",
value: 20, // Improves studying by value%
},
{
costPerLevel: 100,
desc: "Use hashes to improve the experience earned when training at the gym. This effect " +
costPerLevel: 50,
desc: "Use hashes to improve the experience earned when training at the gym by 20%. This effect " +
"persists until you install Augmentations",
name: "Improve Gym Training",
value: 20, // Improves training by value%
},
{
costPerLevel: 250,
costPerLevel: 200,
desc: "Exchange hashes for 1k Scientific Research in all of your Corporation's Industries",
name: "Exchange for Corporation Research",
value: 1000,
@ -56,7 +56,13 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
value: 100,
},
{
costPerLevel: 200,
costPerLevel: 250,
desc: "Exchanges hashes for 10 Bladeburner Skill Points",
name: "Exchange for Bladeburner SP",
value: 10,
},
{
costPerLevel: 150,
desc: "Generate a random Coding Contract on your home computer",
name: "Generate Coding Contract",
value: 1,

@ -3,21 +3,22 @@
*/
import React from "react";
import { purchaseHashUpgrade } from "../HacknetHelpers";
import { HashManager } from "../HashManager";
import { HashUpgrades } from "../HashUpgrades";
import { purchaseHashUpgrade } from "../HacknetHelpers";
import { HashManager } from "../HashManager";
import { HashUpgrades } from "../HashUpgrades";
import { Player } from "../../Player";
import { AllServers } from "../../Server/AllServers";
import { Server } from "../../Server/Server";
import { Player } from "../../Player";
import { AllServers } from "../../Server/AllServers";
import { Server } from "../../Server/Server";
import { numeralWrapper } from "../../ui/numeralFormat";
import { numeralWrapper } from "../../ui/numeralFormat";
import { removePopup } from "../../ui/React/createPopup";
import { removePopup } from "../../ui/React/createPopup";
import { PopupCloseButton } from "../../ui/React/PopupCloseButton";
import { ServerDropdown,
ServerType } from "../../ui/React/ServerDropdown"
ServerType } from "../../ui/React/ServerDropdown"
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { dialogBoxCreate } from "../../../utils/DialogBox";
class HashUpgrade extends React.Component {
constructor(props) {
@ -85,8 +86,6 @@ export class HashUpgradePopup extends React.Component {
constructor(props) {
super(props);
this.closePopup = this.closePopup.bind(this);
this.state = {
totalHashes: Player.hashManager.hashes,
}
@ -100,10 +99,6 @@ export class HashUpgradePopup extends React.Component {
clearInterval(this.interval);
}
closePopup() {
removePopup(this.props.popupId);
}
tick() {
this.setState({
totalHashes: Player.hashManager.hashes,
@ -125,9 +120,7 @@ export class HashUpgradePopup extends React.Component {
return (
<div>
<button className={"std-button"} onClick={this.closePopup}>
Close
</button>
<PopupCloseButton popup={this.props.popupId} text={"Close"} />
<p>Spend your hashes on a variety of different upgrades</p>
<p>Hashes: {numeralWrapper.formatBigNumber(this.state.totalHashes)}</p>
{upgradeElems}

@ -29,7 +29,12 @@ import { Factions,
import { joinFaction,
purchaseAugmentation } from "./Faction/FactionHelpers";
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
import { netscriptCanGrow,
netscriptCanHack,
netscriptCanWeaken } from "./Hacking/netscriptCanHack";
import { getCostOfNextHacknetNode,
getCostOfNextHacknetServer,
hasHacknetServers,
purchaseHacknet } from "./Hacknet/HacknetNode";
import {Locations} from "./Locations";
import { Message } from "./Message/Message";
@ -194,11 +199,11 @@ function initSingularitySFFlags() {
}
function NetscriptFunctions(workerScript) {
var updateDynamicRam = function(fnName, ramCost) {
const updateDynamicRam = function(fnName, ramCost) {
if (workerScript.dynamicLoadedFns[fnName]) {return;}
workerScript.dynamicLoadedFns[fnName] = true;
const threads = workerScript.scriptRef.threads;
let threads = workerScript.scriptRef.threads;
if (typeof threads !== 'number') {
console.warn(`WorkerScript detected NaN for threadcount for ${workerScript.name} on ${workerScript.serverIp}`);
threads = 1;
@ -215,7 +220,7 @@ function NetscriptFunctions(workerScript) {
}
};
var updateStaticRam = function(fnName, ramCost) {
const updateStaticRam = function(fnName, ramCost) {
if (workerScript.loadedFns[fnName]) {
return 0;
} else {
@ -230,7 +235,7 @@ function NetscriptFunctions(workerScript) {
* @param {string} Hostname or IP of the server
* @returns {Server} The specified Server
*/
var safeGetServer = function(ip, callingFnName="") {
const safeGetServer = function(ip, callingFnName="") {
var server = getServer(ip);
if (server == null) {
throw makeRuntimeRejectMsg(workerScript, `Invalid IP or hostname passed into ${callingFnName}() function`);
@ -239,17 +244,27 @@ function NetscriptFunctions(workerScript) {
}
// Utility function to get Hacknet Node object
var getHacknetNode = function(i) {
const getHacknetNode = function(i) {
if (isNaN(i)) {
throw makeRuntimeRejectMsg(workerScript, "Invalid index specified for Hacknet Node: " + i);
}
if (i < 0 || i >= Player.hacknetNodes.length) {
throw makeRuntimeRejectMsg(workerScript, "Index specified for Hacknet Node is out-of-bounds: " + i);
}
return Player.hacknetNodes[i];
if (hasHacknetServers()) {
const hserver = AllServers[Player.hacknetNodes[i]];
if (hserver == null) {
throw makeRuntimeRejectMsg(workerScript, `Could not get Hacknet Server for index ${i}. This is probably a bug, please report to game dev`);
}
return hserver;
} else {
return Player.hacknetNodes[i];
}
};
var getCodingContract = function(fn, ip) {
const getCodingContract = function(fn, ip) {
var server = safeGetServer(ip, "getCodingContract");
return server.getContract(fn);
}
@ -263,43 +278,64 @@ function NetscriptFunctions(workerScript) {
return purchaseHacknet();
},
getPurchaseNodeCost : function() {
return getCostOfNextHacknetNode();
if (hasHacknetServers()) {
return getCostOfNextHacknetServer();
} else {
return getCostOfNextHacknetNode();
}
},
getNodeStats : function(i) {
var node = getHacknetNode(i);
return {
const node = getHacknetNode(i);
const hasUpgraded = hasHacknetServers();
const res = {
name: node.name,
level: node.level,
ram: node.ram,
ram: hasUpgraded ? node.maxRam : node.ram,
cores: node.cores,
production: node.moneyGainRatePerSecond,
production: hasUpgraded ? node.hashRate : node.moneyGainRatePerSecond,
timeOnline: node.onlineTimeSeconds,
totalProduction: node.totalMoneyGenerated,
totalProduction: hasUpgraded ? node.totalHashesGenerated : node.totalMoneyGenerated,
};
if (hasUpgraded) {
res.cache = node.cache;
}
return res;
},
upgradeLevel : function(i, n) {
var node = getHacknetNode(i);
const node = getHacknetNode(i);
return node.purchaseLevelUpgrade(n, Player);
},
upgradeRam : function(i, n) {
var node = getHacknetNode(i);
const node = getHacknetNode(i);
return node.purchaseRamUpgrade(n, Player);
},
upgradeCore : function(i, n) {
var node = getHacknetNode(i);
const node = getHacknetNode(i);
return node.purchaseCoreUpgrade(n, Player);
},
upgradeCache : function(i, n) {
if (!hasHacknetServers()) { return false; }
const node = getHacknetNode(i);
return node.purchaseCacheUpgrade(n, Player);
},
getLevelUpgradeCost : function(i, n) {
var node = getHacknetNode(i);
const node = getHacknetNode(i);
return node.calculateLevelUpgradeCost(n, Player);
},
getRamUpgradeCost : function(i, n) {
var node = getHacknetNode(i);
const node = getHacknetNode(i);
return node.calculateRamUpgradeCost(n, Player);
},
getCoreUpgradeCost : function(i, n) {
var node = getHacknetNode(i);
const node = getHacknetNode(i);
return node.calculateCoreUpgradeCost(n, Player);
},
getCacheUpgradeCost : function(i, n) {
if (!hasHacknetServers()) { return Infinity; }
const node = getHacknetNode(i);
return node.calculateCacheUpgradeCost(n);
}
},
sprintf : sprintf,
@ -351,19 +387,16 @@ function NetscriptFunctions(workerScript) {
var hackingTime = calculateHackingTime(server); //This is in seconds
//No root access or skill level too low
if (server.hasAdminRights == false) {
workerScript.scriptRef.log("Cannot hack this server (" + server.hostname + ") because user does not have root access");
throw makeRuntimeRejectMsg(workerScript, "Cannot hack this server (" + server.hostname + ") because user does not have root access");
}
if (server.requiredHackingSkill > Player.hacking_skill) {
workerScript.scriptRef.log("Cannot hack this server (" + server.hostname + ") because user's hacking skill is not high enough");
throw makeRuntimeRejectMsg(workerScript, "Cannot hack this server (" + server.hostname + ") because user's hacking skill is not high enough");
const canHack = netscriptCanHack(server, Player);
if (!canHack.res) {
workerScript.scriptRef.log(`ERROR: ${canHack.msg}`);
throw makeRuntimeRejectMsg(workerScript, canHack.msg);
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.hack == null) {
workerScript.scriptRef.log("Attempting to hack " + ip + " in " + hackingTime.toFixed(3) + " seconds (t=" + threads + ")");
}
return netscriptDelay(hackingTime * 1000, workerScript).then(function() {
if (workerScript.env.stopFlag) {return Promise.reject(workerScript);}
var hackChance = calculateHackingChance(server);
@ -482,9 +515,10 @@ function NetscriptFunctions(workerScript) {
}
//No root access or skill level too low
if (server.hasAdminRights == false) {
workerScript.scriptRef.log("Cannot grow this server (" + server.hostname + ") because user does not have root access");
throw makeRuntimeRejectMsg(workerScript, "Cannot grow this server (" + server.hostname + ") because user does not have root access");
const canHack = netscriptCanGrow(server);
if (!canHack.res) {
workerScript.scriptRef.log(`ERROR: ${canHack.msg}`);
throw makeRuntimeRejectMsg(workerScript, canHack.msg);
}
var growTime = calculateGrowTime(server);
@ -543,9 +577,10 @@ function NetscriptFunctions(workerScript) {
}
//No root access or skill level too low
if (server.hasAdminRights == false) {
workerScript.scriptRef.log("Cannot weaken this server (" + server.hostname + ") because user does not have root access");
throw makeRuntimeRejectMsg(workerScript, "Cannot weaken this server (" + server.hostname + ") because user does not have root access");
const canHack = netscriptCanWeaken(server);
if (!canHack.res) {
workerScript.scriptRef.log(`ERROR: ${canHack.msg}`);
throw makeRuntimeRejectMsg(workerScript, canHack.msg);
}
var weakenTime = calculateWeakenTime(server);

@ -331,6 +331,7 @@ PlayerObject.prototype.prestigeAugmentation = function() {
this.moneySourceA.reset();
this.hacknetNodes.length = 0;
this.hashManager.prestige(this);
//Re-calculate skills and reset HP
this.updateSkillLevels();
@ -420,6 +421,7 @@ PlayerObject.prototype.prestigeSourceFile = function() {
this.lastUpdate = new Date().getTime();
this.hacknetNodes.length = 0;
this.hashManager.prestige(this);
//Gang
this.gang = null;

@ -1246,23 +1246,26 @@ let Terminal = {
case "free":
Terminal.executeFreeCommand(commandArray);
break;
case "hack":
if (commandArray.length !== 1) {
case "hack": {
if (commandArray.length !== 1) {
postError("Incorrect usage of hack command. Usage: hack");
return;
}
//Hack the current PC (usually for money)
//You can't hack your home pc or servers you purchased
if (Player.getCurrentServer().purchasedByPlayer) {
if (s.purchasedByPlayer) {
postError("Cannot hack your own machines! You are currently connected to your home PC or one of your purchased servers");
} else if (Player.getCurrentServer().hasAdminRights == false ) {
} else if (s.hasAdminRights == false ) {
postError("You do not have admin rights for this machine! Cannot hack");
} else if (Player.getCurrentServer().requiredHackingSkill > Player.hacking_skill) {
} else if (s.requiredHackingSkill > Player.hacking_skill) {
postError("Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill");
} else {
} else if (s instanceof HacknetServer) {
postError("Cannot hack this type of Server")
} else {
Terminal.startHack();
}
break;
}
case "help":
if (commandArray.length !== 1 && commandArray.length !== 2) {
postError("Incorrect usage of help command. Usage: help");
@ -1894,7 +1897,7 @@ let Terminal = {
//var dashes = Array(d * 2 + 1).join("-");
var c = "NO";
if (s.hasAdminRights) {c = "YES";}
post(`${dashes}Root Access: ${c} ${!isHacknet ? ", Required hacking skill: " + s.requiredHackingSkill : ""}`);
post(`${dashes}Root Access: ${c}${!isHacknet ? ", Required hacking skill: " + s.requiredHackingSkill : ""}`);
if (!isHacknet) { post(dashes + "Number of open ports required to NUKE: " + s.numOpenPortsRequired); }
post(dashes + "RAM: " + s.maxRam);
post(" ");

@ -35,3 +35,12 @@ export interface ISelfLoading {
*/
load(saveState: string): void;
}
/**
* Status object for functions that return a boolean indicating success/failure
* and an optional message
*/
export interface IReturnStatus {
res: boolean;
msg?: string;
}