Finished BETA version of Missions. All other changes for v0.29.1

This commit is contained in:
danielyxie 2017-09-27 10:13:42 -05:00
parent c84417607f
commit 22a5d3b3cc
9 changed files with 1989 additions and 1795 deletions

@ -34,7 +34,6 @@
background-color:blue; background-color:blue;
} }
.hack-mission-player-node:hover,
.hack-mission-player-node-active { .hack-mission-player-node-active {
border: 2px solid white; border: 2px solid white;
color: #6666ff; color: #6666ff;

3470
dist/bundle.js vendored

File diff suppressed because one or more lines are too long

@ -6,6 +6,7 @@ import {getServer} from "./Server.js";
import {dialogBoxCreate} from "../utils/DialogBox.js"; import {dialogBoxCreate} from "../utils/DialogBox.js";
import {printArray} from "../utils/HelperFunctions.js"; import {printArray} from "../utils/HelperFunctions.js";
import {logBoxCreate} from "../utils/LogBox.js"; import {logBoxCreate} from "../utils/LogBox.js";
import numeral from "../utils/numeral.min.js";
import {formatNumber} from "../utils/StringHelperFunctions.js"; import {formatNumber} from "../utils/StringHelperFunctions.js";
@ -183,10 +184,10 @@ function updateActiveScriptsItems() {
total += updateActiveScriptsItemContent(workerScripts[i]); total += updateActiveScriptsItemContent(workerScripts[i]);
} }
document.getElementById("active-scripts-total-prod").innerHTML = document.getElementById("active-scripts-total-prod").innerHTML =
"Total online production of Active Scripts: $" + formatNumber(total, 2) + " / second<br>" + "Total online production of Active Scripts: " + numeral(total).format('$0.000a') + " / sec<br>" +
"Total online production since last Augmentation installation: $" + "Total online production since last Aug installation: " +
formatNumber(Player.scriptProdSinceLastAug, 2) + " ($" + numeral(Player.scriptProdSinceLastAug).format('$0.000a') + " (" +
formatNumber(Player.scriptProdSinceLastAug / Player.playtimeSinceLastAug, 2) + " / second)"; numeral(Player.scriptProdSinceLastAug / (Player.playtimeSinceLastAug/1000)).format('$0.000a') + " / sec)";
return total; return total;
} }

@ -124,31 +124,30 @@ let CONSTANTS = {
//Hacking Missions //Hacking Missions
HackingMissionRepToDiffConversion: 5000, //Faction rep is divided by this to get mission difficulty HackingMissionRepToDiffConversion: 5000, //Faction rep is divided by this to get mission difficulty
HackingMissionRepToRewardConversion: 20, //Faction rep divided byt his to get mission rep reward HackingMissionRepToRewardConversion: 12, //Faction rep divided byt his to get mission rep reward
HackingMissionSpamTimeIncrease: 20000, //How much time limit increase is gained when conquering a Spam Node (ms) HackingMissionSpamTimeIncrease: 20000, //How much time limit increase is gained when conquering a Spam Node (ms)
HackingMissionTransferAttackIncrease: 1.05, //Multiplier by which the attack for all Core Nodes is increased when conquering a Transfer Node HackingMissionTransferAttackIncrease: 1.05, //Multiplier by which the attack for all Core Nodes is increased when conquering a Transfer Node
HackingMissionHowToPlay: "Hacking missions are a minigame that, if won, will reward you with faction reputation.<br><br>" + HackingMissionHowToPlay: "Hacking missions are a minigame that, if won, will reward you with faction reputation.<br><br>" +
"In this game you control a set of Nodes and use them to try and defeat an enemy. Your Nodes " + "In this game you control a set of Nodes and use them to try and defeat an enemy. Your Nodes " +
"are colored blue, while the enemy's are red. There are also other nodes on the map colored gray" + "are colored blue, while the enemy's are red. There are also other nodes on the map colored gray " +
"that initially belong to neither you nor the enemy. The goal of the game is " + "that initially belong to neither you nor the enemy. The goal of the game is " +
"to capture all of the enemy's database nodes, which are the parallelogram-shaped ones, within the time limit. " + "to capture all of the enemy's database nodes, which are the parallelogram-shaped ones, within the time limit. " +
"If you cannot capture all of the enemy's database nodes in the time limit, you will lose.<br><br>" + "If you cannot capture all of the enemy's database nodes in the time limit, you will lose.<br><br>" +
"Each Node has three stats: Attack, Defense, and HP. There are five different actions that " + "Each Node has three stats: Attack, Defense, and HP. There are five different actions that " +
"a Node can take:<br><br> " + "a Node can take:<br><br> " +
"Attack - Targets an enemy Node and lowers its HP. The effectiveness is determined by the Node's Attack, the Player's " + "Attack - Targets an enemy Node and lowers its HP. The effectiveness is determined by the owner's Attack, the Player's " +
"hacking level, and the enemy's defense.<br>" + "hacking level, and the enemy's defense.<br>" +
"Scan - Targets an enemy Node and lowers its Defense. The effectiveness is determined by the Player's hacking level and the " + "Scan - Targets an enemy Node and lowers its Defense. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the " +
"enemy's defense.<br>" + "enemy's defense.<br>" +
"Weaken - Targets an enemy Node and lowers its Attack. The effectiveness is determined by the Player's hacking level and the enemy's " + "Weaken - Targets an enemy Node and lowers its Attack. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's " +
"defense.<br>" + "defense.<br>" +
"Fortify - Raises the Node's Defense. The effectiveness is determined by your hacking level.<br><br>" + "Fortify - Raises the Node's Defense. The effectiveness is determined by your hacking level.<br>" +
"To capture a Node, you must lower its HP down to 0. " + "Overflow - Raises the Node's Attack but lowers its Defense. The effectiveness is determined by your hacking level.<br><br>" +
"A Node's 'Attack' stats affects its effectiveness when attacking other Nodes. A Node's 'Defense' helps protect " + "Note that when determining the effectiveness of the above actions, the TOTAL Attack or Defense of the team is used, not just the " +
"against the actions of enemy Nodes. One important thing to note is that, when defending, your total 'Defense' " + "Attack/Defense of the individual Node that is performing the action.<br><br." +
"(sum of the Defense of all of your Nodes) is what's taken into account when determining the effect of offensive actions. " + "To capture a Node, you must lower its HP down to 0.<br><br>" +
"However, when attacking, only the 'Attack' of the Node being used to attack is taken into account.<br><br>" +
"There are six different types of Nodes:<br><br>" + "There are six different types of Nodes:<br><br>" +
"CPU Core - These are your main Nodes that are used to perform actions<br>" + "CPU Core - These are your main Nodes that are used to perform actions. Capable of performing every action<br>" +
"Firewall - Nodes with high defense. These Nodes cannot perform any actions<br>" + "Firewall - Nodes with high defense. These Nodes cannot perform any actions<br>" +
"Database - A special type of Node. The player's objective is to conquer all of the enemy's Database Nodes within " + "Database - A special type of Node. The player's objective is to conquer all of the enemy's Database Nodes within " +
"the time limit. These Nodes cannot perform any actions<br>" + "the time limit. These Nodes cannot perform any actions<br>" +
@ -157,10 +156,16 @@ let CONSTANTS = {
"Transfer - Conquering one of these nodes will increase the Attack of all of your CPU Cores by a small fixed percentage. " + "Transfer - Conquering one of these nodes will increase the Attack of all of your CPU Cores by a small fixed percentage. " +
"These Nodes are capable of performing every action except the 'Attack' action<br>" + "These Nodes are capable of performing every action except the 'Attack' action<br>" +
"Shield - Nodes with high defense. These Nodes cannot perform any actions<br><br>" + "Shield - Nodes with high defense. These Nodes cannot perform any actions<br><br>" +
"To assign an action to a Node, you must first select one of your Nodes. This can be done by simply clicking on it. Only " +
"one Node can be selected at a time, and it will be denoted with a white highlight. After selecting the Node, " +
"select its action using the Action Buttons near the top of the screen. Every action also has a corresponding keyboard " +
"shortcut that can be used as well.<br><br>" +
"For certain actions such as attacking, scanning, and weakening, the Node performing the action must have a target. To target " + "For certain actions such as attacking, scanning, and weakening, the Node performing the action must have a target. To target " +
"another node, simply click-and-drag from the 'source' Node to a target. A Node can only have one target, and you can only target " + "another node, simply click-and-drag from the 'source' Node to a target. A Node can only have one target, and you can only target " +
"Nodes that are adjacent to one of your Nodes (immediately above, below, or to the side. NOT diagonal). Furthermore, only CPU Cores and Transfer Nodes " + "Nodes that are adjacent to one of your Nodes (immediately above, below, or to the side. NOT diagonal). Furthermore, only CPU Cores and Transfer Nodes " +
"can target, since they are the only ones that can perform actions", "can target, since they are the only ones that can perform actions. To remove a target, you can simply click on the line that represents " +
"the connection between one of your Nodes and its target. Alternatively, you can select the 'source' Node and click the 'Drop Connection' button, " +
"or press 'd',",
//Gang constants //Gang constants
@ -418,6 +423,9 @@ let CONSTANTS = {
"args.length<br><br>" + "args.length<br><br>" +
"Note that none of the other functions that typically work with arrays, such as remove(), insert(), clear(), etc., will work on the " + "Note that none of the other functions that typically work with arrays, such as remove(), insert(), clear(), etc., will work on the " +
"args array.<br><br>" + "args array.<br><br>" +
"<u><h1>Javascript Modules</h1></u><br>" +
"Netscript supports the following Javascript Modules:<br><br>" +
"Math<br>Date (static functions only)<br><br>" +
"<u><h1> Functions </h1></u><br>" + "<u><h1> Functions </h1></u><br>" +
"You can NOT define you own functions in Netscript (yet), but there are several built in functions that " + "You can NOT define you own functions in Netscript (yet), but there are several built in functions that " +
"you may use: <br><br> " + "you may use: <br><br> " +
@ -649,8 +657,9 @@ let CONSTANTS = {
"on the server specified by the hostname/ip. The argument must be a string with the hostname/ip of the target server.<br><br>" + "on the server specified by the hostname/ip. The argument must be a string with the hostname/ip of the target server.<br><br>" +
"<i>getScriptIncome([scriptname], [hostname/ip], [args...])</i><br>" + "<i>getScriptIncome([scriptname], [hostname/ip], [args...])</i><br>" +
"Returns the amount of income the specified script generates while online (when the game is open, does not apply for " + "Returns the amount of income the specified script generates while online (when the game is open, does not apply for " +
"offline income). This function can also return the total income of all of your active scripts by running the function " + "offline income). This function can also be called with no arguments. If called with no arguments, then this function " +
"with no arguments.<br><br>" + "will return an array of two values. The first value is the total income ($/sec) of all of your active scripts (currently running). " +
"The second value is the total income ($/sec) from scripts since you last installed Augmentations (or destroyed a BitNode).<br><br>" +
"Remember that a script is uniquely identified by both its name and its arguments. So for example if you ran a script " + "Remember that a script is uniquely identified by both its name and its arguments. So for example if you ran a script " +
"with the arguments 'foodnstuff' and '5' then in order to use this function to get that script's income you must " + "with the arguments 'foodnstuff' and '5' then in order to use this function to get that script's income you must " +
"specify those arguments in this function call.<br><br>" + "specify those arguments in this function call.<br><br>" +
@ -669,6 +678,8 @@ let CONSTANTS = {
"The second argument must be a string with the hostname/IP of the target server. If the first argument is specified " + "The second argument must be a string with the hostname/IP of the target server. If the first argument is specified " +
"then the second argument must be specified as well. Any additional arguments passed to the function will specify " + "then the second argument must be specified as well. Any additional arguments passed to the function will specify " +
"the arguments passed into the target script.<br><br>" + "the arguments passed into the target script.<br><br>" +
"<i>getTimeSinceLastAug()</i><br>" +
"Returns the amount of time in milliseconds that have passed since you last installed Augmentations (or destroyed a BitNode).<br><br>" +
"<u><h1>Hacknet Nodes API</h1></u><br>" + "<u><h1>Hacknet Nodes API</h1></u><br>" +
"Netscript provides the following API for accessing and upgrading your Hacknet Nodes through scripts. This API does NOT work offline.<br><br>" + "Netscript provides the following API for accessing and upgrading your Hacknet Nodes through scripts. This API does NOT work offline.<br><br>" +
"<i>hacknetnodes</i><br> A special variable. This is an array that maps to the Player's Hacknet Nodes. The Hacknet Nodes are accessed through " + "<i>hacknetnodes</i><br> A special variable. This is an array that maps to the Player's Hacknet Nodes. The Hacknet Nodes are accessed through " +
@ -998,28 +1009,24 @@ let CONSTANTS = {
LatestUpdate: LatestUpdate:
"v0.29.1<br>" + "v0.29.1<br>" +
"-New gameplay feature that is currently in BETA: Hacking Missions. Hacking Missions is an active gameplay mechanic (its a minigame) " +
"that is meant to be used to earn faction reputation. However, since this is currently in beta, hacking missions will NOT grant reputation " +
"for the time being, since the feature likely has many bugs, balance problems, and other issues. If you have any feedback " +
"regarding the new feature, feel free to let me know<br>" +
"-CHANGED THE RETURN VALUE OF getScriptIncome() WHEN RAN WITH NO ARGUMENTS. It will now return an array of " +
"two values rather than a single value. This may break your scripts, so make sure to update them!<br>" +
"-Added continue statement for for/while loops<br>" + "-Added continue statement for for/while loops<br>" +
"-Added getServerMinSecurityLevel() Netscript function<br>" + "-Added getServerMinSecurityLevel(), getPurchasedServers(), and getTimeSinceLastAug() Netscript functions<br>" +
"-Netscript scp() function can now take an array as the first argument, and will try to copy " +
"every file specified in the array (it will just call scp() normally for every element in the array). " +
"If an array is passed in, then the scp() function returns true if at least one element from the array is successfully copied<br>" +
"-Added Javascript's Date module to Netscript. Since 'new' is not supported in Netscript yet, only the Date module's " + "-Added Javascript's Date module to Netscript. Since 'new' is not supported in Netscript yet, only the Date module's " +
"static methods will work (now(), UTC(), parse(), etc.).<br>" + "static methods will work (now(), UTC(), parse(), etc.).<br>" +
"-Failing a crime now gives half the experience it did before<br>" + "-Failing a crime now gives half the experience it did before<br>" +
"-The repeated 'Find The-Cave' message after installing The Red Pill Augmentation now only happens " + "-The forced repeated 'Find The-Cave' message after installing The Red Pill Augmentation now only happens " +
"if you've never destroyed a BitNode before<br>" + "if you've never destroyed a BitNode before, and will only popup every 15 minutes. If you have already destroyed a BitNode, " +
"-fileExists() function now works on literature files<br><br>" + "the message will not pop up if you have messages suppressed (if you don't have messages suppressed it WILL still repeatedly popup)<br>" +
"v0.29.0<br>" + "-fileExists() function now works on literature files<br><br>",
"-Added BitNode-5: Artificial Intelligence<br>" +
"-Added getIp(), getIntelligence(), getHackingMultipliers(), and getBitNodeMultipliers() Netscript functions (requires Source-File 5)<br>" +
"-Updated scan() Netscript function so that you can choose to have it print IPs rather than hostnames<br>" +
"-Refactored scp() Netscript function so that it takes an optional 'source server' argument<br>" +
"-For Infiltration, decreased the percentage by which the security level increases by " +
"about 10% for every location<br>" +
"-Using :w in the script editor's Vim keybinding mode should now save and quit to Terminal<br>" +
"-Some minor optimizations that should reduce the size of the save file<br>" +
"-scan-analyze Terminal command will no longer show your purchased servers, unless you pass a '-a' flag into the command<br>" +
"-After installing the Red Pill augmentation from Daedalus, the message telling you to find 'The-Cave' " +
"will now repeatedly pop up regardless of whether or not you have messages suppressed<br>" +
"-Various bugfixes",
} }
export {CONSTANTS}; export {CONSTANTS};

@ -1,7 +1,9 @@
import {Augmentations, Augmentation, import {Augmentations, Augmentation,
AugmentationNames} from "./Augmentations.js"; AugmentationNames} from "./Augmentations.js";
import {Programs} from "./CreateProgram.js"; import {Programs} from "./CreateProgram.js";
import {inMission} from "./Missions.js";
import {Player} from "./Player.js"; import {Player} from "./Player.js";
import {redPillFlag} from "./RedPill.js";
import {GetServerByHostname} from "./Server.js"; import {GetServerByHostname} from "./Server.js";
import {Settings} from "./Settings.js"; import {Settings} from "./Settings.js";
import {dialogBoxCreate, dialogBoxOpened} from "../utils/DialogBox.js"; import {dialogBoxCreate, dialogBoxOpened} from "../utils/DialogBox.js";
@ -75,10 +77,15 @@ function checkForMessagesToSend() {
redpillOwned = true; redpillOwned = true;
} }
if (redpill && redpillOwned && Player.sourceFiles.length === 0) { if (redpill && redpillOwned && Player.sourceFiles.length === 0 && !redPillFlag && !inMission) {
if (!dialogBoxOpened) { if (!dialogBoxOpened) {
sendMessage(redpill, true); sendMessage(redpill, true);
} }
} else if (redpill && redpillOwned) {
//If player has already destroyed a BitNode, message is not forced
if (!redPillFlag && !inMission && !dialogBoxOpened) {
sendMessage(redpill);
}
} else if (jumper0 && !jumper0.recvd && Player.hacking_skill >= 25) { } else if (jumper0 && !jumper0.recvd && Player.hacking_skill >= 25) {
sendMessage(jumper0); sendMessage(jumper0);
Player.getHomeComputer().programs.push(Programs.Flight); Player.getHomeComputer().programs.push(Programs.Flight);
@ -96,8 +103,6 @@ function checkForMessagesToSend() {
sendMessage(jumper4); sendMessage(jumper4);
} else if (bitrunnersTest && !bitrunnersTest.recvd && Player.hacking_skill >= 500) { } else if (bitrunnersTest && !bitrunnersTest.recvd && Player.hacking_skill >= 500) {
sendMessage(bitrunnersTest); sendMessage(bitrunnersTest);
} else if (redpill && redpillOwned) {
sendMessage(redpill);
} }
} }

@ -156,15 +156,17 @@ function HackingMission(rep, fac) {
this.faction = fac; this.faction = fac;
this.started = false; this.started = false;
this.time = 120000; //2 minutes, milliseconds this.time = 180000; //2 minutes, milliseconds
this.playerCores = []; this.playerCores = [];
this.playerNodes = []; //Non-core nodes this.playerNodes = []; //Non-core nodes
this.playerAtk = 0;
this.playerDef = 0; this.playerDef = 0;
this.enemyCores = []; this.enemyCores = [];
this.enemyDatabases = []; this.enemyDatabases = [];
this.enemyNodes = []; //Non-core nodes this.enemyNodes = []; //Non-core nodes
this.enemyAtk = 0;
this.enemyDef = 0; this.enemyDef = 0;
this.miscNodes = []; this.miscNodes = [];
@ -190,7 +192,7 @@ function HackingMission(rep, fac) {
//difficulty capped at 16 //difficulty capped at 16
this.difficulty = Math.min(16, Math.round(rep / CONSTANTS.HackingMissionRepToDiffConversion) + 1); this.difficulty = Math.min(16, Math.round(rep / CONSTANTS.HackingMissionRepToDiffConversion) + 1);
console.log("difficulty: " + this.difficulty); console.log("difficulty: " + this.difficulty);
this.reward = 200 + (rep / CONSTANTS.HackingMissionRepToRewardConversion); this.reward = 250 + (rep / CONSTANTS.HackingMissionRepToRewardConversion);
} }
HackingMission.prototype.init = function() { HackingMission.prototype.init = function() {
@ -213,7 +215,7 @@ HackingMission.prototype.init = function() {
//Randomly generate enemy nodes (CPU and Firewall) based on difficulty //Randomly generate enemy nodes (CPU and Firewall) based on difficulty
var numNodes = getRandomInt(this.difficulty, this.difficulty + 1); var numNodes = getRandomInt(this.difficulty, this.difficulty + 1);
var numFirewalls = getRandomInt(this.difficulty, this.difficulty + 2); var numFirewalls = getRandomInt(this.difficulty, this.difficulty + 1);
var numDatabases = getRandomInt(this.difficulty, this.difficulty + 1); var numDatabases = getRandomInt(this.difficulty, this.difficulty + 1);
var totalNodes = numNodes + numFirewalls + numDatabases; var totalNodes = numNodes + numFirewalls + numDatabases;
var xlimit = 7 - Math.floor(totalNodes / 8); var xlimit = 7 - Math.floor(totalNodes / 8);
@ -254,6 +256,7 @@ HackingMission.prototype.init = function() {
this.enemyDatabases.push(node); this.enemyDatabases.push(node);
} }
this.calculateDefenses(); this.calculateDefenses();
this.calculateAttacks();
this.createMap(); this.createMap();
} }
@ -297,11 +300,23 @@ HackingMission.prototype.createPageDom = function() {
startBtn.style.display = "inline-block"; startBtn.style.display = "inline-block";
startBtn.addEventListener("click", ()=>{ startBtn.addEventListener("click", ()=>{
this.start(); this.start();
return false;
});
var forfeitMission = document.createElement("a");
forfeitMission.innerHTML = "Forfeit Mission (Exit)";
forfeitMission.classList.add("a-link-button");
forfeitMission.classList.add("hack-mission-header-element");
forfeitMission.style.display = "inline-block";
forfeitMission.addEventListener("click", ()=> {
this.finishMission(false);
return false;
}); });
var timer = document.createElement("p"); var timer = document.createElement("p");
timer.setAttribute("id", "hacking-mission-timer"); timer.setAttribute("id", "hacking-mission-timer");
timer.style.display = "inline-block"; timer.style.display = "inline-block";
timer.style.margin = "6px";
//Create Action Buttons (Attack/Scan/Weaken/ etc...) //Create Action Buttons (Attack/Scan/Weaken/ etc...)
var actionsContainer = document.createElement("span"); var actionsContainer = document.createElement("span");
@ -353,18 +368,18 @@ HackingMission.prototype.createPageDom = function() {
this.actionButtons[5].appendChild(dropconnTooltip); this.actionButtons[5].appendChild(dropconnTooltip);
//Player/enemy defense displays will be in action container //Player/enemy defense displays will be in action container
var playerDefense = document.createElement("p"); var playerStats = document.createElement("p");
var enemyDefense = document.createElement("p"); var enemyStats = document.createElement("p");
playerDefense.style.display = "inline-block"; playerStats.style.display = "inline-block";
enemyDefense.style.display = "inline-block"; enemyStats.style.display = "inline-block";
playerDefense.style.color = "blue"; playerStats.style.color = "#00ccff";
enemyDefense.style.color = "red"; enemyStats.style.color = "red";
playerDefense.style.margin = "4px"; playerStats.style.margin = "4px";
enemyDefense.style.margin = "4px"; enemyStats.style.margin = "4px";
playerDefense.setAttribute("id", "hacking-mission-player-def"); playerStats.setAttribute("id", "hacking-mission-player-stats");
enemyDefense.setAttribute("id", "hacking-mission-enemy-def"); enemyStats.setAttribute("id", "hacking-mission-enemy-stats");
actionsContainer.appendChild(playerDefense); actionsContainer.appendChild(playerStats);
actionsContainer.appendChild(enemyDefense); actionsContainer.appendChild(enemyStats);
//Set Action Button event listeners //Set Action Button event listeners
this.actionButtons[0].addEventListener("click", ()=>{ this.actionButtons[0].addEventListener("click", ()=>{
@ -434,6 +449,7 @@ HackingMission.prototype.createPageDom = function() {
container.appendChild(inGameGuideBtn); container.appendChild(inGameGuideBtn);
container.appendChild(wikiGuideBtn); container.appendChild(wikiGuideBtn);
container.appendChild(startBtn); container.appendChild(startBtn);
container.appendChild(forfeitMission);
container.appendChild(timer); container.appendChild(timer);
container.appendChild(actionsContainer); container.appendChild(actionsContainer);
container.appendChild(timeDisplay); container.appendChild(timeDisplay);
@ -485,7 +501,34 @@ HackingMission.prototype.setActionButton = function(i, active=true) {
} }
//Should only be used at the start HackingMission.prototype.calculateAttacks = function() {
var total = 0;
for (var i = 0; i < this.playerCores.length; ++i) {
total += this.playerCores[i].atk;
}
for (var i = 0; i < this.playerNodes.length; ++i) {
total += this.playerNodes[i].atk;
}
this.playerAtk = total;
document.getElementById("hacking-mission-player-stats").innerHTML =
"Player Attack: " + formatNumber(this.playerAtk, 1) + "<br>" +
"Player Defense: " + formatNumber(this.playerDef, 1);
total = 0;
for (var i = 0; i < this.enemyCores.length; ++i) {
total += this.enemyCores[i].atk;
}
for (var i = 0; i < this.enemyDatabases.length; ++i) {
total += this.enemyDatabases[i].atk;
}
for (var i = 0; i < this.enemyNodes.length; ++i) {
total += this.enemyNodes[i].atk;
}
this.enemyAtk = total;
document.getElementById("hacking-mission-enemy-stats").innerHTML =
"Enemy Attack: " + formatNumber(this.enemyAtk, 1) + "<br>" +
"Enemy Defense: " + formatNumber(this.enemyDef, 1);
}
HackingMission.prototype.calculateDefenses = function() { HackingMission.prototype.calculateDefenses = function() {
var total = 0; var total = 0;
for (var i = 0; i < this.playerCores.length; ++i) { for (var i = 0; i < this.playerCores.length; ++i) {
@ -495,7 +538,8 @@ HackingMission.prototype.calculateDefenses = function() {
total += this.playerNodes[i].def; total += this.playerNodes[i].def;
} }
this.playerDef = total; this.playerDef = total;
document.getElementById("hacking-mission-player-def").innerText = document.getElementById("hacking-mission-player-stats").innerHTML =
"Player Attack: " + formatNumber(this.playerAtk, 1) + "<br>" +
"Player Defense: " + formatNumber(this.playerDef, 1); "Player Defense: " + formatNumber(this.playerDef, 1);
total = 0; total = 0;
for (var i = 0; i < this.enemyCores.length; ++i) { for (var i = 0; i < this.enemyCores.length; ++i) {
@ -508,7 +552,8 @@ HackingMission.prototype.calculateDefenses = function() {
total += this.enemyNodes[i].def; total += this.enemyNodes[i].def;
} }
this.enemyDef = total; this.enemyDef = total;
document.getElementById("hacking-mission-enemy-def").innerText = document.getElementById("hacking-mission-enemy-stats").innerHTML =
"Enemy Attack: " + formatNumber(this.enemyAtk, 1) + "<br>" +
"Enemy Defense: " + formatNumber(this.enemyDef, 1); "Enemy Defense: " + formatNumber(this.enemyDef, 1);
} }
@ -563,16 +608,16 @@ HackingMission.prototype.createMap = function() {
case 0: //Spam case 0: //Spam
var stats = { var stats = {
atk: 0, atk: 0,
def: randMult * getRandomInt(30, 40), def: randMult * getRandomInt(35, 55),
hp: randMult * getRandomInt(70, 90) hp: randMult * getRandomInt(125, 150)
} }
node = new Node(NodeTypes.Spam, stats); node = new Node(NodeTypes.Spam, stats);
break; break;
case 1: //Transfer case 1: //Transfer
var stats = { var stats = {
atk: 0, atk: 0,
def: randMult * getRandomInt(50, 70), def: randMult * getRandomInt(45, 65),
hp: randMult * getRandomInt(80, 95) hp: randMult * getRandomInt(150, 175)
} }
node = new Node(NodeTypes.Transfer, stats); node = new Node(NodeTypes.Transfer, stats);
break; break;
@ -580,8 +625,8 @@ HackingMission.prototype.createMap = function() {
default: default:
var stats = { var stats = {
atk: 0, atk: 0,
def: randMult * getRandomInt(90, 105), def: randMult * getRandomInt(60, 80),
hp: randMult * getRandomInt(130, 150) hp: randMult * getRandomInt(200, 250)
} }
node = new Node(NodeTypes.Shield, stats); node = new Node(NodeTypes.Shield, stats);
break; break;
@ -779,11 +824,8 @@ HackingMission.prototype.start = function() {
this.started = true; this.started = true;
this.initJsPlumb(); this.initJsPlumb();
var startBtn = clearEventListeners("hack-mission-start-btn"); var startBtn = clearEventListeners("hack-mission-start-btn");
startBtn.innerHTML = "Forfeit Mission"; startBtn.classList.remove("a-link-button");
startBtn.addEventListener("click", ()=>{ startBtn.classList.add("a-link-button-inactive");
this.finishMission(false);
return false;
});
} }
HackingMission.prototype.initJsPlumb = function() { HackingMission.prototype.initJsPlumb = function() {
@ -806,8 +848,8 @@ HackingMission.prototype.initJsPlumb = function() {
instance.makeSource(this.playerCores[i].el, { instance.makeSource(this.playerCores[i].el, {
deleteEndpointsOnEmpty:true, deleteEndpointsOnEmpty:true,
maxConnections:1, maxConnections:1,
anchor:"Center", anchor:"Continuous",
connector:"Straight" connector:"Flowchart"
}); });
} }
@ -815,29 +857,29 @@ HackingMission.prototype.initJsPlumb = function() {
for (var i = 0; i < this.enemyCores.length; ++i) { for (var i = 0; i < this.enemyCores.length; ++i) {
instance.makeTarget(this.enemyCores[i].el, { instance.makeTarget(this.enemyCores[i].el, {
maxConnections:-1, maxConnections:-1,
anchor:"Center", anchor:"Continuous",
connector:"Straight" connector:"Flowchart"
}); });
} }
for (var i = 0; i < this.enemyDatabases.length; ++i) { for (var i = 0; i < this.enemyDatabases.length; ++i) {
instance.makeTarget(this.enemyDatabases[i].el, { instance.makeTarget(this.enemyDatabases[i].el, {
maxConnections:-1, maxConnections:-1,
anchor:"Center", anchor:"Continuous",
connector:["Straight"] connector:["Flowchart"]
}); });
} }
for (var i = 0; i < this.enemyNodes.length; ++i) { for (var i = 0; i < this.enemyNodes.length; ++i) {
instance.makeTarget(this.enemyNodes[i].el, { instance.makeTarget(this.enemyNodes[i].el, {
maxConnections:-1, maxConnections:-1,
anchor:"Center", anchor:"Continuous",
connector:"Straight" connector:"Flowchart"
}); });
} }
for (var i = 0; i < this.miscNodes.length; ++i) { for (var i = 0; i < this.miscNodes.length; ++i) {
instance.makeTarget(this.miscNodes[i].el, { instance.makeTarget(this.miscNodes[i].el, {
maxConnections:-1, maxConnections:-1,
anchor:"Center", anchor:"Continuous",
connector:"Straight" connector:"Flowchart"
}); });
} }
@ -888,45 +930,60 @@ HackingMission.prototype.dropAllConnectionsToNode = function(node) {
} }
} }
var storedCycles = 0;
HackingMission.prototype.process = function(numCycles=1) { HackingMission.prototype.process = function(numCycles=1) {
if (!this.started) {return;} if (!this.started) {return;}
storedCycles += numCycles;
if (storedCycles < 3) {return;} //Only process every 2 cycles minimum
var res = false; var res = false;
//Process actions of all player nodes //Process actions of all player nodes
this.playerCores.forEach((node)=>{ this.playerCores.forEach((node)=>{
res |= this.processNode(node, numCycles); res |= this.processNode(node, storedCycles);
}); });
this.playerNodes.forEach((node)=>{ this.playerNodes.forEach((node)=>{
if (node.type === NodeTypes.Transfer) { if (node.type === NodeTypes.Transfer) {
res |= this.processNode(node, numCycles); res |= this.processNode(node, storedCycles);
} }
}); });
//Process actions of all enemy nodes //Process actions of all enemy nodes
this.enemyCores.forEach((node)=>{ this.enemyCores.forEach((node)=>{
res |= this.processNode(node, numCycles); res |= this.processNode(node, storedCycles);
}); });
this.enemyNodes.forEach((node)=>{ this.enemyNodes.forEach((node)=>{
if (node.type === NodeTypes.Transfer) { if (node.type === NodeTypes.Transfer) {
res |= this.processNode(node, numCycles); res |= this.processNode(node, storedCycles);
} }
}); });
if (res) {this.calculateDefenses();} if (res) {
this.calculateAttacks();
this.calculateDefenses();
}
if (this.enemyDatabases.length === 0) { if (this.enemyDatabases.length === 0) {
this.finishMission(true); this.finishMission(true);
return; return;
} }
//Defense of every misc Node increase by 1 per second
this.miscNodes.forEach((node)=>{
node.def += (0.1 * storedCycles);
this.updateNodeDomElement(node);
});
//Update timer and check if player lost //Update timer and check if player lost
this.time -= (numCycles * Engine._idleSpeed); this.time -= (storedCycles * Engine._idleSpeed);
if (this.time <= 0) { if (this.time <= 0) {
this.finishMission(false); this.finishMission(false);
return; return;
} }
this.updateTimer(); this.updateTimer();
storedCycles = 0;
} }
//Returns a bool representing whether defenses need to be re-calculated //Returns a bool representing whether defenses need to be re-calculated
@ -935,48 +992,52 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
return; return;
} }
var targetNode = null, def; var targetNode = null, def, atk;
if (nodeObj.conn) { if (nodeObj.conn) {
targetNode = this.getNodeFromElement(nodeObj.conn.target); targetNode = this.getNodeFromElement(nodeObj.conn.target);
if (targetNode.plyrCtrl) { if (targetNode.plyrCtrl) {
def = this.playerDef; def = this.playerDef;
atk = this.enemyAtk;
} else if (targetNode.enmyCtrl) { } else if (targetNode.enmyCtrl) {
def = this.enemyDef; def = this.enemyDef;
atk = this.playerAtk;
} else { //Misc Node } else { //Misc Node
def = targetNode.def; def = targetNode.def;
nodeObj.plyrCtrl ? atk = this.playerAtk : atk = this.enemyAtk;
} }
} }
//Calculations are per second, so divide everything by 5 //Calculations are per second, so divide everything by 5
var calcDefenses = false; var calcStats = false;
switch(nodeObj.action) { switch(nodeObj.action) {
case NodeActions.Attack: case NodeActions.Attack:
if (nodeObj.conn === null) {break;} if (nodeObj.conn === null) {break;}
var dmg = this.calculateAttackDamage(nodeObj.atk, def, Player.hacking_skill); var dmg = this.calculateAttackDamage(atk, def, Player.hacking_skill);
targetNode.hp -= (dmg/5 * numCycles); targetNode.hp -= (dmg/5 * numCycles);
break; break;
case NodeActions.Scan: case NodeActions.Scan:
if (nodeObj.conn === null) {break;} if (nodeObj.conn === null) {break;}
var eff = this.calculateScanEffect(nodeObj.atk, def, Player.hacking_skill); var eff = this.calculateScanEffect(atk, def, Player.hacking_skill);
targetNode.def -= (eff/5 * numCycles); targetNode.def -= (eff/5 * numCycles);
calcDefenses = true; calcStats = true;
break; break;
case NodeActions.Weaken: case NodeActions.Weaken:
if (nodeObj.conn === null) {break;} if (nodeObj.conn === null) {break;}
var eff = this.calculateWeakenEffect(nodeObj.atk, def, Player.hacking_skill); var eff = this.calculateWeakenEffect(atk, def, Player.hacking_skill);
targetNode.atk -= (eff/5 * numCycles); targetNode.atk -= (eff/5 * numCycles);
calcStats = true;
break; break;
case NodeActions.Fortify: case NodeActions.Fortify:
var eff = this.calculateFortifyEffect(Player.hacking_skill); var eff = this.calculateFortifyEffect(Player.hacking_skill);
nodeObj.def += (eff/5 * numCycles); nodeObj.def += (eff/5 * numCycles);
calcDefenses = true; calcStats = true;
break; break;
case NodeActions.Overflow: case NodeActions.Overflow:
var eff = this.calculateOverflowEffect(Player.hacking_skill); var eff = this.calculateOverflowEffect(Player.hacking_skill);
if (nodeObj.def < eff) {break;} if (nodeObj.def < eff) {break;}
nodeObj.def -= (eff/5 * numCycles); nodeObj.def -= (eff/5 * numCycles);
nodeObj.atk += (eff/5 * numCycles); nodeObj.atk += (eff/5 * numCycles);
calcDefenses = true; calcStats = true;
break; break;
default: default:
console.log("ERR: Invalid Node Action: " + nodeObj.action); console.log("ERR: Invalid Node Action: " + nodeObj.action);
@ -1014,20 +1075,20 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
this.jsplumbinstance.makeSource(targetNode.el, { this.jsplumbinstance.makeSource(targetNode.el, {
deleteEndpointsOnEmpty:true, deleteEndpointsOnEmpty:true,
maxConnections:1, maxConnections:1,
anchor:"Center", anchor:"Continuous",
connector:"Straight" connector:"Flowchart"
}); });
} else { } else {
targetNode.setControlledByEnemy(); targetNode.setControlledByEnemy();
this.jsplumbinstance.unmakeSource(targetNode.el); this.jsplumbinstance.unmakeSource(targetNode.el);
this.jsplumbinstance.makeTarget(targetNode.el, { this.jsplumbinstance.makeTarget(targetNode.el, {
maxConnections:-1, maxConnections:-1,
anchor:"Center", anchor:"Continuous",
connector:["Straight"] connector:["Flowchart"]
}); });
} }
calcDefenses = true; calcStats = true;
//Helper function to swap nodes between the respective enemyNodes/playerNodes arrays //Helper function to swap nodes between the respective enemyNodes/playerNodes arrays
function swapNodes(orig, dest, targetNode) { function swapNodes(orig, dest, targetNode) {
@ -1105,24 +1166,24 @@ HackingMission.prototype.processNode = function(nodeObj, numCycles=1) {
} }
this.updateNodeDomElement(nodeObj); this.updateNodeDomElement(nodeObj);
if (targetNode) {this.updateNodeDomElement(targetNode);} if (targetNode) {this.updateNodeDomElement(targetNode);}
return calcDefenses; return calcStats;
} }
var hackEffWeightSelf = 100; //Weight for Node actions on self var hackEffWeightSelf = 150; //Weight for Node actions on self
var hackEffWeightTarget = 15; //Weight for Node Actions against Target var hackEffWeightTarget = 25; //Weight for Node Actions against Target
var hackEffWeightAttack = 100; //Weight for Attack action var hackEffWeightAttack = 110; //Weight for Attack action
//Returns damage per cycle based on stats //Returns damage per cycle based on stats
HackingMission.prototype.calculateAttackDamage = function(atk, def, hacking = 0) { HackingMission.prototype.calculateAttackDamage = function(atk, def, hacking = 0) {
return Math.max(atk + (hacking / hackEffWeightAttack) - def, 0.1); return Math.max(atk + (hacking / hackEffWeightAttack) - def, 1);
} }
HackingMission.prototype.calculateScanEffect = function(atk, def, hacking=0) { HackingMission.prototype.calculateScanEffect = function(atk, def, hacking=0) {
return Math.max((atk/2) + hacking / hackEffWeightTarget - def, 0.1); return Math.max((atk/2) + hacking / hackEffWeightTarget - def, 1);
} }
HackingMission.prototype.calculateWeakenEffect = function(atk, def, hacking=0) { HackingMission.prototype.calculateWeakenEffect = function(atk, def, hacking=0) {
return Math.max((atk/2) + hacking / hackEffWeightTarget - def, 0.1); return Math.max((atk/2) + hacking / hackEffWeightTarget - def, 1);
} }
HackingMission.prototype.calculateFortifyEffect = function(hacking=0) { HackingMission.prototype.calculateFortifyEffect = function(hacking=0) {
@ -1151,9 +1212,11 @@ HackingMission.prototype.finishMission = function(win) {
currMission = null; currMission = null;
if (win) { if (win) {
dialogBoxCreate("Mission won!"); dialogBoxCreate("Mission won! This feature is currently in " +
"beta so you did not earn anything, but normally you would have won " +
this.reward + " reputation with " + this.faction.name);
} else { } else {
dialogBoxCreate("Mission lost!"); dialogBoxCreate("Mission lost/forfeited!");
} }
//Clear mission container //Clear mission container

@ -469,6 +469,16 @@ function NetscriptFunctions(workerScript) {
if (arguments.length !== 2 && arguments.length !== 3) { if (arguments.length !== 2 && arguments.length !== 3) {
throw makeRuntimeRejectMsg(workerScript, "Error: scp() call has incorrect number of arguments. Takes 2 or 3 arguments"); throw makeRuntimeRejectMsg(workerScript, "Error: scp() call has incorrect number of arguments. Takes 2 or 3 arguments");
} }
if (scriptname && scriptname.constructor === Array) {
//Recursively call scp on all elements of array
var res = false;
scriptname.forEach(function(script) {
if (NetscriptFunctions(workerScript).scp(script, ip1, ip2)) {
res = true;
};
});
return res;
}
if (!scriptname.endsWith(".lit") && !scriptname.endsWith(".script")) { if (!scriptname.endsWith(".lit") && !scriptname.endsWith(".script")) {
throw makeRuntimeRejectMsg(workerScript, "Error: scp() only works for .script and .lit files"); throw makeRuntimeRejectMsg(workerScript, "Error: scp() only works for .script and .lit files");
} }
@ -1124,7 +1134,10 @@ function NetscriptFunctions(workerScript) {
getScriptIncome : function(scriptname, ip) { getScriptIncome : function(scriptname, ip) {
if (arguments.length === 0) { if (arguments.length === 0) {
//Get total script income //Get total script income
return updateActiveScriptsItems(); var res = [];
res.push(updateActiveScriptsItems());
res.push(Player.scriptProdSinceLastAug / (Player.playtimeSinceLastAug/1000));
return res;
} else { } else {
//Get income for a particular script //Get income for a particular script
var server = getServer(ip); var server = getServer(ip);
@ -1170,6 +1183,9 @@ function NetscriptFunctions(workerScript) {
return runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime; return runningScriptObj.onlineExpGained / runningScriptObj.onlineRunningTime;
} }
}, },
getTimeSinceLastAug : function() {
return Player.playtimeSinceLastAug;
},
/* Singularity Functions */ /* Singularity Functions */
universityCourse(universityName, className) { universityCourse(universityName, className) {

@ -275,6 +275,7 @@ PlayerObject.prototype.prestigeAugmentation = function() {
this.lastUpdate = new Date().getTime(); this.lastUpdate = new Date().getTime();
this.playtimeSinceLastAug = 0; this.playtimeSinceLastAug = 0;
this.scriptProdSinceLastAug = 0;
this.hacknetNodes.length = 0; this.hacknetNodes.length = 0;
this.totalHacknetNodeProduction = 0; this.totalHacknetNodeProduction = 0;
@ -365,6 +366,7 @@ PlayerObject.prototype.prestigeSourceFile = function() {
this.hasTixApiAccess = false; this.hasTixApiAccess = false;
this.playtimeSinceLastAug = 0; this.playtimeSinceLastAug = 0;
this.scriptProdSinceLastAug = 0;
} }
PlayerObject.prototype.getCurrentServer = function() { PlayerObject.prototype.getCurrentServer = function() {

@ -840,6 +840,7 @@ let Engine = {
Player.gang.process(numCycles); Player.gang.process(numCycles);
} }
//Mission
if (inMission && currMission) { if (inMission && currMission) {
currMission.process(numCycles); currMission.process(numCycles);
} }
@ -967,7 +968,7 @@ let Engine = {
if (Engine.Counters.messages <= 0) { if (Engine.Counters.messages <= 0) {
checkForMessagesToSend(); checkForMessagesToSend();
if (Augmentations[AugmentationNames.TheRedPill].owned) { if (Augmentations[AugmentationNames.TheRedPill].owned) {
Engine.Counters.messages = 600; //2 minutes for Red pill message Engine.Counters.messages = 4500; //15 minutes for Red pill message
} else { } else {
Engine.Counters.messages = 150; Engine.Counters.messages = 150;
} }