Compare commits

..

16 Commits

Author SHA1 Message Date
danielyxie
59cf1d5baf v0.47.2 2019-07-15 21:40:43 -07:00
danielyxie
916ef06913 isBusy() now returns true if you are in a hacking mission 2019-07-15 21:40:43 -07:00
danielyxie
91ee65a101 Converted everything to use acorn npm package. Updated acorn packages to latest version. Updated acorn parsing to use ES9 2019-07-15 21:40:43 -07:00
danielyxie
042f926700 Minor bugfixes with killing Netscript scripts, and cleaned up text 2019-07-15 21:40:43 -07:00
danielyxie
c0432359c3 Implemented 'kill by PID' functionality 2019-07-15 21:40:43 -07:00
danielyxie
fbf5545708 Color-coded BitNode selection screen and added SF information 2019-07-15 21:40:43 -07:00
danielyxie
6ae7b0136c Minor bugfixes for a variety of NS functions. After infiltration, UI returns to corp page rather than city page 2019-07-15 21:40:43 -07:00
danielyxie
200ccd3ad0 Renamed getNoSuchRunningScriptErrorMessage() function 2019-07-15 21:40:43 -07:00
danielyxie
31f97f74fd Fix GH Issue #621: workForFaction() now properly accounts for disabled/enabled logs 2019-07-15 21:40:43 -07:00
danielyxie
4cabd2e4ed Implement GH Issue #620: Add tail() Netscript function 2019-07-15 21:40:43 -07:00
danielyxie
8be7fa9157 Implemented GH Issue #599: Added 'solarized dark' theme to CodeMirror 2019-07-15 21:40:43 -07:00
danielyxie
8ddf7dfbd4 Fix GH Issue #616: Stock Market UI throws error for certain locales because the price format length is too high 2019-07-15 21:40:43 -07:00
danielyxie
571ddb109a Fix GH Issue #632: 'Create Program' link visibility should now be properly dynamically evaluated 2019-07-15 21:40:43 -07:00
danielyxie
b2772bbfc1 Fixed GH Issue #641 2019-07-15 21:40:43 -07:00
Papayaman1000
b82d7c12af Removed outdated information
Firefox added flagless support for dynamic imports in version 67, so it no longer needs special mention.
2019-06-28 13:23:24 -07:00
danielyxie
4476d6b258 Merge pull request #642 from danielyxie/dev
Dev
2019-06-28 13:22:53 -07:00
64 changed files with 978 additions and 4073 deletions

View File

@@ -7,16 +7,28 @@
position: fixed;
}
.bitnode {
color: #00f;
}
&.level-0 {
color: red;
}
.bitnode-destroyed {
color: #f00;
}
&.level-1 {
color: yellow;
}
.bitnode:hover,
.bitnode-destroyed:hover {
color: #fff;
&.level-2 {
color: #48D1CC;
}
&.level-3 {
color: blue;
}
&.unimplemented {
color: gray;
}
&:hover {
color: #fff;
}
}

20
dist/engine.bundle.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,2 @@
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([361,0]),o()}({304:function(n,t,o){},306:function(n,t,o){},308:function(n,t,o){},310:function(n,t,o){},312:function(n,t,o){},314:function(n,t,o){},316:function(n,t,o){},318:function(n,t,o){},320:function(n,t,o){},322:function(n,t,o){},324:function(n,t,o){},326:function(n,t,o){},328:function(n,t,o){},330:function(n,t,o){},332:function(n,t,o){},334:function(n,t,o){},336:function(n,t,o){},338:function(n,t,o){},340:function(n,t,o){},342:function(n,t,o){},344:function(n,t,o){},346:function(n,t,o){},348:function(n,t,o){},350:function(n,t,o){},352:function(n,t,o){},354:function(n,t,o){},356:function(n,t,o){},358:function(n,t,o){},361:function(n,t,o){"use strict";o.r(t);o(360),o(358),o(356),o(354),o(352),o(350),o(348),o(346),o(344),o(342),o(340),o(338),o(336),o(334),o(332),o(330),o(328),o(326),o(324),o(322),o(320),o(318),o(316),o(314),o(312),o(310),o(308),o(306),o(304)}});
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([362,0]),o()}({305:function(n,t,o){},307:function(n,t,o){},309:function(n,t,o){},311:function(n,t,o){},313:function(n,t,o){},315:function(n,t,o){},317:function(n,t,o){},319:function(n,t,o){},321:function(n,t,o){},323:function(n,t,o){},325:function(n,t,o){},327:function(n,t,o){},329:function(n,t,o){},331:function(n,t,o){},333:function(n,t,o){},335:function(n,t,o){},337:function(n,t,o){},339:function(n,t,o){},341:function(n,t,o){},343:function(n,t,o){},345:function(n,t,o){},347:function(n,t,o){},349:function(n,t,o){},351:function(n,t,o){},353:function(n,t,o){},355:function(n,t,o){},357:function(n,t,o){},359:function(n,t,o){},362:function(n,t,o){"use strict";o.r(t);o(361),o(359),o(357),o(355),o(353),o(351),o(349),o(347),o(345),o(343),o(341),o(339),o(337),o(335),o(333),o(331),o(329),o(327),o(325),o(323),o(321),o(319),o(317),o(315),o(313),o(311),o(309),o(307),o(305)}});
//# sourceMappingURL=engineStyle.bundle.js.map

20
dist/engineStyle.css vendored
View File

@@ -1279,14 +1279,22 @@ button {
#red-pill-container {
position: fixed; }
.bitnode {
color: #00f; }
.bitnode.level-0 {
color: red; }
.bitnode-destroyed {
color: #f00; }
.bitnode.level-1 {
color: yellow; }
.bitnode:hover,
.bitnode-destroyed:hover {
.bitnode.level-2 {
color: #48D1CC; }
.bitnode.level-3 {
color: blue; }
.bitnode.unimplemented {
color: gray; }
.bitnode:hover {
color: #fff; }
/* COLORS */

24
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -204,8 +204,8 @@ The list contains the name of (i.e. the value returned by
| | | the string, the result should be an array with only an empty string. |
| | | |
| | | Examples: |
| | | ()())() -> ["()()()", "(())()"] |
| | | (a)())() -> ["(a)()()", "(a())()"] |
| | | ()())() -> [()()(), (())()] |
| | | (a)())() -> [(a)()(), (a())()] |
| | | )( -> [""] |
+------------------------------------+------------------------------------------------------------------------------------------+
| Find All Valid Math Expressions | | You are given a string which contains only digits between 0 and 9 as well as a target |
@@ -214,10 +214,12 @@ The list contains the name of (i.e. the value returned by
| | | |
| | | The answer should be provided as an array of strings containing the valid expressions. |
| | | |
| | | NOTE: Numbers in an expression cannot have leading 0's |
| | | |
| | | Examples: |
| | | Input: digits = "123", target = 6 |
| | | Output: ["1+2+3", "1*2*3"] |
| | | Output: [1+2+3, 1*2*3] |
| | | |
| | | Input: digits = "105", target = 5 |
| | | Output: ["1*0+5", "10-5"] |
| | | Output: [1*0+5, 10-5] |
+------------------------------------+------------------------------------------------------------------------------------------+

View File

@@ -312,9 +312,12 @@ kill
^^^^
$ kill [script name] [args...]
$ kill [pid]
Kill the script specified by the script name and arguments. Each argument must
be separated by a space. Remember that a running script is uniquely identified
Kill the script specified by the script filename and arguments OR by its PID.
If you are killing the script using its filename and arguments, then each argument
must be separated by a space. Remember that a running script is uniquely identified
by both its name and the arguments that are used to start it. So, if a script
was ran with the following arguments::
@@ -324,8 +327,7 @@ Then to kill this script the same arguments would have to be used::
$ kill foo.script 50e3 sigma-cosmetics
Note that after issuing the 'kill' command for a script, it may take a few seconds for
the script to actually stop running.
If you are killing the script using its PID, then the PID argument must be numeric.
killall
^^^^^^^
@@ -403,7 +405,7 @@ to convert from script to text file, or vice versa.
This function can also be used to rename files.
.. note:: Unlike the Linux :code:`mv` command, the *destination* argument must be the
full filepath. It cannot be a directory.
full filepath. It cannot be a directory.
Examples::
@@ -511,6 +513,8 @@ sudov
Prints whether or not you have root access to the current server.
.. _tail_terminal_command:
tail
^^^^

View File

@@ -3,6 +3,39 @@
Changelog
=========
v0.47.2 - 7/15/2019
-------------------
**Netscript Changes**
* Added tail() Netscript function
* hacknet.getNodeStats() function now returns an additional property for Hacknet Servers: hashCapacity
* When writing to a file, the write() function now casts the data being written to a string (using String())
* BitNode-selection page now shows what Source-File level you have for each BitNode
* Overloaded kill() function so that you can kill a script by its PID
* spawn() now only takes 10 seconds to run (decreased from 20 seconds)
* run() and exec() now return the PID of the newly-executed scripts, rather than a boolean
* (A PID is just a positive integer)
* run(), exec(), and spawn() no longer need to be await-ed in NetscriptJS
* Script parsing and RAM calculations now support ES9
* installAugmentations() no longer has a return value since it causes all scripts to die
* isBusy() now returns true if you are in a Hacking Mission
* Bug fix: workForFaction() function now properly accounts for disabled logs
* Bug fix: RAM should now be properly calculated when running a callback script with installAugmentations()
* Bug fix: Fixed bug that caused scripts killed by exit()/spawn() to "clean up" twice
**Misc Changes**
* The 'kill' Terminal command can now kill a script by its PID
* Added 'Solarized Dark' theme to CodeMirror editor
* After Infiltration, you will now return to the company page rather than the city page
* Bug fix: Stock Market UI should no longer crash for certain locale settings
* Bug fix: You can now properly remove unfinished programs (the *.exe-N%-INC files)
* Bug fix: Fixed an issue that allowed you to increase money on servers with a 'maxMoney' of 0 (like CSEC)
* Bug fix: Scripts no longer persist if they were started with syntax/import errors
* Bug fix: 'hack' and 'analyze' Terminal commands are now blocking
* Bug fix: Exp earned by duplicate sleeves at universities/gyms now takes hash upgrades into account
v0.47.1 - 6/27/2019
-------------------
* Stock Market changes:

View File

@@ -6,7 +6,7 @@ clear() Netscript Function
:param string/number port/fn: Port or text file to clear
:RAM cost: 1 GB
This function is used to clear data in a `Netscript Ports <http://bitburner.wikia.com/wiki/Netscript_Ports>`_ or a text file.
This function is used to clear data in a :ref:`Netscript Port <netscript_ports>` or a text file.
If the *port/fn* argument is a number between 1 and 20, then it specifies a port and will clear it (deleting all data from the underlying queue).

View File

@@ -14,10 +14,15 @@ exec() Netscript Function
Run a script as a separate process on a specified server. This is similar to the *run* function except
that it can be used to run a script on any server, instead of just the current server.
Returns true if the script is successfully started, and false otherwise.
If the script was successfully started, then this functions returns the PID
of that script. Otherwise, it returns 0.
Running this function with a *numThreads* argument of 0 will return false without running the script.
However, running this function with a negative *numThreads* argument will cause a runtime error.
.. note:: PID stands for Process ID. The PID is a unique identifier for each script.
The PID will always be a positive integer.
.. warning:: Running this function with a *numThreads* argument of 0 will return 0 without
running the script. However, running this function with a negative *numThreads*
argument will cause a runtime error.
The simplest way to use the *exec* command is to call it with just the script name and the target server.
The following example will try to run *generic-hack.script* on the *foodnstuff* server::

View File

@@ -11,3 +11,6 @@ getGrowTime() Netscript Function
The function takes in an optional *hackLvl* parameter that can be specified
to see what the grow time would be at different hacking levels.
.. note:: For Hacknet Servers (the upgraded version of a Hacknet Node), this function will
return :code:`Infinity`.

View File

@@ -11,3 +11,6 @@ getHackTime() Netscript Function
The function takes in an optional *hackLvl* parameter that can be specified
to see what the hack time would be at different hacking levels.
.. note:: For Hacknet Servers (the upgraded version of a Hacknet Node), this function will
return :code:`Infinity`.

View File

@@ -11,3 +11,6 @@ getWeakenTime() Netscript Function
The function takes in an optional *hackLvl* parameter that can be specified
to see what the weaken time would be at different hacking levels.
.. note:: For Hacknet Servers (the upgraded version of a Hacknet Node), this function will
return :code:`Infinity`.

View File

@@ -4,7 +4,7 @@ growthAnalyze() Netscript Function
.. js:function:: growthAnalyze(hostname/ip, growthAmount)
:param string hostname/ip: IP or hostname of server to analyze
:param number growthAmount: Multiplicative factor by which the server is grown. Decimal form.
:param number growthAmount: Multiplicative factor by which the server is grown. Decimal form. Must be >= 1.
:returns: The amount of grow() calls needed to grow the specified server by the specified amount
:RAM cost: 1 GB

View File

@@ -27,3 +27,22 @@ kill() Netscript Function
The following will try to kill a script named *foo.script* on the current server that was ran with the arguments 1 and "foodnstuff"::
kill("foo.script", getHostname(), 1, "foodnstuff");
.. js:function:: kill(scriptPid)
:param number scriptPid: PID of the script to kill
:RAM cost: 0.5 GB
Kills the script with the specified PID. Killing a script by its PID will typically
have better performance, especially if you have many scripts running.
If this function successfully kills the specified script, then it will return true.
Otherwise, it will return false.
*Examples:*
The following example will try to kill the script with the PID 10::
if (kill(10)) {
print("Killed script with PID 10!");
}

View File

@@ -13,10 +13,15 @@ run() Netscript Function
Run a script as a separate process. This function can only be used to run scripts located on the current server (the server
running the script that calls this function).
Returns true if the script is successfully started, and false otherwise.
If the script was successfully started, then this functions returns the PID
of that script. Otherwise, it returns 0.
Running this function with a *numThreads* argument of 0 will return false without running the script.
However, running this function with a negative *numThreads* argument will cause a runtime error.
.. note:: PID stands for Process ID. The PID is a unique identifier for each script.
The PID will always be a positive integer.
.. warning:: Running this function with a *numThreads* argument of 0 will return 0 without
running the script. However, running this function with a negative *numThreads*
argument will cause a runtime error.
The simplest way to use the *run* command is to call it with just the script name. The following example will run
'foo.script' single-threaded with no arguments::

View File

@@ -0,0 +1,29 @@
tail() Netscript Function
==================================
.. js:function:: tail([fn], [hostname/ip=current ip], [...args])
:param string fn: Optional. Filename of script to get logs from.
:param string ip: Optional. IP or hostname of the server that the script is on
:param args...: Arguments to identify which scripts to get logs for
:RAM cost: 0 GB
Opens a script's logs. This is functionally the same as the
:ref:`tail_terminal_command` Terminal command.
If the function is called with no arguments, it will open the current script's logs.
Otherwise, the `fn`, `hostname/ip,` and `args...` arguments can be used to get the logs
from another script. Remember that scripts are uniquely identified by both
their names and arguments.
Examples::
// Open logs from foo.script on the current server that was run with no args
tail("foo.script");
// Open logs from foo.script on the foodnstuff server that was run with no args
tail("foo.script", "foodnstuff");
// Open logs from foo.script on the foodnstuff server that was run with the arguments [1, "test"]
tail("foo.script", "foodnstuff", 1, "test");

View File

@@ -15,6 +15,7 @@ getNodeStats() Netscript Function
ram: Node's RAM,
cores: Node's number of cores,
cache: Cache level. Only applicable for Hacknet Servers
hashCapacity: Hash Capacity provided by this Node. Only applicable for Hacknet Servers
production: Node's production per second
timeOnline: Number of seconds since Node has been purchased,
totalProduction: Total amount that the Node has produced

View File

@@ -24,6 +24,7 @@ This includes information such as function signatures, what they do, and their r
enableLog() <basicfunctions/enableLog>
isLogEnabled() <basicfunctions/isLogEnabled>
getScriptLogs() <basicfunctions/getScriptLogs>
tail() <basicfunctions/tail>
scan() <basicfunctions/scan>
nuke() <basicfunctions/nuke>
brutessh() <basicfunctions/brutessh>

View File

@@ -16,7 +16,7 @@ there is plenty of documentation on Javascript available on the web.
Browser compatibility
---------------------
As of the time of writing this, Mozilla Firefox and a few other browsers do not support `dynamic import <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import>`_ functionality and therefore cannot run NetscriptJS scripts. These browsers will thus only be capable of using Netscript 1.0.
As of the time of writing this, a few browsers do not support `dynamic import <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import>`_ functionality and therefore cannot run NetscriptJS scripts. These browsers will thus only be capable of using Netscript 1.0.
How to use NetscriptJS
----------------------
@@ -51,8 +51,6 @@ Here is a summary of all rules you need to follow when writing Netscript JS code
* grow
* weaken
* sleep
* run
* exec
* prompt
* wget

View File

@@ -57,6 +57,10 @@ And the data in port 1 will look like::
[3, 4, 5, 6, 7, 8, 9]
.. warning:: In :ref:`netscriptjs`, do not trying writing base
`Promises <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise>`_
to a port.
**Port Handles**
WARNING: Port Handles only work in :ref:`netscriptjs`. They do not work in :ref:`netscript1`
@@ -213,16 +217,6 @@ to specify a namespace for the import::
keyword should **NOT** be used in :ref:`netscript1`, as this will break the script.
It can, however, be used in :ref:`netscriptjs` (but it's not required).
Importing in NetscriptJS
^^^^^^^^^^^^^^^^^^^^^^^^
There is a minor annoyance when using the `import` feature in :ref:`netscriptjs`.
If you make a change in a NetscriptJS script, then you have to manually "refresh" all other
scripts that import from that script.
The easiest way to do this is to simply save and then refresh the game. Alternatively,
you can open up all the scripts that need to be "refreshed" in the script editor
and then simply re-save them.
Standard, Built-In JavaScript Objects
-------------------------------------
Standard built-in JavaScript objects such as

View File

@@ -63,17 +63,17 @@ Examples
**Basic example usage**::
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
sleeve.shockRecovery(i);
sleeve.setToShockRecovery(i);
}
sleep(10 * 60 * 60); // wait 10h
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
sleeve.setToSynchronize(i);
}
sleep(10*60*60); // wait 10h
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
sleeve.synchronize(i);
}
sleep(10*60*60); // wait 10h
for (var i = 0; i < sleeve.getNumSleeves(); i++) {
sleeve.commitCrime(i, 'shoplift');
sleeve.setToCommitCrime(i, 'shoplift');
}

View File

@@ -11,4 +11,7 @@ installAugmentations() Netscript Function
This function will automatically install your Augmentations, resetting the game as usual.
It will return true if successful, and false otherwise.
This function will return false if it was not able to install Augmentations.
If this function successfully installs Augmentations, then it has no return value because
all scripts are immediately terminated.

View File

@@ -5,5 +5,12 @@ isBusy() Netscript Function
If you are not in BitNode-4, then you must have Level 1 of Source-File 4 in order to run this function.
Returns a boolean indicating whether or not the player is currently performing an 'action'. These actions include
working for a company/faction, studying at a univeristy, working out at a gym, creating a program, or committing a crime.
Returns a boolean indicating whether or not the player is currently performing an 'action'.
These actions include:
* Working for a company/faction
* Studying at a univeristy
* Working out at a gym
* Creating a program
* Committing a crime
* Carrying out a Hacking Mission

29
package-lock.json generated
View File

@@ -689,24 +689,9 @@
}
},
"acorn": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
"integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw=="
},
"acorn-dynamic-import": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz",
"integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=",
"requires": {
"acorn": "4.0.13"
},
"dependencies": {
"acorn": {
"version": "4.0.13",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
"integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
}
}
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.0.tgz",
"integrity": "sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw=="
},
"acorn-globals": {
"version": "4.3.2",
@@ -715,7 +700,7 @@
"dev": true,
"requires": {
"acorn": "6.1.1",
"acorn-walk": "6.1.1"
"acorn-walk": "6.2.0"
},
"dependencies": {
"acorn": {
@@ -744,9 +729,9 @@
}
},
"acorn-walk": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz",
"integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw=="
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
"integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA=="
},
"ajv": {
"version": "5.5.2",

View File

@@ -9,9 +9,8 @@
"@types/numeral": "0.0.25",
"@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2",
"acorn": "^5.7.3",
"acorn-dynamic-import": "^2.0.0",
"acorn-walk": "^6.1.1",
"acorn": "^6.2.0",
"acorn-walk": "^6.2.0",
"ajv": "^5.1.5",
"ajv-keywords": "^2.0.0",
"async": "^2.6.1",

View File

@@ -8,7 +8,7 @@ import { AugmentationsRoot } from "./ui/Root";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../Constants";
import { Factions, factionExists } from "../Faction/Factions";
import { addWorkerScript } from "../NetscriptWorker";
import { startWorkerScript } from "../NetscriptWorker";
import { Player } from "../Player";
import { prestigeAugmentation } from "../Prestige";
import { saveObject } from "../SaveObject";
@@ -2078,18 +2078,17 @@ function installAugmentations(cbScript=null) {
//Run a script after prestiging
if (cbScript && isString(cbScript)) {
var home = Player.getHomeComputer();
for (var i = 0; i < home.scripts.length; ++i) {
if (home.scripts[i].filename === cbScript) {
var script = home.scripts[i];
var ramUsage = script.ramUsage;
var ramAvailable = home.maxRam - home.ramUsed;
for (const script of home.scripts) {
if (script.filename === cbScript) {
const ramUsage = script.ramUsage;
const ramAvailable = home.maxRam - home.ramUsed;
if (ramUsage > ramAvailable) {
return; //Not enough RAM
return; // Not enough RAM
}
var runningScriptObj = new RunningScript(script, []); //No args
runningScriptObj.threads = 1; //Only 1 thread
home.runScript(runningScriptObj, Player.hacknet_node_money_mult);
addWorkerScript(runningScriptObj, home);
const runningScriptObj = new RunningScript(script, []); // No args
runningScriptObj.threads = 1; // Only 1 thread
startWorkerScript(runningScriptObj, home);
}
}
}

View File

@@ -6,7 +6,7 @@
import { IMap } from "./types";
export let CONSTANTS: IMap<any> = {
Version: "0.47.1",
Version: "v0.47.2",
/** 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
@@ -197,6 +197,13 @@ export let CONSTANTS: IMap<any> = {
ClassLeadershipBaseCost: 320,
ClassGymBaseCost: 120,
ClassStudyComputerScienceBaseExp: 0.5,
ClassDataStructuresBaseExp: 1,
ClassNetworksBaseExp: 2,
ClassAlgorithmsBaseExp: 4,
ClassManagementBaseExp: 2,
ClassLeadershipBaseExp: 4,
CrimeShoplift: "shoplift",
CrimeRobStore: "rob a store",
CrimeMug: "mug someone",
@@ -221,18 +228,35 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate:
`
v0.47.1
* Stock Market changes:
** Transactions no longer influence stock prices (but they still influence forecast)
** Changed the way stocks behave, particularly with regard to how the stock forecast occasionally "flips"
** Hacking & growing a server can potentially affect the way the corresponding stock's forecast changes
** Working for a company positively affects the way the corresponding stock's forecast changes
v0.47.2
-------
* Scripts now start/stop instantly
* Improved performance when starting up many copies of a new NetscriptJS script (by Ornedan)
* Improved performance when killing scripts
* Dialog boxes can now be closed with the ESC key (by jaguilar)
* NetscriptJS scripts should now be "re-compiled" if their dependencies change (by jaguilar)
* write() function should now properly cause NetscriptJS scripts to "re-compile" (by jaguilar)
Netscript Changes
* Added tail() Netscript function
* hacknet.getNodeStats() function now returns an additional property for Hacknet Servers: hashCapacity
* When writing to a file, the write() function now casts the data being written to a string (using String())
* BitNode-selection page now shows what Source-File level you have for each BitNode
* Overloaded kill() function so that you can kill a script by its PID
* spawn() now only takes 10 seconds to run (decreased from 20 seconds)
* run() and exec() now return the PID of the newly-executed scripts, rather than a boolean
** (A PID is just a positive integer)
* run(), exec(), and spawn() no longer need to be await-ed in NetscriptJS
* Script parsing and RAM calculations now support ES9
* installAugmentations() no longer has a return value since it causes all scripts to die
* isBusy() now returns true if you are in a Hacking Mission
* Bug fix: workForFaction() function now properly accounts for disabled logs
* Bug fix: RAM should now be properly calculated when running a callback script with installAugmentations()
* Bug fix: Fixed bug that caused scripts killed by exit()/spawn() to "clean up" twice
Misc Changes
* The 'kill' Terminal command can now kill a script by its PID
* Added 'Solarized Dark' theme to CodeMirror editor
* After Infiltration, you will now return to the company page rather than the city page
* Bug fix: Stock Market UI should no longer crash for certain locale settings
* Bug fix: You can now properly remove unfinished programs (the *.exe-N%-INC files)
* Bug fix: Fixed an issue that allowed you to increase money on servers with a 'maxMoney' of 0 (like CSEC)
* Bug fix: Scripts no longer persist if they were started with syntax/import errors
* Bug fix: 'hack' and 'analyze' Terminal commands are now blocking
* Bug fix: Exp earned by duplicate sleeves at universities/gyms now takes hash upgrades into account
`
}

View File

@@ -1,6 +1,6 @@
import { FconfSettings } from "./FconfSettings";
import { parse, Node } from "../../utils/acorn";
import { parse, Node } from "acorn";
import { dialogBoxCreate } from "../../utils/DialogBox";
var FconfComments = {

View File

@@ -18,14 +18,17 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
{
costPerLevel: 50,
desc: "Use hashes to decrease the minimum security of a single server by 2%. " +
"Note that a server's minimum security cannot go below 1.",
"Note that a server's minimum security cannot go below 1. This effect persists " +
"until you install Augmentations (since servers are reset at that time).",
hasTargetServer: true,
name: "Reduce Minimum Security",
value: 0.98,
},
{
costPerLevel: 50,
desc: "Use hashes to increase the maximum amount of money on a single server by 2%",
desc: "Use hashes to increase the maximum amount of money on a single server by 2%. " +
"This effect persists until you install Augmentations (since servers " +
"are reset at that time).",
hasTargetServer: true,
name: "Increase Maximum Money",
value: 1.02,

View File

@@ -129,7 +129,7 @@ function endInfiltration(inst, success) {
clearEventListeners("infiltration-bribe");
clearEventListeners("infiltration-escape");
Engine.loadLocationContent();
Engine.loadLocationContent(false);
}
function nextInfiltrationLevel(inst) {

View File

@@ -1,4 +1,4 @@
import * as acorn from "../utils/acorn";
import * as acorn from "acorn";
/**
* @license
* JavaScript Interpreter

View File

@@ -76,37 +76,46 @@ export class UniversityLocation extends React.Component<IProps, any> {
const managementCost = CONSTANTS.ClassManagementBaseCost * costMult;
const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult;
const earnHackingExpTooltip = `Gain hacking experience!`
const earnCharismaExpTooltip = `Gain charisma experience!`;
return (
<div>
<StdButton
onClick={this.study}
style={this.btnStyle}
text={`Study Computer Science (free)`}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.dataStructures}
style={this.btnStyle}
text={`Take Data Structures course (${numeralWrapper.formatMoney(dataStructuresCost)} / sec)`}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.networks}
style={this.btnStyle}
text={`Take Networks course (${numeralWrapper.formatMoney(networksCost)} / sec)`}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.algorithms}
style={this.btnStyle}
text={`Take Algorithms course (${numeralWrapper.formatMoney(algorithmsCost)} / sec)`}
tooltip={earnHackingExpTooltip}
/>
<StdButton
onClick={this.management}
style={this.btnStyle}
text={`Take Management course (${numeralWrapper.formatMoney(managementCost)} / sec)`}
tooltip={earnCharismaExpTooltip}
/>
<StdButton
onClick={this.leadership}
style={this.btnStyle}
text={`Take Leadership course (${numeralWrapper.formatMoney(leadershipCost)} / sec)`}
tooltip={earnCharismaExpTooltip}
/>
</div>
)

View File

@@ -89,7 +89,7 @@ function removeWorkerScript(workerScript: WorkerScript, rerenderUi: boolean=true
// Recalculate ram used on that server
server.ramUsed = roundToTwo(server.ramUsed - workerScript.ramUsage);
if (server.ramUsed < 0) {
console.warn(`Server RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${server.ramUsed}`);
console.warn(`Server (${server.hostname}) RAM usage went negative (if it's due to floating pt imprecision, it's okay): ${server.ramUsed}`);
server.ramUsed = 0;
}

View File

@@ -1,5 +1,4 @@
import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { parse, Node } from "../utils/acorn";
import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
import { isString } from "../utils/helpers/isString";

View File

@@ -145,9 +145,10 @@ import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { is2DArray } from "./utils/helpers/is2DArray";
import { dialogBoxCreate } from "../utils/DialogBox";
import { isPowerOfTwo } from "../utils/helpers/isPowerOfTwo";
import { arrayToString } from "../utils/helpers/arrayToString";
import { formatNumber, isHTML } from "../utils/StringHelperFunctions";
import { logBoxCreate } from "../utils/LogBox";
import { arrayToString } from "../utils/helpers/arrayToString";
import { isPowerOfTwo } from "../utils/helpers/isPowerOfTwo";
import { isString } from "../utils/helpers/isString";
import { createElement } from "../utils/uiHelpers/createElement";
@@ -251,7 +252,7 @@ function NetscriptFunctions(workerScript) {
/**
* Gets the Server for a specific hostname/ip, throwing an error
* if the server doesn't exist.
* @param {string} Hostname or IP of the server
* @param {string} ip - Hostname or IP of the server
* @param {string} callingFnName - Name of calling function. For logging purposes
* @returns {Server} The specified Server
*/
@@ -264,6 +265,59 @@ function NetscriptFunctions(workerScript) {
return server;
}
/**
* Searches for and returns the RunningScript object for the specified script.
* If the 'fn' argument is not specified, this returns the current RunningScript.
* @param {string} fn - Filename of script
* @param {string} ip - Hostname/ip of the server on which the script resides
* @param {any[]} scriptArgs - Running script's arguments
* @returns {RunningScript}
* Running script identified by the parameters, or null if no such script
* exists, or the current running script if the first argument 'fn'
* is not specified.
*/
const getRunningScript = function(fn, ip, callingFnName, scriptArgs) {
// Sanitize arguments
if (typeof callingFnName !== "string" || callingFnName === "") {
callingFnName = "getRunningScript";
}
if (!Array.isArray(scriptArgs)) {
throw makeRuntimeRejectMsg(
workerScript,
`Invalid scriptArgs argument passed into getRunningScript() from ${callingFnName}(). ` +
`This is probably a bug. Please report to game developer`
);
}
if (fn != null && typeof fn === "string") {
// Get Logs of another script
if (ip == null) { ip = workerScript.serverIp; }
const server = safeGetServer(ip, callingFnName);
return findRunningScript(fn, scriptArgs, server);
}
// If no arguments are specified, return the current RunningScript
return workerScript.scriptRef;
}
/**
* Helper function for getting the error log message when the user specifies
* a nonexistent running script
* @param {string} fn - Filename of script
* @param {string} ip - Hostname/ip of the server on which the script resides
* @param {any[]} scriptArgs - Running script's arguments
* @returns {string} Error message to print to logs
*/
const getCannotFindRunningScriptErrorMessage = function(fn, ip, scriptArgs) {
if (!Array.isArray(scriptArgs)) {
scriptArgs = [];
}
return `Cannot find running script ${fn} on server ${ip} with args: ${arrayToString(scriptArgs)}`;
}
/**
* Checks if the player has TIX API access. Throws an error if the player does not
*/
@@ -351,7 +405,7 @@ function NetscriptFunctions(workerScript) {
const node = getHacknetNode(i);
const hasUpgraded = hasHacknetServers();
const res = {
name: node.name,
name: hasUpgraded ? node.hostname : node.name,
level: node.level,
ram: hasUpgraded ? node.maxRam : node.ram,
cores: node.cores,
@@ -362,6 +416,7 @@ function NetscriptFunctions(workerScript) {
if (hasUpgraded) {
res.cache = node.cache;
res.hashCapacity = node.hashCapacity;
}
return res;
@@ -556,8 +611,8 @@ function NetscriptFunctions(workerScript) {
if (time === undefined) {
throw makeRuntimeRejectMsg(workerScript, "sleep() call has incorrect number of arguments. Takes 1 argument");
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.sleep == null) {
workerScript.scriptRef.log("Sleeping for " + time + " milliseconds");
if (workerScript.shouldLog("sleep")) {
workerScript.log(`Sleeping for ${time} milliseconds`);
}
return netscriptDelay(time, workerScript).then(function() {
return Promise.resolve(true);
@@ -597,10 +652,10 @@ function NetscriptFunctions(workerScript) {
if (growthPercentage == 1) {
expGain = 0;
}
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.grow == null) {
workerScript.scriptRef.log("Available money on " + server.hostname + " grown by " +
formatNumber((moneyAfter/moneyBefore)*100 - 100, 6) + "%. Gained " +
formatNumber(expGain, 4) + " hacking exp (t=" + threads +")");
if (workerScript.shouldLog("grow")) {
workerScript.log("Available money on " + server.hostname + " grown by " +
formatNumber((moneyAfter/moneyBefore)*100 - 100, 6) + "%. Gained " +
formatNumber(expGain, 4) + " hacking exp (t=" + threads +")");
}
workerScript.scriptRef.onlineExpGained += expGain;
Player.gainHackingExp(expGain);
@@ -615,8 +670,8 @@ function NetscriptFunctions(workerScript) {
// Check argument validity
const server = safeGetServer(ip, 'growthAnalyze');
if (isNaN(growth)) {
throw makeRuntimeRejectMsg(workerScript, `Invalid growth argument passed into growthAnalyze: ${growth}. Must be numeric`);
if (typeof growth !== "number" || isNaN(growth) || growth < 1) {
throw makeRuntimeRejectMsg(workerScript, `Invalid growth argument passed into growthAnalyze: ${growth}. Must be numeric and >= 1`);
}
return numCycleForGrowth(server, Number(growth), Player);
@@ -699,29 +754,23 @@ function NetscriptFunctions(workerScript) {
}
return workerScript.disableLogs[fn] ? false : true;
},
getScriptLogs: function(fn, ip) {
if (fn != null && typeof fn === 'string') {
// Get Logs of another script
if (ip == null) { ip = workerScript.serverIp; }
const server = getServer(ip);
if (server == null) {
workerScript.log(`getScriptLogs() failed. Invalid IP or hostname passed in: ${ip}`);
throw makeRuntimeRejectMsg(workerScript, `getScriptLogs() failed. Invalid IP or hostname passed in: ${ip}`);
}
let argsForTarget = [];
for (let i = 2; i < arguments.length; ++i) {
argsForTarget.push(arguments[i]);
}
const runningScriptObj = findRunningScript(fn, argsForTarget, server);
if (runningScriptObj == null) {
workerScript.scriptRef.log(`getScriptLogs() failed. No such script ${fn} on ${server.hostname} with args: ${arrayToString(argsForTarget)}`);
return "";
}
return runningScriptObj.logs.slice();
getScriptLogs: function(fn, ip, ...scriptArgs) {
const runningScriptObj = getRunningScript(fn, ip, "getScriptLogs", scriptArgs);
if (runningScriptObj == null) {
workerScript.log(`getScriptLogs() failed. ${getCannotFindRunningScriptErrorMessage(fn, ip, scriptArgs)}`);
return "";
}
return workerScript.scriptRef.logs.slice();
return runningScriptObj.logs.slice();
},
tail: function(fn, ip, ...scriptArgs) {
const runningScriptObj = getRunningScript(fn, ip, "tail", scriptArgs);
if (runningScriptObj == null) {
workerScript.log(`tail() failed. ${getCannotFindRunningScriptErrorMessage(fn, ip, scriptArgs)} `);
return;
}
logBoxCreate(runningScriptObj);
},
nuke: function(ip){
updateDynamicRam("nuke", getRamCost("nuke"));
@@ -924,6 +973,8 @@ function NetscriptFunctions(workerScript) {
if (scriptname == null || threads == null) {
throw makeRuntimeRejectMsg(workerScript, "Invalid scriptname or numThreads argument passed to spawn()");
}
const spawnDelay = 10;
setTimeoutRef(() => {
if (scriptname === undefined) {
throw makeRuntimeRejectMsg(workerScript, "spawn() call has incorrect number of arguments. Usage: spawn(scriptname, numThreads, [arg1], [arg2]...)");
@@ -941,40 +992,57 @@ function NetscriptFunctions(workerScript) {
}
return runScriptFromScript(scriptServer, scriptname, argsForNewScript, workerScript, threads);
}, 20e3);
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.spawn == null) {
workerScript.scriptRef.log("spawn() will execute " + scriptname + " in 20 seconds");
}, spawnDelay * 1e3);
if (workerScript.shouldLog("spawn")) {
workerScript.log(`spawn() will execute ${scriptname} in ${spawnDelay} seconds`);
}
workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
if (killWorkerScript(workerScript)) {
workerScript.log("Exiting...");
}
NetscriptFunctions(workerScript).exit();
},
kill: function(filename, ip) {
kill: function(filename, ip, ...scriptArgs) {
updateDynamicRam("kill", getRamCost("kill"));
if (filename === undefined || ip === undefined) {
throw makeRuntimeRejectMsg(workerScript, "kill() call has incorrect number of arguments. Usage: kill(scriptname, server, [arg1], [arg2]...)");
let res;
const killByPid = (typeof filename === "number");
if (killByPid) {
// Kill by pid
res = killWorkerScript(filename);
} else {
// Kill by filename/ip
if (filename === undefined || ip === undefined) {
throw makeRuntimeRejectMsg(workerScript, "kill() call has incorrect number of arguments. Usage: kill(scriptname, server, [arg1], [arg2]...)");
}
const server = safeGetServer(ip);
const runningScriptObj = getRunningScript(filename, ip, "kill", scriptArgs);
if (runningScriptObj == null) {
workerScript.log(`kill() failed. ${getCannotFindRunningScriptErrorMessage(filename, ip, scriptArgs)}`)
return false;
}
res = killWorkerScript(runningScriptObj, server.ip);
}
var server = getServer(ip);
if (server == null) {
workerScript.scriptRef.log("kill() failed. Invalid IP or hostname passed in: " + ip);
throw makeRuntimeRejectMsg(workerScript, "kill() failed. Invalid IP or hostname passed in: " + ip);
}
var argsForKillTarget = [];
for (var i = 2; i < arguments.length; ++i) {
argsForKillTarget.push(arguments[i]);
}
var runningScriptObj = findRunningScript(filename, argsForKillTarget, server);
if (runningScriptObj == null) {
workerScript.scriptRef.log("kill() failed. No such script "+ filename + " on " + server.hostname + " with args: " + arrayToString(argsForKillTarget));
return false;
}
var res = killWorkerScript(runningScriptObj, server.ip);
if (res) {
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.kill == null) {
workerScript.scriptRef.log("Killing " + filename + " on " + server.hostname + " with args: " + arrayToString(argsForKillTarget) + ". May take up to a few minutes for the scripts to die...");
if (workerScript.shouldLog("kill")) {
if (killByPid) {
workerScript.log(`Killing script with PID ${filename}`);
} else {
workerScript.log(`Killing ${filename} on ${ip} with args: ${arrayToString(scriptArgs)}.`);
}
}
return true;
} else {
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.kill == null) {
workerScript.scriptRef.log("kill() failed. No such script "+ filename + " on " + server.hostname + " with args: " + arrayToString(argsForKillTarget));
if (workerScript.shouldLog("kill")) {
if (killByPid) {
workerScript.log(`kill() failed. No such script with PID ${filename}`);
} else {
workerScript.log(`kill() failed. No such script ${filename} on ${ip} with args: ${arrayToString(scriptArgs)}`);
}
}
return false;
}
@@ -1001,14 +1069,11 @@ function NetscriptFunctions(workerScript) {
return scriptsRunning;
},
exit : function() {
var server = getServer(workerScript.serverIp);
if (server == null) {
throw makeRuntimeRejectMsg(workerScript, "Error getting Server for this script in exit(). This is a bug please contact game dev");
}
if (killWorkerScript(workerScript.scriptRef, server.ip)) {
workerScript.scriptRef.log("Exiting...");
workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
if (killWorkerScript(workerScript)) {
workerScript.log("Exiting...");
} else {
workerScript.scriptRef.log("Exit failed(). This is a bug please contact game developer");
workerScript.log("Exit failed(). This is a bug please contact game developer");
}
},
scp: function(scriptname, ip1, ip2) {
@@ -1945,6 +2010,13 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeRejectMsg(workerScript, `write() failed due to invalid filepath: ${fn}`);
}
// Coerce 'data' to be a string
try {
data = String(data);
} catch (e) {
throw makeRuntimeRejectMsg(workerScript, `write() failed because of invalid data (${e}). Data being written must be convertible to a string`);
}
const server = workerScript.getServer();
if (server == null) {
throw makeRuntimeRejectMsg(workerScript, "Error getting Server for this script in write(). This is a bug please contact game dev");
@@ -2680,7 +2752,7 @@ function NetscriptFunctions(workerScript) {
return;
}
}
return Player.isWorking;
return Player.isWorking || inMission;
},
stopAction: function() {
updateDynamicRam("stopAction", getRamCost("stopAction"));
@@ -2999,8 +3071,8 @@ function NetscriptFunctions(workerScript) {
if (Player.isWorking) {
var txt = Player.singularityStopWork();
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.workForFaction == null) {
workerScript.scriptRef.log(txt);
if (workerScript.shouldLog("workForFaction")) {
workerScript.log(txt);
}
}
@@ -3031,34 +3103,40 @@ function NetscriptFunctions(workerScript) {
case "hacking contracts":
case "hackingcontracts":
if (!hackAvailable.includes(fac.name)) {
workerScript.scriptRef.log("ERROR: Cannot carry out hacking contracts for " + fac.name + ". workForFaction() failed");
workerScript.log("ERROR: Cannot carry out hacking contracts for " + fac.name + ". workForFaction() failed");
return false;
}
Player.startFactionHackWork(fac);
workerScript.scriptRef.log("Started carrying out hacking contracts for " + fac.name);
if (workerScript.shouldLog("workForFaction")) {
workerScript.log("Started carrying out hacking contracts for " + fac.name);
}
return true;
case "field":
case "fieldwork":
case "field work":
if (!fdWkAvailable.includes(fac.name)) {
workerScript.scriptRef.log("ERROR: Cannot carry out field missions for " + fac.name + ". workForFaction() failed");
workerScript.log("ERROR: Cannot carry out field missions for " + fac.name + ". workForFaction() failed");
return false;
}
Player.startFactionFieldWork(fac);
workerScript.scriptRef.log("Started carrying out field missions for " + fac.name);
if (workerScript.shouldLog("workForFaction")) {
workerScript.log("Started carrying out field missions for " + fac.name);
}
return true;
case "security":
case "securitywork":
case "security work":
if (!scWkAvailable.includes(fac.name)) {
workerScript.scriptRef.log("ERROR: Cannot serve as security detail for " + fac.name + ". workForFaction() failed");
workerScript.log("ERROR: Cannot serve as security detail for " + fac.name + ". workForFaction() failed");
return false;
}
Player.startFactionSecurityWork(fac);
workerScript.scriptRef.log("Started serving as security details for " + fac.name);
if (workerScript.shouldLog("workForFaction")) {
workerScript.log("Started serving as security details for " + fac.name);
}
return true;
default:
workerScript.scriptRef.log("ERROR: Invalid work type passed into workForFaction(): " + type);
workerScript.log("ERROR: Invalid work type passed into workForFaction(): " + type);
}
return true;
},
@@ -3419,9 +3497,13 @@ function NetscriptFunctions(workerScript) {
return false;
}
Player.gainIntelligenceExp(CONSTANTS.IntelligenceSingFnBaseExpGain);
workerScript.scriptRef.log("Installing Augmentations. This will cause this script to be killed");
installAugmentations(cbScript);
return true;
workerScript.log("Installing Augmentations. This will cause this script to be killed");
setTimeoutRef(() => {
installAugmentations(cbScript);
}, 0);
workerScript.running = false; // Prevent workerScript from "finishing execution naturally"
killWorkerScript(workerScript);
},
// Gang API

View File

@@ -30,14 +30,14 @@ import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { generate } from "escodegen";
import { parse, Node } from "../utils/acorn";
import { dialogBoxCreate } from "../utils/DialogBox";
import { compareArrays } from "../utils/helpers/compareArrays";
import { arrayToString } from "../utils/helpers/arrayToString";
import { roundToTwo } from "../utils/helpers/roundToTwo";
import { isString } from "../utils/StringHelperFunctions";
const walk = require("acorn/dist/walk");
import { parse, Node } from "acorn";
const walk = require("acorn-walk");
// Netscript Ports are instantiated here
export const NetscriptPorts = [];
@@ -48,6 +48,7 @@ for (var i = 0; i < CONSTANTS.NumNetscriptPorts; ++i) {
export function prestigeWorkerScripts() {
for (const ws of workerScripts.values()) {
ws.env.stopFlag = true;
killWorkerScript(ws);
}
WorkerScriptStartStopEventEmitter.emitEvent();
@@ -149,6 +150,7 @@ function startNetscript1Script(workerScript) {
dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + e);
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return;
}
@@ -160,7 +162,7 @@ function startNetscript1Script(workerScript) {
if (typeof entry === "function") {
//Async functions need to be wrapped. See JS-Interpreter documentation
if (name === "hack" || name === "grow" || name === "weaken" || name === "sleep" ||
name === "prompt" || name === "run" || name === "exec") {
name === "prompt") {
let tempWrapper = function() {
let fnArgs = [];
@@ -183,7 +185,8 @@ function startNetscript1Script(workerScript) {
}
int.setProperty(scope, name, int.createAsyncFunction(tempWrapper));
} else if (name === "sprintf" || name === "vsprintf" || name === "scp" ||
name == "write" || name === "read" || name === "tryWrite") {
name == "write" || name === "read" || name === "tryWrite" ||
name === "run" || name === "exec") {
let tempWrapper = function() {
let fnArgs = [];
@@ -232,6 +235,7 @@ function startNetscript1Script(workerScript) {
dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + e);
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return;
}
@@ -283,7 +287,7 @@ function startNetscript1Script(workerScript) {
*/
function processNetscript1Imports(code, workerScript) {
//allowReserved prevents 'import' from throwing error in ES5
const ast = parse(code, { ecmaVersion: 6, allowReserved: true, sourceType: "module" });
const ast = parse(code, { ecmaVersion: 9, allowReserved: true, sourceType: "module" });
var server = workerScript.getServer();
if (server == null) {
@@ -314,7 +318,7 @@ function processNetscript1Imports(code, workerScript) {
if (script == null) {
throw new Error("'Import' failed due to invalid script: " + scriptName);
}
let scriptAst = parse(script.code, {ecmaVersion:5, allowReserved:true, sourceType:"module"});
let scriptAst = parse(script.code, { ecmaVersion:9, allowReserved:true, sourceType:"module" });
if (node.specifiers.length === 1 && node.specifiers[0].type === "ImportNamespaceSpecifier") {
// import * as namespace from script
@@ -445,14 +449,35 @@ function generateNextPid() {
}
/**
* Start a script
*
* Given a RunningScript object, constructs a corresponding WorkerScript,
* Used to start a RunningScript (by creating and starting its
* corresponding WorkerScript), and add the RunningScript to the server on which
* it is active
* @param {RunningScript} runningScriptObj - Script that's being run
* @param {Server} server - Server on which the script is to be run
* @returns {number} pid of started script
*/
export function startWorkerScript(runningScript, server) {
if (createAndAddWorkerScript(runningScript, server)) {
// Push onto runningScripts.
// This has to come after createAndAddWorkerScript() because that fn updates RAM usage
server.runScript(runningScript, Player.hacknet_node_money_mult);
// Once the WorkerScript is constructed in createAndAddWorkerScript(), the RunningScript
// object should have a PID assigned to it, so we return that
return runningScript.pid;
}
return 0;
}
/**
* Given a RunningScript object, constructs its corresponding WorkerScript,
* adds it to the global 'workerScripts' pool, and begins executing it.
* @param {RunningScript} runningScriptObj - Script that's being run
* @param {Server} server - Server on which the script is to be run
* returns {boolean} indicating whether or not the workerScript was successfully added
*/
export function addWorkerScript(runningScriptObj, server) {
export function createAndAddWorkerScript(runningScriptObj, server) {
const filename = runningScriptObj.filename;
// Update server's ram usage
@@ -471,7 +496,7 @@ export function addWorkerScript(runningScriptObj, server) {
`the game and the script's RAM usage increased (either because of an update to the game or ` +
`your changes to the script.)`
);
return;
return false;
}
server.ramUsed = roundToTwo(server.ramUsed + ramUsage);
@@ -489,31 +514,35 @@ export function addWorkerScript(runningScriptObj, server) {
const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
s.ramUsage = ramUsage;
// Add the WorkerScript to the global pool
workerScripts.set(pid, s);
WorkerScriptStartStopEventEmitter.emitEvent();
// Start the script's execution
let p = null; // Script's resulting promise
if (s.name.endsWith(".js") || s.name.endsWith(".ns")) {
p = startNetscript2Script(s);
} else {
p = startNetscript1Script(s);
if (!(p instanceof Promise)) { return; }
if (!(p instanceof Promise)) { return false; }
}
// Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false
p.then(function(w) {
// If the WorkerScript is no longer "running", then this means its execution was
// already stopped somewhere else (maybe by something like exit()). This prevents
// the script from being cleaned up twice
if (!w.running) { return; }
console.log("Stopping script " + w.name + " because it finished running naturally");
killWorkerScript(s);
w.scriptRef.log("Script finished running");
w.log("Script finished running");
}).catch(function(w) {
if (w instanceof Error) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
return;
} else if (w.constructor === Array && w.length === 2 && w[0] === "RETURNSTATEMENT") {
// Script ends with a return statement
console.log("Script returning with value: " + w[1]);
// TODO maybe do something with this in the future
return;
} else if (w instanceof WorkerScript) {
if (isScriptErrorMessage(w.errorMessage)) {
var errorTextArray = w.errorMessage.split("|");
@@ -529,9 +558,9 @@ export function addWorkerScript(runningScriptObj, server) {
dialogBoxCreate("Script runtime error: <br>Server Ip: " + serverIp +
"<br>Script name: " + scriptName +
"<br>Args:" + arrayToString(w.args) + "<br>" + errorMsg);
w.scriptRef.log("Script crashed with runtime error");
w.log("Script crashed with runtime error");
} else {
w.scriptRef.log("Script killed");
w.log("Script killed");
return; // Already killed, so stop here
}
w.running = false;
@@ -548,10 +577,7 @@ export function addWorkerScript(runningScriptObj, server) {
killWorkerScript(s);
});
// Add the WorkerScript to the global pool
workerScripts.set(pid, s);
WorkerScriptStartStopEventEmitter.emitEvent();
return;
return true;
}
/**
@@ -589,7 +615,7 @@ export function loadAllRunningScripts() {
server.runningScripts.length = 0;
} else {
for (let j = 0; j < server.runningScripts.length; ++j) {
addWorkerScript(server.runningScripts[j], server);
createAndAddWorkerScript(server.runningScripts[j], server);
// Offline production
total += scriptCalculateOfflineProduction(server.runningScripts[j]);
@@ -605,54 +631,62 @@ export function loadAllRunningScripts() {
* Run a script from inside another script (run(), exec(), spawn(), etc.)
*/
export function runScriptFromScript(server, scriptname, args, workerScript, threads=1) {
//Check if the script is already running
let runningScriptObj = findRunningScript(scriptname, args, server);
if (runningScriptObj != null) {
workerScript.scriptRef.log(scriptname + " is already running on " + server.hostname);
return Promise.resolve(false);
// Sanitize arguments
if (!(workerScript instanceof WorkerScript)) {
return 0;
}
//'null/undefined' arguments are not allowed
if (typeof scriptname !== "string" || !Array.isArray(args)) {
workerScript.log(`ERROR: runScriptFromScript() failed due to invalid arguments`);
console.error(`runScriptFromScript() failed due to invalid arguments`);
return 0;
}
// Check if the script is already running
let runningScriptObj = server.getRunningScript(scriptname, args);
if (runningScriptObj != null) {
workerScript.log(`${scriptname} is already running on ${server.hostname}`);
return 0;
}
// 'null/undefined' arguments are not allowed
for (let i = 0; i < args.length; ++i) {
if (args[i] == null) {
workerScript.scriptRef.log("ERROR: Cannot execute a script with null/undefined as an argument");
return Promise.resolve(false);
workerScript.log("ERROR: Cannot execute a script with null/undefined as an argument");
return 0;
}
}
//Check if the script exists and if it does run it
// Check if the script exists and if it does run it
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename == scriptname) {
//Check for admin rights and that there is enough RAM availble to run
var script = server.scripts[i];
var ramUsage = script.ramUsage;
threads = Math.round(Number(threads)); //Convert to number and round
if (threads === 0) { return Promise.resolve(false); }
if (server.scripts[i].filename === scriptname) {
// Check for admin rights and that there is enough RAM availble to run
const script = server.scripts[i];
let ramUsage = script.ramUsage;
threads = Math.round(Number(threads));
if (threads === 0) { return 0; }
ramUsage = ramUsage * threads;
var ramAvailable = server.maxRam - server.ramUsed;
const ramAvailable = server.maxRam - server.ramUsed;
if (server.hasAdminRights == false) {
workerScript.scriptRef.log("Cannot run script " + scriptname + " on " + server.hostname + " because you do not have root access!");
return Promise.resolve(false);
workerScript.log(`Cannot run script ${scriptname} on ${server.hostname} because you do not have root access!`);
return 0;
} else if (ramUsage > ramAvailable){
workerScript.scriptRef.log("Cannot run script " + scriptname + "(t=" + threads + ") on " + server.hostname + " because there is not enough available RAM!");
return Promise.resolve(false);
workerScript.log(`Cannot run script ${scriptname} (t=${threads}) on ${server.hostname} because there is not enough available RAM!`);
return 0;
} else {
//Able to run script
// Able to run script
if (workerScript.disableLogs.ALL == null && workerScript.disableLogs.exec == null && workerScript.disableLogs.run == null && workerScript.disableLogs.spawn == null) {
workerScript.scriptRef.log(`Running script: ${scriptname} on ${server.hostname} with ${threads} threads and args: ${arrayToString(args)}. May take a few seconds to start up...`);
workerScript.log(`Running script: ${scriptname} on ${server.hostname} with ${threads} threads and args: ${arrayToString(args)}.`);
}
let runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = threads;
addWorkerScript(runningScriptObj, server);
// Push onto runningScripts.
// This has to come after addWorkerScript() because that fn updates RAM usage
server.runScript(runningScriptObj, Player.hacknet_node_money_mult);
return Promise.resolve(true);
return startWorkerScript(runningScriptObj, server);
}
}
}
workerScript.scriptRef.log("Could not find script " + scriptname + " on " + server.hostname);
return Promise.resolve(false);
workerScript.log(`Could not find script ${scriptname} on ${server.hostname}`);
return 0;
}

View File

@@ -30,6 +30,7 @@ export interface IPlayer {
corporation: any;
currentServer: string;
factions: string[];
firstProgramAvailable: boolean;
firstTimeTraveled: boolean;
hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server
has4SData: boolean;

View File

@@ -1211,42 +1211,33 @@ export function startClass(costMult, expMult, className) {
var gameCPS = 1000 / Engine._idleSpeed;
//Base exp gains per second
var baseStudyComputerScienceExp = 0.5;
var baseDataStructuresExp = 1;
var baseNetworksExp = 2;
var baseAlgorithmsExp = 4;
var baseManagementExp = 2;
var baseLeadershipExp = 4;
var baseGymExp = 1;
//Find cost and exp gain per game cycle
var cost = 0;
var hackExp = 0, strExp = 0, defExp = 0, dexExp = 0, agiExp = 0, chaExp = 0;
const hashManager = this.hashManager;
switch (className) {
case CONSTANTS.ClassStudyComputerScience:
hackExp = baseStudyComputerScienceExp * expMult / gameCPS * hashManager.getStudyMult();
hackExp = CONSTANTS.ClassStudyComputerScienceBaseExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassDataStructures:
cost = CONSTANTS.ClassDataStructuresBaseCost * costMult / gameCPS;
hackExp = baseDataStructuresExp * expMult / gameCPS * hashManager.getStudyMult();
hackExp = CONSTANTS.ClassDataStructuresBaseExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassNetworks:
cost = CONSTANTS.ClassNetworksBaseCost * costMult / gameCPS;
hackExp = baseNetworksExp * expMult / gameCPS * hashManager.getStudyMult();
hackExp = CONSTANTS.ClassNetworksBaseExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassAlgorithms:
cost = CONSTANTS.ClassAlgorithmsBaseCost * costMult / gameCPS;
hackExp = baseAlgorithmsExp * expMult / gameCPS * hashManager.getStudyMult();
hackExp = CONSTANTS.ClassAlgorithmsBaseExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassManagement:
cost = CONSTANTS.ClassManagementBaseCost * costMult / gameCPS;
chaExp = baseManagementExp * expMult / gameCPS * hashManager.getStudyMult();
chaExp = CONSTANTS.ClassManagementBaseExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassLeadership:
cost = CONSTANTS.ClassLeadershipBaseCost * costMult / gameCPS;
chaExp = baseLeadershipExp * expMult / gameCPS * hashManager.getStudyMult();
chaExp = CONSTANTS.ClassLeadershipBaseExp * expMult / gameCPS * hashManager.getStudyMult();
break;
case CONSTANTS.ClassGymStrength:
cost = CONSTANTS.ClassGymBaseCost * costMult / gameCPS;

View File

@@ -9,9 +9,11 @@
import { SleeveTaskType } from "./SleeveTaskTypesEnum";
import { IPlayer } from "../IPlayer";
import { Person,
ITaskTracker,
createTaskTracker } from "../Person";
import {
Person,
ITaskTracker,
createTaskTracker
} from "../Person";
import { Augmentation } from "../../Augmentation/Augmentation";
@@ -44,6 +46,11 @@ export class Sleeve extends Person {
return Generic_fromJSON(Sleeve, value.data);
}
/**
* Stores the name of the class that the player is currently taking
*/
className: string = "";
/**
* Stores the type of crime the sleeve is currently attempting
* Must match the name of a Crime object
@@ -493,6 +500,7 @@ export class Sleeve extends Person {
break;
case SleeveTaskType.Class:
case SleeveTaskType.Gym:
this.updateTaskGainRates(p);
retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
break;
@@ -560,6 +568,7 @@ export class Sleeve extends Person {
this.crimeType = "";
this.currentTaskLocation = "";
this.gymStatType = "";
this.className = "";
}
shockRecovery(p: IPlayer): boolean {
@@ -597,67 +606,50 @@ export class Sleeve extends Person {
// Set exp/money multipliers based on which university.
// Also check that the sleeve is in the right city
let costMult: number = 1;
let expMult: number = 1;
switch (universityName.toLowerCase()) {
case LocationName.AevumSummitUniversity.toLowerCase():
if (this.city !== CityName.Aevum) { return false; }
this.currentTaskLocation = LocationName.AevumSummitUniversity;
costMult = 4;
expMult = 3;
break;
case LocationName.Sector12RothmanUniversity.toLowerCase():
if (this.city !== CityName.Sector12) { return false; }
this.currentTaskLocation = LocationName.Sector12RothmanUniversity;
costMult = 3;
expMult = 2;
break;
case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():
if (this.city !== CityName.Volhaven) { return false; }
this.currentTaskLocation = LocationName.VolhavenZBInstituteOfTechnology;
costMult = 5;
expMult = 4;
break;
default:
return false;
}
// Set experience/money gains based on class
// TODO Refactor University Courses into its own class or something
const baseStudyComputerScienceExp: number = 0.5;
const baseDataStructuresExp: number = 1;
const baseNetworksExp: number = 2;
const baseAlgorithmsExp: number = 4;
const baseManagementExp: number = 2;
const baseLeadershipExp: number = 4;
switch (className.toLowerCase()) {
case "study computer science":
this.gainRatesForTask.hack = (baseStudyComputerScienceExp * expMult * this.hacking_exp_mult);
break;
case "data structures":
this.gainRatesForTask.hack = (baseDataStructuresExp * expMult * this.hacking_exp_mult);
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassDataStructuresBaseCost * costMult);
break;
case "networks":
this.gainRatesForTask.hack = (baseNetworksExp * expMult * this.hacking_exp_mult);
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassNetworksBaseCost * costMult);
break;
case "algorithms":
this.gainRatesForTask.hack = (baseAlgorithmsExp * expMult * this.hacking_exp_mult);
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassAlgorithmsBaseCost * costMult);
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassAlgorithmsBaseCost * costMult);
break;
case "management":
this.gainRatesForTask.cha = (baseManagementExp * expMult * this.charisma_exp_mult);
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassManagementBaseCost * costMult);
break;
case "leadership":
this.gainRatesForTask.cha = (baseLeadershipExp * expMult * this.charisma_exp_mult);
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassLeadershipBaseCost * costMult);
break;
default:
return false;
}
this.className = className;
this.currentTask = SleeveTaskType.Class;
return true;
}
@@ -672,6 +664,112 @@ export class Sleeve extends Person {
return true;
}
tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean {
if (!p.canAfford(aug.startingCost)) {
return false;
}
p.loseMoney(aug.startingCost);
this.installAugmentation(aug);
return true;
}
updateTaskGainRates(p: IPlayer): void {
if (this.currentTask === SleeveTaskType.Class) {
let expMult: number = 1;
switch (this.currentTaskLocation.toLowerCase()) {
case LocationName.AevumSummitUniversity.toLowerCase():
expMult = 3;
break;
case LocationName.Sector12RothmanUniversity.toLowerCase():
expMult = 2;
break;
case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():
expMult = 4;
break;
default:
return;
}
const totalExpMult = expMult * p.hashManager.getStudyMult();
switch (this.className.toLowerCase()) {
case "study computer science":
this.gainRatesForTask.hack = (CONSTANTS.ClassStudyComputerScienceBaseExp * totalExpMult * this.hacking_exp_mult);
break;
case "data structures":
this.gainRatesForTask.hack = (CONSTANTS.ClassDataStructuresBaseExp * totalExpMult * this.hacking_exp_mult);
break;
case "networks":
this.gainRatesForTask.hack = (CONSTANTS.ClassNetworksBaseExp * totalExpMult * this.hacking_exp_mult);
break;
case "algorithms":
this.gainRatesForTask.hack = (CONSTANTS.ClassAlgorithmsBaseExp * totalExpMult * this.hacking_exp_mult);
break;
case "management":
this.gainRatesForTask.cha = (CONSTANTS.ClassManagementBaseExp * totalExpMult * this.charisma_exp_mult);
break;
case "leadership":
this.gainRatesForTask.cha = (CONSTANTS.ClassLeadershipBaseExp * totalExpMult * this.charisma_exp_mult);
break;
default:
break;
}
return;
}
if (this.currentTask === SleeveTaskType.Gym) {
// Get gym exp multiplier
let expMult: number = 1;
switch (this.currentTaskLocation.toLowerCase()) {
case LocationName.AevumCrushFitnessGym.toLowerCase():
expMult = 2;
break;
case LocationName.AevumSnapFitnessGym.toLowerCase():
expMult = 5;
break;
case LocationName.Sector12IronGym.toLowerCase():
expMult = 1;
break;
case LocationName.Sector12PowerhouseGym.toLowerCase():
expMult = 10;
break;
case LocationName.VolhavenMilleniumFitnessGym:
expMult = 4;
break;
default:
return;
}
// Set stat gain rate
const baseGymExp: number = 1;
const totalExpMultiplier = p.hashManager.getTrainingMult() * expMult;
const sanitizedStat: string = this.gymStatType.toLowerCase();
if (sanitizedStat.includes("str")) {
this.gainRatesForTask.str = (baseGymExp * totalExpMultiplier * this.strength_exp_mult);
} else if (sanitizedStat.includes("def")) {
this.gainRatesForTask.def = (baseGymExp * totalExpMultiplier * this.defense_exp_mult);
} else if (sanitizedStat.includes("dex")) {
this.gainRatesForTask.dex = (baseGymExp * totalExpMultiplier * this.dexterity_exp_mult);
} else if (sanitizedStat.includes("agi")) {
this.gainRatesForTask.agi = (baseGymExp * totalExpMultiplier * this.agility_exp_mult);
}
return;
}
console.warn(`Sleeve.updateTaskGainRates() called for unexpected task type ${this.currentTask}`);
}
upgradeMemory(n: number): void {
if (n < 0) {
console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`);
return;
}
this.memory = Math.min(100, Math.round(this.memory + n));
}
/**
* Start work for one of the player's companies
* Returns boolean indicating success
@@ -698,27 +796,27 @@ export class Sleeve extends Person {
this.gainRatesForTask.hack = companyPosition.hackingExpGain *
company.expMultiplier *
this.hacking_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.str = companyPosition.strengthExpGain *
company.expMultiplier *
this.strength_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.def = companyPosition.defenseExpGain *
company.expMultiplier *
this.defense_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.dex = companyPosition.dexterityExpGain *
company.expMultiplier *
this.dexterity_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.agi = companyPosition.agilityExpGain *
company.expMultiplier *
this.agility_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.cha = companyPosition.charismaExpGain *
company.expMultiplier *
this.charisma_exp_mult *
BitNodeMultipliers.FactionWorkExpGain;
BitNodeMultipliers.CompanyWorkExpGain;
this.currentTaskLocation = companyName;
this.currentTask = SleeveTaskType.Company;
@@ -792,60 +890,48 @@ export class Sleeve extends Person {
// Set exp/money multipliers based on which university.
// Also check that the sleeve is in the right city
let costMult: number = 1;
let expMult: number = 1;
switch (gymName.toLowerCase()) {
case LocationName.AevumCrushFitnessGym.toLowerCase():
if (this.city != CityName.Aevum) { return false; }
this.currentTaskLocation = LocationName.AevumCrushFitnessGym;
costMult = 3;
expMult = 2;
break;
case LocationName.AevumSnapFitnessGym.toLowerCase():
if (this.city != CityName.Aevum) { return false; }
this.currentTaskLocation = LocationName.AevumSnapFitnessGym;
costMult = 10;
expMult = 5;
break;
case LocationName.Sector12IronGym.toLowerCase():
if (this.city != CityName.Sector12) { return false; }
this.currentTaskLocation = LocationName.Sector12IronGym;
costMult = 1;
expMult = 1;
break;
case LocationName.Sector12PowerhouseGym.toLowerCase():
if (this.city != CityName.Sector12) { return false; }
this.currentTaskLocation = LocationName.Sector12PowerhouseGym;
costMult = 20;
expMult = 10;
break;
case LocationName.VolhavenMilleniumFitnessGym:
if (this.city != CityName.Volhaven) { return false; }
this.currentTaskLocation = LocationName.VolhavenMilleniumFitnessGym;
costMult = 7;
expMult = 4;
break;
default:
return false;
}
// Set experience/money gains based on class
// TODO Refactor University Courses into its own class or something
const baseGymExp: number = 1;
const sanitizedStat: string = stat.toLowerCase();
// Set cost
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassGymBaseCost * costMult);
// Set stat gain rate
if (sanitizedStat.includes("str")) {
this.gainRatesForTask.str = (baseGymExp * expMult);
} else if (sanitizedStat.includes("def")) {
this.gainRatesForTask.def = (baseGymExp * expMult);
} else if (sanitizedStat.includes("dex")) {
this.gainRatesForTask.dex = (baseGymExp * expMult);
} else if (sanitizedStat.includes("agi")) {
this.gainRatesForTask.agi = (baseGymExp * expMult);
} else {
// Validate "stat" argument
if (!sanitizedStat.includes("str")
&& !sanitizedStat.includes("def")
&& !sanitizedStat.includes("dex")
&& !sanitizedStat.includes("agi")) {
return false;
}
@@ -855,25 +941,6 @@ export class Sleeve extends Person {
return true;
}
tryBuyAugmentation(p: IPlayer, aug: Augmentation): boolean {
if (!p.canAfford(aug.startingCost)) {
return false;
}
p.loseMoney(aug.startingCost);
this.installAugmentation(aug);
return true;
}
upgradeMemory(n: number): void {
if (n < 0) {
console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`);
return;
}
this.memory = Math.min(100, Math.round(this.memory + n));
}
/**
* Serialize the current object to a JSON save state.
*/

View File

@@ -566,6 +566,11 @@ function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSlee
// First selector has class type
for (let i = 0; i < universitySelectorOptions.length; ++i) {
elems.taskDetailsSelector!.add(createOptionElement(universitySelectorOptions[i]));
// Set initial value
if (sleeve.className === universitySelectorOptions[i]) {
elems.taskDetailsSelector!.selectedIndex = i;
}
}
// Second selector has which university

View File

@@ -41,7 +41,6 @@ function getNumAvailableCreateProgram() {
if (Player.firstProgramAvailable === false && count > 0) {
Player.firstProgramAvailable = true;
document.getElementById("create-program-tab").style.display = "list-item";
document.getElementById("hacking-menu-header").click();
document.getElementById("hacking-menu-header").click();
}

View File

@@ -5,8 +5,9 @@ import { BitNodes } from "./BitNode/BitNode";
import { Engine } from "./engine";
import { Player } from "./Player";
import { prestigeSourceFile } from "./Prestige";
import { SourceFiles } from "./SourceFile/SourceFiles";
import { PlayerOwnedSourceFile } from "./SourceFile/PlayerOwnedSourceFile";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { SourceFiles } from "./SourceFile/SourceFiles";
import { Terminal } from "./Terminal";
import { setTimeoutRef } from "./utils/SetTimeoutRef";
@@ -138,15 +139,25 @@ function giveSourceFile(bitNodeNumber) {
}
}
// Keeps track of what Source-Files the player will have AFTER the current bitnode
// is destroyed. Updated every time loadBitVerse() is called
let nextSourceFileFlags = [];
function loadBitVerse(destroyedBitNodeNum, flume=false) {
// Clear the screen
var container = document.getElementById("red-pill-content");
const container = document.getElementById("red-pill-content");
removeChildrenFromElement(container);
// Update NextSourceFileFlags
nextSourceFileFlags = SourceFileFlags.slice();
if (!flume) {
++nextSourceFileFlags[destroyedBitNodeNum];
}
// Create the Bit Verse
var bitVerseImage = document.createElement("pre");
var bitNodes = [];
for (var i = 1; i <= 12; ++i) {
const bitVerseImage = document.createElement("pre");
const bitNodes = [];
for (let i = 1; i <= 12; ++i) {
bitNodes.push(createBitNode(i));
}
@@ -177,53 +188,29 @@ function loadBitVerse(destroyedBitNodeNum, flume=false) {
" | | | | | | | | <br>" +
" \\JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ <br><br><br><br>";
/*
" O <br>" +
" | O O | O O | <br>" +
" O | | / __| \ | | O <br>" +
" O | O | | O / | O | | O | O <br>" +
" | | | | |_/ |/ | \_ \_| | | | | <br>" +
" O | | | O | | O__/ | / \__ | | O | | | O <br>" +
" | | | | | | | / /| O / \| | | | | | | <br>" +
"O | | | \| | O / _/ | / O | |/ | | | O<br>" +
"| | | |O / | | O / | O O | | \ O| | | |<br>" +
"| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |<br>" +
" \| O | |_/ |\| \ O \__| \_| | O |/ <br>" +
" | | |_/ | | \| / | \_| | | <br>" +
" \| / \| | / / \ |/ <br>" +
" | O | | / | O | <br>" +
" O | | | | | | | O <br>" +
" | | | / / \ \ | | | <br>" +
" \| | / O / \ O \ | |/ <br>" +
" \ | / / | | \ \ | / <br>" +
" \ \JUMP O3R | | | | | | R3O PMUJ/ / <br>" +
" \|| | | | | | | | | ||/ <br>" +
" \| \_ | | | | | | _/ |/ <br>" +
" \ \| / \ / \ |/ / <br>" +
" O |/ O | | O \| O <br>" +
" | | | | | | | | <br>" +
" \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ <br>";
*/
container.appendChild(bitVerseImage);
// Bit node event listeners
for (var i = 1; i <= 12; ++i) {
// BitNode event listeners
for (let i = 1; i <= 12; ++i) {
(function(i) {
var elemId = "bitnode-" + i.toString();
var elem = clearEventListeners(elemId);
if (elem == null) {return;}
const elemId = "bitnode-" + i.toString();
const elem = clearEventListeners(elemId);
if (elem == null) { return; }
if (i >= 1 && i <= 12) {
elem.addEventListener("click", function() {
var bitNodeKey = "BitNode" + i;
var bitNode = BitNodes[bitNodeKey];
const bitNodeKey = "BitNode" + i;
const bitNode = BitNodes[bitNodeKey];
if (bitNode == null) {
console.log("ERROR: Could not find BitNode object for number: " + i);
console.error(`Could not find BitNode object for number: ${i}`);
return;
}
yesNoBoxCreate("BitNode-" + i + ": " + bitNode.name + "<br><br>" + bitNode.info);
createBitNodeYesNoEventListeners(i, destroyedBitNodeNum, flume);
const maxSourceFileLevel = i === 12 ? "∞" : "3";
const popupBoxText = `BitNode-${i}: ${bitNode.name}<br>` +
`Source-File Level: ${nextSourceFileFlags[i]} / ${maxSourceFileLevel}<br><br>` +
`${bitNode.info}`;
yesNoBoxCreate(popupBoxText);
createBitNodeYesNoEventListener(i, destroyedBitNodeNum, flume);
});
} else {
elem.addEventListener("click", function() {
@@ -284,18 +271,28 @@ function loadBitVerse(destroyedBitNodeNum, flume=false) {
// Returns string with DOM element for Bit Node
function createBitNode(n) {
var bitNodeStr = "BitNode" + n.toString();
var bitNode = BitNodes[bitNodeStr];
if (bitNode == null) {return "O";}
return "<a class='bitnode tooltip' id='bitnode-" + bitNode.number.toString() + "'><strong>O</strong>" +
"<span class='tooltiptext'>" +
"<strong>BitNode-" + bitNode.number.toString() + "<br>" + bitNode.name+ "</strong><br>" +
bitNode.desc + "<br>" +
"</span></a>";
const bitNodeStr = "BitNode" + n.toString();
const bitNode = BitNodes[bitNodeStr];
if (bitNode == null) { return "O"; }
const level = nextSourceFileFlags[n];
let cssClass;
if (n === 12 && level >= 2) {
// Repeating BitNode
cssClass = "level-2";
} else {
cssClass = `level-${level}`;
}
return `<a class='bitnode ${cssClass} tooltip' id='bitnode-${bitNode.number}'><strong>O</strong>` +
"<span class='tooltiptext'>" +
`<strong>BitNode-${bitNode.number.toString()}<br>${bitNode.name}</strong><br>` +
`${bitNode.desc}<br>` +
"</span></a>";
}
function createBitNodeYesNoEventListeners(newBitNode, destroyedBitNode, flume=false) {
var yesBtn = yesNoBoxGetYesButton();
function createBitNodeYesNoEventListener(newBitNode, destroyedBitNode, flume=false) {
const yesBtn = yesNoBoxGetYesButton();
yesBtn.innerHTML = "Enter BitNode-" + newBitNode;
yesBtn.addEventListener("click", function() {
if (!flume) {
@@ -311,7 +308,7 @@ function createBitNodeYesNoEventListeners(newBitNode, destroyedBitNode, flume=fa
// Set new Bit Node
Player.bitNodeN = newBitNode;
console.log("Entering Bit Node " + Player.bitNodeN);
console.log(`Entering Bit Node ${Player.bitNodeN}`);
// Reenable terminal
$("#hack-progress-bar").attr('id', "old-hack-progress-bar");
@@ -324,7 +321,7 @@ function createBitNodeYesNoEventListeners(newBitNode, destroyedBitNode, flume=fa
prestigeSourceFile();
yesNoBoxClose();
});
var noBtn = yesNoBoxGetNoButton();
const noBtn = yesNoBoxGetNoButton();
noBtn.innerHTML = "Back";
noBtn.addEventListener("click", function() {
yesNoBoxClose();

View File

@@ -6,11 +6,11 @@
* the way
*/
import * as walk from "acorn-walk";
import { parse, Node } from "acorn";
import { RamCalculationErrorCode } from "./RamCalculationErrorCodes";
import { RamCosts, RamCostConstants } from "../Netscript/RamCostGenerator";
import { parse, Node } from "../../utils/acorn";
// These special strings are used to reference the presence of a given logical
// construct within a user script.
@@ -215,7 +215,7 @@ async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
* that need to be parsed (i.e. are 'import'ed scripts).
*/
function parseOnlyCalculateDeps(code, currentModule) {
const ast = parse(code, {sourceType:"module", ecmaVersion: 8});
const ast = parse(code, {sourceType:"module", ecmaVersion: 9});
// Everything from the global scope goes in ".". Everything else goes in ".function", where only
// the outermost layer of functions counts.

View File

@@ -62,7 +62,7 @@ let NetscriptFunctions =
"hack|hackAnalyzeThreads|hackAnalyzePercent|hackChance|" +
"sleep|grow|weaken|growthAnalyze|print|tprint|scan|nuke|brutessh|" +
"ftpcrack|" +
"clearLog|disableLog|enableLog|isLogEnabled|getScriptLogs|" +
"clearLog|disableLog|enableLog|isLogEnabled|getScriptLogs|tail|" +
"relaysmtp|httpworm|sqlinject|run|exec|spawn|kill|killall|exit|" +
"scp|ls|ps|hasRootAccess|" +
"getIp|getHackingMultipliers|getBitNodeMultipliers|getStats|isBusy|" +

View File

@@ -37,7 +37,7 @@
if (!options.indent) // JSHint error.character actually is a column index, this fixes underlining on lines using tabs for indentation
options.indent = 1; // JSHint default value is 4
options.esversion = 6;
options.esversion = 9;
JSHINT(sanitizedText, options, options.globals);
var errors = JSHINT.data().errors, result = [];

View File

@@ -58,6 +58,7 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) {
"enableLog": atom,
"isLogEnabled": atom,
"getScriptLogs": atom,
"tail": atom,
"relaysmtp": atom,
"httpworm": atom,
"sqlinject": atom,

View File

@@ -12,6 +12,7 @@ import { IReturnStatus } from "../types";
import { isScriptFilename } from "../Script/ScriptHelpersTS";
import { createRandomIp } from "../../utils/IPAddress";
import { compareArrays } from "../../utils/helpers/compareArrays";
interface IConstructorParams {
adminRights?: boolean;
@@ -113,8 +114,27 @@ export class BaseServer {
return null;
}
// Given the name of the script, returns the corresponding
// script object on the server (if it exists)
/**
* Find an actively running script on this server
* @param scriptName - Filename of script to search for
* @param scriptArgs - Arguments that script is being run with
* @returns RunningScript for the specified active script
* Returns null if no such script can be found
*/
getRunningScript(scriptName: string, scriptArgs: any[]): RunningScript | null {
for (let rs of this.runningScripts) {
if (rs.filename === scriptName && compareArrays(rs.args, scriptArgs)) {
return rs;
}
}
return null;
}
/**
* Given the name of the script, returns the corresponding
* Script object on the server (if it exists)
*/
getScript(scriptName: string): Script | null {
for (let i = 0; i < this.scripts.length; i++) {
if (this.scripts[i].filename === scriptName) {
@@ -129,7 +149,6 @@ export class BaseServer {
* Returns boolean indicating whether the given script is running on this server
*/
isRunning(fn: string): boolean {
// Check that the script isnt currently running
for (const runningScriptObj of this.runningScripts) {
if (runningScriptObj.filename === fn) {
return true;
@@ -157,7 +176,7 @@ export class BaseServer {
* @returns {IReturnStatus} Return status object indicating whether or not file was deleted
*/
removeFile(fn: string): IReturnStatus {
if (fn.endsWith(".exe")) {
if (fn.endsWith(".exe") || fn.match(/^.+\.exe-\d+(?:\.\d*)?%-INC$/) != null) {
for (let i = 0; i < this.programs.length; ++i) {
if (this.programs[i] === fn) {
this.programs.splice(i, 1);

View File

@@ -11,6 +11,7 @@ import { HacknetServer } from "../Hacknet/HacknetServer";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Programs } from "../Programs/Programs";
import { isValidNumber } from "../utils/helpers/isValidNumber";
import { isValidIPAddress } from "../../utils/helpers/isValidIPAddress";
/**
@@ -35,11 +36,17 @@ export function safetlyCreateUniqueServer(params: IConstructorParams): Server {
return new Server(params);
}
// Returns the number of cycles needed to grow the specified server by the
// specified amount. 'growth' parameter is in decimal form, not percentage
/**
* Returns the number of "growth cycles" needed to grow the specified server by the
* specified amount.
* @param server - Server being grown
* @param growth - How much the server is being grown by, in DECIMAL form (e.g. 1.5 rather than 50)
* @param p - Reference to Player object
* @returns Number of "growth cycles" needed
*/
export function numCycleForGrowth(server: Server, growth: number, p: IPlayer) {
let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty;
if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) {
if (ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) {
ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate;
}
@@ -75,12 +82,12 @@ export function processSingleServerGrowth(server: Server, numCycles: number, p:
server.moneyAvailable *= serverGrowth;
// in case of data corruption
if (server.moneyMax && isNaN(server.moneyAvailable)) {
if (isValidNumber(server.moneyMax) && isNaN(server.moneyAvailable)) {
server.moneyAvailable = server.moneyMax;
}
// cap at max
if (server.moneyMax && server.moneyAvailable > server.moneyMax) {
if (isValidNumber(server.moneyMax) && server.moneyAvailable > server.moneyMax) {
server.moneyAvailable = server.moneyMax;
}

View File

@@ -67,6 +67,7 @@ export enum CodeMirrorThemeSetting {
Seti = "seti",
Shadowfox = "shadowfox",
Solarized = "solarized",
SolarizedDark = "solarized dark",
ssms = "ssms",
The_matrix = "the-matrix",
Tomorrow_night_bright = "tomorrow-night-bright",

View File

@@ -20,6 +20,7 @@ import { CONSTANTS } from "../Constants";
import { WorkerScript } from "../Netscript/WorkerScript";
import { Player } from "../Player";
import { IMap } from "../types";
import { EventEmitter } from "../utils/EventEmitter";
import { Page, routing } from ".././ui/navigationTracking";
import { numeralWrapper } from ".././ui/numeralFormat";
@@ -290,11 +291,15 @@ function initStockMarketFnForReact() {
initSymbolToStockMap();
}
const eventEmitterForUiReset = new EventEmitter();
export function displayStockMarketContent() {
if (!routing.isOn(Page.StockMarket)) {
return;
}
eventEmitterForUiReset.emitEvent();
if (stockMarketContainer instanceof HTMLElement) {
const castedStockMarket = StockMarket as IStockMarket;
ReactDOM.render(
@@ -302,6 +307,7 @@ export function displayStockMarketContent() {
buyStockLong={buyStock}
buyStockShort={shortStock}
cancelOrder={cancelOrder}
eventEmitterForReset={eventEmitterForUiReset}
initStockMarket={initStockMarketFnForReact}
p={Player}
placeOrder={placeOrder}

View File

@@ -12,6 +12,7 @@ import { OrderTypes } from "../data/OrderTypes";
import { PositionTypes } from "../data/PositionTypes";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { EventEmitter } from "../../utils/EventEmitter";
type txFn = (stock: Stock, shares: number) => boolean;
export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean;
@@ -20,6 +21,7 @@ type IProps = {
buyStockLong: txFn;
buyStockShort: txFn;
cancelOrder: (params: object) => void;
eventEmitterForReset?: EventEmitter;
initStockMarket: () => void;
p: IPlayer;
placeOrder: placeOrderFn;
@@ -65,6 +67,7 @@ export class StockMarketRoot extends React.Component<IProps, IState> {
buyStockLong={this.props.buyStockLong}
buyStockShort={this.props.buyStockShort}
cancelOrder={this.props.cancelOrder}
eventEmitterForReset={this.props.eventEmitterForReset}
p={this.props.p}
placeOrder={this.props.placeOrder}
sellStockLong={this.props.sellStockLong}

View File

@@ -9,6 +9,7 @@ import { Stock } from "../Stock";
import { TickerHeaderFormatData } from "../data/TickerHeaderFormatData";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat";
type IProps = {
@@ -16,12 +17,22 @@ type IProps = {
stock: Stock;
}
const localesWithLongPriceFormat = [
"cs",
"lv",
"pl",
"ru",
];
export function StockTickerHeaderText(props: IProps): React.ReactElement {
const stock = props.stock;
const stockPriceFormat = numeralWrapper.formatMoney(stock.price);
const spacesAllottedForStockPrice = localesWithLongPriceFormat.includes(Settings.Locale) ? 15 : 12;
const spacesAfterStockName = " ".repeat(1 + TickerHeaderFormatData.longestName - stock.name.length + (TickerHeaderFormatData.longestSymbol - stock.symbol.length));
const spacesBeforePrice = " ".repeat(spacesAllottedForStockPrice - stockPriceFormat.length);
let hdrText = `${stock.name}${" ".repeat(1 + TickerHeaderFormatData.longestName - stock.name.length + (TickerHeaderFormatData.longestSymbol - stock.symbol.length))}${stock.symbol} -${" ".repeat(10 - stockPriceFormat.length)}${stockPriceFormat}`;
let hdrText = `${stock.name}${spacesAfterStockName}${stock.symbol} -${spacesBeforePrice}${stockPriceFormat}`;
if (props.p.has4SData) {
hdrText += ` - Volatility: ${numeralWrapper.format(stock.mv, '0,0.00')}% - Price Forecast: `;
let plusOrMinus = stock.b; // True for "+", false for "-"

View File

@@ -14,6 +14,9 @@ import { OrderTypes } from "../data/OrderTypes";
import { PositionTypes } from "../data/PositionTypes";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { EventEmitter } from "../../utils/EventEmitter";
import { ErrorBoundary } from "../../ui/React/ErrorBoundary";
export type txFn = (stock: Stock, shares: number) => boolean;
export type placeOrderFn = (stock: Stock, shares: number, price: number, ordType: OrderTypes, posType: PositionTypes) => boolean;
@@ -22,6 +25,7 @@ type IProps = {
buyStockLong: txFn;
buyStockShort: txFn;
cancelOrder: (params: object) => void;
eventEmitterForReset?: EventEmitter;
p: IPlayer;
placeOrder: placeOrderFn;
sellStockLong: txFn;
@@ -169,8 +173,13 @@ export class StockTickers extends React.Component<IProps, IState> {
}
}
const errorBoundaryProps = {
eventEmitterForReset: this.props.eventEmitterForReset,
id: "StockTickersErrorBoundary",
}
return (
<div>
<ErrorBoundary {...errorBoundaryProps}>
<StockTickersConfig
changeDisplayMode={this.changeDisplayMode}
changeWatchlistFilter={this.changeWatchlistFilter}
@@ -182,7 +191,7 @@ export class StockTickers extends React.Component<IProps, IState> {
<ul id="stock-market-list" ref={this.listRef}>
{tickers}
</ul>
</div>
</ErrorBoundary>
)
}
}

View File

@@ -53,7 +53,7 @@ import {
import { showLiterature } from "./Literature";
import { Message } from "./Message/Message";
import { showMessage } from "./Message/MessageHelpers";
import { addWorkerScript } from "./NetscriptWorker";
import { startWorkerScript } from "./NetscriptWorker";
import { killWorkerScript } from "./Netscript/killWorkerScript";
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
import { Player } from "./Player";
@@ -707,6 +707,11 @@ let Terminal = {
},
executeCommand : function(command) {
if (Terminal.hackFlag || Terminal.analyzeFlag) {
postError(`Cannot execute command (${command}) while an action is in progress`);
return;
}
// Process any aliases
command = substituteAliases(command);
@@ -1150,7 +1155,7 @@ let Terminal = {
killWorkerScript(s.runningScripts[i], s.ip, false);
}
WorkerScriptStartStopEventEmitter.emitEvent();
post("Killing all running scripts. May take up to a few minutes for the scripts to die...");
post("Killing all running scripts");
break;
}
case "ls": {
@@ -1573,19 +1578,32 @@ let Terminal = {
return;
}
// Kill by PID
if (typeof commandArray[1] === "number") {
const pid = commandArray[1];
const res = killWorkerScript(pid);
if (res) {
post(`Killing script with PID ${pid}`);
} else {
post(`Failed to kill script with PID ${pid}. No such script exists`);
}
return;
}
const s = Player.getCurrentServer();
const scriptName = Terminal.getFilepath(commandArray[1]);
const args = [];
for (let i = 2; i < commandArray.length; ++i) {
args.push(commandArray[i]);
}
const runningScript = findRunningScript(scriptName, args, s);
const runningScript = s.getRunningScript(scriptName, args);
if (runningScript == null) {
postError("No such script is running. Nothing to kill");
return;
}
killWorkerScript(runningScript, s.ip);
post(`Killing ${scriptName}. May take up to a few seconds for the scripts to die...`);
post(`Killing ${scriptName}`);
} catch(e) {
Terminal.postThrownError(e);
}
@@ -2290,22 +2308,19 @@ let Terminal = {
return;
} else {
// Able to run script
post("Running script with " + numThreads + " thread(s) and args: " + arrayToString(args) + ".");
post("May take a few seconds to start up the process...");
var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = numThreads;
addWorkerScript(runningScriptObj, server);
// This has to come after addWorkerScript() because that fn
// updates the RAM usage. This kinda sucks, address if possible
server.runScript(runningScriptObj, Player.hacknet_node_money_mult);
return;
if (startWorkerScript(runningScriptObj, server)) {
post("Running script with " + numThreads + " thread(s) and args: " + arrayToString(args) + ".");
} else {
postError(`Failed to start script`);
}
return;
}
}
}
post("ERROR: No such script");
},

View File

@@ -36,9 +36,9 @@ export function removeTrailingSlash(s: string): string {
* not the entire filepath
*/
export function isValidFilename(filename: string): boolean {
// Allows alphanumerics, hyphens, underscores.
// Allows alphanumerics, hyphens, underscores, and percentage signs
// Must have a file extension
const regex = /^[.a-zA-Z0-9_-]+[.][.a-zA-Z0-9]+$/;
const regex = /^[.a-zA-Z0-9_-]+[.][a-zA-Z0-9]+(?:-\d+(?:\.\d*)?%-INC)?$/;
// match() returns null if no match is found
return filename.match(regex) != null;
@@ -49,7 +49,7 @@ export function isValidFilename(filename: string): boolean {
* not an entire path
*/
export function isValidDirectoryName(name: string): boolean {
// Allows alphanumerics, hyphens and underscores.
// Allows alphanumerics, hyphens, underscores, and percentage signs.
// Name can begin with a single period, but otherwise cannot have any
const regex = /^.?[a-zA-Z0-9_-]+$/;

View File

@@ -20,7 +20,7 @@ export const TerminalHelpText: string =
"home Connect to home computer<br>" +
"hostname Displays the hostname of the machine<br>" +
"ifconfig Displays the IP address of the machine<br>" +
"kill [script] [args...] Stops the specified script on the current server <br>" +
"kill [script/pid] [args...] Stops the specified script on the current server <br>" +
"killall Stops all running scripts on the current machine<br>" +
"ls [dir] [| grep pattern] Displays all files on the machine<br>" +
"lscpu Displays the number of CPU cores on the machine<br>" +
@@ -128,15 +128,16 @@ export const HelpTexts: IMap<string> = {
ifconfig: "ipconfig<br>" +
"Prints the IP address of the current server",
kill: "kill [script name] [args...]<br>" +
"Kill the script specified by the script name and arguments. Each argument must be separated by " +
"a space. Remember that a running script is uniquely identified by " +
"both its name and the arguments that are used to start it. So, if a script was ran with the following arguments:<br><br>" +
"kill [pid]<br>" +
"Kill the script specified by the script name and arguments OR by its PID.<br><br>" +
"If you are killing the script using its filename and arguments, then each " +
"argument must be separated by a space. Remember that a running script is " +
"uniquely identified by both its name and the arguments that are used to start " +
"it. So, if a script was ran with the following arguments:<br><br>" +
"run foo.script 1 sigma-cosmetics<br><br>" +
"Then to kill this script the same arguments would have to be used:<br><br>" +
"kill foo.script 1 sigma-cosmetics<br><br>" +
"Note that after issuing the 'kill' command for a script, it may take a while for the script to actually stop running. " +
"This will happen if the script is in the middle of a command such as grow() or weaken() that takes time to execute. " +
"The script will not be stopped/killed until after that time has elapsed.",
"If you are killing the script using its PID, then the PID argument must be numeric",
killall: "killall<br>" +
"Kills all scripts on the current server. " +
"Note that after the 'kill' command is issued for a script, it may take a while for the script to actually stop running. " +
@@ -168,7 +169,7 @@ export const HelpTexts: IMap<string> = {
"Move the source file to the specified destination. This can also be used to rename files. " +
"This command only works for scripts and text files (.txt). This command CANNOT be used to " +
"convert to different file types<br><br>" +
"Note that, unlike the Linux 'mv' command, the destination argument must be the " +
"Note that, unlike the Linux 'mv' command, the destination argument must be the " +
"full filepath. " +
"Examples: <br><br>" +
"mv hacking-controller.script scripts/hacking-controller.script<br>" +

View File

@@ -748,8 +748,8 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"the result should be an array with only an empty string.\n\n",
"IMPORTANT: The string may contain letters, not just parentheses.",
`Examples:\n`,
`"()())()" -> ["()()()", "(())()"]\n`,
`"(a)())()" -> ["(a)()()", "(a())()"]\n`,
`"()())()" -> [()()(), (())()]\n`,
`"(a)())()" -> [(a)()(), (a())()]\n`,
`")( -> [""]`].join(" ");
},
difficulty: 10,
@@ -841,11 +841,13 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"The data provided by this problem is an array with two elements. The first element",
"is the string of digits, while the second element is the target number:\n\n",
`["${digits}", ${target}]\n\n`,
"NOTE: Numbers in the expression cannot have leading 0's. In other words,",
`"1+01" is not a valid expression`,
"Examples:\n\n",
`Input: digits = "123", target = 6\n`,
`Output: ["1+2+3", "1*2*3"]\n\n`,
`Output: [1+2+3, 1*2*3]\n\n`,
`Input: digits = "105", target = 5\n`,
`Output: ["1*0+5", "10-5"]`].join(" ");
`Output: [1*0+5, 10-5]`].join(" ");
},
difficulty: 10,
gen: () => {

View File

@@ -25,7 +25,6 @@ export function WorkerScriptAccordion(props: IProps): React.ReactElement {
const workerScript = props.workerScript;
const scriptRef = workerScript.scriptRef;
const logClickHandler = logBoxCreate.bind(null, scriptRef);
const killScript = killWorkerScript.bind(null, scriptRef, scriptRef.server);

View File

@@ -66,6 +66,8 @@ export function initializeMainMenuHeaders(p: IPlayer, dev: boolean=false): boole
const createProgram: HTMLElement = safeGetElement("create-program-tab");
const createProgramNot: HTMLElement = safeGetElement("create-program-notification");
createProgram.style.display = p.firstProgramAvailable ? "list-item" : "none";
this.classList.toggle("opened");
const elems: HTMLElement[] = [terminal, createScript, activeScripts, createProgram];
@@ -87,7 +89,7 @@ export function initializeMainMenuHeaders(p: IPlayer, dev: boolean=false): boole
const sleeves: HTMLElement = safeGetElement("sleeves-tab");
sleeves.style.display = p.sleeves.length > 0 ? "list-item" : "none";
this.classList.toggle("opened");
const elems: HTMLElement[] = [stats, factions, augmentations, hacknetnodes, sleeves];

View File

@@ -0,0 +1,103 @@
/**
* React Component for a simple Error Boundary. The fallback UI for
* this error boundary is simply a bordered text box
*/
import * as React from "react";
import { EventEmitter } from "../../utils/EventEmitter";
type IProps = {
eventEmitterForReset?: EventEmitter;
id?: string;
};
type IState = {
errorInfo: string;
hasError: boolean;
}
type IErrorInfo = {
componentStack: string;
}
// TODO: Move this out to a css file
const styleMarkup = {
border: "1px solid red",
display: "inline-block",
margin: "4px",
padding: "4px",
}
export class ErrorBoundary extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
errorInfo: "",
hasError: false,
}
}
static getDerivedStateFromError(error: Error) {
return {
errorInfo: error.message,
hasError: true,
};
}
componentDidCatch(error: Error, info: IErrorInfo) {
console.error(`Caught error in React ErrorBoundary. Component stack:`);
console.error(info.componentStack);
}
componentDidMount() {
const cb = () => {
this.setState({
hasError: false,
});
}
if (this.hasEventEmitter()) {
this.props.eventEmitterForReset!.addSubscriber({
cb: cb,
id: this.props.id!,
});
}
}
componentWillUnmount() {
if (this.hasEventEmitter()) {
this.props.eventEmitterForReset!.removeSubscriber(this.props.id!);
}
}
hasEventEmitter(): boolean {
return this.props.eventEmitterForReset instanceof EventEmitter
&& typeof this.props.id === "string";
}
render() {
if (this.state.hasError) {
return (
<div style={styleMarkup}>
<p>
{
`Error rendering UI. This is (probably) a bug. Please report to game developer.`
}
</p>
<p>
{
`In the meantime, try refreshing the game WITHOUT saving.`
}
</p>
<p>
{
`Error info: ${this.state.errorInfo}`
}
</p>
</div>
)
}
return this.props.children;
}
}

View File

@@ -0,0 +1,7 @@
/**
* Checks that a variable is a valid number. A valid number
* must be a "number" type and cannot be NaN
*/
export function isValidNumber(n: number): boolean {
return (typeof n === "number") && !isNaN(n);
}

View File

@@ -67,6 +67,10 @@ describe("Terminal Directory Tests", function() {
expect(isValidFilename("_foo.lit")).to.equal(true);
expect(isValidFilename("mult.periods.script")).to.equal(true);
expect(isValidFilename("mult.per-iods.again.script")).to.equal(true);
expect(isValidFilename("BruteSSH.exe-50%-INC")).to.equal(true);
expect(isValidFilename("DeepscanV1.exe-1.01%-INC")).to.equal(true);
expect(isValidFilename("DeepscanV2.exe-1.00%-INC")).to.equal(true);
expect(isValidFilename("AutoLink.exe-1.%-INC")).to.equal(true);
});
it("should return false for invalid filenames", function() {
@@ -79,6 +83,10 @@ describe("Terminal Directory Tests", function() {
expect(isValidFilename("foo._script")).to.equal(false);
expect(isValidFilename("foo.hyphened-ext")).to.equal(false);
expect(isValidFilename("")).to.equal(false);
expect(isValidFilename("AutoLink-1.%-INC.exe")).to.equal(false);
expect(isValidFilename("AutoLink.exe-1.%-INC.exe")).to.equal(false);
expect(isValidFilename("foo%.exe")).to.equal(false);
expect(isValidFilename("-1.00%-INC")).to.equal(false);
});
});

File diff suppressed because it is too large Load Diff