Compare commits

..

11 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
hydroflame
db2bf79e3b v0.51.1 (#835)
* 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
2021-04-06 03:50:09 -04:00
hydroflame
6f330efc44 Added Disable ASCII art to options (#832)
* hotfix getPlayer missing factions

* Added ability to disable ascii art in options. ASCII art is impossible to deal with for screenreaders.
2021-04-02 20:14:35 -04:00
hydroflame
708c73fa0f hotfix getPlayer missing factions (#831) 2021-04-01 12:55:43 -04:00
hydroflame
c7febd5551 Failed crime no longer pretend you gained money. (#830) 2021-04-01 00:06:00 -04:00
hydroflame
ddbdf66d00 hotfix analyze (#828)
* hotfix analyze
2021-03-31 11:45:55 -04:00
79 changed files with 1904 additions and 497 deletions

10
README_contribution.md Normal file
View File

@@ -0,0 +1,10 @@
Deploying a new version
-----------------------
Update the following
- `src/Constants.ts` `Version` and `LatestUpdate`
- `package.json` `version`
- `doc/source/conf.py` `version` and `release`
- `doc/source/changelog.rst`
- post to discord
- post to reddit.com/r/Bitburner

View File

@@ -184,5 +184,4 @@
#infiltration-buttons .a-link-button { #infiltration-buttons .a-link-button {
display: inline; display: inline;
width: 25%;
} }

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([395,0]),o()}({338:function(n,t,o){},340:function(n,t,o){},342:function(n,t,o){},344:function(n,t,o){},346:function(n,t,o){},348:function(n,t,o){},350:function(n,t,o){},352:function(n,t,o){},354:function(n,t,o){},356:function(n,t,o){},358:function(n,t,o){},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){},395:function(n,t,o){"use strict";o.r(t);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),o(342),o(340),o(338)}}); !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

10
dist/engineStyle.css vendored
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;
@@ -1374,8 +1375,7 @@ button {
margin-top: 20px; } margin-top: 20px; }
#infiltration-buttons .a-link-button { #infiltration-buttons .a-link-button {
display: inline; display: inline; }
width: 25%; }
/** /**
* Styling for the Augmentations UI. This is the page that displays all of the * Styling for the Augmentations UI. This is the page that displays all of the

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,6 +3,127 @@
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)
------------------------------------------------------------------------------------------
**Netscript**
* 'getPlayer' returns players faction and tor
* '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.**
* 'fl1ght.exe' will no longer suggest the combat path. Related faction
requirements unchanged.
v0.51.0 - 2021-03-31 Formulas (hydroflame) v0.51.0 - 2021-03-31 Formulas (hydroflame)
------------------------------------------ ------------------------------------------

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.0' 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

@@ -0,0 +1,12 @@
hashCapacity() Netscript Function
=================================
.. warning:: This page contains spoilers for the game
.. js:function:: hashCapacity()
:RAM cost: 0 GB
:returns: The players maximum hash capacity.
.. note:: This function is only applicable for Hacknet Servers (the upgraded version of
a Hacknet Node).

View File

@@ -46,6 +46,7 @@ In :ref:`netscriptjs`::
upgradeCache() <hacknetnodeapi/upgradeCache> upgradeCache() <hacknetnodeapi/upgradeCache>
getCacheUpgradeCost() <hacknetnodeapi/getCacheUpgradeCost> getCacheUpgradeCost() <hacknetnodeapi/getCacheUpgradeCost>
numHashes() <hacknetnodeapi/numHashes> numHashes() <hacknetnodeapi/numHashes>
hashCapacity() <hacknetnodeapi/hashCapacity>
hashCost() <hacknetnodeapi/hashCost> hashCost() <hacknetnodeapi/hashCost>
spendHashes() <hacknetnodeapi/spendHashes> spendHashes() <hacknetnodeapi/spendHashes>
getHashUpgradeLevel() <hacknetnodeapi/getHashUpgradeLevel> getHashUpgradeLevel() <hacknetnodeapi/getHashUpgradeLevel>

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

@@ -28,6 +28,7 @@ level 3, then you will be able to access all of the Singularity Functions.
connect() <singularityfunctions/connect> connect() <singularityfunctions/connect>
manualHack() <singularityfunctions/manualHack> manualHack() <singularityfunctions/manualHack>
getPlayer() <singularityfunctions/getPlayer> getPlayer() <singularityfunctions/getPlayer>
hospitalize() <singularityfunctions/hospitalize>
isBusy() <singularityfunctions/isBusy> isBusy() <singularityfunctions/isBusy>
stopAction() <singularityfunctions/stopAction> stopAction() <singularityfunctions/stopAction>
upgradeHomeRam() <singularityfunctions/upgradeHomeRam> upgradeHomeRam() <singularityfunctions/upgradeHomeRam>

View File

@@ -93,6 +93,8 @@ getPlayer() Netscript Function
playtimeSinceLastAug playtimeSinceLastAug
playtimeSinceLastBitnode playtimeSinceLastBitnode
jobs jobs
factions
tor
} }
Example:: Example::

View File

@@ -0,0 +1,11 @@
hospitalize() Netscript Function
===================================
.. js:function:: hospitalize()
:RAM cost: 1 GB
:returns: The cost of your visit to the hospital.
If you are not in BitNode-4, then you must have Level 1 of Source-File 4 in order to use this function.
Hospitalize yourself. Recovering all lost hp.

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>
@@ -261,16 +261,16 @@
<div id="infiltration-left-panel"> <div id="infiltration-left-panel">
<p id="infiltration-level-text"> </p> <p id="infiltration-level-text"> </p>
<div id="infiltration-buttons"> <div id="infiltration-buttons">
<a class="a-link-button tooltip" id="infiltration-kill"> </a> <button class="a-link-button tooltip" id="infiltration-kill"> </button>
<a class="a-link-button tooltip" id="infiltration-knockout"> </a> <button class="a-link-button tooltip" id="infiltration-knockout"> </button>
<a class="a-link-button tooltip" id="infiltration-stealthknockout"> </a> <button class="a-link-button tooltip" id="infiltration-stealthknockout"> </button>
<a class="a-link-button tooltip" id="infiltration-assassinate"> </a> <button class="a-link-button tooltip" id="infiltration-assassinate"> </button>
<a class="a-link-button tooltip" id="infiltration-hacksecurity"> </a> <button class="a-link-button tooltip" id="infiltration-hacksecurity"> </button>
<a class="a-link-button tooltip" id="infiltration-destroysecurity"> </a> <button class="a-link-button tooltip" id="infiltration-destroysecurity"> </button>
<a class="a-link-button tooltip" id="infiltration-sneak"> </a> <button class="a-link-button tooltip" id="infiltration-sneak"> </button>
<a class="a-link-button tooltip" id="infiltration-pickdoor"> </a> <button class="a-link-button tooltip" id="infiltration-pickdoor"> </button>
<a class="a-link-button tooltip" id="infiltration-bribe"> </a> <button class="a-link-button tooltip" id="infiltration-bribe"> </button>
<a class="a-link-button tooltip" id="infiltration-escape"> </a> <button class="a-link-button tooltip" id="infiltration-escape"> </button>
</div> </div>
</div> </div>
<div id="infiltration-right-panel"> <div id="infiltration-right-panel">
@@ -381,13 +381,13 @@
<!-- 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 -->
<div id="game-options-container" class="popup-box-container"> <div id="game-options-container" class="popup-box-container">
<div id="game-options-content" class="game-options-box"> <div id="game-options-content" class="game-options-box">
<button id="game-options-close-button">&times;</button> <button id="game-options-close-button" aria-label="close options dialog">&times;</button>
<h1> Game Options </h1> <h1> Game Options </h1>
<br/> <br/>
<div id="game-options-left-panel"> <div id="game-options-left-panel">
@@ -511,6 +511,16 @@
<input class="optionCheckbox" type="checkbox" name="settingsDisableHotkeys" id="settingsDisableHotkeys"> <input class="optionCheckbox" type="checkbox" name="settingsDisableHotkeys" id="settingsDisableHotkeys">
</fieldset> </fieldset>
<!-- View city as list of buttons instead of ASCII art. -->
<fieldset>
<label for="settingsDisableASCIIArt" class="tooltip">Disable ASCII art:
<span class="tooltiptexthigh">
If this is set all ASCII art will be disabled.
</span>
</label>
<input class="optionCheckbox" type="checkbox" name="settingsDisableASCIIArt" id="settingsDisableASCIIArt">
</fieldset>
<!-- Locale for displaying numbers --> <!-- Locale for displaying numbers -->
<fieldset> <fieldset>
<label for="settingsLocale" class="tooltip">Locale: <label for="settingsLocale" class="tooltip">Locale:
@@ -549,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.50.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

@@ -8,6 +8,7 @@ import { Factions, factionExists } from "./Faction/Factions";
import { joinFaction, displayFactionContent } from "./Faction/FactionHelpers"; import { joinFaction, displayFactionContent } from "./Faction/FactionHelpers";
import { Player } from "./Player"; import { Player } from "./Player";
import { hackWorldDaemon, redPillFlag } from "./RedPill"; import { hackWorldDaemon, redPillFlag } from "./RedPill";
import { calculateHospitalizationCost } from "./Hospital/Hospital";
import { Page, routing } from "./ui/navigationTracking"; import { Page, routing } from "./ui/navigationTracking";
import { numeralWrapper } from "./ui/numeralFormat"; import { numeralWrapper } from "./ui/numeralFormat";
@@ -728,9 +729,10 @@ Bladeburner.prototype.completeAction = function() {
damage = action.hpLoss * difficultyMultiplier; damage = action.hpLoss * difficultyMultiplier;
damage = Math.ceil(addOffset(damage, 10)); damage = Math.ceil(addOffset(damage, 10));
this.hpLost += damage; this.hpLost += damage;
const cost = calculateHospitalizationCost(Player, damage);
if (Player.takeDamage(damage)) { if (Player.takeDamage(damage)) {
++this.numHosp; ++this.numHosp;
this.moneyLost += (CONSTANTS.HospitalCostPerHp * Player.max_hp); this.moneyLost += cost;
} }
} }
var logLossText = ""; var logLossText = "";
@@ -800,9 +802,10 @@ Bladeburner.prototype.completeAction = function() {
if (action.hpLoss) { if (action.hpLoss) {
damage = action.hpLoss * difficultyMultiplier; damage = action.hpLoss * difficultyMultiplier;
damage = Math.ceil(addOffset(damage, 10)); damage = Math.ceil(addOffset(damage, 10));
const cost = calculateHospitalizationCost(Player, damage);
if (Player.takeDamage(damage)) { if (Player.takeDamage(damage)) {
++this.numHosp; ++this.numHosp;
this.moneyLost += (CONSTANTS.HospitalCostPerHp * Player.max_hp); this.moneyLost += cost;
} }
} }
teamLossMax = Math.floor(teamCount); teamLossMax = Math.floor(teamCount);
@@ -1066,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() {
@@ -1771,7 +1778,7 @@ Bladeburner.prototype.updateOverviewContent = function() {
Stamina Penalty: {formatNumber((1-this.calculateStaminaPenalty())*100, 1)}%<br /><br /> Stamina Penalty: {formatNumber((1-this.calculateStaminaPenalty())*100, 1)}%<br /><br />
Team Size: {formatNumber(this.teamSize, 0)}<br /> Team Size: {formatNumber(this.teamSize, 0)}<br />
Team Members Lost: {formatNumber(this.teamLost, 0)}<br /><br /> Team Members Lost: {formatNumber(this.teamLost, 0)}<br /><br />
Num Times Hospitalized: this.numHosp<br /> Num Times Hospitalized: {this.numHosp}<br />
Money Lost From Hospitalizations: {Money(this.moneyLost)}<br /><br /> Money Lost From Hospitalizations: {Money(this.moneyLost)}<br /><br />
Current City: {this.city}<br /> Current City: {this.city}<br />
</>, DomElems.overviewGen1); </>, DomElems.overviewGen1);

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.0", 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,25 +228,34 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate: LatestUpdate:
` `
v0.51.0 - 2021-03-31 Formulas (hydroflame) v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame)
------- -------
Formulas API Manual hacking
* A new API is introduced, this gives players access to various formulas used in the game. * These bonus require an install or a soft reset to take effect.
It'll help you make more informed decisions. * 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
* 'getServer' is a new function meant to be used with the formulas API. * growthAnalyze handles Infinity correctly.
* 'getPlayer' is a new function meant to be used with the formulas API.
* 'getStats' and 'getCharacterInformation' are deprecated in favor of 'getPlayer'
* 'getCurrentServer' is a new function that returns the server the player is currently connected.
Display Misc.
* All money should now consistently be orange. * Faction Augmentation will list how much reputation is required even after
* All rep should now consistently be light-yellow. that goal has been reached.
* Most numbers should display consistently now (aka all money is formatted the same). * Removed dollar sign in travel agency confirmation dialog box.
* Fixed typo in alpha-omega.lit
Click to copy * the 'Game saved!' text no longer blocks the save game/options button.
* Certain UI elements are now 'click-to-copy' * 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

@@ -42,11 +42,9 @@ export class GeneralInfo extends React.Component {
hackers all around the world to anonymously share computing power and hackers all around the world to anonymously share computing power and
perform distributed cyberattacks without the fear of being traced. perform distributed cyberattacks without the fear of being traced.
</p> </p>
<br />
<p className={"hacknet-general-info"}> <p className={"hacknet-general-info"}>
{this.getSecondParagraph()} {this.getSecondParagraph()}
</p> </p>
<br />
<p className={"hacknet-general-info"}> <p className={"hacknet-general-info"}>
{this.getThirdParagraph()} {this.getThirdParagraph()}
</p> </p>

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>
@@ -134,19 +133,22 @@ export class HacknetNode extends React.Component {
</span> </span>
</div> </div>
<div className={"row"}> <div className={"row"}>
<p>Level:</p><span className={"text upgradable-info"}>{node.level}</span> <p>Level:</p>
<span className={"text upgradable-info"}>{node.level}</span>
<button className={upgradeLevelClass} onClick={upgradeLevelOnClick}> <button className={upgradeLevelClass} onClick={upgradeLevelOnClick}>
{upgradeLevelContent} {upgradeLevelContent}
</button> </button>
</div> </div>
<div className={"row"}> <div className={"row"}>
<p>RAM:</p><span className={"text upgradable-info"}>{node.ram}GB</span> <p>RAM:</p>
<span className={"text upgradable-info"}>{node.ram}GB</span>
<button className={upgradeRamClass} onClick={upgradeRamOnClick}> <button className={upgradeRamClass} onClick={upgradeRamOnClick}>
{upgradeRamContent} {upgradeRamContent}
</button> </button>
</div> </div>
<div className={"row"}> <div className={"row"}>
<p>Cores:</p><span className={"text upgradable-info"}>{node.cores}</span> <p>Cores:</p>
<span className={"text upgradable-info"}>{node.cores}</span>
<button className={upgradeCoresClass} onClick={upgradeCoresOnClick}> <button className={upgradeCoresClass} onClick={upgradeCoresOnClick}>
{upgradeCoresContent} {upgradeCoresContent}
</button> </button>

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>

26
src/Hospital/Hospital.ts Normal file
View File

@@ -0,0 +1,26 @@
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer"
export function getHospitalizationCost(p: IPlayer): number {
let money;
if (typeof p.money === 'number') {
money = p.money;
} else {
money = p.money.toNumber();
}
if (money < 0) {
return 0;
}
return Math.min(money*0.1, (p.max_hp - p.hp) * CONSTANTS.HospitalCostPerHp);
}
export function calculateHospitalizationCost(p: IPlayer, damage: number): number {
const oldhp = p.hp;
p.hp -= damage
if (p.hp < 0) p.hp = 0;
const cost = getHospitalizationCost(p);
p.hp = oldhp;
return cost;
}

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

@@ -7,6 +7,7 @@ import * as React from "react";
import { City } from "../City"; import { City } from "../City";
import { LocationName } from "../data/LocationNames"; import { LocationName } from "../data/LocationNames";
import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
@@ -16,7 +17,7 @@ type IProps = {
} }
export class LocationCity extends React.Component<IProps, any> { export class LocationCity extends React.Component<IProps, any> {
render() { asciiCity() {
const thiscity = this; const thiscity = this;
const topprop = this.props const topprop = this.props
@@ -66,9 +67,29 @@ export class LocationCity extends React.Component<IProps, any> {
elems.push(<pre key={i}>{lineElems(lines[i])}</pre>) elems.push(<pre key={i}>{lineElems(lines[i])}</pre>)
} }
return elems;
}
listCity() {
const locationButtons = this.props.city.locations.map((locName) => {
return (
<li key={locName}>
<StdButton onClick={this.props.enterLocation.bind(this, locName)} text={locName} />
</li>
)
});
return (
<ul>
{locationButtons}
</ul>
)
}
render() {
return ( return (
<> <>
{elems} {Settings.DisableASCIIArt ? this.listCity() : this.asciiCity()}
</> </>
) )
} }

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

@@ -7,6 +7,7 @@ import * as React from "react";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { getHospitalizationCost } from "../../Hospital/Hospital";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton"; import { AutoupdatingStdButton } from "../../ui/React/AutoupdatingStdButton";
@@ -42,7 +43,7 @@ export class HospitalLocation extends React.Component<IProps, IState> {
} }
getCost(): number { getCost(): number {
return (this.props.p.max_hp - this.props.p.hp) * CONSTANTS.HospitalCostPerHp; return getHospitalizationCost(this.props.p);
} }
getHealed(e: React.MouseEvent<HTMLElement>): void { getHealed(e: React.MouseEvent<HTMLElement>): void {

View File

@@ -10,6 +10,7 @@ import { createTravelPopup } from "../LocationsHelpers";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { Settings } from "../../Settings/Settings";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { StdButton } from "../../ui/React/StdButton"; import { StdButton } from "../../ui/React/StdButton";
@@ -32,7 +33,7 @@ export class TravelAgencyLocation extends React.Component<IProps, any> {
this.btnStyle = { display: "block" }; this.btnStyle = { display: "block" };
} }
render() { asciiWorldMap() {
const thisTravelAgencyLocation = this; const thisTravelAgencyLocation = this;
function LocationLetter(props: any) { function LocationLetter(props: any) {
@@ -76,4 +77,42 @@ export class TravelAgencyLocation extends React.Component<IProps, any> {
</div> </div>
) )
} }
listWorldMap() {
const travelBtns: React.ReactNode[] = [];
for (const key in CityName) {
const city = CityName[key];
// Skip current city
if (city === this.props.p.city) { continue; }
travelBtns.push(
<StdButton
key={city}
onClick={createTravelPopup.bind(null, city, this.props.travel)}
style={this.btnStyle}
text={`Travel to ${city}`}
/>
)
}
return (
<div>
<p>
From here, you can travel to any other city! A ticket
costs {Money(CONSTANTS.TravelCost)}.
</p>
{travelBtns}
</div>
)
}
render() {
if (Settings.DisableASCIIArt) {
return this.listWorldMap();
} else {
return this.asciiWorldMap();
}
}
} }

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

@@ -183,6 +183,7 @@ export const RamCosts: IMap<any> = {
getStats: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, getStats: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
getCharacterInformation: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, getCharacterInformation: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
getPlayer: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, getPlayer: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
hospitalize: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
isBusy: () => RamCostConstants.ScriptSingularityFn1RamCost / 4, isBusy: () => RamCostConstants.ScriptSingularityFn1RamCost / 4,
stopAction: () => RamCostConstants.ScriptSingularityFn1RamCost / 2, stopAction: () => RamCostConstants.ScriptSingularityFn1RamCost / 2,
upgradeHomeRam: () => RamCostConstants.ScriptSingularityFn2RamCost, upgradeHomeRam: () => RamCostConstants.ScriptSingularityFn2RamCost,

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();
@@ -766,6 +782,10 @@ function NetscriptFunctions(workerScript) {
if (!hasHacknetServers()) { return 0; } if (!hasHacknetServers()) { return 0; }
return Player.hashManager.hashes; return Player.hashManager.hashes;
}, },
hashCapacity: function() {
if (!hasHacknetServers()) { return 0; }
return Player.hashManager.capacity;
},
hashCost : function(upgName) { hashCost : function(upgName) {
if (!hasHacknetServers()) { return Infinity; } if (!hasHacknetServers()) { return Infinity; }
@@ -904,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}.`);
} }
@@ -940,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();
@@ -2018,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)) {
@@ -2970,10 +2989,17 @@ function NetscriptFunctions(workerScript) {
playtimeSinceLastAug: Player.playtimeSinceLastAug, playtimeSinceLastAug: Player.playtimeSinceLastAug,
playtimeSinceLastBitnode: Player.playtimeSinceLastBitnode, playtimeSinceLastBitnode: Player.playtimeSinceLastBitnode,
jobs: {}, jobs: {},
factions: Player.factions.slice(),
tor: SpecialServerIps.hasOwnProperty("Darkweb Server"),
}; };
Object.assign(data.jobs, Player.jobs); Object.assign(data.jobs, Player.jobs);
return data; return data;
}, },
hospitalize: function() {
updateDynamicRam("hospitalize", getRamCost("hospitalize"));
checkSingularityAccess("hospitalize", 1);
return Player.hospitalize();
},
isBusy: function() { isBusy: function() {
updateDynamicRam("isBusy", getRamCost("isBusy")); updateDynamicRam("isBusy", getRamCost("isBusy"));
checkSingularityAccess("isBusy", 1); checkSingularityAccess("isBusy", 1);
@@ -3574,27 +3600,35 @@ function NetscriptFunctions(workerScript) {
checkGangApiAccess("getMemberInformation"); checkGangApiAccess("getMemberInformation");
const member = getGangMember("getMemberInformation", name); const member = getGangMember("getMemberInformation", name);
return { return {
agility: member.agi, name: member.name,
agilityEquipMult: member.agi_mult, task: member.task,
agilityAscensionMult: member.agi_asc_mult, earnedRespect: member.earnedRespect,
augmentations: member.augmentations.slice(), hack: member.hack,
charisma: member.cha, str: member.str,
charismaEquipMult: member.cha_mult, def: member.def,
charismaAscensionMult: member.cha_asc_mult, dex: member.dex,
defense: member.def, agi: member.agi,
defenseEquipMult: member.def_mult, cha: member.cha,
defenseAscensionMult: member.def_asc_mult, hack_exp: member.hack_exp,
dexterity: member.dex, str_exp: member.str_exp,
dexterityEquipMult: member.dex_mult, def_exp: member.def_exp,
dexterityAscensionMult: member.dex_asc_mult, dex_exp: member.dex_exp,
equipment: member.upgrades.slice(), agi_exp: member.agi_exp,
hacking: member.hack, cha_exp: member.cha_exp,
hackingEquipMult: member.hack_mult, hack_mult: member.hack_mult,
hackingAscensionMult: member.hack_asc_mult, str_mult: member.str_mult,
strength: member.str, def_mult: member.def_mult,
strengthEquipMult: member.str_mult, dex_mult: member.dex_mult,
strengthAscensionMult: member.str_asc_mult, agi_mult: member.agi_mult,
task: member.task, cha_mult: member.cha_mult,
hack_asc_mult: member.hack_asc_mult,
str_asc_mult: member.str_asc_mult,
def_asc_mult: member.def_asc_mult,
dex_asc_mult: member.dex_asc_mult,
agi_asc_mult: member.agi_asc_mult,
cha_asc_mult: member.cha_asc_mult,
upgrades: member.upgrades.slice(),
augmentations: member.augmentations.slice(),
} }
}, },
canRecruitMember: function() { canRecruitMember: function() {
@@ -4366,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,
@@ -43,6 +48,7 @@ import { applyExploit } from "../../Exploits/applyExploits";
import { SourceFiles } from "../../SourceFile/SourceFiles"; import { SourceFiles } from "../../SourceFile/SourceFiles";
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags"; import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { influenceStockThroughCompanyWork } from "../../StockMarket/PlayerInfluencing"; import { influenceStockThroughCompanyWork } from "../../StockMarket/PlayerInfluencing";
import { getHospitalizationCost } from "../../Hospital/Hospital";
import Decimal from "decimal.js"; import Decimal from "decimal.js";
@@ -428,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) {
@@ -438,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) {
@@ -448,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) {
@@ -458,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) {
@@ -468,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) {
@@ -478,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) {
@@ -549,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;
@@ -591,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)
@@ -621,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 />
@@ -635,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();
@@ -852,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"
@@ -869,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"
@@ -878,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) {
@@ -1111,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) {
@@ -1504,7 +1535,6 @@ export function finishCrime(cancelled) {
dialogBoxCreate(<> dialogBoxCreate(<>
Crime failed!<br /><br /> Crime failed!<br /><br />
You gained:<br /> You gained:<br />
{Money(this.workMoneyGained)}<br />
{numeralWrapper.formatExp(this.workHackExpGained)} hacking experience <br /> {numeralWrapper.formatExp(this.workHackExpGained)} hacking experience <br />
{numeralWrapper.formatExp(this.workStrExpGained)} strength experience<br /> {numeralWrapper.formatExp(this.workStrExpGained)} strength experience<br />
{numeralWrapper.formatExp(this.workDefExpGained)} defense experience<br /> {numeralWrapper.formatExp(this.workDefExpGained)} defense experience<br />
@@ -1589,18 +1619,19 @@ export function regenerateHp(amt) {
} }
export function hospitalize() { export function hospitalize() {
const cost = getHospitalizationCost(this);
if (Settings.SuppressHospitalizationPopup === false) { if (Settings.SuppressHospitalizationPopup === false) {
dialogBoxCreate(<> dialogBoxCreate(<>
You were in critical condition! You were taken to the hospital where You were in critical condition! You were taken to the hospital where
luckily they were able to save your life. You were charged&nbsp; luckily they were able to save your life. You were charged&nbsp;
{Money(this.max_hp * CONSTANTS.HospitalCostPerHp)} {Money(cost)}
</>); </>);
} }
const cost = this.max_hp * CONSTANTS.HospitalCostPerHp
this.loseMoney(cost); this.loseMoney(cost);
this.recordMoneySource(-1 * cost, "hospitalization"); this.recordMoneySource(-1 * cost, "hospitalization");
this.hp = this.max_hp; this.hp = this.max_hp;
return cost;
} }
/********* Company job application **********/ /********* Company job application **********/
@@ -2334,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

@@ -20,6 +20,11 @@ interface IDefaultSettings {
*/ */
CodeInstructionRunTime: number; CodeInstructionRunTime: number;
/**
* Render city as list of buttons.
*/
DisableASCIIArt: boolean;
/** /**
* Whether global keyboard shortcuts should be recognized throughout the game. * Whether global keyboard shortcuts should be recognized throughout the game.
*/ */
@@ -101,6 +106,7 @@ interface ISettings extends IDefaultSettings {
const defaultSettings: IDefaultSettings = { const defaultSettings: IDefaultSettings = {
AutosaveInterval: 60, AutosaveInterval: 60,
CodeInstructionRunTime: 50, CodeInstructionRunTime: 50,
DisableASCIIArt: false,
DisableHotkeys: false, DisableHotkeys: false,
Locale: "en", Locale: "en",
MaxLogCapacity: 50, MaxLogCapacity: 50,
@@ -119,6 +125,7 @@ const defaultSettings: IDefaultSettings = {
export const Settings: ISettings & ISelfInitializer & ISelfLoading = { export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
AutosaveInterval: defaultSettings.AutosaveInterval, AutosaveInterval: defaultSettings.AutosaveInterval,
CodeInstructionRunTime: 25, CodeInstructionRunTime: 25,
DisableASCIIArt: defaultSettings.DisableASCIIArt,
DisableHotkeys: defaultSettings.DisableHotkeys, DisableHotkeys: defaultSettings.DisableHotkeys,
Editor: EditorSetting.Ace, Editor: EditorSetting.Ace,
EditorKeybinding: AceKeybindingSetting.Ace, EditorKeybinding: AceKeybindingSetting.Ace,

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)*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();
@@ -2139,23 +2140,11 @@ let Terminal = {
const numAugReq = Math.round(BitNodeMultipliers.DaedalusAugsRequirement*30) const numAugReq = Math.round(BitNodeMultipliers.DaedalusAugsRequirement*30)
const fulfilled = Player.augmentations.length >= numAugReq && const fulfilled = Player.augmentations.length >= numAugReq &&
Player.money.gt(1e11) && Player.money.gt(1e11) &&
((Player.hacking_skill >= 2500)|| Player.hacking_skill >= 2500;
(Player.strength >= 1500 &&
Player.defense >= 1500 &&
Player.dexterity >= 1500 &&
Player.agility >= 1500));
if(!fulfilled) { if(!fulfilled) {
post(`Augmentations: ${Player.augmentations.length} / ${numAugReq}`); post(`Augmentations: ${Player.augmentations.length} / ${numAugReq}`);
postElement(<>Money: {Money(Player.money.toNumber())} / {Money(1e11)}</>); postElement(<>Money: {Money(Player.money.toNumber())} / {Money(1e11)}</>);
post("One path below must be fulfilled...");
post("----------HACKING PATH----------");
post(`Hacking skill: ${Player.hacking_skill} / 2500`); post(`Hacking skill: ${Player.hacking_skill} / 2500`);
post("----------COMBAT PATH----------");
post(`Strength: ${Player.strength} / 1500`);
post(`Defense: ${Player.defense} / 1500`);
post(`Dexterity: ${Player.dexterity} / 1500`);
post(`Agility: ${Player.agility} / 1500`);
return; return;
} }
@@ -2185,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
@@ -2283,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 = [];
@@ -2319,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;
@@ -2345,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
@@ -2391,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>
@@ -274,16 +274,16 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<div id="infiltration-left-panel"> <div id="infiltration-left-panel">
<p id="infiltration-level-text"> </p> <p id="infiltration-level-text"> </p>
<div id="infiltration-buttons"> <div id="infiltration-buttons">
<a class="a-link-button tooltip" id="infiltration-kill"> </a> <button class="a-link-button tooltip" id="infiltration-kill"> </button>
<a class="a-link-button tooltip" id="infiltration-knockout"> </a> <button class="a-link-button tooltip" id="infiltration-knockout"> </button>
<a class="a-link-button tooltip" id="infiltration-stealthknockout"> </a> <button class="a-link-button tooltip" id="infiltration-stealthknockout"> </button>
<a class="a-link-button tooltip" id="infiltration-assassinate"> </a> <button class="a-link-button tooltip" id="infiltration-assassinate"> </button>
<a class="a-link-button tooltip" id="infiltration-hacksecurity"> </a> <button class="a-link-button tooltip" id="infiltration-hacksecurity"> </button>
<a class="a-link-button tooltip" id="infiltration-destroysecurity"> </a> <button class="a-link-button tooltip" id="infiltration-destroysecurity"> </button>
<a class="a-link-button tooltip" id="infiltration-sneak"> </a> <button class="a-link-button tooltip" id="infiltration-sneak"> </button>
<a class="a-link-button tooltip" id="infiltration-pickdoor"> </a> <button class="a-link-button tooltip" id="infiltration-pickdoor"> </button>
<a class="a-link-button tooltip" id="infiltration-bribe"> </a> <button class="a-link-button tooltip" id="infiltration-bribe"> </button>
<a class="a-link-button tooltip" id="infiltration-escape"> </a> <button class="a-link-button tooltip" id="infiltration-escape"> </button>
</div> </div>
</div> </div>
<div id="infiltration-right-panel"> <div id="infiltration-right-panel">
@@ -394,13 +394,13 @@ 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 -->
<div id="game-options-container" class="popup-box-container"> <div id="game-options-container" class="popup-box-container">
<div id="game-options-content" class="game-options-box"> <div id="game-options-content" class="game-options-box">
<button id="game-options-close-button">&times;</button> <button id="game-options-close-button" aria-label="close options dialog">&times;</button>
<h1> Game Options </h1> <h1> Game Options </h1>
<br /> <br />
<div id="game-options-left-panel"> <div id="game-options-left-panel">
@@ -524,6 +524,16 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<input class="optionCheckbox" type="checkbox" name="settingsDisableHotkeys" id="settingsDisableHotkeys"> <input class="optionCheckbox" type="checkbox" name="settingsDisableHotkeys" id="settingsDisableHotkeys">
</fieldset> </fieldset>
<!-- View city as list of buttons instead of ASCII art. -->
<fieldset>
<label for="settingsDisableASCIIArt" class="tooltip">Disable ASCII art:
<span class="tooltiptexthigh">
If this is set all ASCII art will be disabled.
</span>
</label>
<input class="optionCheckbox" type="checkbox" name="settingsDisableASCIIArt" id="settingsDisableASCIIArt">
</fieldset>
<!-- Locale for displaying numbers --> <!-- Locale for displaying numbers -->
<fieldset> <fieldset>
<label for="settingsLocale" class="tooltip">Locale: <label for="settingsLocale" class="tooltip">Locale:
@@ -562,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><br /></> return <><span>{`Hacknet Nodes owned: ${p.hacknetNodes.length}`}</span><br /></>
} else { } else {
return <><span>Hacknet Servers owned: {p.hacknetNodes.length} / {HacknetServerConstants.MaxServers}</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, "");
} }
@@ -97,8 +99,8 @@ export function CharacterInfo(p: IPlayer): React.ReactElement {
<table> <table>
<tbody> <tbody>
{props.rows.map((r: any) => <tr key={r[0]}> {props.rows.map((r: any) => <tr key={r[0]}>
<td key='0'>{r[0]} multiplier: </td> <td key='0'>{`${r[0]} multiplier:`}</td>
<td key='1' style={{textAlign: 'right'}}>{numeralWrapper.formatPercentage(r[1])}</td> <td key='1' style={{textAlign: 'right', paddingLeft: '5px'}}>{numeralWrapper.formatPercentage(r[1])}</td>
{bn5Stat(r)} {bn5Stat(r)}
</tr>)} </tr>)}
</tbody> </tbody>
@@ -234,9 +236,9 @@ export function CharacterInfo(p: IPlayer): React.ReactElement {
]} /><br /><br /> ]} /><br /><br />
<b>Misc.</b><br /><br /> <b>Misc.</b><br /><br />
<span>Servers owned: {p.purchasedServers.length} / {getPurchaseServerLimit()}</span><br /> <span>{`Servers owned: ${p.purchasedServers.length} / ${getPurchaseServerLimit()}`}</span><br />
<Hacknet /> <Hacknet />
<span>Augmentations installed: {p.augmentations.length}</span><br /><br /> <span>{`Augmentations installed: ${p.augmentations.length}`}</span><br /><br />
{StatsTable(timeRows, null)} {StatsTable(timeRows, null)}
<br /> <br />
<CurrentBitNode /> <CurrentBitNode />

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

@@ -23,6 +23,7 @@ function setSettingsLabels() {
const suppressHospitalizationPopup = document.getElementById("settingsSuppressHospitalizationPopup"); const suppressHospitalizationPopup = document.getElementById("settingsSuppressHospitalizationPopup");
const autosaveInterval = document.getElementById("settingsAutosaveIntervalValLabel"); const autosaveInterval = document.getElementById("settingsAutosaveIntervalValLabel");
const disableHotkeys = document.getElementById("settingsDisableHotkeys"); const disableHotkeys = document.getElementById("settingsDisableHotkeys");
const disableASCIIArt = document.getElementById("settingsDisableASCIIArt");
const locale = document.getElementById("settingsLocale"); const locale = document.getElementById("settingsLocale");
//Initialize values on labels //Initialize values on labels
@@ -36,6 +37,7 @@ function setSettingsLabels() {
suppressHospitalizationPopup.checked = Settings.SuppressHospitalizationPopup; suppressHospitalizationPopup.checked = Settings.SuppressHospitalizationPopup;
setAutosaveLabel(autosaveInterval); setAutosaveLabel(autosaveInterval);
disableHotkeys.checked = Settings.DisableHotkeys; disableHotkeys.checked = Settings.DisableHotkeys;
disableASCIIArt.checked = Settings.CityListView;
locale.value = Settings.Locale; locale.value = Settings.Locale;
numeralWrapper.updateLocale(Settings.Locale); //Initialize locale numeralWrapper.updateLocale(Settings.Locale); //Initialize locale
@@ -99,6 +101,10 @@ function setSettingsLabels() {
Settings.DisableHotkeys = this.checked; Settings.DisableHotkeys = this.checked;
} }
disableASCIIArt.onclick = function() {
Settings.DisableASCIIArt = this.checked;
}
//Locale selector //Locale selector
locale.onchange = function() { locale.onchange = function() {
if (!numeralWrapper.updateLocale(locale.value)) { if (!numeralWrapper.updateLocale(locale.value)) {

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 {
@@ -134,7 +136,10 @@ export function yesNoTxtInpBoxGetInput(): string {
export function yesNoTxtInpBoxCreate(txt: string | JSX.Element) { export function yesNoTxtInpBoxCreate(txt: string | JSX.Element) {
yesNoBoxOpen = true; yesNoBoxOpen = true;
if (yesNoTextInputBoxTextElement) { if (yesNoTextInputBoxTextElement) {
ReactDOM.unmountComponentAtNode(yesNoTextInputBoxTextElement);
yesNoTextInputBoxTextElement.innerHTML = '';
if(typeof txt === 'string') { if(typeof txt === 'string') {
yesNoTextInputBoxTextElement.innerHTML = txt; yesNoTextInputBoxTextElement.innerHTML = txt;
} else { } else {