Merge pull request #16 from danielyxie/dev

Dev v0.11
This commit is contained in:
danielyxie 2017-05-21 17:05:27 -04:00 committed by GitHub
commit 5d1371dcf6
22 changed files with 1323 additions and 320 deletions

@ -35,10 +35,5 @@ Tasks TODO:
Private beta feedback
I'd suggest putting a "Back" button in the tutorial
window,
Also not really a big deal, but I'm at 110% zoom on chrome and the tutorial window
covers some of the text
Now, only other suggestion before sleep would be to be able to buy multiple Hacknet upgrades in one click
6) - Maybe show total $ somewhere onscreen at all (or most) times. $/sec would also be good to know, depending on if you want the player to know that. Bottom of the menu on the left is an empty enough place to keep these.
test for open beta

@ -1,19 +1,26 @@
/* interactivetutorial.css */
#interactive-tutorial-wrapper {
position:relative;
}
#interactive-tutorial-container {
display: none;
position: fixed; /* Stay in place */
position: absolute; /* Stay in place */
right: 0;
top: 0;
height: 100%; /* Full height */
margin: 30% auto;
height: 400px; /* Full height */
padding: 10px;
border: 5px solid #FFFFFF;
width: 35%;
width: 25%;
overflow: auto; /* Enable scroll if needed */
background-color: #444; /* Fallback color */
color: white;
}
#interactive-tutorial-container > strong {
background-color: #444;
}
#interactive-tutorial-text {
padding: 4px;
margin: 4px;

@ -39,7 +39,7 @@
#script-editor-save-and-close-button,
#script-editor-netscript-doc-button {
float: right;
display: inline-block;
display: block;
width: 50%;
margin-right: 25%;
}
@ -56,7 +56,7 @@
float: left;
resize: none;
color: #66ff33;
width: 75%;
width: 70%;
margin: 10px;
padding: 5px;
@ -79,7 +79,7 @@
#script-editor-text {
color: #66ff33;
width: 75%;
width: 70%;
height: 100%;
margin: 10px;
padding: 5px;
@ -112,14 +112,14 @@
}
#active-scripts-text {
width: 80%;
width: 70%;
margin: 6px;
padding: 4px;
}
.active-scripts-list > li {
margin: 6px;
width: 80%;
width: 70%;
}
.active-scripts-list>li h2{
@ -151,21 +151,33 @@
#hacknet-nodes-container li{
padding: 6px;
margin: 6px;
width: 70%;
}
#hacknet-nodes-text,
#hacknet-nodes-money {
width: 80%;
width: 70%;
margin: 10px;
padding: 10px;
}
#hacknet-nodes-purchase-button {
display: block;
}
#hacknet-nodes-money-multipliers-div {
display: inline-block;
width: 70%;
}
#hacknet-nodes-multipliers {
float: right;
}
.hacknet-node {
margin: 6px;
padding: 6px;
width: 85%;
border: 2px solid white;
-webkit-box-shadow:
inset 0 0 8px rgba(0,0,0,0.1),
@ -213,7 +225,7 @@
}
#create-program-page-text {
width: 80%;
width: 70%;
}
.create-program-a-link-button {
@ -263,26 +275,45 @@
margin: 6px;
}
#faction-hack-div,
#faction-fieldwork-div,
#faction-securitywork-div {
#faction-securitywork-div,
#faction-donate-div {
overflow: hidden;
width: 70%;
height: 100%;
}
#faction-hack-div-wrapper,
#faction-fieldwork-div-wrapper,
#faction-securitywork-div-wrapper {
#faction-securitywork-div-wrapper,
#faction-donate-div-wrapper {
float: left;
border: 2px solid #333;
padding: 14px 6px 4px 6px;
margin: 6px;
}
#faction-hack-button {
#faction-hack-button,
#faction-fieldwork-button,
#faction-securitywork-button {
margin: 8px;
}
#faction-donate-amount-txt,
#faction-donate-input {
padding: 6px;
margin: 6px;
display: inline-block;
color: #66ff33;
}
#faction-donate-amount-txt {
width:50%;
}
div.faction-clear {
clear: both;
}
@ -290,6 +321,7 @@ div.faction-clear {
#faction-container p {
padding: 6px;
margin: 6px;
width: 70%;
}
/* Faction Augmentations */
@ -311,6 +343,11 @@ div.faction-clear {
padding: 4px;
}
#faction-augmentations-list > li{
margin: 4px;
padding: 4px;
}
/* World */
#world-container li {
margin: 0 0 15px 0;
@ -330,7 +367,7 @@ div.faction-clear {
}
#augmentations-list li {
width: 80%;
width: 70%;
background-color: #333;
}
@ -339,7 +376,7 @@ div.faction-clear {
margin: 4px;
color: #66ff33;
padding: 8px;
width: 80%;
width: 70%;
background-color: #333;
text-decoration: none;
}
@ -361,7 +398,7 @@ div.faction-clear {
}
#tutorial-text {
width: 80%;
width: 70%;
margin: 10px;
}
@ -376,7 +413,7 @@ div.faction-clear {
}
#location-slums-description {
width: 80%;
width: 70%;
margin: 10px;
}

@ -89,8 +89,8 @@ tr:focus {
text-decoration: none;
background-color: #555;
color: #FFFFFF;
padding: 6px;
margin: 6px;
padding: 5px;
margin: 5px;
border: 1px solid #333333;
width: 50%;
}
@ -104,8 +104,8 @@ tr:focus {
text-decoration: none;
background-color: #333;
color: #FFFFFF;
padding: 6px;
margin: 6px;
padding: 5px;
margin: 5px;
border-top: 1px solid #333333;
border-right: 1px solid #333333;
border-bottom: 1px solid #333333;
@ -214,11 +214,45 @@ tr:focus {
position:fixed;
top:0px;
-webkit-animation:status-text 3s 1;
background-color: transparent;
}
#status-text-container {
background-color: transparent;
}
#status-text {
font-size: 20px;
color: #FFFFFF;
right: 0;
bottom: 0;
bottom: 0;
padding: 4px;
background-color: transparent;
z-index: 2;
}
/* Character Overview */
#character-overview-wrapper {
position: relative;
}
#character-overview-container {
display: none;
position: absolute; /* Stay in place */
right: 0;
top: 0;
height: 150px; /* Full height */
/*margin: 50% auto;*/
padding: 5px;
border: 2px solid #66ff33;
width: 18%;
overflow: auto; /* Enable scroll if needed */
background-color: #444; /* Fallback color */
z-index: 1;
}
#character-overview-text {
padding: 4px;
margin: 12px;
color: white;
background-color: #444;
}

@ -116,6 +116,7 @@
<li class="debug-delete-scripts-tab">
<a href="#" id="debug-delete-scripts-link"> (DEBUG) Delete Active Scripts </a>
</li>
</ul>
</div>
@ -179,7 +180,15 @@
in order to increase its computing power and thereby increase the profit you earn from it.
</p>
<a href="#" id="hacknet-nodes-purchase-button" class="a-link-button"> Purchase Hacknet Node </a>
<p id="hacknet-nodes-money"> </p>
<div id="hacknet-nodes-money-multipliers-div">"
<p id="hacknet-nodes-money"> </p>
<span id="hacknet-nodes-multipliers">
<a id="hacknet-nodes-1x-multiplier" class="a-link-button-inactive"> x1 </a>
<a id="hacknet-nodes-5x-multiplier" class="a-link-button"> x5 </a>
<a id="hacknet-nodes-10x-multiplier" class="a-link-button"> x10 </a>
<a id="hacknet-nodes-max-multiplier" class="a-link-button"> MAX </a>
</span>
</div>
<ul id="hacknet-nodes-list" style="list-style : none;">
</ul>
</div>
@ -434,7 +443,7 @@
<p id="faction-reputation"></p>
<p id="work-description-text">
<p id="faction-work-description-text">
Perform work/carry out assignments for your faction to help further its cause! By doing so
you will gain reputation for your faction. You will also gain reputation passively over time,
although at a very slow rate. Note that you cannot
@ -445,8 +454,8 @@
<div id="faction-hack-div-wrapper">
<a href="#" id="faction-hack-button" class="a-link-button">Hacking Contracts</a>
<p id="faction-hack-text">
Complete hacking contracts for your faction! <br>
Your effectiveness, which determines how much reputation you gain for this faction, is based on your hacking skill. <br>
Complete hacking contracts for your faction.
Your effectiveness, which determines how much reputation you gain for this faction, is based on your hacking skill.
You will gain hacking exp.
</p>
</div>
@ -457,8 +466,8 @@
<div id="faction-fieldwork-div-wrapper">
<a href="#" id="faction-fieldwork-button" class="a-link-button">Field Work</a>
<p id="faction-fieldwork-text">
Carry out field missions for your faction. <br>
Your effectiveness, which determines how much reputation you gain for this faction, is based on all of your stats. <br>
Carry out field missions for your faction.
Your effectiveness, which determines how much reputation you gain for this faction, is based on all of your stats.
You will gain exp for all stats.
</p>
</div>
@ -469,13 +478,29 @@
<div id="faction-securitywork-div-wrapper">
<a href="#" id="faction-securitywork-button" class="a-link-button">Security Work</a>
<p id="faction-securitywork-text">
Serve in a security detail for your faction. <br>
Your effectiveness, which determines how much reputation you gain for this faction, is based on your combat stats. <br>
Serve in a security detail for your faction.
Your effectiveness, which determines how much reputation you gain for this faction, is based on your combat stats.
You will gain exp for all combat stats.
</p>
</div>
<div class="faction-clear"></div>
</div>
<div id="faction-donate-div">
<div id="faction-donate-div-wrapper">
<a href="#" id="faction-donate-button" class="a-link-button">Donate Money</a>
<p id="faction-donate-text">
Donate money to your faction. You will gain reputation based on how much money you donate
</p>
<div>
<label id="faction-donate-amount-txt">Enter amount to donate: $</label>
<input id="faction-donate-input" type="number"> </input>
</div>
<p id="faction-donate-rep-gain"> This donation will result in 0 reputation gain</p>
</div>
<div class="faction-clear"></div>
</div>
<p>
<br><br><br>
@ -572,7 +597,7 @@
<a href="#" id="location-purchase-512gb" class="a-link-button"> Purchase 512GB Server - $38,400,000</a>
<a href="#" id="location-purchase-1tb" class="a-link-button"> Purchase 1TB Server - $75,000,000</a>
<a href="#" id="location-purchase-tor" class="a-link-button"> Purchase TOR Router - $100,000</a>
<a href="#" id="location-purchase-home-ram" class="a-link-button"> Purchase RAM for Home computer </a>
<a href="#" id="location-purchase-home-ram" class="a-link-button"> Purchase additional RAM for Home computer </a>
<!-- Travel agency -->
<p id="location-travel-agency-text">
@ -691,16 +716,25 @@
</div>
<!-- Interactive Tutorial Text Screen -->
<div id="interactive-tutorial-wrapper">
<div id="interactive-tutorial-container">
<p id="interactive-tutorial-text"> </p>
<span id="interactive-tutorial-exit"> Exit Tutorial </span>
<span id="interactive-tutorial-next"> Next </span>
<span id="interactive-tutorial-back"> Back </span>
</div>
</div>
<!-- Character Overview Screen -->
<div id="character-overview-wrapper">
<div id="character-overview-container">
<p id="character-overview-text"> </p>
</div>
</div>
<!-- Status text -->
<div id="status-text-container">
<p id="status-text"> </p>
<p id="status-text">Test </p>
</div>
</body>

File diff suppressed because it is too large Load Diff

@ -1,5 +1,5 @@
CONSTANTS = {
Version: "0.10",
Version: "0.11",
//Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
//and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
@ -15,14 +15,14 @@ CONSTANTS = {
BaseCostFor1GBOfRamHacknetNode: 30000,
BaseCostForHacknetNode: 1000,
BaseCostForHacknetNodeCore: 1000000,
BaseCostForHacknetNodeCore: 400000,
/* Hacknet Node constants */
HacknetNodeMoneyGainPerLevel: 1,
HacknetNodePurchaseNextMult: 1.30, //Multiplier when purchasing an additional hacknet node
HacknetNodeMoneyGainPerLevel: 1.25,
HacknetNodePurchaseNextMult: 1.33, //Multiplier when purchasing an additional hacknet node
HacknetNodeUpgradeLevelMult: 1.06, //Multiplier for cost when upgrading level
HacknetNodeUpgradeRamMult: 1.20, //Multiplier for cost when upgrading RAM
HacknetNodeUpgradeCoreMult: 1.40, //Multiplier for cost when buying another core
HacknetNodeUpgradeRamMult: 1.23, //Multiplier for cost when upgrading RAM
HacknetNodeUpgradeCoreMult: 1.43, //Multiplier for cost when buying another core
HacknetNodeMaxLevel: 200,
HacknetNodeMaxRam: 64,
@ -31,7 +31,6 @@ CONSTANTS = {
/* Augmentation */
//NeuroFlux Governor cost multiplier as you level up
NeuroFluxGovernorLevelMult: 1.09,
/* Script related things */
//Time (ms) it takes to run one operation in Netscript.
@ -49,10 +48,12 @@ CONSTANTS = {
ScriptRelaysmtpRamCost: 0.05,
ScriptHttpwormRamCost: 0.05,
ScriptSqlinjectRamCost: 0.05,
ScriptRunRamCost: 0.75,
ScriptRunRamCost: 0.8,
ScriptGetHackingLevelRamCost: 0.1,
ScriptGetServerMoneyRamCost: 0.1,
ScriptOperatorRamCost: 0.01,
ScriptPurchaseHacknetRamCost: 1.0,
ScriptUpgradeHacknetRamCost: 1.0,
//Server growth rate
ServerGrowthRate: 1.00075,
@ -156,9 +157,9 @@ CONSTANTS = {
"and you can purchase additional servers as you progress through the game. Connecting to other servers " +
"and hacking them can be a major source of income and experience. Servers can also be used to run " +
"scripts which can automatically hack servers for you. <br><br>" +
"In order to navigate between machines, use the 'scan' or 'netstat' commands to see all servers " +
"that are reachable from your current server. Then, you can use the 'connect [hostname/ip]' or " +
"'telnet [hostname/ip]' commands to connect to one of the available machines. <br><br>" +
"In order to navigate between machines, use the 'scan' command to see all servers " +
"that are reachable from your current server. Then, you can use the 'connect [hostname/ip]' " +
"command to connect to one of the available machines. <br><br>" +
"The 'hostname' and 'ifconfig' commands can be used to display the hostname/IP of the " +
"server you are currently connected to.",
@ -169,7 +170,7 @@ CONSTANTS = {
"The key to hacking a server is to gain root access to that server. This can be done using " +
"the NUKE virus (NUKE.exe). You start the game with a copy of the NUKE virus on your home " +
"computer. The NUKE virus attacks the target server's open ports using buffer overflow " +
"exploits. When successful, you are granted root administrative access to the machine. <br>" +
"exploits. When successful, you are granted root administrative access to the machine. <br><br>" +
"Typically, in order for the NUKE virus to succeed, the target server needs to have at least " +
"one of its ports opened. Some servers have no security and will not need any ports opened. Some " +
"will have very high security and will need many ports opened. In order to open ports on another " +
@ -209,16 +210,23 @@ CONSTANTS = {
"run [script] - Run a script <br>" +
"tail [script] - Displays a script's logs<br>" +
"top - Displays all active scripts and their RAM usage <br><br>" +
"<strong>Note that because of the way the Netscript interpreter is implemented, " +
"<u><h1> Notes about how scripts work offline </h1> </u><br>" +
"<strong> The scripts that you write and execute are interpreted in Javascript. For this " +
"reason, it is not possible for these scripts to run while offline (when the game is closed). " +
"It is important to note that for this reason, conditionals such as if/else statements and certain " +
"commands such as purchaseHacknetNode() or nuke() will not work while the game is offline.<br><br>" +
"However, Scripts WILL continue to generate money and hacking exp for you while the game is offline. This " +
"offline production is based off of the scripts' production while the game is online.<br><br> " +
"Also, note that because of the way the Netscript interpreter is implemented, " +
"whenever you reload or re-open the game all of the scripts that you are running will " +
"start running from the BEGINNING of the code. The game does not keep track of where exactly " +
"the execution of a script is when it saves/loads. </strong><br><br>",
TutorialNetscriptText: "Netscript is a very simple programming language implemented for this game. The language has " +
TutorialNetscriptText: "Netscript is a programming language implemented for this game. The language has " +
"your basic programming constructs and several built-in commands that are used to hack. <br><br>" +
"<u><h1> Variables and data types </h1></u><br>" +
"The following data types are supported by Netscript: <br>" +
"numeric - Integers and floats (6, 10.4999)<br>" +
"string - Encapsulated by single or double quotes ('this is a string')<br>" +
"numeric - Integers and floats (eg. 6, 10.4999)<br>" +
"string - Encapsulated by single or double quotes (eg. 'this is a string')<br>" +
"boolean - true or false<br><br>" +
"To create a variable, use the assign (=) operator. The language is not strongly typed. Examples: <br>" +
"i = 5;<br>" +
@ -245,26 +253,41 @@ CONSTANTS = {
"You can NOT define you own functions in Netscript (yet), but there are several built in functions that " +
"you may use: <br><br> " +
"<i>hack(hostname/ip)</i><br>Core function that is used to try and hack servers to steal money and gain hacking experience. The argument passed in must be a string with " +
"either the IP or hostname of the server you want to hack. Attempting to hack a server takes time. This time is dependent on your hacking skill and the server's " +
"security level. <br>Examples: hack('foodnstuff'); or hack('148.192.0.12');<br><br>" +
"either the IP or hostname of the server you want to hack. A script can hack a server from anywhere. It does not need to be running on the same server to hack that server. " +
"For example, you can create a script that hacks the 'foodnstuff' server and run it on your home computer. <br>" +
"Examples: hack('foodnstuff'); or hack('148.192.0.12');<br><br>" +
"<i>sleep(n)</i><br>Suspends the script for n milliseconds. <br>Example: sleep(5000);<br><br>" +
"<i>grow(hostname/ip)</i><br>Use your hacking skills to increase the amount of money available on a server. The argument passed in " +
"must be a string with either the IP or hostname of the target server. The grow() command takes a flat 2 minutes to execute " +
"and grants 1 hacking exp when complete. <br> Example: grow('foodnstuff');<br><br>" +
"must be a string with either the IP or hostname of the target server. The grow() command requires root access to the target server, but " +
"there is no required hacking level to run the command. The grow() command takes a flat 2 minutes to execute " +
"and grants 1 hacking exp when it completes. <br> Example: grow('foodnstuff');<br><br>" +
"<i>print(x)</i> <br> Prints a value or a variable to the scripts logs (which can be viewed with the 'tail [script]' terminal command )<br><br>" +
"<i>nuke(hostname/ip)</i><br>Run NUKE.exe on the target server. NUKE.exe must exist on your home computer. <br> Example: nuke('foodnstuff'); <br><br>" +
"<i>brutessh(hostname/ip)</i><br>Run BruteSSH.exe on the target server. BruteSSH.exe must exist on your home computer <br> Example: brutessh('foodnstuff');<br><br>" +
"<i>ftpcrack(hostname/ip)</i><br>Run FTPCrack.exe on the target server. FTPCrack.exe must exist on your home computer <br> Example: ftpcrack('foodnstuff');<br><br>" +
"<i>relaysmtp(hostname/ip)</i><br>Run relaySMTP.exe on the target server. relaySMTP.exe must exist on your home computer <br> Example: relaysmtp('foodnstuff');<br><br>" +
"<i>httpworm(hostname/ip)</i><br>Run HTTPWorm.exe on the target server. HTTPWorm.exe must exist on your home computer <br> Example: httpworm('foodnstuff');<br><br>" +
"<i>sqlinject(hostname/ip)</i><br>Run SQLInject.exe on the target server. SQLInject.exe must exist on your home computer <br> Example: sqlinject('foodnstuff');<br><br>" +
"<i>nuke(hostname/ip)</i><br>Run NUKE.exe on the target server. NUKE.exe must exist on your home computer. Does NOT work while offline <br> Example: nuke('foodnstuff'); <br><br>" +
"<i>brutessh(hostname/ip)</i><br>Run BruteSSH.exe on the target server. BruteSSH.exe must exist on your home computer. Does NOT work while offline <br> Example: brutessh('foodnstuff');<br><br>" +
"<i>ftpcrack(hostname/ip)</i><br>Run FTPCrack.exe on the target server. FTPCrack.exe must exist on your home computer. Does NOT work while offline <br> Example: ftpcrack('foodnstuff');<br><br>" +
"<i>relaysmtp(hostname/ip)</i><br>Run relaySMTP.exe on the target server. relaySMTP.exe must exist on your home computer. Does NOT work while offline <br> Example: relaysmtp('foodnstuff');<br><br>" +
"<i>httpworm(hostname/ip)</i><br>Run HTTPWorm.exe on the target server. HTTPWorm.exe must exist on your home computer. Does NOT work while offline <br> Example: httpworm('foodnstuff');<br><br>" +
"<i>sqlinject(hostname/ip)</i><br>Run SQLInject.exe on the target server. SQLInject.exe must exist on your home computer. Does NOT work while offline <br> Example: sqlinject('foodnstuff');<br><br>" +
"<i>run(script)</i> <br> Run a script as a separate process. The argument that is passed in is the name of the script as a string. This function can only " +
"be used to run scripts located on the same server. Returns true if the script is successfully started, and false otherwise. Requires a significant amount " +
"of RAM to run this command.<br>Example: run('hack-foodnstuff.script'); <br> The example above will try and launch the 'hack-foodnstuff.script' script on " +
"of RAM to run this command. Does NOT work while offline <br>Example: run('hack-foodnstuff.script'); <br> The example above will try and launch the 'hack-foodnstuff.script' script on " +
"the current server, if it exists. <br><br>" +
"<i>getHackingLevel() </i><br> Returns the Player's current hacking level <br><br> " +
"<i>getHackingLevel() </i><br> Returns the Player's current hacking level. Does NOT work while offline <br><br> " +
"<i>getServerMoneyAvailable(hostname/ip)</i><br> Returns the amount of money available on a server. The argument passed in must be a string with either the " +
"hostname or IP of the target server. <br> Example: getServerMoneyAvailable('foodnstuff');<br><br>" +
"hostname or IP of the target server. Does NOT work while offline <br> Example: getServerMoneyAvailable('foodnstuff');<br><br>" +
"<i>purchaseHacknetNode()</i><br> Purchases a new Hacknet Node. Returns a string with the name of the new Hacknet Node. If the player cannot afford to purchase " +
"a new hacknet node then the function will return an empty string. Does NOT work offline<br><br>" +
"<i>upgradeHacknetNode(name)</i><br> Upgrades the level of a Hacknet Node. The argument passed in must be a string with the name of the Hacknet Node to upgrade. " +
"If the Hacknet Node is successfully upgraded the function will return true. It will return false otherwise. Does NOT work offline. Example: <br>" +
"var node = purchaseHacknetNode();<br>" +
"if (node != '') {<br>" +
"&nbsp;&nbsp;&nbsp;&nbsp;var i = 0;<br>" +
"&nbsp;&nbsp;&nbsp;&nbsp;while(i < 10) {<br>" +
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (upgradeHacknetNode(node)) {i = i + 1;}<br>" +
"&nbsp;&nbsp;&nbsp;&nbsp;}; <br>" +
"};<br><br>" +
"The example code above will attempt to purchase a new Hacknet Node. If the Hacknet Node is purchased, then it will " +
"continuously try to level it up until it is leveled up 10 times. <br><br>" +
"<u><h1>While loops </h1></u><br>" +
"A while loop is a control flow statement that repeatedly executes code as long as a condition is met. <br><br> " +
"<i>while (<i>[cond]</i>) {<br>&nbsp;&nbsp;&nbsp;&nbsp;<i>[code]</i><br>}</i><br><br>" +
@ -313,7 +336,7 @@ CONSTANTS = {
"different companies which you can work for. By working for a company you can earn money, " +
"train your various labor skills, and unlock powerful passive perks. <br><br> " +
"To apply for a job, visit the company you want to work for through the 'World' menu. The company " +
"page will have options that let you apply to positions in the company. There might be several different" +
"page will have options that let you apply to positions in the company. There might be several different " +
"positions you can apply for, ranging from software engineer to business analyst to security officer. <br><br> " +
"When you apply for a job, you will get the offer if your stats are high enough. Your first position at " +
"a company will be an entry-level position such as 'intern'. Once you get the job, an button will appear on " +
@ -341,7 +364,7 @@ CONSTANTS = {
"information about the Faction and also lets you perform work for the faction. " +
"Working for a Faction is similar to working for a company except that you don't get paid a salary. " +
"You will only earn reputation in your Faction and train your stats. Also, cancelling work early " +
"when working for a Faction does NOT result in reduced experience/reputation earnings. <br>" +
"when working for a Faction does NOT result in reduced experience/reputation earnings. <br><br>" +
"Earning reputation for a Faction unlocks powerful Augmentations. Purchasing and installing these Augmentations will " +
"upgrade your abilities. The Augmentations that are available to unlock vary from faction to faction.",
TutorialAugmentationsText: "Advances in science and medicine have lead to powerful new technologies that allow people to augment themselves " +

@ -78,6 +78,7 @@ function determineCrimeSuccess(crime, moneyGained) {
dialogBoxCreate("ERR: Unrecognized crime type. This is probably a bug please contact the developer");
return;
}
chance *= Player.crime_success_mult;
if (Math.random() <= chance) {
//Success
Player.gainMoney(moneyGained);

@ -1,4 +1,21 @@
//Netburner Faction class
function factionInit() {
$('#faction-donate-input').on('input', function() {
if (Engine.currentPage == Engine.Page.Faction) {
var val = document.getElementById("faction-donate-input").value;
if (isPositiveNumber(val)) {
var numMoneyDonate = Number(val);
document.getElementById("faction-donate-rep-gain").innerHTML =
"This donation will result in " + formatNumber(numMoneyDonate/1000, 3) + " reputation gain";
} else {
document.getElementById("faction-donate-rep-gain").innerHTML =
"This donation will result in 0 reputation gain";
}
}
});
}
document.addEventListener("DOMContentLoaded", factionInit, false);
function Faction(name) {
this.name = name;
this.augmentations = []; //Name of augmentation only
@ -284,7 +301,6 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
}
}
//BitRunners
var bitrunnersFac = Factions["BitRunners"];
var homeComp = Player.getHomeComputer();
@ -296,15 +312,15 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
//The Black Hand
var theblackhandFac = Factions["The Black Hand"];
if (theblackhandFac.isBanned == false && theblackhandFac.isMember == false &&
this.hacking_skill >= 400 && this.strength >= 300 && this.defense >= 300 &&
this.agility >= 300 && this.dexterity >= 300 && homeComp.maxRam >= 16) {
this.hacking_skill >= 400 && this.strength >= 200 && this.defense >= 200 &&
this.agility >= 200 && this.dexterity >= 200 && homeComp.maxRam >= 16) {
invitedFactions.push(theblackhandFac);
}
//NiteSec
var nitesecFac = Factions["NiteSec"];
if (nitesecFac.isBanned == false && nitesecFac.isMember == false &&
this.hacking_skill >= 500 && homeComp.maxRam >= 32) {
this.hacking_skill >= 200 && homeComp.maxRam >= 8) {
invitedFactions.push(nitesecFac);
}
@ -358,7 +374,7 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
this.numPeopleKilledTotal >= 100 && this.karma <= -50 && this.companyName != Locations.Sector12CIA &&
this.companyName != Locations.Sector12NSA) {
invitedFactions.push(speakersforthedeadFac);
}
}
//The Dark Army
var thedarkarmyFac = Factions["The Dark Army"];
@ -404,7 +420,7 @@ PlayerObject.prototype.checkForFactionInvitations = function() {
var slumsnakesFac = Factions["Slum Snakes"];
if (slumsnakesFac.isBanned == false && slumsnakesFac.isMember == false &&
this.strength >= 30 && this.defense >= 30 && this.dexterity >= 30 &&
this.agility >= 30 && this.karma <= -15 && this.money >= 1000000) {
this.agility >= 30 && this.karma <= -10 && this.money >= 1000000) {
invitedFactions.push(slumsnakesFac);
}
@ -554,13 +570,14 @@ displayFactionContent = function(factionName) {
var hackDiv = document.getElementById("faction-hack-div");
var fieldWorkDiv = document.getElementById("faction-fieldwork-div");
var securityWorkDiv = document.getElementById("faction-securitywork-div");
var donateDiv = document.getElementById("faction-donate-div");
//Set new event listener for all of the work buttons
//The old buttons need to be replaced to clear the old event listeners
var newHackButton = clearEventListeners("faction-hack-button");
var newFieldWorkButton = clearEventListeners("faction-fieldwork-button");
var newSecurityWorkButton = clearEventListeners("faction-securitywork-button");
var newDonateWorkButton = clearEventListeners("faction-donate-button");
newHackButton.addEventListener("click", function() {
Player.startFactionHackWork(faction);
@ -577,6 +594,25 @@ displayFactionContent = function(factionName) {
return false;
});
newDonateWorkButton.addEventListener("click", function() {
var donateAmountVal = document.getElementById("faction-donate-input").value;
if (isPositiveNumber(donateAmountVal)) {
var numMoneyDonate = Number(donateAmountVal);
if (Player.money < numMoneyDonate) {
dialogBoxCreate("You cannot afford to donate this much money!");
return;
}
Player.loseMoney(numMoneyDonate);
var repGain = numMoneyDonate / 1000;
faction.playerReputation += repGain;
dialogBoxCreate("You just donated $" + formatNumber(numMoneyDonate, 2) + " to " +
faction.name + " to gain " + formatNumber(repGain, 3) + " reputation");
displayFactionContent(factionName);
} else {
dialogBoxCreate("Invalid amount entered!");
}
return false;
});
//Set new event listener for the purchase augmentation buttons
//The old button needs to be replaced to clear the old event listeners
@ -597,6 +633,7 @@ displayFactionContent = function(factionName) {
});
if (faction.isMember) {
donateDiv.style.display = "inline";
switch(faction.name) {
case "Illuminati":
hackDiv.style.display = "inline";
@ -786,12 +823,13 @@ displayFactionAugmentations = function(factionName) {
pElem.innerHTML = "LOCKED (Requires " + formatNumber(req, 4) + " faction reputation)";
pElem.style.color = "red";
}
aElem.style.display = "inline-block";
pElem.style.display = "inline-block";
aElem.style.display = "inline";
pElem.style.display = "inline";
aElem.innerHTML = aug.name;
if (aug.name == AugmentationNames.NeuroFluxGovernor) {
aElem.innerHTML += " - Level " + (aug.level + 1);
}
span.style.display = "inline-block"
aElem.innerHTML += '<span class="tooltiptext">' + aug.info + " </span>";

@ -125,9 +125,9 @@ FactionInfo = {
TianDiHuiInfo: "Obey Heaven and Work Righteousness",
CyberSecInfo: "The Internet is the first thing that humanity has built that humanity doesnt understand,\n" +
"the largest experiment in anarchy that we have ever had. And as the world becomes increasingly \n" +
"dominated by the internet, society approaches the brink of total chaos. \n\n" +
CyberSecInfo: "The Internet is the first thing that humanity has built that humanity doesnt understand, " +
"the largest experiment in anarchy that we have ever had. And as the world becomes increasingly " +
"dominated by the internet, society approaches the brink of total chaos. " +
"We serve only to protect society, to protect humanity, to protect the world from its imminent collapse.",
}

@ -1,4 +1,36 @@
/* HacknetNode.js */
function hacknetNodesInit() {
var mult1x = document.getElementById("hacknet-nodes-1x-multiplier");
mult1x.addEventListener("click", function() {
hacknetNodePurchaseMultiplier = 1;
updateHacknetNodesMultiplierButtons();
updateHacknetNodesContent();
return false;
});
var mult5x = document.getElementById("hacknet-nodes-5x-multiplier");
mult5x.addEventListener("click", function() {
hacknetNodePurchaseMultiplier = 5;
updateHacknetNodesMultiplierButtons();
updateHacknetNodesContent();
return false;
});
var mult10x = document.getElementById("hacknet-nodes-10x-multiplier");
mult10x.addEventListener("click", function() {
hacknetNodePurchaseMultiplier = 10;
updateHacknetNodesMultiplierButtons();
updateHacknetNodesContent();
return false;
});
var multMax = document.getElementById("hacknet-nodes-max-multiplier");
multMax.addEventListener("click", function() {
hacknetNodePurchaseMultiplier = 0;
updateHacknetNodesMultiplierButtons();
updateHacknetNodesContent();
return false;
});
}
document.addEventListener("DOMContentLoaded", hacknetNodesInit, false);
function HacknetNode(name) {
this.level = 1;
this.ram = 1; //GB
@ -16,9 +48,8 @@ HacknetNode.prototype.updateMoneyGainRate = function() {
//How much extra $/s is gained per level
var gainPerLevel = CONSTANTS.HacknetNodeMoneyGainPerLevel;
//Each CPU core doubles the speed. Every 1GB of ram adds 15% increase
this.moneyGainRatePerSecond = (this.level * gainPerLevel) *
Math.pow(1.07, this.ram-1) *
Math.pow(1.05, this.ram-1) *
((this.numCores + 1) / 2) * Player.hacknet_node_money_mult;
if (isNaN(this.moneyGainRatePerSecond)) {
this.moneyGainRatePerSecond = 0;
@ -28,19 +59,28 @@ HacknetNode.prototype.updateMoneyGainRate = function() {
updateTotalHacknetProduction();
}
HacknetNode.prototype.calculateLevelUpgradeCost = function() {
//Upgrade cost = Base cost * multiplier ^ level
HacknetNode.prototype.calculateLevelUpgradeCost = function(levels=1) {
if (levels < 1) {return 0;}
var mult = CONSTANTS.HacknetNodeUpgradeLevelMult;
return CONSTANTS.BaseCostForHacknetNode / 2 * Math.pow(mult, this.level) * Player.hacknet_node_level_cost_mult;
var totalMultiplier = 0; //Summed
var currLevel = this.level;
for (var i = 0; i < levels; ++i) {
totalMultiplier += Math.pow(mult, currLevel);
++currLevel;
}
return CONSTANTS.BaseCostForHacknetNode / 2 * totalMultiplier * Player.hacknet_node_level_cost_mult;
}
HacknetNode.prototype.purchaseLevelUpgrade = function() {
var cost = this.calculateLevelUpgradeCost();
HacknetNode.prototype.purchaseLevelUpgrade = function(levels=1) {
var cost = this.calculateLevelUpgradeCost(levels);
if (isNaN(cost)) {throw new Error("Cost is NaN"); return;}
if (cost > Player.money) {return;}
Player.loseMoney(cost);
if (this.level >= CONSTANTS.HacknetNodeMaxLevel) {return;}
++this.level;
if (this.level + levels >= CONSTANTS.HacknetNodeMaxLevel) {
this.level = CONSTANTS.HacknetNodeMaxLevel;
return;
}
this.level += levels;
this.updateMoneyGainRate();
}
@ -48,11 +88,10 @@ HacknetNode.prototype.calculateRamUpgradeCost = function() {
var numUpgrades = Math.log2(this.ram);
//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 = this.ram * CONSTANTS.BaseCostFor1GBOfRamHacknetNode;
//Base cost of RAM is 50k per 1GB, increased by some multiplier for each time RAM is upgraded
var baseCost = this.ram * CONSTANTS.BaseCostFor1GBOfRamHacknetNode;
var mult = Math.pow(CONSTANTS.HacknetNodeUpgradeRamMult, numUpgrades);
return cost * mult * Player.hacknet_node_ram_cost_mult;
return baseCost * mult * Player.hacknet_node_ram_cost_mult;
}
HacknetNode.prototype.purchaseRamUpgrade = function() {
@ -141,6 +180,55 @@ getCostOfNextHacknetNode = function() {
return CONSTANTS.BaseCostForHacknetNode * Math.pow(mult, numOwned) * Player.hacknet_node_purchase_cost_mult;
}
var hacknetNodePurchaseMultiplier = 1;
updateHacknetNodesMultiplierButtons = function() {
var mult1x = document.getElementById("hacknet-nodes-1x-multiplier");
var mult5x = document.getElementById("hacknet-nodes-5x-multiplier");
var mult10x = document.getElementById("hacknet-nodes-10x-multiplier");
var multMax = document.getElementById("hacknet-nodes-max-multiplier");
mult1x.setAttribute("class", "a-link-button");
mult5x.setAttribute("class", "a-link-button");
mult10x.setAttribute("class", "a-link-button");
multMax.setAttribute("class", "a-link-button");
if (Player.hacknetNodes.length == 0) {
mult1x.setAttribute("class", "a-link-button-inactive");
mult5x.setAttribute("class", "a-link-button-inactive");
mult10x.setAttribute("class", "a-link-button-inactive");
multMax.setAttribute("class", "a-link-button-inactive");
} else if (hacknetNodePurchaseMultiplier == 1) {
mult1x.setAttribute("class", "a-link-button-inactive");
} else if (hacknetNodePurchaseMultiplier == 5) {
mult5x.setAttribute("class", "a-link-button-inactive");
} else if (hacknetNodePurchaseMultiplier == 10) {
mult10x.setAttribute("class", "a-link-button-inactive");
} else {
multMax.setAttribute("class", "a-link-button-inactive");
}
}
//Calculate the maximum number of times the Player can afford to upgrade
//a Hacknet Node's level"
getMaxNumberLevelUpgrades = function(nodeObj) {
if (nodeObj.calculateLevelUpgradeCost(1) > Player.money) {return 0;}
var min = 1;
var max = 199;
while (min <= max) {
var curr = (min + max) / 2 | 0;
if (curr != 200 &&
nodeObj.calculateLevelUpgradeCost(curr) < Player.money &&
nodeObj.calculateLevelUpgradeCost(curr+1) > Player.money) {
return curr;
} else if (nodeObj.calculateLevelUpgradeCost(curr) > Player.money) {
max = curr - 1;
} else if (nodeObj.calculateLevelUpgradeCost(curr) < Player.money) {
min = curr + 1;
} else {
return curr;
}
}
}
//Creates Hacknet Node DOM elements when the page is opened
displayHacknetNodesContent = function() {
//Update Hacknet Nodes button
@ -151,6 +239,9 @@ displayHacknetNodesContent = function() {
return false;
});
//Handle Purchase multiplier buttons
updateHacknetNodesMultiplierButtons();
//Remove all old hacknet Node DOM elements
var hacknetNodesList = document.getElementById("hacknet-nodes-list");
while (hacknetNodesList.firstChild) {
@ -214,7 +305,11 @@ createHacknetNodeDomElement = function(nodeObj) {
upgradeLevelButton.id = "hacknet-node-upgrade-level-" + nodeName;
upgradeLevelButton.setAttribute("class", "a-link-button-inactive");
upgradeLevelButton.addEventListener("click", function() {
nodeObj.purchaseLevelUpgrade();
var numUpgrades = hacknetNodePurchaseMultiplier;
if (hacknetNodePurchaseMultiplier == 0) {
numUpgrades = getMaxNumberLevelUpgrades(nodeObj);
}
nodeObj.purchaseLevelUpgrade(numUpgrades);
updateHacknetNodesContent();
return false;
});
@ -268,8 +363,17 @@ updateHacknetNodeDomElement = function(nodeObj) {
upgradeLevelButton.innerHTML = "MAX LEVEL";
upgradeLevelButton.setAttribute("class", "a-link-button-inactive");
} else {
var upgradeLevelCost = nodeObj.calculateLevelUpgradeCost();
upgradeLevelButton.innerHTML = "Upgrade Hacknet Node Level - $" + formatNumber(upgradeLevelCost, 2);
var multiplier = 0;
if (hacknetNodePurchaseMultiplier == 0) {
//Max
multiplier = getMaxNumberLevelUpgrades(nodeObj);
} else {
multiplier = hacknetNodePurchaseMultiplier;
}
var upgradeLevelCost = nodeObj.calculateLevelUpgradeCost(multiplier);
upgradeLevelButton.innerHTML = "Upgrade Hacknet Node Level x" + multiplier +
" - $" + formatNumber(upgradeLevelCost, 2);
if (upgradeLevelCost > Player.money ) {
upgradeLevelButton.setAttribute("class", "a-link-button-inactive");
} else {
@ -311,9 +415,11 @@ updateHacknetNodeDomElement = function(nodeObj) {
}
processAllHacknetNodeEarnings = function(numCycles) {
var total = 0;
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
processSingleHacknetNodeEarnings(numCycles, Player.hacknetNodes[i]);
total += processSingleHacknetNodeEarnings(numCycles, Player.hacknetNodes[i]);
}
return total;
}
processSingleHacknetNodeEarnings = function(numCycles, nodeObj) {
@ -324,4 +430,14 @@ processSingleHacknetNodeEarnings = function(numCycles, nodeObj) {
nodeObj.totalMoneyGenerated += totalEarnings;
nodeObj.onlineTimeSeconds += (numCycles * (Engine._idleSpeed / 1000));
Player.gainMoney(totalEarnings);
return totalEarnings;
}
getHacknetNode = function(name) {
for (var i = 0; i < Player.hacknetNodes.length; ++i) {
if (Player.hacknetNodes[i].name == name) {
return player.hacknetNodes[i];
}
}
return null;
}

@ -12,6 +12,7 @@ iTutorialSteps = {
TerminalAnalyze: "Use the analyze command to display details about this server",
TerminalNuke: "Use the NUKE Program to gain root access to a server",
TerminalManualHack: "Use the hack command to manually hack a server",
TerminalHackingMechanics: "Briefly explain hacking mechanics",
TerminalCreateScript: "Create a script using nano",
TerminalTypeScript: "This occurs in the Script Editor page...type the script then save and close",
TerminalFree: "Use the free command to check RAM",
@ -155,7 +156,7 @@ function iTutorialEvaluateStep() {
//next step triggered by terminal command
break;
case iTutorialSteps.TerminalConnect:
iTutorialSetText("The 'scan/netstat' command shows all available network connections. In other words, " +
iTutorialSetText("The 'scan' command shows all available network connections. In other words, " +
"it displays a list of all servers that can be connected to from your " +
"current machine. A server is identified by either its IP or its hostname. <br><br> " +
"To connect to a machine, use the 'connect [ip/hostname]' command. You can type in " +
@ -187,24 +188,40 @@ function iTutorialEvaluateStep() {
"Try doing that now. ");
//next step triggered by terminal command
break;
case iTutorialSteps.TerminalCreateScript:
case iTutorialSteps.TerminalHackingMechanics:
iTutorialSetText("You are now attempting to hack the server. Note that performing a hack takes time and " +
"only has a certain percentage chance " +
"of success. This time and percentage is determined by a variety of factors, including " +
"of success. This time and success chance is determined by a variety of factors, including " +
"your hacking skill and the server's security level. <br><br>" +
"Hacking is the core mechanic of the game and is necessary for progressing. However, " +
"If your attempt to hack the server is successful, you will steal a certain percentage " +
"of the server's total money. This percentage is affected by your hacking skill and " +
"the server's security level. <br><br> The amount of money on a server is not limitless. So, if " +
"you constantly hack a server and deplete its money, then you will encounter " +
"diminishing returns in your hacking.<br>");
var next = clearEventListeners("interactive-tutorial-next");
next.style.display = "inline-block";
next.addEventListener("click", function() {
iTutorialNextStep();
return false;
});
break;
case iTutorialSteps.TerminalCreateScript:
iTutorialSetText("Hacking is the core mechanic of the game and is necessary for progressing. However, " +
"you don't want to be hacking manually the entire time. You can automate your hacking " +
"by writing scripts! <br><br>To create a new script or edit an existing one, you can use the 'nano' " +
"command. Scripts must end with the '.script' extension. Let's make a script now by " +
"entering 'nano foodnstuff.script' after the hack command finishes running (Sidenote: Pressing ctrl + c" +
" will end a command like hack early)");
var next = clearEventListeners("interactive-tutorial-next");
next.style.display = "none";
//next step triggered by terminal command
break;
case iTutorialSteps.TerminalTypeScript:
iTutorialSetText("This is the script editor. You can use it to program your scripts. Scripts are " +
"written in the Netscript language, a very simple programming language created for " +
"this game. There are details about the Netscript language in the documentation, which " +
"can be accessed in the 'Tutorial' tab on the main navigation menu. For now, just copy " +
"written in the Netscript language, a programming language created for " +
"this game. <strong style='background-color:#444;'>There are details about the Netscript language in the documentation, which " +
"can be accessed in the 'Tutorial' tab on the main navigation menu. I highly suggest you check " +
"it out after this tutorial. </strong> For now, just copy " +
"and paste the following code into the script editor: <br><br>" +
"while(true) { <br>" +
"hack('foodnstuff'); <br>" +
@ -230,7 +247,7 @@ function iTutorialEvaluateStep() {
iTutorialSetText("Your script is now running! The script might take a few seconds to 'fully start up'. " +
"Your scripts will continuously run in the background and will automatically stop if " +
"the code ever completes (the 'foodnstuff.script' will never complete because it " +
"runs an infinite loop). <br><br>These scripts will passively earn you income and hacking experience. " +
"runs an infinite loop). <br><br>These scripts can passively earn you income and hacking experience. " +
"Your scripts will also earn money and experience while you are offline, although at a " +
"much slower rate. <br><br> " +
"Let's check out some statistics of our active, running scripts by clicking the " +
@ -300,7 +317,7 @@ function iTutorialEvaluateStep() {
//Next step triggered by purchaseHacknet() (HacknetNode.js)
break;
case iTutorialSteps.HacknetNodesGoToWorldPage:
iTutorialSetText("You just purchase a Hacknet Node! This Hacknet Node will passively " +
iTutorialSetText("You just purchased a Hacknet Node! This Hacknet Node will passively " +
"earn you money over time, both online and offline. When you get enough " +
" money, you can upgrade " +
"your newly-purchased Hacknet Node below. <br><br>" +
@ -405,6 +422,10 @@ function iTutorialNextStep() {
iTutorialEvaluateStep();
break;
case iTutorialSteps.TerminalManualHack:
currITutorialStep = iTutorialSteps.TerminalHackingMechanics;
iTutorialEvaluateStep();
break;
case iTutorialSteps.TerminalHackingMechanics:
currITutorialStep = iTutorialSteps.TerminalCreateScript;
iTutorialEvaluateStep();
break;
@ -523,6 +544,10 @@ function iTutorialPrevStep() {
currITutorialStep = iTutorialSteps.TerminalNuke;
iTutorialEvaluateStep();
break;
case iTutorialSteps.TerminalHackingMechanics:
currITutorialStep = iTutorialSteps.TerminalManualHack;
iTutorialEvaluateStep();
break;
case iTutorialSteps.TerminalCreateScript:
currITutorialStep = iTutorialSteps.TerminalManualHack;
iTutorialEvaluateStep();

@ -220,6 +220,19 @@ displayLocationContent = function() {
purchaseTor.style.display = "none";
purchaseHomeRam.style.display = "none";
purchase1gb.innerHTML = "Purchase 1GB Server - $" + formatNumber(1*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase2gb.innerHTML = "Purchase 2GB Server - $" + formatNumber(2*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase4gb.innerHTML = "Purchase 4GB Server - $" + formatNumber(4*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase8gb.innerHTML = "Purchase 8GB Server - $" + formatNumber(8*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase16gb.innerHTML = "Purchase 16GB Server - $" + formatNumber(16*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase32gb.innerHTML = "Purchase 32GB Server - $" + formatNumber(32*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase64gb.innerHTML = "Purchase 64GB Server - $" + formatNumber(64*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase128gb.innerHTML = "Purchase 128GB Server - $" + formatNumber(128*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase256gb.innerHTML = "Purchase 256GB Server - $" + formatNumber(256*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase512gb.innerHTML = "Purchase 512GB Server - $" + formatNumber(512*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchase1tb.innerHTML = "Purchase 1TB Server - $" + formatNumber(1024*CONSTANTS.BaseCostFor1GBOfRamServer, 2);
purchaseTor.innerHTML = "Purchase TOR Router - $" + formatNumber(CONSTANTS.TorRouterCost, 2);
travelAgencyText.style.display = "none";
travelToAevum.style.display = "none";
travelToChongqing.style.display = "none";

@ -690,6 +690,7 @@ function evaluate(exp, workerScript) {
} else if (exp.func.value == "getServerMoneyAvailable") {
if (exp.args.length != 1) {
reject("|"+workerScript.serverIp+"|"+workerScript.name+"|getServerMoneyAvailable() call has incorrect number of arguments. Takes 1 arguments");
return;
}
var ipPromise = evaluate(exp.args[0], workerScript);
ipPromise.then(function(ip) {
@ -706,6 +707,69 @@ function evaluate(exp, workerScript) {
}, function(e) {
reject(e);
});
} else if (exp.func.value == "purchaseHacknetNode") {
if (exp.args.length != 0) {
reject("|"+workerScript.serverIp+"|"+workerScript.name+"|purchaseHacknetNode() call has incorrect number of arguments. Takes 0 arguments");
return;
}
setTimeout(function() {
var cost = getCostOfNextHacknetNode();
if (isNaN(cost)) {
reject("|"+workerScript.serverIp+"|"+workerScript.name+"|Could not calculate cost in purchaseHacknetNode(). This is a bug please report to game dev");
return;
}
if (cost > Player.money) {
workerScript.scriptRef.log("Could not afford to purchase new Hacknet Node");
resolve("");
}
//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);
workerScript.scriptRef.log("Purchased new Hacknet Node with name: " + name);
resolve(name);
}, CONSTANTS.CodeInstructionRunTime);
} else if (exp.func.value == "upgradeHacknetNode") {
if (exp.args.length != 1) {
reject("|"+workerScript.serverIp+"|"+workerScript.name+"|upgradeHacknetNode() call has incorrect number of arguments. Takes 1 argument");
return;
}
var namePromise = evaluate(exp.args[0], workerScript);
namePromise.then(function(name) {
var node = getHacknetNode(name);
if (node == null) {
reject("|"+workerScript.serverIp+"|"+workerScript.name+"|Invalid Hacknet Node name passed into upgradeHacknetNode()");
return;
}
var cost = node.calculateLevelUpgradeCost(1);
if (isNaN(cost)) {
reject("|"+workerScript.serverIp+"|"+workerScript.name+"|Could not calculate cost in upgradeHacknetNode(). This is a bug please report to game dev");
return;
}
if (cost > Player.money) {
workerScript.scriptRef.log("Could not afford to upgrade Hacknet Node: " + name);
resolve(false);
return;
}
if (node.level >= CONSTANTS.HacknetNodeMaxLevel) {
workerScript.scriptRef.log("Hacknet Node " + name + " already at max level");
node.level = CONSTANTS.HacknetNodeMaxLevel;
resolve(false);
return;
}
Player.loseMoney(cost);
node.level += 1;
node.updateMoneyGainRate();
workerScript.scriptRef.log("Hacknet node " + name + " upgraded to level " + node.level + "!");
resolve(true);
}, function(e) {
reject(e);
});
}
}, CONSTANTS.CodeInstructionRunTime);
});

@ -17,6 +17,7 @@ function PlayerObject() {
this.hacking_chance_mult = 1; //Increase through ascensions/augmentations
this.hacking_speed_mult = 1; //Decrease through ascensions/augmentations
this.hacking_money_mult = 1; //Increase through ascensions/augmentations. Can't go above 1
this.hacking_grow_mult = 1;
//Note: "Lifetime" refers to current ascension, "total" refers to the entire game history
//Accumulative stats and skills
@ -116,6 +117,7 @@ function PlayerObject() {
this.numTimesHeistLifetime = 0;
this.crime_money_mult = 1;
this.crime_success_mult = 1;
//Flag to let the engine know the player is starting an action
// Current actions: hack, analyze
@ -209,7 +211,7 @@ PlayerObject.prototype.updateSkillLevels = function() {
//Calculates the chance of hacking a server
//The formula is:
// (hacking_chance_multiplier * hacking_skill - requiredLevel) 100 - difficulty
// (2 * hacking_chance_multiplier * hacking_skill - requiredLevel) 100 - difficulty
// ----------------------------------------------------------- * -----------------
// (hacking_chance_multiplier * hacking_skill) 100
PlayerObject.prototype.calculateHackingChance = function() {
@ -217,8 +219,9 @@ PlayerObject.prototype.calculateHackingChance = function() {
var skillMult = (2 * this.hacking_chance_mult * this.hacking_skill);
var skillChance = (skillMult - this.getCurrentServer().requiredHackingSkill) / skillMult;
var chance = skillChance * difficultyMult;
if (chance > 1) {return 1;}
if (chance < 0) {return 0;}
else {return chance;}
return chance;
}
//Calculate the time it takes to hack a server in seconds. Returns the time
@ -360,6 +363,8 @@ PlayerObject.prototype.resetWorkStatus = function() {
this.currentWorkFactionDescription = "";
this.createProgramName = "";
this.className = "";
document.getElementById("work-in-progress-text").innerHTML = "";
}
PlayerObject.prototype.gainWorkExp = function(divMult = 1) {
@ -825,12 +830,17 @@ PlayerObject.prototype.getFactionFieldWorkRepGain = function() {
}
/* Creating a Program */
PlayerObject.prototype.startCreateProgramWork = function(programName, time) {
PlayerObject.prototype.startCreateProgramWork = function(programName, time, reqLevel) {
this.resetWorkStatus();
this.isWorking = true;
this.workType = CONSTANTS.WorkTypeCreateProgram;
this.timeNeededToCompleteWork = time;
//Time needed to complete work affected by hacking skill (linearly based on
//ratio of (your skill - required level) to MAX skill)
var timeMultiplier = (CONSTANTS.MaxSkillLevel - (this.hacking_skill - reqLevel)) / CONSTANTS.MaxSkillLevel;
if (timeMultiplier > 1) {timeMultiplier = 1;}
if (timeMultiplier < 0.01) {timeMultiplier = 0.01;}
this.timeNeededToCompleteWork = timeMultiplier * time;
this.createProgramName = programName;

@ -127,6 +127,16 @@ function prestigeAugmentation() {
homeComp.isOnline = true;
homeComp.ramUsed = 0;
homeComp.programs.push(Programs.NukeProgram);
if (augmentationExists(AugmentationNames.Neurolink) &&
Augmentations[AugmentationNames.Neurolink].owned) {
homeComp.programs.push(Programs.FTPCrackProgram);
homeComp.programs.push(Programs.RelaySMTPProgram);
}
if (augmentationExists(AugmentationNames.CashRoot) &&
Augmentations[AugmentationNames.CashRoot].owned) {
Player.money = 1000000;
homeComp.programs.push(Programs.BruteSSHProgram);
}
Player.currentServer = homeComp.ip;
Player.homeComputer = homeComp.ip;
AddToAllServers(homeComp);

@ -2,8 +2,8 @@
* Script object
*/
//Initialize the 'save and close' button on script editor page
function scriptEditorInit() {
//Initialize save and close button
var closeButton = document.getElementById("script-editor-save-and-close-button");
closeButton.addEventListener("click", function() {
@ -11,20 +11,26 @@ function scriptEditorInit() {
return false;
});
//Allow tabs (four spaces) in all textareas
var textareas = document.getElementsByTagName('textarea');
var count = textareas.length;
for(var i=0;i<count;i++){
textareas[i].onkeydown = function(e){
if(e.keyCode==9 || e.which==9){
e.preventDefault();
var s = this.selectionStart;
this.value = this.value.substring(0,this.selectionStart) + "\t" + this.value.substring(this.selectionEnd);
this.selectionEnd = s+1;
var start = this.selectionStart;
var end = this.selectionEnd;
//Set textarea value to: text before caret + four spaces + text after caret
spaces = " ";
this.value = this.value.substring(0, start) + spaces + this.value.substring(end);
//Put caret at after the four spaces
this.selectionStart = this.selectionEnd = start + spaces.length;
}
}
}
};
document.addEventListener("DOMContentLoaded", scriptEditorInit, false);
//Define key commands in script editor (ctrl o to save + close, etc.)
@ -178,6 +184,8 @@ Script.prototype.updateRamUsage = function() {
var getHackingLevelCount = numOccurrences(codeCopy, "getHackingLevel(");
var getServerMoneyAvailableCount = numOccurrences(codeCopy, "getServerMoneyAvailable(");
var numOperators = numNetscriptOperators(codeCopy);
var purchaseHacknetCount = numOccurrences(codeCopy, "purchaseHacknetNode(");
var upgradeHacknetCount = numOccurrences(codeCopy, "upgradeHacknetNode(");
this.ramUsage = baseRam +
((whileCount * CONSTANTS.ScriptWhileRamCost) +
@ -194,7 +202,9 @@ Script.prototype.updateRamUsage = function() {
(runCount * CONSTANTS.ScriptRunRamCost) +
(getHackingLevelCount * CONSTANTS.ScriptGetHackingLevelRamCost) +
(getServerMoneyAvailableCount * CONSTANTS.ScriptGetServerMoneyRamCost) +
(numOperators * CONSTANTS.ScriptOperatorRamCost));
(numOperators * CONSTANTS.ScriptOperatorRamCost) +
(purchaseHacknetCount * CONSTANTS.ScriptPurchaseHacknetRamCost) +
(upgradeHacknetCount * CONSTANTS.ScriptUpgradeHacknetRamCost));
console.log("ram usage: " + this.ramUsage);
if (isNaN(this.ramUsage)) {
dialogBoxCreate("ERROR in calculating ram usage. This is a bug, please report to game develoepr");
@ -233,6 +243,7 @@ Reviver.constructors.Script = Script;
//into worker scripts so that they will start running
loadAllRunningScripts = function() {
var count = 0;
var total = 0;
for (var property in AllServers) {
if (AllServers.hasOwnProperty(property)) {
var server = AllServers[property];
@ -248,10 +259,11 @@ loadAllRunningScripts = function() {
addWorkerScript(script, server);
//Offline production
scriptCalculateOfflineProduction(script);
total += scriptCalculateOfflineProduction(script);
}
}
}
return total;
console.log("Loaded " + count.toString() + " running scripts");
}
@ -299,7 +311,7 @@ scriptCalculateOfflineProduction = function(script) {
script.offlineMoneyMade += totalOfflineProduction;
script.offlineRunningTime += timePassed;
script.offlineExpGained += expGain;
return totalOfflineProduction;
//DEBUG
var serverName = AllServers[script.server].hostname;
console.log(script.filename + " from server " + serverName + " generated $" + totalOfflineProduction + " TOTAL while offline");

@ -45,7 +45,7 @@ $(document).keydown(function(event) {
//Enter
if (event.keyCode == 13) {
event.preventDefault();
event.preventDefault(); //Prevent newline from being entered in Script Editor
var command = $('input[class=terminal-input]').val();
if (command.length > 0) {
post("> " + command);

@ -222,6 +222,18 @@ var Engine = {
Engine.volhavenLocationsList.style.display = "none";
},
displayCharacterOverviewInfo: function() {
document.getElementById("character-overview-text").innerHTML =
("Money: $" + formatNumber(Player.money, 2) + "<br>" +
"Hack: " + (Player.hacking_skill).toLocaleString() + "<br>" +
"Str: " + (Player.strength).toLocaleString() + "<br>" +
"Def: " + (Player.defense).toLocaleString() + "<br>" +
"Dex: " + (Player.dexterity).toLocaleString() + "<br>" +
"Agi: " + (Player.agility).toLocaleString() + "<br>" +
"Cha: " + (Player.charisma).toLocaleString()
).replace( / /g, "&nbsp;" );
},
/* Display character info */
displayCharacterInfo: function() {
var companyPosition = "";
@ -276,7 +288,6 @@ var Engine = {
'Servers owned: ' + Player.purchasedServers.length + '<br>' +
'Hacknet Nodes owned: ' + Player.hacknetNodes.length + '<br>' +
'Time played: ' + convertTimeMsToTimeElapsedString(Player.totalPlaytime) + '<br><br><br>').replace( / /g, "&nbsp;" );
},
/* Display locations in the world*/
@ -609,6 +620,7 @@ var Engine = {
}
if (Engine.Counters.updateDisplays <= 0) {
Engine.displayCharacterOverviewInfo();
if (Engine.currentPage == Engine.Page.ActiveScripts) {
Engine.updateActiveScriptsItems();
} else if (Engine.currentPage == Engine.Page.CharacterInfo) {
@ -724,7 +736,7 @@ var Engine = {
/* Process offline progress */
processServerGrowth(numCyclesOffline); //Should be done before offline production for scripts
loadAllRunningScripts(); //This also takes care of offline production for those scripts
var offlineProductionFromScripts = loadAllRunningScripts(); //This also takes care of offline production for those scripts
if (Player.isWorking) {
console.log("work() called in load() for " + numCyclesOffline * Engine._idleSpeed + " milliseconds");
if (Player.workType == CONSTANTS.WorkTypeFaction) {
@ -743,7 +755,7 @@ var Engine = {
}
//Hacknet Nodes offline progress
processAllHacknetNodeEarnings(numCyclesOffline);
var offlineProductionFromHacknetNodes = processAllHacknetNodeEarnings(numCyclesOffline);
//Passive faction rep gain offline
processPassiveFactionRepGain(numCyclesOffline);
@ -755,6 +767,9 @@ var Engine = {
Player.lastUpdate = Engine._lastUpdate;
Engine.start(); //Run main game loop and Scripts loop
dialogBoxCreate("While you were offline, your scripts generated $" +
formatNumber(offlineProductionFromScripts, 2) + " and your Hacknet Nodes generated $" +
formatNumber(offlineProductionFromHacknetNodes, 2));
} else {
//No save found, start new game
console.log("Initializing new game");
@ -991,22 +1006,22 @@ var Engine = {
var httpWormALink = document.getElementById("create-program-httpworm");
var sqlInjectALink = document.getElementById("create-program-sqlinject");
portHackALink.addEventListener("click", function() {
createProgram(Programs.PortHackProgram, CONSTANTS.MillisecondsPerQuarterHour);
Player.startCreateProgramWork(Programs.NukeProgram, CONSTANTS.MillisecondsPerQuarterHour, 1);
});
bruteSshALink.addEventListener("click", function() {
Player.startCreateProgramWork(Programs.BruteSSHProgram, CONSTANTS.MillisecondsPerQuarterHour);
Player.startCreateProgramWork(Programs.BruteSSHProgram, CONSTANTS.MillisecondsPerQuarterHour, 50);
});
ftpCrackALink.addEventListener("click", function() {
Player.startCreateProgramWork(Programs.FTPCrackProgram, CONSTANTS.MillisecondsPerHalfHour);
Player.startCreateProgramWork(Programs.FTPCrackProgram, CONSTANTS.MillisecondsPerHalfHour, 100);
});
relaySmtpALink.addEventListener("click", function() {
Player.startCreateProgramWork(Programs.RelaySMTPProgram. CONSTANTS.MillisecondsPer2Hours);
Player.startCreateProgramWork(Programs.RelaySMTPProgram. CONSTANTS.MillisecondsPer2Hours, 250);
});
httpWormALink.addEventListener("click", function() {
Player.startCreateProgramWork(Programs.HTTPWormProgram, CONSTANTS.MillisecondsPer4Hours);
Player.startCreateProgramWork(Programs.HTTPWormProgram, CONSTANTS.MillisecondsPer4Hours, 500);
});
sqlInjectALink.addEventListener("click", function() {
Player.startCreateProgramWork(Programs.SQLInjectProgram, CONSTANTS.MillisecondsPer8Hours);
Player.startCreateProgramWork(Programs.SQLInjectProgram, CONSTANTS.MillisecondsPer8Hours, 750);
});
//Message at the top of terminal
@ -1034,6 +1049,18 @@ var Engine = {
Engine.loadWorkInProgressContent();
}
//character overview screen
document.getElementById("character-overview-container").style.display = "block";
//Remove classes from links (they might be set from tutorial)
document.getElementById("terminal-menu-link").removeAttribute("class");
document.getElementById("character-menu-link").removeAttribute("class");
document.getElementById("create-script-menu-link").removeAttribute("class");
document.getElementById("active-scripts-menu-link").removeAttribute("class");
document.getElementById("hacknet-nodes-menu-link").removeAttribute("class");
document.getElementById("world-menu-link").removeAttribute("class");
document.getElementById("tutorial-menu-link").removeAttribute("class");
//DEBUG
document.getElementById("debug-delete-scripts-link").addEventListener("click", function() {
Player.getHomeComputer().runningScripts = [];

@ -39,8 +39,8 @@ purchaseAugmentationBoxCreate = function(aug, fac) {
newConfirmButton.addEventListener("click", function() {
//TODO Requirements for specific augmentations (e.g Embedded Netburner Module b4 its upgrades)
if (aug.name == "Augmented Targeting II") {
var targeting1 = Augmentations["Augmented Targeting I"];
if (aug.name == AugmentationNames.Targeting2) {
var targeting1 = Augmentations[AugmentationNames.Targeting1];
if (targeting1 == null) {
console.log("ERROR: Could not find Augmented Targeting I");
return;
@ -48,8 +48,8 @@ purchaseAugmentationBoxCreate = function(aug, fac) {
if (targeting1.owned == false) {
dialogBoxCreate("You must first install Augmented Targeting I before you can upgrade it to Augmented Targeting II");
}
} else if (aug.name == "Augmented Targeting III") {
var targeting2 = Augmentations["Augmented Targeting II"];
} else if (aug.name == AugmentationNames.Targeting3) {
var targeting2 = Augmentations[AugmentationNames.Targeting2];
if (targeting2 == null) {
console.log("ERROR: Could not find Augmented Targeting II");
return;
@ -57,8 +57,8 @@ purchaseAugmentationBoxCreate = function(aug, fac) {
if (targeting2.owned == false) {
dialogBoxCreate("You must first install Augmented Targeting II before you can upgrade it to Augmented Targeting III");
}
} else if (aug.name == "Combat Rib II") {
var combatRib1 = Augmentations["Combat Rib I"];
} else if (aug.name == AugmentationNames.CombatRib2) {
var combatRib1 = Augmentations[AugmentationNames.CombatRib1];
if (combatRib1 == null) {
console.log("ERROR: Could not find Combat Rib I");
return;
@ -66,8 +66,8 @@ purchaseAugmentationBoxCreate = function(aug, fac) {
if (combatRib1.owned == false) {
dialogBoxCreate("You must first install Combat Rib I before you can upgrade it to Combat Rib II");
}
} else if (aug.name == "Combat Rib III") {
var combatRib2 = Augmentations["Combat Rib II"];
} else if (aug.name == AugmentationNames.CombatRib3) {
var combatRib2 = Augmentations[AugmentationNames.CombatRib2];
if (combatRib2 == null) {
console.log("ERROR: Could not find Combat Rib II");
return;
@ -75,8 +75,8 @@ purchaseAugmentationBoxCreate = function(aug, fac) {
if (combatRib2.owned == false) {
dialogBoxCreate("You must first install Combat Rib II before you can upgrade it to Combat Rib III");
}
} else if (aug.name == "Graphene Bionic Spine Upgrade") {
var bionicSpine = Augmentations["Bionic Spine"];
} else if (aug.name == AugmentationNames.GrapheneBionicSpine) {
var bionicSpine = Augmentations[AugmentationNames.BionicSpine];
if (bionicSpine == null) {
console.log("ERROR: Could not find Bionic Spine");
return;
@ -84,8 +84,8 @@ purchaseAugmentationBoxCreate = function(aug, fac) {
if (bionicSpine.owned == false) {
dialogBoxCreate("You must first install a Bionic Spine before you can upgrade it to a Graphene Bionic Spine");
}
} else if (aug.name == "Graphene Bionic Legs Upgrade") {
var bionicLegs = Augmentations["Bionic Legs"];
} else if (aug.name == AugmentationNames.GrapheneBionicLegs) {
var bionicLegs = Augmentations[AugmentationNames.BionicLegs];
if (bionicLegs == null) {
console.log("ERROR: Could not find Bionic Legs");
return;
@ -93,8 +93,8 @@ purchaseAugmentationBoxCreate = function(aug, fac) {
if (bionicLegs.owned == false ) {
dialogBoxCreate("You must first install Bionic Legs before you can upgrade it to Graphene Bionic Legs");
}
} else if (aug.name == "Embedded Netburner Module Core V2 Upgrade") {
var coreImplant = Augmentations["Embedded Netburner Module Core Implant"];
} else if (aug.name == AugmentationNames.ENMCoreV2) {
var coreImplant = Augmentations[AugmentationNames.ENMCore];
if (coreImplant == null) {
console.log("ERROR: Could not find ENM Core Implant");
return;
@ -102,8 +102,8 @@ purchaseAugmentationBoxCreate = function(aug, fac) {
if (coreImplant.owned == false) {
dialogBoxCreate("You must first install Embedded Netburner Module Core Implant before you can upgrade it to V2");
}
} else if (aug.name == "Embedded Netburner Module Core V3 Upgrade") {
var v2Upgrade = Augmentations["Embedded Netburner Module Core V2 Upgrade"];
} else if (aug.name == AugmentationNames.ENMCoreV3) {
var v2Upgrade = Augmentations[AugmentationNames.ENMCoreV2];
if (v2Upgrade == null) {
console.log("ERROR: Could not find ENM Core V2 upgrade");
return;
@ -111,10 +111,10 @@ purchaseAugmentationBoxCreate = function(aug, fac) {
if (v2Upgrade.owned == false) {
dialogBoxCreate("You must first install Embedded Netburner Module Core V2 Upgrade before you can upgrade it to V3");
}
} else if (aug.name == "Embedded Netburner Module Core Implant" ||
aug.name == "Embedded Netburner Module Analyze Engine" ||
aug.name == "Embedded Netburner Module Direct Memory Access Upgrade") {
var enm = Augmentations["Embedded Netburner Module"];
} else if (aug.name == AugmentationNames.ENMCore ||
aug.name == AugmentationNames.ENMAnalyzeEngine ||
aug.name == AugmentationNames.ENMDMA) {
var enm = Augmentations[AugmentationNames.ENM];
if (enm == null) {
console.log("ERROR: Could not find ENM");
return;
@ -123,17 +123,35 @@ purchaseAugmentationBoxCreate = function(aug, fac) {
dialogBoxCreate("You must first install the Embedded Netburner Module before installing any upgrades to it");
}
} else if (aug.name == "PC Direct-Neural Interface Optimization Submodule" ||
aug.name == "PC Direct-Neural Interface NeuroNet Injector") {
var pcdni = Augmentations["PC Direct-Neural Interface"];
} else if (aug.name == AugmentationNames.PCDNIOptimizer ||
aug.name == AugmentationNames.PCDNINeuralNetwork) {
var pcdni = Augmentations[AugmentationNames.PCDNI];
if (pcdni == null) {
console.log("ERROR: Could not find PC Direct Neural Interface");
return;
}
if (pcdni.owned == false) {
dialogBoxCreate("You must first install the PD Direct-Neural Interface before installing this upgrade");
dialogBoxCreate("You must first install the Pc Direct-Neural Interface before installing this upgrade");
}
} else if (aug.name == AugmentationNames.GrapheneBrachiBlades) {
var brachiblades = Augmentations[AugmentationNames.BrachiBlades];
if (brachiblades == null) {
console.log("ERROR: Could not find Brachi Blades aug");
return;
}
if (brachiblades.owned == false) {
dialogBoxCreate("You must first install the Brachi Blades augmentation before installing this upgrade");
}
} else if (aug.name == AugmentationNames.GrapheneBionicArms) {
var bionicarms = Augmentations[AugmentationNames.BionicArms];
if (bionicarms == null) {
console.log("ERORR: Could not find Bionic Arms aug");
return;
}
if (bionicarms.owned == false) {
dialogBoxCreate("You must first install the Bionic Arms augmentation before installing this upgrade");
}
} else if (Player.money >= (aug.baseCost * fac.augmentationPriceMult)) {
applyAugmentation(aug, fac);
//TODO Make this text better

@ -36,7 +36,7 @@ purchaseRamForHomeBoxCreate = function() {
//Calculate cost
//Have cost increase by some percentage each time RAM has been upgraded
var cost = currentRam * CONSTANTS.BaseCostFor1GBOfRamHome;
var mult = Math.pow(1.33, numUpgrades);
var mult = Math.pow(1.35, numUpgrades);
cost = cost * mult;
purchaseRamForHomeBoxSetText("Would you like to purchase additional RAM for your home computer? <br><br>" +

@ -44,10 +44,10 @@ function convertTimeMsToTimeElapsedString(time) {
var seconds = time;
var res = "";
if (days) {res += days + " days";}
if (days) {res += days + " days ";}
if (hours) {res += hours + " hours ";}
if (minutes) {res += minutes + " minutes ";}
if (seconds) {res += seconds + " seconds ";}
res += seconds + " seconds ";
return res;
}
@ -68,6 +68,11 @@ function isString(str) {
return (typeof str === 'string' || str instanceof String);
}
//Returns true if string contains only digits (meaning it would be a positive number)
function isPositiveNumber(str) {
return /^\d+$/.test(str);
}
//Returns whether an array contains entirely of string objects
function containsAllStrings(arr) {
return arr.every(isString);