Compare commits

..

6 Commits

Author SHA1 Message Date
hydroflame
135df8703c V0.51.4 (#847)
* BladeBurner
    * nerfed int exp gained.

    Documentation
    * purchaseServer specifies what happens on failure.
    * Fixed typo in recommended bitnode page.
    * Removed misleading ram requirements for hacking factions.

    Netscript
    * growthAnalyze handles Infinity correctly.

    Misc.
    * Faction Augmentation will list how much reputation is required even after
      that goal has been reached.
    * Removed dollar sign in travel agency confirmation.
    * Fixed typo in alpha-omega.lit

* the game save text no longer obstruct the save game and options button

* the text editors now remember where your cursor was and restores it when loading the same script again.

* v0.51.4
2021-04-19 21:26:51 -04:00
hydroflame
4743801e86 hotfix (#846) 2021-04-18 11:33:46 -04:00
hydroflame
4e5ebcfe6f V0.51.3 (#845)
v0.51.3 - 2021-04-16 Y'all broke it on the first day (hydroflame)
-------

Passive faction reputation
* Reworked, from 1 rep / 2 minute. Now is a complicated percentage of the
  reputation you'd gain working for them. It's not op but it feels a bit
  more useful.

Netscript
* print/tprint now take any number of arguments.
* print/tprint will now print object as json.
* print/tprint now handle passing in an undefined argument properly.

Casino
* Cannot bet negative money anymore.
* Roulette max bet is a bit higher.
* Coin Flip has a small cooldown.
* All buttons reject unstrusted mouse events.

Documentation
* Changed a message that said nsjs only works on Chrome.

Bugfix
* hacknet.maxNumNodes now works for both nodes and servers.
* Fixed a bug where the popup boxes would contain data from previous popup boxes.
* .js files will also have the export async function boilerplate.

Misc.
* turned off autocomplete for the terminal text input.
* Fixed an issue on Windows+Firefox where pressing up on the terminal would
  bring the cursor to the begining of the line. (Issue #836)
* Hacknet node names is easier to handle for screen readers.
* Money spent on classes is now tracked independently of work money.
* running coding contract from the terminal will display its name.
2021-04-18 11:18:56 -04:00
hydroflame
80b703639e Small hotfix (#840)
* yesno box now correctly clean up before new content is loaded in.

* formatHp doesnt display decimal, duh

* character overview uses numeralWrapper formatHp

* minor formatting stuff

* Class spending is tracked indepedently of work money

* Made an augmentation named after myself.

* hotfix a bunch of small stuff
2021-04-12 20:03:32 -04:00
hydroflame
0afdba8f38 fixed ns imports requiring semicolon (#839) 2021-04-10 02:26:13 -04:00
hydroflame
925e96345d v0.51.2 (#838)
* infiltration use buttons instead of a links

* minor accessibility patch

* Hospitalization will not cost more than 10% of the players money.

* Adde hospitalization netscript function

* Removed the suggestion that the combat path will lead to Daedalus, it still will. But new players should not be told that this is a viable path to completing a BitNode.

* getMemberInformation now returns everything about the member.

* New netscript function to get the players hacknet server hash capacity

* yesno dialog box will not keep older messages anymore

* v0.51.1

* Casino part 1

* Discord link in options, documentation for getMemberInformation updated, dev menu has more money options, tech vendors now handle max cores or max ram better

* Removed text under Factiosn referencing rejected factions.

* Removed html element forgotten in plain text

* Casino implementation

* v0.51.2
2021-04-09 18:12:31 -04:00
64 changed files with 1630 additions and 424 deletions

View File

@@ -228,14 +228,15 @@ a:visited {
} }
.status-text { .status-text {
display: inline-block;
position: fixed;
z-index: 2; z-index: 2;
-webkit-animation: status-text 3s 1; -webkit-animation: status-text 3s 1;
} }
#status-text-container { #status-text-container {
background-color: transparent; background-color: transparent;
position:absolute;
top:0;
left:50%;
} }
#status-text { #status-text {

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([396,0]),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){},361:function(n,t,o){},363:function(n,t,o){},365:function(n,t,o){},367:function(n,t,o){},369:function(n,t,o){},371:function(n,t,o){},373:function(n,t,o){},375:function(n,t,o){},377:function(n,t,o){},379:function(n,t,o){},381:function(n,t,o){},383:function(n,t,o){},385:function(n,t,o){},387:function(n,t,o){},389:function(n,t,o){},391:function(n,t,o){},393:function(n,t,o){},396:function(n,t,o){"use strict";o.r(t);o(395),o(393),o(391),o(389),o(387),o(385),o(383),o(381),o(379),o(377),o(375),o(373),o(371),o(369),o(367),o(365),o(363),o(361),o(359),o(357),o(355),o(353),o(351),o(349),o(347),o(345),o(343),o(341),o(339)}}); !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([401,0]),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){},360:function(n,t,o){},362:function(n,t,o){},364:function(n,t,o){},366:function(n,t,o){},368:function(n,t,o){},370:function(n,t,o){},372:function(n,t,o){},374:function(n,t,o){},376:function(n,t,o){},378:function(n,t,o){},380:function(n,t,o){},382:function(n,t,o){},384:function(n,t,o){},386:function(n,t,o){},388:function(n,t,o){},390:function(n,t,o){},392:function(n,t,o){},394:function(n,t,o){},396:function(n,t,o){},398:function(n,t,o){},401:function(n,t,o){"use strict";o.r(t);o(400),o(398),o(396),o(394),o(392),o(390),o(388),o(386),o(384),o(382),o(380),o(378),o(376),o(374),o(372),o(370),o(368),o(366),o(364),o(362),o(360),o(358),o(356),o(354),o(352),o(350),o(348),o(346),o(344)}});
//# sourceMappingURL=engineStyle.bundle.js.map //# sourceMappingURL=engineStyle.bundle.js.map

View File

@@ -250,13 +250,14 @@ a:visited {
opacity: 0; } } opacity: 0; } }
.status-text { .status-text {
display: inline-block;
position: fixed;
z-index: 2; z-index: 2;
-webkit-animation: status-text 3s 1; } -webkit-animation: status-text 3s 1; }
#status-text-container { #status-text-container {
background-color: transparent; } background-color: transparent;
position: absolute;
top: 0;
left: 50%; }
#status-text { #status-text {
background-color: transparent; background-color: transparent;

26
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -75,13 +75,10 @@ List of Factions and their Requirements
| | | | * Ishima | | | | | * Ishima |
+---------------------+----------------+-----------------------------------------+-------------------------------+ +---------------------+----------------+-----------------------------------------+-------------------------------+
| Hacking | NiteSec | * Hack avmnite-02h manually | | | Hacking | NiteSec | * Hack avmnite-02h manually | |
| Groups | | * Home Computer RAM of at least 32GB | |
+ +----------------+-----------------------------------------+-------------------------------+ + +----------------+-----------------------------------------+-------------------------------+
| | The Black Hand | * Hack I.I.I.I manually | | | | The Black Hand | * Hack I.I.I.I manually | |
| | | * Home Computer RAM of at least 64GB | |
+ +----------------+-----------------------------------------+-------------------------------+ + +----------------+-----------------------------------------+-------------------------------+
| | Bitrunners | * Hack run4theh111z manually | | | | Bitrunners | * Hack run4theh111z manually | |
| | | * Home Computer RAM of at least 128GB | |
+---------------------+----------------+-----------------------------------------+-------------------------------+ +---------------------+----------------+-----------------------------------------+-------------------------------+
| Megacorporations | ECorp | * Have 200k reputation with | | | Megacorporations | ECorp | * Have 200k reputation with | |
| | | the Corporation | | | | | the Corporation | |

View File

@@ -3,22 +3,116 @@
Changelog Changelog
========= =========
v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame)
-------------------------------------------------------
Manual hacking
* These bonus require an install or a soft reset to take effect.
* Manual hacking gyms and university gives you a 10% discount.
* Manual hacking a corporation server decreases the penalty for leaving work
early.
BladeBurner
* nerfed int exp gained.
Documentation
* purchaseServer specifies what happens on failure.
* Fixed typo in recommended bitnode page.
* Removed misleading ram requirements for hacking factions.
Netscript
* growthAnalyze handles Infinity correctly.
Misc.
* Faction Augmentation will list how much reputation is required even after
that goal has been reached.
* Removed dollar sign in travel agency confirmation dialog box.
* Fixed typo in alpha-omega.lit
* the 'Game saved!' text no longer blocks the save game/options button.
* The text editor now remembers the location of your cursor and restores it.
* skills are recalculated instantly.
* Fix typo in Operation Zero description.
v0.51.3 - 2021-04-16 Y'all broke it on the first day (hydroflame)
-----------------------------------------------------------------
**Passive faction reputation**
* Reworked, from 1 rep / 2 minute. Now is a complicated percentage of the
reputation you'd gain working for them. It's not op but it feels a bit
more useful.
**Netscript**
* print/tprint now take any number of arguments.
* print/tprint will now print object as json.
* print/tprint now handle passing in an undefined argument properly.
**Casino**
* Cannot bet negative money anymore.
* Roulette max bet is a bit higher.
* Coin Flip has a small cooldown.
* All buttons reject unstrusted mouse events.
**Documentation**
* Changed a message that said nsjs only works on Chrome.
**Bugfix**
* hacknet.maxNumNodes now works for both nodes and servers.
* Fixed a bug where the popup boxes would contain data from previous popup boxes.
* .js files will also have the 'export async function' boilerplate.
**Misc.**
* turned off web form autocomplete for the terminal text input.
* Fixed an issue on Windows+Firefox where pressing up on the terminal would
bring the cursor to the begining of the line. (Issue #836)
* Hacknet node names is easier to handle for screen readers.
* Money spent on classes is now tracked independently of work money.
* running coding contract from the terminal will display its name.
v0.51.2 - 2021-04-09 Vegas, Baby! (hydroflame)
----------------------------------------------
**New location: The Iker Molina Casino**
* A casino opened in Aevum. However the house is rumored to cheat. If only
we could give them a taste of their own medicine.
**Misc.**
* Link to discord added under options
* 'getMemberInformation' doc updated, oops
* tech vendor now handle max ram and cores.
v0.51.1 - 2021-04-06 Bugfixes because the author of the last patch sucks (it's hydroflame) v0.51.1 - 2021-04-06 Bugfixes because the author of the last patch sucks (it's hydroflame)
------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------
**Netscript** **Netscript**
* 'getPlayer' returns players faction and tor * 'getPlayer' returns players faction and tor
* 'hospitalization' is a new singularity function. * 'hospitalization' is a new singularity function.
* 'gang.getMemberInformation' now returns more information. * 'gang.getMemberInformation' now returns more information.
* 'hacknet.hashCapacity' is a new hacknet function that returns the maximum hash capacity. * 'hacknet.hashCapacity' is a new hacknet function that returns the maximum hash capacity.
**Hospitalization** **Hospitalization**
* Now only cost at most 10% of your money. * Now only cost at most 10% of your money.
**Bugfix** **Bugfix**
* confirmation dialog box no longer use previous text * confirmation dialog box no longer use previous text
**Accessibility** **Accessibility**
* The game is a little easier to handle for screen readers (yes, there's an * The game is a little easier to handle for screen readers (yes, there's an
absolute legend playing this game with a screen reader) absolute legend playing this game with a screen reader)
* Infiltration use buttons instead of a-links * Infiltration use buttons instead of a-links
@@ -26,6 +120,7 @@ v0.51.1 - 2021-04-06 Bugfixes because the author of the last patch sucks (it's h
map display as a list of buttons. map display as a list of buttons.
**Misc.** **Misc.**
* 'fl1ght.exe' will no longer suggest the combat path. Related faction * 'fl1ght.exe' will no longer suggest the combat path. Related faction
requirements unchanged. requirements unchanged.

View File

@@ -66,7 +66,7 @@ documentation_title = '{0} Documentation'.format(project)
# The short X.Y version. # The short X.Y version.
version = '0.51' version = '0.51'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.51.1' release = '0.51.4'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View File

@@ -278,6 +278,7 @@ Description
hashes, which can be spent on a variety of different upgrades. hashes, which can be spent on a variety of different upgrades.
In this BitNode: In this BitNode:
* Your stats are significantly decreased * Your stats are significantly decreased
* You cannnot purchase additional servers * You cannnot purchase additional servers
* Hacking is significantly less profitable * Hacking is significantly less profitable
@@ -312,6 +313,7 @@ Description
2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously 2. Duplicate Sleeves: Duplicate your consciousness into Synthoids, allowing you to perform different tasks synchronously
In this BitNode: In this BitNode:
* Your stats are significantly decreased * Your stats are significantly decreased
* All methods of gaining money are half as profitable (except Stock Market) * All methods of gaining money are half as profitable (except Stock Market)
* Purchased servers are more expensive, have less max RAM, and a lower maximum limit * Purchased servers are more expensive, have less max RAM, and a lower maximum limit

View File

@@ -1,15 +1,16 @@
print() Netscript Function print() Netscript Function
=========================== ===========================
.. js:function:: print(x) .. js:function:: print(args...)
:RAM cost: 0 GB :RAM cost: 0 GB
:param x: Value to be printed. :param args: Values to be printed.
Prints a value or a variable to the script's logs. Prints any number of values to the script's logs.
Example: Example:
.. code-block:: javascript .. code-block:: javascript
print("Hello world!"); // Prints "Hello world!" in the logs. print("Hello world!"); // Prints "Hello world!" in the logs.
print({a:5}); // Prints '{"a":5}' in the logs.

View File

@@ -7,7 +7,7 @@ purchaseServer() Netscript Function
:param string hostname: Hostname of the purchased server. :param string hostname: Hostname of the purchased server.
:param number ram: Amount of RAM of the purchased server. Must be a power of :param number ram: Amount of RAM of the purchased server. Must be a power of
2. Maximum value of :doc:`getPurchasedServerMaxRam<getPurchasedServerMaxRam>` 2. Maximum value of :doc:`getPurchasedServerMaxRam<getPurchasedServerMaxRam>`
:returns: The hostname of the newly purchased server. :returns: The hostname of the newly purchased server. Empty string on failure.
Purchased a server with the specified hostname and amount of RAM. Purchased a server with the specified hostname and amount of RAM.

View File

@@ -1,15 +1,16 @@
tprint() Netscript Function tprint() Netscript Function
=========================== ===========================
.. js:function:: tprint(x) .. js:function:: tprint(args...)
:RAM cost: 0 GB :RAM cost: 0 GB
:param x: Value to be printed :param args: Values to be printed
Prints a value or a variable to the Terminal. Prints any number of values to the Terminal.
Example: Example:
.. code-block:: javascript .. code-block:: javascript
tprint("Hello world!"); // Prints "Hello world!" to the terminal. tprint("Hello world!"); // Prints "Hello world!" to the terminal.
tprint({a:5}); // Prints '{"a":5}' to the terminal.

View File

@@ -5,7 +5,7 @@ calculateExp() Netscript Function
:RAM cost: 0 GB :RAM cost: 0 GB
:param number skillLevel: ``skillLevel`` to convert to exp. :param number skillLevel: ``skillLevel`` to convert to exp.
:param number mult: Assume a specific skill multipler. :param number mult: Assume a specific skill multipler (not exp multiplier).
:returns: number of exp required to reach given ``skillLevel`` with that multiplier. :returns: number of exp required to reach given ``skillLevel`` with that multiplier.
You must have Source-File 5-1 in order to use this function. You must have Source-File 5-1 in order to use this function.

View File

@@ -5,7 +5,7 @@ calculateSkill() Netscript Function
:RAM cost: 0 GB :RAM cost: 0 GB
:param number exp: ``exp`` to convert to skillLevel. :param number exp: ``exp`` to convert to skillLevel.
:param number mult: Assume a specific skill multipler. :param number mult: Assume a specific skill multipler (not exp multiplier).
:returns: skillLevel that ``exp`` would reach with that multiplier. :returns: skillLevel that ``exp`` would reach with that multiplier.
You must have Source-File 5-1 in order to use this function. You must have Source-File 5-1 in order to use this function.

View File

@@ -10,27 +10,35 @@ getMemberInformation() Netscript Function
The object has the following structure:: The object has the following structure::
{ {
agility: Agility stat name: Name of this member.
agilityEquipMult: Agility multiplier from equipment. Decimal form task: Name of currently assigned task.
agilityAscensionMult: Agility multiplier from ascension. Decimal form earnedRespect: Total amount of respect earned by this member.
augmentations: Array of names of all owned Augmentations hack: Hacking stat
charisma: Charisma stat str: Strength stat
charismaEquipMult: Charisma multiplier from equipment. Decimal form def: Defense stat
charismaAscensionMult: Charisma multiplier from ascension. Decimal form dex: Dexterity stat
defense: Defense stat agi: Agility stat
defenseEquipMult: Defense multiplier from equipment. Decimal form cha: Charisma stat
defenseAscensionMult: Defense multiplier from ascension. Decimal form hack_exp: Hacking experience
dexterity: Dexterity stat str_exp: Strength experience
dexterityEquipMult: Dexterity multiplier from equipment. Decimal form def_exp: Defense experience
dexterityAscensionMult: Dexterity multiplier from ascension. Decimal form dex_exp: Dexterity experience
equipment: Array of names of all owned Non-Augmentation Equipment agi_exp: Agility experience
hacking: Hacking stat cha_exp: Charisma experience
hackingEquipMult: Hacking multiplier from equipment. Decimal form hack_mult: Hacking multiplier from equipment. Decimal form
hackingAscensionMult: Hacking multiplier from ascension. Decimal form str_mult: Strength multiplier from equipment. Decimal form
strength: Strength stat def_mult: Defense multiplier from equipment. Decimal form
strengthEquipMult: Strength multiplier from equipment. Decimal form dex_mult: Dexterity multiplier from equipment. Decimal form
strengthAscensionMult: Strength multiplier from ascension. Decimal form agi_mult: Agility multiplier from equipment. Decimal form
task: Name of currently assigned task cha_mult: Charisma multiplier from equipment. Decimal form
hack_asc_mult: Hacking multiplier from ascension. Decimal form
str_asc_mult: Strength multiplier from ascension. Decimal form
def_asc_mult: Defense multiplier from ascension. Decimal form
dex_asc_mult: Dexterity multiplier from ascension. Decimal form
agi_asc_mult: Agility multiplier from ascension. Decimal form
cha_asc_mult: Charisma multiplier from ascension. Decimal form
upgrades: Array of names of all owned Non-Augmentation Equipment
augmentations: Array of names of all owned Augmentations
} }
Get stat and equipment-related information about a Gang Member Get stat and equipment-related information about a Gang Member

View File

@@ -65,6 +65,6 @@ Here is a short summary of the differences between Netscript 1.0 and Netscript 2
* Supports (almost) all features of modern JavaScript * Supports (almost) all features of modern JavaScript
* Extremely fast - code is executed as an Async Function * Extremely fast - code is executed as an Async Function
* Currently only works with Google Chrome browser * Works on most modern browsers.
* Each script becomes a module and therefore all instances of that script can easily * Each script becomes a module and therefore all instances of that script can easily
share data between each other (essentially global/static variables) share data between each other (essentially global/static variables)

View File

@@ -177,7 +177,7 @@
<table id="terminal"> <table id="terminal">
<tr id="terminal-input"> <tr id="terminal-input">
<td id="terminal-input-td" tabindex="2">$ <td id="terminal-input-td" tabindex="2">$
<input type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" onfocus="this.value = this.value;"/> <input type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" onfocus="this.value = this.value;" autocomplete="off"/>
</td> </td>
</tr> </tr>
</table> </table>
@@ -381,7 +381,7 @@
<!-- Status text --> <!-- Status text -->
<div id="status-text-container"> <div id="status-text-container">
<p id="status-text"> </p> <p id="status-text"></p>
</div> </div>
<!-- Game Options --> <!-- Game Options -->
@@ -559,6 +559,7 @@
<div id="game-options-right-panel"> <div id="game-options-right-panel">
<a class="a-link-button" href="https://bitburner.readthedocs.io/en/latest/changelog.html" target="_blank"> Changelog </a> <a class="a-link-button" href="https://bitburner.readthedocs.io/en/latest/changelog.html" target="_blank"> Changelog </a>
<a class="a-link-button" href="https://bitburner.readthedocs.io/en/latest/index.html" target="_blank">Documentation</a> <a class="a-link-button" href="https://bitburner.readthedocs.io/en/latest/index.html" target="_blank">Documentation</a>
<a class="a-link-button" href="https://discord.gg/TFc3hKD" target="_blank">Discord</a>
<a class="a-link-button" href="https://www.reddit.com/r/bitburner" target="_blank">Subreddit</a> <a class="a-link-button" href="https://www.reddit.com/r/bitburner" target="_blank">Subreddit</a>
<button id="save-game-link" class="a-link-button"> Save Game </button> <button id="save-game-link" class="a-link-button"> Save Game </button>
<button id="delete-game-link" class="a-link-button"> Delete Game </button> <button id="delete-game-link" class="a-link-button"> Delete Game </button>

View File

@@ -121,5 +121,5 @@
"watch": "webpack --watch --mode production", "watch": "webpack --watch --mode production",
"watch:dev": "webpack --watch --mode development" "watch:dev": "webpack --watch --mode development"
}, },
"version": "0.51.1" "version": "0.51.4"
} }

View File

@@ -1395,6 +1395,23 @@ function initAugmentations() {
} }
AddToAugmentations(Xanipher); AddToAugmentations(Xanipher);
const HydroflameLeftArm = new Augmentation({
name:AugmentationNames.HydroflameLeftArm, repCost:500e3, moneyCost:500e9,
info:"The left arm of a legendary BitRunner who ascended beyond this world. " +
"It projects a light blue energy shield that protects the exposed inner parts. " +
"Even though it contains no weapons, the advance tungsten titanium " +
"alloy increases the users strength to unbelievable levels.<br><br>" +
"This augmentation increases the player's strength by 300%.",
strength_mult: 3,
});
HydroflameLeftArm.addToFactions(["NWO"]);
if (augmentationExists(AugmentationNames.HydroflameLeftArm)) {
delete Augmentations[AugmentationNames.HydroflameLeftArm];
}
AddToAugmentations(HydroflameLeftArm);
// ClarkeIncorporated // ClarkeIncorporated
const nextSENS = new Augmentation({ const nextSENS = new Augmentation({
name:AugmentationNames.nextSENS, repCost:175e3, moneyCost:385e6, name:AugmentationNames.nextSENS, repCost:175e3, moneyCost:385e6,

View File

@@ -89,6 +89,7 @@ export let AugmentationNames: IMap<string> = {
BrachiBlades: "BrachiBlades", BrachiBlades: "BrachiBlades",
BionicArms: "Bionic Arms", BionicArms: "Bionic Arms",
SNA: "Social Negotiation Assistant (S.N.A)", SNA: "Social Negotiation Assistant (S.N.A)",
HydroflameLeftArm: "Hydroflame Left Arm",
EsperEyewear: "EsperTech Bladeburner Eyewear", EsperEyewear: "EsperTech Bladeburner Eyewear",
EMS4Recombination: "EMS-4 Recombination", EMS4Recombination: "EMS-4 Recombination",
OrionShoulder: "ORION-MKIV Shoulder", OrionShoulder: "ORION-MKIV Shoulder",

View File

@@ -1069,7 +1069,11 @@ Bladeburner.prototype.gainActionStats = function(action, success) {
Player.gainDexterityExp(unweightedGain * action.weights.dex * Player.dexterity_exp_mult * skillMult); Player.gainDexterityExp(unweightedGain * action.weights.dex * Player.dexterity_exp_mult * skillMult);
Player.gainAgilityExp(unweightedGain * action.weights.agi * Player.agility_exp_mult * skillMult); Player.gainAgilityExp(unweightedGain * action.weights.agi * Player.agility_exp_mult * skillMult);
Player.gainCharismaExp(unweightedGain * action.weights.cha * Player.charisma_exp_mult * skillMult); Player.gainCharismaExp(unweightedGain * action.weights.cha * Player.charisma_exp_mult * skillMult);
Player.gainIntelligenceExp(unweightedIntGain * action.weights.int * skillMult); let intExp = unweightedIntGain * action.weights.int * skillMult;
if (intExp > 1) {
intExp = Math.pow(intExp, 0.8);
}
Player.gainIntelligenceExp(intExp);
} }
Bladeburner.prototype.randomEvent = function() { Bladeburner.prototype.randomEvent = function() {

View File

@@ -21,7 +21,7 @@ export const BlackOperations: IMap<BlackOperation> = {};
BlackOperations["Operation Zero"] = new BlackOperation({ BlackOperations["Operation Zero"] = new BlackOperation({
name:"Operation Zero", name:"Operation Zero",
desc:"AeroCorp is one of the world's largest defense contractors. " + desc:"AeroCorp is one of the world's largest defense contractors. " +
"It's leader, Steve Watataki, is thought to be a supporter of " + "Its leader, Steve Watataki, is thought to be a supporter of " +
"Synthoid rights. He must be removed.<br><br>" + "Synthoid rights. He must be removed.<br><br>" +
"The goal of Operation Zero is to covertly infiltrate AeroCorp and " + "The goal of Operation Zero is to covertly infiltrate AeroCorp and " +
"uncover any incriminating evidence or " + "uncover any incriminating evidence or " +

98
src/Casino/CoinFlip.tsx Normal file
View File

@@ -0,0 +1,98 @@
/**
* React Subcomponent for displaying a location's UI, when that location is a gym
*
* This subcomponent renders all of the buttons for training at the gym
*/
import * as React from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { BadRNG } from "./RNG";
import { Game } from "./Game";
import { trusted } from "./utils";
type IProps = {
p: IPlayer;
}
type IState = {
investment: number;
result: any;
status: string;
playLock: boolean;
}
const minPlay = 0;
const maxPlay = 10e3;
export class CoinFlip extends Game<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
investment: 1000,
result: <span> </span>,
status: '',
playLock: false,
};
this.play = this.play.bind(this);
this.updateInvestment = this.updateInvestment.bind(this);
}
updateInvestment(e: React.FormEvent<HTMLInputElement>) {
let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) {
investment = minPlay;
}
if (investment > maxPlay) {
investment = maxPlay;
}
if (investment < minPlay) {
investment = minPlay;
}
this.setState({investment: investment});
}
play(guess: string) {
if(this.reachedLimit(this.props.p)) return;
const v = BadRNG.random();
let letter: string;
if (v < 0.5) {
letter = 'H';
} else {
letter = 'T';
}
const correct: boolean = guess===letter;
this.setState({
result: <span className={correct ? "text" : "failure"}>{letter}</span>,
status: correct ? " win!" : "lose!",
playLock: true,
});
setTimeout(()=>this.setState({playLock: false}), 250);
if (correct) {
this.win(this.props.p, this.state.investment);
} else {
this.win(this.props.p, -this.state.investment);
}
if(this.reachedLimit(this.props.p)) return;
}
render() {
return <>
<pre>
++<br />
| | | |<br />
| | {this.state.result} | |<br />
| | | |<br />
++<br />
</pre>
<span className="text">Play for: </span><input type="number" className='text-input' onChange={this.updateInvestment} value={this.state.investment} /><br />
<StdButton onClick={trusted(() => this.play('H'))} text={"Head!"} disabled={this.state.playLock} />
<StdButton onClick={trusted(() => this.play('T'))} text={"Tail!"} disabled={this.state.playLock} />
<h1>{this.state.status}</h1>
</>
}
}

20
src/Casino/Game.tsx Normal file
View File

@@ -0,0 +1,20 @@
import * as React from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { dialogBoxCreate } from "../../utils/DialogBox";
const gainLimit = 10e9;
export class Game<T,U> extends React.Component<T, U> {
win(p: IPlayer, n: number) {
p.gainMoney(n);
p.recordMoneySource(n, "casino");
}
reachedLimit(p: IPlayer): boolean {
const reached = p.getCasinoWinnings() > gainLimit;
if(reached) {
dialogBoxCreate(<>Alright cheater get out of here. You're not allowed here anymore.</>);
}
return reached;
}
}

64
src/Casino/RNG.ts Normal file
View File

@@ -0,0 +1,64 @@
export interface RNG {
random(): number
}
/*
* very bad RNG, meant to be used as introduction to RNG manipulation. It has a
* period of 1024.
*/
class RNG0 implements RNG {
x: number;
m: number = 1024;
a: number = 341;
c: number = 1;
constructor() {
this.x = 0;
this.reset();
}
step() {
this.x = (this.a*this.x+this.c) % this.m;
}
random(): number {
this.step();
return this.x/this.m;
}
reset() {
this.x = (new Date()).getTime() % this.m;
}
}
export const BadRNG: RNG0 = new RNG0();
/*
* WichmannHill PRNG
* The period is 6e12.
*/
export class WHRNG implements RNG {
s1: number = 0;
s2: number = 0;
s3: number = 0;
constructor(totalPlaytime: number) {
// This one is seeded by the players total play time.
const v: number = (totalPlaytime/1000)%30000;
this.s1 = v;
this.s2 = v;
this.s3 = v;
}
step() {
this.s1 = (171 * this.s1) % 30269;
this.s2 = (172 * this.s2) % 30307;
this.s3 = (170 * this.s3) % 30323;
}
random(): number {
this.step();
return (this.s1/30269.0 + this.s2/30307.0 + this.s3/30323.0)%1.0;
}
}

295
src/Casino/Roulette.tsx Normal file
View File

@@ -0,0 +1,295 @@
import * as React from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { Money } from "../ui/React/Money";
import { Game } from "./Game";
import { WHRNG } from "./RNG";
import { trusted } from "./utils";
type IProps = {
p: IPlayer;
}
type IState = {
investment: number;
canPlay: boolean;
status: string | JSX.Element;
n: number;
lock: boolean;
strategy: Strategy;
}
const minPlay = 0;
const maxPlay = 1e7;
function isRed(n: number): boolean {
return [1, 3, 5, 7, 9, 12, 14, 16, 18, 19,
21, 23, 25, 27, 30, 32, 34, 36].includes(n);
}
function isBlack(n: number): boolean {
return !isRed(n);
}
type Strategy = {
match: (n: number) => boolean;
payout: number;
}
const redNumbers: number[] = [1, 3, 5, 7, 9, 12, 14, 16, 18, 19,
21, 23, 25, 27, 30, 32, 34, 36];
const strategies: {
Red: Strategy;
Black: Strategy;
Odd: Strategy;
Even: Strategy;
High: Strategy;
Low: Strategy;
Third1: Strategy;
Third2: Strategy;
Third3: Strategy;
} = {
Red: {
match: (n: number): boolean => {
if (n === 0) return false;
return redNumbers.includes(n);
},
payout: 1,
},
Black: {
match: (n: number): boolean => {
return !redNumbers.includes(n);
},
payout: 1,
},
Odd: {
match: (n: number): boolean => {
if (n === 0) return false;
return n%2 === 1;
},
payout: 1,
},
Even: {
match: (n: number): boolean => {
if (n === 0) return false;
return n%2 === 0;
},
payout: 1,
},
High: {
match: (n: number): boolean => {
if (n === 0) return false;
return n>18
},
payout: 1,
},
Low: {
match: (n: number): boolean => {
if (n === 0) return false;
return n<19;
},
payout: 1,
},
Third1: {
match: (n: number): boolean => {
if (n === 0) return false;
return n <= 12;
},
payout: 2,
},
Third2: {
match: (n: number): boolean => {
if (n === 0) return false;
return 13 <= n && n <= 24;
},
payout: 2,
},
Third3: {
match: (n: number): boolean => {
if (n === 0) return false;
return 25 <= n;
},
payout: 2,
},
}
function Single(s: number): Strategy {
return {
match: (n: number): boolean => {
return s === n;
},
payout: 36,
}
}
export class Roulette extends Game<IProps, IState> {
interval: number = -1;
rng: WHRNG;
constructor(props: IProps) {
super(props);
this.rng = new WHRNG((new Date()).getTime());
this.state = {
investment: 1000,
canPlay: true,
status: 'waiting',
n: 0,
lock: true,
strategy: {
payout: 0,
match: (n: number): boolean => { return false },
},
}
this.step = this.step.bind(this);
this.currentNumber = this.currentNumber.bind(this);
this.updateInvestment = this.updateInvestment.bind(this);
}
componentDidMount() {
this.interval = setInterval(this.step, 50);
}
step() {
if (!this.state.lock) {
this.setState({n: Math.floor(Math.random()*37)});
}
}
componentWillUnmount() {
clearInterval(this.interval);
}
updateInvestment(e: React.FormEvent<HTMLInputElement>) {
let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) {
investment = minPlay;
}
if (investment > maxPlay) {
investment = maxPlay;
}
if (investment < minPlay) {
investment = minPlay;
}
this.setState({investment: investment});
}
currentNumber() {
if (this.state.n === 0) return '0';
const color = isRed(this.state.n) ? 'R' : 'B';
return `${this.state.n}${color}`;
}
play(s: Strategy) {
if(this.reachedLimit(this.props.p)) return;
this.setState({
canPlay: false,
lock: false,
status: 'playing',
strategy: s,
})
setTimeout(() => {
let n = Math.floor(this.rng.random()*37);
let status = <></>;
let gain = 0;
let playerWin = this.state.strategy.match(n)
// oh yeah, the house straight up cheats. Try finding the seed now!
if(playerWin && Math.random() > 0.9) {
playerWin = false;
while(this.state.strategy.match(n)) {
n++;
}
}
if(playerWin) {
gain = this.state.investment*this.state.strategy.payout;
status = <>won {Money(gain)}</>;
} else {
gain = -this.state.investment;
status = <>lost {Money(-gain)}</>;
}
this.win(this.props.p, gain);
this.setState({
canPlay: true,
lock: true,
status: status,
n: n,
});
this.reachedLimit(this.props.p);
}, 1600);
}
render() {
return <>
<h1>{this.currentNumber()}</h1>
<input type="number" className='text-input' onChange={this.updateInvestment} placeholder={"Amount to play"} value={this.state.investment} disabled={!this.state.canPlay} />
<h1>{this.state.status}</h1>
<table>
<tbody>
<tr>
<td><StdButton text={"3"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(3)))} /></td>
<td><StdButton text={"6"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(6)))} /></td>
<td><StdButton text={"9"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(9)))} /></td>
<td><StdButton text={"12"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(12)))} /></td>
<td><StdButton text={"15"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(15)))} /></td>
<td><StdButton text={"18"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(18)))} /></td>
<td><StdButton text={"21"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(21)))} /></td>
<td><StdButton text={"24"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(24)))} /></td>
<td><StdButton text={"27"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(27)))} /></td>
<td><StdButton text={"30"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(30)))} /></td>
<td><StdButton text={"33"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(33)))} /></td>
<td><StdButton text={"36"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(36)))} /></td>
</tr>
<tr>
<td><StdButton text={"2"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(2)))} /></td>
<td><StdButton text={"5"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(5)))} /></td>
<td><StdButton text={"8"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(8)))} /></td>
<td><StdButton text={"11"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(11)))} /></td>
<td><StdButton text={"14"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(14)))} /></td>
<td><StdButton text={"17"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(17)))} /></td>
<td><StdButton text={"20"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(20)))} /></td>
<td><StdButton text={"23"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(23)))} /></td>
<td><StdButton text={"26"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(26)))} /></td>
<td><StdButton text={"29"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(29)))} /></td>
<td><StdButton text={"32"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(32)))} /></td>
<td><StdButton text={"35"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(35)))} /></td>
</tr>
<tr>
<td><StdButton text={"1"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(1)))} /></td>
<td><StdButton text={"4"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(4)))} /></td>
<td><StdButton text={"7"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(7)))} /></td>
<td><StdButton text={"10"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(10)))} /></td>
<td><StdButton text={"13"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(13)))} /></td>
<td><StdButton text={"16"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(16)))} /></td>
<td><StdButton text={"19"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(19)))} /></td>
<td><StdButton text={"22"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(22)))} /></td>
<td><StdButton text={"25"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(25)))} /></td>
<td><StdButton text={"28"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(28)))} /></td>
<td><StdButton text={"31"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(31)))} /></td>
<td><StdButton text={"34"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(34)))} /></td>
</tr>
<tr>
<td colSpan={4}><StdButton text={"1 to 12"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Third1))} /></td>
<td colSpan={4}><StdButton text={"13 to 24"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Third2))} /></td>
<td colSpan={4}><StdButton text={"25 to 36"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Third3))} /></td>
</tr>
<tr>
<td colSpan={2}><StdButton text={"Red"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Red))} /></td>
<td colSpan={2}><StdButton text={"Black"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Black))} /></td>
<td colSpan={2}><StdButton text={"Odd"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Odd))} /></td>
<td colSpan={2}><StdButton text={"Even"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Even))} /></td>
<td colSpan={2}><StdButton text={"High"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.High))} /></td>
<td colSpan={2}><StdButton text={"Low"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(strategies.Low))} /></td>
</tr>
<tr>
<td><StdButton text={"0"} disabled={!this.state.canPlay} onClick={trusted(()=>this.play(Single(0)))} /></td>
</tr>
</tbody>
</table>
</>
}
}

239
src/Casino/SlotMachine.tsx Normal file
View File

@@ -0,0 +1,239 @@
import * as React from "react";
import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { Money } from "../ui/React/Money";
import { WHRNG } from "./RNG";
import { Game } from "./Game";
import { trusted } from "./utils";
type IProps = {
p: IPlayer;
}
type IState = {
index: number[];
locks: number[];
investment: number;
canPlay: boolean;
status: string | JSX.Element;
}
// statically shuffled array of symbols.
let symbols = ["D", "C", "$", "?", "♥", "A", "C", "B", "C", "E", "B", "E", "C",
"*", "D", "♥", "B", "A", "A", "A", "C", "A", "D", "B", "E", "?", "D", "*",
"@", "♥", "B", "E", "?"];
function getPayout(s: string, n: number): number {
switch (s) {
case "$":
return [20, 200, 1000][n];
case "@":
return [8, 80, 400][n];
case "♥":
case "?":
return [6, 20, 150][n];
case "D":
case "E":
return [1, 8, 30][n];
default:
return [1, 5, 20][n];
}
}
const payLines = [
// lines
[[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]],
[[1, 0], [1, 1], [1, 2], [1, 3], [1, 4]],
[[2, 0], [2, 1], [2, 2], [2, 3], [2, 4]],
// Vs
[[2, 0], [1, 1], [0, 2], [1, 3], [2, 4]],
[[0, 0], [1, 1], [2, 2], [1, 3], [0, 4]],
// rest
[[0, 0], [1, 1], [1, 2], [1, 3], [0, 4]],
[[2, 0], [1, 1], [1, 2], [1, 3], [2, 4]],
[[1, 0], [0, 1], [0, 2], [0, 3], [1, 4]],
[[1, 0], [2, 1], [2, 2], [2, 3], [1, 4]],
];
const minPlay = 0;
const maxPlay = 1e6;
export class SlotMachine extends Game<IProps, IState> {
rng: WHRNG;
interval: number = -1;
constructor(props: IProps) {
super(props);
this.rng = new WHRNG(this.props.p.totalPlaytime);
this.state = {
index: [0, 0, 0, 0, 0],
investment: 1000,
locks: [0, 0, 0, 0, 0],
canPlay: true,
status: 'waiting',
};
this.play = this.play.bind(this);
this.lock = this.lock.bind(this);
this.unlock = this.unlock.bind(this);
this.step = this.step.bind(this);
this.checkWinnings = this.checkWinnings.bind(this);
this.getTable = this.getTable.bind(this);
this.updateInvestment = this.updateInvestment.bind(this);
}
componentDidMount() {
this.interval = setInterval(this.step, 50);
}
step() {
let stoppedOne = false;
const index = this.state.index.slice();
for(const i in index) {
if (index[i] === this.state.locks[i] && !stoppedOne) continue;
index[i] = (index[i] + 1) % symbols.length;
stoppedOne = true;
}
this.setState({index: index});
if(stoppedOne && index.every((e, i) => e === this.state.locks[i])) {
this.checkWinnings();
}
}
componentWillUnmount() {
clearInterval(this.interval);
}
getTable(): string[][] {
return [
[symbols[(this.state.index[0]+symbols.length-1)%symbols.length], symbols[(this.state.index[1]+symbols.length-1)%symbols.length], symbols[(this.state.index[2]+symbols.length-1)%symbols.length], symbols[(this.state.index[3]+symbols.length-1)%symbols.length], symbols[(this.state.index[4]+symbols.length-1)%symbols.length]],
[symbols[this.state.index[0]], symbols[this.state.index[1]], symbols[this.state.index[2]], symbols[this.state.index[3]], symbols[this.state.index[4]]],
[symbols[(this.state.index[0]+1)%symbols.length], symbols[(this.state.index[1]+1)%symbols.length], symbols[(this.state.index[2]+1)%symbols.length], symbols[(this.state.index[3]+1)%symbols.length], symbols[(this.state.index[4]+1)%symbols.length]],
];
}
play() {
if(this.reachedLimit(this.props.p)) return;
this.setState({status: 'playing'});
this.win(this.props.p, -this.state.investment);
if(!this.state.canPlay) return;
this.unlock();
setTimeout(this.lock, this.rng.random()*2000+1000);
}
lock() {
this.setState({
locks: [
Math.floor(this.rng.random()*symbols.length),
Math.floor(this.rng.random()*symbols.length),
Math.floor(this.rng.random()*symbols.length),
Math.floor(this.rng.random()*symbols.length),
Math.floor(this.rng.random()*symbols.length),
],
})
}
checkWinnings() {
const t = this.getTable();
const getPaylineData = function(payline: number[][]): string[] {
let data = [];
for(const point of payline) {
data.push(t[point[0]][point[1]]);
}
return data;
}
const countSequence = function(data: string[]): number {
let count = 1;
for(let i = 1; i < data.length; i++) {
if (data[i]!==data[i-1]) break;
count++;
}
return count;
}
let gains = -this.state.investment;
for (const payline of payLines) {
const data = getPaylineData(payline);
const count = countSequence(data);
if (count < 3) continue;
const payout = getPayout(data[0], count-3);
gains += this.state.investment*payout;
this.win(this.props.p, this.state.investment*payout);
}
this.setState({
status: <>{gains>0?"gained":"lost"} {Money(Math.abs(gains))}</>,
canPlay: true,
})
if(this.reachedLimit(this.props.p)) return;
}
unlock() {
this.setState({
locks: [-1, -1, -1, -1, -1],
canPlay: false,
})
}
updateInvestment(e: React.FormEvent<HTMLInputElement>) {
let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) {
investment = minPlay;
}
if (investment > maxPlay) {
investment = maxPlay;
}
if (investment < minPlay) {
investment = minPlay;
}
this.setState({investment: investment});
}
render() {
const t = this.getTable();
return <>
<pre>
++<br />
| | {t[0][0]} | {t[0][1]} | {t[0][2]} | {t[0][3]} | {t[0][4]} | |<br />
| | | | | | | |<br />
| | {symbols[this.state.index[0]]} | {symbols[this.state.index[1]]} | {symbols[this.state.index[2]]} | {symbols[this.state.index[3]]} | {symbols[this.state.index[4]]} | |<br />
| | | | | | | |<br />
| | {symbols[(this.state.index[0]+1)%symbols.length]} | {symbols[(this.state.index[1]+1)%symbols.length]} | {symbols[(this.state.index[2]+1)%symbols.length]} | {symbols[(this.state.index[3]+1)%symbols.length]} | {symbols[(this.state.index[4]+1)%symbols.length]} | |<br />
++<br />
</pre>
<input type="number" className='text-input' onChange={this.updateInvestment} placeholder={"Amount to play"} value={this.state.investment} disabled={!this.state.canPlay} />
<StdButton onClick={trusted(this.play)} text={"Spin!"} disabled={!this.state.canPlay} />
<h1>{this.state.status}</h1>
<h2>Pay lines</h2>
<pre>
----- ····· ····· <br />
····· ----- ····· <br />
····· ····· ----- <br />
</pre>
<br />
<pre>
··^·· \···/ \···/<br />
·/·\· ·\·/· ·---·<br />
/···\ ··v·· ·····<br />
</pre>
<br />
<pre>
····· ·---· ·····<br />
·---· /···\ \···/<br />
/···\ ····· ·---·<br />
</pre>
</>
}
}
// https://felgo.com/doc/how-to-make-a-slot-game-tutorial/

8
src/Casino/utils.ts Normal file
View File

@@ -0,0 +1,8 @@
import * as React from "react";
export function trusted(f: () => void): (event: React.MouseEvent<HTMLElement, MouseEvent>) => any {
return function(event: React.MouseEvent<HTMLElement, MouseEvent>): any {
if(!event.isTrusted) return;
f();
};
}

View File

@@ -181,6 +181,9 @@ export class CodingContract {
return new Promise<CodingContractResult>((resolve: Function, reject: Function) => { return new Promise<CodingContractResult>((resolve: Function, reject: Function) => {
const contractType: CodingContractType = CodingContractTypes[this.type]; const contractType: CodingContractType = CodingContractTypes[this.type];
const popupId: string = `coding-contract-prompt-popup-${this.fn}`; const popupId: string = `coding-contract-prompt-popup-${this.fn}`;
const title: HTMLElement = createElement("h1", {
innerHTML: this.type,
});
const txt: HTMLElement = createElement("p", { const txt: HTMLElement = createElement("p", {
innerHTML: ["You are attempting to solve a Coding Contract. You have", innerHTML: ["You are attempting to solve a Coding Contract. You have",
`${this.getMaxNumTries() - this.tries} tries remaining,`, `${this.getMaxNumTries() - this.tries} tries remaining,`,
@@ -225,7 +228,7 @@ export class CodingContract {
innerText: "Cancel", innerText: "Cancel",
}); });
const lineBreak: HTMLElement = createElement("br"); const lineBreak: HTMLElement = createElement("br");
createPopup(popupId, [txt, lineBreak, lineBreak, answerInput, solveBtn, cancelBtn]); createPopup(popupId, [title, lineBreak, txt, lineBreak, lineBreak, answerInput, solveBtn, cancelBtn]);
answerInput.focus(); answerInput.focus();
}); });
} }

View File

@@ -6,13 +6,13 @@
import { IMap } from "./types"; import { IMap } from "./types";
export let CONSTANTS: IMap<any> = { export let CONSTANTS: IMap<any> = {
Version: "0.51.1", Version: "0.51.4",
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience /** 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 * and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
* the player will have this level assuming no multipliers. Multipliers can cause skills to go above this. * the player will have this level assuming no multipliers. Multipliers can cause skills to go above this.
*/ */
MaxSkillLevel: 975, MaxSkillLevel: 975,
// Milliseconds per game cycle // Milliseconds per game cycle
MilliPerCycle: 200, MilliPerCycle: 200,
@@ -218,7 +218,7 @@ export let CONSTANTS: IMap<any> = {
CrimeHeist: "pull off the ultimate heist", CrimeHeist: "pull off the ultimate heist",
// Coding Contract // Coding Contract
// TODO Move this into Coding contract impelmentation? // TODO: Move this into Coding contract implementation?
CodingContractBaseFactionRepGain: 2500, CodingContractBaseFactionRepGain: 2500,
CodingContractBaseCompanyRepGain: 4000, CodingContractBaseCompanyRepGain: 4000,
CodingContractBaseMoneyGain: 75e6, CodingContractBaseMoneyGain: 75e6,
@@ -228,30 +228,34 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate: LatestUpdate:
` `
v0.51.1 - 2021-04-06 Bugfixes because the author of the last patch sucks (it's hydroflame) v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame)
------- -------
Manual hacking
* These bonus require an install or a soft reset to take effect.
* Manual hacking gyms and university gives you a 10% discount.
* Manual hacking a corporation server decreases the penalty for leaving work
early.
BladeBurner
* nerfed int exp gained.
Documentation
* purchaseServer specifies what happens on failure.
* Fixed typo in recommended bitnode page.
* Removed misleading ram requirements for hacking factions.
Netscript Netscript
* 'getPlayer' returns players faction and tor * growthAnalyze handles Infinity correctly.
* 'hospitalization' is a new singularity function.
* 'gang.getMemberInformation' now returns more information.
* 'hacknet.hashCapacity' is a new hacknet function that returns the maximum hash capacity.
Hospitalization
* Now only cost at most 10% of your money.
Bugfix
* confirmation dialog box no longer use previous text
Accessibility
* The game is a little easier to handle for screen readers (yes, there's an
absolute legend playing this game with a screen reader)
* Infiltration use buttons instead of a-links
* New option to disable ASCII art. This will make the metro map and world
map display as a list of buttons.
Misc. Misc.
* 'fl1ght.exe' will no longer suggest the combat path. Related faction * Faction Augmentation will list how much reputation is required even after
requirements unchanged. that goal has been reached.
* Removed dollar sign in travel agency confirmation dialog box.
* Fixed typo in alpha-omega.lit
* the 'Game saved!' text no longer blocks the save game/options button.
* The text editor now remembers the location of your cursor and restores it.
* skills are recalculated instantly.
* Fix typo in Operation Zero description.
` `
} }

View File

@@ -708,8 +708,11 @@ class DevMenuComponent extends Component {
<h2>Generic</h2> <h2>Generic</h2>
</div> </div>
<div className="row"> <div className="row">
<button className="std-button" onClick={this.addMoney(1e6)}>Add $1m</button>
<button className="std-button" onClick={this.addMoney(1e9)}>Add $1b</button>
<button className="std-button" onClick={this.addMoney(1e12)}>Add $1t</button> <button className="std-button" onClick={this.addMoney(1e12)}>Add $1t</button>
<button className="std-button" onClick={this.addMoney(1e15)}>Add $1000t</button> <button className="std-button" onClick={this.addMoney(1e15)}>Add $1000t</button>
<button className="std-button" onClick={this.addMoney(1e27)}>Add $1e27</button>
<button className="std-button" onClick={this.upgradeRam}>Upgrade Home Computer's RAM</button> <button className="std-button" onClick={this.upgradeRam}>Upgrade Home Computer's RAM</button>
</div> </div>
<div className="row"> <div className="row">

View File

@@ -15,6 +15,11 @@ import { Factions } from "./Factions";
import { HackingMission, setInMission } from "../Missions"; import { HackingMission, setInMission } from "../Missions";
import { Player } from "../Player"; import { Player } from "../Player";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
import {
getHackingWorkRepGain,
getFactionSecurityWorkRepGain,
getFactionFieldWorkRepGain,
} from "../PersonObjects/formulas/reputation";
import { Page, routing } from "../ui/navigationTracking"; import { Page, routing } from "../ui/navigationTracking";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../../utils/DialogBox";
@@ -235,15 +240,24 @@ export function getNextNeurofluxLevel() {
} }
export function processPassiveFactionRepGain(numCycles) { export function processPassiveFactionRepGain(numCycles) {
var numTimesGain = (numCycles / 600) * Player.faction_rep_mult; for (const name in Factions) {
for (var name in Factions) { if (name === Player.currentWorkFactionName) continue;
if (Factions.hasOwnProperty(name)) { if (!Factions.hasOwnProperty(name)) continue;
var faction = Factions[name]; const faction = Factions[name];
if (!faction.isMember) continue;
// 0 favor = 1%/s
// 50 favor = 6%/s
// 100 favor = 11%/s
const favorMult = Math.min(0.1, (faction.favor / 1000) + 0.01);
// Find the best of all possible favor gain, minimum 1 rep / 2 minute.
const hRep = getHackingWorkRepGain(Player, faction);
const sRep = getFactionSecurityWorkRepGain(Player, faction);
const fRep = getFactionFieldWorkRepGain(Player, faction);
const rate = Math.max(hRep * favorMult, sRep * favorMult, fRep * favorMult, 1/120);
//TODO Get hard value of 1 rep per "rep gain cycle"" for now.. faction.playerReputation += rate *
//maybe later make this based on (numCycles) *
//a player's 'status' like how powerful they are and how much money they have Player.faction_rep_mult *
if (faction.isMember) {faction.playerReputation += (numTimesGain * BitNodeMultipliers.FactionPassiveRepGain);} BitNodeMultipliers.FactionPassiveRepGain;
} }
}
} }

View File

@@ -118,7 +118,7 @@ export class PurchaseableAugmentation extends React.Component<IProps, any> {
} else if (this.aug.name !== AugmentationNames.NeuroFluxGovernor && (this.aug.owned || this.owned())) { } else if (this.aug.name !== AugmentationNames.NeuroFluxGovernor && (this.aug.owned || this.owned())) {
disabled = true; disabled = true;
} else if (this.hasReputation()) { } else if (this.hasReputation()) {
status = <>UNLOCKED - {Money(moneyCost)}</>; status = <>UNLOCKED (at {Reputation(repCost)} faction reputation) - {Money(moneyCost)}</>;
} else { } else {
disabled = true; disabled = true;
status = <>LOCKED (Requires {Reputation(repCost)} faction reputation - {Money(moneyCost)})</>; status = <>LOCKED (Requires {Reputation(repCost)} faction reputation - {Money(moneyCost)})</>;

View File

@@ -124,8 +124,7 @@ export class HacknetNode extends React.Component {
<li className={"hacknet-node"}> <li className={"hacknet-node"}>
<div className={"hacknet-node-container"}> <div className={"hacknet-node-container"}>
<div className={"row"}> <div className={"row"}>
<p>Node name:</p> <h1 style={{"fontSize":"1em"}}>{node.name}</h1>
<span className={"text"}>{node.name}</span>
</div> </div>
<div className={"row"}> <div className={"row"}>
<p>Production:</p> <p>Production:</p>

View File

@@ -163,8 +163,7 @@ export class HacknetServer extends React.Component {
<li className={"hacknet-node"}> <li className={"hacknet-node"}>
<div className={"hacknet-node-container"}> <div className={"hacknet-node-container"}>
<div className={"row"}> <div className={"row"}>
<p>Node name:</p> <h1 style={{"fontSize":"1em"}}>{node.hostname}</h1>
<span className={"text"}>{node.hostname}</span>
</div> </div>
<div className={"row"}> <div className={"row"}>
<p>Production:</p> <p>Production:</p>

View File

@@ -148,7 +148,7 @@ export const Literatures: IMap<Literature> = {};
"will be our people, and we will be with them as their Gods. We will wipe away every tear from their eyes, and death " + "will be our people, and we will be with them as their Gods. We will wipe away every tear from their eyes, and death " +
"shall be no more, neither shall there be mourning, nor crying, nor pain anymore, for the former things " + "shall be no more, neither shall there be mourning, nor crying, nor pain anymore, for the former things " +
"have passed away.'<br><br>" + "have passed away.'<br><br>" +
"And once were were seated on the throne we said 'Behold, I am making all things new.' " + "And once we were seated on the throne we said 'Behold, I am making all things new.' " +
"Also we said, 'Write this down, for these words are trustworthy and true.' And we said to you, " + "Also we said, 'Write this down, for these words are trustworthy and true.' And we said to you, " +
"'It is done! I am the Alpha and the Omega, the beginning and the end. To the thirsty I will give from the spring " + "'It is done! I am the Alpha and the Omega, the beginning and the end. To the thirsty I will give from the spring " +
"of the water of life without payment. The one who conquers will have this heritage, and we will be his God and " + "of the water of life without payment. The one who conquers will have this heritage, and we will be his God and " +

View File

@@ -11,4 +11,5 @@ export enum LocationType {
TechVendor, TechVendor,
TravelAgency, TravelAgency,
University, University,
Casino,
} }

View File

@@ -47,14 +47,14 @@ Cities[CityName.Aevum].asciiArt = `
\\ 56 B \\ 56 B
x \\ [summit university] x \\ [summit university]
\\ \\ 28 \\ \\ 28
\\ [snap fitness gym] x o--L------------ \\ [snap fitness gym] x o--L-----------N
K \\ / K \\ /
\\ \\ P \\ \\ Q [casino]
x 58 \\ / [travel agency] x 58 \\ / [travel agency]
\\ 94 95 o \\ 94 95 o
90 x 59 o------o | 90 x 59 o------o |
\\ / \\ | 98 102 103 \\ / \\ | 98 102 103
o--------N------x----o 93 96 o-----+------------o o----o o--------O------x----o 93 96 o-----+------------o o----o
\\ | \\ / \\ | \\ /
[hospital] \\ 61 [ecorp] x 31 99 o-F-o 101 [hospital] \\ 61 [ecorp] x 31 99 o-F-o 101
o | o |
@@ -69,13 +69,13 @@ Cities[CityName.Aevum].asciiArt = `
| 34 x \\ | 34 x \\
[clarke inc.] C | \\ [world stock exchange] [clarke inc.] C | \\ [world stock exchange]
| | \\ | | \\
| | o-M-------Q--------o | | o-M-------R--------o
[galactic cybersystems] G 35 x [galactic cybersystems] G 35 x
| [watchdog security] | [watchdog security]
| |
67 o 67 o
[the slums] O ` [the slums] P `
Cities[CityName.Chongqing].asciiArt = ` Cities[CityName.Chongqing].asciiArt = `
| |
75 o 75 o

View File

@@ -78,7 +78,7 @@ export function createTravelPopup(destination: CityName, travelFn: TravelFunctio
return false; return false;
}); });
yesNoBoxCreate(<span>Would you like to travel to ${destination}? The trip will yesNoBoxCreate(<span>Would you like to travel to {destination}? The trip will
cost {Money(cost)}.</span>); cost {Money(cost)}.</span>);
} }
@@ -198,7 +198,12 @@ export function createStartCorporationPopup(p: IPlayer) {
*/ */
export function createUpgradeHomeCoresPopup(p: IPlayer) { export function createUpgradeHomeCoresPopup(p: IPlayer) {
const currentCores = p.getHomeComputer().cpuCores; const currentCores = p.getHomeComputer().cpuCores;
if (currentCores >= 8) { return; } // Max of 8 cores if (currentCores >= 8) {
dialogBoxCreate(<>
You've have the maximum amount of CPU cores on your home computer.
</>);
return;
}
// Cost of purchasing another cost is found by indexing this array with number of current cores // Cost of purchasing another cost is found by indexing this array with number of current cores
const allCosts = [ const allCosts = [
@@ -255,6 +260,14 @@ export function createUpgradeHomeRamPopup(p: IPlayer) {
const noBtn = yesNoBoxGetNoButton(); const noBtn = yesNoBoxGetNoButton();
if (yesBtn == null || noBtn == null) { return; } if (yesBtn == null || noBtn == null) { return; }
const homeComputer = p.getHomeComputer();
if (homeComputer.maxRam >= CONSTANTS.HomeComputerMaxRam) {
dialogBoxCreate(<>
You've have the maximum amount of RAM on your home computer.
</>);
return;
}
yesBtn.innerText = "Purchase"; yesBtn.innerText = "Purchase";
yesBtn.addEventListener("click", ()=>{ yesBtn.addEventListener("click", ()=>{
purchaseRamForHomeComputer(cost, p); purchaseRamForHomeComputer(cost, p);

View File

@@ -24,6 +24,7 @@ export enum LocationName {
AevumSnapFitnessGym = "Snap Fitness Gym", AevumSnapFitnessGym = "Snap Fitness Gym",
AevumSummitUniversity = "Summit University", AevumSummitUniversity = "Summit University",
AevumWatchdogSecurity = "Watchdog Security", AevumWatchdogSecurity = "Watchdog Security",
AevumCasino = "Iker Molina Casino",
// Chongqing locations // Chongqing locations
ChongqingKuaiGongInternational = "KuaiGong International", ChongqingKuaiGongInternational = "KuaiGong International",

View File

@@ -145,6 +145,11 @@ export const LocationsMetadata: IConstructorParams[] = [
name: LocationName.AevumWatchdogSecurity, name: LocationName.AevumWatchdogSecurity,
types: [LocationType.Company], types: [LocationType.Company],
}, },
{
city: CityName.Aevum,
name: LocationName.AevumCasino,
types: [LocationType.Casino],
},
{ {
city: CityName.Chongqing, city: CityName.Chongqing,
infiltrationData: { infiltrationData: {

View File

@@ -0,0 +1,89 @@
/**
* React Subcomponent for displaying a location's UI, when that location is a gym
*
* This subcomponent renders all of the buttons for training at the gym
*/
import * as React from "react";
import { Location } from "../Location";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton";
import { Money } from "../../ui/React/Money";
import { SlotMachine } from "../../Casino/SlotMachine";
import { CoinFlip } from "../../Casino/CoinFlip";
import { Roulette } from "../../Casino/Roulette";
type IProps = {
p: IPlayer;
}
type IState = {
game: string;
}
export class CasinoLocation extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
game: '',
}
this.updateGame = this.updateGame.bind(this);
}
updateGame(game: string) {
this.setState({
game: game,
});
}
renderGames() {
return (<>
<StdButton
onClick={() => this.updateGame('coin')}
text={"Play coin flip"}
/><br />
<StdButton
onClick={() => this.updateGame('slots')}
text={"Play slots"}
/><br />
<StdButton
onClick={() => this.updateGame('roulette')}
text={"Play roulette"}
/>
</>)
}
renderGame() {
let elem;
switch(this.state.game) {
case 'coin':
elem = <CoinFlip p={this.props.p} />
break;
case 'slots':
elem = <SlotMachine p={this.props.p} />
break;
case 'roulette':
elem = <Roulette p={this.props.p} />
break;
}
return (<>
<StdButton onClick={() => this.updateGame('')} text={"Stop playing"} />
{elem}
</>)
}
render() {
if(!this.state.game) {
return this.renderGames();
} else {
return this.renderGame();
}
}
}

View File

@@ -14,6 +14,7 @@ import { SpecialLocation } from "./SpecialLocation";
import { TechVendorLocation } from "./TechVendorLocation"; import { TechVendorLocation } from "./TechVendorLocation";
import { TravelAgencyLocation } from "./TravelAgencyLocation"; import { TravelAgencyLocation } from "./TravelAgencyLocation";
import { UniversityLocation } from "./UniversityLocation"; import { UniversityLocation } from "./UniversityLocation";
import { CasinoLocation } from "./CasinoLocation";
import { Location } from "../Location"; import { Location } from "../Location";
import { LocationType } from "../LocationTypeEnum"; import { LocationType } from "../LocationTypeEnum";
@@ -131,6 +132,15 @@ export class GenericLocation extends React.Component<IProps, any> {
) )
} }
if (this.props.loc.types.includes(LocationType.Casino)) {
content.push(
<CasinoLocation
key={"casinoLocation"}
p={this.props.p}
/>
)
}
return content; return content;
} }

View File

@@ -7,8 +7,11 @@ import * as React from "react";
import { Location } from "../Location"; import { Location } from "../Location";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { getServer } from "../../Server/ServerHelpers";
import { Server } from "../../Server/Server";
import { SpecialServerIps } from "../../Server/SpecialServerIps";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
@@ -34,11 +37,22 @@ export class GymLocation extends React.Component<IProps, any> {
this.trainDefense = this.trainDefense.bind(this); this.trainDefense = this.trainDefense.bind(this);
this.trainDexterity = this.trainDexterity.bind(this); this.trainDexterity = this.trainDexterity.bind(this);
this.trainAgility = this.trainAgility.bind(this); this.trainAgility = this.trainAgility.bind(this);
this.calculateCost = this.calculateCost.bind(this);
}
calculateCost(): number {
const ip = SpecialServerIps.getIp(this.props.loc.name);
console.log(`ip: ${ip}`);
const server = getServer(ip);
if(server == null || !server.hasOwnProperty('manuallyHacked')) return this.props.loc.costMult;
const discount = (server as Server).manuallyHacked? 0.9 : 1;
return this.props.loc.costMult * discount;
} }
train(stat: string) { train(stat: string) {
const loc = this.props.loc; const loc = this.props.loc;
this.props.p.startClass(loc.costMult, loc.expMult, stat); this.props.p.startClass(this.calculateCost(), loc.expMult, stat);
} }
trainStrength() { trainStrength() {
@@ -58,9 +72,7 @@ export class GymLocation extends React.Component<IProps, any> {
} }
render() { render() {
const costMult: number = this.props.loc.costMult; const cost = CONSTANTS.ClassGymBaseCost * this.calculateCost();
const cost = CONSTANTS.ClassGymBaseCost * costMult;
return ( return (
<div> <div>

View File

@@ -9,6 +9,9 @@ import { Location } from "../Location";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { getServer } from "../../Server/ServerHelpers";
import { Server } from "../../Server/Server";
import { SpecialServerIps } from "../../Server/SpecialServerIps";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
@@ -37,11 +40,22 @@ export class UniversityLocation extends React.Component<IProps, any> {
this.algorithms = this.algorithms.bind(this); this.algorithms = this.algorithms.bind(this);
this.management = this.management.bind(this); this.management = this.management.bind(this);
this.leadership = this.leadership.bind(this); this.leadership = this.leadership.bind(this);
this.calculateCost = this.calculateCost.bind(this);
}
calculateCost(): number {
const ip = SpecialServerIps.getIp(this.props.loc.name);
console.log(`ip: ${ip}`);
const server = getServer(ip);
if(server == null || !server.hasOwnProperty('manuallyHacked')) return this.props.loc.costMult;
const discount = (server as Server).manuallyHacked? 0.9 : 1;
return this.props.loc.costMult * discount;
} }
take(stat: string) { take(stat: string) {
const loc = this.props.loc; const loc = this.props.loc;
this.props.p.startClass(loc.costMult, loc.expMult, stat); this.props.p.startClass(this.calculateCost(), loc.expMult, stat);
} }
study() { study() {
@@ -69,7 +83,7 @@ export class UniversityLocation extends React.Component<IProps, any> {
} }
render() { render() {
const costMult: number = this.props.loc.costMult; const costMult: number = this.calculateCost();
const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult; const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult;
const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult; const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult;

View File

@@ -686,13 +686,29 @@ function NetscriptFunctions(workerScript) {
}); });
} }
const argsToString = function(args) {
let out = '';
for(let arg of args) {
if(typeof arg === 'object') {
out += JSON.stringify(arg);
continue
}
out += `${arg}`;
}
return out;
}
return { return {
hacknet : { hacknet : {
numNodes : function() { numNodes : function() {
return Player.hacknetNodes.length; return Player.hacknetNodes.length;
}, },
maxNumNodes : function() { maxNumNodes : function() {
return MaxNumberHacknetServers; if (hasHacknetServers()) {
return HacknetServerConstants.MaxServers;
}
return Infinity;
}, },
purchaseNode : function() { purchaseNode : function() {
return purchaseHacknet(); return purchaseHacknet();
@@ -908,7 +924,7 @@ function NetscriptFunctions(workerScript) {
// Check argument validity // Check argument validity
const server = safeGetServer(ip, 'growthAnalyze'); const server = safeGetServer(ip, 'growthAnalyze');
if (typeof growth !== "number" || isNaN(growth) || growth < 1) { if (typeof growth !== "number" || isNaN(growth) || growth < 1 || !isFinite(growth)) {
throw makeRuntimeErrorMsg("growthAnalyze", `Invalid argument: growth must be numeric and >= 1, is ${growth}.`); throw makeRuntimeErrorMsg("growthAnalyze", `Invalid argument: growth must be numeric and >= 1, is ${growth}.`);
} }
@@ -944,18 +960,17 @@ function NetscriptFunctions(workerScript) {
return Promise.resolve(CONSTANTS.ServerWeakenAmount * threads); return Promise.resolve(CONSTANTS.ServerWeakenAmount * threads);
}); });
}, },
print: function(args){ print: function(){
if (args === undefined) { if (arguments.length === 0) {
throw makeRuntimeErrorMsg("print", "Takes 1 argument."); throw makeRuntimeErrorMsg("print", "Takes at least 1 argument.");
} }
workerScript.print(args.toString()); workerScript.print(argsToString(arguments));
}, },
tprint: function(args) { tprint: function() {
if (args === undefined || args == null) { if (arguments.length === 0) {
throw makeRuntimeErrorMsg("tprint", "Takes 1 argument."); throw makeRuntimeErrorMsg("tprint", "Takes at least 1 argument.");
} }
var x = args.toString(); post(`${workerScript.scriptRef.filename}: ${argsToString(arguments)}`);
post(`${workerScript.scriptRef.filename}: ${args.toString()}`);
}, },
clearLog: function() { clearLog: function() {
workerScript.scriptRef.clearLog(); workerScript.scriptRef.clearLog();
@@ -2022,7 +2037,7 @@ function NetscriptFunctions(workerScript) {
const cost = getPurchaseServerCost(ram); const cost = getPurchaseServerCost(ram);
if (cost === Infinity) { if (cost === Infinity) {
workerScript.log("purchaseServer", `Invalid argument: ram='${ram}'`); workerScript.log("purchaseServer", `Invalid argument: ram='${ram}'`);
return Infinity; return "";
} }
if (Player.money.lt(cost)) { if (Player.money.lt(cost)) {
@@ -4385,4 +4400,4 @@ function NetscriptFunctions(workerScript) {
} // End return } // End return
} // End NetscriptFunction() } // End NetscriptFunction()
export { NetscriptFunctions }; export { NetscriptFunctions };

View File

@@ -107,7 +107,7 @@ export function _getScriptUrls(script, scripts, seen) {
// import {foo} from "blob://<uuid>" // import {foo} from "blob://<uuid>"
// //
// Where the blob URL contains the script content. // Where the blob URL contains the script content.
let transformedCode = script.code.replace(/((?:from|import)\s+(?:'|"))(?:\.\/)?([^'"]+)('|";)/g, let transformedCode = script.code.replace(/((?:from|import)\s+(?:'|"))(?:\.\/)?([^'"]+)('|")/g,
(unmodified, prefix, filename, suffix) => { (unmodified, prefix, filename, suffix) => {
const isAllowedImport = scripts.some(s => s.filename == filename); const isAllowedImport = scripts.some(s => s.filename == filename);
if (!isAllowedImport) return unmodified; if (!isAllowedImport) return unmodified;

View File

@@ -179,4 +179,5 @@ export interface IPlayer {
giveExploit(exploit: Exploit): void; giveExploit(exploit: Exploit): void;
queryStatFromString(str: string): number; queryStatFromString(str: string): number;
getIntelligenceBonus(weight: number): number; getIntelligenceBonus(weight: number): number;
getCasinoWinnings(): number;
} }

View File

@@ -30,6 +30,11 @@ import { LocationName } from "../../Locations/data/LocationNames";
import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve"; import { Sleeve } from "../../PersonObjects/Sleeve/Sleeve";
import { calculateSkill as calculateSkillF } from "../formulas/skill"; import { calculateSkill as calculateSkillF } from "../formulas/skill";
import { calculateIntelligenceBonus } from "../formulas/intelligence"; import { calculateIntelligenceBonus } from "../formulas/intelligence";
import {
getHackingWorkRepGain,
getFactionSecurityWorkRepGain,
getFactionFieldWorkRepGain,
} from '../formulas/reputation';
import { import {
AllServers, AllServers,
AddToAllServers, AddToAllServers,
@@ -429,6 +434,8 @@ export function gainHackingExp(exp) {
if(this.hacking_exp < 0) { if(this.hacking_exp < 0) {
this.hacking_exp = 0; this.hacking_exp = 0;
} }
this.hacking_skill = calculateSkillF(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier);
} }
export function gainStrengthExp(exp) { export function gainStrengthExp(exp) {
@@ -439,6 +446,8 @@ export function gainStrengthExp(exp) {
if(this.strength_exp < 0) { if(this.strength_exp < 0) {
this.strength_exp = 0; this.strength_exp = 0;
} }
this.strength = calculateSkillF(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier);
} }
export function gainDefenseExp(exp) { export function gainDefenseExp(exp) {
@@ -449,6 +458,8 @@ export function gainDefenseExp(exp) {
if(this.defense_exp < 0) { if(this.defense_exp < 0) {
this.defense_exp = 0; this.defense_exp = 0;
} }
this.defense = calculateSkillF(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier);
} }
export function gainDexterityExp(exp) { export function gainDexterityExp(exp) {
@@ -459,6 +470,8 @@ export function gainDexterityExp(exp) {
if(this.dexterity_exp < 0) { if(this.dexterity_exp < 0) {
this.dexterity_exp = 0; this.dexterity_exp = 0;
} }
this.dexterity = calculateSkillF(this.dexterity_exp, this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier);
} }
export function gainAgilityExp(exp) { export function gainAgilityExp(exp) {
@@ -469,6 +482,8 @@ export function gainAgilityExp(exp) {
if(this.agility_exp < 0) { if(this.agility_exp < 0) {
this.agility_exp = 0; this.agility_exp = 0;
} }
this.agility = calculateSkillF(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier);
} }
export function gainCharismaExp(exp) { export function gainCharismaExp(exp) {
@@ -479,6 +494,8 @@ export function gainCharismaExp(exp) {
if(this.charisma_exp < 0) { if(this.charisma_exp < 0) {
this.charisma_exp = 0; this.charisma_exp = 0;
} }
this.charisma = calculateSkillF(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier);
} }
export function gainIntelligenceExp(exp) { export function gainIntelligenceExp(exp) {
@@ -550,7 +567,11 @@ export function processWorkEarnings(numCycles=1) {
this.gainAgilityExp(agiExpGain); this.gainAgilityExp(agiExpGain);
this.gainCharismaExp(chaExpGain); this.gainCharismaExp(chaExpGain);
this.gainMoney(moneyGain); this.gainMoney(moneyGain);
this.recordMoneySource(moneyGain, "work"); if (this.className) {
this.recordMoneySource(moneyGain, "class");
} else {
this.recordMoneySource(moneyGain, "work");
}
this.workHackExpGained += hackExpGain; this.workHackExpGained += hackExpGain;
this.workStrExpGained += strExpGain; this.workStrExpGained += strExpGain;
this.workDefExpGained += defExpGain; this.workDefExpGained += defExpGain;
@@ -592,6 +613,17 @@ export function startWork(companyName) {
Engine.loadWorkInProgressContent(); Engine.loadWorkInProgressContent();
} }
export function cancelationPenalty() {
const company = Companies[this.companyName];
const specialIp = SpecialServerIps[this.companyName];
if(specialIp) {
const server = AllServers[specialIp];
if(server && server.manuallyHacked) return 0.75;
}
return 0.5;
}
export function work(numCycles) { export function work(numCycles) {
// Cap the number of cycles being processed to whatever would put you at // Cap the number of cycles being processed to whatever would put you at
// the work time limit (8 hours) // the work time limit (8 hours)
@@ -622,6 +654,10 @@ export function work(numCycles) {
const position = this.jobs[this.companyName]; const position = this.jobs[this.companyName];
const penalty = this.cancelationPenalty();
const penaltyString = penalty === 0.5 ? 'half' : 'three quarter'
var elem = document.getElementById("work-in-progress-text"); var elem = document.getElementById("work-in-progress-text");
ReactDOM.render(<> ReactDOM.render(<>
You are currently working as a {position} at {this.companyName} (Current Company Reputation: {Reputation(companyRep)})<br /><br /> You are currently working as a {position} at {this.companyName} (Current Company Reputation: {Reputation(companyRep)})<br /><br />
@@ -636,17 +672,17 @@ export function work(numCycles) {
{numeralWrapper.formatExp(this.workAgiExpGained)} ({`${numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}) agility exp <br /><br /> {numeralWrapper.formatExp(this.workAgiExpGained)} ({`${numeralWrapper.formatExp(this.workAgiExpGainRate * CYCLES_PER_SEC)} / sec`}) agility exp <br /><br />
{numeralWrapper.formatExp(this.workChaExpGained)} ({`${numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}) charisma exp <br /><br /> {numeralWrapper.formatExp(this.workChaExpGained)} ({`${numeralWrapper.formatExp(this.workChaExpGainRate * CYCLES_PER_SEC)} / sec`}) charisma exp <br /><br />
You will automatically finish after working for 8 hours. You can cancel earlier if you wish, You will automatically finish after working for 8 hours. You can cancel earlier if you wish,
but you will only gain half of the reputation you've earned so far. but you will only gain {penaltyString} of the reputation you've earned so far.
</>, elem); </>, elem);
} }
export function finishWork(cancelled, sing=false) { export function finishWork(cancelled, sing=false) {
//Since the work was cancelled early, player only gains half of what they've earned so far //Since the work was cancelled early, player only gains half of what they've earned so far
if (cancelled) { if (cancelled) {
this.workRepGained /= 2; this.workRepGained *= this.cancelationPenalty();
} }
var company = Companies[this.companyName]; const company = Companies[this.companyName];
company.playerReputation += (this.workRepGained); company.playerReputation += (this.workRepGained);
this.updateSkillLevels(); this.updateSkillLevels();
@@ -853,7 +889,7 @@ export function startFactionFieldWork(faction) {
this.workDexExpGainRate = .1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workDexExpGainRate = .1 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workAgiExpGainRate = .1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workAgiExpGainRate = .1 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workChaExpGainRate = .1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workChaExpGainRate = .1 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workRepGainRate = this.getFactionFieldWorkRepGain(); this.workRepGainRate = getFactionFieldWorkRepGain(this, faction);
this.factionWorkType = CONSTANTS.FactionWorkField; this.factionWorkType = CONSTANTS.FactionWorkField;
this.currentWorkFactionDescription = "carrying out field missions" this.currentWorkFactionDescription = "carrying out field missions"
@@ -870,7 +906,7 @@ export function startFactionSecurityWork(faction) {
this.workDexExpGainRate = 0.15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workDexExpGainRate = 0.15 * this.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workAgiExpGainRate = 0.15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workAgiExpGainRate = 0.15 * this.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workChaExpGainRate = 0.00 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain; this.workChaExpGainRate = 0.00 * this.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
this.workRepGainRate = this.getFactionSecurityWorkRepGain(); this.workRepGainRate = getFactionSecurityWorkRepGain(this, faction);
this.factionWorkType = CONSTANTS.FactionWorkSecurity; this.factionWorkType = CONSTANTS.FactionWorkSecurity;
this.currentWorkFactionDescription = "performing security detail" this.currentWorkFactionDescription = "performing security detail"
@@ -879,29 +915,23 @@ export function startFactionSecurityWork(faction) {
} }
export function workForFaction(numCycles) { export function workForFaction(numCycles) {
var faction = Factions[this.currentWorkFactionName]; const faction = Factions[this.currentWorkFactionName];
//Constantly update the rep gain rate //Constantly update the rep gain rate
switch (this.factionWorkType) { switch (this.factionWorkType) {
case CONSTANTS.FactionWorkHacking: case CONSTANTS.FactionWorkHacking:
this.workRepGainRate = (this.hacking_skill + this.intelligence) / CONSTANTS.MaxSkillLevel * this.faction_rep_mult * this.getIntelligenceBonus(0.5); this.workRepGainRate = getHackingWorkRepGain(this, faction);
break; break;
case CONSTANTS.FactionWorkField: case CONSTANTS.FactionWorkField:
this.workRepGainRate = this.getFactionFieldWorkRepGain(); this.workRepGainRate = getFactionFieldWorkRepGain(this, faction);
break; break;
case CONSTANTS.FactionWorkSecurity: case CONSTANTS.FactionWorkSecurity:
this.workRepGainRate = this.getFactionSecurityWorkRepGain(); this.workRepGainRate = getFactionSecurityWorkRepGain(this, faction);
break; break;
default: default:
break; break;
} }
//Update reputation gain rate to account for faction favor
var favorMult = 1 + (faction.favor / 100);
if (isNaN(favorMult)) {favorMult = 1;}
this.workRepGainRate *= favorMult;
this.workRepGainRate *= BitNodeMultipliers.FactionWorkRepGain;
//Cap the number of cycles being processed to whatever would put you at limit (20 hours) //Cap the number of cycles being processed to whatever would put you at limit (20 hours)
var overMax = false; var overMax = false;
if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer20Hours) { if (this.timeWorked + (Engine._idleSpeed * numCycles) >= CONSTANTS.MillisecondsPer20Hours) {
@@ -1112,25 +1142,25 @@ export function getWorkRepGain() {
return jobPerformance * this.company_rep_mult * favorMult; return jobPerformance * this.company_rep_mult * favorMult;
} }
export function getFactionSecurityWorkRepGain() { // export function getFactionSecurityWorkRepGain() {
var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel + // var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel +
this.strength / CONSTANTS.MaxSkillLevel + // this.strength / CONSTANTS.MaxSkillLevel +
this.defense / CONSTANTS.MaxSkillLevel + // this.defense / CONSTANTS.MaxSkillLevel +
this.dexterity / CONSTANTS.MaxSkillLevel + // this.dexterity / CONSTANTS.MaxSkillLevel +
this.agility / CONSTANTS.MaxSkillLevel) / 4.5; // this.agility / CONSTANTS.MaxSkillLevel) / 4.5;
return t * this.faction_rep_mult; // return t * this.faction_rep_mult;
} // }
export function getFactionFieldWorkRepGain() { // export function getFactionFieldWorkRepGain() {
var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel + // var t = 0.9 * (this.hacking_skill / CONSTANTS.MaxSkillLevel +
this.strength / CONSTANTS.MaxSkillLevel + // this.strength / CONSTANTS.MaxSkillLevel +
this.defense / CONSTANTS.MaxSkillLevel + // this.defense / CONSTANTS.MaxSkillLevel +
this.dexterity / CONSTANTS.MaxSkillLevel + // this.dexterity / CONSTANTS.MaxSkillLevel +
this.agility / CONSTANTS.MaxSkillLevel + // this.agility / CONSTANTS.MaxSkillLevel +
this.charisma / CONSTANTS.MaxSkillLevel + // this.charisma / CONSTANTS.MaxSkillLevel +
this.intelligence / CONSTANTS.MaxSkillLevel) / 5.5; // this.intelligence / CONSTANTS.MaxSkillLevel) / 5.5;
return t * this.faction_rep_mult; // return t * this.faction_rep_mult;
} // }
/* Creating a Program */ /* Creating a Program */
export function startCreateProgramWork(programName, time, reqLevel) { export function startCreateProgramWork(programName, time, reqLevel) {
@@ -2335,7 +2365,10 @@ export function giveExploit(exploit) {
} }
} }
export function getIntelligenceBonus(weight) { export function getIntelligenceBonus(weight) {
return calculateIntelligenceBonus(this.intelligence, weight); return calculateIntelligenceBonus(this.intelligence, weight);
}
export function getCasinoWinnings() {
return this.moneySourceA.casino;
} }

View File

@@ -0,0 +1,36 @@
import { IPlayer } from '../IPlayer';
import { Faction } from '../../Faction/Faction';
import { CONSTANTS } from '../../Constants';
import { BitNodeMultipliers } from '../../BitNode/BitNodeMultipliers';
function mult(f: Faction): number {
var favorMult = 1 + (f.favor / 100);
if (isNaN(favorMult)) {favorMult = 1;}
return favorMult * BitNodeMultipliers.FactionWorkRepGain;
}
export function getHackingWorkRepGain(p: IPlayer, f: Faction): number {
return (p.hacking_skill + p.intelligence) /
CONSTANTS.MaxSkillLevel * p.faction_rep_mult *
p.getIntelligenceBonus(0.25) * mult(f);
}
export function getFactionSecurityWorkRepGain(p: IPlayer, f: Faction): number {
var t = 0.9 * (p.hacking_skill / CONSTANTS.MaxSkillLevel +
p.strength / CONSTANTS.MaxSkillLevel +
p.defense / CONSTANTS.MaxSkillLevel +
p.dexterity / CONSTANTS.MaxSkillLevel +
p.agility / CONSTANTS.MaxSkillLevel) / 4.5;
return t * p.faction_rep_mult * mult(f);
}
export function getFactionFieldWorkRepGain(p: IPlayer, f: Faction): number {
var t = 0.9 * (p.hacking_skill / CONSTANTS.MaxSkillLevel +
p.strength / CONSTANTS.MaxSkillLevel +
p.defense / CONSTANTS.MaxSkillLevel +
p.dexterity / CONSTANTS.MaxSkillLevel +
p.agility / CONSTANTS.MaxSkillLevel +
p.charisma / CONSTANTS.MaxSkillLevel +
p.intelligence / CONSTANTS.MaxSkillLevel) / 5.5;
return t * p.faction_rep_mult * mult(f);
}

View File

@@ -16,6 +16,7 @@ import {
import { Player } from "../Player"; import { Player } from "../Player";
import { AceEditor } from "../ScriptEditor/Ace"; import { AceEditor } from "../ScriptEditor/Ace";
import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror"; import { CodeMirrorEditor } from "../ScriptEditor/CodeMirror";
import { CursorPositions } from "../ScriptEditor/CursorPositions";
import { AllServers } from "../Server/AllServers"; import { AllServers } from "../Server/AllServers";
import { processSingleServerGrowth } from "../Server/ServerHelpers"; import { processSingleServerGrowth } from "../Server/ServerHelpers";
import { Settings } from "../Settings/Settings"; import { Settings } from "../Settings/Settings";
@@ -224,11 +225,13 @@ $(document).keydown(function(e) {
function saveAndCloseScriptEditor() { function saveAndCloseScriptEditor() {
var filename = document.getElementById("script-editor-filename").value; var filename = document.getElementById("script-editor-filename").value;
let code; let code, cursor;
try { try {
code = getCurrentEditor().getCode(); code = getCurrentEditor().getCode();
cursor = getCurrentEditor().getCursor();
CursorPositions.saveCursor(filename, cursor);
} catch(e) { } catch(e) {
dialogBoxCreate("Something went wrong when trying to save (getCurrentEditor().getCode()). Please report to game developer with details"); dialogBoxCreate("Something went wrong when trying to save (getCurrentEditor().getCode() or getCurrentEditor().getCursor()). Please report to game developer with details");
return; return;
} }
@@ -282,7 +285,7 @@ function saveAndCloseScriptEditor() {
} }
} else if (isScriptFilename(filename)) { } else if (isScriptFilename(filename)) {
//If the current script already exists on the server, overwrite it //If the current script already exists on the server, overwrite it
for (var i = 0; i < s.scripts.length; i++) { for (let i = 0; i < s.scripts.length; i++) {
if (filename == s.scripts[i].filename) { if (filename == s.scripts[i].filename) {
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts); s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
Engine.loadTerminalContent(); Engine.loadTerminalContent();
@@ -295,14 +298,14 @@ function saveAndCloseScriptEditor() {
script.saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts); script.saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
s.scripts.push(script); s.scripts.push(script);
} else if (filename.endsWith(".txt")) { } else if (filename.endsWith(".txt")) {
for (var i = 0; i < s.textFiles.length; ++i) { for (let i = 0; i < s.textFiles.length; ++i) {
if (s.textFiles[i].fn === filename) { if (s.textFiles[i].fn === filename) {
s.textFiles[i].write(code); s.textFiles[i].write(code);
Engine.loadTerminalContent(); Engine.loadTerminalContent();
return; return;
} }
} }
var textFile = new TextFile(filename, code); const textFile = new TextFile(filename, code);
s.textFiles.push(textFile); s.textFiles.push(textFile);
} else { } else {
dialogBoxCreate("Invalid filename. Must be either a script (.script) or " + dialogBoxCreate("Invalid filename. Must be either a script (.script) or " +

View File

@@ -314,6 +314,14 @@ class AceEditorWrapper extends ScriptEditor {
elem.style.display = "none"; elem.style.display = "none";
} }
} }
getCursor() {
return this.editor.getCursorPosition();
}
setCursor(pos) {
this.editor.gotoLine(pos.row+1, pos.column);
}
} }
export const AceEditor = new AceEditorWrapper(); export const AceEditor = new AceEditorWrapper();

View File

@@ -570,6 +570,15 @@ class CodeMirrorEditorWrapper extends ScriptEditor {
elem.style.display = "none"; elem.style.display = "none";
} }
} }
getCursor() {
const c = this.editor.getCursor(); //I need to get the cursor position
return {row: c.line, column: c.ch};
}
setCursor(pos) {
this.editor.setCursor({line: pos.row, ch: pos.column});
}
} }
export const CodeMirrorEditor = new CodeMirrorEditorWrapper(); export const CodeMirrorEditor = new CodeMirrorEditorWrapper();

View File

@@ -0,0 +1,29 @@
export type Position = {
row: number;
column: number;
};
export class PositionTracker {
positions: Map<string, Position>;
constructor() {
this.positions = new Map<string, Position>();
}
saveCursor(filename: string, pos: Position) {
this.positions.set(filename, pos);
}
getCursor(filename: string): Position {
const position = this.positions.get(filename);
if (!position) {
return {
row: 0,
column: 0,
};
}
return position;
}
};
export const CursorPositions: PositionTracker = new PositionTracker();

View File

@@ -1,3 +1,5 @@
import { CursorPositions } from './CursorPositions';
// Base Script Editor class for the Ace/CodeMirror/etc. wrappers // Base Script Editor class for the Ace/CodeMirror/etc. wrappers
const beautify = require('js-beautify').js_beautify; const beautify = require('js-beautify').js_beautify;
@@ -33,6 +35,7 @@ export class ScriptEditor {
if (filename != "") { if (filename != "") {
this.filenameInput.value = filename; this.filenameInput.value = filename;
this.editor.setValue(code); this.editor.setValue(code);
this.setCursor(CursorPositions.getCursor(filename));
} }
this.editor.focus(); this.editor.focus();

View File

@@ -90,6 +90,7 @@ export const serverMetadata: IServerMetadata[] = [
min: 1050, min: 1050,
}, },
serverGrowth: 99, serverGrowth: 99,
specialName: LocationName.AevumECorp,
}, },
{ {
hackDifficulty: 99, hackDifficulty: 99,
@@ -106,6 +107,7 @@ export const serverMetadata: IServerMetadata[] = [
min: 1100, min: 1100,
}, },
serverGrowth: 99, serverGrowth: 99,
specialName: LocationName.Sector12MegaCorp,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -128,6 +130,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 80, max: 80,
min: 60, min: 60,
}, },
specialName: LocationName.AevumBachmanAndAssociates,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -155,6 +158,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 85, max: 85,
min: 55, min: 55,
}, },
specialName: LocationName.Sector12BladeIndustries,
}, },
{ {
hackDifficulty: 99, hackDifficulty: 99,
@@ -175,6 +179,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 95, max: 95,
min: 65, min: 65,
}, },
specialName: LocationName.VolhavenNWO,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -201,6 +206,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 75, max: 75,
min: 45, min: 45,
}, },
specialName: LocationName.AevumClarkeIncorporated,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -231,6 +237,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 99, max: 99,
min: 95, min: 95,
}, },
specialName: LocationName.VolhavenOmniTekIncorporated,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -253,6 +260,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 99, max: 99,
min: 75, min: 75,
}, },
specialName: LocationName.Sector12FourSigma,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -275,6 +283,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 99, max: 99,
min: 90, min: 90,
}, },
specialName: LocationName.ChongqingKuaiGongInternational,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -302,6 +311,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 99, max: 99,
min: 80, min: 80,
}, },
specialName: LocationName.AevumFulcrumTechnologies,
}, },
{ {
hackDifficulty: 99, hackDifficulty: 99,
@@ -338,6 +348,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 92, max: 92,
min: 68, min: 68,
}, },
specialName: LocationName.IshimaStormTechnologies,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -360,6 +371,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 73, max: 73,
min: 47, min: 47,
}, },
specialName: LocationName.NewTokyoDefComm,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -409,6 +421,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 80, max: 80,
min: 70, min: 70,
}, },
specialName: LocationName.VolhavenHeliosLabs,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -436,6 +449,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 80, max: 80,
min: 60, min: 60,
}, },
specialName: LocationName.NewTokyoVitaLife,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -458,6 +472,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 95, max: 95,
min: 85, min: 85,
}, },
specialName: LocationName.Sector12IcarusMicrosystems,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -484,6 +499,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 90, max: 90,
min: 80, min: 80,
}, },
specialName: LocationName.Sector12UniversalEnergy,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -586,6 +602,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 90, max: 90,
min: 70, min: 70,
}, },
specialName: LocationName.AevumGalacticCybersystems,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -609,6 +626,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 65, max: 65,
min: 55, min: 55,
}, },
specialName: LocationName.AevumAeroCorp,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -636,6 +654,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 70, max: 70,
min: 60, min: 60,
}, },
specialName: LocationName.VolhavenOmniaCybersystems,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -711,6 +730,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 80, max: 80,
min: 70, min: 70,
}, },
specialName: LocationName.ChongqingSolarisSpaceSystems,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -733,6 +753,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 70, max: 70,
min: 50, min: 50,
}, },
specialName: LocationName.Sector12DeltaOne,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -760,6 +781,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 90, max: 90,
min: 80, min: 80,
}, },
specialName: LocationName.NewTokyoGlobalPharmaceuticals,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -782,6 +804,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 85, max: 85,
min: 65, min: 65,
}, },
specialName: LocationName.IshimaNovaMedical,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -856,6 +879,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 65, max: 65,
min: 55, min: 55,
}, },
specialName: LocationName.VolhavenLexoCorp,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -882,6 +906,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 60, max: 60,
min: 40, min: 40,
}, },
specialName: LocationName.AevumRhoConstruction,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -909,6 +934,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 60, max: 60,
min: 50, min: 50,
}, },
specialName: LocationName.Sector12AlphaEnterprises,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -935,6 +961,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 50, max: 50,
min: 30, min: 30,
}, },
specialName: LocationName.AevumPolice,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -966,6 +993,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 45, max: 45,
min: 35, min: 35,
}, },
specialName: LocationName.Sector12RothmanUniversity,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -992,6 +1020,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 85, max: 85,
min: 75, min: 75,
}, },
specialName: LocationName.VolhavenZBInstituteOfTechnology,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1023,6 +1052,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 60, max: 60,
min: 40, min: 40,
}, },
specialName: LocationName.AevumSummitUniversity,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1045,6 +1075,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 70, max: 70,
min: 60, min: 60,
}, },
specialName: LocationName.VolhavenSysCoreSecurities,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1121,6 +1152,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 65, max: 65,
min: 45, min: 45,
}, },
specialName: LocationName.VolhavenCompuTek,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1145,6 +1177,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 75, max: 75,
min: 45, min: 45,
}, },
specialName: LocationName.AevumNetLinkTechnologies,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1179,6 +1212,7 @@ export const serverMetadata: IServerMetadata[] = [
organizationName: LocationName.Sector12FoodNStuff, organizationName: LocationName.Sector12FoodNStuff,
requiredHackingSkill: 1, requiredHackingSkill: 1,
serverGrowth: 5, serverGrowth: 5,
specialName: LocationName.Sector12FoodNStuff,
}, },
{ {
hackDifficulty: 10, hackDifficulty: 10,
@@ -1198,9 +1232,10 @@ export const serverMetadata: IServerMetadata[] = [
moneyAvailable: 2500000, moneyAvailable: 2500000,
networkLayer: 1, networkLayer: 1,
numOpenPortsRequired: 0, numOpenPortsRequired: 0,
organizationName: "Joes Guns", organizationName: LocationName.Sector12JoesGuns,
requiredHackingSkill: 10, requiredHackingSkill: 10,
serverGrowth: 20, serverGrowth: 20,
specialName: LocationName.Sector12JoesGuns,
}, },
{ {
hackDifficulty: 25, hackDifficulty: 25,
@@ -1316,6 +1351,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 40, max: 40,
min: 30, min: 30,
}, },
specialName: LocationName.IshimaOmegaSoftware,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1338,6 +1374,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 33, max: 33,
min: 27, min: 27,
}, },
specialName: LocationName.AevumCrushFitnessGym,
}, },
{ {
hackDifficulty: 30, hackDifficulty: 30,
@@ -1349,6 +1386,7 @@ export const serverMetadata: IServerMetadata[] = [
organizationName: "Iron Gym Network", organizationName: "Iron Gym Network",
requiredHackingSkill: 100, requiredHackingSkill: 100,
serverGrowth: 20, serverGrowth: 20,
specialName: LocationName.Sector12IronGym,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1372,6 +1410,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 45, max: 45,
min: 25, min: 25,
}, },
specialName: LocationName.VolhavenMilleniumFitnessGym,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1395,6 +1434,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 60, max: 60,
min: 50, min: 50,
}, },
specialName: LocationName.Sector12PowerhouseGym,
}, },
{ {
hackDifficulty: { hackDifficulty: {
@@ -1414,6 +1454,7 @@ export const serverMetadata: IServerMetadata[] = [
max: 60, max: 60,
min: 40, min: 40,
}, },
specialName: LocationName.AevumSnapFitnessGym,
}, },
{ {
hackDifficulty: 0, hackDifficulty: 0,

View File

@@ -60,7 +60,7 @@ import { Player } from "./Player";
import { hackWorldDaemon } from "./RedPill"; import { hackWorldDaemon } from "./RedPill";
import { RunningScript } from "./Script/RunningScript"; import { RunningScript } from "./Script/RunningScript";
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers"; import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
import { findRunningScript } from "./Script/ScriptHelpers"; import { getCurrentEditor, findRunningScript } from "./Script/ScriptHelpers";
import { isScriptFilename } from "./Script/ScriptHelpersTS"; import { isScriptFilename } from "./Script/ScriptHelpersTS";
import { AllServers } from "./Server/AllServers"; import { AllServers } from "./Server/AllServers";
import { Server } from "./Server/Server"; import { Server } from "./Server/Server";
@@ -108,7 +108,7 @@ import React from "react";
function postNetburnerText() { function postNetburnerText() {
post("Bitburner v" + CONSTANTS.Version); post("Bitburner v" + CONSTANTS.Version);
} }
// Helper function that checks if an argument (which is a string) is a valid number // Helper function that checks if an argument (which is a string) is a valid number
@@ -123,16 +123,16 @@ function getTerminalInput() {
// Defines key commands in terminal // Defines key commands in terminal
$(document).keydown(function(event) { $(document).keydown(function(event) {
// Terminal // Terminal
if (routing.isOn(Page.Terminal)) { if (routing.isOn(Page.Terminal)) {
var terminalInput = document.getElementById("terminal-input-text-box"); var terminalInput = document.getElementById("terminal-input-text-box");
if (terminalInput != null && !event.ctrlKey && !event.shiftKey && !Terminal.contractOpen) {terminalInput.focus();} if (terminalInput != null && !event.ctrlKey && !event.shiftKey && !Terminal.contractOpen) {terminalInput.focus();}
if (event.keyCode === KEY.ENTER) { if (event.keyCode === KEY.ENTER) {
event.preventDefault(); // Prevent newline from being entered in Script Editor event.preventDefault(); // Prevent newline from being entered in Script Editor
const command = getTerminalInput(); const command = getTerminalInput();
const dir = Terminal.currDir; const dir = Terminal.currDir;
post( post(
"<span class='prompt'>[" + "<span class='prompt'>[" +
(FconfSettings.ENABLE_TIMESTAMPS ? getTimestamp() + " " : "") + (FconfSettings.ENABLE_TIMESTAMPS ? getTimestamp() + " " : "") +
Player.getCurrentServer().hostname + Player.getCurrentServer().hostname +
@@ -142,20 +142,20 @@ $(document).keydown(function(event) {
if (command.length > 0) { if (command.length > 0) {
Terminal.resetTerminalInput(); // Clear input first Terminal.resetTerminalInput(); // Clear input first
Terminal.executeCommands(command); Terminal.executeCommands(command);
} }
} }
if (event.keyCode === KEY.C && event.ctrlKey) { if (event.keyCode === KEY.C && event.ctrlKey) {
if (Engine._actionInProgress) { if (Engine._actionInProgress) {
// Cancel action // Cancel action
post("Cancelling..."); post("Cancelling...");
Engine._actionInProgress = false; Engine._actionInProgress = false;
Terminal.finishAction(true); Terminal.finishAction(true);
} else if (FconfSettings.ENABLE_BASH_HOTKEYS) { } else if (FconfSettings.ENABLE_BASH_HOTKEYS) {
// Dont prevent default so it still copies // Dont prevent default so it still copies
Terminal.resetTerminalInput(); // Clear Terminal Terminal.resetTerminalInput(); // Clear Terminal
} }
} }
if (event.keyCode === KEY.L && event.ctrlKey) { if (event.keyCode === KEY.L && event.ctrlKey) {
event.preventDefault(); event.preventDefault();
@@ -183,7 +183,7 @@ $(document).keydown(function(event) {
} }
var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex]; var prevCommand = Terminal.commandHistory[Terminal.commandHistoryIndex];
terminalInput.value = prevCommand; terminalInput.value = prevCommand;
setTimeoutRef(function(){terminalInput.selectionStart = terminalInput.selectionEnd = 10000; }, 0); setTimeoutRef(function(){terminalInput.selectionStart = terminalInput.selectionEnd = 10000; }, 10);
} }
if (event.keyCode === KEY.DOWNARROW || if (event.keyCode === KEY.DOWNARROW ||
@@ -295,44 +295,44 @@ $(document).keydown(function(event) {
// ^k clears line after cursor // ^k clears line after cursor
// ^u clears line before cursor // ^u clears line before cursor
} }
} }
}); });
// Keep terminal in focus // Keep terminal in focus
let terminalCtrlPressed = false, shiftKeyPressed = false; let terminalCtrlPressed = false, shiftKeyPressed = false;
$(document).ready(function() { $(document).ready(function() {
if (routing.isOn(Page.Terminal)) { if (routing.isOn(Page.Terminal)) {
$('.terminal-input').focus(); $('.terminal-input').focus();
} }
}); });
$(document).keydown(function(e) { $(document).keydown(function(e) {
if (routing.isOn(Page.Terminal)) { if (routing.isOn(Page.Terminal)) {
if (e.which == KEY.CTRL) { if (e.which == KEY.CTRL) {
terminalCtrlPressed = true; terminalCtrlPressed = true;
} else if (e.shiftKey) { } else if (e.shiftKey) {
shiftKeyPressed = true; shiftKeyPressed = true;
} else if (terminalCtrlPressed || shiftKeyPressed || Terminal.contractOpen) { } else if (terminalCtrlPressed || shiftKeyPressed || Terminal.contractOpen) {
// Don't focus // Don't focus
} else { } else {
var inputTextBox = document.getElementById("terminal-input-text-box"); var inputTextBox = document.getElementById("terminal-input-text-box");
if (inputTextBox != null) {inputTextBox.focus();} if (inputTextBox != null) {inputTextBox.focus();}
terminalCtrlPressed = false; terminalCtrlPressed = false;
shiftKeyPressed = false; shiftKeyPressed = false;
} }
} }
}); });
$(document).keyup(function(e) { $(document).keyup(function(e) {
if (routing.isOn(Page.Terminal)) { if (routing.isOn(Page.Terminal)) {
if (e.which == KEY.CTRL) { if (e.which == KEY.CTRL) {
terminalCtrlPressed = false; terminalCtrlPressed = false;
} }
if (e.shiftKey) { if (e.shiftKey) {
shiftKeyPressed = false; shiftKeyPressed = false;
} }
} }
}); });
let Terminal = { let Terminal = {
@@ -361,14 +361,14 @@ let Terminal = {
if (FconfSettings.WRAP_INPUT) { if (FconfSettings.WRAP_INPUT) {
document.getElementById("terminal-input-td").innerHTML = document.getElementById("terminal-input-td").innerHTML =
`<div id='terminal-input-header' class='prompt'>[${Player.getCurrentServer().hostname} ~${dir}]$ </div>` + `<div id='terminal-input-header' class='prompt'>[${Player.getCurrentServer().hostname} ~${dir}]$ </div>` +
`<textarea type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" value=\"${input}\"/>`; `<textarea type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" value=\"${input}\" autocomplete="off" />`;
// Auto re-size the line element as it wraps // Auto re-size the line element as it wraps
autosize(document.getElementById("terminal-input-text-box")); autosize(document.getElementById("terminal-input-text-box"));
} else { } else {
document.getElementById("terminal-input-td").innerHTML = document.getElementById("terminal-input-td").innerHTML =
`<div id='terminal-input-header' class='prompt'>[${Player.getCurrentServer().hostname} ~${dir}]$ </div>` + `<div id='terminal-input-header' class='prompt'>[${Player.getCurrentServer().hostname} ~${dir}]$ </div>` +
`<input type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" value=\"${input}\"/>`; `<input type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" value=\"${input}\" autocomplete="off" />`;
} }
const hdr = document.getElementById("terminal-input-header"); const hdr = document.getElementById("terminal-input-header");
hdr.style.display = "inline"; hdr.style.display = "inline";
@@ -512,16 +512,16 @@ let Terminal = {
}, },
// Complete the hack/analyze command // Complete the hack/analyze command
finishHack: function(cancelled = false) { finishHack: function(cancelled = false) {
if (cancelled == false) { if (cancelled == false) {
var server = Player.getCurrentServer(); var server = Player.getCurrentServer();
// Calculate whether hack was successful // Calculate whether hack was successful
var hackChance = calculateHackingChance(server, Player); var hackChance = calculateHackingChance(server, Player);
var rand = Math.random(); var rand = Math.random();
var expGainedOnSuccess = calculateHackingExpGain(server, Player); var expGainedOnSuccess = calculateHackingExpGain(server, Player);
var expGainedOnFailure = (expGainedOnSuccess / 4); var expGainedOnFailure = (expGainedOnSuccess / 4);
if (rand < hackChance) { // Success! if (rand < hackChance) { // Success!
if (SpecialServerIps[SpecialServerNames.WorldDaemon] && if (SpecialServerIps[SpecialServerNames.WorldDaemon] &&
SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip) { SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip) {
if (Player.bitNodeN == null) { if (Player.bitNodeN == null) {
@@ -531,26 +531,26 @@ let Terminal = {
return; return;
} }
server.manuallyHacked = true; server.manuallyHacked = true;
var moneyGained = calculatePercentMoneyHacked(server, Player); var moneyGained = calculatePercentMoneyHacked(server, Player);
moneyGained = Math.floor(server.moneyAvailable * moneyGained); moneyGained = Math.floor(server.moneyAvailable * moneyGained);
if (moneyGained <= 0) {moneyGained = 0;} // Safety check if (moneyGained <= 0) {moneyGained = 0;} // Safety check
server.moneyAvailable -= moneyGained; server.moneyAvailable -= moneyGained;
Player.gainMoney(moneyGained); Player.gainMoney(moneyGained);
Player.recordMoneySource(moneyGained, "hacking"); Player.recordMoneySource(moneyGained, "hacking");
Player.gainHackingExp(expGainedOnSuccess) Player.gainHackingExp(expGainedOnSuccess)
Player.gainIntelligenceExp(expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain); Player.gainIntelligenceExp(expGainedOnSuccess / CONSTANTS.IntelligenceTerminalHackBaseExpGain);
server.fortify(CONSTANTS.ServerFortifyAmount); server.fortify(CONSTANTS.ServerFortifyAmount);
postElement(<>Hack successful! Gained {Money(moneyGained)} and {numeralWrapper.formatExp(expGainedOnSuccess)} hacking exp</>); postElement(<>Hack successful! Gained {Money(moneyGained)} and {numeralWrapper.formatExp(expGainedOnSuccess)} hacking exp</>);
} else { // Failure } else { // Failure
// Player only gains 25% exp for failure? TODO Can change this later to balance // Player only gains 25% exp for failure? TODO Can change this later to balance
Player.gainHackingExp(expGainedOnFailure) Player.gainHackingExp(expGainedOnFailure)
post(`Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`); post(`Failed to hack ${server.hostname}. Gained ${numeralWrapper.formatExp(expGainedOnFailure)} hacking exp`);
} }
} }
// Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal // Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal
$("#hack-progress-bar").attr('id', "old-hack-progress-bar"); $("#hack-progress-bar").attr('id', "old-hack-progress-bar");
@@ -559,55 +559,55 @@ let Terminal = {
$('input[class=terminal-input]').prop('disabled', false); $('input[class=terminal-input]').prop('disabled', false);
Terminal.hackFlag = false; Terminal.hackFlag = false;
}, },
finishAnalyze: function(cancelled = false) { finishAnalyze: function(cancelled = false) {
if (cancelled == false) { if (cancelled == false) {
let currServ = Player.getCurrentServer(); let currServ = Player.getCurrentServer();
const isHacknet = currServ instanceof HacknetServer; const isHacknet = currServ instanceof HacknetServer;
post(currServ.hostname + ": "); post(currServ.hostname + ": ");
post("Organization name: " + currServ.organizationName); post("Organization name: " + currServ.organizationName);
var rootAccess = ""; var rootAccess = "";
if (currServ.hasAdminRights) {rootAccess = "YES";} if (currServ.hasAdminRights) {rootAccess = "YES";}
else {rootAccess = "NO";} else {rootAccess = "NO";}
post("Root Access: " + rootAccess); post("Root Access: " + rootAccess);
if (!isHacknet) { post("Required hacking skill: " + currServ.requiredHackingSkill); } if (!isHacknet) { post("Required hacking skill: " + currServ.requiredHackingSkill); }
post("Server security level: " + numeralWrapper.formatServerSecurity(currServ.hackDifficulty)); post("Server security level: " + numeralWrapper.formatServerSecurity(currServ.hackDifficulty));
post("Chance to hack: " + numeralWrapper.formatPercentage(calculateHackingChance(currServ, Player))); post("Chance to hack: " + numeralWrapper.formatPercentage(calculateHackingChance(currServ, Player)));
post("Time to hack: " + convertTimeMsToTimeElapsedString(calculateHackingTime(currServ, Player)*1000)); post("Time to hack: " + convertTimeMsToTimeElapsedString(calculateHackingTime(currServ, Player)*1000));
postElement(<>Total money available on server: {Money(currServ.moneyAvailable)}</>); postElement(<>Total money available on server: {Money(currServ.moneyAvailable)}</>);
if (!isHacknet) { post("Required number of open ports for NUKE: " + currServ.numOpenPortsRequired); } if (!isHacknet) { post("Required number of open ports for NUKE: " + currServ.numOpenPortsRequired); }
if (currServ.sshPortOpen) { if (currServ.sshPortOpen) {
post("SSH port: Open") post("SSH port: Open")
} else { } else {
post("SSH port: Closed") post("SSH port: Closed")
} }
if (currServ.ftpPortOpen) { if (currServ.ftpPortOpen) {
post("FTP port: Open") post("FTP port: Open")
} else { } else {
post("FTP port: Closed") post("FTP port: Closed")
} }
if (currServ.smtpPortOpen) { if (currServ.smtpPortOpen) {
post("SMTP port: Open") post("SMTP port: Open")
} else { } else {
post("SMTP port: Closed") post("SMTP port: Closed")
} }
if (currServ.httpPortOpen) { if (currServ.httpPortOpen) {
post("HTTP port: Open") post("HTTP port: Open")
} else { } else {
post("HTTP port: Closed") post("HTTP port: Closed")
} }
if (currServ.sqlPortOpen) { if (currServ.sqlPortOpen) {
post("SQL port: Open") post("SQL port: Open")
} else { } else {
post("SQL port: Closed") post("SQL port: Closed")
} }
} }
Terminal.analyzeFlag = false; Terminal.analyzeFlag = false;
// Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal // Rename the progress bar so that the next hacks dont trigger it. Re-enable terminal
@@ -728,7 +728,7 @@ let Terminal = {
return args; return args;
}, },
executeCommand : function(command) { executeCommand : function(command) {
if (Terminal.hackFlag || Terminal.analyzeFlag) { if (Terminal.hackFlag || Terminal.analyzeFlag) {
postError(`Cannot execute command (${command}) while an action is in progress`); postError(`Cannot execute command (${command}) while an action is in progress`);
return; return;
@@ -743,8 +743,8 @@ let Terminal = {
} }
// Only split the first space // Only split the first space
var commandArray = Terminal.parseCommandArguments(command); var commandArray = Terminal.parseCommandArguments(command);
if (commandArray.length == 0) { return; } if (commandArray.length == 0) { return; }
/****************** Interactive Tutorial Terminal Commands ******************/ /****************** Interactive Tutorial Terminal Commands ******************/
if (ITutorial.isRunning) { if (ITutorial.isRunning) {
@@ -820,7 +820,7 @@ let Terminal = {
Terminal.startHack(); Terminal.startHack();
iTutorialNextStep(); iTutorialNextStep();
} else {post("Bad command. Please follow the tutorial");} } else {post("Bad command. Please follow the tutorial");}
break; break;
case iTutorialSteps.TerminalCreateScript: case iTutorialSteps.TerminalCreateScript:
if (commandArray.length == 2 && if (commandArray.length == 2 &&
commandArray[0] == "nano" && commandArray[1] == "foodnstuff.script") { commandArray[0] == "nano" && commandArray[1] == "foodnstuff.script") {
@@ -865,7 +865,7 @@ let Terminal = {
/* Command parser */ /* Command parser */
var s = Player.getCurrentServer(); var s = Player.getCurrentServer();
switch (commandArray[0].toLowerCase()) { switch (commandArray[0].toLowerCase()) {
case "alias": case "alias":
if (commandArray.length === 1) { if (commandArray.length === 1) {
printAliases(); printAliases();
@@ -887,13 +887,13 @@ let Terminal = {
} }
postError('Incorrect usage of alias command. Usage: alias [-g] [aliasname="value"]'); postError('Incorrect usage of alias command. Usage: alias [-g] [aliasname="value"]');
break; break;
case "analyze": case "analyze":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
post("Incorrect usage of analyze command. Usage: analyze"); post("Incorrect usage of analyze command. Usage: analyze");
return; return;
} }
Terminal.startAnalyze(); Terminal.startAnalyze();
break; break;
case "buy": case "buy":
if (SpecialServerIps.hasOwnProperty("Darkweb Server")) { if (SpecialServerIps.hasOwnProperty("Darkweb Server")) {
executeDarkwebTerminalCommand(commandArray); executeDarkwebTerminalCommand(commandArray);
@@ -908,10 +908,10 @@ let Terminal = {
return; return;
} }
const filename = Terminal.getFilepath(commandArray[1]); const filename = Terminal.getFilepath(commandArray[1]);
if (!filename.endsWith(".msg") && !filename.endsWith(".lit") && !filename.endsWith(".txt")) { if (!filename.endsWith(".msg") && !filename.endsWith(".lit") && !filename.endsWith(".txt")) {
postError("Only .msg, .txt, and .lit files are viewable with cat (filename must end with .msg, .txt, or .lit)"); postError("Only .msg, .txt, and .lit files are viewable with cat (filename must end with .msg, .txt, or .lit)");
return; return;
} }
if (filename.endsWith(".msg") || filename.endsWith(".lit")) { if (filename.endsWith(".msg") || filename.endsWith(".lit")) {
for (let i = 0; i < s.messages.length; ++i) { for (let i = 0; i < s.messages.length; ++i) {
@@ -996,17 +996,17 @@ let Terminal = {
break; break;
} }
case "clear": case "clear":
case "cls": case "cls":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of clear/cls command. Usage: clear/cls"); postError("Incorrect usage of clear/cls command. Usage: clear/cls");
return; return;
} }
$("#terminal tr:not(:last)").remove(); $("#terminal tr:not(:last)").remove();
postNetburnerText(); postNetburnerText();
break; break;
case "connect": { case "connect": {
// Disconnect from current server in terminal and connect to new one // Disconnect from current server in terminal and connect to new one
if (commandArray.length !== 2) { if (commandArray.length !== 2) {
postError("Incorrect usage of connect command. Usage: connect [ip/hostname]"); postError("Incorrect usage of connect command. Usage: connect [ip/hostname]");
return; return;
@@ -1022,7 +1022,7 @@ let Terminal = {
} }
postError("Host not found"); postError("Host not found");
break; break;
} }
case "download": { case "download": {
try { try {
@@ -1102,35 +1102,35 @@ let Terminal = {
post(result); post(result);
break; break;
} }
case "free": case "free":
Terminal.executeFreeCommand(commandArray); Terminal.executeFreeCommand(commandArray);
break; break;
case "hack": { case "hack": {
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of hack command. Usage: hack"); postError("Incorrect usage of hack command. Usage: hack");
return; return;
} }
// Hack the current PC (usually for money) // Hack the current PC (usually for money)
// You can't hack your home pc or servers you purchased // You can't hack your home pc or servers you purchased
if (s.purchasedByPlayer) { if (s.purchasedByPlayer) {
postError("Cannot hack your own machines! You are currently connected to your home PC or one of your purchased servers"); postError("Cannot hack your own machines! You are currently connected to your home PC or one of your purchased servers");
} else if (s.hasAdminRights == false ) { } else if (s.hasAdminRights == false ) {
postError("You do not have admin rights for this machine! Cannot hack"); postError("You do not have admin rights for this machine! Cannot hack");
} else if (s.requiredHackingSkill > Player.hacking_skill) { } else if (s.requiredHackingSkill > Player.hacking_skill) {
postError("Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill"); postError("Your hacking skill is not high enough to attempt hacking this machine. Try analyzing the machine to determine the required hacking skill");
} else if (s instanceof HacknetServer) { } else if (s instanceof HacknetServer) {
postError("Cannot hack this type of Server") postError("Cannot hack this type of Server")
} else { } else {
Terminal.startHack(); Terminal.startHack();
} }
break; break;
} }
case "help": case "help":
if (commandArray.length !== 1 && commandArray.length !== 2) { if (commandArray.length !== 1 && commandArray.length !== 2) {
postError("Incorrect usage of help command. Usage: help"); postError("Incorrect usage of help command. Usage: help");
return; return;
} }
if (commandArray.length === 1) { if (commandArray.length === 1) {
post(TerminalHelpText); post(TerminalHelpText);
} else { } else {
var cmd = commandArray[1]; var cmd = commandArray[1];
@@ -1141,9 +1141,9 @@ let Terminal = {
} }
post(txt); post(txt);
} }
break; break;
case "home": case "home":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of home command. Usage: home"); postError("Incorrect usage of home command. Usage: home");
return; return;
} }
@@ -1153,24 +1153,24 @@ let Terminal = {
post("Connected to home"); post("Connected to home");
Terminal.currDir = "/"; Terminal.currDir = "/";
Terminal.resetTerminalInput(); Terminal.resetTerminalInput();
break; break;
case "hostname": case "hostname":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of hostname command. Usage: hostname"); postError("Incorrect usage of hostname command. Usage: hostname");
return; return;
} }
post(Player.getCurrentServer().hostname); post(Player.getCurrentServer().hostname);
break; break;
case "ifconfig": case "ifconfig":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of ifconfig command. Usage: ifconfig"); postError("Incorrect usage of ifconfig command. Usage: ifconfig");
return; return;
} }
post(Player.getCurrentServer().ip); post(Player.getCurrentServer().ip);
break; break;
case "kill": { case "kill": {
Terminal.executeKillCommand(commandArray); Terminal.executeKillCommand(commandArray);
break; break;
} }
case "killall": { case "killall": {
for (let i = s.runningScripts.length - 1; i >= 0; --i) { for (let i = s.runningScripts.length - 1; i >= 0; --i) {
@@ -1180,9 +1180,9 @@ let Terminal = {
post("Killing all running scripts"); post("Killing all running scripts");
break; break;
} }
case "ls": { case "ls": {
Terminal.executeListCommand(commandArray); Terminal.executeListCommand(commandArray);
break; break;
} }
case "lscpu": { case "lscpu": {
post(Player.getCurrentServer().cpuCores + " Core(s)"); post(Player.getCurrentServer().cpuCores + " Core(s)");
@@ -1269,25 +1269,25 @@ let Terminal = {
break; break;
} }
case "nano": case "nano":
Terminal.executeNanoCommand(commandArray); Terminal.executeNanoCommand(commandArray);
break; break;
case "ps": case "ps":
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of ps command. Usage: ps"); postError("Incorrect usage of ps command. Usage: ps");
return; return;
} }
for (let i = 0; i < s.runningScripts.length; i++) { for (let i = 0; i < s.runningScripts.length; i++) {
let rsObj = s.runningScripts[i]; let rsObj = s.runningScripts[i];
let res = `(PID - ${rsObj.pid}) ${rsObj.filename}`; let res = `(PID - ${rsObj.pid}) ${rsObj.filename}`;
for (let j = 0; j < rsObj.args.length; ++j) { for (let j = 0; j < rsObj.args.length; ++j) {
res += (" " + rsObj.args[j].toString()); res += (" " + rsObj.args[j].toString());
} }
post(res); post(res);
} }
break; break;
case "rm": { case "rm": {
if (commandArray.length !== 2) { if (commandArray.length !== 2) {
postError("Incorrect number of arguments. Usage: rm [program/script]"); postError("Incorrect number of arguments. Usage: rm [program/script]");
return; return;
} }
@@ -1299,14 +1299,14 @@ let Terminal = {
if (!status.res) { if (!status.res) {
postError(status.msg); postError(status.msg);
} }
break; break;
} }
case "run": case "run":
// Run a program or a script // Run a program or a script
if (commandArray.length < 2) { if (commandArray.length < 2) {
postError("Incorrect number of arguments. Usage: run [program/script] [-t] [num threads] [arg1] [arg2]..."); postError("Incorrect number of arguments. Usage: run [program/script] [-t] [num threads] [arg1] [arg2]...");
} else { } else {
var executableName = commandArray[1]; var executableName = commandArray[1];
// Secret Music player! // Secret Music player!
if (executableName === "musicplayer") { if (executableName === "musicplayer") {
@@ -1314,19 +1314,19 @@ let Terminal = {
return; return;
} }
// Check if its a script or just a program/executable // Check if its a script or just a program/executable
if (isScriptFilename(executableName)) { if (isScriptFilename(executableName)) {
Terminal.runScript(commandArray); Terminal.runScript(commandArray);
} else if (executableName.endsWith(".cct")) { } else if (executableName.endsWith(".cct")) {
Terminal.runContract(executableName); Terminal.runContract(executableName);
} else { } else {
Terminal.runProgram(commandArray); Terminal.runProgram(commandArray);
} }
} }
break; break;
case "scan": case "scan":
Terminal.executeScanCommand(commandArray); Terminal.executeScanCommand(commandArray);
break; break;
case "scan-analyze": case "scan-analyze":
if (commandArray.length === 1) { if (commandArray.length === 1) {
Terminal.executeScanAnalyzeCommand(1); Terminal.executeScanAnalyzeCommand(1);
@@ -1362,7 +1362,7 @@ let Terminal = {
} }
break; break;
/* eslint-disable no-case-declarations */ /* eslint-disable no-case-declarations */
case "scp": case "scp":
Terminal.executeScpCommand(commandArray); Terminal.executeScpCommand(commandArray);
break; break;
/* eslint-enable no-case-declarations */ /* eslint-enable no-case-declarations */
@@ -1378,7 +1378,7 @@ let Terminal = {
post("You do NOT have root access to this machine"); post("You do NOT have root access to this machine");
} }
break; break;
case "tail": { case "tail": {
try { try {
if (commandArray.length < 2) { if (commandArray.length < 2) {
postError("Incorrect number of arguments. Usage: tail [script] [arg1] [arg2]..."); postError("Incorrect number of arguments. Usage: tail [script] [arg1] [arg2]...");
@@ -1407,7 +1407,7 @@ let Terminal = {
Terminal.postThrownError(e); Terminal.postThrownError(e);
} }
break; break;
} }
case "theme": { case "theme": {
let args = commandArray.slice(1); let args = commandArray.slice(1);
@@ -1455,11 +1455,11 @@ let Terminal = {
} }
break; break;
} }
case "top": { case "top": {
if (commandArray.length !== 1) { if (commandArray.length !== 1) {
postError("Incorrect usage of top command. Usage: top"); postError("Incorrect usage of top command. Usage: top");
return; return;
} }
// Headers // Headers
const scriptWidth = 40; const scriptWidth = 40;
@@ -1477,29 +1477,29 @@ let Terminal = {
const headers = `${scriptTxt}${spacesAfterScriptTxt}${pidTxt}${spacesAfterPidTxt}${threadsTxt}${spacesAfterThreadsTxt}${ramTxt}`; const headers = `${scriptTxt}${spacesAfterScriptTxt}${pidTxt}${spacesAfterPidTxt}${threadsTxt}${spacesAfterThreadsTxt}${ramTxt}`;
post(headers); post(headers);
let currRunningScripts = s.runningScripts; let currRunningScripts = s.runningScripts;
// Iterate through scripts on current server // Iterate through scripts on current server
for (let i = 0; i < currRunningScripts.length; i++) { for (let i = 0; i < currRunningScripts.length; i++) {
let script = currRunningScripts[i]; let script = currRunningScripts[i];
// Calculate name padding // Calculate name padding
const numSpacesScript = Math.max(0, scriptWidth - script.filename.length); const numSpacesScript = Math.max(0, scriptWidth - script.filename.length);
const spacesScript = " ".repeat(numSpacesScript); const spacesScript = " ".repeat(numSpacesScript);
// Calculate PID padding // Calculate PID padding
const numSpacesPid = Math.max(0, pidWidth - (script.pid + "").length); const numSpacesPid = Math.max(0, pidWidth - (script.pid + "").length);
const spacesPid = " ".repeat(numSpacesPid); const spacesPid = " ".repeat(numSpacesPid);
// Calculate thread padding // Calculate thread padding
const numSpacesThread = Math.max(0, threadsWidth - (script.threads + "").length); const numSpacesThread = Math.max(0, threadsWidth - (script.threads + "").length);
const spacesThread = " ".repeat(numSpacesThread); const spacesThread = " ".repeat(numSpacesThread);
// Calculate and transform RAM usage // Calculate and transform RAM usage
const ramUsage = numeralWrapper.formatRAM(getRamUsageFromRunningScript(script) * script.threads); const ramUsage = numeralWrapper.formatRAM(getRamUsageFromRunningScript(script) * script.threads);
const entry = [ const entry = [
script.filename, script.filename,
spacesScript, spacesScript,
script.pid, script.pid,
@@ -1508,9 +1508,9 @@ let Terminal = {
spacesThread, spacesThread,
ramUsage ramUsage
].join(""); ].join("");
post(entry); post(entry);
} }
break; break;
} }
case "unalias": { case "unalias": {
if (commandArray.length !== 2) { if (commandArray.length !== 2) {
@@ -1555,10 +1555,10 @@ let Terminal = {
}) })
break; break;
} }
default: default:
postError(`Command ${commandArray[0]} not found`); postError(`Command ${commandArray[0]} not found`);
} }
}, },
connectToServer: function(ip) { connectToServer: function(ip) {
var serv = getServer(ip); var serv = getServer(ip);
@@ -1783,12 +1783,13 @@ let Terminal = {
const script = Terminal.getScript(filename); const script = Terminal.getScript(filename);
if (script == null) { if (script == null) {
let code = "" let code = ""
if(filename.endsWith(".ns")) { if(filename.endsWith(".ns") || filename.endsWith(".js")) {
code = `export async function main(ns) { code = `export async function main(ns) {
}`; }`;
} }
Engine.loadScriptEditorContent(filepath, code); Engine.loadScriptEditorContent(filepath, code);
getCurrentEditor().setCursor({row: 1, column: 4});
} else { } else {
Engine.loadScriptEditorContent(filepath, script.code); Engine.loadScriptEditorContent(filepath, script.code);
} }
@@ -2003,24 +2004,24 @@ let Terminal = {
} }
}, },
// First called when the "run [program]" command is called. Checks to see if you // First called when the "run [program]" command is called. Checks to see if you
// have the executable and, if you do, calls the executeProgram() function // have the executable and, if you do, calls the executeProgram() function
runProgram: function(commandArray) { runProgram: function(commandArray) {
if (commandArray.length < 2) { return; } if (commandArray.length < 2) { return; }
// Check if you have the program on your computer. If you do, execute it, otherwise // Check if you have the program on your computer. If you do, execute it, otherwise
// display an error message // display an error message
const programName = commandArray[1]; const programName = commandArray[1];
if (Player.hasProgram(programName)) { if (Player.hasProgram(programName)) {
Terminal.executeProgram(commandArray); Terminal.executeProgram(commandArray);
return; return;
} }
post("ERROR: No such executable on home computer (Only programs that exist on your home computer can be run)"); post("ERROR: No such executable on home computer (Only programs that exist on your home computer can be run)");
}, },
// Contains the implementations of all possible programs // Contains the implementations of all possible programs
executeProgram: function(commandArray) { executeProgram: function(commandArray) {
if (commandArray.length < 2) { return; } if (commandArray.length < 2) { return; }
var s = Player.getCurrentServer(); var s = Player.getCurrentServer();
@@ -2173,7 +2174,7 @@ let Terminal = {
} }
programHandlers[programName](s, splitArgs); programHandlers[programName](s, splitArgs);
}, },
/** /**
* Given a filename, returns that file's full path. This takes into account * Given a filename, returns that file's full path. This takes into account
@@ -2271,13 +2272,13 @@ let Terminal = {
} }
}, },
runScript: function(commandArray) { runScript: function(commandArray) {
if (commandArray.length < 2) { if (commandArray.length < 2) {
dialogBoxCreate(`Bug encountered with Terminal.runScript(). Command array has a length of less than 2: ${commandArray}`); dialogBoxCreate(`Bug encountered with Terminal.runScript(). Command array has a length of less than 2: ${commandArray}`);
return; return;
} }
const server = Player.getCurrentServer(); const server = Player.getCurrentServer();
let numThreads = 1; let numThreads = 1;
const args = []; const args = [];
@@ -2307,23 +2308,23 @@ let Terminal = {
return; return;
} }
// Check if the script exists and if it does run it // Check if the script exists and if it does run it
for (var i = 0; i < server.scripts.length; i++) { for (var i = 0; i < server.scripts.length; i++) {
if (server.scripts[i].filename === scriptName) { if (server.scripts[i].filename === scriptName) {
// Check for admin rights and that there is enough RAM availble to run // Check for admin rights and that there is enough RAM availble to run
var script = server.scripts[i]; var script = server.scripts[i];
var ramUsage = script.ramUsage * numThreads; var ramUsage = script.ramUsage * numThreads;
var ramAvailable = server.maxRam - server.ramUsed; var ramAvailable = server.maxRam - server.ramUsed;
if (server.hasAdminRights == false) { if (server.hasAdminRights == false) {
post("Need root access to run script"); post("Need root access to run script");
return; return;
} else if (ramUsage > ramAvailable){ } else if (ramUsage > ramAvailable){
post("This machine does not have enough RAM to run this script with " + post("This machine does not have enough RAM to run this script with " +
numThreads + " threads. Script requires " + ramUsage + "GB of RAM"); numThreads + " threads. Script requires " + ramUsage + "GB of RAM");
return; return;
} else { } else {
// Able to run script // Able to run script
var runningScriptObj = new RunningScript(script, args); var runningScriptObj = new RunningScript(script, args);
runningScriptObj.threads = numThreads; runningScriptObj.threads = numThreads;
@@ -2333,12 +2334,12 @@ let Terminal = {
postError(`Failed to start script`); postError(`Failed to start script`);
} }
return; return;
} }
} }
} }
post("ERROR: No such script"); post("ERROR: No such script");
}, },
runContract: async function(contractName) { runContract: async function(contractName) {
// There's already an opened contract // There's already an opened contract
@@ -2379,4 +2380,4 @@ let Terminal = {
}, },
}; };
export {postNetburnerText, Terminal}; export {postNetburnerText, Terminal};

View File

@@ -612,8 +612,7 @@ const Engine = {
})); }));
Engine.Display.factionsContent.appendChild(createElement("p", { Engine.Display.factionsContent.appendChild(createElement("p", {
width:"70%", width:"70%",
innerText:"Lists factions you have been invited to, as well as " + innerText:"Lists factions you have been invited to. You can accept " +
"factions you have previously rejected. You can accept " +
"these faction invitations at any time." "these faction invitations at any time."
})); }));
var invitationsList = createElement("ul"); var invitationsList = createElement("ul");
@@ -778,7 +777,7 @@ const Engine = {
createProgramNotifications: 10, createProgramNotifications: 10,
augmentationsNotifications: 10, augmentationsNotifications: 10,
checkFactionInvitations: 100, checkFactionInvitations: 100,
passiveFactionGrowth: 600, passiveFactionGrowth: 5,
messages: 150, messages: 150,
mechanicProcess: 5, // Processes certain mechanics (Corporation, Bladeburner) mechanicProcess: 5, // Processes certain mechanics (Corporation, Bladeburner)
contractGeneration: 3000, // Generate Coding Contracts contractGeneration: 3000, // Generate Coding Contracts
@@ -912,9 +911,9 @@ const Engine = {
} }
if (Engine.Counters.passiveFactionGrowth <= 0) { if (Engine.Counters.passiveFactionGrowth <= 0) {
var adjustedCycles = Math.floor((600 - Engine.Counters.passiveFactionGrowth)); var adjustedCycles = Math.floor((5 - Engine.Counters.passiveFactionGrowth));
processPassiveFactionRepGain(adjustedCycles); processPassiveFactionRepGain(adjustedCycles);
Engine.Counters.passiveFactionGrowth = 600; Engine.Counters.passiveFactionGrowth = 5;
} }
if (Engine.Counters.messages <= 0) { if (Engine.Counters.messages <= 0) {

View File

@@ -179,7 +179,7 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<table id="terminal"> <table id="terminal">
<tr id="terminal-input"> <tr id="terminal-input">
<td id="terminal-input-td" tabindex="2">$ <td id="terminal-input-td" tabindex="2">$
<input type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" onfocus="this.value = this.value;" /> <input type="text" id="terminal-input-text-box" class="terminal-input" tabindex="1" onfocus="this.value = this.value;" autocomplete="off" />
</td> </td>
</tr> </tr>
</table> </table>
@@ -394,7 +394,7 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<!-- Status text --> <!-- Status text -->
<div id="status-text-container"> <div id="status-text-container">
<p id="status-text"> </p> <p id="status-text"></p>
</div> </div>
<!-- Game Options --> <!-- Game Options -->
@@ -572,6 +572,7 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<div id="game-options-right-panel"> <div id="game-options-right-panel">
<a class="a-link-button" href="https://bitburner.readthedocs.io/en/latest/changelog.html" target="_blank"> Changelog </a> <a class="a-link-button" href="https://bitburner.readthedocs.io/en/latest/changelog.html" target="_blank"> Changelog </a>
<a class="a-link-button" href="https://bitburner.readthedocs.io/en/latest/index.html" target="_blank">Documentation</a> <a class="a-link-button" href="https://bitburner.readthedocs.io/en/latest/index.html" target="_blank">Documentation</a>
<a class="a-link-button" href="https://discord.gg/TFc3hKD" target="_blank">Discord</a>
<a class="a-link-button" href="https://www.reddit.com/r/bitburner" target="_blank">Subreddit</a> <a class="a-link-button" href="https://www.reddit.com/r/bitburner" target="_blank">Subreddit</a>
<button id="save-game-link" class="a-link-button"> Save Game </button> <button id="save-game-link" class="a-link-button"> Save Game </button>
<button id="delete-game-link" class="a-link-button"> Delete Game </button> <button id="delete-game-link" class="a-link-button"> Delete Game </button>

View File

@@ -40,9 +40,9 @@ export function CharacterInfo(p: IPlayer): React.ReactElement {
function Hacknet(): React.ReactElement { function Hacknet(): React.ReactElement {
// Can't import HacknetHelpers for some reason. // Can't import HacknetHelpers for some reason.
if(!(p.bitNodeN === 9 || SourceFileFlags[9] > 0)) { if(!(p.bitNodeN === 9 || SourceFileFlags[9] > 0)) {
return <><span>{`Hacknet Nodes owned: ${p.hacknetNodes.length}</span>`}</span><br /></> return <><span>{`Hacknet Nodes owned: ${p.hacknetNodes.length}`}</span><br /></>
} else { } else {
return <><span>{`Hacknet Servers owned: ${p.hacknetNodes.length} / ${HacknetServerConstants.MaxServers}</span>`}</span><br /></> return <><span>{`Hacknet Servers owned: ${p.hacknetNodes.length} / ${HacknetServerConstants.MaxServers}`}</span><br /></>
} }
} }
@@ -51,6 +51,7 @@ export function CharacterInfo(p: IPlayer): React.ReactElement {
if (src.bladeburner) { parts.push([`Bladeburner:`, Money(src.bladeburner)]) }; if (src.bladeburner) { parts.push([`Bladeburner:`, Money(src.bladeburner)]) };
if (src.codingcontract) { parts.push([`Coding Contracts:`, Money(src.codingcontract)]) }; if (src.codingcontract) { parts.push([`Coding Contracts:`, Money(src.codingcontract)]) };
if (src.work) { parts.push([`Company Work:`, Money(src.work)]) }; if (src.work) { parts.push([`Company Work:`, Money(src.work)]) };
if (src.class) { parts.push([`Class:`, Money(src.class)]) };
if (src.corporation) { parts.push([`Corporation:`, Money(src.corporation)]) }; if (src.corporation) { parts.push([`Corporation:`, Money(src.corporation)]) };
if (src.crime) { parts.push([`Crimes:`, Money(src.crime)]) }; if (src.crime) { parts.push([`Crimes:`, Money(src.crime)]) };
if (src.gang) { parts.push([`Gang:`, Money(src.gang)]) }; if (src.gang) { parts.push([`Gang:`, Money(src.gang)]) };
@@ -59,6 +60,7 @@ export function CharacterInfo(p: IPlayer): React.ReactElement {
if (src.hospitalization) { parts.push([`Hospitalization:`, Money(src.hospitalization)]) }; if (src.hospitalization) { parts.push([`Hospitalization:`, Money(src.hospitalization)]) };
if (src.infiltration) { parts.push([`Infiltration:`, Money(src.infiltration)]) }; if (src.infiltration) { parts.push([`Infiltration:`, Money(src.infiltration)]) };
if (src.stock) { parts.push([`Stock Market:`, Money(src.stock)]) }; if (src.stock) { parts.push([`Stock Market:`, Money(src.stock)]) };
if (src.casino) { parts.push([`Casino:`, Money(src.casino)]) };
return StatsTable(parts, ""); return StatsTable(parts, "");
} }

View File

@@ -19,7 +19,7 @@ export class CharacterOverviewComponent extends Component {
<table> <table>
<tbody> <tbody>
<tr id="character-hp-wrapper"> <tr id="character-hp-wrapper">
<td className="character-hp-cell">Hp:</td><td id="character-hp-text" className="character-hp-cell character-stat-cell">{Player.hp + " / " + Player.max_hp}</td> <td className="character-hp-cell">Hp:</td><td id="character-hp-text" className="character-hp-cell character-stat-cell">{numeralWrapper.formatHp(Player.hp) + " / " + numeralWrapper.formatHp(Player.max_hp)}</td>
</tr> </tr>
<tr id="character-money-wrapper"> <tr id="character-money-wrapper">
<td className="character-money-cell">Money:&nbsp;</td><td id="character-money-text" className="character-money-cell character-stat-cell">{numeralWrapper.formatMoney(Player.money.toNumber())}</td> <td className="character-money-cell">Money:&nbsp;</td><td id="character-money-text" className="character-money-cell character-stat-cell">{numeralWrapper.formatMoney(Player.money.toNumber())}</td>

View File

@@ -49,7 +49,7 @@ class NumeralFormatter {
} }
formatHp(n: number): string { formatHp(n: number): string {
return this.format(n, "0.0"); return this.format(n, "0");
} }
formatMoney(n: number): string { formatMoney(n: number): string {

View File

@@ -11,6 +11,8 @@ export class MoneySourceTracker {
} }
bladeburner: number = 0; bladeburner: number = 0;
casino: number = 0;
class: number = 0;
codingcontract: number = 0; codingcontract: number = 0;
corporation: number = 0; corporation: number = 0;
crime: number = 0; crime: number = 0;

View File

@@ -56,6 +56,8 @@ export function yesNoBoxCreate(txt: string | JSX.Element) {
yesNoBoxOpen = true; yesNoBoxOpen = true;
if (yesNoBoxTextElement) { if (yesNoBoxTextElement) {
ReactDOM.unmountComponentAtNode(yesNoBoxTextElement);
yesNoBoxTextElement.innerHTML = '';
if(typeof txt === 'string') { if(typeof txt === 'string') {
yesNoBoxTextElement.innerHTML = txt as string; yesNoBoxTextElement.innerHTML = txt as string;
} else { } else {
@@ -136,6 +138,7 @@ export function yesNoTxtInpBoxCreate(txt: string | JSX.Element) {
if (yesNoTextInputBoxTextElement) { if (yesNoTextInputBoxTextElement) {
ReactDOM.unmountComponentAtNode(yesNoTextInputBoxTextElement);
yesNoTextInputBoxTextElement.innerHTML = ''; yesNoTextInputBoxTextElement.innerHTML = '';
if(typeof txt === 'string') { if(typeof txt === 'string') {
yesNoTextInputBoxTextElement.innerHTML = txt; yesNoTextInputBoxTextElement.innerHTML = txt;