Merge pull request #811 from danielyxie/dev

v0.48.0
This commit is contained in:
hydroflame 2021-03-07 18:46:03 -05:00 committed by GitHub
commit 802f28082d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1006 additions and 388 deletions

@ -59,10 +59,16 @@
} }
#character-hp-wrapper { color: $my-stat-hp-color; } #character-hp-wrapper { color: $my-stat-hp-color; }
.character-hp-cell { color: $my-stat-hp-color; }
#character-money-wrapper { color: $my-stat-money-color; } #character-money-wrapper { color: $my-stat-money-color; }
.character-money-cell { color: $my-stat-money-color; }
#character-hack-wrapper { color: $my-stat-hack-color; } #character-hack-wrapper { color: $my-stat-hack-color; }
.character-hack-cell { color: $my-stat-hack-color; }
#character-cha-wrapper { color: $my-stat-cha-color; } #character-cha-wrapper { color: $my-stat-cha-color; }
.character-cha-cell { color: $my-stat-cha-color; }
#character-int-wrapper { color: $my-stat-int-color; } #character-int-wrapper { color: $my-stat-int-color; }
.character-int-cell { color: $my-stat-int-color; }
.character-combat-cell { color: $my-stat-physical; }
.character-overview-btn { .character-overview-btn {
@include borderRadius(12px); @include borderRadius(12px);

@ -17,7 +17,8 @@ body {
p, p,
pre, pre,
h2, h2,
.text { .text,
td {
color: var(--my-font-color); color: var(--my-font-color);
} }
@ -79,6 +80,28 @@ a:visited {
right: 0; right: 0;
} }
#factions-tab {
position: relative;
}
#factions-notification {
font-size: $defaultFontSize * 0.625;
position: absolute; /* Position the badge within the relatively positioned button */
top: 0;
right: 0;
}
#augmentations-tab {
position: relative;
}
#augmentations-notification {
font-size: $defaultFontSize * 0.625;
position: absolute; /* Position the badge within the relatively positioned button */
top: 0;
right: 0;
}
.notification-on { .notification-on {
background-color: #fa3e3e; background-color: #fa3e3e;
color: #fff; color: #fff;

File diff suppressed because one or more lines are too long

@ -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([362,0]),o()}({305:function(n,t,o){},307:function(n,t,o){},309:function(n,t,o){},311:function(n,t,o){},313:function(n,t,o){},315:function(n,t,o){},317:function(n,t,o){},319:function(n,t,o){},321:function(n,t,o){},323:function(n,t,o){},325:function(n,t,o){},327:function(n,t,o){},329:function(n,t,o){},331:function(n,t,o){},333:function(n,t,o){},335:function(n,t,o){},337:function(n,t,o){},339:function(n,t,o){},341:function(n,t,o){},343:function(n,t,o){},345:function(n,t,o){},347:function(n,t,o){},349:function(n,t,o){},351:function(n,t,o){},353:function(n,t,o){},355:function(n,t,o){},357:function(n,t,o){},359:function(n,t,o){},362:function(n,t,o){"use strict";o.r(t);o(361),o(359),o(357),o(355),o(353),o(351),o(349),o(347),o(345),o(343),o(341),o(339),o(337),o(335),o(333),o(331),o(329),o(327),o(325),o(323),o(321),o(319),o(317),o(315),o(313),o(311),o(309),o(307),o(305)}}); !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([363,0]),o()}({306:function(n,t,o){},308:function(n,t,o){},310:function(n,t,o){},312:function(n,t,o){},314:function(n,t,o){},316:function(n,t,o){},318:function(n,t,o){},320:function(n,t,o){},322:function(n,t,o){},324:function(n,t,o){},326:function(n,t,o){},328:function(n,t,o){},330:function(n,t,o){},332:function(n,t,o){},334:function(n,t,o){},336:function(n,t,o){},338:function(n,t,o){},340:function(n,t,o){},342:function(n,t,o){},344:function(n,t,o){},346:function(n,t,o){},348:function(n,t,o){},350:function(n,t,o){},352:function(n,t,o){},354:function(n,t,o){},356:function(n,t,o){},358:function(n,t,o){},360:function(n,t,o){},363:function(n,t,o){"use strict";o.r(t);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),o(336),o(334),o(332),o(330),o(328),o(326),o(324),o(322),o(320),o(318),o(316),o(314),o(312),o(310),o(308),o(306)}});
//# sourceMappingURL=engineStyle.bundle.js.map //# sourceMappingURL=engineStyle.bundle.js.map

41
dist/engineStyle.css vendored

@ -26,7 +26,8 @@ body {
p, p,
pre, pre,
h2, h2,
.text { .text,
td {
color: var(--my-font-color); } color: var(--my-font-color); }
h1 { h1 {
@ -78,6 +79,26 @@ a:visited {
top: 0; top: 0;
right: 0; } right: 0; }
#factions-tab {
position: relative; }
#factions-notification {
font-size: 10px;
position: absolute;
/* Position the badge within the relatively positioned button */
top: 0;
right: 0; }
#augmentations-tab {
position: relative; }
#augmentations-notification {
font-size: 10px;
position: absolute;
/* Position the badge within the relatively positioned button */
top: 0;
right: 0; }
.notification-on { .notification-on {
background-color: #fa3e3e; background-color: #fa3e3e;
color: #fff; color: #fff;
@ -684,18 +705,36 @@ button {
#character-hp-wrapper { #character-hp-wrapper {
color: #dd3434; } color: #dd3434; }
.character-hp-cell {
color: #dd3434; }
#character-money-wrapper { #character-money-wrapper {
color: #ffd700; } color: #ffd700; }
.character-money-cell {
color: #ffd700; }
#character-hack-wrapper { #character-hack-wrapper {
color: #adff2f; } color: #adff2f; }
.character-hack-cell {
color: #adff2f; }
#character-cha-wrapper { #character-cha-wrapper {
color: #a671d1; } color: #a671d1; }
.character-cha-cell {
color: #a671d1; }
#character-int-wrapper { #character-int-wrapper {
color: #6495ed; } color: #6495ed; }
.character-int-cell {
color: #6495ed; }
.character-combat-cell {
color: #faffdf; }
.character-overview-btn { .character-overview-btn {
-webkit-border-radius: 12px; -webkit-border-radius: 12px;
-moz-border-radius: 12px; -moz-border-radius: 12px;

24
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -3,6 +3,49 @@
Changelog Changelog
========= =========
v0.48.0 - ASCII - 2020-03-07
-------
**ASCII**
* Travel Agency now displays a world map
* Cities are now top view of metro station maps
**Netscript**
* 'softReset' is a new netscript function that performs a soft reset
regardless of if the player has bought augmentations or not.
* 'getAugmentationStats' is a new netscript function that returns the stats of
an augmentation.
* getCharacterInformation now additionally returns exp
* pid resets back to 1 when installing or destroying a BitNode.
* New '.ns' scripts start with a main function.
* 'hacknet.maxNumNodes' returns the maximum number of hacknet nodes.
**Bladeburner**
* Current stamina will scale as max stamina increases, this prevents players
from having very high penalty when they gain huge amount of exp at the
start of a reset.
**Misc.**
* Fixed an issue where SF3 was listed as infinitly repeatable and SF12 as
having a limit of 3.
* Fixed an issue where the gang equipment screen would freeze the game if a
script installed augmentations while it is open.
* All BonusTime now displays in the 'H M S' format.
* Donation textbox style updated to match the rest of the game.
* Corporation name style updated to match the rest of the game.
* minor formatting under Hacking>Active Scripts
* typo in BN12 description
* BN12 now reduces contract money
* Character>Stats percentages are aligned, server and hacknet limit are
displayed, if the player has SF5 the reduces stats are shown.
* Character>Augmentations now displays by how much the player stats will
increase.
* Character>Augmentations has a badge indicating how many augs the player
has bought but not installed
* Character>Factions has a badge indicating how many factions have pending
invites.
v0.47.2 - 7/15/2019 v0.47.2 - 7/15/2019
------------------- -------------------

@ -0,0 +1,8 @@
maxNumNodes() Netscript Function
=============================
.. js:function:: maxNumNodes()
:RAM cost: 0 GB
Returns the maximum number of Hacknet Nodes you can own.

@ -0,0 +1,16 @@
getAugmentationStats() Netscript Function
=========================================
.. js:function:: getAugmentationStats(name)
:RAM cost: 5 GB
:param string name: Name of Augmentation. CASE-SENSITIVE
If you are not in BitNode-4, then you must have Level 3 of Source-File 4 in order to use this function.
ns.getAugmentationStats("Synfibril Muscle")
{
strength_mult: 1.3,
defense_mult: 1.3,
}

@ -49,4 +49,10 @@ getCharacterInformation() Netscript Function
workChaExpGain: Cha experience earned so far from work workChaExpGain: Cha experience earned so far from work
workRepGain: Reputation earned so far from work, if applicable workRepGain: Reputation earned so far from work, if applicable
workMoneyGain: Money earned so far from work, if applicable workMoneyGain: Money earned so far from work, if applicable
hackingExp: Total hacking experience
strengthExp: Total strength experience
defenseExp: Total defense experience
dexterityExp: Total dexterity experience
agilityExp: Total agility experience
charismaExp: Total charisma experience
} }

@ -0,0 +1,8 @@
softReset() Netscript Function
===================================
.. js:function:: softReset()
If you are not in BitNode-4, then you must have Level 3 of Source-File 4 in order to use this function.
This function will perform a reset even if you don't have any augmentation installed.

@ -58,9 +58,11 @@
</li> </li>
<li id="factions-tab" class="mainmenu-accordion-panel"> <li id="factions-tab" class="mainmenu-accordion-panel">
<button id="factions-menu-link"> Factions </button> <button id="factions-menu-link"> Factions </button>
<span id="factions-notification" class="notification-off"> </span>
</li> </li>
<li id="augmentations-tab" class="mainmenu-accordion-panel"> <li id="augmentations-tab" class="mainmenu-accordion-panel">
<button id="augmentations-menu-link" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> Augmentations </button> <button id="augmentations-menu-link" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> Augmentations </button>
<span id="augmentations-notification" class="notification-off"> </span>
</li> </li>
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel"> <li id="hacknet-nodes-tab" class="mainmenu-accordion-panel">
<button id="hacknet-nodes-menu-link"> Hacknet Nodes </button> <button id="hacknet-nodes-menu-link"> Hacknet Nodes </button>

@ -5,92 +5,104 @@ import * as React from "react";
import { Player } from "../../Player"; import { Player } from "../../Player";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { Augmentations} from "../Augmentations";
function calculateAugmentedStats() {
const augP: any = {};
for(const aug of Player.queuedAugmentations) {
const augObj = Augmentations[aug.name];
for (const mult in augObj.mults) {
const v = augP[mult] ? augP[mult] : 1;
augP[mult] = v * augObj.mults[mult];
}
}
return augP;
}
export function PlayerMultipliers(): React.ReactElement { export function PlayerMultipliers(): React.ReactElement {
const mults = calculateAugmentedStats();
function MultiplierTable(rows: any[]): React.ReactElement {
function improvements(r: number) {
let elems: any[] = [];
if(r) {
elems = [
<td key='2'>&nbsp;=>&nbsp;</td>,
<td key='3'>{numeralWrapper.formatPercentage(r)}</td>
];
}
return elems;
}
return <table>
<tbody>
{rows.map((r: any) => <tr key={r[0]}>
<td key='0'><span>{r[0]} multiplier:&nbsp;</span></td>
<td key='1' style={{textAlign: 'right'}}>{numeralWrapper.formatPercentage(r[1])}</td>
{improvements(r[2])}
</tr>)}
</tbody>
</table>
}
return ( return (
<> <>
<p><strong><u>Total Multipliers:</u></strong></p> <p><strong><u>Multipliers:</u></strong></p><br />
{MultiplierTable([
['Hacking Chance ', Player.hacking_chance_mult, Player.hacking_chance_mult*mults.hacking_chance_mult],
['Hacking Speed ', Player.hacking_speed_mult, Player.hacking_speed_mult*mults.hacking_speed_mult],
['Hacking Money ', Player.hacking_money_mult, Player.hacking_money_mult*mults.hacking_money_mult],
['Hacking Growth ', Player.hacking_grow_mult, Player.hacking_grow_mult*mults.hacking_grow_mult]
])}<br />
<pre> {MultiplierTable([
{'Hacking Chance multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_chance_mult)} ['Hacking Level ', Player.hacking_mult, Player.hacking_mult*mults.hacking_mult],
</pre> ['Hacking Experience ', Player.hacking_exp_mult, Player.hacking_exp_mult*mults.hacking_exp_mult]
<pre> ])}<br />
{'Hacking Speed multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_speed_mult)}
</pre>
<pre> {MultiplierTable([
{'Hacking Money multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_money_mult)} ['Strength Level ', Player.strength_mult, Player.strength_mult*mults.strength_mult],
</pre> ['Strength Experience ', Player.strength_exp_mult, Player.strength_exp_mult*mults.strength_exp_mult]
<pre> ])}<br />
{'Hacking Growth multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_grow_mult)}
</pre><br /> {MultiplierTable([
<pre> ['Defense Level ', Player.defense_mult, Player.defense_mult*mults.defense_mult],
{'Hacking Level multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_mult)} ['Defense Experience ', Player.defense_exp_mult, Player.defense_exp_mult*mults.defense_exp_mult]
</pre> ])}<br />
<pre>
{'Hacking Experience multiplier: ' + numeralWrapper.formatPercentage(Player.hacking_exp_mult)} {MultiplierTable([
</pre> ['Dexterity Level ', Player.dexterity_mult, Player.dexterity_mult*mults.dexterity_mult],
<br /> ['Dexterity Experience ', Player.dexterity_exp_mult, Player.dexterity_exp_mult*mults.dexterity_exp_mult]
<pre> ])}<br />
{'Strength Level multiplier: ' + numeralWrapper.formatPercentage(Player.strength_mult)}
</pre> {MultiplierTable([
<pre> ['Agility Level ', Player.agility_mult, Player.agility_mult*mults.agility_mult],
{'Strength Experience multiplier: ' + numeralWrapper.formatPercentage(Player.strength_exp_mult)} ['Agility Experience ', Player.agility_exp_mult, Player.agility_exp_mult*mults.agility_exp_mult]
</pre> ])}<br />
<br />
<pre> {MultiplierTable([
{'Defense Level multiplier: ' + numeralWrapper.formatPercentage(Player.defense_mult)} ['Charisma Level ', Player.charisma_mult, Player.charisma_mult*mults.charisma_mult],
</pre> ['Charisma Experience ', Player.charisma_exp_mult, Player.charisma_exp_mult*mults.charisma_exp_mult]
<pre> ])}<br />
{'Defense Experience multiplier: ' + numeralWrapper.formatPercentage(Player.defense_exp_mult)}
</pre><br /> {MultiplierTable([
<pre> ['Hacknet Node production ', Player.hacknet_node_money_mult, Player.hacknet_node_money_mult*mults.hacknet_node_money_mult],
{'Dexterity Level multiplier: ' + numeralWrapper.formatPercentage(Player.dexterity_mult)} ['Hacknet Node purchase cost ', Player.hacknet_node_purchase_cost_mult, Player.hacknet_node_purchase_cost_mult*mults.hacknet_node_purchase_cost_mult],
</pre> ['Hacknet Node RAM upgrade cost ', Player.hacknet_node_ram_cost_mult, Player.hacknet_node_ram_cost_mult*mults.hacknet_node_ram_cost_mult],
<pre> ['Hacknet Node Core purchase cost ', Player.hacknet_node_core_cost_mult, Player.hacknet_node_core_cost_mult*mults.hacknet_node_core_cost_mult],
{'Dexterity Experience multiplier: ' + numeralWrapper.formatPercentage(Player.dexterity_exp_mult)} ['Hacknet Node level upgrade cost ', Player.hacknet_node_level_cost_mult, Player.hacknet_node_level_cost_mult*mults.hacknet_node_level_cost_mult]
</pre><br /> ])}<br />
<pre>
{'Agility Level multiplier: ' + numeralWrapper.formatPercentage(Player.agility_mult)} {MultiplierTable([
</pre> ['Company reputation gain ', Player.company_rep_mult, Player.company_rep_mult*mults.company_rep_mult],
<pre> ['Faction reputation gain ', Player.faction_rep_mult, Player.faction_rep_mult*mults.faction_rep_mult],
{'Agility Experience multiplier: ' + numeralWrapper.formatPercentage(Player.agility_exp_mult)} ['Salary ', Player.work_money_mult, Player.work_money_mult*mults.work_money_mult]
</pre><br /> ])}<br />
<pre>
{'Charisma Level multiplier: ' + numeralWrapper.formatPercentage(Player.charisma_mult)} {MultiplierTable([
</pre> ['Crime success ', Player.crime_success_mult, Player.crime_success_mult*mults.crime_success_mult],
<pre> ['Crime money ', Player.crime_money_mult, Player.crime_money_mult*mults.crime_money_mult],
{'Charisma Experience multiplier: ' + numeralWrapper.formatPercentage(Player.charisma_exp_mult)} ])}<br />
</pre><br />
<pre>
{'Hacknet Node production multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_money_mult)}
</pre>
<pre>
{'Hacknet Node purchase cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_purchase_cost_mult)}
</pre>
<pre>
{'Hacknet Node RAM upgrade cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_ram_cost_mult)}
</pre>
<pre>
{'Hacknet Node Core purchase cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_core_cost_mult)}
</pre>
<pre>
{'Hacknet Node level upgrade cost multiplier: ' + numeralWrapper.formatPercentage(Player.hacknet_node_level_cost_mult)}
</pre><br />
<pre>
{'Company reputation gain multiplier: ' + numeralWrapper.formatPercentage(Player.company_rep_mult)}
</pre>
<pre>
{'Faction reputation gain multiplier: ' + numeralWrapper.formatPercentage(Player.faction_rep_mult)}
</pre>
<pre>
{'Salary multiplier: ' + numeralWrapper.formatPercentage(Player.work_money_mult)}
</pre><br />
<pre>
{'Crime success multiplier: ' + numeralWrapper.formatPercentage(Player.crime_success_mult)}
</pre>
<pre>
{'Crime money multiplier: ' + numeralWrapper.formatPercentage(Player.crime_money_mult)}
</pre>
</> </>
) )
} }

@ -234,7 +234,7 @@ BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.",
"Level 3: 56%"); "Level 3: 56%");
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.", BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
"To iterate is human, to recurse divine.<br><br>" + "To iterate is human, to recurse divine.<br><br>" +
"Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give your Souce-File 12, or " + "Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give you Souce-File 12, or " +
"if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " + "if you already have this Source-File it will upgrade its level. There is no maximum level for Source-File 12. Each level " +
"of Source-File 12 will increase all of your multipliers by 1%. This effect is multiplicative with itself. " + "of Source-File 12 will increase all of your multipliers by 1%. This effect is multiplicative with itself. " +
"In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)"); "In other words, level N of this Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers that decrease)");
@ -465,6 +465,7 @@ export function initBitNodeMultipliers(p: IPlayer) {
BitNodeMultipliers.CompanyWorkMoney = dec; BitNodeMultipliers.CompanyWorkMoney = dec;
BitNodeMultipliers.CrimeMoney = dec; BitNodeMultipliers.CrimeMoney = dec;
BitNodeMultipliers.HacknetNodeMoney = dec; BitNodeMultipliers.HacknetNodeMoney = dec;
BitNodeMultipliers.CodingContractMoney = dec;
BitNodeMultipliers.CompanyWorkExpGain = dec; BitNodeMultipliers.CompanyWorkExpGain = dec;
BitNodeMultipliers.ClassGymExpGain = dec; BitNodeMultipliers.ClassGymExpGain = dec;

@ -31,6 +31,7 @@ import { KEY } from "../utils/helpers/keyCodes";
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement"; import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
import { appendLineBreaks } from "../utils/uiHelpers/appendLineBreaks"; import { appendLineBreaks } from "../utils/uiHelpers/appendLineBreaks";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { createElement } from "../utils/uiHelpers/createElement"; import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup"; import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElement } from "../utils/uiHelpers/removeElement"; import { removeElement } from "../utils/uiHelpers/removeElement";
@ -991,12 +992,16 @@ Bladeburner.prototype.process = function() {
} }
Bladeburner.prototype.calculateMaxStamina = function() { Bladeburner.prototype.calculateMaxStamina = function() {
var effAgility = Player.agility * this.skillMultipliers.effAgi; const effAgility = Player.agility * this.skillMultipliers.effAgi;
var maxStamina = (Math.pow(effAgility, 0.8) + this.staminaBonus); let maxStamina = (Math.pow(effAgility, 0.8) + this.staminaBonus) *
maxStamina *= this.skillMultipliers.stamina; this.skillMultipliers.stamina *
maxStamina *= Player.bladeburner_max_stamina_mult; Player.bladeburner_max_stamina_mult;
if (isNaN(maxStamina)) {throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");} if (this.maxStamina !== maxStamina) {
const oldMax = this.maxStamina;
this.maxStamina = maxStamina; this.maxStamina = maxStamina;
this.stamina = this.maxStamina * this.stamina / oldMax;
}
if (isNaN(maxStamina)) {throw new Error("Max Stamina calculated to be NaN in Bladeburner.calculateMaxStamina()");}
} }
Bladeburner.prototype.calculateStaminaGainPerSecond = function() { Bladeburner.prototype.calculateStaminaGainPerSecond = function() {
@ -2349,7 +2354,7 @@ Bladeburner.prototype.updateOverviewContent = function() {
DomElems.overviewEstComms.childNodes[0].nodeValue = "Est. Synthoid Communities: " + formatNumber(this.getCurrentCity().comms, 0); DomElems.overviewEstComms.childNodes[0].nodeValue = "Est. Synthoid Communities: " + formatNumber(this.getCurrentCity().comms, 0);
DomElems.overviewChaos.childNodes[0].nodeValue = "City Chaos: " + formatNumber(this.getCurrentCity().chaos); DomElems.overviewChaos.childNodes[0].nodeValue = "City Chaos: " + formatNumber(this.getCurrentCity().chaos);
DomElems.overviewSkillPoints.innerText = "Skill Points: " + formatNumber(this.skillPoints, 0); DomElems.overviewSkillPoints.innerText = "Skill Points: " + formatNumber(this.skillPoints, 0);
DomElems.overviewBonusTime.childNodes[0].nodeValue = "Bonus time: " + this.storedCycles/CyclesPerSecond; DomElems.overviewBonusTime.childNodes[0].nodeValue = "Bonus time: " + convertTimeMsToTimeElapsedString(this.storedCycles/CyclesPerSecond*1000);
DomElems.overviewAugSuccessMult.innerText = "Aug. Success Chance Mult: " + formatNumber(Player.bladeburner_success_chance_mult*100, 1) + "%"; DomElems.overviewAugSuccessMult.innerText = "Aug. Success Chance Mult: " + formatNumber(Player.bladeburner_success_chance_mult*100, 1) + "%";
DomElems.overviewAugMaxStaminaMult.innerText = "Aug. Max Stamina Mult: " + formatNumber(Player.bladeburner_max_stamina_mult*100, 1) + "%"; DomElems.overviewAugMaxStaminaMult.innerText = "Aug. Max Stamina Mult: " + formatNumber(Player.bladeburner_max_stamina_mult*100, 1) + "%";
DomElems.overviewAugStaminaGainMult.innerText = "Aug. Stamina Gain Mult: " + formatNumber(Player.bladeburner_stamina_gain_mult*100, 1) + "%"; DomElems.overviewAugStaminaGainMult.innerText = "Aug. Stamina Gain Mult: " + formatNumber(Player.bladeburner_stamina_gain_mult*100, 1) + "%";

@ -74,7 +74,6 @@ for (const md of codingContractTypesMetadata) {
// tslint:disable-next-line // tslint:disable-next-line
CodingContractTypes[md.name] = new CodingContractType(md.name, md.desc, md.gen, md.solver, md.difficulty, md.numTries); CodingContractTypes[md.name] = new CodingContractType(md.name, md.desc, md.gen, md.solver, md.difficulty, md.numTries);
} }
console.info(`${Object.keys(CodingContractTypes).length} Coding Contract Types loaded`);
/** /**
* Enum representing the different types of rewards a Coding Contract can give * Enum representing the different types of rewards a Coding Contract can give

@ -6,7 +6,7 @@
import { IMap } from "./types"; import { IMap } from "./types";
export let CONSTANTS: IMap<any> = { export let CONSTANTS: IMap<any> = {
Version: "0.47.3", Version: "0.48.0",
/** 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
@ -228,11 +228,46 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate: LatestUpdate:
` `
v0.47.3 v0.48.0 - ASCII
------- -------
ASCII
* Travel Agency now displays a world map
* Cities are now top view of metro station maps
Netscript
* 'softReset' is a new netscript function that performs a soft reset
regardless of if the player has bought augmentations or not.
* 'getAugmentationStats' is a new netscript function that returns the stats of
an augmentation.
* getCharacterInformation now additionally returns exp
* pid resets back to 1 when installing or destroying a BitNode.
* New '.ns' scripts start with a main function.
* 'hacknet.maxNumNodes' returns the maximum number of hacknet nodes.
Bladeburner
* Current stamina will scale as max stamina increases, this prevents players
from having very high penalty when they gain huge amount of exp at the
start of a reset.
Misc. Misc.
* missing ram cost in many function now added to documentation * Fixed an issue where SF3 was listed as infinitly repeatable and SF12 as
* typos having a limit of 3.
* Fixed an issue where the gang equipment screen would freeze the game if a
script installed augmentations while it is open.
* All BonusTime now displays in the 'H M S' format.
* Donation textbox style updated to match the rest of the game.
* Corporation name style updated to match the rest of the game.
* minor formatting under Hacking>Active Scripts
* typo in BN12 description
* BN12 now reduces contract money
* Character>Stats percentages are aligned, server and hacknet limit are
displayed, if the player has SF5 the reduces stats are shown.
* Character>Augmentations now displays by how much the player stats will
increase.
* Character>Augmentations has a badge indicating how many augs the player
has bought but not installed
* Character>Factions has a badge indicating how many factions have pending
invites.
` `
} }

@ -10,6 +10,7 @@ import { CorporationUpgrades } from "../data/CorporationUpgrades";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
export class Overview extends BaseReactComponent { export class Overview extends BaseReactComponent {
// Generic Function for Creating a button // Generic Function for Creating a button
@ -71,9 +72,9 @@ export class Overview extends BaseReactComponent {
`Private Shares: ${numeralWrapper.format(this.corp().totalShares - this.corp().issuedShares - this.corp().numShares, "0.000a")}` + `Private Shares: ${numeralWrapper.format(this.corp().totalShares - this.corp().issuedShares - this.corp().numShares, "0.000a")}` +
"</span></p><br><br>"; "</span></p><br><br>";
const storedTime = this.corp().storedCycles * CONSTANTS.MilliPerCycle / 1000; const storedTime = this.corp().storedCycles * CONSTANTS.MilliPerCycle;
if (storedTime > 15) { if (storedTime > 15000) {
txt += `Bonus Time: ${storedTime} seconds<br><br>`; txt += `Bonus time: ${convertTimeMsToTimeElapsedString(storedTime)}<br><br>`;
} }
let prodMult = this.corp().getProductionMultiplier(), let prodMult = this.corp().getProductionMultiplier(),

@ -88,7 +88,7 @@ export class DonateOption extends React.Component<IProps, IState> {
return ( return (
<div className={"faction-work-div"}> <div className={"faction-work-div"}>
<div className={"faction-work-div-wrapper"}> <div className={"faction-work-div-wrapper"}>
<input onChange={this.handleChange} placeholder={"Donation amount"} style={inputStyleMarkup} /> <input className='text-input' onChange={this.handleChange} placeholder={"Donation amount"} style={inputStyleMarkup} />
<StdButton <StdButton
onClick={this.donate} onClick={this.donate}
text={"Donate Money"} text={"Donate Money"}

@ -33,6 +33,7 @@ import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement"; import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
import { removeElement } from "../utils/uiHelpers/removeElement"; import { removeElement } from "../utils/uiHelpers/removeElement";
import { removeElementById } from "../utils/uiHelpers/removeElementById"; import { removeElementById } from "../utils/uiHelpers/removeElementById";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
// Constants // Constants
@ -1570,7 +1571,9 @@ Gang.prototype.updateGangContent = function() {
// Update territory information // Update territory information
UIElems.gangTerritoryInfoText.innerHTML = ""; UIElems.gangTerritoryInfoText.innerHTML = "";
const playerPower = AllGangs[this.facName].power; const playerPower = AllGangs[this.facName].power;
for (const gangname in AllGangs) { let gangNames = Object.keys(AllGangs).filter(g => g != this.facName);
gangNames.unshift(this.facName);
for (const gangname of gangNames) {
if (AllGangs.hasOwnProperty(gangname)) { if (AllGangs.hasOwnProperty(gangname)) {
const gangTerritoryInfo = AllGangs[gangname]; const gangTerritoryInfo = AllGangs[gangname];
let territory = gangTerritoryInfo.territory * 100; let territory = gangTerritoryInfo.territory * 100;
@ -1670,7 +1673,7 @@ Gang.prototype.updateGangContent = function() {
const CyclesPerSecond = 1000 / Engine._idleSpeed; const CyclesPerSecond = 1000 / Engine._idleSpeed;
UIElems.gangInfo.appendChild(createElement("p", { // Stored Cycles UIElems.gangInfo.appendChild(createElement("p", { // Stored Cycles
innerText: `Bonus time(s): ${this.storedCycles / CyclesPerSecond}`, innerText: `Bonus time: ${convertTimeMsToTimeElapsedString(this.storedCycles / CyclesPerSecond*1000)}`,
display: "inline-block", display: "inline-block",
tooltip: "You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by the browser). " + tooltip: "You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by the browser). " +
"Bonus time makes the Gang mechanic progress faster, up to 5x the normal speed", "Bonus time makes the Gang mechanic progress faster, up to 5x the normal speed",
@ -1942,6 +1945,8 @@ Gang.prototype.setGangMemberTaskDescription = function(memberObj, taskName) {
Gang.prototype.clearUI = function() { Gang.prototype.clearUI = function() {
if (UIElems.gangContainer instanceof Element) { removeElement(UIElems.gangContainer); } if (UIElems.gangContainer instanceof Element) { removeElement(UIElems.gangContainer); }
if (UIElems.gangMemberUpgradeBox instanceof Element) { removeElement(UIElems.gangMemberUpgradeBox); }
for (const prop in UIElems) { for (const prop in UIElems) {
UIElems[prop] = null; UIElems[prop] = null;
} }

@ -15,9 +15,15 @@ export class City {
*/ */
name: CityName; name: CityName;
constructor(name: CityName, locations: LocationName[]=[]) { /**
* Metro map ascii art
*/
asciiArt: string;
constructor(name: CityName, locations: LocationName[]=[], asciiArt: string='') {
this.name = name; this.name = name;
this.locations = locations; this.locations = locations;
this.asciiArt = asciiArt;
} }
addLocation(loc: LocationName): void { addLocation(loc: LocationName): void {

@ -40,6 +40,181 @@ Cities[CityName.NewTokyo] = new City(CityName.NewTokyo);
Cities[CityName.Sector12] = new City(CityName.Sector12); Cities[CityName.Sector12] = new City(CityName.Sector12);
Cities[CityName.Volhaven] = new City(CityName.Volhaven); Cities[CityName.Volhaven] = new City(CityName.Volhaven);
Cities[CityName.Aevum].asciiArt = `
[aevum police headquarters] 26
o
I \\ [bachman & associates]
\\ 56 B
x \\ [summit university]
\\ \\ 28
\\ [snap fitness gym] x o--L------------
K \\ /
\\ \\ P
x 58 \\ / [travel agency]
\\ 94 95 o
90 x 59 o------o |
\\ / \\ | 98 102 103
o--------N------x----o 93 96 o-----+------------o o----o
\\ | \\ /
[hospital] \\ 61 [ecorp] x 31 99 o-F-o 101
o |
| o---E-- | [fulcrum tech.]
x 62 / A [aerocorp]
[crush fitness gym] | / |
| / |
o--------D------+--o o
| |\\ [rho construction]
H [netlink tech.] | J
| | \\
| 34 x \\
[clarke inc.] C | \\ [world stock exchange]
| | \\
| | o-M-------Q--------o
[galactic cybersystems] G 35 x
| [watchdog security]
|
67 o
[the slums] O `
Cities[CityName.Chongqing].asciiArt = `
|
75 o
\\
o 76
7 | |
| + 77
[world stock exchange] F |
\\ o 78 [kuaigong international]
\\ /
38 o----x--x------x------A---------
/ 39 | 41
37 o + 79 o--x--x-C-0
/ | /
/ x-----+-----x-----0 [hospital]
[solaris space system] B |
| + 80
| |
34 o E [travel agency]
|
|
x 82
[the slums] D `
Cities[CityName.Ishima].asciiArt = `
o 59
o o |
[storm tech.] | | G [world stock exchange]
| | 28 |
23 o--C------o--------+----x----o |
/ / 25 | 27 \\ x 57
/ / | \\ |
/ / | \\ |
o 22 o | \\| 29/56
| | o
| [hospital] D / \\ 3 2 1
o | / \\ o-------x------o
/ o / \\ /
48 o / 55 x \\ /
\\ / / x
\\ / [nova medical] / 4/30 \\
49 x A \\
/ \\ / \\
/ \\ [travel agency] F o 31
/ \\ 51 /
/ o----B------x-----o
o 50 52
[omega soft.]
[the slums] E `
Cities[CityName.NewTokyo].asciiArt = `
o
\\
\\ [defcomm]
\\
o--x---A--x--o [travel agency]
7 8 10 G
[vitalife] o 12 [global pharmaceuticals]
|
o--D-x----x-------x-C-+--------x--x-B-x---x-o
21 22 23 \\ 24 25 26 27
\\
[noodle bar] x 14
\\
\\
[hospital] o 15 [world stock exchange]
|
o--x--E--x-----x-----x---+---x----x--H--x-o
|
|
o 17
F [the slums]
`
Cities[CityName.Sector12].asciiArt = `
78 o 97
o [icarus microsystems] /
N [powerhouse gym] o I
1 | | /
o-----+---x----o 4 A [alpha ent.] o-------o /
| 3 \\ | \\ /
| \\ | [iron gym] x 95
(79) x \\ | / \\
| o-E----+----x----J--o 10 / o----T--o
| | 8 \\ 94 x
80 x [city hall] | x 11 / [world stock exchange]
| | \\ /
| C [cia] \\ /
Q [hospital] | F P [universal energy]
| o [deltaone] \\ /
| 35 o---------x 13/92/36
L [megacorp] 33 / / \\
| o------------o 34 / \\
(29) | / [carmichael sec.] D \\
o-----+-----x------o / O [rothman university]
| 31 32 [nsa] M
| /
B [blade industries] H
| / [four sigma]
| [joe's guns] /
| /
85 o--G--------K--------S-------o 88 [the slums] R
[foodnstuff] [travel agency] `
Cities[CityName.Volhaven].asciiArt = `
[omnia cybersystems]
17 66 68
o o------G-------o
\\ / \\
\\ o 65 o 69
[syscore sec.] H | |
\\ | | [millenium fitness gym]
\\ | 21 22 23 24 | 26
o----+--x--x----x---x---+-----x-------D-----o
19 | | 28
| F [omnitek inc.]
[hospital] J 63 o
| / 72
3 | 5 6 / 9
o--------+----x-----x----+----------M-------o
/ | |
/ 61 x [helios labs] B [world stock exchange]
[travel agency] L | |
/ | o
/ E [nwo] / 75
/ [computek] | /
/ A-------o------I-----o
1 o | |
| [zb] o 77
[lexocorp] C
|
o
57
[the slums] K `
// Then construct all locations, and add them to the cities as we go. // Then construct all locations, and add them to the cities as we go.
for (const metadata of LocationsMetadata) { for (const metadata of LocationsMetadata) {
const loc = constructLocation(metadata); const loc = constructLocation(metadata);

@ -127,6 +127,7 @@ export function createStartCorporationPopup(p: IPlayer) {
}); });
const nameInput = createElement("input", { const nameInput = createElement("input", {
class: 'text-input',
placeholder: "Corporation Name", placeholder: "Corporation Name",
}) as HTMLInputElement; }) as HTMLInputElement;

@ -17,18 +17,59 @@ type IProps = {
export class LocationCity extends React.Component<IProps, any> { export class LocationCity extends React.Component<IProps, any> {
render() { render() {
const locationButtons = this.props.city.locations.map((locName) => { const thiscity = this;
return ( const topprop = this.props
<li key={locName}>
<StdButton onClick={this.props.enterLocation.bind(this, locName)} text={locName} /> function LocationLetter(location: string) {
</li> if (location)
) return <span key={location} className='tooltip' style={{color: 'blue', whiteSpace: 'nowrap', margin: '0px', padding: '0px', cursor: 'pointer'}} onClick={topprop.enterLocation.bind(thiscity, location)}>
}); X
</span>
return <span>*</span>
}
const locationLettersRegex = /[A-Z]/g;
const letterMap: any = {'A': 0,'B': 1,'C': 2,'D': 3,'E': 4,'F': 5,'G': 6,
'H': 7,'I': 8,'J': 9,'K': 10,'L': 11,'M': 12,'N': 13,'O': 14,
'P': 15,'Q': 16,'R': 17,'S': 18,'T': 19,'U': 20,'V': 21,'W': 22,
'X': 23,'Y': 24,'Z': 25}
let locI = 0;
function lineElems(s: string) {
let elems: any[] = [];
let matches: any[] = [];
let match: any;
while ((match = locationLettersRegex.exec(s)) !== null) {
matches.push(match);
}
if (matches.length === 0) {
elems.push(s);
return elems;
}
let parts: any[] = [];
for(let i = 0; i < matches.length; i++) {
const startI = i === 0 ? 0 : matches[i-1].index+1;
const endI = matches[i].index;
elems.push(s.slice(startI, endI))
const locationI = letterMap[s[matches[i].index]];
elems.push(LocationLetter(thiscity.props.city.locations[locationI]))
locI++;
}
elems.push(s.slice(matches[matches.length-1].index+1))
return elems;
}
let elems: any[] = [];
const lines = this.props.city.asciiArt.split('\n');
for(const i in lines) {
elems.push(<pre key={i}>{lineElems(lines[i])}</pre>)
}
return ( return (
<ul> <>
{locationButtons} {elems}
</ul> </>
) )
} }
} }

@ -32,21 +32,16 @@ export class TravelAgencyLocation extends React.Component<IProps, any> {
} }
render() { render() {
const travelBtns: React.ReactNode[] = []; const thisTravelAgencyLocation = this;
for (const key in CityName) {
const city = CityName[key];
// Skip current city function LocationLetter(props: any) {
if (city === this.props.p.city) { continue; } if(props.city !== thisTravelAgencyLocation.props.p.city) {
return <span className='tooltip' style={{color: 'blue', whiteSpace: 'nowrap', margin: '0px', padding: '0px'}} onClick={createTravelPopup.bind(null, props.city, thisTravelAgencyLocation.props.travel)}>
travelBtns.push( <span className='tooltiptext'>{props.city}</span>
<StdButton {props.city[0]}
key={city} </span>
onClick={createTravelPopup.bind(null, city, this.props.travel)} }
style={this.btnStyle} return <span>{props.city[0]}</span>
text={`Travel to ${city}`}
/>
)
} }
return ( return (
@ -55,7 +50,28 @@ export class TravelAgencyLocation extends React.Component<IProps, any> {
From here, you can travel to any other city! A ticket From here, you can travel to any other city! A ticket
costs {numeralWrapper.formatMoney(CONSTANTS.TravelCost)} costs {numeralWrapper.formatMoney(CONSTANTS.TravelCost)}
</p> </p>
{travelBtns} <pre> ,_ . ._. _. .</pre>
<pre> , _-\','|~\~ ~/ ;-'_ _-' ,;_;_, ~~-</pre>
<pre> /~~-\_/-'~'--' \~~| ', ,' / / ~|-_\_/~/~ ~~--~~~~'--_</pre>
<pre> / ,/'-/~ '\ ,' _ , '<LocationLetter city='Volhaven' />,'|~ ._/-, /~</pre>
<pre> ~/-'~\_, '-,| '|. ' ~ ,\ /'~ / /_ /~</pre>
<pre>.-~ '| '',\~|\ _\~ ,_ , <LocationLetter city='Chongqing' /> /,</pre>
<pre> '\ <LocationLetter city='Sector-12' /> /'~ |_/~\\,-,~ \ " ,_,/ |</pre>
<pre> | / ._-~'\_ _~| \ ) <LocationLetter city='New Tokyo' /></pre>
<pre> \ __-\ '/ ~ |\ \_ / ~</pre>
<pre> ., '\ |, ~-_ - | \\_' ~| /\ \~ ,</pre>
<pre> ~-_' _; '\ '-, \,' /\/ |</pre>
<pre> '\_,~'\_ \_ _, /' ' |, /|'</pre>
<pre> / \_ ~ | / \ ~'; -,_.</pre>
<pre> | ~\ | | , '-_, ,; ~ ~\</pre>
<pre> \, <LocationLetter city='Aevum' /> / \ / /| ,-, , -,</pre>
<pre> | ,/ | |' |/ ,- ~ \ '.</pre>
<pre> ,| ,/ \ ,/ \ <LocationLetter city='Ishima' /> |</pre>
<pre> / | ~ -~~-, / _</pre>
<pre> | ,-' ~ /</pre>
<pre> / ,' ~</pre>
<pre> ',| ~</pre>
<pre> ~'</pre>
</div> </div>
) )
} }

42
src/Netscript/Pid.ts Normal file

@ -0,0 +1,42 @@
import { workerScripts } from "./WorkerScripts";
let pidCounter = 1;
/**
* Find and return the next availble PID for a script
*/
export function generateNextPid(): number {
let tempCounter = pidCounter;
// Cap the number of search iterations at some arbitrary value to avoid
// infinite loops. We'll assume that players wont have 1mil+ running scripts
let found = false;
for (let i = 0; i < 1e6;) {
if (!workerScripts.has(tempCounter + i)) {
found = true;
tempCounter = tempCounter + i;
break;
}
if (i === Number.MAX_SAFE_INTEGER - 1) {
i = 1;
} else {
++i;
}
}
if (found) {
pidCounter = tempCounter + 1;
if (pidCounter >= Number.MAX_SAFE_INTEGER) {
pidCounter = 1;
}
return tempCounter;
} else {
return -1;
}
}
export function resetPidCounter(): void {
pidCounter = 1;
}

@ -202,7 +202,9 @@ export const RamCosts: IMap<any> = {
getAugmentationsFromFaction: () => RamCostConstants.ScriptSingularityFn3RamCost, getAugmentationsFromFaction: () => RamCostConstants.ScriptSingularityFn3RamCost,
getAugmentationPrereq: () => RamCostConstants.ScriptSingularityFn3RamCost, getAugmentationPrereq: () => RamCostConstants.ScriptSingularityFn3RamCost,
getAugmentationCost: () => RamCostConstants.ScriptSingularityFn3RamCost, getAugmentationCost: () => RamCostConstants.ScriptSingularityFn3RamCost,
getAugmentationStats: () => RamCostConstants.ScriptSingularityFn3RamCost,
purchaseAugmentation: () => RamCostConstants.ScriptSingularityFn3RamCost, purchaseAugmentation: () => RamCostConstants.ScriptSingularityFn3RamCost,
softReset: () => RamCostConstants.ScriptSingularityFn3RamCost,
installAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost, installAugmentations: () => RamCostConstants.ScriptSingularityFn3RamCost,
// Gang API // Gang API

@ -10,6 +10,7 @@ import {
augmentationExists, augmentationExists,
installAugmentations installAugmentations
} from "./Augmentation/AugmentationHelpers"; } from "./Augmentation/AugmentationHelpers";
import { prestigeAugmentation } from "./Prestige";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames"; import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { findCrime } from "./Crime/CrimeHelpers"; import { findCrime } from "./Crime/CrimeHelpers";
@ -51,7 +52,7 @@ import {
purchaseHashUpgrade, purchaseHashUpgrade,
updateHashManagerCapacity, updateHashManagerCapacity,
} from "./Hacknet/HacknetHelpers"; } from "./Hacknet/HacknetHelpers";
import { HacknetServer } from "./Hacknet/HacknetServer"; import { HacknetServer, MaxNumberHacknetServers } from "./Hacknet/HacknetServer";
import { CityName } from "./Locations/data/CityNames"; import { CityName } from "./Locations/data/CityNames";
import { LocationName } from "./Locations/data/LocationNames"; import { LocationName } from "./Locations/data/LocationNames";
@ -391,6 +392,9 @@ function NetscriptFunctions(workerScript) {
numNodes : function() { numNodes : function() {
return Player.hacknetNodes.length; return Player.hacknetNodes.length;
}, },
maxNumNodes : function() {
return MaxNumberHacknetServers;
},
purchaseNode : function() { purchaseNode : function() {
return purchaseHacknet(); return purchaseHacknet();
}, },
@ -2742,6 +2746,12 @@ function NetscriptFunctions(workerScript) {
workChaExpGain: Player.workChaExpGained, workChaExpGain: Player.workChaExpGained,
workRepGain: Player.workRepGained, workRepGain: Player.workRepGained,
workMoneyGain: Player.workMoneyGained, workMoneyGain: Player.workMoneyGained,
hackingExp: Player.hacking_exp,
strengthExp: Player.strength_exp,
defenseExp: Player.defense_exp,
dexterityExp: Player.dexterity_exp,
agilityExp: Player.agility_exp,
charismaExp: Player.charisma_exp,
}; };
}, },
isBusy: function() { isBusy: function() {
@ -3415,6 +3425,24 @@ function NetscriptFunctions(workerScript) {
var aug = Augmentations[name]; var aug = Augmentations[name];
return [aug.baseRepRequirement, aug.baseCost]; return [aug.baseRepRequirement, aug.baseCost];
}, },
getAugmentationStats: function(name) {
updateDynamicRam("getAugmentationStats", getRamCost("getAugmentationStats"));
if (Player.bitNodeN !== 4) {
if (SourceFileFlags[4] <= 2) {
throw makeRuntimeRejectMsg(workerScript, "Cannot run getAugmentationStats(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");
return false;
}
}
if (!augmentationExists(name)) {
workerScript.scriptRef.log("ERROR: getAugmentationStats() failed. Invalid Augmentation name passed in (note: this is case-sensitive): " + name);
return {};
}
var aug = Augmentations[name];
return Object.assign({}, aug.mults);
},
purchaseAugmentation: function(faction, name) { purchaseAugmentation: function(faction, name) {
updateDynamicRam("purchaseAugmentation", getRamCost("purchaseAugmentation")); updateDynamicRam("purchaseAugmentation", getRamCost("purchaseAugmentation"));
if (Player.bitNodeN !== 4) { if (Player.bitNodeN !== 4) {
@ -3483,6 +3511,24 @@ function NetscriptFunctions(workerScript) {
return false; return false;
} }
}, },
softReset: function() {
updateDynamicRam("softReset", getRamCost("softReset"));
if (Player.bitNodeN !== 4) {
if (SourceFileFlags[4] <= 2) {
throw makeRuntimeRejectMsg(workerScript, "Cannot run softReset(). It is a Singularity Function and requires SourceFile-4 (level 3) to run.");
return false;
}
}
workerScript.log("Soft resetting. This will cause this script to be killed");
setTimeoutRef(() => {
prestigeAugmentation();
}, 0);
// Prevent workerScript from "finishing execution naturally"
workerScript.running = false;
killWorkerScript(workerScript);
},
installAugmentations: function(cbScript) { installAugmentations: function(cbScript) {
updateDynamicRam("installAugmentations", getRamCost("installAugmentations")); updateDynamicRam("installAugmentations", getRamCost("installAugmentations"));
if (Player.bitNodeN !== 4) { if (Player.bitNodeN !== 4) {

@ -6,6 +6,7 @@ import { killWorkerScript } from "./Netscript/killWorkerScript";
import { WorkerScript } from "./Netscript/WorkerScript"; import { WorkerScript } from "./Netscript/WorkerScript";
import { workerScripts } from "./Netscript/WorkerScripts"; import { workerScripts } from "./Netscript/WorkerScripts";
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter"; import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
import { generateNextPid } from "./Netscript/Pid";
import { CONSTANTS } from "./Constants"; import { CONSTANTS } from "./Constants";
import { Engine } from "./engine"; import { Engine } from "./engine";
@ -412,42 +413,6 @@ function processNetscript1Imports(code, workerScript) {
return res; return res;
} }
/**
* Find and return the next availble PID for a script
*/
let pidCounter = 1;
function generateNextPid() {
let tempCounter = pidCounter;
// Cap the number of search iterations at some arbitrary value to avoid
// infinite loops. We'll assume that players wont have 1mil+ running scripts
let found = false;
for (let i = 0; i < 1e6;) {
if (!workerScripts.has(tempCounter + i)) {
found = true;
tempCounter = tempCounter + i;
break;
}
if (i === Number.MAX_SAFE_INTEGER - 1) {
i = 1;
} else {
++i;
}
}
if (found) {
pidCounter = tempCounter + 1;
if (pidCounter >= Number.MAX_SAFE_INTEGER) {
pidCounter = 1;
}
return tempCounter;
} else {
return -1;
}
}
/** /**
* Used to start a RunningScript (by creating and starting its * Used to start a RunningScript (by creating and starting its
* corresponding WorkerScript), and add the RunningScript to the server on which * corresponding WorkerScript), and add the RunningScript to the server on which
@ -535,7 +500,6 @@ export function createAndAddWorkerScript(runningScriptObj, server) {
// the script from being cleaned up twice // the script from being cleaned up twice
if (!w.running) { return; } if (!w.running) { return; }
console.log("Stopping script " + w.name + " because it finished running naturally");
killWorkerScript(s); killWorkerScript(s);
w.log("Script finished running"); w.log("Script finished running");
}).catch(function(w) { }).catch(function(w) {
@ -547,8 +511,8 @@ export function createAndAddWorkerScript(runningScriptObj, server) {
if (isScriptErrorMessage(w.errorMessage)) { if (isScriptErrorMessage(w.errorMessage)) {
var errorTextArray = w.errorMessage.split("|"); var errorTextArray = w.errorMessage.split("|");
if (errorTextArray.length != 4) { if (errorTextArray.length != 4) {
console.log("ERROR: Something wrong with Error text in evaluator..."); console.error("ERROR: Something wrong with Error text in evaluator...");
console.log("Error text: " + errorText); console.error("Error text: " + errorText);
return; return;
} }
var serverIp = errorTextArray[1]; var serverIp = errorTextArray[1];
@ -567,11 +531,11 @@ export function createAndAddWorkerScript(runningScriptObj, server) {
w.env.stopFlag = true; w.env.stopFlag = true;
} else if (isScriptErrorMessage(w)) { } else if (isScriptErrorMessage(w)) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer"); dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.log("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString()); console.error("ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " + w.toString());
return; return;
} else { } else {
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev"); dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
console.log(w); console.error(w);
} }
killWorkerScript(s); killWorkerScript(s);

@ -4,7 +4,7 @@ import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
export function canAccessBladeburner() { export function canAccessBladeburner() {
if (this.bitNodeN === 8) { return false; } if (this.bitNodeN === 8) { return false; }
return (this.bitNodeN === 6) || (this.bitNodeN === 7) || (SourceFileFlags[6] > 0); return (this.bitNodeN === 6) || (this.bitNodeN === 7) || (SourceFileFlags[6] > 0) || (SourceFileFlags[7] > 0);
} }
export function inBladeburner() { export function inBladeburner() {

@ -411,7 +411,7 @@ export function recordMoneySource(amt, source) {
export function gainHackingExp(exp) { export function gainHackingExp(exp) {
if (isNaN(exp)) { if (isNaN(exp)) {
console.log("ERR: NaN passed into Player.gainHackingExp()"); return; console.error("ERR: NaN passed into Player.gainHackingExp()"); return;
} }
this.hacking_exp += exp; this.hacking_exp += exp;
if(this.hacking_exp < 0) { if(this.hacking_exp < 0) {
@ -421,7 +421,7 @@ export function gainHackingExp(exp) {
export function gainStrengthExp(exp) { export function gainStrengthExp(exp) {
if (isNaN(exp)) { if (isNaN(exp)) {
console.log("ERR: NaN passed into Player.gainStrengthExp()"); return; console.error("ERR: NaN passed into Player.gainStrengthExp()"); return;
} }
this.strength_exp += exp; this.strength_exp += exp;
if(this.strength_exp < 0) { if(this.strength_exp < 0) {
@ -431,7 +431,7 @@ export function gainStrengthExp(exp) {
export function gainDefenseExp(exp) { export function gainDefenseExp(exp) {
if (isNaN(exp)) { if (isNaN(exp)) {
console.log("ERR: NaN passed into player.gainDefenseExp()"); return; console.error("ERR: NaN passed into player.gainDefenseExp()"); return;
} }
this.defense_exp += exp; this.defense_exp += exp;
if(this.defense_exp < 0) { if(this.defense_exp < 0) {
@ -441,7 +441,7 @@ export function gainDefenseExp(exp) {
export function gainDexterityExp(exp) { export function gainDexterityExp(exp) {
if (isNaN(exp)) { if (isNaN(exp)) {
console.log("ERR: NaN passed into Player.gainDexterityExp()"); return; console.error("ERR: NaN passed into Player.gainDexterityExp()"); return;
} }
this.dexterity_exp += exp; this.dexterity_exp += exp;
if(this.dexterity_exp < 0) { if(this.dexterity_exp < 0) {
@ -451,7 +451,7 @@ export function gainDexterityExp(exp) {
export function gainAgilityExp(exp) { export function gainAgilityExp(exp) {
if (isNaN(exp)) { if (isNaN(exp)) {
console.log("ERR: NaN passed into Player.gainAgilityExp()"); return; console.error("ERR: NaN passed into Player.gainAgilityExp()"); return;
} }
this.agility_exp += exp; this.agility_exp += exp;
if(this.agility_exp < 0) { if(this.agility_exp < 0) {
@ -461,7 +461,7 @@ export function gainAgilityExp(exp) {
export function gainCharismaExp(exp) { export function gainCharismaExp(exp) {
if (isNaN(exp)) { if (isNaN(exp)) {
console.log("ERR: NaN passed into Player.gainCharismaExp()"); return; console.error("ERR: NaN passed into Player.gainCharismaExp()"); return;
} }
this.charisma_exp += exp; this.charisma_exp += exp;
if(this.charisma_exp < 0) { if(this.charisma_exp < 0) {
@ -471,7 +471,7 @@ export function gainCharismaExp(exp) {
export function gainIntelligenceExp(exp) { export function gainIntelligenceExp(exp) {
if (isNaN(exp)) { if (isNaN(exp)) {
console.log("ERROR: NaN passed into Player.gainIntelligenceExp()"); return; console.error("ERROR: NaN passed into Player.gainIntelligenceExp()"); return;
} }
if (SourceFileFlags[5] > 0 || this.intelligence > 0) { if (SourceFileFlags[5] > 0 || this.intelligence > 0) {
this.intelligence_exp += exp; this.intelligence_exp += exp;
@ -1827,7 +1827,6 @@ export function isQualified(company, position) {
/********** Reapplying Augmentations and Source File ***********/ /********** Reapplying Augmentations and Source File ***********/
export function reapplyAllAugmentations(resetMultipliers=true) { export function reapplyAllAugmentations(resetMultipliers=true) {
console.log("Re-applying augmentations");
if (resetMultipliers) { if (resetMultipliers) {
this.resetMultipliers(); this.resetMultipliers();
} }
@ -1856,7 +1855,6 @@ export function reapplyAllAugmentations(resetMultipliers=true) {
} }
export function reapplyAllSourceFiles() { export function reapplyAllSourceFiles() {
console.log("Re-applying source files");
//Will always be called after reapplyAllAugmentations() so multipliers do not have to be reset //Will always be called after reapplyAllAugmentations() so multipliers do not have to be reset
//this.resetMultipliers(); //this.resetMultipliers();

@ -20,6 +20,7 @@ import { Message } from "./Message/Message";
import { initMessages, Messages } from "./Message/MessageHelpers"; import { initMessages, Messages } from "./Message/MessageHelpers";
import { prestigeWorkerScripts } from "./NetscriptWorker"; import { prestigeWorkerScripts } from "./NetscriptWorker";
import { Player } from "./Player"; import { Player } from "./Player";
import { resetPidCounter } from "./Netscript/Pid";
import { import {
AllServers, AllServers,
@ -172,6 +173,8 @@ function prestigeAugmentation() {
DaedalusServer.serversOnNetwork.push(WorldDaemon.ip); DaedalusServer.serversOnNetwork.push(WorldDaemon.ip);
} }
} }
resetPidCounter();
} }
@ -346,6 +349,8 @@ function prestigeSourceFile() {
// Gain int exp // Gain int exp
Player.gainIntelligenceExp(5); Player.gainIntelligenceExp(5);
resetPidCounter();
} }
export {prestigeAugmentation, prestigeSourceFile}; export {prestigeAugmentation, prestigeSourceFile};

@ -112,7 +112,7 @@ BitburnerSaveObject.prototype.saveGame = function(db) {
var request = objectStore.put(saveString, "save"); var request = objectStore.put(saveString, "save");
request.onerror = function(e) { request.onerror = function(e) {
console.log("Error saving game to IndexedDB: " + e); console.error("Error saving game to IndexedDB: " + e);
} }
request.onsuccess = function(e) { request.onsuccess = function(e) {
@ -124,7 +124,7 @@ BitburnerSaveObject.prototype.saveGame = function(db) {
} catch(e) { } catch(e) {
if (e.code == 22) { if (e.code == 22) {
createStatusText("Save failed for localStorage! Check console(F12)"); createStatusText("Save failed for localStorage! Check console(F12)");
console.log("Failed to save game to localStorage because the size of the save file " + console.error("Failed to save game to localStorage because the size of the save file " +
"is too large. However, the game will still be saved to IndexedDb if your browser " + "is too large. However, the game will still be saved to IndexedDb if your browser " +
"supports it. If you would like to save to localStorage as well, then " + "supports it. If you would like to save to localStorage as well, then " +
"consider killing several of your scripts to " + "consider killing several of your scripts to " +
@ -247,7 +247,7 @@ function loadGame(saveString) {
try { try {
Settings.load(saveObj.SettingsSave); Settings.load(saveObj.SettingsSave);
} catch(e) { } catch(e) {
console.log("ERROR: Failed to parse Settings. Re-initing default values"); console.error("ERROR: Failed to parse Settings. Re-initing default values");
Settings.init(); Settings.init();
} }
} else { } else {
@ -257,7 +257,7 @@ function loadGame(saveString) {
try { try {
loadFconf(saveObj.FconfSettingsSave); loadFconf(saveObj.FconfSettingsSave);
} catch(e) { } catch(e) {
console.log("ERROR: Failed to parse .fconf Settings."); console.error("ERROR: Failed to parse .fconf Settings.");
} }
} }
if (saveObj.hasOwnProperty("VersionSave")) { if (saveObj.hasOwnProperty("VersionSave")) {
@ -281,7 +281,7 @@ function loadGame(saveString) {
try { try {
loadAllGangs(saveObj.AllGangsSave); loadAllGangs(saveObj.AllGangsSave);
} catch(e) { } catch(e) {
console.log("ERROR: Failed to parse AllGangsSave: " + e); console.error("ERROR: Failed to parse AllGangsSave: " + e);
} }
} }
@ -439,7 +439,7 @@ function loadImportedGame(saveObj, saveString) {
try { try {
loadFconf(saveObj.FconfSettingsSave); loadFconf(saveObj.FconfSettingsSave);
} catch(e) { } catch(e) {
console.log("ERROR: Failed to load .fconf settings when importing"); console.error("ERROR: Failed to load .fconf settings when importing");
} }
} }
if (saveObj.hasOwnProperty("VersionSave")) { if (saveObj.hasOwnProperty("VersionSave")) {
@ -460,7 +460,7 @@ function loadImportedGame(saveObj, saveString) {
try { try {
loadAllGangs(saveObj.AllGangsSave); loadAllGangs(saveObj.AllGangsSave);
} catch(e) { } catch(e) {
console.log("ERROR: Failed to parse AllGangsSave: " + e); console.error("ERROR: Failed to parse AllGangsSave: " + e);
} }
} }

@ -91,7 +91,7 @@ let NetscriptFunctions =
"createProgram|commitCrime|getCrimeChance|getOwnedAugmentations|" + "createProgram|commitCrime|getCrimeChance|getOwnedAugmentations|" +
"getOwnedSourceFiles|getAugmentationsFromFaction|" + "getOwnedSourceFiles|getAugmentationsFromFaction|" +
"getAugmentationPrereq|getAugmentationCost|purchaseAugmentation|" + "getAugmentationPrereq|getAugmentationCost|purchaseAugmentation|" +
"installAugmentations|" + "softReset|installAugmentations|getAugmentationStats|" +
// TIX API // TIX API
"getStockPrice|getStockPosition|getStockSymbols|getStockMaxShares|" + "getStockPrice|getStockPosition|getStockSymbols|getStockMaxShares|" +
@ -103,7 +103,7 @@ let NetscriptFunctions =
// Hacknet Node API // Hacknet Node API
"hacknet|numNodes|purchaseNode|getPurchaseNodeCost|getNodeStats|" + "hacknet|numNodes|purchaseNode|getPurchaseNodeCost|getNodeStats|" +
"upgradeLevel|upgradeRam|upgradeCore|upgradeCache|getLevelUpgradeCost|" + "upgradeLevel|upgradeRam|upgradeCore|upgradeCache|getLevelUpgradeCost|" +
"getRamUpgradeCost|getCoreUpgradeCost|getCacheUpgradeCost|" + "getRamUpgradeCost|getCoreUpgradeCost|getCacheUpgradeCost|maxNumNodes|" +
// Gang API // Gang API
"gang|" + "gang|" +

@ -1760,7 +1760,14 @@ let Terminal = {
const filepath = Terminal.getFilepath(filename); const filepath = Terminal.getFilepath(filename);
const script = Terminal.getScript(filename); const script = Terminal.getScript(filename);
if (script == null) { if (script == null) {
Engine.loadScriptEditorContent(filepath); let code = ""
if(filename.endsWith(".ns")) {
code = `export async function main(ns) {
}`;
}
console.log('default code');
Engine.loadScriptEditorContent(filepath, code);
} else { } else {
Engine.loadScriptEditorContent(filepath, script.code); Engine.loadScriptEditorContent(filepath, script.code);
} }

@ -86,7 +86,7 @@ import {
} from "./PersonObjects/Resleeving/ResleevingUI"; } from "./PersonObjects/Resleeving/ResleevingUI";
import { createStatusText } from "./ui/createStatusText"; import { createStatusText } from "./ui/createStatusText";
import { displayCharacterInfo } from "./ui/displayCharacterInfo"; import { CharacterInfo } from "./ui/CharacterInfo";
import { Page, routing } from "./ui/navigationTracking"; import { Page, routing } from "./ui/navigationTracking";
import { numeralWrapper } from "./ui/numeralFormat"; import { numeralWrapper } from "./ui/numeralFormat";
import { setSettingsLabels } from "./ui/setSettingsLabels"; import { setSettingsLabels } from "./ui/setSettingsLabels";
@ -567,7 +567,7 @@ const Engine = {
/// Display character info /// Display character info
updateCharacterInfo: function() { updateCharacterInfo: function() {
displayCharacterInfo(Engine.Display.characterInfo, Player); ReactDOM.render(CharacterInfo(Player), Engine.Display.characterInfo)
}, },
// TODO Refactor this into Faction implementation // TODO Refactor this into Faction implementation
@ -592,7 +592,7 @@ const Engine = {
factionsList.appendChild(createElement("a", { factionsList.appendChild(createElement("a", {
class:"a-link-button", innerText:factionName, padding:"4px", margin:"4px", class:"a-link-button", innerText:factionName, padding:"4px", margin:"4px",
display:"inline-block", display:"inline-block",
clickListener:()=>{ clickListener: () => {
Engine.loadFactionContent(); Engine.loadFactionContent();
displayFactionContent(factionName); displayFactionContent(factionName);
return false; return false;
@ -774,6 +774,7 @@ const Engine = {
updateDisplaysLong: 15, updateDisplaysLong: 15,
updateActiveScriptsDisplay: 5, updateActiveScriptsDisplay: 5,
createProgramNotifications: 10, createProgramNotifications: 10,
augmentationsNotifications: 10,
checkFactionInvitations: 100, checkFactionInvitations: 100,
passiveFactionGrowth: 600, passiveFactionGrowth: 600,
messages: 150, messages: 150,
@ -868,6 +869,19 @@ const Engine = {
Engine.Counters.createProgramNotifications = 10; Engine.Counters.createProgramNotifications = 10;
} }
if (Engine.Counters.augmentationsNotifications <= 0) {
var num = Player.queuedAugmentations.length;
var elem = document.getElementById("augmentations-notification");
if (num > 0) {
elem.innerHTML = num;
elem.setAttribute("class", "notification-on");
} else {
elem.innerHTML = "";
elem.setAttribute("class", "notification-off");
}
Engine.Counters.augmentationsNotifications = 10;
}
if (Engine.Counters.checkFactionInvitations <= 0) { if (Engine.Counters.checkFactionInvitations <= 0) {
var invitedFactions = Player.checkForFactionInvitations(); var invitedFactions = Player.checkForFactionInvitations();
if (invitedFactions.length > 0) { if (invitedFactions.length > 0) {
@ -881,6 +895,17 @@ const Engine = {
var randFaction = invitedFactions[Math.floor(Math.random() * invitedFactions.length)]; var randFaction = invitedFactions[Math.floor(Math.random() * invitedFactions.length)];
inviteToFaction(randFaction); inviteToFaction(randFaction);
} }
const num = Player.factionInvitations.length;
const elem = document.getElementById("factions-notification");
if (num > 0) {
elem.innerHTML = num;
elem.setAttribute("class", "notification-on");
} else {
elem.innerHTML = "";
elem.setAttribute("class", "notification-off");
}
Engine.Counters.checkFactionInvitations = 100; Engine.Counters.checkFactionInvitations = 100;
} }
@ -1536,19 +1561,18 @@ window.onload = function() {
indexedDbRequest = window.indexedDB.open("bitburnerSave", 1); indexedDbRequest = window.indexedDB.open("bitburnerSave", 1);
indexedDbRequest.onerror = function(e) { indexedDbRequest.onerror = function(e) {
console.log("Error opening indexedDB: "); console.error("Error opening indexedDB: ");
console.log(e); console.error(e);
return Engine.load(null); // Try to load from localstorage return Engine.load(null); // Try to load from localstorage
}; };
indexedDbRequest.onsuccess = function(e) { indexedDbRequest.onsuccess = function(e) {
console.log("Opening bitburnerSave database successful!");
indexedDb = e.target.result; indexedDb = e.target.result;
var transaction = indexedDb.transaction(["savestring"]); var transaction = indexedDb.transaction(["savestring"]);
var objectStore = transaction.objectStore("savestring"); var objectStore = transaction.objectStore("savestring");
var request = objectStore.get("save"); var request = objectStore.get("save");
request.onerror = function(e) { request.onerror = function(e) {
console.log("Error in Database request to get savestring: " + e); console.error("Error in Database request to get savestring: " + e);
return Engine.load(null); // Try to load from localstorage return Engine.load(null); // Try to load from localstorage
} }

@ -60,9 +60,11 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
</li> </li>
<li id="factions-tab" class="mainmenu-accordion-panel"> <li id="factions-tab" class="mainmenu-accordion-panel">
<button id="factions-menu-link"> Factions </button> <button id="factions-menu-link"> Factions </button>
<span id="factions-notification" class="notification-off"> </span>
</li> </li>
<li id="augmentations-tab" class="mainmenu-accordion-panel"> <li id="augmentations-tab" class="mainmenu-accordion-panel">
<button id="augmentations-menu-link" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> Augmentations </button> <button id="augmentations-menu-link" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> Augmentations </button>
<span id="augmentations-notification" class="notification-off"> </span>
</li> </li>
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel"> <li id="hacknet-nodes-tab" class="mainmenu-accordion-panel">
<button id="hacknet-nodes-menu-link"> Hacknet Nodes </button> <button id="hacknet-nodes-menu-link"> Hacknet Nodes </button>

@ -24,14 +24,14 @@ export function ScriptProduction(props: IProps): React.ReactElement {
return ( return (
<p id="active-scripts-total-prod"> <p id="active-scripts-total-prod">
Total online production of Active scripts: Total online production of Active scripts:&nbsp;
<span className="money-gold"> <span className="money-gold">
<span id="active-scripts-total-production-active"> <span id="active-scripts-total-production-active">
{numeralWrapper.formatMoney(onlineProduction)} {numeralWrapper.formatMoney(onlineProduction)}
</span> / sec </span> / sec
</span><br /> </span><br />
Total online production since last Aug installation: Total online production since last Aug installation:&nbsp;
<span id="active-scripts-total-prod-aug-total" className="money-gold"> <span id="active-scripts-total-prod-aug-total" className="money-gold">
{numeralWrapper.formatMoney(props.p.scriptProdSinceLastAug)} {numeralWrapper.formatMoney(props.p.scriptProdSinceLastAug)}
</span> </span>

238
src/ui/CharacterInfo.tsx Normal file

@ -0,0 +1,238 @@
import * as React from "react";
import { numeralWrapper } from "../ui/numeralFormat";
import { BitNodes } from "../BitNode/BitNode";
import { IPlayer } from "../PersonObjects/IPlayer";
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
import { getPurchaseServerLimit } from "../Server/ServerPurchases";
import { MaxNumberHacknetServers } from "../Hacknet/HacknetServer";
export function CharacterInfo(p: IPlayer): React.ReactElement {
function LastEmployer(): React.ReactElement {
if (p.companyName) {
return <><span>Employer at which you last worked: {p.companyName}</span><br /></>;
}
return <></>;
}
function LastJob(): React.ReactElement {
if (p.companyName !== "") {
return <><span>Job you last worked: {p.jobs[p.companyName]}</span><br /></>;
}
return <></>;
}
function Employers(): React.ReactElement {
if (p.jobs && Object.keys(p.jobs).length !== 0)
return <>
<span>All Employers:</span><br />
<ul>
{Object.keys(p.jobs).map(j => <li key='j'> * {j}</li>)}
</ul><br /><br />
</>
return <></>;
}
function convertMoneySourceTrackerToString(src: MoneySourceTracker): string {
let parts: string[] = [`Total: ${numeralWrapper.formatMoney(src.total)}`];
if (src.bladeburner) { parts.push(`Bladeburner: ${numeralWrapper.formatMoney(src.bladeburner)}`) };
if (src.codingcontract) { parts.push(`Coding Contracts: ${numeralWrapper.formatMoney(src.codingcontract)}`) };
if (src.work) { parts.push(`Company Work: ${numeralWrapper.formatMoney(src.work)}`) };
if (src.corporation) { parts.push(`Corporation: ${numeralWrapper.formatMoney(src.corporation)}`) };
if (src.crime) { parts.push(`Crimes: ${numeralWrapper.formatMoney(src.crime)}`) };
if (src.gang) { parts.push(`Gang: ${numeralWrapper.formatMoney(src.gang)}`) };
if (src.hacking) { parts.push(`Hacking: ${numeralWrapper.formatMoney(src.hacking)}`) };
if (src.hacknetnode) { parts.push(`Hacknet Nodes: ${numeralWrapper.formatMoney(src.hacknetnode)}`) };
if (src.hospitalization) { parts.push(`Hospitalization: ${numeralWrapper.formatMoney(src.hospitalization)}`) };
if (src.infiltration) { parts.push(`Infiltration: ${numeralWrapper.formatMoney(src.infiltration)}`) };
if (src.stock) { parts.push(`Stock Market: ${numeralWrapper.formatMoney(src.stock)}`) };
return parts.join("<br>");
}
function openMoneyModal() {
let txt: string = "<u>Money earned since you last installed Augmentations:</u><br>" +
convertMoneySourceTrackerToString(p.moneySourceA);
if (p.sourceFiles.length !== 0) {
txt += "<br><br><u>Money earned in this BitNode:</u><br>" +
convertMoneySourceTrackerToString(p.moneySourceB);
}
dialogBoxCreate(txt, false);
}
function Intelligence(): React.ReactElement {
if (p.intelligence > 0) {
return <tr key='5'>
<td>Intelligence:</td>
<td style={{textAlign: 'right'}}>{(p.intelligence).toLocaleString()}</td>
</tr>;
}
return <></>;
}
function MultiplierTable(props: any): React.ReactElement {
function bn5Stat(r: any) {
if(SourceFileFlags[5] > 0 && r.length > 2 && r[1] != r[2]) {
return <td key='2' style={{textAlign: 'right'}}> ({numeralWrapper.formatPercentage(r[2])})</td>
}
return undefined;
}
return <>
<table>
<tbody>
{props.rows.map((r: any) => <tr key={r[0]}>
<td key='0'>{r[0]} multiplier: </td>
<td key='1' style={{textAlign: 'right'}}>{numeralWrapper.formatPercentage(r[1])}</td>
{bn5Stat(r)}
</tr>)}
</tbody>
</table>
</>
}
function BitNodeTimeText(): React.ReactElement {
if(p.sourceFiles.length > 0) {
return <>
<span>Time played since last Bitnode destroyed: {convertTimeMsToTimeElapsedString(p.playtimeSinceLastBitnode)}</span>
<br />
</>
}
return <></>
}
function CurrentBitNode(): React.ReactElement {
if(p.sourceFiles.length > 0) {
const index = "BitNode" + p.bitNodeN;
return <>
<span>Current BitNode: {p.bitNodeN} ({BitNodes[index].name})</span><br /><br />
<div style={{width:"60%", fontSize: "13px", marginLeft:"4%"}}>
{BitNodes[index].info.split("<br>").map((t, i) => <div key={i}>
<span style={{whiteSpace: 'pre-wrap', overflowWrap: 'break-word'}}>{t}</span><br />
</div>)}
</div>
</>
}
return <></>
}
return (
<pre>
<b>General</b>
<br /><br />
<span>Current City: {p.city}</span><br />
<LastEmployer />
<LastJob />
<Employers />
<span>Money: {numeralWrapper.formatMoney(p.money.toNumber())}</span>
<button className="popup-box-button" style={{display: 'inline-block', float: 'none'}} onClick={openMoneyModal}>Money Statistics & Breakdown</button><br /><br />
<b>Stats</b>
<table>
<tbody>
<tr key='0'>
<td key='0'>Hacking:</td>
<td key='1' style={{textAlign: 'right'}}>{p.hacking_skill.toLocaleString()}</td>
<td key='2' style={{textAlign: 'right'}}>({numeralWrapper.format(p.hacking_exp, '0.000a')} exp)</td>
</tr>
<tr key='1'>
<td key='0'>Strength:</td>
<td key='1' style={{textAlign: 'right'}}>{p.strength.toLocaleString()}</td>
<td key='2' style={{textAlign: 'right'}}>({numeralWrapper.format(p.strength_exp, '0.000a')} exp)</td>
</tr>
<tr key='2'>
<td key='0'>Defense:</td>
<td key='1' style={{textAlign: 'right'}}>{p.defense.toLocaleString()}</td>
<td key='2' style={{textAlign: 'right'}}>({numeralWrapper.format(p.defense_exp, '0.000a')} exp)</td>
</tr>
<tr key='3'>
<td key='0'>Dexterity:</td>
<td key='1' style={{textAlign: 'right'}}>{p.dexterity.toLocaleString()}</td>
<td key='2' style={{textAlign: 'right'}}>({numeralWrapper.format(p.dexterity_exp, '0.000a')} exp)</td>
</tr>
<tr key='4'>
<td key='0'>Agility:</td>
<td key='1' style={{textAlign: 'right'}}>{p.agility.toLocaleString()}</td>
<td key='2' style={{textAlign: 'right'}}>({numeralWrapper.format(p.agility_exp, '0.000a')} exp)</td>
</tr>
<tr key='5'>
<td key='0'>Charisma:</td>
<td key='1' style={{textAlign: 'right'}}>{p.charisma.toLocaleString()}</td>
<td key='2' style={{textAlign: 'right'}}>({numeralWrapper.format(p.charisma_exp, '0.000a')} exp)</td>
</tr>
<Intelligence />
</tbody>
</table>
<br />
<MultiplierTable rows={[
['Hacking Chance', p.hacking_chance_mult],
['Hacking Speed', p.hacking_speed_mult],
['Hacking Money', p.hacking_money_mult, p.hacking_speed_mult*BitNodeMultipliers.ScriptHackMoney],
['Hacking Growth', p.hacking_grow_mult, p.hacking_money_mult*BitNodeMultipliers.ServerGrowthRate]
]} /><br />
<MultiplierTable rows={[
['Hacking Level', p.hacking_mult, p.hacking_mult*BitNodeMultipliers.HackingLevelMultiplier],
['Hacking Experience', p.hacking_exp_mult, p.hacking_exp_mult*BitNodeMultipliers.HackExpGain],
]} /><br />
<MultiplierTable rows={[
['Strength Level', p.strength_mult, p.strength_mult*BitNodeMultipliers.StrengthLevelMultiplier],
['Strength Experience', p.strength_exp_mult]
]} /><br />
<MultiplierTable rows={[
['Defense Level', p.defense_mult, p.defense_mult*BitNodeMultipliers.DefenseLevelMultiplier],
['Defense Experience', p.defense_exp_mult]
]} /><br />
<MultiplierTable rows={[
['Dexterity Level', p.dexterity_mult, p.dexterity_mult*BitNodeMultipliers.DexterityLevelMultiplier],
['Dexterity Experience', p.dexterity_exp_mult]
]} /><br />
<MultiplierTable rows={[
['Agility Level', p.agility_mult, p.agility_mult*BitNodeMultipliers.AgilityLevelMultiplier],
['Agility Experience', p.agility_exp_mult]
]} /><br />
<MultiplierTable rows={[
['Charisma Level', p.charisma_mult, p.charisma_mult*BitNodeMultipliers.CharismaLevelMultiplier],
['Charisma Experience', p.charisma_exp_mult]
]} /><br />
<MultiplierTable rows={[
['Hacknet Node production', p.hacknet_node_money_mult, p.hacknet_node_money_mult*BitNodeMultipliers.HacknetNodeMoney],
['Hacknet Node purchase cost', p.hacknet_node_purchase_cost_mult],
['Hacknet Node RAM upgrade cost', p.hacknet_node_ram_cost_mult],
['Hacknet Node Core purchase cost', p.hacknet_node_core_cost_mult],
['Hacknet Node level upgrade cost', p.hacknet_node_level_cost_mult]
]} /><br />
<MultiplierTable rows={[
['Company reputation gain', p.company_rep_mult],
['Faction reputation gain', p.faction_rep_mult, p.faction_rep_mult*BitNodeMultipliers.FactionWorkRepGain],
['Salary', p.work_money_mult, p.work_money_mult*BitNodeMultipliers.CompanyWorkMoney]
]} /><br />
<MultiplierTable rows={[
['Crime success', p.crime_success_mult],
['Crime money', p.crime_money_mult, p.crime_money_mult*BitNodeMultipliers.CrimeMoney]
]} /><br /><br />
<b>Misc.</b><br /><br />
<span>Servers owned: {p.purchasedServers.length} / {getPurchaseServerLimit()}</span><br />
<span>Hacknet Nodes owned: {p.hacknetNodes.length} / {MaxNumberHacknetServers}</span><br />
<span>Augmentations installed: {p.augmentations.length}</span><br />
<span>Time played since last Augmentation: {convertTimeMsToTimeElapsedString(p.playtimeSinceLastAug)}</span><br />
<BitNodeTimeText />
<span>Time played: {convertTimeMsToTimeElapsedString(p.totalPlaytime)}</span><br />
<br />
<CurrentBitNode />
</pre>
)
}

@ -10,7 +10,7 @@ export class CharacterOverviewComponent extends Component {
render() { render() {
const intelligence = ( const intelligence = (
<tr id="character-int-wrapper"> <tr id="character-int-wrapper">
<td>Int:&nbsp;</td><td id="character-int-text" className="character-stat-cell">{(Player.intelligence).toLocaleString()}</td> <td className="character-int-cell">Int:&nbsp;</td><td id="character-int-text" className="character-int-cell character-stat-cell">{(Player.intelligence).toLocaleString()}</td>
</tr> </tr>
); );
@ -19,28 +19,28 @@ export class CharacterOverviewComponent extends Component {
<table> <table>
<tbody> <tbody>
<tr id="character-hp-wrapper"> <tr id="character-hp-wrapper">
<td>Hp:</td><td id="character-hp-text" className="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">{Player.hp + " / " + Player.max_hp}</td>
</tr> </tr>
<tr id="character-money-wrapper"> <tr id="character-money-wrapper">
<td>Money:&nbsp;</td><td id="character-money-text" className="character-stat-cell">{numeralWrapper.format(Player.money.toNumber(), '$0.000a')}</td> <td className="character-money-cell">Money:&nbsp;</td><td id="character-money-text" className="character-money-cell character-stat-cell">{numeralWrapper.format(Player.money.toNumber(), '$0.000a')}</td>
</tr> </tr>
<tr id="character-hack-wrapper"> <tr id="character-hack-wrapper">
<td>Hack:&nbsp;</td><td id="character-hack-text" className="character-stat-cell">{(Player.hacking_skill).toLocaleString()}</td> <td className="character-hack-cell">Hack:&nbsp;</td><td id="character-hack-text" className="character-hack-cell character-stat-cell">{(Player.hacking_skill).toLocaleString()}</td>
</tr> </tr>
<tr id="character-str-wrapper"> <tr id="character-str-wrapper">
<td>Str:&nbsp;</td><td id="character-str-text" className="character-stat-cell">{(Player.strength).toLocaleString()}</td> <td className="character-combat-cell">Str:&nbsp;</td><td id="character-str-text" className="character-combat-cell character-stat-cell">{(Player.strength).toLocaleString()}</td>
</tr> </tr>
<tr id="character-def-wrapper"> <tr id="character-def-wrapper">
<td>Def:&nbsp;</td><td id="character-def-text" className="character-stat-cell">{(Player.defense).toLocaleString()}</td> <td className="character-combat-cell">Def:&nbsp;</td><td id="character-def-text" className="character-combat-cell character-stat-cell">{(Player.defense).toLocaleString()}</td>
</tr> </tr>
<tr id="character-dex-wrapper"> <tr id="character-dex-wrapper">
<td>Dex:&nbsp;</td><td id="character-dex-text" className="character-stat-cell">{(Player.dexterity).toLocaleString()}</td> <td className="character-combat-cell">Dex:&nbsp;</td><td id="character-dex-text" className="character-combat-cell character-stat-cell">{(Player.dexterity).toLocaleString()}</td>
</tr> </tr>
<tr id="character-agi-wrapper"> <tr id="character-agi-wrapper">
<td>Agi:&nbsp;</td><td id="character-agi-text" className="character-stat-cell">{(Player.agility).toLocaleString()}</td> <td className="character-combat-cell">Agi:&nbsp;</td><td id="character-agi-text" className="character-combat-cell character-stat-cell">{(Player.agility).toLocaleString()}</td>
</tr> </tr>
<tr id="character-cha-wrapper"> <tr id="character-cha-wrapper">
<td>Cha:&nbsp;</td><td id="character-cha-text" className="character-stat-cell">{(Player.charisma).toLocaleString()}</td> <td className="character-cha-cell">Cha:&nbsp;</td><td id="character-cha-text" className="character-cha-cell character-stat-cell">{(Player.charisma).toLocaleString()}</td>
</tr> </tr>
{ {
Player.intelligence >= 1 && Player.intelligence >= 1 &&

@ -16,7 +16,7 @@ type IProps = {
} }
export function SourceFileAccordion(props: IProps): React.ReactElement { export function SourceFileAccordion(props: IProps): React.ReactElement {
const maxLevel = props.sf.n === 3 ? "∞" : "3"; const maxLevel = props.sf.n === 12 ? "∞" : "3";
return ( return (
<Accordion <Accordion

@ -1,157 +0,0 @@
// Displays character info on a given element. This is used to create & update
// the 'Stats' page from the main menu
import { BitNodes } from "../BitNode/BitNode";
import { IPlayer } from "../PersonObjects/IPlayer";
import { numeralWrapper } from "../ui/numeralFormat";
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { createElement } from "../../utils/uiHelpers/createElement";
import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement";
export function displayCharacterInfo(elem: HTMLElement, p: IPlayer) {
removeChildrenFromElement(elem);
let companyPosition = "";
if (p.companyName !== "") {
companyPosition = p.jobs[p.companyName];
}
var intText = "";
if (p.intelligence > 0) {
intText = 'Intelligence: ' + (p.intelligence).toLocaleString() + '<br>';
}
let bitNodeTimeText = "";
if(p.sourceFiles.length > 0) {
bitNodeTimeText = 'Time played since last Bitnode destroyed: ' + convertTimeMsToTimeElapsedString(p.playtimeSinceLastBitnode) + '<br>';
}
const unlockedBitnodes: boolean = p.sourceFiles.length !== 0;
// General info
elem.appendChild(createElement("pre", {
display: "block",
innerHTML:
'<b>General</b><br><br>' +
'Current City: ' + p.city + '<br><br>' +
`Employer at which you last worked: ${p.companyName}<br>` +
`Job you last worked: ${companyPosition}<br>` +
`All Employers: ${Object.keys(p.jobs).join(", ")}<br><br>`
}));
// Money, and a button to show money breakdown
elem.appendChild(createElement("pre", {
display: "inline-block",
innerHTML: 'Money: ' + numeralWrapper.formatMoney(p.money.toNumber()) + '<br><br><br>',
margin: "6px",
}));
function convertMoneySourceTrackerToString(src: MoneySourceTracker): string {
let parts: string[] = [`Total: ${numeralWrapper.formatMoney(src.total)}`];
if (src.bladeburner) { parts.push(`Bladeburner: ${numeralWrapper.formatMoney(src.bladeburner)}`) };
if (src.codingcontract) { parts.push(`Coding Contracts: ${numeralWrapper.formatMoney(src.codingcontract)}`) };
if (src.work) { parts.push(`Company Work: ${numeralWrapper.formatMoney(src.work)}`) };
if (src.corporation) { parts.push(`Corporation: ${numeralWrapper.formatMoney(src.corporation)}`) };
if (src.crime) { parts.push(`Crimes: ${numeralWrapper.formatMoney(src.crime)}`) };
if (src.gang) { parts.push(`Gang: ${numeralWrapper.formatMoney(src.gang)}`) };
if (src.hacking) { parts.push(`Hacking: ${numeralWrapper.formatMoney(src.hacking)}`) };
if (src.hacknetnode) { parts.push(`Hacknet Nodes: ${numeralWrapper.formatMoney(src.hacknetnode)}`) };
if (src.hospitalization) { parts.push(`Hospitalization: ${numeralWrapper.formatMoney(src.hospitalization)}`) };
if (src.infiltration) { parts.push(`Infiltration: ${numeralWrapper.formatMoney(src.infiltration)}`) };
if (src.stock) { parts.push(`Stock Market: ${numeralWrapper.formatMoney(src.stock)}`) };
return parts.join("<br>");
}
elem.appendChild(createElement("button", {
class: "popup-box-button",
display: "inline-block",
float: "none",
innerText: "Money Statistics & Breakdown",
clickListener: () => {
let txt: string = "<u>Money earned since you last installed Augmentations:</u><br>" +
convertMoneySourceTrackerToString(p.moneySourceA);
if (unlockedBitnodes) {
txt += "<br><br><u>Money earned in this BitNode:</u><br>" +
convertMoneySourceTrackerToString(p.moneySourceB);
}
dialogBoxCreate(txt, false);
}
}));
// Stats and multiplier
elem.appendChild(createElement("pre", {
display: "block",
innerHTML:
'<b>Stats</b><br><br>' +
'Hacking Level: ' + (p.hacking_skill).toLocaleString() +
' (' + numeralWrapper.format(p.hacking_exp, '(0.000a)') + ' experience)<br>' +
'Strength: ' + (p.strength).toLocaleString() +
' (' + numeralWrapper.format(p.strength_exp, '(0.000a)') + ' experience)<br>' +
'Defense: ' + (p.defense).toLocaleString() +
' (' + numeralWrapper.format(p.defense_exp, '(0.000a)') + ' experience)<br>' +
'Dexterity: ' + (p.dexterity).toLocaleString() +
' (' + numeralWrapper.format(p.dexterity_exp, '(0.000a)') + ' experience)<br>' +
'Agility: ' + (p.agility).toLocaleString() +
' (' + numeralWrapper.format(p.agility_exp, '(0.000a)') + ' experience)<br>' +
'Charisma: ' + (p.charisma).toLocaleString() +
' (' + numeralWrapper.format(p.charisma_exp, '(0.000a)') + ' experience)<br>' +
intText + '<br><br>' +
'<b>Multipliers</b><br><br>' +
'Hacking Chance multiplier: ' + numeralWrapper.formatPercentage(p.hacking_chance_mult) + '<br>' +
'Hacking Speed multiplier: ' + numeralWrapper.formatPercentage(p.hacking_speed_mult) + '<br>' +
'Hacking Money multiplier: ' + numeralWrapper.formatPercentage(p.hacking_money_mult) + '<br>' +
'Hacking Growth multiplier: ' + numeralWrapper.formatPercentage(p.hacking_grow_mult) + '<br><br>' +
'Hacking Level multiplier: ' + numeralWrapper.formatPercentage(p.hacking_mult) + '<br>' +
'Hacking Experience multiplier: ' + numeralWrapper.formatPercentage(p.hacking_exp_mult) + '<br><br>' +
'Strength Level multiplier: ' + numeralWrapper.formatPercentage(p.strength_mult) + '<br>' +
'Strength Experience multiplier: ' + numeralWrapper.formatPercentage(p.strength_exp_mult) + '<br><br>' +
'Defense Level multiplier: ' + numeralWrapper.formatPercentage(p.defense_mult) + '<br>' +
'Defense Experience multiplier: ' + numeralWrapper.formatPercentage(p.defense_exp_mult) + '<br><br>' +
'Dexterity Level multiplier: ' + numeralWrapper.formatPercentage(p.dexterity_mult) + '<br>' +
'Dexterity Experience multiplier: ' + numeralWrapper.formatPercentage(p.dexterity_exp_mult) + '<br><br>' +
'Agility Level multiplier: ' + numeralWrapper.formatPercentage(p.agility_mult) + '<br>' +
'Agility Experience multiplier: ' + numeralWrapper.formatPercentage(p.agility_exp_mult) + '<br><br>' +
'Charisma Level multiplier: ' + numeralWrapper.formatPercentage(p.charisma_mult) + '<br>' +
'Charisma Experience multiplier: ' + numeralWrapper.formatPercentage(p.charisma_exp_mult) + '<br><br>' +
'Hacknet Node production multiplier: ' + numeralWrapper.formatPercentage(p.hacknet_node_money_mult) + '<br>' +
'Hacknet Node purchase cost multiplier: ' + numeralWrapper.formatPercentage(p.hacknet_node_purchase_cost_mult) + '<br>' +
'Hacknet Node RAM upgrade cost multiplier: ' + numeralWrapper.formatPercentage(p.hacknet_node_ram_cost_mult) + '<br>' +
'Hacknet Node Core purchase cost multiplier: ' + numeralWrapper.formatPercentage(p.hacknet_node_core_cost_mult) + '<br>' +
'Hacknet Node level upgrade cost multiplier: ' + numeralWrapper.formatPercentage(p.hacknet_node_level_cost_mult) + '<br><br>' +
'Company reputation gain multiplier: ' + numeralWrapper.formatPercentage(p.company_rep_mult) + '<br>' +
'Faction reputation gain multiplier: ' + numeralWrapper.formatPercentage(p.faction_rep_mult) + '<br>' +
'Salary multiplier: ' + numeralWrapper.formatPercentage(p.work_money_mult) + '<br>' +
'Crime success multiplier: ' + numeralWrapper.formatPercentage(p.crime_success_mult) + '<br>' +
'Crime money multiplier: ' + numeralWrapper.formatPercentage(p.crime_money_mult) + '<br><br><br>' +
'<b>Misc</b><br><br>' +
'Servers owned: ' + p.purchasedServers.length + '<br>' +
'Hacknet Nodes owned: ' + p.hacknetNodes.length + '<br>' +
'Augmentations installed: ' + p.augmentations.length + '<br>' +
'Time played since last Augmentation: ' + convertTimeMsToTimeElapsedString(p.playtimeSinceLastAug) + '<br>' +
bitNodeTimeText +
'Time played: ' + convertTimeMsToTimeElapsedString(p.totalPlaytime),
}));
// BitNode information, if player has gotten that far
if (unlockedBitnodes) {
var index = "BitNode" + p.bitNodeN;
elem.appendChild(createElement("p", {
width:"60%",
innerHTML:
"<br>Current BitNode: " + p.bitNodeN + " (" + BitNodes[index].name + ")<br><br>",
}));
elem.appendChild(createElement("p", {
width:"60%", fontSize: "13px", marginLeft:"4%",
innerHTML:BitNodes[index].info,
}))
}
}

@ -42,7 +42,6 @@ document.addEventListener("keydown", function (event) {
let dialogBoxOpened = false; let dialogBoxOpened = false;
function dialogBoxCreate(txt, preformatted=false) { function dialogBoxCreate(txt, preformatted=false) {
console.log(`dialogBoxCreate() called`)
var container = document.createElement("div"); var container = document.createElement("div");
container.setAttribute("class", "dialog-box-container"); container.setAttribute("class", "dialog-box-container");