diff --git a/css/menupages.css b/css/menupages.css index 802dc40da..910997e76 100644 --- a/css/menupages.css +++ b/css/menupages.css @@ -116,10 +116,43 @@ height: 100%; margin-left: 10%; width: 99%; + overflow-y: scroll; } -.hacknet-node li { +#hacknet-nodes-container li{ + padding: 6px; + margin: 6px; +} + +#hacknet-nodes-text, +#hacknet-nodes-money { + width: 80%; + margin: 10px; + padding: 10px; +} + +.hacknet-node { + margin: 6px; + padding: 6px; + border: 2px solid white; + -webkit-box-shadow: + inset 0 0 8px rgba(0,0,0,0.1), + 0 0 16px rgba(0,0,0,0.1); + -moz-box-shadow: + inset 0 0 8px rgba(0,0,0,0.1), + 0 0 16px rgba(0,0,0,0.1); + box-shadow: + inset 0 0 8px rgba(0,0,0,0.1), + 0 0 16px rgba(0,0,0,0.1); +} + +.hacknet-node-button-div a { + display: block; +} + +.hacknet-node-button-div:not(:last-child) { + border-bottom: none; } /* World */ diff --git a/css/styles.css b/css/styles.css index 9975f581e..d8fbc6c5e 100644 --- a/css/styles.css +++ b/css/styles.css @@ -22,6 +22,7 @@ h2 { } ul { + padding: 6px; list-style-type: none; } @@ -87,10 +88,14 @@ span { border-left: 1px solid #333333; } +.a-link-button:hover { + background-color: #666; +} + /* Make anchor tags ("a" elements) inactive (not clickable) */ .a-link-button-inactive { text-decoration: none; - background-color: #555; + background-color: #333; color: #FFFFFF; padding: 6px; margin: 6px; diff --git a/index.html b/index.html index bc64ebd14..804bc32eb 100644 --- a/index.html +++ b/index.html @@ -73,6 +73,10 @@
  • Active Scripts
  • + +
  • + Hacknet Nodes +
  • World @@ -140,8 +144,22 @@
    +

    Hacknet Nodes

    +

    + The Hacknet is a global, decentralized network of machines. It is used by hackers all around + the world to anonymously share computing power and perform distributed cyberattacks without the + fear of being traced. +

    + Here, you can purchase a Hacknet Node, a specialized machine that can connect and contribute its + resources to the Hacknet network. This allows you to take a small percentage of profits + from hacks performed on the network. Essentially, you are renting out your Node's computing power. +

    + Each Hacknet Node you purchase will passively earn you money. Each Hacknet Node can be upgraded + in order to increase its computing power and thereby increase the profit you earn from it. +

    + Purchase Hacknet Node +

    diff --git a/src/Constants.js b/src/Constants.js index e8ab15b27..451ed2d60 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -8,8 +8,18 @@ CONSTANTS = { //How much reputation is needed to join a megacorporation's faction CorpFactionRepRequirement: 250000, - //Base cost for 1GB of RAM - BaseCostFor1GBOfRam: 50000, + /* Base costs */ + BaseCostFor1GBOfRam: 50000, //1 GB of RAM + + BaseCostForHacknetNode: 1000, + BaseCostForHacknetNodeCore: 1000000, + + /* Hacknet Node constants */ + HacknetNodeMoneyGainPerLevel: 0.25, + HacknetNodePurchaseNextMult: 1.2; //Multiplier when purchasing an additional hacknet node + HacknetNodeUpgradeLevelMult: 1.08, //Multiplier for cost when upgrading level + HacknetNodeUpgradeRamMult: 1.2, //Multiplier for cost when upgrading RAM + HacknetNodeUpgradeCoreMult: 1.5, //Multiplier for cost when buying another core /* Script related things */ //Time (ms) it takes to run one operation in Netscript. diff --git a/src/HacknetNode.js b/src/HacknetNode.js index 37349f1d0..2c38da252 100644 --- a/src/HacknetNode.js +++ b/src/HacknetNode.js @@ -1,7 +1,7 @@ /* HacknetNode.js */ function HacknetNode(name) { this.level = 1; - this.ram = 0; //GB + this.ram = 1; //GB this.numCores = 1; this.name = name; @@ -14,17 +14,19 @@ function HacknetNode(name) { HacknetNode.prototype.updateMoneyGainRate = function() { //How much extra $/s is gained per level - var gainPerLevel = 0.25; + var gainPerLevel = CONSTANTS.HacknetNodeMoneyGainPerLevel; //Each CPU core doubles the speed. Every 1GB of ram adds 10% increase - this.moneyGainRatePerSecond = (this.level * gainPerLevel) * Math.pow(1.1, ram) * this.numCores; + this.moneyGainRatePerSecond = (this.level * gainPerLevel) * Math.pow(1.1, this.ram-1) * this.numCores; + if (isNaN(this.moneyGainRatePerSecond)) { + throw new Error("Money gain rate calculated for Hacknet Node is NaN"); + } } HacknetNode.prototype.calculateLevelUpgradeCost = function() { //Upgrade cost = Base cost * multiplier ^ level - var baseCost = 10000; - var mult = 1.08; - return baseCost * Math.pow(mult, this.level); + var mult = CONSTANTS.HacknetNodeUpgradeLevelMult; + return CONSTANTS.BaseCostForHacknetNode * Math.pow(mult, this.level); } HacknetNode.prototype.purchaseLevelUpgrade = function() { @@ -32,6 +34,7 @@ HacknetNode.prototype.purchaseLevelUpgrade = function() { if (cost > Player.money) {return;} Player.loseMoney(cost); ++this.level; + this.updateMoneyGainRate(); } HacknetNode.prototype.calculateRamUpgradeCost = function() { @@ -40,8 +43,8 @@ HacknetNode.prototype.calculateRamUpgradeCost = function() { //Calculate cost //Base cost of RAM is 50k per 1GB...but lets have this increase by 10% for every time //the RAM has been upgraded - var cost = currentRam * CONSTANTS.BaseCostFor1GBOfRam; - var mult = Math.pow(1.1, numUpgrades); + var cost = this.ram * CONSTANTS.BaseCostFor1GBOfRam; + var mult = Math.pow(CONSTANTS.HacknetNodeUpgradeRamMult, numUpgrades); return cost * mult; } @@ -50,12 +53,13 @@ HacknetNode.prototype.purchaseRamUpgrade = function() { if (cost > Player.money) {return;} Player.loseMoney(cost); this.ram *= 2; //Ram is always doubled + this.updateMoneyGainRate(); } HacknetNode.prototype.calculateCoreUpgradeCost = function() { - var coreBaseCost = 1000000; - var mult = 1.5; - return coreBaseCost * Math.pow(mult, this.numCores); + var coreBaseCost = CONSTANTS.BaseCostForHacknetNodeCore; + var mult = CONSTANTS.HacknetNodeUpgradeCoreMult; + return coreBaseCost * Math.pow(mult, this.numCores-1); } HacknetNode.prototype.purchaseCoreUpgrade = function() { @@ -63,6 +67,7 @@ HacknetNode.prototype.purchaseCoreUpgrade = function() { if (cost > Player.money) {return;} Player.loseMoney(cost); ++this.numCores; + this.updateMoneyGainRate(); } /* Saving and loading HackNets */ @@ -74,54 +79,199 @@ HacknetNode.fromJSON = function(value) { return Generic_fromJSON(HacknetNode, value.data); } +Reviver.constructors.HacknetNode = HacknetNode; + + + +purchaseHacknet = function() { + var cost = getCostOfNextHacknetNode(); + if (cost > Player.money) { + dialogBoxCreate("You cannot afford to purchase a Hacknet Node!"); + return; + } + + //Auto generate a name for the node for now...TODO + var numOwned = Player.hacknetNodes.length; + var name = "hacknet-node-" + numOwned; + var node = new HacknetNode(name); + node.updateMoneyGainRate(); + + Player.loseMoney(cost); + Player.hacknetNodes.push(node); + + displayHacknetNodesContent(); +} + +getCostOfNextHacknetNode = function() { + //Cost increases exponentially based on how many you own + var numOwned = Player.hacknetNodes.length; + var mult = CONSTANTS.HacknetNodePurchaseNextMult; + return CONSTANTS.BaseCostForHacknetNode * Math.pow(mult, numOwned); +} + +//Creates Hacknet Node DOM elements when the page is opened +displayHacknetNodesContent = function() { + //Update Hacknet Nodes button + var purchaseButton = document.getElementById("hacknet-nodes-purchase-button"); + var newPurchaseButton = purchaseButton.cloneNode(true); + purchaseButton.parentNode.replaceChild(newPurchaseButton, purchaseButton); + + newPurchaseButton.addEventListener("click", function() { + purchaseHacknet(); + return false; + }); + + //Remove all old hacknet Node DOM elements + var hacknetNodesList = document.getElementById("hacknet-nodes-list"); + while (hacknetNodesList.firstChild) { + hacknetNodesList.removeChild(hacknetNodesList.firstChild); + } + + //Then re-create them + for (var i = 0; i < Player.hacknetNodes.length; ++i) { + createHacknetNodeDomElement(Player.hacknetNodes[i]); + } + updateHacknetNodesContent(); +} + +//Update information on all Hacknet Node DOM elements +updateHacknetNodesContent = function() { + //Set purchase button to inactive if not enough money, and update its price display + var cost = getCostOfNextHacknetNode(); + var purchaseButton = document.getElementById("hacknet-nodes-purchase-button"); + purchaseButton.innerHTML = "Purchase Hacknet Node - $" + cost.toFixed(2); + if (cost > Player.money) { + purchaseButton.setAttribute("class", "a-link-button-inactive"); + } else { + purchaseButton.setAttribute("class", "a-link-button"); + } + + //Update player's money + document.getElementById("hacknet-nodes-money").innerHTML = "Money: $" + Player.money.toFixed(2); + + //Update information in each owned hacknet node + for (var i = 0; i < Player.hacknetNodes.length; ++i) { + updateHacknetNodeDomElement(Player.hacknetNodes[i]); + } +} + +//Creates a single Hacknet Node DOM element createHacknetNodeDomElement = function(nodeObj) { var nodeName = nodeObj.name; - - var list = document.getElementById("hacknet-nodes-list"); - + var listItem = document.createElement("li"); - item.setAttribute("class", "hacknet-node"); + listItem.setAttribute("class", "hacknet-node"); var span = document.createElement("span"); - span.style.display = "inline-block"; + span.style.display = "inline"; + + var buttonDiv = document.createElement("div"); + buttonDiv.setAttribute("class", "hacknet-node-button-div"); //Text var txt = document.createElement("p"); - txt.setAttribute("id", "hacknet-node-text-" + nodeName); - txt.innerHTML = "Node name: " + nodeName + "
    " - "Production: " + nodeObj.totalMoneyGenerated + - " ($" + nodeObj.moneyGainRatePerSecond + ")
    " + - "Level: " + nodeObj.level + "
    " + - "RAM: " + nodeObj.ram + "GB
    " + - "Cores: " + nodeObj.numCores; + //txt.setAttribute("id", "hacknet-node-text-" + nodeName); + txt.id = "hacknet-node-text-" + nodeName; //Upgrade buttons var upgradeLevelButton = document.createElement("a"); var upgradeRamButton = document.createElement("a"); var upgradeCoreButton = document.createElement("a"); - upgradeLevelButton.setAttribute("id", "hacknet-node-upgrade-level-" + nodeName); + //upgradeLevelButton.setAttribute("id", "hacknet-node-upgrade-level-" + nodeName); + upgradeLevelButton.id = "hacknet-node-upgrade-level-" + nodeName; upgradeLevelButton.setAttribute("class", "a-link-button-inactive"); - upgradeRamButton.setAttribute("id", "hacknet-node-upgrade-ram-" + nodeName); + upgradeLevelButton.addEventListener("click", function() { + nodeObj.purchaseLevelUpgrade(); + updateHacknetNodesContent(); + return false; + }); + //upgradeRamButton.setAttribute("id", "hacknet-node-upgrade-ram-" + nodeName); + upgradeRamButton.id = "hacknet-node-upgrade-ram-" + nodeName; upgradeRamButton.setAttribute("class", "a-link-button-inactive"); - upgradeCoreButton.setAttribute("id", "hacknet-node-upgrade-core-" + nodeName); + upgradeRamButton.addEventListener("click", function() { + nodeObj.purchaseRamUpgrade(); + updateHacknetNodesContent(); + return false; + }); + //upgradeCoreButton.setAttribute("id", "hacknet-node-upgrade-core-" + nodeName); + upgradeCoreButton.id = "hacknet-node-upgrade-core-" + nodeName; upgradeCoreButton.setAttribute("class", "a-link-button-inactive"); - - upgradeLevelButton.innerHTML = "Upgrade Hacknet Node Level"; - upgradeRamButton.innerHTML = "Upgrade Hacknet Node RAM"; - upgradeCoreButton.innerHTML = "Purchase additional CPU Core for Hacknet Node"; + upgradeCoreButton.addEventListener("click", function() { + nodeObj.purchaseCoreUpgrade(); + updateHacknetNodesContent(); + return false; + }); + + //Put all the components together in the li element + span.appendChild(txt); + buttonDiv.appendChild(upgradeLevelButton); + buttonDiv.appendChild(upgradeRamButton); + buttonDiv.appendChild(upgradeCoreButton); + span.appendChild(buttonDiv); + listItem.appendChild(span); - updateHacknetNodeDomElement(item, nodeObj); + document.getElementById("hacknet-nodes-list").appendChild(listItem); - list.appendChild(item); + //Set the text and stuff inside the DOM element + updateHacknetNodeDomElement(nodeObj); } -updateHacknetNodeDomElement = function(li, nodeObj) { +//Updates information on a single hacknet node DOM element +updateHacknetNodeDomElement = function(nodeObj) { var nodeName = nodeObj.name; -} - -Reviver.constructors.HacknetNode = HacknetNode; - -purchaseHacknet = function() { + var txt = document.getElementById("hacknet-node-text-" + nodeName); + if (txt == null) {throw new Error("Cannot find text element");} + txt.innerHTML = "Node name: " + nodeName + "
    " + + "Production: $" + nodeObj.totalMoneyGenerated.toFixed(2) + + " ($" + nodeObj.moneyGainRatePerSecond + " / second)
    " + + "Level: " + nodeObj.level + "
    " + + "RAM: " + nodeObj.ram + "GB
    " + + "Cores: " + nodeObj.numCores; + + var upgradeLevelButton = document.getElementById("hacknet-node-upgrade-level-" + nodeName); + if (upgradeLevelButton == null) {throw new Error("Cannot find upgrade level button element");} + var upgradeLevelCost = nodeObj.calculateLevelUpgradeCost(); + upgradeLevelButton.innerHTML = "Upgrade Hacknet Node Level - $" + upgradeLevelCost.toFixed(2); + if (upgradeLevelCost > Player.money) { + upgradeLevelButton.setAttribute("class", "a-link-button-inactive"); + } else { + upgradeLevelButton.setAttribute("class", "a-link-button"); + } + var upgradeRamButton = document.getElementById("hacknet-node-upgrade-ram-" + nodeName); + if (upgradeRamButton == null) {throw new Error("Cannot find upgrade ram button element");} + var upgradeRamCost = nodeObj.calculateRamUpgradeCost(); + upgradeRamButton.innerHTML = "Upgrade Hacknet Node RAM -$" + upgradeRamCost.toFixed(2); + if (upgradeRamCost > Player.money) { + upgradeRamButton.setAttribute("class", "a-link-button-inactive"); + } else { + upgradeRamButton.setAttribute("class", "a-link-button"); + } + + var upgradeCoreButton = document.getElementById("hacknet-node-upgrade-core-" + nodeName); + if (upgradeCoreButton == null) {throw new Error("Cannot find upgrade cores button element");} + var upgradeCoreCost = nodeObj.calculateCoreUpgradeCost(); + upgradeCoreButton.innerHTML = "Purchase additional CPU Core - $" + upgradeCoreCost.toFixed(2); + if (upgradeCoreCost > Player.money) { + upgradeCoreButton.setAttribute("class", "a-link-button-inactive"); + } else { + upgradeCoreButton.setAttribute("class", "a-link-button"); + } } + +processAllHacknetNodeEarnings = function(numCycles) { + for (var i = 0; i < Player.hacknetNodes.length; ++i) { + processSingleHacknetNodeEarnings(numCycles, Player.hacknetNodes[i]); + } +} + +processSingleHacknetNodeEarnings = function(numCycles, nodeObj) { + var cyclesPerSecond = 1000 / Engine._idleSpeed; + var earningPerCycle = nodeObj.moneyGainRatePerSecond / cyclesPerSecond; + if (isNaN(earningPerCycle)) {throw new Error("Calculated Earnings is not a number");} + var totalEarnings = numCycles * earningPerCycle; + nodeObj.totalMoneyGenerated += totalEarnings; + nodeObj.onlineTimeSeconds += (numCycles * (Engine._idleSpeed / 1000)); + Player.gainMoney(totalEarnings); +} \ No newline at end of file diff --git a/src/Terminal.js b/src/Terminal.js index 37934bd54..1bbc36127 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -297,7 +297,7 @@ var Terminal = { for (var i = 0; i < Player.getCurrentServer().runningScripts.length; i++) { if (Player.getCurrentServer().runningScripts[i] == scriptName) { killWorkerScript(scriptName, Player.getCurrentServer().ip); - post("Killing " + scriptName + ". May take a few seconds"); + post("Killing " + scriptName + ". May take up to a few minutes for the scripts to die..."); return; } } diff --git a/src/engine.js b/src/engine.js index 129592bcb..cb4ede5d1 100644 --- a/src/engine.js +++ b/src/engine.js @@ -8,6 +8,7 @@ var Engine = { characterMainMenuButton: null, scriptEditorMainMenuButton: null, activeScriptsMainMenuButton: null, + hacknetNodesMainMenuButton: null, worldMainMenuButton: null, createProgramMainMenuButton: null, factionsMainMenuButton: null, @@ -43,6 +44,7 @@ var Engine = { characterContent: null, scriptEditorContent: null, activeScriptsContent: null, + hacknetNodesContent: null, worldContent: null, createProgramContent: null, factionsContent: null, @@ -66,6 +68,7 @@ var Engine = { CharacterInfo: "CharacterInfo", ScriptEditor: "ScriptEditor", ActiveScripts: "ActiveScripts", + HacknetNodes: "HacknetNodes", World: "World", CreateProgram: "CreateProgram", Factions: "Factions", @@ -202,6 +205,14 @@ var Engine = { Engine.currentPage = Engine.Page.ActiveScripts; }, + loadHacknetNodesContent: function() { + Engine.hideAllContent(); + Engine.Display.hacknetNodesContent.style.visibility = "visible"; + displayHacknetNodesContent(); + + Engine.currentPage = Engine.Page.HacknetNodes; + }, + loadWorldContent: function() { Engine.hideAllContent(); Engine.Display.worldContent.style.visibility = "visible"; @@ -274,6 +285,7 @@ var Engine = { Engine.Display.characterContent.style.visibility = "hidden"; Engine.Display.scriptEditorContent.style.visibility = "hidden"; Engine.Display.activeScriptsContent.style.visibility = "hidden"; + Engine.Display.hacknetNodesContent.style.visibility = "hidden"; Engine.Display.worldContent.style.visibility = "hidden"; Engine.Display.createProgramContent.style.visibility = "hidden"; Engine.Display.factionsContent.style.visibility = "hidden"; @@ -310,6 +322,7 @@ var Engine = { 'Agility: ' + (Player.agility).toLocaleString() + '

    ' + 'Charisma: ' + (Player.charisma).toLocaleString() + '

    ' + 'Servers owned: ' + Player.purchasedServers.length + '

    ' + + 'Hacknet Nodes owned: ' + Player.hacknetNodes.length + '

    ' + 'Hacking experience: ' + (Player.hacking_exp.toFixed(4)).toLocaleString() + '

    ' + 'Strength experience: ' + (Player.strength_exp.toFixed(4)).toLocaleString() + '

    ' + 'Defense experience: ' + (Player.defense_exp.toFixed(4)).toLocaleString() + '

    ' + @@ -585,6 +598,9 @@ var Engine = { //Update the running time of all active scripts updateOnlineScriptTimes(numCycles); + + //Hacknet Nodes + processAllHacknetNodeEarnings(numCycles); }, //Counters for the main event loop. Represent the number of game cycles are required @@ -592,7 +608,7 @@ var Engine = { Counters: { autoSaveCounter: 300, //Autosave every minute updateSkillLevelsCounter: 10, //Only update skill levels every 2 seconds. Might improve performance - updateDisplays: 5, //Update displays such as Active Scripts display and character display + updateDisplays: 4, //Update displays such as Active Scripts display and character display serverGrowth: 450, //Process server growth every minute and a half checkFactionInvitations: 1500, //Check whether you qualify for any faction invitations every 5 minutes }, @@ -623,9 +639,11 @@ var Engine = { Engine.updateActiveScriptsItems(); } else if (Engine.currentPage == Engine.Page.CharacterInfo) { Engine.displayCharacterInfo(); - } + } else if (Engine.currentPage == Engine.Page.HacknetNodes) { + updateHacknetNodesContent(); + } - Engine.Counters.updateDisplays = 5; + Engine.Counters.updateDisplays = 4; } if (Engine.Counters.serverGrowth <= 0) { @@ -706,6 +724,12 @@ var Engine = { return false; }); + Engine.Clickables.hacknetNodesMainMenuButton = document.getElementById("hacknet-nodes-menu-link"); + Engine.Clickables.hacknetNodesMainMenuButton.addEventListener("click", function() { + Engine.loadHacknetNodesContent(); + return false; + }); + Engine.Clickables.worldMainMenuButton = document.getElementById("world-menu-link"); Engine.Clickables.worldMainMenuButton.addEventListener("click", function() { Engine.loadWorldContent(); @@ -811,6 +835,9 @@ var Engine = { Engine.Display.activeScriptsContent = document.getElementById("active-scripts-container"); Engine.Display.activeScriptsContent.style.visibility = "hidden"; + Engine.Display.hacknetNodesContent = document.getElementById("hacknet-nodes-container"); + Engine.Display.hacknetNodesContent.style.visibility = "hidden"; + Engine.Display.worldContent = document.getElementById("world-container"); Engine.Display.worldContent.style.visibility = "hidden"; @@ -881,6 +908,9 @@ var Engine = { Player.work(numCyclesOffline); } } + + //Hacknet Nodes + processAllHacknetNodeEarnings(numCyclesOffline); } else { //No save found, start new game console.log("Initializing new game");