mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 14:28:36 +02:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d2b8b4f6f | ||
|
|
b148b2f0b5 | ||
|
|
0b3c114cd0 | ||
|
|
e0d631f8b3 | ||
|
|
d66e36b637 | ||
|
|
a564957092 | ||
|
|
4b8e63f342 | ||
|
|
4de20f8cce | ||
|
|
4b38d296a8 | ||
|
|
6561413137 | ||
|
|
b67c03ff8a | ||
|
|
ee5a70901b | ||
|
|
63b2c77907 | ||
|
|
474befa091 | ||
|
|
cd1c1ce145 | ||
|
|
5aa24f22c4 | ||
|
|
f02c6443cc | ||
|
|
4497143785 | ||
|
|
0b3c48827b | ||
|
|
86de11e794 | ||
|
|
fb87385704 | ||
|
|
b1caea796a | ||
|
|
2dfd19c9e0 | ||
|
|
0e24020796 | ||
|
|
ed62a3ebc2 | ||
|
|
258716388e | ||
|
|
73ec97db87 | ||
|
|
567c5dc230 | ||
|
|
980665b77c | ||
|
|
dcddc0c2d5 | ||
|
|
6e1100750e | ||
|
|
df457a0c6e | ||
|
|
ee3530d9b9 | ||
|
|
1a1a43c1ce | ||
|
|
d6b349b6ff | ||
|
|
5c92360310 | ||
|
|
1fbb971d6f | ||
|
|
fa78b3f421 | ||
|
|
9af9bf58b6 | ||
|
|
99afb156fa | ||
|
|
8d550157bc | ||
|
|
4865563f26 | ||
|
|
cc8de58cff | ||
|
|
58ada6d128 | ||
|
|
ae6f95b59a | ||
|
|
99d4f17cdb | ||
|
|
33f0efd49c | ||
|
|
988ca37764 | ||
|
|
0e9d7450c9 | ||
|
|
78cd319c21 | ||
|
|
7367167019 | ||
|
|
392f164f8e | ||
|
|
e4b2a6853d | ||
|
|
697a8119b0 | ||
|
|
b31b3dc735 | ||
|
|
5848fa53b7 | ||
|
|
539b206cb5 | ||
|
|
0f92890f0c | ||
|
|
056f0213dd | ||
|
|
41871de26c | ||
|
|
5803ddc613 | ||
|
|
99263309ba | ||
|
|
8a78ee4cf6 | ||
|
|
af46324c6d | ||
|
|
42aa6525a6 | ||
|
|
05b1b55e9a | ||
|
|
febf0835c2 | ||
|
|
94ea0d253c | ||
|
|
31cf02e8e4 | ||
|
|
97fdf7cb7f | ||
|
|
2c7fbc03cf | ||
|
|
07cca48a17 | ||
|
|
43d0fcb9f9 | ||
|
|
99b8dfa0c1 | ||
|
|
4cdd65e96c | ||
|
|
9e345b1375 | ||
|
|
9466017906 | ||
|
|
9c24f1325f | ||
|
|
26401fbb93 | ||
|
|
69dfbb6673 | ||
|
|
5863797b03 | ||
|
|
25f546c691 | ||
|
|
67e5e413e4 | ||
|
|
796d91835c | ||
|
|
be8d56ced9 | ||
|
|
0671c48c86 | ||
|
|
94388ba6af | ||
|
|
306fe97ed8 | ||
|
|
c9b47ac3a6 | ||
|
|
1aa141b87e | ||
|
|
abe204109e | ||
|
|
434dd2b58d |
@@ -1,5 +1,6 @@
|
||||
@import "mixins";
|
||||
@import "theme";
|
||||
@import "styles";
|
||||
|
||||
/**
|
||||
* Styling for all buttons
|
||||
@@ -16,6 +17,7 @@ button {
|
||||
|
||||
.a-link-button,
|
||||
.std-button {
|
||||
@extend .noselect;
|
||||
text-decoration: none;
|
||||
background-color: #555;
|
||||
color: #fff;
|
||||
@@ -23,11 +25,6 @@ button {
|
||||
margin: 5px;
|
||||
border: 1px solid #333;
|
||||
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
@@ -68,6 +65,7 @@ button {
|
||||
|
||||
.a-link-button-bought,
|
||||
.std-button-bought {
|
||||
@extend .noselect;
|
||||
text-decoration: none;
|
||||
background-color: #0a0;
|
||||
color: #fff;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import "mixins";
|
||||
@import "theme";
|
||||
@import "styles";
|
||||
|
||||
/* Pop-up boxes */
|
||||
.popup-box-container {
|
||||
@@ -12,7 +13,6 @@
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rbga(var(--my-background-color), 0.4);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
width: 70%;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
z-index: 11; /* Sit on top of the container */
|
||||
color: var(--my-font-color);
|
||||
}
|
||||
|
||||
@@ -56,8 +57,7 @@
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.dialog-box-container,
|
||||
#log-box-container {
|
||||
.dialog-box-container {
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
@@ -72,8 +72,54 @@
|
||||
border: 5px solid var(--my-highlight-color);
|
||||
}
|
||||
|
||||
.dialog-box-content,
|
||||
#log-box-content {
|
||||
|
||||
.log-box-container {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
background-color: gray;
|
||||
width: 50%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 40%;
|
||||
margin: -10% 0 0 -25%;
|
||||
height: auto;
|
||||
max-height: 50%;
|
||||
z-index: 10;
|
||||
background-color: var(--my-background-color);
|
||||
border: 2px solid var(--my-highlight-color);
|
||||
}
|
||||
|
||||
.log-box-header {
|
||||
background-color: #333;
|
||||
border: 1px solid var(--my-highlight-color);
|
||||
display: flex;
|
||||
flex: row nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.log-box-log-container {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.log-box-button {
|
||||
color: #aaa;
|
||||
font-size: $defaultFontSize;
|
||||
font-weight: bold;
|
||||
padding: 2px;
|
||||
margin: 6px;
|
||||
border: 1px solid #fff;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.log-box-button:hover,
|
||||
.log-box-button:focus {
|
||||
color: var(--my-font-color);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dialog-box-content {
|
||||
z-index: 2;
|
||||
background-color: var(--my-background-color);
|
||||
padding: 10px;
|
||||
@@ -87,6 +133,7 @@
|
||||
.dialog-box-close-button {
|
||||
@include borderRadius(12px);
|
||||
@include boxShadow(1px 1px 3px #000);
|
||||
@extend .noselect;
|
||||
|
||||
float: right;
|
||||
color: #aaa;
|
||||
|
||||
@@ -9,19 +9,6 @@
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#ace-editor {
|
||||
margin: 10px;
|
||||
height: 80%;
|
||||
width: 100%;
|
||||
margin-left: 6px;
|
||||
padding-left: 6px;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
border: 2px solid var(--my-highlight-color);
|
||||
z-index: 1;
|
||||
font-family: $fontFamily;
|
||||
}
|
||||
|
||||
/* This temp element is used for auto adjusting filename field */
|
||||
.tmp-element {
|
||||
visibility: hidden;
|
||||
@@ -47,7 +34,6 @@
|
||||
|
||||
#script-editor-filename-wrapper {
|
||||
background-color: #555;
|
||||
margin-left: 6px;
|
||||
margin-right: 0;
|
||||
padding-left: 6px;
|
||||
width: 100%;
|
||||
@@ -108,17 +94,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Specific overrides for Ace Editor */
|
||||
.ace_line,
|
||||
.ace_line * {
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
.editor-options-container {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.ace_text-input {
|
||||
font-size: $defaultFontSize;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Specified overrides for Code mirror Editor are defined in codemirror-override.scss */
|
||||
.editor-options-line {
|
||||
display: flex;
|
||||
flex: row nowrap;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
}
|
||||
@@ -17,6 +17,8 @@ body {
|
||||
p,
|
||||
pre,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
.text,
|
||||
td {
|
||||
color: var(--my-font-color);
|
||||
@@ -36,6 +38,10 @@ li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
br {
|
||||
@extend .noselect;
|
||||
}
|
||||
|
||||
#entire-game-container {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
4
dist/engine.bundle.js
vendored
4
dist/engine.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/engineStyle.bundle.js
vendored
2
dist/engineStyle.bundle.js
vendored
@@ -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([852,0]),o()}({789:function(n,t,o){},791:function(n,t,o){},793:function(n,t,o){},795:function(n,t,o){},797:function(n,t,o){},799:function(n,t,o){},801:function(n,t,o){},803:function(n,t,o){},805:function(n,t,o){},807:function(n,t,o){},809:function(n,t,o){},811:function(n,t,o){},813:function(n,t,o){},815:function(n,t,o){},817:function(n,t,o){},819:function(n,t,o){},821:function(n,t,o){},823:function(n,t,o){},825:function(n,t,o){},827:function(n,t,o){},829:function(n,t,o){},831:function(n,t,o){},833:function(n,t,o){},835:function(n,t,o){},837:function(n,t,o){},839:function(n,t,o){},841:function(n,t,o){},843:function(n,t,o){},845:function(n,t,o){},847:function(n,t,o){},849:function(n,t,o){},852:function(n,t,o){"use strict";o.r(t);o(851),o(849),o(847),o(845),o(843),o(841),o(839),o(837),o(835),o(833),o(831),o(829),o(827),o(825),o(823),o(821),o(819),o(817),o(815),o(813),o(811),o(809),o(807),o(805),o(803),o(801),o(799),o(797),o(795),o(793),o(791),o(789)}});
|
||||
!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([863,0]),o()}({800:function(n,t,o){},802:function(n,t,o){},804:function(n,t,o){},806:function(n,t,o){},808:function(n,t,o){},810:function(n,t,o){},812:function(n,t,o){},814:function(n,t,o){},816:function(n,t,o){},818:function(n,t,o){},820:function(n,t,o){},822:function(n,t,o){},824:function(n,t,o){},826:function(n,t,o){},828:function(n,t,o){},830:function(n,t,o){},832:function(n,t,o){},834:function(n,t,o){},836:function(n,t,o){},838:function(n,t,o){},840:function(n,t,o){},842:function(n,t,o){},844:function(n,t,o){},846:function(n,t,o){},848:function(n,t,o){},850:function(n,t,o){},852:function(n,t,o){},854:function(n,t,o){},856:function(n,t,o){},858:function(n,t,o){},860:function(n,t,o){},863:function(n,t,o){"use strict";o.r(t);o(862),o(860),o(858),o(856),o(854),o(852),o(850),o(848),o(846),o(844),o(842),o(840),o(838),o(836),o(834),o(832),o(830),o(828),o(826),o(824),o(822),o(820),o(818),o(816),o(814),o(812),o(810),o(808),o(806),o(804),o(802),o(800)}});
|
||||
//# sourceMappingURL=engineStyle.bundle.js.map
|
||||
927
dist/engineStyle.css
vendored
927
dist/engineStyle.css
vendored
File diff suppressed because it is too large
Load Diff
77
dist/vendor.bundle.js
vendored
77
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
5536
dist/vendor.css
vendored
5536
dist/vendor.css
vendored
File diff suppressed because one or more lines are too long
@@ -3,10 +3,170 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
v0.52.8 - 2021-07-23 Fixing the previous patch tbh ROUND 2 (hydroflame)
|
||||
-------------------------------------------
|
||||
|
||||
** Script editor **
|
||||
|
||||
* Correctly reloads old script when clicking "Script Editor"
|
||||
* No longer jumps to the end of the text for no reason.
|
||||
|
||||
** Hash upgrades **
|
||||
|
||||
* Fixed an issue where the default option would say ecorp but was really
|
||||
foodnstuff
|
||||
|
||||
** Misc. **
|
||||
|
||||
* The "Delete all active script" button under the options has a clearer
|
||||
description.
|
||||
* Removed some debug console.log
|
||||
* nerf noodle bar
|
||||
|
||||
v0.52.7 - 2021-07-21 Fixing the previous patch tbh (hydroflame)
|
||||
-------------------------------------------
|
||||
|
||||
** Netscript **
|
||||
|
||||
* API BREAKING CHANGE: getActionEstimatedSuccessChance now returns a pair of
|
||||
value to reflect the UI changes. I'm very sorry.
|
||||
|
||||
** Bladeburner **
|
||||
|
||||
* General actions now display time required.
|
||||
* Recruitment now displays success chance.
|
||||
* All other success chance now display a range instead of a single value
|
||||
The real value is guaranteed to be within that range.
|
||||
|
||||
** Misc. **
|
||||
|
||||
* Fix tutorial not working after Monaco upate
|
||||
* Fix logbox logs not taking up the whole logbox
|
||||
* Fix script editor shortcut (ctrl+b)
|
||||
* Fix Corporation popup appearing in the wrong order, hiding one of them
|
||||
* Fix error when loading Corp
|
||||
* Fix logbox dragging (smoother now)
|
||||
* Fix logbox name collision
|
||||
* Fix logbox allowing to open the same box multiple times
|
||||
* Fix netscript write.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.52.6 - 2021-07-21 Logboxes and VS-code (hydroflame)
|
||||
-------------------------------------------
|
||||
|
||||
** Text Editor **
|
||||
|
||||
* Ace and Codemirror have been removed in favor of monaco (web version of
|
||||
vs-code). The options are a bit lackluster but more will be added as
|
||||
feedback comes.
|
||||
|
||||
** Log boxes **
|
||||
|
||||
* Multiple log boxes can be opened at once. They can be moved around the
|
||||
screen. (but the movement behavior is a bit weird.)
|
||||
|
||||
** Misc. **
|
||||
|
||||
* Job promotion now correctly updates the UI.
|
||||
* Milestones now call the faction CyberSec instead of CSEC
|
||||
* Can no longer create file that break the filesystem.
|
||||
* Remove dollar sign in blade contract UI element
|
||||
* nerf noodle bar
|
||||
|
||||
v0.52.5 - 2021-07-19 CPU cores are useful!? (hydroflame)
|
||||
-------------------------------------------
|
||||
|
||||
** Terminal **
|
||||
|
||||
* When executing 'run SCRIPT' any script can now add '--tail' to
|
||||
automatically bring up the logs.
|
||||
|
||||
** Netscript **
|
||||
|
||||
* The 'flags' function now works with single letter flags but they only take
|
||||
one dash.
|
||||
* Fix several broken bladeburner netscript functions.
|
||||
* Fix gang.getMemberInformation returning inconsistent data after the gang
|
||||
rework.
|
||||
|
||||
** CPU Cores **
|
||||
|
||||
* CPU Cores on the home computer now provide a bonus to grow() money gain
|
||||
and makes weaken lower more security. Only for scripts running on 'home'
|
||||
|
||||
** Misc. **
|
||||
|
||||
* Fix weird scrolling in the new Bladeburner React console.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.52.4 - 2021-07-19 Bladeburner in React (hydroflame)
|
||||
-------------------------------------------
|
||||
|
||||
** Bladeburner **
|
||||
|
||||
* The entire UI was rebuild in React. It should be more responsive
|
||||
|
||||
** Hacknet **
|
||||
|
||||
* Displays how many time each hash upgrade was bought.
|
||||
* Displays cummulative effect of the upgrade.
|
||||
* Removed "Close" button from hash upgrade menu.
|
||||
|
||||
** Misc. **
|
||||
|
||||
* More popup/modals have dark background, can be dismissed by clicking
|
||||
outside, or by pressing escape.
|
||||
* Small reword in the guide.
|
||||
* Fix several typos in the bladeburner documentation.
|
||||
* Linting (no one cares except the dev)
|
||||
* nerf noodle bar
|
||||
|
||||
v0.52.3 - 2021-07-15 Gangs were OP (hydroflame)
|
||||
-------------------------------------------
|
||||
|
||||
** Gang **
|
||||
|
||||
* Significant rework. Ascension is now based on exp gained.
|
||||
* All upgrades give exp bonuses.
|
||||
* Maximum gang members reduced to 12.
|
||||
* Respect required to recruit sharply increased.
|
||||
* Rewritten in React, the UI should be smoother and less laggy now.
|
||||
|
||||
** Infiltration **
|
||||
|
||||
* Now isTrusted protected.
|
||||
|
||||
** Misc. **
|
||||
|
||||
* Many UI element are now "noselect" protected.
|
||||
* Fixed an issue where you could join the same faction twice via script and
|
||||
UI simultaneously.
|
||||
* Factions list screen converted to React.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.52.2 - 2021-07-15 Oh yeah, BN11 is a thing (drunk hydroflame tbh)
|
||||
-------------------------------------------
|
||||
|
||||
** Source-Files **
|
||||
|
||||
* Source-File 11 now also provides a small reduction to the price increase
|
||||
multiplier.
|
||||
|
||||
** Augmentations **
|
||||
|
||||
* New Augmentation offered by Aevum, themed around 777 and offers some basic
|
||||
programs.
|
||||
* Augmentation descriptions are now more concise and consistent.
|
||||
|
||||
** Misc. **
|
||||
|
||||
* nerf noodle bar
|
||||
|
||||
v0.52.1 - 2021-07-10 bugfixing (hydroflame & community)
|
||||
-------------------------------------------
|
||||
|
||||
**Misc.**
|
||||
|
||||
* Fix game crash/corruption when quitting a job while working for it unfocused.
|
||||
* Fix typo in corporation Market Data.
|
||||
* Fix typo in docs for hackPercent.
|
||||
@@ -16,8 +176,12 @@ v0.52.1 - 2021-07-10 bugfixing (hydroflame & community)
|
||||
* Character overview screen no longer hidden on the corporation screen.
|
||||
* Infiltration difficulty display is now more explicit (It's a big arrow instead
|
||||
of just one word.)
|
||||
* Fix wrong ram value in tutorial. (@MageKing17)
|
||||
* Plenty of augmentation description cleanup (@Kwazygloo)
|
||||
* Plenty of typo/description fixed (@MageKing17)
|
||||
* Cleanup description of singularity function on readthedocs (@PurePandemonium)
|
||||
* Fix bug when autolinking a server while backdooring (@schroederIT)
|
||||
* nerf noodle bar
|
||||
|
||||
v0.52.0 - 2021-06-13 Infiltration 2.0 (hydroflame & community)
|
||||
--------------------------------------------------------------
|
||||
@@ -67,6 +231,7 @@ v0.52.0 - 2021-06-13 Infiltration 2.0 (hydroflame & community)
|
||||
* Fixed an issue where reputation could be transfered to new jobs when unfocused.
|
||||
* Empty stack traces should no longer appear.
|
||||
* Purchasing anything with Infinity money doesn't result in NaN.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.51.10 - 2021-05-31 Focus Mark, Focus! (hydroflame)
|
||||
-----------------------------------------------------
|
||||
@@ -110,6 +275,7 @@ v0.51.10 - 2021-05-31 Focus Mark, Focus! (hydroflame)
|
||||
|
||||
* Very large number will no longer appear as "$NaNt"
|
||||
* Hash capacity now displays in the "big number" format.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.51.9 - 2021-05-17 offline progress and exports! (hydroflame & community)
|
||||
---------------------------------------------------------------
|
||||
@@ -163,6 +329,7 @@ v0.51.9 - 2021-05-17 offline progress and exports! (hydroflame & community)
|
||||
* Updated several dependencies (big who cares, I know)
|
||||
* ls no longer prints lingering newline.
|
||||
* Money earned/spent by sleeves is now tracked under Character>Money
|
||||
* nerf noodle bar
|
||||
|
||||
|
||||
v0.51.8 - 2021-05-07 It was there all along (hydroflame & community)
|
||||
@@ -220,6 +387,7 @@ v0.51.8 - 2021-05-07 It was there all along (hydroflame & community)
|
||||
* Fix infiltration number formatting.
|
||||
* script income transfers to parent on death. This helps keep track of
|
||||
income for scripts that spawn short lived scripts.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.51.7 - 2021-04-28 n00dles (hydroflame & community)
|
||||
-----------------------------------------
|
||||
@@ -277,6 +445,7 @@ v0.51.7 - 2021-04-28 n00dles (hydroflame & community)
|
||||
* Money amount under 1000 dont display 3 decimal anymore.
|
||||
* Fix nextSourceFile flag miscalculation on the bitverse (for Bn12)
|
||||
* Faction invite text says "Decide later"/"Join!" instead of "No"/"Yes"
|
||||
* nerf noodle bar
|
||||
|
||||
|
||||
v0.51.6 - 2021-04-28 Backdoor! (hydroflame & community)
|
||||
@@ -327,6 +496,7 @@ v0.51.6 - 2021-04-28 Backdoor! (hydroflame & community)
|
||||
* so many documentation and typos fixes (@Pimgd)
|
||||
* A corruption visual effect has been added to location with servers that
|
||||
have backdoor installed. (@dewint)
|
||||
* nerf noodle bar
|
||||
|
||||
|
||||
v0.51.5 - 2021-04-20 Flags! (hydroflame)
|
||||
@@ -351,6 +521,7 @@ v0.51.5 - 2021-04-20 Flags! (hydroflame)
|
||||
* Souce-File typo fix
|
||||
* Fix 'while you were away' screen.
|
||||
* Bladeburner team size can no longer be set to negative amounts.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame)
|
||||
-------------------------------------------------------
|
||||
@@ -386,6 +557,7 @@ v0.51.4 - 2021-04-19 Manual hacking is fun (hydroflame)
|
||||
* The text editor now remembers the location of your cursor and restores it.
|
||||
* skills are recalculated instantly.
|
||||
* Fix typo in Operation Zero description.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.51.3 - 2021-04-16 Y'all broke it on the first day (hydroflame)
|
||||
-----------------------------------------------------------------
|
||||
@@ -427,6 +599,7 @@ v0.51.3 - 2021-04-16 Y'all broke it on the first day (hydroflame)
|
||||
* 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.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.51.2 - 2021-04-09 Vegas, Baby! (hydroflame)
|
||||
----------------------------------------------
|
||||
@@ -441,6 +614,7 @@ v0.51.2 - 2021-04-09 Vegas, Baby! (hydroflame)
|
||||
* Link to discord added under options
|
||||
* 'getMemberInformation' doc updated, oops
|
||||
* tech vendor now handle max ram and cores.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.51.1 - 2021-04-06 Bugfixes because the author of the last patch sucks (it's hydroflame)
|
||||
------------------------------------------------------------------------------------------
|
||||
@@ -472,6 +646,7 @@ v0.51.1 - 2021-04-06 Bugfixes because the author of the last patch sucks (it's h
|
||||
|
||||
* 'fl1ght.exe' will no longer suggest the combat path. Related faction
|
||||
requirements unchanged.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.51.0 - 2021-03-31 Formulas (hydroflame)
|
||||
------------------------------------------
|
||||
@@ -498,6 +673,10 @@ v0.51.0 - 2021-03-31 Formulas (hydroflame)
|
||||
|
||||
* Certain UI elements are now 'click-to-copy'
|
||||
|
||||
** Misc. **
|
||||
|
||||
* nerf noodle bar
|
||||
|
||||
v0.50.2 - 2021-03-25 Everyone asked for this one. (hydroflame)
|
||||
--------------------------------------------------------------
|
||||
|
||||
@@ -515,6 +694,7 @@ v0.50.2 - 2021-03-25 Everyone asked for this one. (hydroflame)
|
||||
|
||||
* New shortcut, Alt + b, brings you to bladeburner
|
||||
* New shortcut, Alt + g, brings you to gang
|
||||
* nerf noodle bar
|
||||
|
||||
v0.50.1 - 2021-03-22 (hydroflame)
|
||||
---------------------------------
|
||||
@@ -537,6 +717,7 @@ v0.50.1 - 2021-03-22 (hydroflame)
|
||||
**Misc.**
|
||||
|
||||
* Minor spacing in stats tables.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.50.0 - 2021-03-20 Intelligence (hydroflame)
|
||||
----------------------------------------------
|
||||
@@ -557,6 +738,7 @@ v0.50.0 - 2021-03-20 Intelligence (hydroflame)
|
||||
* number formatting
|
||||
* remove wiki button in Hacking Missions.
|
||||
* Fix NaN displayed when very very large numbers are reached.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.49.2 - 2021-03-13 (hydroflame)
|
||||
---------------------------------
|
||||
@@ -594,6 +776,7 @@ v0.49.2 - 2021-03-13 (hydroflame)
|
||||
**Misc.**
|
||||
|
||||
* Fix issue where the effective stats under Character>Stats were being calculated.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.49.0 - 2021-03-11 Source-File -1 (hydroflame)
|
||||
------------------------------------------------
|
||||
@@ -621,6 +804,7 @@ v0.49.0 - 2021-03-11 Source-File -1 (hydroflame)
|
||||
|
||||
* Minor formatting under Hacking>Active Scripts
|
||||
* option menu colors now match the rest of the game, kinda.
|
||||
* nerf noodle bar
|
||||
|
||||
|
||||
v0.48.0 - ASCII - 2021-03-07 (hydroflame)
|
||||
@@ -668,6 +852,7 @@ v0.48.0 - ASCII - 2021-03-07 (hydroflame)
|
||||
has bought but not installed
|
||||
* Character>Factions has a badge indicating how many factions have pending
|
||||
invites.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.47.2 - 7/15/2019
|
||||
-------------------
|
||||
|
||||
@@ -66,7 +66,7 @@ documentation_title = '{0} Documentation'.format(project)
|
||||
# The short X.Y version.
|
||||
version = '0.52'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.52.1'
|
||||
release = '0.52.8'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -636,7 +636,7 @@ This tells me that I can reach :code:`CSEC` by going through :code:`iron-gym`::
|
||||
from CSEC once you hit 50 hacking, you cannot actually pass their test
|
||||
until your hacking is high enough to install a backdoor on their server.
|
||||
|
||||
After you are connected to the :code:`CSEC` server, you can hack it. Note that this
|
||||
After you are connected to the :code:`CSEC` server, you can backdoor it. Note that this
|
||||
server requires one open port in order to gain root access. We can open the SSH port
|
||||
using the :code:`BruteSSH.exe` program we created earlier. In |Terminal|::
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ getActionCountRemaining() Netscript Function
|
||||
This function will return 'Infinity' for actions such as 'Training' and
|
||||
'Field Analysis'.
|
||||
This function will return 1 for BlackOps not yet completed regardless of
|
||||
wether the player has the required rank to attempt the mission or not.
|
||||
whether the player has the required rank to attempt the mission or not.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -12,4 +12,4 @@ getActionCurrentLevel() Netscript Function
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
bladeburner.getActionCountRemaining("Contracts", "Tracking"); // returns: 7
|
||||
bladeburner.getActionCurrentLevel("Contracts", "Tracking"); // returns: 9
|
||||
@@ -6,10 +6,10 @@ getActionEstimatedSuccessChance() Netscript Function
|
||||
:RAM cost: 4 GB
|
||||
:param string type: Type of action. See :ref:`bladeburner_action_types`
|
||||
:param string name: Name of action. Must be an exact match
|
||||
:returns: Estimated success chance in decimal
|
||||
:returns: Array of 2 number, lower and upper bound of the action chance.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
bladeburner.getActionEstimatedSuccessChance("Contracts", "Tracking"); // returns: .3
|
||||
bladeburner.getActionEstimatedSuccessChance("Contracts", "Tracking"); // returns: [.3, .6]
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
growPercent() Netscript Function
|
||||
=================================
|
||||
|
||||
.. js:function:: growPercent(server, threads, player)
|
||||
.. js:function:: growPercent(server, threads, player, cores)
|
||||
|
||||
:RAM cost: 0 GB
|
||||
:param server server: The server that receives the growth.
|
||||
:param number threads: The number of thread that would be used.
|
||||
:param player player: The player.
|
||||
:param number cores: The amount of cores on the host computer.
|
||||
:returns: The amount the server's money would be multiplied by with these
|
||||
parameters.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ hashGainRate() Netscript Function
|
||||
:param number ramUsed: ram used on the server.
|
||||
:param number maxRam: max ram of the server.
|
||||
:param number core: cores of the server.
|
||||
:returns: Money per second that a server with those stats would gain per second.
|
||||
:returns: Hashes per second that a server with those stats would gain.
|
||||
|
||||
If you are not in BitNode-5, then you must have Source-File 5-1 in order to
|
||||
use this function. In addition, if you are not in BitNode-9, then you must
|
||||
|
||||
@@ -13,30 +13,42 @@ getMemberInformation() Netscript Function
|
||||
name: Name of this member.
|
||||
task: Name of currently assigned task.
|
||||
earnedRespect: Total amount of respect earned by this member.
|
||||
|
||||
hack: Hacking stat
|
||||
str: Strength stat
|
||||
def: Defense stat
|
||||
dex: Dexterity stat
|
||||
agi: Agility stat
|
||||
cha: Charisma stat
|
||||
|
||||
hack_exp: Hacking experience
|
||||
str_exp: Strength experience
|
||||
def_exp: Defense experience
|
||||
dex_exp: Dexterity experience
|
||||
agi_exp: Agility experience
|
||||
cha_exp: Charisma experience
|
||||
|
||||
hack_mult: Hacking multiplier from equipment. Decimal form
|
||||
str_mult: Strength multiplier from equipment. Decimal form
|
||||
def_mult: Defense multiplier from equipment. Decimal form
|
||||
dex_mult: Dexterity multiplier from equipment. Decimal form
|
||||
agi_mult: Agility multiplier from equipment. Decimal form
|
||||
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
|
||||
|
||||
hack_asc_points: Hacking ascension points.
|
||||
str_asc_points: Strength ascension points.
|
||||
def_asc_points: Defense ascension points.
|
||||
dex_asc_points: Dexterity ascension points.
|
||||
agi_asc_points: Agility ascension points.
|
||||
cha_asc_points: Charisma ascension points.
|
||||
|
||||
upgrades: Array of names of all owned Non-Augmentation Equipment
|
||||
augmentations: Array of names of all owned Augmentations
|
||||
}
|
||||
|
||||
87
index.html
87
index.html
@@ -33,83 +33,83 @@
|
||||
<ul id="mainmenu" class="mainmenu noscrollbar">
|
||||
<!-- Hacking dropdown -->
|
||||
<li id="hacking-menu-header-li">
|
||||
<button id="hacking-menu-header" class="mainmenu-accordion-header"> Hacking </button>
|
||||
<button id="hacking-menu-header" class="mainmenu-accordion-header noselect"> Hacking </button>
|
||||
</li>
|
||||
<li id="terminal-tab" class="mainmenu-accordion-panel">
|
||||
<li id="terminal-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="terminal-menu-link"> Terminal </button>
|
||||
</li>
|
||||
<li id="create-script-tab" class="mainmenu-accordion-panel">
|
||||
<li id="create-script-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="create-script-menu-link"> Create Script </button>
|
||||
</li>
|
||||
<li id="active-scripts-tab" class="mainmenu-accordion-panel">
|
||||
<li id="active-scripts-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="active-scripts-menu-link"> Active Scripts </button>
|
||||
</li>
|
||||
<li id="create-program-tab" class="mainmenu-accordion-panel">
|
||||
<li id="create-program-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="create-program-menu-link"> Create Program </button>
|
||||
<span id="create-program-notification" class="notification-off"> </span>
|
||||
</li>
|
||||
|
||||
<!-- Character dropdown -->
|
||||
<li id="character-menu-header-li">
|
||||
<button id="character-menu-header" class="mainmenu-accordion-header"> Character </button>
|
||||
<button id="character-menu-header" class="mainmenu-accordion-header noselect"> Character </button>
|
||||
</li>
|
||||
<li id="stats-tab" class="mainmenu-accordion-panel">
|
||||
<li id="stats-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="stats-menu-link"> Stats </button>
|
||||
</li>
|
||||
<li id="factions-tab" class="mainmenu-accordion-panel">
|
||||
<li id="factions-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="factions-menu-link"> Factions </button>
|
||||
<span id="factions-notification" class="notification-off"> </span>
|
||||
</li>
|
||||
<li id="augmentations-tab" class="mainmenu-accordion-panel">
|
||||
<li id="augmentations-tab" class="mainmenu-accordion-panel noselect">
|
||||
<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 id="hacknet-nodes-tab" class="mainmenu-accordion-panel">
|
||||
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="hacknet-nodes-menu-link"> Hacknet </button>
|
||||
</li>
|
||||
<li id="sleeves-tab" class="mainmenu-accordion-panel">
|
||||
<li id="sleeves-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="sleeves-menu-link"> Sleeves </button>
|
||||
</li>
|
||||
|
||||
<!-- World dropdown -->
|
||||
<li id="world-menu-header-li">
|
||||
<button id="world-menu-header" class="mainmenu-accordion-header">World</button>
|
||||
<button id="world-menu-header" class="mainmenu-accordion-header noselect">World</button>
|
||||
</li>
|
||||
<li id="city-tab" class="mainmenu-accordion-panel">
|
||||
<li id="city-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="city-menu-link"> City </button>
|
||||
</li>
|
||||
<li id="travel-tab" class="mainmenu-accordion-panel">
|
||||
<li id="travel-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="travel-menu-link"> Travel </button>
|
||||
</li>
|
||||
<li id="job-tab" class="mainmenu-accordion-panel">
|
||||
<li id="job-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="job-menu-link"> Job </button>
|
||||
</li>
|
||||
<li id="stock-market-tab" class="mainmenu-accordion-panel">
|
||||
<li id="stock-market-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="stock-market-menu-link"> Stock Market </button>
|
||||
</li>
|
||||
<li id="bladeburner-tab" class="mainmenu-accordion-panel">
|
||||
<li id="bladeburner-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="bladeburner-menu-link"> Bladeburner </button>
|
||||
</li>
|
||||
<li id="corporation-tab" class="mainmenu-accordion-panel">
|
||||
<li id="corporation-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="corporation-menu-link"> Corp </button>
|
||||
</li>
|
||||
<li id="gang-tab" class="mainmenu-accordion-panel">
|
||||
<li id="gang-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="gang-menu-link"> Gang </button>
|
||||
</li>
|
||||
|
||||
<li id="help-menu-header-li">
|
||||
<button id="help-menu-header" class="mainmenu-accordion-header"> Help </button>
|
||||
<button id="help-menu-header" class="mainmenu-accordion-header noselect"> Help </button>
|
||||
</li>
|
||||
<li id="milestones-tab" class="mainmenu-accordion-panel">
|
||||
<li id="milestones-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="milestones-menu-link"> Milestones </button>
|
||||
</li>
|
||||
<li id="tutorial-tab" class="mainmenu-accordion-panel">
|
||||
<li id="tutorial-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="tutorial-menu-link"> Tutorial </button>
|
||||
</li>
|
||||
<li id="options-tab" class="mainmenu-accordion-panel">
|
||||
<li id="options-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="options-menu-link"> Options </button>
|
||||
</li>
|
||||
<li id="dev-tab" class="mainmenu-accordion-panel">
|
||||
<li id="dev-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="dev-menu-link"> Dev </button>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -123,11 +123,8 @@
|
||||
<input id="script-editor-filename" type="text" maxlength="100" tabindex="1"/>
|
||||
</div>
|
||||
|
||||
<div id="ace-editor"></div>
|
||||
<form id="codemirror-form-wrapper"><textarea id="codemirror-editor"></textarea></form>
|
||||
<div id="codemirror-vim-command-display-wrapper">
|
||||
Key Buffer: <span id="codemirror-vim-command-display"></span>
|
||||
</div>
|
||||
<div id="monaco-editor"></div>
|
||||
|
||||
|
||||
<div id="script-editor-buttons-wrapper"></div> <!-- Buttons below script editor -->
|
||||
</div> <!-- End wrapper -->
|
||||
@@ -173,6 +170,14 @@
|
||||
<fieldset id="script-editor-option-flex4-fieldset"></fieldset>
|
||||
|
||||
</div> <!-- End script editor options panel -->
|
||||
|
||||
|
||||
<!-- TODO(hydroflame): remove this once Monaco is implemented -->
|
||||
<div id="ace-editor" style="display: none"></div>
|
||||
<form id="codemirror-form-wrapper" style="display: none"><textarea id="codemirror-editor"></textarea></form>
|
||||
<div id="codemirror-vim-command-display-wrapper" style="display: none">
|
||||
Key Buffer: <span id="codemirror-vim-command-display"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Terminal page -->
|
||||
@@ -230,8 +235,10 @@
|
||||
<div id="augmentations-container" class="generic-menupage-container"></div>
|
||||
|
||||
<!-- Milestones content -->
|
||||
<div id="milestones-container" class="generic-menupage-container">
|
||||
</div>
|
||||
<div id="milestones-container" class="generic-menupage-container"></div>
|
||||
|
||||
<!-- Bladeburner -->
|
||||
<div id="bladeburner-container" class="generic-menupage-container"></div>
|
||||
|
||||
<!-- Tutorial content -->
|
||||
<div id="tutorial-container" class="generic-menupage-container">
|
||||
@@ -271,14 +278,8 @@
|
||||
<!-- React Component -->
|
||||
</div>
|
||||
|
||||
<!-- Log Box -->
|
||||
<div id="log-box-container">
|
||||
<div id="log-box-content">
|
||||
<button id="log-box-close" class="popup-box-button"> Close </button>
|
||||
<button id="log-box-kill-script" class="popup-box-button">Kill Script</button>
|
||||
<p id="log-box-text-header"> </p>
|
||||
<p id="log-box-text"> </p>
|
||||
</div>
|
||||
<div id="gang-container" class="generic-menupage-container">
|
||||
<!-- React Component -->
|
||||
</div>
|
||||
|
||||
<!-- Generic Yes/No Pop Up box -->
|
||||
@@ -362,7 +363,7 @@
|
||||
<div id="character-overview-text">
|
||||
<!-- ReactJS Component -->
|
||||
</div>
|
||||
<div class="character-quick-options">
|
||||
<div class="character-quick-options noselect">
|
||||
<button id="character-overview-save-button" class="character-overview-btn">Save Game</button>
|
||||
<button id="character-overview-options-button" class="character-overview-btn">Options</button>
|
||||
</div>
|
||||
@@ -580,10 +581,12 @@
|
||||
Copy Save data to Clipboard
|
||||
</button>
|
||||
<button id="debug-delete-scripts-link" class="a-link-button tooltip">
|
||||
Delete all active scripts
|
||||
Force kill all active scripts
|
||||
<span class="tooltiptextleft">
|
||||
Forcefully kill all active running scripts, in case there is a bug or some unexpected issue with the game. After
|
||||
using this, save the game and then reload the page.
|
||||
using this, save the game and then reload the page. This is different then normal kill in that normal kill
|
||||
will tell the script to shut down while force kill just removes the references to it (and it should crash on it's own).
|
||||
This will not remove the files on your computer. Just forcefully kill all running instance of all scripts.
|
||||
</span>
|
||||
</button>
|
||||
<button id="debug-soft-reset" class="a-link-button tooltip">
|
||||
|
||||
1674
package-lock.json
generated
1674
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.3",
|
||||
"@monaco-editor/react": "^4.2.2",
|
||||
"@types/js-beautify": "^1.13.2",
|
||||
"@types/numeral": "0.0.25",
|
||||
"@types/react": "^16.8.6",
|
||||
"@types/react-dom": "^16.8.2",
|
||||
@@ -33,6 +35,7 @@
|
||||
"loader-runner": "^2.3.0",
|
||||
"loader-utils": "^1.1.0",
|
||||
"memory-fs": "~0.4.1",
|
||||
"monaco-editor": "^0.27.0",
|
||||
"node-sass": "^6.0.1",
|
||||
"normalize.css": "^8.0.0",
|
||||
"numeral": "2.0.6",
|
||||
@@ -126,5 +129,5 @@
|
||||
"watch": "webpack --watch --mode production",
|
||||
"watch:dev": "webpack --watch --mode development"
|
||||
},
|
||||
"version": "0.52.1"
|
||||
"version": "0.52.8"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
// Class definition for a single Augmentation object
|
||||
import * as React from "react";
|
||||
import { IMap } from "../types";
|
||||
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { Faction } from "../Faction/Faction";
|
||||
import { Factions } from "../Faction/Factions";
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
import { Money } from "../ui/React/Money";
|
||||
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
interface IConstructorParams {
|
||||
info: string | JSX.Element;
|
||||
stats?: JSX.Element;
|
||||
isSpecial?: boolean;
|
||||
moneyCost: number;
|
||||
name: string;
|
||||
@@ -45,6 +49,129 @@ interface IConstructorParams {
|
||||
bladeburner_stamina_gain_mult?: number;
|
||||
bladeburner_analysis_mult?: number;
|
||||
bladeburner_success_chance_mult?: number;
|
||||
|
||||
startingMoney?: number;
|
||||
programs?: string[];
|
||||
}
|
||||
|
||||
function generateStatsDescription(mults: IMap<number>, programs?: string[], startingMoney?: number): JSX.Element {
|
||||
const f = (x: number, decimals = 0): string => {
|
||||
// look, I don't know how to make a "smart decimals"
|
||||
// todo, make it smarter
|
||||
if(x === 1.0777-1) return "7.77%";
|
||||
if(x === 1.777-1) return "77.7%";
|
||||
return numeralWrapper.formatPercentage(x, decimals);
|
||||
};
|
||||
let desc = <>Effects:</>;
|
||||
|
||||
if(mults.hacking_mult &&
|
||||
mults.hacking_mult == mults.strength_mult &&
|
||||
mults.hacking_mult == mults.defense_mult &&
|
||||
mults.hacking_mult == mults.dexterity_mult &&
|
||||
mults.hacking_mult == mults.agility_mult &&
|
||||
mults.hacking_mult == mults.charisma_mult){
|
||||
desc = <>{desc}<br />+{f(mults.hacking_mult-1)} all skills</>
|
||||
} else {
|
||||
if(mults.hacking_mult)
|
||||
desc = <>{desc}<br />+{f(mults.hacking_mult-1)} hacking skill</>
|
||||
|
||||
if(mults.strength_mult &&
|
||||
mults.strength_mult == mults.defense_mult &&
|
||||
mults.strength_mult == mults.dexterity_mult &&
|
||||
mults.strength_mult == mults.agility_mult) {
|
||||
desc = <>{desc}<br />+{f(mults.strength_mult-1)} combat skills</>
|
||||
} else {
|
||||
if(mults.strength_mult)
|
||||
desc = <>{desc}<br />+{f(mults.strength_mult-1)} strength skill</>
|
||||
if(mults.defense_mult)
|
||||
desc = <>{desc}<br />+{f(mults.defense_mult-1)} defense skill</>
|
||||
if(mults.dexterity_mult)
|
||||
desc = <>{desc}<br />+{f(mults.dexterity_mult-1)} dexterity skill</>
|
||||
if(mults.agility_mult)
|
||||
desc = <>{desc}<br />+{f(mults.agility_mult-1)} agility skill</>
|
||||
}
|
||||
if(mults.charisma_mult)
|
||||
desc = <>{desc}<br />+{f(mults.charisma_mult-1)} Charisma skill</>
|
||||
}
|
||||
|
||||
if(mults.hacking_exp_mult &&
|
||||
mults.hacking_exp_mult === mults.strength_exp_mult &&
|
||||
mults.hacking_exp_mult === mults.defense_exp_mult &&
|
||||
mults.hacking_exp_mult === mults.dexterity_exp_mult &&
|
||||
mults.hacking_exp_mult === mults.agility_exp_mult &&
|
||||
mults.hacking_exp_mult === mults.charisma_exp_mult) {
|
||||
desc = <>{desc}<br />+{f(mults.hacking_exp_mult-1)} exp for all skills</>
|
||||
} else {
|
||||
if(mults.hacking_exp_mult)
|
||||
desc = <>{desc}<br />+{f(mults.hacking_exp_mult-1)} hacking exp</>
|
||||
|
||||
if(mults.strength_exp_mult &&
|
||||
mults.strength_exp_mult === mults.defense_exp_mult &&
|
||||
mults.strength_exp_mult === mults.dexterity_exp_mult &&
|
||||
mults.strength_exp_mult === mults.agility_exp_mult) {
|
||||
desc = <>{desc}<br />+{f(mults.strength_exp_mult-1)} combat exp</>
|
||||
} else {
|
||||
if(mults.strength_exp_mult)
|
||||
desc = <>{desc}<br />+{f(mults.strength_exp_mult-1)} strength exp</>
|
||||
if(mults.defense_exp_mult)
|
||||
desc = <>{desc}<br />+{f(mults.defense_exp_mult-1)} defense exp</>
|
||||
if(mults.dexterity_exp_mult)
|
||||
desc = <>{desc}<br />+{f(mults.dexterity_exp_mult-1)} dexterity exp</>
|
||||
if(mults.agility_exp_mult)
|
||||
desc = <>{desc}<br />+{f(mults.agility_exp_mult-1)} agility exp</>
|
||||
}
|
||||
if(mults.charisma_exp_mult)
|
||||
desc = <>{desc}<br />+{f(mults.charisma_exp_mult-1)} charisma exp</>
|
||||
}
|
||||
|
||||
if(mults.hacking_speed_mult)
|
||||
desc = <>{desc}<br />+{f(mults.hacking_speed_mult-1)} faster hacking</>
|
||||
if(mults.hacking_chance_mult)
|
||||
desc = <>{desc}<br />+{f(mults.hacking_chance_mult-1)} hack() success chance</>
|
||||
if(mults.hacking_money_mult)
|
||||
desc = <>{desc}<br />+{f(mults.hacking_money_mult-1)} hack() power</>
|
||||
if(mults.hacking_grow_mult)
|
||||
desc = <>{desc}<br />+{f(mults.hacking_grow_mult-1)} grow() power</>
|
||||
|
||||
if(mults.faction_rep_mult &&
|
||||
mults.faction_rep_mult === mults.company_rep_mult) {
|
||||
desc = <>{desc}<br />+{f(mults.faction_rep_mult-1)} reputation from factions and companies</>
|
||||
} else {
|
||||
if(mults.faction_rep_mult)
|
||||
desc = <>{desc}<br />+{f(mults.faction_rep_mult-1)} reputation from factions</>
|
||||
if(mults.company_rep_mult)
|
||||
desc = <>{desc}<br />+{f(mults.company_rep_mult-1)} reputation from companies</>
|
||||
}
|
||||
|
||||
if(mults.crime_money_mult)
|
||||
desc = <>{desc}<br />+{f(mults.crime_money_mult-1)} crime money</>
|
||||
if(mults.crime_success_mult)
|
||||
desc = <>{desc}<br />+{f(mults.crime_success_mult-1)} crime success rate</>
|
||||
if(mults.work_money_mult)
|
||||
desc = <>{desc}<br />+{f(mults.work_money_mult-1)} work money</>
|
||||
|
||||
if(mults.hacknet_node_money_mult)
|
||||
desc = <>{desc}<br />+{f(mults.hacknet_node_money_mult-1)} hacknet production</>
|
||||
if(mults.hacknet_node_purchase_cost_mult)
|
||||
desc = <>{desc}<br />-{f(-(mults.hacknet_node_purchase_cost_mult-1))} hacknet nodes cost</>
|
||||
if(mults.hacknet_node_level_cost_mult)
|
||||
desc = <>{desc}<br />-{f(-(mults.hacknet_node_level_cost_mult-1))} hacknet nodes upgrade cost</>
|
||||
|
||||
if(mults.bladeburner_max_stamina_mult)
|
||||
desc = <>{desc}<br />+{f(mults.bladeburner_max_stamina_mult-1)} Bladeburner Max Stamina</>
|
||||
if(mults.bladeburner_stamina_gain_mult)
|
||||
desc = <>{desc}<br />+{f(mults.bladeburner_stamina_gain_mult-1)} Bladeburner Stamina gain</>
|
||||
if(mults.bladeburner_analysis_mult)
|
||||
desc = <>{desc}<br />+{f(mults.bladeburner_analysis_mult-1)} Bladeburner Field Analysis effectiveness</>
|
||||
if(mults.bladeburner_success_chance_mult)
|
||||
desc = <>{desc}<br />+{f(mults.bladeburner_success_chance_mult-1)} Bladeburner Contracts and Operations success chance</>
|
||||
|
||||
if(startingMoney)
|
||||
desc = <>{desc}<br />Start with {Money(startingMoney)} after installing Augmentations.</>
|
||||
|
||||
if(programs)
|
||||
desc = <>{desc}<br />Start with {programs.join(' and ')} after installing Augmentations.</>
|
||||
return desc;
|
||||
}
|
||||
|
||||
export class Augmentation {
|
||||
@@ -58,6 +185,9 @@ export class Augmentation {
|
||||
// Description of what this Aug is and what it does
|
||||
info: string | JSX.Element;
|
||||
|
||||
// Description of the stats, often autogenerated, sometimes manually written.
|
||||
stats: JSX.Element;
|
||||
|
||||
// Any Augmentation not immediately available in BitNode-1 is special (e.g. Bladeburner augs)
|
||||
isSpecial = false;
|
||||
|
||||
@@ -126,6 +256,11 @@ export class Augmentation {
|
||||
if (params.bladeburner_stamina_gain_mult) { this.mults.bladeburner_stamina_gain_mult = params.bladeburner_stamina_gain_mult; }
|
||||
if (params.bladeburner_analysis_mult) { this.mults.bladeburner_analysis_mult = params.bladeburner_analysis_mult; }
|
||||
if (params.bladeburner_success_chance_mult) { this.mults.bladeburner_success_chance_mult = params.bladeburner_success_chance_mult; }
|
||||
|
||||
if(params.stats)
|
||||
this.stats = params.stats;
|
||||
else
|
||||
this.stats = generateStatsDescription(this.mults, params.programs, params.startingMoney);
|
||||
}
|
||||
|
||||
// Adds this Augmentation to the specified Factions
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,6 +47,7 @@ export const AugmentationNames: IMap<string> = {
|
||||
PCDNI: "PC Direct-Neural Interface",
|
||||
PCDNIOptimizer: "PC Direct-Neural Interface Optimization Submodule",
|
||||
PCDNINeuralNetwork: "PC Direct-Neural Interface NeuroNet Injector",
|
||||
PCMatrix: "PCMatrix",
|
||||
ADRPheromone1: "ADR-V1 Pheromone Gene",
|
||||
ADRPheromone2: "ADR-V2 Pheromone Gene",
|
||||
ShadowsSimulacrum: "The Shadow's Simulacrum",
|
||||
|
||||
@@ -7,7 +7,6 @@ import * as React from "react";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
import { IPlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
|
||||
|
||||
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
|
||||
|
||||
|
||||
@@ -231,7 +231,11 @@ BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.",
|
||||
"This Source-File also increases the player's company salary and reputation gain multipliers by:<br><br>" +
|
||||
"Level 1: 32%<br>" +
|
||||
"Level 2: 48%<br>" +
|
||||
"Level 3: 56%");
|
||||
"Level 3: 56%<br><br>" +
|
||||
"It also reduces the price increase for every aug bought by:<br><br>"+
|
||||
"Level 1: 4%<br>"+
|
||||
"Level 2: 6%<br>"+
|
||||
"Level 3: 7%");
|
||||
BitNodes["BitNode12"] = new BitNode(12, "The Recursion", "Repeat.",
|
||||
"To iterate is human, to recurse divine.<br><br>" +
|
||||
"Every time this BitNode is destroyed, it becomes slightly harder. Destroying this BitNode will give you Source-File 12, or " +
|
||||
|
||||
3265
src/Bladeburner.jsx
3265
src/Bladeburner.jsx
File diff suppressed because it is too large
Load Diff
@@ -194,6 +194,23 @@ export class Action implements IAction {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getEstSuccessChance(inst: IBladeburner): number[] {
|
||||
function clamp(x: number): number {
|
||||
return Math.max(0, Math.min(x, 1));
|
||||
}
|
||||
const est = this.getSuccessChance(inst, {est: true});
|
||||
const real = this.getSuccessChance(inst);
|
||||
const diff = Math.abs(real-est);
|
||||
let low = real-diff;
|
||||
let high = real+diff;
|
||||
const city = inst.getCurrentCity();
|
||||
const r = city.pop / city.popEst;
|
||||
|
||||
if(r < 1) low *= r;
|
||||
else high *= r;
|
||||
return [clamp(low), clamp(high)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inst - Bladeburner Object
|
||||
* @params - options:
|
||||
|
||||
28
src/Bladeburner/ActionIdentifier.ts
Normal file
28
src/Bladeburner/ActionIdentifier.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { IActionIdentifier } from "./IActionIdentifier";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
interface IParams {
|
||||
name?: string;
|
||||
type?: number;
|
||||
}
|
||||
|
||||
export class ActionIdentifier implements IActionIdentifier {
|
||||
name = "";
|
||||
type = -1;
|
||||
|
||||
constructor(params: IParams = {}) {
|
||||
if (params.name) this.name = params.name;
|
||||
if (params.type) this.type = params.type;
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("ActionIdentifier", this);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static fromJSON(value: any): ActionIdentifier {
|
||||
return Generic_fromJSON(ActionIdentifier, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
Reviver.constructors.ActionIdentifier = ActionIdentifier;
|
||||
2006
src/Bladeburner/Bladeburner.ts
Normal file
2006
src/Bladeburner/Bladeburner.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,14 +4,14 @@ import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
import { addOffset } from "../../utils/helpers/addOffset";
|
||||
|
||||
export class ChangePopulationByCountParams {
|
||||
estChange = 0;
|
||||
estOffset = 0;
|
||||
interface IChangePopulationByCountParams {
|
||||
estChange: number;
|
||||
estOffset: number;
|
||||
}
|
||||
|
||||
export class ChangePopulationByPercentageParams {
|
||||
nonZero = false;
|
||||
changeEstEqually = false;
|
||||
interface IChangePopulationByPercentageParams {
|
||||
nonZero: boolean;
|
||||
changeEstEqually: boolean;
|
||||
}
|
||||
|
||||
export class City {
|
||||
@@ -113,7 +113,7 @@ export class City {
|
||||
* estChange(int): How much the estimate should change by
|
||||
* estOffset(int): Add offset to estimate (offset by percentage)
|
||||
*/
|
||||
changePopulationByCount(n: number, params: ChangePopulationByCountParams=new ChangePopulationByCountParams()): void {
|
||||
changePopulationByCount(n: number, params: IChangePopulationByCountParams = {estChange: 0, estOffset: 0}): void {
|
||||
if (isNaN(n)) {throw new Error("NaN passed into City.changePopulationByCount()");}
|
||||
this.pop += n;
|
||||
if (params.estChange && !isNaN(params.estChange)) {this.popEst += params.estChange;}
|
||||
@@ -129,7 +129,7 @@ export class City {
|
||||
* changeEstEqually(bool) - Change the population estimate by an equal amount
|
||||
* nonZero (bool) - Set to true to ensure that population always changes by at least 1
|
||||
*/
|
||||
changePopulationByPercentage(p: number, params: ChangePopulationByPercentageParams=new ChangePopulationByPercentageParams()): number {
|
||||
changePopulationByPercentage(p: number, params: IChangePopulationByPercentageParams={nonZero: false, changeEstEqually: false}): number {
|
||||
if (isNaN(p)) {throw new Error("NaN passed into City.changePopulationByPercentage()");}
|
||||
if (p === 0) {return 0;}
|
||||
let change = Math.round(this.pop * (p/100));
|
||||
|
||||
@@ -64,6 +64,7 @@ export interface IAction {
|
||||
getActionTypeSkillSuccessBonus(inst: IBladeburner): number;
|
||||
getChaosCompetencePenalty(inst: IBladeburner, params: ISuccessChanceParams): number;
|
||||
getChaosDifficultyBonus(inst: IBladeburner): number;
|
||||
getEstSuccessChance(inst: IBladeburner): number[];
|
||||
getSuccessChance(inst: IBladeburner, params: ISuccessChanceParams): number;
|
||||
getSuccessesNeededForNextLevel(baseSuccessesPerLevel: number): number;
|
||||
setMaxLevel(baseSuccessesPerLevel: number): void;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface IActionIdentifier {
|
||||
name: string;
|
||||
type: string;
|
||||
type: number;
|
||||
}
|
||||
@@ -1,20 +1,33 @@
|
||||
import { IActionIdentifier } from "./IActionIdentifier";
|
||||
import { City } from "./City";
|
||||
import { Skill } from "./Skill";
|
||||
import { IAction } from "./IAction";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
|
||||
export interface IBladeburner {
|
||||
numHosp: number;
|
||||
moneyLost: number;
|
||||
rank: number;
|
||||
maxRank: number;
|
||||
|
||||
skillPoints: number;
|
||||
totalSkillPoints: number;
|
||||
|
||||
teamSize: number;
|
||||
teamLost: number;
|
||||
hpLost: number;
|
||||
|
||||
storedCycles: number;
|
||||
|
||||
randomEventCounter: number;
|
||||
|
||||
actionTimeToComplete: number;
|
||||
actionTimeCurrent: number;
|
||||
actionTimeOverflow: number;
|
||||
|
||||
action: IActionIdentifier;
|
||||
|
||||
cities: any;
|
||||
city: string;
|
||||
skills: any;
|
||||
@@ -27,13 +40,66 @@ export interface IBladeburner {
|
||||
blackops: any;
|
||||
logging: any;
|
||||
automateEnabled: boolean;
|
||||
automateActionHigh: number;
|
||||
automateActionHigh: IActionIdentifier;
|
||||
automateThreshHigh: number;
|
||||
automateActionLow: number;
|
||||
automateActionLow: IActionIdentifier;
|
||||
automateThreshLow: number;
|
||||
consoleHistory: string[];
|
||||
consoleLogs: string[];
|
||||
|
||||
getCurrentCity(): City;
|
||||
calculateStaminaPenalty(): number;
|
||||
startAction(player: IPlayer, action: IActionIdentifier): void;
|
||||
upgradeSkill(skill: Skill): void;
|
||||
executeConsoleCommands(player: IPlayer, command: string): void;
|
||||
postToConsole(input: string, saveToLogs?: boolean): void;
|
||||
log(input: string): void;
|
||||
resetAction(): void;
|
||||
clearConsole(): void;
|
||||
|
||||
prestige(): void;
|
||||
storeCycles(numCycles?: number): void;
|
||||
getTypeAndNameFromActionId(actionId: IActionIdentifier): {type: string; name: string};
|
||||
getContractNamesNetscriptFn(): string[];
|
||||
getOperationNamesNetscriptFn(): string[];
|
||||
getBlackOpNamesNetscriptFn(): string[];
|
||||
getGeneralActionNamesNetscriptFn(): string[];
|
||||
getSkillNamesNetscriptFn(): string[];
|
||||
startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean;
|
||||
getActionTimeNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number;
|
||||
getActionEstimatedSuccessChanceNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number[];
|
||||
getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
|
||||
getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number;
|
||||
getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number;
|
||||
upgradeSkillNetscriptFn(skillName: string, workerScript: WorkerScript): boolean;
|
||||
getTeamSizeNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
|
||||
setTeamSizeNetscriptFn(type: string, name: string, size: number, workerScript: WorkerScript): number;
|
||||
joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean;
|
||||
getActionIdFromTypeAndName(type: string, name: string): IActionIdentifier | null;
|
||||
executeStartConsoleCommand(player: IPlayer, args: string[]): void;
|
||||
executeSkillConsoleCommand(args: string[]): void;
|
||||
executeLogConsoleCommand(args: string[]): void;
|
||||
executeHelpConsoleCommand(args: string[]): void;
|
||||
executeAutomateConsoleCommand(args: string[]): void;
|
||||
parseCommandArguments(command: string): string[];
|
||||
executeConsoleCommand(player: IPlayer, command: string): void;
|
||||
triggerMigration(sourceCityName: string): void;
|
||||
triggerPotentialMigration(sourceCityName: string, chance: number): void;
|
||||
randomEvent(): void;
|
||||
gainActionStats(player: IPlayer, action: IAction, success: boolean): void;
|
||||
getDiplomacyEffectiveness(player: IPlayer): number;
|
||||
getRecruitmentSuccessChance(player: IPlayer): number;
|
||||
getRecruitmentTime(player: IPlayer): number;
|
||||
resetSkillMultipliers(): void;
|
||||
updateSkillMultipliers(): void;
|
||||
completeOperation(success: boolean): void;
|
||||
getActionObject(actionId: IActionIdentifier): IAction | null;
|
||||
completeContract(success: boolean): void;
|
||||
completeAction(player: IPlayer): void;
|
||||
changeRank(player: IPlayer, change: number): void;
|
||||
processAction(player: IPlayer, seconds: number): void;
|
||||
calculateStaminaGainPerSecond(player: IPlayer): number;
|
||||
calculateMaxStamina(player: IPlayer): void;
|
||||
create(): void;
|
||||
process(player: IPlayer): void;
|
||||
}
|
||||
@@ -103,5 +103,28 @@ export class Skill {
|
||||
calculateCost(currentLevel: number): number {
|
||||
return Math.floor((this.baseCost + (currentLevel * this.costInc)) * BitNodeMultipliers.BladeburnerSkillCost);
|
||||
}
|
||||
|
||||
getMultiplier(name: string): number {
|
||||
if(name === "successChanceAll") return this.successChanceAll;
|
||||
if(name === "successChanceStealth") return this.successChanceStealth;
|
||||
if(name === "successChanceKill") return this.successChanceKill;
|
||||
if(name === "successChanceContract") return this.successChanceContract;
|
||||
if(name === "successChanceOperation") return this.successChanceOperation;
|
||||
if(name === "successChanceEstimate") return this.successChanceEstimate;
|
||||
|
||||
if(name === "actionTime") return this.actionTime;
|
||||
|
||||
if(name === "effHack") return this.effHack;
|
||||
if(name === "effStr") return this.effStr;
|
||||
if(name === "effDef") return this.effDef;
|
||||
if(name === "effDex") return this.effDex;
|
||||
if(name === "effAgi") return this.effAgi;
|
||||
if(name === "effCha") return this.effCha;
|
||||
|
||||
if(name === "stamina") return this.stamina;
|
||||
if(name === "money") return this.money;
|
||||
if(name === "expGain") return this.expGain;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
// Action Identifier enum
|
||||
export const ActionTypes = Object.freeze({
|
||||
export const ActionTypes: {
|
||||
[key: string]: number;
|
||||
"Idle": number;
|
||||
"Contract": number;
|
||||
"Operation": number;
|
||||
"BlackOp": number;
|
||||
"BlackOperation": number;
|
||||
"Training": number;
|
||||
"Recruitment": number;
|
||||
"FieldAnalysis": number;
|
||||
"Field Analysis": number;
|
||||
"Diplomacy": number;
|
||||
"Hyperbolic Regeneration Chamber": number;
|
||||
} = {
|
||||
"Idle": 1,
|
||||
"Contract": 2,
|
||||
"Operation": 3,
|
||||
@@ -11,4 +24,4 @@ export const ActionTypes = Object.freeze({
|
||||
"Field Analysis": 7,
|
||||
"Diplomacy": 8,
|
||||
"Hyperbolic Regeneration Chamber": 9,
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
export const ConsoleHelpText: {
|
||||
[key: string]: string[];
|
||||
helpList: string[];
|
||||
automate: string[];
|
||||
clear: string[];
|
||||
|
||||
14
src/Bladeburner/data/Icons.tsx
Normal file
14
src/Bladeburner/data/Icons.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as React from "react";
|
||||
|
||||
export const stealthIcon = <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 166 132" style={{fill:'#adff2f'}}>
|
||||
<g>
|
||||
<path d="M132.658-0.18l-24.321,24.321c-7.915-2.71-16.342-4.392-25.087-4.392c-45.84,0-83,46-83,46 s14.1,17.44,35.635,30.844L12.32,120.158l12.021,12.021L144.68,11.841L132.658-0.18z M52.033,80.445 c-2.104-4.458-3.283-9.438-3.283-14.695c0-19.054,15.446-34.5,34.5-34.5c5.258,0,10.237,1.179,14.695,3.284L52.033,80.445z" />
|
||||
<path d="M134.865,37.656l-18.482,18.482c0.884,3.052,1.367,6.275,1.367,9.612c0,19.055-15.446,34.5-34.5,34.5 c-3.337,0-6.56-0.483-9.611-1.367l-10.124,10.124c6.326,1.725,12.934,2.743,19.735,2.743c45.84,0,83-46,83-46 S153.987,50.575,134.865,37.656z" />
|
||||
</g>
|
||||
</svg>
|
||||
export const killIcon = <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="-22 0 511 511.99561" style={{fill:'#adff2f'}}>
|
||||
<path d="m.496094 466.242188 39.902344-39.902344 45.753906 45.753906-39.898438 39.902344zm0 0" />
|
||||
<path d="m468.421875 89.832031-1.675781-89.832031-300.265625 300.265625 45.753906 45.753906zm0 0" />
|
||||
<path d="m95.210938 316.785156 16.84375 16.847656h.003906l83.65625 83.65625 22.753906-22.753906-100.503906-100.503906zm0 0" />
|
||||
<path d="m101.445312 365.300781-39.902343 39.902344 45.753906 45.753906 39.902344-39.902343-39.90625-39.902344zm0 0" />
|
||||
</svg>
|
||||
49
src/Bladeburner/ui/AllPages.tsx
Normal file
49
src/Bladeburner/ui/AllPages.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { GeneralActionPage } from "./GeneralActionPage";
|
||||
import { ContractPage } from "./ContractPage";
|
||||
import { OperationPage } from "./OperationPage";
|
||||
import { BlackOpPage } from "./BlackOpPage";
|
||||
import { SkillPage } from "./SkillPage";
|
||||
import { stealthIcon, killIcon } from "../data/Icons";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function AllPages(props: IProps): React.ReactElement {
|
||||
const [page, setPage] = useState('General');
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function Header(props: {name: string}): React.ReactElement {
|
||||
return (<a
|
||||
onClick={()=>setPage(props.name)}
|
||||
className={page !== props.name ?
|
||||
"bladeburner-nav-button" :
|
||||
"bladeburner-nav-button-inactive"}>
|
||||
{props.name}
|
||||
</a>);
|
||||
}
|
||||
return (<>
|
||||
<Header name={'General'} />
|
||||
<Header name={'Contracts'} />
|
||||
<Header name={'Operations'} />
|
||||
<Header name={'BlackOps'} />
|
||||
<Header name={'Skills'} />
|
||||
<div style={{display:"block", margin:"4px", padding:"4px"}}>
|
||||
{page === 'General' && <GeneralActionPage bladeburner={props.bladeburner} player={props.player} />}
|
||||
{page === 'Contracts' && <ContractPage bladeburner={props.bladeburner} player={props.player} />}
|
||||
{page === 'Operations' && <OperationPage bladeburner={props.bladeburner} player={props.player} />}
|
||||
{page === 'BlackOps' && <BlackOpPage bladeburner={props.bladeburner} player={props.player} />}
|
||||
{page === 'Skills' && <SkillPage bladeburner={props.bladeburner} />}
|
||||
</div>
|
||||
<span className="text">{stealthIcon} = This action requires stealth, {killIcon} = This action involves retirement</span>
|
||||
</>);
|
||||
}
|
||||
88
src/Bladeburner/ui/BlackOpElem.tsx
Normal file
88
src/Bladeburner/ui/BlackOpElem.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
formatNumber,
|
||||
convertTimeMsToTimeElapsedString,
|
||||
} from "../../../utils/StringHelperFunctions";
|
||||
import { ActionTypes } from "../data/ActionTypes";
|
||||
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
||||
import { stealthIcon, killIcon } from "../data/Icons";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { TeamSizePopup } from "./TeamSizePopup";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { SuccessChance } from "./SuccessChance";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
action: any;
|
||||
}
|
||||
|
||||
export function BlackOpElem(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const isCompleted = (props.bladeburner.blackops[props.action.name] != null);
|
||||
if(isCompleted) {
|
||||
return (
|
||||
<h2 style={{display: 'block'}}>{props.action.name} (COMPLETED)</h2>);
|
||||
}
|
||||
|
||||
const isActive = props.bladeburner.action.type === ActionTypes["BlackOperation"] && props.action.name === props.bladeburner.action.name;
|
||||
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
|
||||
const actionTime = props.action.getActionTime(props.bladeburner);
|
||||
const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank;
|
||||
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete);
|
||||
|
||||
function onStart(): void {
|
||||
props.bladeburner.action.type = ActionTypes.BlackOperation;
|
||||
props.bladeburner.action.name = props.action.name;
|
||||
props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
function onTeam(): void {
|
||||
const popupId = "bladeburner-operation-set-team-size-popup";
|
||||
createPopup(popupId, TeamSizePopup, {
|
||||
bladeburner: props.bladeburner,
|
||||
action: props.action,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
return (<>
|
||||
<h2 style={{display: 'inline-block'}}>
|
||||
{isActive ?
|
||||
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
|
||||
<>{props.action.name}</>
|
||||
}
|
||||
</h2>
|
||||
{isActive ?
|
||||
<p style={{display: 'block'}}>{createProgressBarText({progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}</p> :
|
||||
<>
|
||||
<a
|
||||
className={hasReqdRank ? "a-link-button" : "a-link-button-inactive"}
|
||||
style={{margin:"3px", padding:"3px"}}
|
||||
onClick={onStart}
|
||||
>Start</a>
|
||||
<a
|
||||
onClick={onTeam}
|
||||
style={{margin:"3px", padding:"3px"}}
|
||||
className="a-link-button">
|
||||
Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)})
|
||||
</a>
|
||||
</>}
|
||||
<br />
|
||||
<br />
|
||||
<p style={{display:"inline-block"}} dangerouslySetInnerHTML={{__html: props.action.desc}} />
|
||||
<br />
|
||||
<br />
|
||||
<p style={{display:"block", color:hasReqdRank ? "white" : "red"}}>
|
||||
Required Rank: {formatNumber(props.action.reqdRank, 0)}
|
||||
</p>
|
||||
<br />
|
||||
<pre style={{display:"inline-block"}}>
|
||||
Estimated Success Chance: <SuccessChance chance={estimatedSuccessChance} /> {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}
|
||||
<br />
|
||||
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}
|
||||
</pre>
|
||||
</>);
|
||||
}
|
||||
36
src/Bladeburner/ui/BlackOpList.tsx
Normal file
36
src/Bladeburner/ui/BlackOpList.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import { BlackOperations } from "../BlackOperations";
|
||||
import { BlackOperation } from "../BlackOperation";
|
||||
import { BlackOpElem } from "./BlackOpElem";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function BlackOpList(props: IProps): React.ReactElement {
|
||||
let blackops: BlackOperation[] = [];
|
||||
for (const blackopName in BlackOperations) {
|
||||
if (BlackOperations.hasOwnProperty(blackopName)) {
|
||||
blackops.push(BlackOperations[blackopName]);
|
||||
}
|
||||
}
|
||||
blackops.sort(function(a, b) {
|
||||
return (a.reqdRank - b.reqdRank);
|
||||
});
|
||||
|
||||
blackops = blackops.filter((blackop: BlackOperation, i: number) => !(props.bladeburner.blackops[blackops[i].name] == null &&
|
||||
i !== 0 &&
|
||||
props.bladeburner.blackops[blackops[i-1].name] == null));
|
||||
|
||||
blackops = blackops.reverse();
|
||||
|
||||
return (<>
|
||||
{blackops.map((blackop: BlackOperation) => <li key={blackop.name} className="bladeburner-action">
|
||||
<BlackOpElem bladeburner={props.bladeburner} action={blackop} player={props.player} />
|
||||
</li>,
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
28
src/Bladeburner/ui/BlackOpPage.tsx
Normal file
28
src/Bladeburner/ui/BlackOpPage.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as React from "react";
|
||||
import { BlackOpList } from "./BlackOpList";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function BlackOpPage(props: IProps): React.ReactElement {
|
||||
return (<>
|
||||
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
|
||||
Black Operations (Black Ops) are special, one-time covert operations.
|
||||
Each Black Op must be unlocked successively by completing
|
||||
the one before it.
|
||||
<br />
|
||||
<br />
|
||||
<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete
|
||||
all of the Black Ops.</b>
|
||||
<br />
|
||||
<br />
|
||||
Like normal operations, you may use a team for Black Ops. Failing
|
||||
a black op will incur heavy HP and rank losses.
|
||||
</p>
|
||||
<BlackOpList bladeburner={props.bladeburner} player={props.player} />
|
||||
</>);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { BlackOperations } from "../BlackOperations";
|
||||
/*
|
||||
if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) {
|
||||
throw new Error("Bladeburner.createBlackOpsContent called with either " +
|
||||
"DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");
|
||||
}
|
||||
|
||||
DomElems.actionsAndSkillsDesc.innerHTML =
|
||||
"Black Operations (Black Ops) are special, one-time covert operations. " +
|
||||
"Each Black Op must be unlocked successively by completing " +
|
||||
"the one before it.<br><br>" +
|
||||
"<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete " +
|
||||
"all of the Black Ops.</b><br><br>" +
|
||||
"Like normal operations, you may use a team for Black Ops. Failing " +
|
||||
"a black op will incur heavy HP and rank losses.";
|
||||
|
||||
// Put Black Operations in sequence of required rank
|
||||
var blackops = [];
|
||||
for (var blackopName in BlackOperations) {
|
||||
if (BlackOperations.hasOwnProperty(blackopName)) {
|
||||
blackops.push(BlackOperations[blackopName]);
|
||||
}
|
||||
}
|
||||
blackops.sort(function(a, b) {
|
||||
return (a.reqdRank - b.reqdRank);
|
||||
});
|
||||
|
||||
for (var i = blackops.length-1; i >= 0 ; --i) {
|
||||
if (this.blackops[[blackops[i].name]] == null && i !== 0 && this.blackops[[blackops[i-1].name]] == null) {continue;} // If this one nor the next are completed then this isn't unlocked yet.
|
||||
DomElems.blackops[blackops[i].name] = createElement("div", {
|
||||
class:"bladeburner-action", name:blackops[i].name
|
||||
});
|
||||
DomElems.actionsAndSkillsList.appendChild(DomElems.blackops[blackops[i].name]);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
export function BlackOperationsPage(): React.ReactElement {
|
||||
// Put Black Operations in sequence of required rank
|
||||
const blackops = [];
|
||||
for (const name in BlackOperations) {
|
||||
if (BlackOperations.hasOwnProperty(name)) {
|
||||
blackops.push(BlackOperations[name]);
|
||||
}
|
||||
}
|
||||
blackops.sort(function(a, b) {
|
||||
return (a.reqdRank - b.reqdRank);
|
||||
});
|
||||
|
||||
return (<div>
|
||||
<p>
|
||||
Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked successively by completing the one before it.<br /><br />
|
||||
<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete all of the Black Ops.</b><br /><br />
|
||||
Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank losses.</p>
|
||||
{blackops.map(() => <div className="bladeburner-action">
|
||||
</div>,
|
||||
)}
|
||||
</div>)
|
||||
}
|
||||
115
src/Bladeburner/ui/Console.tsx
Normal file
115
src/Bladeburner/ui/Console.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface ILineProps {
|
||||
content: any;
|
||||
}
|
||||
|
||||
function Line(props: ILineProps): React.ReactElement {
|
||||
return (<tr>
|
||||
<td className="bladeburner-console-line" style={{color: 'var(--my-font-color)', whiteSpace: 'pre-wrap'}}>{props.content}</td>
|
||||
</tr>)
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function Console(props: IProps): React.ReactElement {
|
||||
const lastRef = useRef<HTMLDivElement>(null);
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length);
|
||||
|
||||
// TODO: Figure out how to actually make the scrolling work correctly.
|
||||
function scrollToBottom(): void {
|
||||
if(!lastRef.current) return;
|
||||
lastRef.current.scrollTop = lastRef.current.scrollHeight;
|
||||
}
|
||||
|
||||
function rerender(): void {
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(rerender, 1000);
|
||||
const id2 = setInterval(scrollToBottom, 100);
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
clearInterval(id2);
|
||||
};
|
||||
}, []);
|
||||
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
const command = event.currentTarget.value;
|
||||
event.currentTarget.value = "";
|
||||
if (command.length > 0) {
|
||||
props.bladeburner.postToConsole("> " + command);
|
||||
props.bladeburner.executeConsoleCommands(props.player, command);
|
||||
setConsoleHistoryIndex(props.bladeburner.consoleHistory.length);
|
||||
rerender();
|
||||
}
|
||||
}
|
||||
|
||||
const consoleHistory = props.bladeburner.consoleHistory;
|
||||
|
||||
if (event.keyCode === 38) { // up
|
||||
let i = consoleHistoryIndex;
|
||||
const len = consoleHistory.length;
|
||||
if (len === 0) {return;}
|
||||
if (i < 0 || i > len) {
|
||||
setConsoleHistoryIndex(len);
|
||||
}
|
||||
|
||||
if (i !== 0) {
|
||||
i = i-1;
|
||||
}
|
||||
setConsoleHistoryIndex(i);
|
||||
const prevCommand = consoleHistory[i];
|
||||
event.currentTarget.value = prevCommand;
|
||||
}
|
||||
|
||||
if (event.keyCode === 40) {
|
||||
const i = consoleHistoryIndex;
|
||||
const len = consoleHistory.length;
|
||||
|
||||
if (len == 0) {return;}
|
||||
if (i < 0 || i > len) {
|
||||
setConsoleHistoryIndex(len);
|
||||
}
|
||||
|
||||
// Latest command, put nothing
|
||||
if (i == len || i == len-1) {
|
||||
setConsoleHistoryIndex(len);
|
||||
event.currentTarget.value = "";
|
||||
} else {
|
||||
setConsoleHistoryIndex(consoleHistoryIndex+1);
|
||||
const prevCommand = consoleHistory[consoleHistoryIndex+1];
|
||||
event.currentTarget.value = prevCommand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (<div ref={lastRef} className="bladeburner-console-div">
|
||||
<table className="bladeburner-console-table">
|
||||
<tbody>
|
||||
{/*
|
||||
TODO: optimize this.
|
||||
using `i` as a key here isn't great because it'll re-render everything
|
||||
everytime the console reaches max length.
|
||||
*/}
|
||||
{props.bladeburner.consoleLogs.map((log: any, i: number) => <Line key={i} content={log} />)}
|
||||
<tr key="input" id="bladeburner-console-input-row" className="bladeburner-console-input-row">
|
||||
<td className="bladeburner-console-input-cell">
|
||||
<pre>{"> "}</pre><input autoFocus className="bladeburner-console-input" tabIndex={1} type="text" onKeyDown={handleKeyDown} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>);
|
||||
}
|
||||
118
src/Bladeburner/ui/ContractElem.tsx
Normal file
118
src/Bladeburner/ui/ContractElem.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import React, { useState } from "react";
|
||||
import { ActionTypes } from "../data/ActionTypes";
|
||||
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
||||
import {
|
||||
formatNumber,
|
||||
convertTimeMsToTimeElapsedString,
|
||||
} from "../../../utils/StringHelperFunctions";
|
||||
import { stealthIcon, killIcon } from "../data/Icons";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { SuccessChance } from "./SuccessChance";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
action: any;
|
||||
}
|
||||
|
||||
export function ContractElem(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const isActive = props.bladeburner.action.type === ActionTypes["Contract"] && props.action.name === props.bladeburner.action.name;
|
||||
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
|
||||
const successChance = props.action.getSuccessChance(props.bladeburner);
|
||||
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete);
|
||||
const maxLevel = (props.action.level >= props.action.maxLevel);
|
||||
const actionTime = props.action.getActionTime(props.bladeburner);
|
||||
const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`;
|
||||
|
||||
function onStart(): void {
|
||||
props.bladeburner.action.type = ActionTypes.Contract;
|
||||
props.bladeburner.action.name = props.action.name;
|
||||
props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
function increaseLevel(): void {
|
||||
++props.action.level;
|
||||
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
function decreaseLevel(): void {
|
||||
--props.action.level;
|
||||
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
function onAutolevel(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
props.action.autoLevel = event.target.checked;
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<h2 style={{display: 'inline-block'}}>
|
||||
{isActive ?
|
||||
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
|
||||
<>{props.action.name}</>
|
||||
}
|
||||
</h2>
|
||||
{isActive ?
|
||||
<p style={{display: 'block'}}>{createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}</p> :
|
||||
<>
|
||||
<a
|
||||
onClick={onStart}
|
||||
className="a-link-button"
|
||||
style={{margin:"3px", padding:"3px"}}>
|
||||
Start
|
||||
</a>
|
||||
</>}
|
||||
<br />
|
||||
<br />
|
||||
<pre className="tooltip" style={{display:"inline-block"}}>
|
||||
<span className="tooltiptext">
|
||||
{props.action.getSuccessesNeededForNextLevel(BladeburnerConstants.ContractSuccessesPerLevel)} successes needed for next level
|
||||
</span>
|
||||
Level: {props.action.level} / {props.action.maxLevel}
|
||||
</pre>
|
||||
<a
|
||||
onClick={increaseLevel}
|
||||
style={{padding:"2px", margin:"2px"}}
|
||||
className={`tooltip ${maxLevel ? "a-link-button-inactive" : "a-link-button"}`}>
|
||||
{isActive && (<span className="tooltiptext">WARNING: changing the level will restart the Operation</span>)}
|
||||
↑
|
||||
</a>
|
||||
<a
|
||||
onClick={decreaseLevel}
|
||||
style={{padding:"2px", margin:"2px"}}
|
||||
className={`tooltip ${props.action.level <= 1 ? "a-link-button-inactive" : "a-link-button"}`}>
|
||||
{isActive && (<span className="tooltiptext">WARNING: changing the level will restart the Operation</span>)}
|
||||
↓
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
<pre style={{display: 'inline-block'}}>
|
||||
<span dangerouslySetInnerHTML={{__html: props.action.desc}} />
|
||||
<br /><br />
|
||||
Estimated success chance: <SuccessChance chance={estimatedSuccessChance} /> {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}<br />
|
||||
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}<br />
|
||||
Contracts remaining: {Math.floor(props.action.count)}<br />
|
||||
Successes: {props.action.successes}<br />
|
||||
Failures: {props.action.failures}
|
||||
</pre>
|
||||
<br />
|
||||
<label
|
||||
className="tooltip"
|
||||
style={{color: 'white'}}
|
||||
htmlFor={autolevelCheckboxId}>
|
||||
Autolevel:
|
||||
<span className="tooltiptext">Automatically increase operation level when possible</span>
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={autolevelCheckboxId}
|
||||
checked={props.action.autoLevel}
|
||||
onChange={onAutolevel}/>
|
||||
</>);
|
||||
}
|
||||
20
src/Bladeburner/ui/ContractList.tsx
Normal file
20
src/Bladeburner/ui/ContractList.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import { ContractElem } from "./ContractElem";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function ContractList(props: IProps): React.ReactElement {
|
||||
const names = Object.keys(props.bladeburner.contracts);
|
||||
const contracts = props.bladeburner.contracts;
|
||||
return (<>
|
||||
{names.map((name: string) => <li key={name} className="bladeburner-action">
|
||||
<ContractElem bladeburner={props.bladeburner} action={contracts[name]} player={props.player} />
|
||||
</li>,
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
23
src/Bladeburner/ui/ContractPage.tsx
Normal file
23
src/Bladeburner/ui/ContractPage.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as React from "react";
|
||||
import { ContractList } from "./ContractList";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function ContractPage(props: IProps): React.ReactElement {
|
||||
return (<>
|
||||
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
|
||||
Complete contracts in order to increase your Bladeburner rank and earn money.
|
||||
Failing a contract will cause you to lose HP, which can lead to hospitalization.
|
||||
<br />
|
||||
<br />
|
||||
You can unlock higher-level contracts by successfully completing them.
|
||||
Higher-level contracts are more difficult, but grant more rank, experience, and money.
|
||||
</p>
|
||||
<ContractList bladeburner={props.bladeburner} player={props.player} />
|
||||
</>);
|
||||
}
|
||||
68
src/Bladeburner/ui/GeneralActionElem.tsx
Normal file
68
src/Bladeburner/ui/GeneralActionElem.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { useState } from "react";
|
||||
import { ActionTypes } from "../data/ActionTypes";
|
||||
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
||||
import {
|
||||
formatNumber,
|
||||
convertTimeMsToTimeElapsedString,
|
||||
} from "../../../utils/StringHelperFunctions";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
action: any;
|
||||
}
|
||||
|
||||
export function GeneralActionElem(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const isActive = props.action.name === props.bladeburner.action.name;
|
||||
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeToComplete);
|
||||
const actionTime = (function(): number{
|
||||
switch(props.action.name) {
|
||||
case "Training":
|
||||
case "Field Analysis":
|
||||
return 30;
|
||||
case "Diplomacy":
|
||||
case "Hyperbolic Regeneration Chamber":
|
||||
return 60;
|
||||
case "Recruitment":
|
||||
return props.bladeburner.getRecruitmentTime(props.player);
|
||||
}
|
||||
return -1; // dead code
|
||||
})();
|
||||
const successChance = props.action.name === "Recruitment" ? Math.max(0, Math.min(props.bladeburner.getRecruitmentSuccessChance(props.player), 1)) : -1;
|
||||
|
||||
function onStart(): void {
|
||||
props.bladeburner.action.type = ActionTypes[(props.action.name as string)];
|
||||
props.bladeburner.action.name = props.action.name;
|
||||
props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<h2 style={{display: 'inline-block'}}>
|
||||
{isActive ?
|
||||
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
|
||||
<>{props.action.name}</>
|
||||
}
|
||||
</h2>
|
||||
{isActive ?
|
||||
<p style={{display: 'block'}}>{createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}</p> :
|
||||
<>
|
||||
<a
|
||||
onClick={onStart}
|
||||
className="a-link-button"
|
||||
style={{margin:"3px", padding:"3px"}}>
|
||||
Start
|
||||
</a>
|
||||
</>}
|
||||
<br />
|
||||
<br />
|
||||
<pre style={{display: 'inline-block'}} dangerouslySetInnerHTML={{__html: props.action.desc}}></pre><br /><br />
|
||||
<pre style={{display: 'inline-block'}}>
|
||||
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}
|
||||
{successChance !== -1 && <><br />Estimated success chance: {formatNumber(successChance*100, 1)}%</>}
|
||||
</pre>
|
||||
</>);
|
||||
}
|
||||
26
src/Bladeburner/ui/GeneralActionList.tsx
Normal file
26
src/Bladeburner/ui/GeneralActionList.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
import { GeneralActionElem } from "./GeneralActionElem";
|
||||
import { Action } from "../Action";
|
||||
import { GeneralActions } from "../GeneralActions";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function GeneralActionList(props: IProps): React.ReactElement {
|
||||
const actions: Action[] = [];
|
||||
for (const name in GeneralActions) {
|
||||
if (GeneralActions.hasOwnProperty(name)) {
|
||||
actions.push(GeneralActions[name]);
|
||||
}
|
||||
}
|
||||
return (<>
|
||||
{actions.map((action: Action) => <li key={action.name} className="bladeburner-action">
|
||||
<GeneralActionElem bladeburner={props.bladeburner} action={action} player={props.player} />
|
||||
</li>,
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
19
src/Bladeburner/ui/GeneralActionPage.tsx
Normal file
19
src/Bladeburner/ui/GeneralActionPage.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from "react";
|
||||
import { GeneralActionList } from "./GeneralActionList";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function GeneralActionPage(props: IProps): React.ReactElement {
|
||||
return (<>
|
||||
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
|
||||
These are generic actions that will assist you in your Bladeburner
|
||||
duties. They will not affect your Bladeburner rank in any way.
|
||||
</p>
|
||||
<GeneralActionList bladeburner={props.bladeburner} player={props.player} />
|
||||
</>);
|
||||
}
|
||||
134
src/Bladeburner/ui/OperationElem.tsx
Normal file
134
src/Bladeburner/ui/OperationElem.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import React, { useState } from "react";
|
||||
import { ActionTypes } from "../data/ActionTypes";
|
||||
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
||||
import {
|
||||
formatNumber,
|
||||
convertTimeMsToTimeElapsedString,
|
||||
} from "../../../utils/StringHelperFunctions";
|
||||
import { stealthIcon, killIcon } from "../data/Icons";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { TeamSizePopup } from "./TeamSizePopup";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { SuccessChance } from "./SuccessChance";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
action: any;
|
||||
}
|
||||
|
||||
export function OperationElem(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const isActive = props.bladeburner.action.type === ActionTypes["Operation"] && props.action.name === props.bladeburner.action.name;
|
||||
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
|
||||
const computedActionTimeCurrent = Math.min(props.bladeburner.actionTimeCurrent+props.bladeburner.actionTimeOverflow,props.bladeburner.actionTimeToComplete);
|
||||
const maxLevel = (props.action.level >= props.action.maxLevel);
|
||||
const actionTime = props.action.getActionTime(props.bladeburner);
|
||||
const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`;
|
||||
|
||||
function onStart(): void {
|
||||
props.bladeburner.action.type = ActionTypes.Operation;
|
||||
props.bladeburner.action.name = props.action.name;
|
||||
props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
function onTeam(): void {
|
||||
const popupId = "bladeburner-operation-set-team-size-popup";
|
||||
createPopup(popupId, TeamSizePopup, {
|
||||
bladeburner: props.bladeburner,
|
||||
action: props.action,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
function increaseLevel(): void {
|
||||
++props.action.level;
|
||||
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
function decreaseLevel(): void {
|
||||
--props.action.level;
|
||||
if (isActive) props.bladeburner.startAction(props.player, props.bladeburner.action);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
function onAutolevel(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
props.action.autoLevel = event.target.checked;
|
||||
setRerender(old => !old);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<h2 style={{display: 'inline-block'}}>
|
||||
{isActive ?
|
||||
<>{props.action.name} (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} / {formatNumber(props.bladeburner.actionTimeToComplete, 0)})</> :
|
||||
<>{props.action.name}</>
|
||||
}
|
||||
</h2>
|
||||
{isActive ?
|
||||
<p style={{display: 'block'}}>{createProgressBarText({progress:computedActionTimeCurrent / props.bladeburner.actionTimeToComplete})}</p> :
|
||||
<>
|
||||
<a
|
||||
onClick={onStart}
|
||||
className="a-link-button"
|
||||
style={{margin:"3px", padding:"3px"}}>
|
||||
Start
|
||||
</a>
|
||||
<a
|
||||
onClick={onTeam}
|
||||
style={{margin:"3px", padding:"3px"}}
|
||||
className="a-link-button">
|
||||
Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)})
|
||||
</a>
|
||||
</>}
|
||||
<br />
|
||||
<br />
|
||||
<pre className="tooltip" style={{display:"inline-block"}}>
|
||||
<span className="tooltiptext">
|
||||
{props.action.getSuccessesNeededForNextLevel(BladeburnerConstants.OperationSuccessesPerLevel)} successes needed for next level
|
||||
</span>
|
||||
Level: {props.action.level} / {props.action.maxLevel}
|
||||
</pre>
|
||||
<a
|
||||
onClick={increaseLevel}
|
||||
style={{padding:"2px", margin:"2px"}}
|
||||
className={`tooltip ${maxLevel ? "a-link-button-inactive" : "a-link-button"}`}>
|
||||
{isActive && (<span className="tooltiptext">WARNING: changing the level will restart the Operation</span>)}
|
||||
↑
|
||||
</a>
|
||||
<a
|
||||
onClick={decreaseLevel}
|
||||
style={{padding:"2px", margin:"2px"}}
|
||||
className={`tooltip ${props.action.level <= 1 ? "a-link-button-inactive" : "a-link-button"}`}>
|
||||
{isActive && (<span className="tooltiptext">WARNING: changing the level will restart the Operation</span>)}
|
||||
↓
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
<pre style={{display:"inline-block"}}>
|
||||
<span dangerouslySetInnerHTML={{__html: props.action.desc}} />
|
||||
<br /><br />
|
||||
Estimated success chance: <SuccessChance chance={estimatedSuccessChance} /> {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}<br />
|
||||
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}<br />
|
||||
Operations remaining: {Math.floor(props.action.count)}<br />
|
||||
Successes: {props.action.successes}<br />
|
||||
Failures: {props.action.failures}
|
||||
</pre>
|
||||
<br />
|
||||
<label
|
||||
className="tooltip"
|
||||
style={{color: 'white'}}
|
||||
htmlFor={autolevelCheckboxId}>
|
||||
Autolevel:
|
||||
<span className="tooltiptext">Automatically increase operation level when possible</span>
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={autolevelCheckboxId}
|
||||
checked={props.action.autoLevel}
|
||||
onChange={onAutolevel}/>
|
||||
</>);
|
||||
}
|
||||
20
src/Bladeburner/ui/OperationList.tsx
Normal file
20
src/Bladeburner/ui/OperationList.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import { OperationElem } from "./OperationElem";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function OperationList(props: IProps): React.ReactElement {
|
||||
const names = Object.keys(props.bladeburner.operations);
|
||||
const operations = props.bladeburner.operations;
|
||||
return (<>
|
||||
{names.map((name: string) => <li key={name} className="bladeburner-action">
|
||||
<OperationElem bladeburner={props.bladeburner} action={operations[name]} player={props.player} />
|
||||
</li>,
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
34
src/Bladeburner/ui/OperationPage.tsx
Normal file
34
src/Bladeburner/ui/OperationPage.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as React from "react";
|
||||
import { OperationList } from "./OperationList";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function OperationPage(props: IProps): React.ReactElement {
|
||||
return (<>
|
||||
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
|
||||
Carry out operations for the Bladeburner division.
|
||||
Failing an operation will reduce your Bladeburner rank. It will also
|
||||
cause you to lose HP, which can lead to hospitalization. In general,
|
||||
operations are harder and more punishing than contracts,
|
||||
but are also more rewarding.
|
||||
<br />
|
||||
<br />
|
||||
Operations can affect the chaos level and Synthoid population of your
|
||||
current city. The exact effects vary between different Operations.
|
||||
<br />
|
||||
<br />
|
||||
For operations, you can use a team. You must first recruit team members.
|
||||
Having a larger team will improves your chances of success.
|
||||
<br />
|
||||
<br />
|
||||
You can unlock higher-level operations by successfully completing them.
|
||||
Higher-level operations are more difficult, but grant more rank and experience.
|
||||
</p>
|
||||
<OperationList bladeburner={props.bladeburner} player={props.player} />
|
||||
</>);
|
||||
}
|
||||
28
src/Bladeburner/ui/Root.tsx
Normal file
28
src/Bladeburner/ui/Root.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from "react";
|
||||
import { Stats } from "./Stats";
|
||||
import { Console } from "./Console";
|
||||
import { AllPages } from "./AllPages";
|
||||
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { IEngine } from "../../IEngine";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
engine: IEngine;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function Root(props: IProps): React.ReactElement {
|
||||
return (<div id="bladeburner-container">
|
||||
<div style={{height:"60%", display:"block", position:"relative"}}>
|
||||
<div style={{height: '100%', width:"30%", display:"inline-block", border:"1px solid white"}}>
|
||||
<Stats bladeburner={props.bladeburner} player={props.player} engine={props.engine} />
|
||||
</div>
|
||||
<Console bladeburner={props.bladeburner} player={props.player} />
|
||||
</div>
|
||||
<div style={{width:"70%", display:"block", border:"1px solid white", marginTop:"6px", padding: "6px", position:"relative"}}>
|
||||
<AllPages bladeburner={props.bladeburner} player={props.player} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
48
src/Bladeburner/ui/SkillElem.tsx
Normal file
48
src/Bladeburner/ui/SkillElem.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import { CopyableText } from "../../ui/React/CopyableText";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
skill: any;
|
||||
bladeburner: IBladeburner;
|
||||
onUpgrade: () => void;
|
||||
}
|
||||
|
||||
export function SkillElem(props: IProps): React.ReactElement {
|
||||
const skillName = props.skill.name;
|
||||
let currentLevel = 0;
|
||||
if (props.bladeburner.skills[skillName] && !isNaN(props.bladeburner.skills[skillName])) {
|
||||
currentLevel = props.bladeburner.skills[skillName];
|
||||
}
|
||||
const pointCost = props.skill.calculateCost(currentLevel);
|
||||
|
||||
const canLevel = props.bladeburner.skillPoints >= pointCost;
|
||||
const maxLvl = props.skill.maxLvl ? currentLevel >= props.skill.maxLvl : false;
|
||||
|
||||
function onClick(): void {
|
||||
if (props.bladeburner.skillPoints < pointCost) return;
|
||||
props.bladeburner.skillPoints -= pointCost;
|
||||
props.bladeburner.upgradeSkill(props.skill);
|
||||
props.onUpgrade();
|
||||
}
|
||||
|
||||
return (<>
|
||||
<h2 style={{display: 'inline-block'}}>
|
||||
<CopyableText value={props.skill.name} />
|
||||
</h2>
|
||||
<a
|
||||
onClick={onClick}
|
||||
style={{display: "inline-block", margin: "3px", padding: "3px"}}
|
||||
className={canLevel && !maxLvl ? "a-link-button" : "a-link-button-inactive"}>
|
||||
Level
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
<p style={{display: 'block'}}>Level: {currentLevel}</p>
|
||||
{maxLvl ?
|
||||
<p style={{color:"red", display:"block"}}>MAX LEVEL</p> :
|
||||
<p style={{display:"block"}}>Skill Points required: {formatNumber(pointCost, 0)}</p>}
|
||||
<p style={{display:"inline-block"}} dangerouslySetInnerHTML={{__html: props.skill.desc}} />
|
||||
</>);
|
||||
}
|
||||
18
src/Bladeburner/ui/SkillList.tsx
Normal file
18
src/Bladeburner/ui/SkillList.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as React from "react";
|
||||
import { SkillElem } from "./SkillElem";
|
||||
import { Skills } from "../Skills";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
onUpgrade: () => void;
|
||||
}
|
||||
|
||||
export function SkillList(props: IProps): React.ReactElement {
|
||||
return (<>
|
||||
{Object.keys(Skills).map((skill: string) => <li key={skill} className="bladeburner-action">
|
||||
<SkillElem bladeburner={props.bladeburner} skill={Skills[skill]} onUpgrade={props.onUpgrade} />
|
||||
</li>,
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
70
src/Bladeburner/ui/SkillPage.tsx
Normal file
70
src/Bladeburner/ui/SkillPage.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { useState } from "react";
|
||||
import { SkillList } from "./SkillList";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
}
|
||||
|
||||
|
||||
export function SkillPage(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const mults = props.bladeburner.skillMultipliers;
|
||||
|
||||
function valid(mult: any): boolean {
|
||||
return mult && mult !== 1
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p>
|
||||
<strong>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</strong>
|
||||
</p>
|
||||
<p>
|
||||
You will gain one skill point every {BladeburnerConstants.RanksPerSkillPoint} ranks.
|
||||
<br />
|
||||
<br />
|
||||
Note that when upgrading a skill, the benefit for that skill is additive.
|
||||
However, the effects of different skills with each other is multiplicative.
|
||||
<br />
|
||||
</p>
|
||||
<br />
|
||||
{valid(mults["successChanceAll"]) && <p>Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)}</p>}
|
||||
{valid(mults["successChanceStealth"]) && <p>Stealth Success Chance: x{formatNumber(mults["successChanceStealth"], 3)}</p>}
|
||||
{valid(mults["successChanceKill"]) && <p>Retirement Success Chance: x{formatNumber(mults["successChanceKill"], 3)}</p>}
|
||||
{valid(mults["successChanceContract"]) && <p>Contract Success Chance: x{formatNumber(mults["successChanceContract"], 3)}</p>}
|
||||
{valid(mults["successChanceOperation"]) && <p>Operation Success Chance: x{formatNumber(mults["successChanceOperation"], 3)}</p>}
|
||||
{valid(mults["successChanceEstimate"]) && <p>Synthoid Data Estimate: x{formatNumber(mults["successChanceEstimate"], 3)}</p>}
|
||||
{valid(mults["actionTime"]) && <p>Action Time: x{formatNumber(mults["actionTime"], 3)}</p>}
|
||||
{valid(mults["effHack"]) && <p>Hacking Skill: x{formatNumber(mults["effHack"], 3)}</p>}
|
||||
{valid(mults["effStr"]) && <p>Strength: x{formatNumber(mults["effStr"], 3)}</p>}
|
||||
{valid(mults["effDef"]) && <p>Defense: x{formatNumber(mults["effDef"], 3)}</p>}
|
||||
{valid(mults["effDex"]) && <p>Dexterity: x{formatNumber(mults["effDex"], 3)}</p>}
|
||||
{valid(mults["effAgi"]) && <p>Agility: x{formatNumber(mults["effAgi"], 3)}</p>}
|
||||
{valid(mults["effCha"]) && <p>Charisma: x{formatNumber(mults["effCha"], 3)}</p>}
|
||||
{valid(mults["effInt"]) && <p>Intelligence: x{formatNumber(mults["effInt"], 3)}</p>}
|
||||
{valid(mults["stamina"]) && <p>Stamina: x{formatNumber(mults["stamina"], 3)}</p>}
|
||||
{valid(mults["money"]) && <p>Contract Money: x{formatNumber(mults["money"], 3)}</p>}
|
||||
{valid(mults["expGain"]) && <p>Exp Gain: x{formatNumber(mults["expGain"], 3)}</p>}
|
||||
<br />
|
||||
<SkillList bladeburner={props.bladeburner} onUpgrade={() => setRerender(old => !old)} />
|
||||
</>);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
|
||||
var multKeys = Object.keys(this.skillMultipliers);
|
||||
for (var i = 0; i < multKeys.length; ++i) {
|
||||
var mult = this.skillMultipliers[multKeys[i]];
|
||||
if (mult && mult !== 1) {
|
||||
mult = formatNumber(mult, 3);
|
||||
switch(multKeys[i]) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
139
src/Bladeburner/ui/Stats.tsx
Normal file
139
src/Bladeburner/ui/Stats.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
formatNumber,
|
||||
convertTimeMsToTimeElapsedString,
|
||||
} from "../../../utils/StringHelperFunctions";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { IEngine } from "../../IEngine";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { StatsTable } from "../../ui/React/StatsTable";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import {
|
||||
joinFaction,
|
||||
displayFactionContent,
|
||||
} from "../../Faction/FactionHelpers";
|
||||
import { IBladeburner } from "../IBladeburner"
|
||||
|
||||
import { TravelPopup } from "./TravelPopup";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
engine: IEngine;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function Stats(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function openStaminaHelp(): void {
|
||||
dialogBoxCreate("Performing actions will use up your stamina.<br><br>" +
|
||||
"Your max stamina is determined primarily by your agility stat.<br><br>" +
|
||||
"Your stamina gain rate is determined by both your agility and your " +
|
||||
"max stamina. Higher max stamina leads to a higher gain rate.<br><br>" +
|
||||
"Once your " +
|
||||
"stamina falls below 50% of its max value, it begins to negatively " +
|
||||
"affect the success rate of your contracts/operations. This penalty " +
|
||||
"is shown in the overview panel. If the penalty is 15%, then this means " +
|
||||
"your success rate would be multipled by 85% (100 - 15).<br><br>" +
|
||||
"Your max stamina and stamina gain rate can also be increased by " +
|
||||
"training, or through skills and Augmentation upgrades.");
|
||||
}
|
||||
|
||||
function openPopulationHelp(): void {
|
||||
dialogBoxCreate("The success rate of your contracts/operations depends on " +
|
||||
"the population of Synthoids in your current city. " +
|
||||
"The success rate that is shown to you is only an estimate, " +
|
||||
"and it is based on your Synthoid population estimate.<br><br>" +
|
||||
"Therefore, it is important that this Synthoid population estimate " +
|
||||
"is accurate so that you have a better idea of your " +
|
||||
"success rate for contracts/operations. Certain " +
|
||||
"actions will increase the accuracy of your population " +
|
||||
"estimate.<br><br>" +
|
||||
"The Synthoid populations of cities can change due to your " +
|
||||
"actions or random events. If random events occur, they will " +
|
||||
"be logged in the Bladeburner Console.");
|
||||
}
|
||||
|
||||
function openTravel(): void {
|
||||
const popupId = "bladeburner-travel-popup";
|
||||
createPopup(popupId, TravelPopup, {
|
||||
bladeburner: props.bladeburner,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
function openFaction(): void {
|
||||
const faction = Factions["Bladeburners"];
|
||||
if (faction.isMember) {
|
||||
props.engine.loadFactionContent();
|
||||
displayFactionContent("Bladeburners");
|
||||
} else {
|
||||
if (props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) {
|
||||
joinFaction(faction);
|
||||
dialogBoxCreate("Congratulations! You were accepted into the Bladeburners faction");
|
||||
} else {
|
||||
dialogBoxCreate("You need a rank of 25 to join the Bladeburners Faction!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p className="tooltip" style={{display: 'inline-block'}}>
|
||||
Rank: {formatNumber(props.bladeburner.rank, 2)}
|
||||
<span className="tooltiptext">Your rank within the Bladeburner division.</span>
|
||||
</p><br />
|
||||
<p>Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)}</p>
|
||||
<div className="help-tip" onClick={openStaminaHelp}>?</div><br />
|
||||
<p>Stamina Penalty: {formatNumber((1-props.bladeburner.calculateStaminaPenalty())*100, 1)}%</p><br />
|
||||
<p>Team Size: {formatNumber(props.bladeburner.teamSize, 0)}</p>
|
||||
<p>Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}</p><br />
|
||||
<p>Num Times Hospitalized: {props.bladeburner.numHosp}</p>
|
||||
<p>Money Lost From Hospitalizations: {Money(props.bladeburner.moneyLost)}</p><br />
|
||||
<p>Current City: {props.bladeburner.city}</p>
|
||||
<p className="tooltip" style={{display: 'inline-block'}}>
|
||||
Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)}
|
||||
<span className="tooltiptext">This is your Bladeburner division's estimate of how many Synthoids exist in your current city.</span>
|
||||
</p>
|
||||
<div className="help-tip" onClick={openPopulationHelp}>?</div><br />
|
||||
<p className="tooltip" style={{display: 'inline-block'}}>
|
||||
Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)}
|
||||
<span className="tooltiptext">This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city.</span>
|
||||
</p><br />
|
||||
<p className="tooltip" style={{display: 'inline-block'}}>
|
||||
City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)}
|
||||
<span className="tooltiptext">The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a chaos level can make contracts and operations harder.</span>
|
||||
</p><br />
|
||||
<br />
|
||||
<p className="tooltip" style={{display: 'inline-block'}}>
|
||||
Bonus time: {convertTimeMsToTimeElapsedString(props.bladeburner.storedCycles/BladeburnerConstants.CyclesPerSecond*1000)}<br />
|
||||
<span className="tooltiptext">
|
||||
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by browser).
|
||||
Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed.
|
||||
</span>
|
||||
</p>
|
||||
<p>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</p><br />
|
||||
{StatsTable([
|
||||
["Aug. Success Chance mult: ", formatNumber(props.player.bladeburner_success_chance_mult*100, 1) + "%"],
|
||||
["Aug. Max Stamina mult: ", formatNumber(props.player.bladeburner_max_stamina_mult*100, 1) + "%"],
|
||||
["Aug. Stamina Gain mult: ", formatNumber(props.player.bladeburner_stamina_gain_mult*100, 1) + "%"],
|
||||
["Aug. Field Analysis mult: ", formatNumber(props.player.bladeburner_analysis_mult*100, 1) + "%"],
|
||||
])}
|
||||
<br />
|
||||
<a onClick={openTravel} className="a-link-button" style={{display:"inline-block"}}>Travel</a>
|
||||
<a onClick={openFaction} className="a-link-button tooltip" style={{display:"inline-block"}}>
|
||||
<span className="tooltiptext">Apply to the Bladeburner Faction, or go to the faction page if you are already a member</span>
|
||||
Faction
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
</>);
|
||||
}
|
||||
14
src/Bladeburner/ui/SuccessChance.tsx
Normal file
14
src/Bladeburner/ui/SuccessChance.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
|
||||
interface IProps {
|
||||
chance: number[];
|
||||
}
|
||||
|
||||
export function SuccessChance(props: IProps): React.ReactElement {
|
||||
if(props.chance[0] === props.chance[1]) {
|
||||
return (<>{formatNumber(props.chance[0]*100, 1)}%</>);
|
||||
}
|
||||
|
||||
return (<>{formatNumber(props.chance[0]*100, 1)}% ~ {formatNumber(props.chance[1]*100, 1)}%</>);
|
||||
}
|
||||
37
src/Bladeburner/ui/TeamSizePopup.tsx
Normal file
37
src/Bladeburner/ui/TeamSizePopup.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { useState } from "react";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { Action } from "../Action";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
action: Action;
|
||||
popupId: string;
|
||||
}
|
||||
|
||||
export function TeamSizePopup(props: IProps): React.ReactElement {
|
||||
const [teamSize, setTeamSize] = useState<number | undefined>();
|
||||
|
||||
function confirmTeamSize(): void {
|
||||
if(teamSize === undefined) return;
|
||||
const num = Math.round(teamSize);
|
||||
if (isNaN(num) || num < 0) {
|
||||
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric, positive)")
|
||||
} else {
|
||||
props.action.teamCount = num;
|
||||
}
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p>
|
||||
Enter the amount of team members you would like to take on this
|
||||
Op. If you do not have the specified number of team members,
|
||||
then as many as possible will be used. Note that team members may
|
||||
be lost during operations.
|
||||
</p>
|
||||
<input autoFocus type="number" placeholder= "Team size" className= "text-input" onChange={event => setTeamSize(parseFloat(event.target.value))} />
|
||||
<a className="a-link-button" onClick={confirmTeamSize}>Confirm</a>
|
||||
</>);
|
||||
}
|
||||
32
src/Bladeburner/ui/TravelPopup.tsx
Normal file
32
src/Bladeburner/ui/TravelPopup.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
popupId: string;
|
||||
}
|
||||
|
||||
export function TravelPopup(props: IProps): React.ReactElement {
|
||||
function travel(city: string): void {
|
||||
props.bladeburner.city = city;
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p>
|
||||
Travel to a different city for your Bladeburner
|
||||
activities. This does not cost any money. The city you are
|
||||
in for your Bladeburner duties does not affect
|
||||
your location in the game otherwise.
|
||||
</p>
|
||||
{BladeburnerConstants.CityNames.map(city => {
|
||||
// Reusing this css class...it adds a border and makes it
|
||||
// so that background color changes when you hover
|
||||
return <div key={city} className="cmpy-mgmt-find-employee-option"
|
||||
onClick={() => travel(city)}>
|
||||
{city}
|
||||
</div>})}
|
||||
</>);
|
||||
}
|
||||
@@ -172,7 +172,7 @@ export class CodingContract {
|
||||
async prompt(): Promise<CodingContractResult> {
|
||||
const popupId = `coding-contract-prompt-popup-${this.fn}`;
|
||||
return new Promise<CodingContractResult>((resolve) => {
|
||||
const popup = new CodingContractPopup({
|
||||
createPopup(popupId, CodingContractPopup, {
|
||||
c: this,
|
||||
popupId: popupId,
|
||||
onClose: () => {
|
||||
@@ -188,7 +188,6 @@ export class CodingContract {
|
||||
removePopup(popupId);
|
||||
},
|
||||
});
|
||||
createPopup(popupId, CodingContractPopup, popup.props);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
import { IMap } from "./types";
|
||||
|
||||
export const CONSTANTS: IMap<any> = {
|
||||
Version: "0.52.0",
|
||||
Version: "0.52.8",
|
||||
|
||||
// Speed (in ms) at which the main loop is updated
|
||||
_idleSpeed: 200,
|
||||
|
||||
/** 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
|
||||
@@ -224,48 +227,30 @@ export const CONSTANTS: IMap<any> = {
|
||||
// BitNode/Source-File related stuff
|
||||
TotalNumBitNodes: 24,
|
||||
|
||||
LatestUpdate:
|
||||
`
|
||||
v0.52.0 - 2021-06-13 Infiltration 2.0 (hydroflame)
|
||||
-------
|
||||
LatestUpdate: `
|
||||
v0.52.8 - 2021-07-23 Fixing the previous patch tbh ROUND 2 (hydroflame)
|
||||
-------------------------------------------
|
||||
|
||||
Infiltration
|
||||
* Completely reworked. Not the same mechanic at all.
|
||||
** Script editor **
|
||||
|
||||
Terminal
|
||||
* tail is smarter. It automatically assume the only possible options in some
|
||||
cases.
|
||||
* Correctly reloads old script when clicking "Script Editor"
|
||||
* No longer jumps to the end of the text for no reason.
|
||||
|
||||
Intelligence
|
||||
* Now available when starting BN5 instead of after beating it for the first
|
||||
time.
|
||||
* Nerf the effect of intelligence on reputation gain.
|
||||
** Hash upgrades **
|
||||
|
||||
Augmentation
|
||||
* Added a new augmentation, the 'Unstable Circadian Modulator', whose
|
||||
gimmick is that its stats are randomized every hour.
|
||||
* Fixed an issue where the default option would say ecorp but was really
|
||||
foodnstuff
|
||||
|
||||
Netscript
|
||||
* 'getPlayer' is not a singularity function anymore.
|
||||
* 'hacknetNodes.constants' returns the correct values.
|
||||
* 'createGang' has been added.
|
||||
* 'inGang' has been added.
|
||||
** Misc. **
|
||||
|
||||
Tutorial
|
||||
* Updated the tutorial. Made it look cleaner, fixed typos, etc.
|
||||
* The "Delete all active script" button under the options has a clearer
|
||||
description.
|
||||
* Removed some debug console.log
|
||||
* nerf noodle bar
|
||||
`,
|
||||
|
||||
Misc.
|
||||
* Fix many typos in literature (@kwazygloo)
|
||||
* Fix being able to unfocus from gym and university.
|
||||
* Fix being able to do hacking missions while unfocused.
|
||||
* Fix many typos in Augmentation descriptions (@kwazygloo)
|
||||
* More numbers handle absurdly large values. (@Tesseract1234567890)
|
||||
* Fix many typos (@Tesseract1234567890)
|
||||
* Fixed an issue that caused a UI desync when sleeves were set to workout
|
||||
stats other than strength at the gym.
|
||||
* Fix weird alignment of donation text box and button. (@Tesseract1234567890)
|
||||
* Fixed an issue where reputation could be transfered to new jobs when unfocused.
|
||||
* Empty stack traces should no longer appear.
|
||||
* Purchasing anything with Infinity money doesn't result in NaN.
|
||||
`,
|
||||
/*
|
||||
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -119,7 +119,7 @@ function Industry(params={}) {
|
||||
};
|
||||
|
||||
this.name = params.name ? params.name : 0;
|
||||
this.type = params.type ? params.type : 0;
|
||||
this.type = params.type ? params.type : Industries.Agriculture;
|
||||
|
||||
this.sciResearch = new Material({name: "Scientific Research"});
|
||||
this.researched = {}; // Object of acquired Research. Keys = research name
|
||||
@@ -1397,11 +1397,12 @@ Industry.prototype.createResearchBox = function() {
|
||||
researchTree.research(allResearch[i]);
|
||||
this.researched[allResearch[i]] = true;
|
||||
|
||||
const researchBox = this.createResearchBox();
|
||||
dialogBoxCreate(`Researched ${allResearch[i]}. It may take a market cycle ` +
|
||||
`(~${SecsPerMarketCycle} seconds) before the effects of ` +
|
||||
`the Research apply.`);
|
||||
|
||||
return this.createResearchBox();
|
||||
return researchBox;
|
||||
} else {
|
||||
dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { AllServers } from "./Server/AllServers";
|
||||
import { GetServerByHostname } from "./Server/ServerHelpers";
|
||||
import { hackWorldDaemon } from "./RedPill";
|
||||
import { StockMarket } from "./StockMarket/StockMarket";
|
||||
import { Bladeburner } from "./Bladeburner";
|
||||
import { Bladeburner } from "./Bladeburner/Bladeburner";
|
||||
import { Stock } from "./StockMarket/Stock";
|
||||
import { Engine } from "./engine";
|
||||
import { saveObject } from "./SaveObject";
|
||||
@@ -76,6 +76,7 @@ class DevMenuComponent extends Component {
|
||||
|
||||
this.setSF = this.setSF.bind(this);
|
||||
this.setAllSF = this.setAllSF.bind(this);
|
||||
this.clearExploits = this.clearExploits.bind(this);
|
||||
this.processStocks = this.processStocks.bind(this);
|
||||
this.setStockPrice = this.setStockPrice.bind(this);
|
||||
this.viewStockCaps = this.viewStockCaps.bind(this);
|
||||
@@ -378,6 +379,10 @@ class DevMenuComponent extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
clearExploits() {
|
||||
Player.exploits = [];
|
||||
}
|
||||
|
||||
addProgram() {
|
||||
const program = this.state.program;
|
||||
if(!Player.hasProgram(program)) {
|
||||
@@ -497,7 +502,7 @@ class DevMenuComponent extends Component {
|
||||
modifyBladeburnerRank(modify) {
|
||||
return function(rank) {
|
||||
if (Player.bladeburner) {
|
||||
Player.bladeburner.changeRank(rank*modify);
|
||||
Player.bladeburner.changeRank(Player, rank*modify);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -509,7 +514,7 @@ class DevMenuComponent extends Component {
|
||||
|
||||
addTonsBladeburnerRank() {
|
||||
if (Player.bladeburner) {
|
||||
Player.bladeburner.changeRank(tonsP);
|
||||
Player.bladeburner.changeRank(Player, tonsP);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -953,6 +958,12 @@ class DevMenuComponent extends Component {
|
||||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span className="text">Exploits:</span></td>
|
||||
<td>
|
||||
<button className="std-button touch-right" onClick={this.clearExploits()}>Clear</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr key={'sf-all'}>
|
||||
<td><span className="text">All:</span></td>
|
||||
<td>
|
||||
|
||||
2
src/Faction/FactionHelpers.d.ts
vendored
2
src/Faction/FactionHelpers.d.ts
vendored
@@ -5,3 +5,5 @@ export declare function getNextNeurofluxLevel(): number;
|
||||
export declare function hasAugmentationPrereqs(aug: Augmentation): boolean;
|
||||
export declare function purchaseAugmentationBoxCreate(aug: Augmentation, fac: Faction): void;
|
||||
export declare function purchaseAugmentation(aug: Augmentation, fac: Faction, sing?: boolean): void;
|
||||
export declare function displayFactionContent(factionName: string, initiallyOnAugmentationsPage: boolean=false);
|
||||
export declare function joinFaction(faction: Faction): void;
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
getFactionSecurityWorkRepGain,
|
||||
getFactionFieldWorkRepGain,
|
||||
} from "../PersonObjects/formulas/reputation";
|
||||
import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
||||
|
||||
import { Page, routing } from "../ui/navigationTracking";
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
@@ -45,6 +46,7 @@ export function inviteToFaction(faction) {
|
||||
}
|
||||
|
||||
export function joinFaction(faction) {
|
||||
if(faction.isMember) return;
|
||||
faction.isMember = true;
|
||||
Player.factions.push(faction.name);
|
||||
const factionInfo = faction.getInfo();
|
||||
@@ -194,13 +196,13 @@ export function purchaseAugmentation(aug, fac, sing=false) {
|
||||
aug.baseCost = 750e3 * mult * BitNodeMultipliers.AugmentationMoneyCost;
|
||||
|
||||
for (var i = 0; i < Player.queuedAugmentations.length-1; ++i) {
|
||||
aug.baseCost *= CONSTANTS.MultipleAugMultiplier;
|
||||
aug.baseCost *= (CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]]);
|
||||
}
|
||||
}
|
||||
|
||||
for (var name in Augmentations) {
|
||||
if (Augmentations.hasOwnProperty(name)) {
|
||||
Augmentations[name].baseCost *= CONSTANTS.MultipleAugMultiplier;
|
||||
Augmentations[name].baseCost *= (CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
50
src/Faction/ui/FactionList.tsx
Normal file
50
src/Faction/ui/FactionList.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { IEngine } from "../../IEngine";
|
||||
import { Factions } from "../Factions";
|
||||
import { displayFactionContent, joinFaction } from "../FactionHelpers";
|
||||
|
||||
interface IProps {
|
||||
player: IPlayer;
|
||||
engine: IEngine;
|
||||
}
|
||||
|
||||
export function FactionList(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
function openFaction(faction: string): void {
|
||||
props.engine.loadFactionContent();
|
||||
displayFactionContent(faction);
|
||||
}
|
||||
|
||||
function acceptInvitation(event: React.MouseEvent<HTMLElement>, faction: string): void {
|
||||
if (!event.isTrusted) return;
|
||||
joinFaction(Factions[faction]);
|
||||
setRerender(x => !x);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<h1>Factions</h1>
|
||||
<p>Lists all factions you have joined</p>
|
||||
<br />
|
||||
<ul>
|
||||
{props.player.factions.map((faction: string) => <li key={faction}><a
|
||||
className="a-link-button"
|
||||
onClick={() => openFaction(faction)}
|
||||
style={{padding:"4px", margin:"4px", display:"inline-block"}}>{faction}
|
||||
</a></li>)}
|
||||
</ul>
|
||||
<br />
|
||||
<h1>Outstanding Faction Invitations</h1>
|
||||
<p style={{width: '70%'}}>Lists factions you have been invited to. You can accept these faction invitations at any time.</p>
|
||||
<ul>
|
||||
{props.player.factionInvitations.map((faction: string) => <li key={faction} style={{padding:"6px", margin:"6px"}}>
|
||||
<p style={{display:"inline", margin:"4px", padding:"4px"}}>{faction}</p>
|
||||
<a
|
||||
className="a-link-button"
|
||||
onClick={(e) => acceptInvitation(e, faction)}
|
||||
style={{display:"inline", margin:"4px", padding:"4px"}}>Accept Faction Invitation</a>
|
||||
</li>)}
|
||||
</ul>
|
||||
</>);
|
||||
}
|
||||
@@ -137,6 +137,12 @@ export class PurchaseableAugmentation extends React.Component<IProps, any> {
|
||||
btnTxt += ` - Level ${getNextNeurofluxLevel()}`;
|
||||
}
|
||||
|
||||
let tooltip = <></>;
|
||||
if(typeof this.aug.info === "string")
|
||||
tooltip = <><span dangerouslySetInnerHTML={{__html: this.aug.info}} /><br /><br />{this.aug.stats}</>
|
||||
else
|
||||
tooltip = <>{this.aug.info}<br /><br />{this.aug.stats}</>
|
||||
|
||||
return (
|
||||
<li>
|
||||
<span style={spanStyleMarkup}>
|
||||
@@ -145,7 +151,7 @@ export class PurchaseableAugmentation extends React.Component<IProps, any> {
|
||||
onClick={this.handleClick}
|
||||
style={inlineStyleMarkup}
|
||||
text={btnTxt}
|
||||
tooltip={this.aug.info}
|
||||
tooltip={tooltip}
|
||||
/>
|
||||
<p style={txtStyle}>{status}</p>
|
||||
</span>
|
||||
|
||||
1
src/Fconf/Fconf.d.ts
vendored
Normal file
1
src/Fconf/Fconf.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function parseFconfSettings(config: string): void;
|
||||
1991
src/Gang.jsx
1991
src/Gang.jsx
File diff suppressed because it is too large
Load Diff
76
src/Gang/AllGangs.ts
Normal file
76
src/Gang/AllGangs.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
interface GangTerritory {
|
||||
power: number;
|
||||
territory: number;
|
||||
}
|
||||
|
||||
export let AllGangs: {
|
||||
[key: string]: GangTerritory;
|
||||
} = {
|
||||
"Slum Snakes" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Tetrads" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Syndicate" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Dark Army" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Speakers for the Dead" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"NiteSec" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Black Hand" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
}
|
||||
|
||||
export function resetGangs(): void {
|
||||
AllGangs = {
|
||||
"Slum Snakes" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Tetrads" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Syndicate" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Dark Army" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Speakers for the Dead" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"NiteSec" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Black Hand" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function loadAllGangs(saveString: string): void {
|
||||
AllGangs = JSON.parse(saveString, Reviver);
|
||||
}
|
||||
411
src/Gang/Gang.ts
Normal file
411
src/Gang/Gang.ts
Normal file
@@ -0,0 +1,411 @@
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Add police clashes
|
||||
* balance point to keep them from running out of control
|
||||
*/
|
||||
|
||||
import { Faction } from "../Faction/Faction";
|
||||
import { Factions } from "../Faction/Factions";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import {
|
||||
Reviver,
|
||||
Generic_toJSON,
|
||||
Generic_fromJSON,
|
||||
} from "../../utils/JSONReviver";
|
||||
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
import { GangConstants } from "./data/Constants";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { GangMemberTasks } from "./GangMemberTasks";
|
||||
import { IAscensionResult } from "./IAscensionResult";
|
||||
|
||||
import { AllGangs } from "./AllGangs";
|
||||
import { GangMember } from "./GangMember";
|
||||
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
|
||||
|
||||
export class Gang {
|
||||
facName: string;
|
||||
members: GangMember[];
|
||||
wanted: number;
|
||||
respect: number;
|
||||
|
||||
isHackingGang: boolean;
|
||||
|
||||
respectGainRate: number;
|
||||
wantedGainRate: number;
|
||||
moneyGainRate: number;
|
||||
|
||||
storedCycles: number;
|
||||
|
||||
storedTerritoryAndPowerCycles: number;
|
||||
|
||||
territoryClashChance: number;
|
||||
territoryWarfareEngaged: boolean;
|
||||
|
||||
notifyMemberDeath: boolean;
|
||||
|
||||
constructor(facName = "", hacking = false) {
|
||||
this.facName = facName;
|
||||
this.members = [];
|
||||
this.wanted = 1;
|
||||
this.respect = 1;
|
||||
|
||||
this.isHackingGang = hacking;
|
||||
|
||||
this.respectGainRate = 0;
|
||||
this.wantedGainRate = 0;
|
||||
this.moneyGainRate = 0;
|
||||
|
||||
// When processing gains, this stores the number of cycles until some
|
||||
// limit is reached, and then calculates and applies the gains only at that limit
|
||||
this.storedCycles = 0;
|
||||
|
||||
// Separate variable to keep track of cycles for Territry + Power gang, which
|
||||
// happens on a slower "clock" than normal processing
|
||||
this.storedTerritoryAndPowerCycles = 0;
|
||||
|
||||
this.territoryClashChance = 0;
|
||||
this.territoryWarfareEngaged = false;
|
||||
|
||||
this.notifyMemberDeath = true;
|
||||
}
|
||||
|
||||
getPower(): number {
|
||||
return AllGangs[this.facName].power;
|
||||
}
|
||||
|
||||
getTerritory(): number {
|
||||
return AllGangs[this.facName].territory;
|
||||
}
|
||||
|
||||
process(numCycles = 1, player: IPlayer): void {
|
||||
const CyclesPerSecond = 1000 / CONSTANTS._idleSpeed;
|
||||
|
||||
if (isNaN(numCycles)) {
|
||||
console.error(`NaN passed into Gang.process(): ${numCycles}`);
|
||||
}
|
||||
this.storedCycles += numCycles;
|
||||
|
||||
// Only process if there are at least 2 seconds, and at most 5 seconds
|
||||
if (this.storedCycles < 2 * CyclesPerSecond) return;
|
||||
const cycles = Math.min(this.storedCycles, 5 * CyclesPerSecond);
|
||||
|
||||
try {
|
||||
this.processGains(cycles, player);
|
||||
this.processExperienceGains(cycles);
|
||||
this.processTerritoryAndPowerGains(cycles);
|
||||
this.storedCycles -= cycles;
|
||||
} catch(e) {
|
||||
console.error(`Exception caught when processing Gang: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
processGains(numCycles = 1, player: IPlayer): void {
|
||||
// Get gains per cycle
|
||||
let moneyGains = 0;
|
||||
let respectGains = 0;
|
||||
let wantedLevelGains = 0;
|
||||
let justice = 0;
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
respectGains += (this.members[i].calculateRespectGain(this));
|
||||
moneyGains += (this.members[i].calculateMoneyGain(this));
|
||||
const wantedLevelGain = this.members[i].calculateWantedLevelGain(this);
|
||||
wantedLevelGains += wantedLevelGain;
|
||||
if(this.members[i].getTask().baseWanted < 0) justice++; // this member is lowering wanted.
|
||||
}
|
||||
this.respectGainRate = respectGains;
|
||||
this.wantedGainRate = wantedLevelGains;
|
||||
this.moneyGainRate = moneyGains;
|
||||
|
||||
const gain = respectGains * numCycles;
|
||||
this.respect += gain;
|
||||
// Faction reputation gains is respect gain divided by some constant
|
||||
const fac = Factions[this.facName];
|
||||
if (!(fac instanceof Faction)) {
|
||||
dialogBoxCreate("ERROR: Could not get Faction associates with your gang. This is a bug, please report to game dev");
|
||||
throw new Error('Could not find the faction associated with this gang.');
|
||||
}
|
||||
const favorMult = 1 + (fac.favor / 100);
|
||||
fac.playerReputation += ((player.faction_rep_mult * gain * favorMult) / GangConstants.GangRespectToReputationRatio);
|
||||
|
||||
// Keep track of respect gained per member
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
this.members[i].recordEarnedRespect(numCycles, this);
|
||||
}
|
||||
if (!(this.wanted === 1 && wantedLevelGains < 0)) {
|
||||
const oldWanted = this.wanted;
|
||||
let newWanted = oldWanted + (wantedLevelGains * numCycles);
|
||||
newWanted = newWanted * (1 - justice * 0.001); // safeguard
|
||||
// Prevent overflow
|
||||
if (wantedLevelGains <= 0 && newWanted > oldWanted) newWanted = 1;
|
||||
|
||||
this.wanted = newWanted;
|
||||
if (this.wanted < 1) this.wanted = 1;
|
||||
}
|
||||
player.gainMoney(moneyGains * numCycles);
|
||||
player.recordMoneySource(moneyGains * numCycles, "gang");
|
||||
}
|
||||
|
||||
processTerritoryAndPowerGains(numCycles = 1): void {
|
||||
this.storedTerritoryAndPowerCycles += numCycles;
|
||||
if (this.storedTerritoryAndPowerCycles < GangConstants.CyclesPerTerritoryAndPowerUpdate) return;
|
||||
this.storedTerritoryAndPowerCycles -= GangConstants.CyclesPerTerritoryAndPowerUpdate;
|
||||
|
||||
// Process power first
|
||||
const gangName = this.facName;
|
||||
for (const name in AllGangs) {
|
||||
if (AllGangs.hasOwnProperty(name)) {
|
||||
if (name == gangName) {
|
||||
AllGangs[name].power += this.calculatePower();
|
||||
} else {
|
||||
// All NPC gangs get random power gains
|
||||
const gainRoll = Math.random();
|
||||
if (gainRoll < 0.5) {
|
||||
// Multiplicative gain (50% chance)
|
||||
// This is capped per cycle, to prevent it from getting out of control
|
||||
const multiplicativeGain = AllGangs[name].power * 0.005;
|
||||
AllGangs[name].power += Math.min(0.85, multiplicativeGain);
|
||||
} else {
|
||||
// Additive gain (50% chance)
|
||||
const additiveGain = 0.75 * gainRoll * AllGangs[name].territory;
|
||||
AllGangs[name].power += (additiveGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if territory should be processed
|
||||
if (this.territoryWarfareEngaged) {
|
||||
this.territoryClashChance = 1;
|
||||
} else if (this.territoryClashChance > 0) {
|
||||
// Engagement turned off, but still a positive clash chance. So there's
|
||||
// still a chance of clashing but it slowly goes down over time
|
||||
this.territoryClashChance = Math.max(0, this.territoryClashChance - 0.01);
|
||||
}
|
||||
|
||||
// Then process territory
|
||||
for (let i = 0; i < GangConstants.Names.length; ++i) {
|
||||
const others = GangConstants.Names.filter((e) => {
|
||||
return e !== GangConstants.Names[i];
|
||||
});
|
||||
const other = getRandomInt(0, others.length - 1);
|
||||
|
||||
const thisGang = GangConstants.Names[i];
|
||||
const otherGang = others[other];
|
||||
|
||||
// If either of the gangs involved in this clash is the player, determine
|
||||
// whether to skip or process it using the clash chance
|
||||
if (thisGang === gangName || otherGang === gangName) {
|
||||
if (!(Math.random() < this.territoryClashChance)) continue;
|
||||
}
|
||||
|
||||
const thisPwr = AllGangs[thisGang].power;
|
||||
const otherPwr = AllGangs[otherGang].power;
|
||||
const thisChance = thisPwr / (thisPwr + otherPwr);
|
||||
|
||||
function calculateTerritoryGain(winGang: string, loseGang: string): number {
|
||||
const powerBonus = Math.max(1, 1+Math.log(AllGangs[winGang].power/AllGangs[loseGang].power)/Math.log(50));
|
||||
const gains = Math.min(AllGangs[loseGang].territory, powerBonus*0.0001*(Math.random()+.5))
|
||||
return gains;
|
||||
}
|
||||
|
||||
if (Math.random() < thisChance) {
|
||||
if (AllGangs[otherGang].territory <= 0) return;
|
||||
const territoryGain = calculateTerritoryGain(thisGang, otherGang);
|
||||
AllGangs[thisGang].territory += territoryGain;
|
||||
AllGangs[otherGang].territory -= territoryGain;
|
||||
if (thisGang === gangName) {
|
||||
this.clash(true); // Player won
|
||||
AllGangs[otherGang].power *= (1 / 1.01);
|
||||
} else if (otherGang === gangName) {
|
||||
this.clash(false); // Player lost
|
||||
} else {
|
||||
AllGangs[otherGang].power *= (1 / 1.01);
|
||||
}
|
||||
} else {
|
||||
if (AllGangs[thisGang].territory <= 0) return;
|
||||
const territoryGain = calculateTerritoryGain(otherGang, thisGang);
|
||||
AllGangs[thisGang].territory -= territoryGain;
|
||||
AllGangs[otherGang].territory += territoryGain;
|
||||
if (thisGang === gangName) {
|
||||
this.clash(false); // Player lost
|
||||
} else if (otherGang === gangName) {
|
||||
this.clash(true); // Player won
|
||||
AllGangs[thisGang].power *= (1 / 1.01);
|
||||
} else {
|
||||
AllGangs[thisGang].power *= (1 / 1.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processExperienceGains(numCycles = 1): void {
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
this.members[i].gainExperience(numCycles);
|
||||
this.members[i].updateSkillLevels();
|
||||
}
|
||||
}
|
||||
|
||||
clash(won = false): void {
|
||||
// Determine if a gang member should die
|
||||
let baseDeathChance = 0.01;
|
||||
if (won) baseDeathChance /= 2;
|
||||
|
||||
// If the clash was lost, the player loses a small percentage of power
|
||||
else AllGangs[this.facName].power *= (1 / 1.008);
|
||||
|
||||
// Deaths can only occur during X% of clashes
|
||||
if (Math.random() < 0.65) return;
|
||||
|
||||
for (let i = this.members.length - 1; i >= 0; --i) {
|
||||
const member = this.members[i];
|
||||
|
||||
// Only members assigned to Territory Warfare can die
|
||||
if (member.task !== "Territory Warfare") continue;
|
||||
|
||||
// Chance to die is decreased based on defense
|
||||
const modifiedDeathChance = baseDeathChance / Math.pow(member.def, 0.6);
|
||||
if (Math.random() < modifiedDeathChance) {
|
||||
this.killMember(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canRecruitMember(): boolean {
|
||||
if (this.members.length >= GangConstants.MaximumGangMembers) return false;
|
||||
return (this.respect >= this.getRespectNeededToRecruitMember());
|
||||
}
|
||||
|
||||
getRespectNeededToRecruitMember(): number {
|
||||
// First N gang members are free (can be recruited at 0 respect)
|
||||
const numFreeMembers = 3;
|
||||
if (this.members.length < numFreeMembers) return 0;
|
||||
|
||||
const i = this.members.length - (numFreeMembers - 1);
|
||||
return Math.pow(5, i);
|
||||
}
|
||||
|
||||
recruitMember(name: string): boolean {
|
||||
name = String(name);
|
||||
if (name === "" || !this.canRecruitMember()) return false;
|
||||
|
||||
// Check for already-existing names
|
||||
const sameNames = this.members.filter((m) => m.name === name);
|
||||
if (sameNames.length >= 1) return false;
|
||||
|
||||
const member = new GangMember(name);
|
||||
this.members.push(member);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Money and Respect gains multiplied by this number (< 1)
|
||||
getWantedPenalty(): number {
|
||||
return (this.respect) / (this.respect + this.wanted);
|
||||
}
|
||||
|
||||
|
||||
//Calculates power GAIN, which is added onto the Gang's existing power
|
||||
calculatePower(): number {
|
||||
let memberTotal = 0;
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
if (!GangMemberTasks.hasOwnProperty(this.members[i].task) ||
|
||||
this.members[i].task !== "Territory Warfare") continue;
|
||||
memberTotal += this.members[i].calculatePower();
|
||||
}
|
||||
return (0.015 * Math.max(0.002, this.getTerritory()) * memberTotal);
|
||||
}
|
||||
|
||||
|
||||
killMember(member: GangMember): void {
|
||||
// Player loses a percentage of total respect, plus whatever respect that member has earned
|
||||
const totalRespect = this.respect;
|
||||
const lostRespect = (0.05 * totalRespect) + member.earnedRespect;
|
||||
this.respect = Math.max(0, totalRespect - lostRespect);
|
||||
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
if (member.name === this.members[i].name) {
|
||||
this.members.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify of death
|
||||
if (this.notifyMemberDeath) {
|
||||
dialogBoxCreate(`${member.name} was killed in a gang clash! You lost ${lostRespect} respect`);
|
||||
}
|
||||
}
|
||||
|
||||
ascendMember(member: GangMember, workerScript?: WorkerScript): IAscensionResult {
|
||||
try {
|
||||
const res = member.ascend();
|
||||
this.respect = Math.max(1, this.respect - res.respect);
|
||||
if (workerScript) {
|
||||
workerScript.log('ascend', `Ascended Gang member ${member.name}`);
|
||||
}
|
||||
return res;
|
||||
} catch(e) {
|
||||
if (workerScript == null) {
|
||||
exceptionAlert(e);
|
||||
}
|
||||
throw e; // Re-throw, will be caught in the Netscript Function
|
||||
}
|
||||
}
|
||||
|
||||
// Cost of upgrade gets cheaper as gang increases in respect + power
|
||||
getDiscount(): number {
|
||||
const power = this.getPower();
|
||||
const respect = this.respect;
|
||||
|
||||
const respectLinearFac = 5e6;
|
||||
const powerLinearFac = 1e6;
|
||||
const discount = Math.pow(respect, 0.01) +
|
||||
respect / respectLinearFac +
|
||||
Math.pow(power, 0.01) +
|
||||
power / powerLinearFac - 1;
|
||||
return Math.max(1, discount);
|
||||
}
|
||||
|
||||
// Returns only valid tasks for this gang. Excludes 'Unassigned'
|
||||
getAllTaskNames(): string[] {
|
||||
return Object.keys(GangMemberTasks).filter((taskName: string) => {
|
||||
const task = GangMemberTasks[taskName];
|
||||
if (task == null) return false;
|
||||
if (task.name === "Unassigned") return false;
|
||||
// yes you need both checks
|
||||
return this.isHackingGang === task.isHacking ||
|
||||
!this.isHackingGang === task.isCombat;
|
||||
});
|
||||
}
|
||||
|
||||
getUpgradeCost(upg: GangMemberUpgrade | null): number {
|
||||
if (upg == null) { return Infinity; }
|
||||
return upg.cost/this.getDiscount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the current object to a JSON save state.
|
||||
*/
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("Gang", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiatizes a Gang object from a JSON save state.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static fromJSON(value: any): Gang {
|
||||
return Generic_fromJSON(Gang, value.data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reviver.constructors.Gang = Gang;
|
||||
320
src/Gang/GangMember.ts
Normal file
320
src/Gang/GangMember.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import { GangMemberTask } from "./GangMemberTask";
|
||||
import { GangMemberTasks } from "./GangMemberTasks";
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
import { GangMemberUpgrades } from "./GangMemberUpgrades";
|
||||
import { IAscensionResult } from "./IAscensionResult";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { AllGangs } from "./AllGangs";
|
||||
import { IGang } from "./IGang";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
interface IMults {
|
||||
hack: number;
|
||||
str: number;
|
||||
def: number;
|
||||
dex: number;
|
||||
agi: number;
|
||||
cha: number;
|
||||
}
|
||||
|
||||
export class GangMember {
|
||||
name: string;
|
||||
task = "Unassigned";
|
||||
|
||||
earnedRespect = 0;
|
||||
|
||||
hack = 1;
|
||||
str = 1;
|
||||
def = 1;
|
||||
dex = 1;
|
||||
agi = 1;
|
||||
cha = 1;
|
||||
|
||||
hack_exp = 0;
|
||||
str_exp = 0;
|
||||
def_exp = 0;
|
||||
dex_exp = 0;
|
||||
agi_exp = 0;
|
||||
cha_exp = 0;
|
||||
|
||||
hack_mult = 1;
|
||||
str_mult = 1;
|
||||
def_mult = 1;
|
||||
dex_mult = 1;
|
||||
agi_mult = 1;
|
||||
cha_mult = 1;
|
||||
|
||||
hack_asc_points = 0;
|
||||
str_asc_points = 0;
|
||||
def_asc_points = 0;
|
||||
dex_asc_points = 0;
|
||||
agi_asc_points = 0;
|
||||
cha_asc_points = 0;
|
||||
|
||||
upgrades: string[] = []; // Names of upgrades
|
||||
augmentations: string[] = []; // Names of augmentations only
|
||||
|
||||
constructor(name = "") {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
calculateSkill(exp: number, mult = 1): number {
|
||||
return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1);
|
||||
}
|
||||
|
||||
calculateAscensionMult(points: number): number {
|
||||
return Math.max(Math.pow(points/4000, 0.7), 1);
|
||||
}
|
||||
|
||||
updateSkillLevels(): void {
|
||||
this.hack = this.calculateSkill(this.hack_exp, this.hack_mult * this.calculateAscensionMult(this.hack_asc_points));
|
||||
this.str = this.calculateSkill(this.str_exp, this.str_mult * this.calculateAscensionMult(this.str_asc_points));
|
||||
this.def = this.calculateSkill(this.def_exp, this.def_mult * this.calculateAscensionMult(this.def_asc_points));
|
||||
this.dex = this.calculateSkill(this.dex_exp, this.dex_mult * this.calculateAscensionMult(this.dex_asc_points));
|
||||
this.agi = this.calculateSkill(this.agi_exp, this.agi_mult * this.calculateAscensionMult(this.agi_asc_points));
|
||||
this.cha = this.calculateSkill(this.cha_exp, this.cha_mult * this.calculateAscensionMult(this.cha_asc_points));
|
||||
}
|
||||
|
||||
calculatePower(): number {
|
||||
return (this.hack +
|
||||
this.str +
|
||||
this.def +
|
||||
this.dex +
|
||||
this.agi +
|
||||
this.cha) / 95;
|
||||
}
|
||||
|
||||
assignToTask(taskName: string): boolean {
|
||||
if (!GangMemberTasks.hasOwnProperty(taskName)) {
|
||||
this.task = "Unassigned";
|
||||
return false;
|
||||
}
|
||||
this.task = taskName;
|
||||
return true;
|
||||
}
|
||||
|
||||
unassignFromTask(): void {
|
||||
this.task = "Unassigned";
|
||||
}
|
||||
|
||||
getTask(): GangMemberTask {
|
||||
// TODO(hydroflame): transfer that to a save file migration function
|
||||
// Backwards compatibility
|
||||
if ((this.task as any) instanceof GangMemberTask) {
|
||||
this.task = (this.task as any).name;
|
||||
}
|
||||
|
||||
if (GangMemberTasks.hasOwnProperty(this.task)) {
|
||||
return GangMemberTasks[this.task];
|
||||
}
|
||||
return GangMemberTasks["Unassigned"];
|
||||
}
|
||||
|
||||
calculateRespectGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseRespect === 0) return 0;
|
||||
let statWeight = (task.hackWeight/100) * this.hack +
|
||||
(task.strWeight/100) * this.str +
|
||||
(task.defWeight/100) * this.def +
|
||||
(task.dexWeight/100) * this.dex +
|
||||
(task.agiWeight/100) * this.agi +
|
||||
(task.chaWeight/100) * this.cha;
|
||||
statWeight -= (4 * task.difficulty);
|
||||
if (statWeight <= 0) return 0;
|
||||
const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.respect) / 100);
|
||||
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
|
||||
const respectMult = gang.getWantedPenalty();
|
||||
return 11 * task.baseRespect * statWeight * territoryMult * respectMult;
|
||||
}
|
||||
|
||||
calculateWantedLevelGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseWanted === 0) return 0;
|
||||
let statWeight = (task.hackWeight / 100) * this.hack +
|
||||
(task.strWeight / 100) * this.str +
|
||||
(task.defWeight / 100) * this.def +
|
||||
(task.dexWeight / 100) * this.dex +
|
||||
(task.agiWeight / 100) * this.agi +
|
||||
(task.chaWeight / 100) * this.cha;
|
||||
statWeight -= (3.5 * task.difficulty);
|
||||
if (statWeight <= 0) return 0;
|
||||
const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / 100);
|
||||
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
|
||||
if (task.baseWanted < 0) {
|
||||
return 0.4 * task.baseWanted * statWeight * territoryMult;
|
||||
}
|
||||
const calc = 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8));
|
||||
|
||||
// Put an arbitrary cap on this to prevent wanted level from rising too fast if the
|
||||
// denominator is very small. Might want to rethink formula later
|
||||
return Math.min(100, calc);
|
||||
}
|
||||
|
||||
calculateMoneyGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseMoney === 0) return 0;
|
||||
let statWeight = (task.hackWeight/100) * this.hack +
|
||||
(task.strWeight/100) * this.str +
|
||||
(task.defWeight/100) * this.def +
|
||||
(task.dexWeight/100) * this.dex +
|
||||
(task.agiWeight/100) * this.agi +
|
||||
(task.chaWeight/100) * this.cha;
|
||||
|
||||
statWeight -= (3.2 * task.difficulty);
|
||||
if (statWeight <= 0) return 0;
|
||||
const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.money) / 100);
|
||||
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
|
||||
const respectMult = gang.getWantedPenalty();
|
||||
return 5 * task.baseMoney * statWeight * territoryMult * respectMult;
|
||||
}
|
||||
|
||||
expMult(): IMults {
|
||||
return {
|
||||
hack: (this.hack_mult-1)/4+1,
|
||||
str: (this.str_mult-1)/4+1,
|
||||
def: (this.def_mult-1)/4+1,
|
||||
dex: (this.dex_mult-1)/4+1,
|
||||
agi: (this.agi_mult-1)/4+1,
|
||||
cha: (this.cha_mult-1)/4+1,
|
||||
};
|
||||
}
|
||||
|
||||
gainExperience(numCycles = 1): void {
|
||||
const task = this.getTask();
|
||||
if (task === GangMemberTasks["Unassigned"]) return;
|
||||
const difficultyMult = Math.pow(task.difficulty, 0.9);
|
||||
const difficultyPerCycles = difficultyMult * numCycles;
|
||||
const weightDivisor = 1500;
|
||||
const expMult = this.expMult();
|
||||
this.hack_exp += (task.hackWeight / weightDivisor) * difficultyPerCycles * expMult.hack;
|
||||
this.str_exp += (task.strWeight / weightDivisor) * difficultyPerCycles * expMult.str;
|
||||
this.def_exp += (task.defWeight / weightDivisor) * difficultyPerCycles * expMult.def;
|
||||
this.dex_exp += (task.dexWeight / weightDivisor) * difficultyPerCycles * expMult.dex;
|
||||
this.agi_exp += (task.agiWeight / weightDivisor) * difficultyPerCycles * expMult.agi;
|
||||
this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles * expMult.cha;
|
||||
}
|
||||
|
||||
recordEarnedRespect(numCycles = 1, gang: IGang): void {
|
||||
this.earnedRespect += (this.calculateRespectGain(gang) * numCycles);
|
||||
}
|
||||
|
||||
getGainedAscensionPoints(): IMults {
|
||||
return {
|
||||
hack: Math.max(this.hack_exp - 1000, 0),
|
||||
str: Math.max(this.str_exp - 1000, 0),
|
||||
def: Math.max(this.def_exp - 1000, 0),
|
||||
dex: Math.max(this.dex_exp - 1000, 0),
|
||||
agi: Math.max(this.agi_exp - 1000, 0),
|
||||
cha: Math.max(this.cha_exp - 1000, 0),
|
||||
}
|
||||
}
|
||||
|
||||
canAscend(): boolean {
|
||||
const points = this.getGainedAscensionPoints();
|
||||
return points.hack > 0 ||
|
||||
points.str > 0 ||
|
||||
points.def > 0 ||
|
||||
points.dex > 0 ||
|
||||
points.agi > 0 ||
|
||||
points.cha > 0;
|
||||
}
|
||||
|
||||
getAscensionResults(): IMults {
|
||||
const points = this.getGainedAscensionPoints();
|
||||
return {
|
||||
hack: this.calculateAscensionMult(this.hack_asc_points+points.hack)/this.calculateAscensionMult(this.hack_asc_points),
|
||||
str: this.calculateAscensionMult(this.str_asc_points+points.str)/this.calculateAscensionMult(this.str_asc_points),
|
||||
def: this.calculateAscensionMult(this.def_asc_points+points.def)/this.calculateAscensionMult(this.def_asc_points),
|
||||
dex: this.calculateAscensionMult(this.dex_asc_points+points.dex)/this.calculateAscensionMult(this.dex_asc_points),
|
||||
agi: this.calculateAscensionMult(this.agi_asc_points+points.agi)/this.calculateAscensionMult(this.agi_asc_points),
|
||||
cha: this.calculateAscensionMult(this.cha_asc_points+points.cha)/this.calculateAscensionMult(this.cha_asc_points),
|
||||
}
|
||||
}
|
||||
|
||||
ascend(): IAscensionResult {
|
||||
const res = this.getAscensionResults();
|
||||
const points = this.getGainedAscensionPoints();
|
||||
this.hack_asc_points += points.hack;
|
||||
this.str_asc_points += points.str;
|
||||
this.def_asc_points += points.def;
|
||||
this.dex_asc_points += points.dex;
|
||||
this.agi_asc_points += points.agi;
|
||||
this.cha_asc_points += points.cha;
|
||||
|
||||
// Remove upgrades. Then re-calculate multipliers and stats
|
||||
this.upgrades.length = 0;
|
||||
this.hack_mult = 1;
|
||||
this.str_mult = 1;
|
||||
this.def_mult = 1;
|
||||
this.dex_mult = 1;
|
||||
this.agi_mult = 1;
|
||||
this.cha_mult = 1;
|
||||
for (let i = 0; i < this.augmentations.length; ++i) {
|
||||
const aug = GangMemberUpgrades[this.augmentations[i]];
|
||||
this.applyUpgrade(aug);
|
||||
}
|
||||
|
||||
// Clear exp and recalculate stats
|
||||
this.hack_exp = 0;
|
||||
this.str_exp = 0;
|
||||
this.def_exp = 0;
|
||||
this.dex_exp = 0;
|
||||
this.agi_exp = 0;
|
||||
this.cha_exp = 0;
|
||||
this.updateSkillLevels();
|
||||
|
||||
const respectToDeduct = this.earnedRespect;
|
||||
this.earnedRespect = 0;
|
||||
return {
|
||||
respect: respectToDeduct,
|
||||
hack: res.hack,
|
||||
str: res.str,
|
||||
def: res.def,
|
||||
dex: res.dex,
|
||||
agi: res.agi,
|
||||
cha: res.cha,
|
||||
};
|
||||
}
|
||||
|
||||
applyUpgrade(upg: GangMemberUpgrade): void {
|
||||
if (upg.mults.str != null) this.str_mult *= upg.mults.str;
|
||||
if (upg.mults.def != null) this.def_mult *= upg.mults.def;
|
||||
if (upg.mults.dex != null) this.dex_mult *= upg.mults.dex;
|
||||
if (upg.mults.agi != null) this.agi_mult *= upg.mults.agi;
|
||||
if (upg.mults.cha != null) this.cha_mult *= upg.mults.cha;
|
||||
if (upg.mults.hack != null) this.hack_mult *= upg.mults.hack;
|
||||
}
|
||||
|
||||
buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: IGang): boolean {
|
||||
// Prevent purchasing of already-owned upgrades
|
||||
if (this.augmentations.includes(upg.name) ||
|
||||
this.upgrades.includes(upg.name)) return false;
|
||||
|
||||
if (player.money.lt(gang.getUpgradeCost(upg))) return false;
|
||||
player.loseMoney(gang.getUpgradeCost(upg));
|
||||
if (upg.type === "g") {
|
||||
this.augmentations.push(upg.name);
|
||||
} else {
|
||||
this.upgrades.push(upg.name);
|
||||
}
|
||||
this.applyUpgrade(upg);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the current object to a JSON save state.
|
||||
*/
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("GangMember", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiatizes a GangMember object from a JSON save state.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static fromJSON(value: any): GangMember {
|
||||
return Generic_fromJSON(GangMember, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
Reviver.constructors.GangMember = GangMember;
|
||||
62
src/Gang/GangMemberTask.ts
Normal file
62
src/Gang/GangMemberTask.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { ITaskParams, ITerritory } from "./ITaskParams";
|
||||
|
||||
export class GangMemberTask {
|
||||
name: string;
|
||||
desc: string;
|
||||
|
||||
isHacking: boolean;
|
||||
isCombat: boolean;
|
||||
|
||||
baseRespect: number;
|
||||
baseWanted: number;
|
||||
baseMoney: number;
|
||||
|
||||
hackWeight: number;
|
||||
strWeight: number;
|
||||
defWeight: number;
|
||||
dexWeight: number;
|
||||
agiWeight: number;
|
||||
chaWeight: number;
|
||||
|
||||
difficulty: number;
|
||||
|
||||
territory: ITerritory;
|
||||
|
||||
// Defines tasks that Gang Members can work on
|
||||
constructor(name: string, desc: string, isHacking: boolean, isCombat: boolean, params: ITaskParams) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
|
||||
// Flags that describe whether this Task is applicable for Hacking/Combat gangs
|
||||
this.isHacking = isHacking;
|
||||
this.isCombat = isCombat;
|
||||
|
||||
// Base gain rates for respect/wanted/money
|
||||
this.baseRespect = params.baseRespect ? params.baseRespect : 0;
|
||||
this.baseWanted = params.baseWanted ? params.baseWanted : 0;
|
||||
this.baseMoney = params.baseMoney ? params.baseMoney : 0;
|
||||
|
||||
// Weighting for the effect that each stat has on the tasks effectiveness.
|
||||
// Weights must add up to 100
|
||||
this.hackWeight = params.hackWeight ? params.hackWeight : 0;
|
||||
this.strWeight = params.strWeight ? params.strWeight : 0;
|
||||
this.defWeight = params.defWeight ? params.defWeight : 0;
|
||||
this.dexWeight = params.dexWeight ? params.dexWeight : 0;
|
||||
this.agiWeight = params.agiWeight ? params.agiWeight : 0;
|
||||
this.chaWeight = params.chaWeight ? params.chaWeight : 0;
|
||||
|
||||
if (Math.round(this.hackWeight + this.strWeight + this.defWeight + this.dexWeight + this.agiWeight + this.chaWeight) != 100) {
|
||||
console.error(`GangMemberTask ${this.name} weights do not add up to 100`);
|
||||
}
|
||||
|
||||
// 1 - 100
|
||||
this.difficulty = params.difficulty ? params.difficulty : 1;
|
||||
|
||||
// Territory Factors. Exponential factors that dictate how territory affects gains
|
||||
// Formula: Territory Mutiplier = (Territory * 100) ^ factor / 100
|
||||
// So factor should be > 1 if something should scale exponentially with territory
|
||||
// and should be < 1 if it should have diminshing returns
|
||||
this.territory = params.territory ? params.territory : {money: 1, respect: 1, wanted: 1};
|
||||
}
|
||||
|
||||
}
|
||||
12
src/Gang/GangMemberTasks.ts
Normal file
12
src/Gang/GangMemberTasks.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { gangMemberTasksMetadata } from "./data/tasks";
|
||||
import { GangMemberTask } from "./GangMemberTask";
|
||||
|
||||
export const GangMemberTasks: {
|
||||
[key: string]: GangMemberTask;
|
||||
} = {};
|
||||
|
||||
(function() {
|
||||
gangMemberTasksMetadata.forEach((e) => {
|
||||
GangMemberTasks[e.name] = new GangMemberTask(e.name, e.desc, e.isHacking, e.isCombat, e.params);
|
||||
});
|
||||
})();
|
||||
66
src/Gang/GangMemberUpgrade.ts
Normal file
66
src/Gang/GangMemberUpgrade.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { IMults, UpgradeType } from "./data/upgrades";
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
export class GangMemberUpgrade {
|
||||
name: string;
|
||||
cost: number;
|
||||
type: UpgradeType;
|
||||
desc: string;
|
||||
mults: IMults;
|
||||
|
||||
constructor(name = "", cost = 0, type: UpgradeType = UpgradeType.Weapon, mults: IMults = {}) {
|
||||
this.name = name;
|
||||
this.cost = cost;
|
||||
this.type = type;
|
||||
this.mults = mults;
|
||||
|
||||
this.desc = this.createDescription();
|
||||
}
|
||||
|
||||
createDescription(): string {
|
||||
const lines = ["Effects:"];
|
||||
if (this.mults.str != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.str-1, 0)} strength skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.str-1)/4, 2)} strength exp`);
|
||||
}
|
||||
if (this.mults.def != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.def-1, 0)} defense skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.def-1)/4, 2)} defense exp`);
|
||||
}
|
||||
if (this.mults.dex != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.dex-1, 0)} dexterity skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.dex-1)/4, 2)} dexterity exp`);
|
||||
}
|
||||
if (this.mults.agi != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.agi-1, 0)} agility skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.agi-1)/4, 2)} agility exp`);
|
||||
}
|
||||
if (this.mults.cha != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.cha-1, 0)} charisma skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.cha-1)/4, 2)} charisma exp`);
|
||||
}
|
||||
if (this.mults.hack != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.hack-1, 0)} hacking skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.hack-1)/4, 2)} hacking exp`);
|
||||
}
|
||||
return lines.join("<br>");
|
||||
}
|
||||
|
||||
// User friendly version of type.
|
||||
getType(): string {
|
||||
switch (this.type) {
|
||||
case UpgradeType.Weapon:
|
||||
return "Weapon";
|
||||
case UpgradeType.Armor:
|
||||
return "Armor";
|
||||
case UpgradeType.Vehicle:
|
||||
return "Vehicle";
|
||||
case UpgradeType.Rootkit:
|
||||
return "Rootkit";
|
||||
case UpgradeType.Augmentation:
|
||||
return "Augmentation";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/Gang/GangMemberUpgrades.ts
Normal file
12
src/Gang/GangMemberUpgrades.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { gangMemberUpgradesMetadata } from "./data/upgrades";
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
|
||||
export const GangMemberUpgrades: {
|
||||
[key: string]: GangMemberUpgrade;
|
||||
} = {};
|
||||
|
||||
(function() {
|
||||
gangMemberUpgradesMetadata.forEach((e) => {
|
||||
GangMemberUpgrades[e.name] = new GangMemberUpgrade(e.name, e.cost, e.upgType, e.mults);
|
||||
});
|
||||
})();
|
||||
32
src/Gang/Helpers.tsx
Normal file
32
src/Gang/Helpers.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { IEngine } from "../IEngine";
|
||||
import { Root } from "./ui/Root";
|
||||
import { Gang } from "./Gang";
|
||||
import { Page, routing } from ".././ui/navigationTracking";
|
||||
|
||||
let gangContainer: HTMLElement;
|
||||
|
||||
(function() {
|
||||
function set(): void {
|
||||
const c = document.getElementById("gang-container");
|
||||
if(c === null) throw new Error("Could not find element 'gang-container'");
|
||||
gangContainer = c;
|
||||
document.removeEventListener("DOMContentLoaded", set);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", set);
|
||||
})();
|
||||
|
||||
|
||||
export function displayGangContent(engine: IEngine, gang: Gang, player: IPlayer): void {
|
||||
if (!routing.isOn(Page.Gang)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDOM.render(<Root
|
||||
engine={engine}
|
||||
gang={gang}
|
||||
player={player} />, gangContainer);
|
||||
}
|
||||
9
src/Gang/IAscensionResult.ts
Normal file
9
src/Gang/IAscensionResult.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface IAscensionResult {
|
||||
respect: number;
|
||||
hack: number;
|
||||
str: number;
|
||||
def: number;
|
||||
dex: number;
|
||||
agi: number;
|
||||
cha: number;
|
||||
}
|
||||
44
src/Gang/IGang.ts
Normal file
44
src/Gang/IGang.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
import { GangMember } from "./GangMember";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
|
||||
export interface IGang {
|
||||
facName: string;
|
||||
members: GangMember[];
|
||||
wanted: number;
|
||||
respect: number;
|
||||
|
||||
isHackingGang: boolean;
|
||||
|
||||
respectGainRate: number;
|
||||
wantedGainRate: number;
|
||||
moneyGainRate: number;
|
||||
|
||||
storedCycles: number;
|
||||
|
||||
storedTerritoryAndPowerCycles: number;
|
||||
|
||||
territoryClashChance: number;
|
||||
territoryWarfareEngaged: boolean;
|
||||
|
||||
notifyMemberDeath: boolean;
|
||||
|
||||
getPower(): number;
|
||||
getTerritory(): number;
|
||||
process(numCycles: number, player: IPlayer): void;
|
||||
processGains(numCycles: number, player: IPlayer): void;
|
||||
processTerritoryAndPowerGains(numCycles: number): void;
|
||||
processExperienceGains(numCycles: number): void;
|
||||
clash(won: boolean): void;
|
||||
canRecruitMember(): boolean;
|
||||
getRespectNeededToRecruitMember(): number;
|
||||
recruitMember(name: string): boolean;
|
||||
getWantedPenalty(): number;
|
||||
calculatePower(): number;
|
||||
killMember(member: GangMember): void;
|
||||
ascendMember(member: GangMember, workerScript: WorkerScript): void;
|
||||
getDiscount(): number;
|
||||
getAllTaskNames(): string[];
|
||||
getUpgradeCost(upg: GangMemberUpgrade): number;
|
||||
}
|
||||
20
src/Gang/ITaskParams.ts
Normal file
20
src/Gang/ITaskParams.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
export interface ITerritory {
|
||||
money: number;
|
||||
respect: number;
|
||||
wanted: number;
|
||||
}
|
||||
|
||||
export interface ITaskParams {
|
||||
baseRespect?: number;
|
||||
baseWanted?: number;
|
||||
baseMoney?: number;
|
||||
hackWeight?: number;
|
||||
strWeight?: number;
|
||||
defWeight?: number;
|
||||
dexWeight?: number;
|
||||
agiWeight?: number;
|
||||
chaWeight?: number;
|
||||
difficulty?: number;
|
||||
territory?: ITerritory;
|
||||
}
|
||||
24
src/Gang/data/Constants.ts
Normal file
24
src/Gang/data/Constants.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export const GangConstants: {
|
||||
GangRespectToReputationRatio: number;
|
||||
MaximumGangMembers: number;
|
||||
CyclesPerTerritoryAndPowerUpdate: number;
|
||||
AscensionMultiplierRatio: number;
|
||||
Names: string[];
|
||||
} = {
|
||||
// Respect is divided by this to get rep gain
|
||||
GangRespectToReputationRatio: 25,
|
||||
MaximumGangMembers: 12,
|
||||
CyclesPerTerritoryAndPowerUpdate: 100,
|
||||
// Portion of upgrade multiplier that is kept after ascending
|
||||
AscensionMultiplierRatio: .15,
|
||||
// Names of possible Gangs
|
||||
Names: [
|
||||
"Slum Snakes",
|
||||
"Tetrads",
|
||||
"The Syndicate",
|
||||
"The Dark Army",
|
||||
"Speakers for the Dead",
|
||||
"NiteSec",
|
||||
"The Black Hand",
|
||||
],
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ITaskParams } from "../ITaskParams";
|
||||
/* tslint:disable:max-line-length */
|
||||
|
||||
/**
|
||||
@@ -29,7 +30,7 @@ export interface IGangMemberTaskMetadata {
|
||||
* An object containing weighting parameters for the task. These parameters are used for
|
||||
* various calculations (respect gain, wanted gain, etc.)
|
||||
*/
|
||||
params?: any;
|
||||
params: ITaskParams;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,7 +255,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
|
||||
name: "Train Combat",
|
||||
params: {
|
||||
strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 25,
|
||||
difficulty: 5,
|
||||
difficulty: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -262,7 +263,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
|
||||
isCombat: true,
|
||||
isHacking: true,
|
||||
name: "Train Hacking",
|
||||
params: {hackWeight: 100, difficulty: 8},
|
||||
params: {hackWeight: 100, difficulty: 45},
|
||||
},
|
||||
{
|
||||
desc: "Assign this gang member to train their charisma",
|
||||
@@ -281,4 +282,4 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
|
||||
difficulty: 5,
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
@@ -1,12 +1,29 @@
|
||||
export interface IMults {
|
||||
hack?: number;
|
||||
str?: number;
|
||||
def?: number;
|
||||
dex?: number;
|
||||
agi?: number;
|
||||
cha?: number;
|
||||
}
|
||||
|
||||
export enum UpgradeType {
|
||||
Weapon = "w",
|
||||
Armor = "a",
|
||||
Vehicle = "v",
|
||||
Rootkit = "r",
|
||||
Augmentation = "g",
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the parameters that can be used to initialize and describe a GangMemberUpgrade
|
||||
* (defined in Gang.js)
|
||||
*/
|
||||
export interface IGangMemberUpgradeMetadata {
|
||||
cost: number;
|
||||
mults: any;
|
||||
mults: IMults;
|
||||
name: string;
|
||||
upgType: string;
|
||||
upgType: UpgradeType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -18,192 +35,192 @@ export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [
|
||||
cost: 1e6,
|
||||
mults: {str: 1.04, def: 1.04},
|
||||
name: "Baseball Bat",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 12e6,
|
||||
mults: {str: 1.08, def: 1.08, dex: 1.08},
|
||||
name: "Katana",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 25e6,
|
||||
mults: {str: 1.1, def: 1.1, dex: 1.1, agi: 1.1},
|
||||
name: "Glock 18C",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 50e6,
|
||||
mults: {str: 1.12, def: 1.1, agi: 1.1},
|
||||
name: "P90C",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 60e6,
|
||||
mults: {str: 1.2, def: 1.15},
|
||||
name: "Steyr AUG",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 100e6,
|
||||
mults: {str: 1.25, def: 1.2},
|
||||
name: "AK-47",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 150e6,
|
||||
mults: {str: 1.3, def: 1.25},
|
||||
name: "M15A10 Assault Rifle",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 225e6,
|
||||
mults: {str: 1.3, dex: 1.25, agi: 1.3},
|
||||
name: "AWM Sniper Rifle",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 2e6,
|
||||
mults: {def: 1.04},
|
||||
name: "Bulletproof Vest",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 5e6,
|
||||
mults: {def: 1.08},
|
||||
name: "Full Body Armor",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 25e6,
|
||||
mults: {def: 1.15, agi: 1.15},
|
||||
name: "Liquid Body Armor",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 40e6,
|
||||
mults: {def: 1.2},
|
||||
name: "Graphene Plating Armor",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 3e6,
|
||||
mults: {agi: 1.04, cha: 1.04},
|
||||
name: "Ford Flex V20",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 9e6,
|
||||
mults: {agi: 1.08, cha: 1.08},
|
||||
name: "ATX1070 Superbike",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 18e6,
|
||||
mults: {agi: 1.12, cha: 1.12},
|
||||
name: "Mercedes-Benz S9001",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 30e6,
|
||||
mults: {agi: 1.16, cha: 1.16},
|
||||
name: "White Ferrari",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 5e6,
|
||||
mults: {hack: 1.05},
|
||||
name: "NUKE Rootkit",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 25e6,
|
||||
mults: {hack: 1.1},
|
||||
name: "Soulstealer Rootkit",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 75e6,
|
||||
mults: {hack: 1.15},
|
||||
name: "Demon Rootkit",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 40e6,
|
||||
mults: {hack: 1.12},
|
||||
name: "Hmap Node",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 75e6,
|
||||
mults: {hack: 1.15},
|
||||
name: "Jack the Ripper",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 10e9,
|
||||
mults: {str: 1.3, dex: 1.3},
|
||||
name: "Bionic Arms",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 10e9,
|
||||
mults: {agi: 1.6},
|
||||
name: "Bionic Legs",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 15e9,
|
||||
mults: {str: 1.15, def: 1.15, dex: 1.15, agi: 1.15},
|
||||
name: "Bionic Spine",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 20e9,
|
||||
mults: {str: 1.4, def: 1.4},
|
||||
name: "BrachiBlades",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 12e9,
|
||||
mults: {str: 1.2, def: 1.2},
|
||||
name: "Nanofiber Weave",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 25e9,
|
||||
mults: {str: 1.5, agi: 1.5},
|
||||
name: "Synthetic Heart",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 15e9,
|
||||
mults: {str: 1.3, def: 1.3},
|
||||
name: "Synfibril Muscle",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 5e9,
|
||||
mults: {hack: 1.05},
|
||||
name: "BitWire",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 10e9,
|
||||
mults: {hack: 1.15},
|
||||
name: "Neuralstimulator",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 7.5e9,
|
||||
mults: {hack: 1.1},
|
||||
name: "DataJack",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 50e9,
|
||||
mults: {str: 1.7, def: 1.7},
|
||||
name: "Graphene Bone Lacings",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
];
|
||||
70
src/Gang/ui/AscensionPopup.tsx
Normal file
70
src/Gang/ui/AscensionPopup.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* React Component for the content of the popup before the player confirms the
|
||||
* ascension of a gang member.
|
||||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
popupId: string;
|
||||
onAscend: () => void;
|
||||
}
|
||||
|
||||
export function AscensionPopup(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function confirm(): void {
|
||||
props.onAscend();
|
||||
const res = props.gang.ascendMember(props.member);
|
||||
dialogBoxCreate(<p>
|
||||
You ascended {props.member.name}!<br />
|
||||
<br />
|
||||
Your gang lost {numeralWrapper.formatRespect(res.respect)} respect.<br />
|
||||
<br />
|
||||
{props.member.name} gained the following stat multipliers for ascending:<br />
|
||||
Hacking: x{numeralWrapper.format(res.hack, '0.000')}<br />
|
||||
Strength: x{numeralWrapper.format(res.str, '0.000')}<br />
|
||||
Defense: x{numeralWrapper.format(res.def, '0.000')}<br />
|
||||
Dexterity: x{numeralWrapper.format(res.dex, '0.000')}<br />
|
||||
Agility: x{numeralWrapper.format(res.agi, '0.000')}<br />
|
||||
Charisma: x{numeralWrapper.format(res.cha, '0.000')}<br />
|
||||
</p>);
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
const ascendBenefits = props.member.getAscensionResults();
|
||||
|
||||
return (<>
|
||||
<pre>
|
||||
Are you sure you want to ascend this member? They will lose all of<br />
|
||||
their non-Augmentation upgrades and their stats will reset back to 1.<br />
|
||||
<br />
|
||||
Furthermore, your gang will lose {numeralWrapper.formatRespect(props.member.earnedRespect)} respect<br />
|
||||
<br />
|
||||
In return, they will gain the following permanent boost to stat multipliers:<br />
|
||||
Hacking: x{numeralWrapper.format(ascendBenefits.hack, '0.000')}<br />
|
||||
Strength: x{numeralWrapper.format(ascendBenefits.str, '0.000')}<br />
|
||||
Defense: x{numeralWrapper.format(ascendBenefits.def, '0.000')}<br />
|
||||
Dexterity: x{numeralWrapper.format(ascendBenefits.dex, '0.000')}<br />
|
||||
Agility: x{numeralWrapper.format(ascendBenefits.agi, '0.000')}<br />
|
||||
Charisma: x{numeralWrapper.format(ascendBenefits.cha, '0.000')}<br />
|
||||
</pre>
|
||||
<button className="std-button" onClick={confirm}>Ascend</button>
|
||||
<button className="std-button" onClick={cancel}>Cancel</button>
|
||||
</>);
|
||||
}
|
||||
29
src/Gang/ui/BonusTime.tsx
Normal file
29
src/Gang/ui/BonusTime.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* React Component for displaying the bonus time remaining.
|
||||
*/
|
||||
import * as React from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
}
|
||||
|
||||
export function BonusTime(props: IProps): React.ReactElement {
|
||||
const CyclerPerSecond = 1000 / CONSTANTS._idleSpeed;
|
||||
if (props.gang.storedCycles / CyclerPerSecond*1000 <= 5000) return (<></>);
|
||||
const bonusMillis = props.gang.storedCycles / CyclerPerSecond * 1000;
|
||||
return (<>
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Bonus time: {convertTimeMsToTimeElapsedString(bonusMillis)}
|
||||
<span className="tooltiptext noselect">
|
||||
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.
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
</>);
|
||||
}
|
||||
22
src/Gang/ui/GangMemberAccordion.tsx
Normal file
22
src/Gang/ui/GangMemberAccordion.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* React Component for a gang member on the management subpage.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { Accordion } from "../../ui/React/Accordion";
|
||||
import { GangMemberAccordionContent } from "./GangMemberAccordionContent";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
member: GangMember;
|
||||
}
|
||||
|
||||
export function GangMemberAccordion(props: IProps): React.ReactElement {
|
||||
return <Accordion
|
||||
panelInitiallyOpened={true}
|
||||
headerContent={<>{props.member.name}</>}
|
||||
panelContent={<GangMemberAccordionContent
|
||||
gang={props.gang}
|
||||
member={props.member} />} />
|
||||
}
|
||||
36
src/Gang/ui/GangMemberAccordionContent.tsx
Normal file
36
src/Gang/ui/GangMemberAccordionContent.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* React Component for the content of the accordion of gang members on the
|
||||
* management subpage.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { GangMemberStats } from "./GangMemberStats";
|
||||
import { TaskSelector } from "./TaskSelector";
|
||||
import { TaskDescription } from "./TaskDescription";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
member: GangMember;
|
||||
}
|
||||
|
||||
export function GangMemberAccordionContent(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
return (<>
|
||||
<div className={"gang-member-info-div tooltip"}>
|
||||
<GangMemberStats
|
||||
onAscend={()=>setRerender(old => !old)}
|
||||
gang={props.gang}
|
||||
member={props.member} />
|
||||
</div>
|
||||
<div className={"gang-member-info-div"}>
|
||||
<TaskSelector
|
||||
onTaskChange={()=>setRerender(old => !old)}
|
||||
gang={props.gang}
|
||||
member={props.member} />
|
||||
</div>
|
||||
<div className={"gang-member-info-div"}>
|
||||
<TaskDescription member={props.member} />
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
58
src/Gang/ui/GangMemberList.tsx
Normal file
58
src/Gang/ui/GangMemberList.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* React Component for the list of gang members on the management subpage.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { GangMemberUpgradePopup } from "./GangMemberUpgradePopup";
|
||||
import { GangMemberAccordion } from "./GangMemberAccordion";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { RecruitButton } from "./RecruitButton";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function GangMemberList(props: IProps): React.ReactElement {
|
||||
const [filter, setFilter] = useState("");
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
function openUpgradePopup(): void {
|
||||
const popupId = `gang-upgrade-popup`;
|
||||
createPopup(popupId, GangMemberUpgradePopup, {
|
||||
gang: props.gang,
|
||||
player: props.player,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
function onFilterChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setFilter(event.target.value);
|
||||
}
|
||||
|
||||
const members = props.gang.members.filter((member: GangMember) => member.name.indexOf(filter) > -1 || member.task.indexOf(filter) > -1);
|
||||
|
||||
return (<>
|
||||
<RecruitButton
|
||||
onRecruit={() => setRerender(old => !old)}
|
||||
gang={props.gang} />
|
||||
<br />
|
||||
<input
|
||||
className="text-input noselect"
|
||||
placeholder="Filter gang member"
|
||||
style={{margin: "5px", padding: "5px"}}
|
||||
value={filter}
|
||||
onChange={onFilterChange} />
|
||||
<a
|
||||
className="a-link-button"
|
||||
style={{display: 'inline-block'}}
|
||||
onClick={openUpgradePopup}>Manage Equipment</a>
|
||||
<ul>
|
||||
{members.map((member: GangMember) => <li key={member.name}>
|
||||
<GangMemberAccordion gang={props.gang} member={member} />
|
||||
</li>)}
|
||||
</ul>
|
||||
</>);
|
||||
}
|
||||
77
src/Gang/ui/GangMemberStats.tsx
Normal file
77
src/Gang/ui/GangMemberStats.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* React Component for the first part of a gang member details.
|
||||
* Contains skills and exp.
|
||||
*/
|
||||
import React from "react";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { AscensionPopup } from "./AscensionPopup";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
onAscend: () => void;
|
||||
}
|
||||
|
||||
export function GangMemberStats(props: IProps): React.ReactElement {
|
||||
function ascend(): void {
|
||||
const popupId = `gang-management-ascend-member ${props.member.name}`;
|
||||
createPopup(popupId, AscensionPopup, {
|
||||
member: props.member,
|
||||
gang: props.gang,
|
||||
popupId: popupId,
|
||||
onAscend: props.onAscend,
|
||||
});
|
||||
}
|
||||
|
||||
function openAscensionHelp(): void {
|
||||
dialogBoxCreate(<>
|
||||
Ascending a Gang Member resets the member's progress and stats in
|
||||
exchange for a permanent boost to their stat multipliers.
|
||||
<br /><br />
|
||||
The additional stat multiplier that the Gang Member gains upon
|
||||
ascension is based on the amount of exp they have.
|
||||
<br /><br />
|
||||
Upon ascension, the member will lose all of its non-Augmentation
|
||||
Equipment and your gang will lose respect equal to the total respect
|
||||
earned by the member.
|
||||
</>);
|
||||
}
|
||||
|
||||
const asc = {
|
||||
hack: props.member.calculateAscensionMult(props.member.hack_asc_points),
|
||||
str: props.member.calculateAscensionMult(props.member.str_asc_points),
|
||||
def: props.member.calculateAscensionMult(props.member.def_asc_points),
|
||||
dex: props.member.calculateAscensionMult(props.member.dex_asc_points),
|
||||
agi: props.member.calculateAscensionMult(props.member.agi_asc_points),
|
||||
cha: props.member.calculateAscensionMult(props.member.cha_asc_points),
|
||||
};
|
||||
|
||||
return (<>
|
||||
<span className="tooltiptext smallfont">
|
||||
Hk: x{numeralWrapper.formatMultiplier(props.member.hack_mult * asc.hack)}(x{numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.hack)} Asc)<br />
|
||||
St: x{numeralWrapper.formatMultiplier(props.member.str_mult * asc.str)}(x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.str)} Asc)<br />
|
||||
Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * asc.def)}(x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.def)} Asc)<br />
|
||||
Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * asc.dex)}(x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.dex)} Asc)<br />
|
||||
Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * asc.agi)}(x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.agi)} Asc)<br />
|
||||
Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * asc.cha)}(x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.cha)} Asc)
|
||||
</span>
|
||||
<pre>
|
||||
Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)<br />
|
||||
Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)<br />
|
||||
Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)<br />
|
||||
Dexterity: {formatNumber(props.member.dex, 0)} ({numeralWrapper.formatExp(props.member.dex_exp)} exp)<br />
|
||||
Agility: {formatNumber(props.member.agi, 0)} ({numeralWrapper.formatExp(props.member.agi_exp)} exp)<br />
|
||||
Charisma: {formatNumber(props.member.cha, 0)} ({numeralWrapper.formatExp(props.member.cha_exp)} exp)<br />
|
||||
</pre>
|
||||
<br />
|
||||
{ props.member.canAscend() && <>
|
||||
<button className="accordion-button noselect" onClick={ascend}>Ascend</button>
|
||||
<div className="help-tip noselect" style={{marginTop: "5px"}} onClick={openAscensionHelp}>?</div>
|
||||
</>}
|
||||
</>);
|
||||
}
|
||||
149
src/Gang/ui/GangMemberUpgradePopup.tsx
Normal file
149
src/Gang/ui/GangMemberUpgradePopup.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* React Component for the popup that manages gang members upgrades
|
||||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { GangMemberUpgrades } from "../GangMemberUpgrades";
|
||||
import { GangMemberUpgrade } from "../GangMemberUpgrade";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { Gang } from "../Gang";
|
||||
import { UpgradeType } from "../data/upgrades";
|
||||
|
||||
interface IPanelProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
function filterUpgrades(list: string[], type: UpgradeType): GangMemberUpgrade[] {
|
||||
return Object.keys(GangMemberUpgrades).filter((upgName: string) => {
|
||||
const upg = GangMemberUpgrades[upgName];
|
||||
if (props.player.money.lt(props.gang.getUpgradeCost(upg)))
|
||||
return false;
|
||||
if(upg.type !== type) return false;
|
||||
if(list.includes(upgName)) return false;
|
||||
return true;
|
||||
}).map((upgName: string) => GangMemberUpgrades[upgName]);
|
||||
}
|
||||
const weaponUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Weapon);
|
||||
const armorUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Armor);
|
||||
const vehicleUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Vehicle);
|
||||
const rootkitUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Rootkit);
|
||||
const augUpgrades = filterUpgrades(props.member.augmentations, UpgradeType.Augmentation);
|
||||
|
||||
function purchasedUpgrade(upgName: string): React.ReactElement {
|
||||
const upg = GangMemberUpgrades[upgName]
|
||||
return (<div key={upgName} className="gang-owned-upgrade tooltip">
|
||||
{upg.name}
|
||||
<span className="tooltiptext" dangerouslySetInnerHTML={{__html: upg.desc}} />
|
||||
</div>);
|
||||
}
|
||||
|
||||
function upgradeButton(upg: GangMemberUpgrade, left = false): React.ReactElement {
|
||||
function onClick(): void {
|
||||
props.member.buyUpgrade(upg, props.player, props.gang);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
return (<a key={upg.name} className="a-link-button tooltip" style={{margin:"2px", padding:"2px", display:"block", fontSize:"11px"}} onClick={onClick}>
|
||||
{upg.name} - {Money(props.gang.getUpgradeCost(upg))}
|
||||
<span className={left?"tooltiptextleft":"tooltiptext"} dangerouslySetInnerHTML={{__html: upg.desc}} />
|
||||
</a>);
|
||||
}
|
||||
|
||||
const asc = {
|
||||
hack: props.member.calculateAscensionMult(props.member.hack_asc_points),
|
||||
str: props.member.calculateAscensionMult(props.member.str_asc_points),
|
||||
def: props.member.calculateAscensionMult(props.member.def_asc_points),
|
||||
dex: props.member.calculateAscensionMult(props.member.dex_asc_points),
|
||||
agi: props.member.calculateAscensionMult(props.member.agi_asc_points),
|
||||
cha: props.member.calculateAscensionMult(props.member.cha_asc_points),
|
||||
};
|
||||
return (<div style={{border: '1px solid white'}}>
|
||||
<h1>{props.member.name}({props.member.task})</h1>
|
||||
<pre style={{fontSize:"14px", display: "inline-block", width:"20%"}}>
|
||||
Hack: {props.member.hack} (x{formatNumber(props.member.hack_mult * asc.hack, 2)})<br />
|
||||
Str: {props.member.str} (x{formatNumber(props.member.str_mult * asc.str, 2)})<br />
|
||||
Def: {props.member.def} (x{formatNumber(props.member.def_mult * asc.def, 2)})<br />
|
||||
Dex: {props.member.dex} (x{formatNumber(props.member.dex_mult * asc.dex, 2)})<br />
|
||||
Agi: {props.member.agi} (x{formatNumber(props.member.agi_mult * asc.agi, 2)})<br />
|
||||
Cha: {props.member.cha} (x{formatNumber(props.member.cha_mult * asc.cha, 2)})
|
||||
</pre>
|
||||
<div className="gang-owned-upgrades-div noselect">
|
||||
Purchased Upgrades: {props.member.upgrades.map((upg: string) => purchasedUpgrade(upg))}
|
||||
{props.member.augmentations.map((upg: string) => purchasedUpgrade(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Weapons</h2>
|
||||
{weaponUpgrades.map(upg => upgradeButton(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Armor</h2>
|
||||
{armorUpgrades.map(upg => upgradeButton(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Vehicles</h2>
|
||||
{vehicleUpgrades.map(upg => upgradeButton(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Rootkits</h2>
|
||||
{rootkitUpgrades.map(upg => upgradeButton(upg, true))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Augmentations</h2>
|
||||
{augUpgrades.map(upg => upgradeButton(upg, true))}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
popupId: string;
|
||||
}
|
||||
|
||||
export function GangMemberUpgradePopup(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
function closePopup(this: Window, ev: KeyboardEvent): void {
|
||||
if(ev.keyCode !== 27) return;
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener<'keydown'>('keydown', closePopup);
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
window.removeEventListener<'keydown'>('keydown', closePopup);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (<>
|
||||
<input
|
||||
className="text-input noselect"
|
||||
value={filter}
|
||||
placeholder="Filter gang member"
|
||||
onChange={event => setFilter(event.target.value)} />
|
||||
<p className="tooltip" style={{marginLeft: '6px', display: 'inline-block'}}>
|
||||
Discount: -{numeralWrapper.formatPercentage(1 - 1 / props.gang.getDiscount())}
|
||||
<span className="tooltiptext noselect">
|
||||
You get a discount on equipment and upgrades based on your
|
||||
gang's respect and power. More respect and power leads to more
|
||||
discounts.
|
||||
</span>
|
||||
</p>
|
||||
{props.gang.members.map((member: GangMember) => <GangMemberUpgradePanel
|
||||
key={member.name}
|
||||
player={props.player}
|
||||
gang={props.gang}
|
||||
member={member} />)
|
||||
}
|
||||
</>);
|
||||
}
|
||||
72
src/Gang/ui/GangStats.tsx
Normal file
72
src/Gang/ui/GangStats.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* React Component for the stats related to the gang, like total respect and
|
||||
* money per second.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { Gang } from "../Gang";
|
||||
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { MoneyRate } from "../../ui/React/MoneyRate";
|
||||
import { Reputation } from "../../ui/React/Reputation";
|
||||
import { AllGangs } from "../AllGangs";
|
||||
import { BonusTime } from "./BonusTime";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
}
|
||||
|
||||
export function GangStats(props: IProps): React.ReactElement {
|
||||
const territoryMult = AllGangs[props.gang.facName].territory * 100;
|
||||
let territoryStr;
|
||||
if (territoryMult <= 0) {
|
||||
territoryStr = formatNumber(0, 2);
|
||||
} else if (territoryMult >= 100) {
|
||||
territoryStr = formatNumber(100, 2);
|
||||
} else {
|
||||
territoryStr = formatNumber(territoryMult, 2);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Respect: {numeralWrapper.formatRespect(props.gang.respect)} ({numeralWrapper.formatRespect(5*props.gang.respectGainRate)} / sec)
|
||||
<span className="tooltiptext">
|
||||
Represents the amount of respect your gang has from other gangs and criminal organizations. Your respect affects the amount of money your gang members will earn, and also determines how much reputation you are earning with your gang's corresponding Faction.
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Wanted Level: {numeralWrapper.formatWanted(props.gang.wanted)} ({numeralWrapper.formatWanted(5*props.gang.wantedGainRate)} / sec)
|
||||
<span className="tooltiptext">
|
||||
Represents how much the gang is wanted by law enforcement. The higher your gang's wanted level, the harder it will be for your gang members to make money and earn respect. Note that the minimum wanted level is 1.
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Wanted Level Penalty: -{formatNumber((1 - props.gang.getWantedPenalty()) * 100, 2)}%
|
||||
<span className="tooltiptext">
|
||||
Penalty for respect and money gain rates due to Wanted Level
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<div>
|
||||
<p style={{display: "inline-block"}}>
|
||||
Money gain rate: {MoneyRate(5 * props.gang.moneyGainRate)}
|
||||
</p>
|
||||
</div>
|
||||
<br />
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Territory: {territoryStr}%
|
||||
<span className="tooltiptext">
|
||||
The percentage of total territory your Gang controls
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<p style={{display: "inline-block"}}>
|
||||
Faction reputation: {Reputation(Factions[props.gang.facName].playerReputation)}
|
||||
</p>
|
||||
<br />
|
||||
<BonusTime gang={props.gang} />
|
||||
</>);
|
||||
}
|
||||
46
src/Gang/ui/ManagementSubpage.tsx
Normal file
46
src/Gang/ui/ManagementSubpage.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* React Component for the subpage that manages gang members, the main page.
|
||||
*/
|
||||
import React from "react";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { GangStats } from "./GangStats";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMemberList } from "./GangMemberList";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function ManagementSubpage(props: IProps): React.ReactElement {
|
||||
return (<div style={{display: 'block'}}>
|
||||
<p className="noselect" style={{width: "70%"}}>
|
||||
This page is used to manage your gang members and get an overview of
|
||||
your gang's stats.
|
||||
<br />
|
||||
<br />
|
||||
If a gang member is not earning much money or respect, the task that
|
||||
you have assigned to that member might be too difficult. Consider
|
||||
training that member's stats or choosing an easier task. The tasks
|
||||
closer to the top of the dropdown list are generally easier.
|
||||
Alternatively, the gang member's low production might be due to the
|
||||
fact that your wanted level is too high. Consider assigning a few
|
||||
members to the '{props.gang.isHackingGang?"Ethical Hacking":"Vigilante Justice"}'
|
||||
task to lower your wanted level.
|
||||
<br />
|
||||
<br />
|
||||
Installing Augmentations does NOT reset your progress with your
|
||||
Gang. Furthermore, after installing Augmentations, you will
|
||||
automatically be a member of whatever Faction you created your gang
|
||||
with.
|
||||
<br />
|
||||
<br />
|
||||
You can also manage your gang programmatically through Netscript
|
||||
using the Gang API
|
||||
</p>
|
||||
<br />
|
||||
<GangStats gang={props.gang} />
|
||||
<br />
|
||||
<GangMemberList gang={props.gang} player={props.player} />
|
||||
</div>);
|
||||
}
|
||||
50
src/Gang/ui/RecruitButton.tsx
Normal file
50
src/Gang/ui/RecruitButton.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* React Component for the recruitment button and text on the gang main page.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { RecruitPopup } from "./RecruitPopup";
|
||||
import { GangConstants } from "../data/Constants";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
onRecruit: () => void;
|
||||
}
|
||||
|
||||
export function RecruitButton(props: IProps): React.ReactElement {
|
||||
if (props.gang.members.length >= GangConstants.MaximumGangMembers) {
|
||||
return (<></>);
|
||||
}
|
||||
|
||||
if (!props.gang.canRecruitMember()) {
|
||||
const respect = props.gang.getRespectNeededToRecruitMember();
|
||||
return (<>
|
||||
<a className="a-link-button-inactive"
|
||||
style={{display: 'inline-block', margin: '10px'}}>
|
||||
Recruit Gang Member
|
||||
</a>
|
||||
<p style={{margin: '10px', color: 'red', display: 'inline-block'}}>
|
||||
{formatNumber(respect, 2)} respect needed to recruit next member
|
||||
</p>
|
||||
</>);
|
||||
}
|
||||
|
||||
function onClick(): void {
|
||||
const popupId = "recruit-gang-member-popup";
|
||||
createPopup(popupId, RecruitPopup, {
|
||||
gang: props.gang,
|
||||
popupId: popupId,
|
||||
onRecruit: props.onRecruit,
|
||||
});
|
||||
}
|
||||
|
||||
return (<>
|
||||
<a className="a-link-button"
|
||||
onClick={onClick}
|
||||
style={{display: 'inline-block', margin: '10px'}}>
|
||||
Recruit Gang Member
|
||||
</a>
|
||||
</>);
|
||||
}
|
||||
63
src/Gang/ui/RecruitPopup.tsx
Normal file
63
src/Gang/ui/RecruitPopup.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* React Component for the popup used to recruit new gang members.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
|
||||
interface IRecruitPopupProps {
|
||||
gang: Gang;
|
||||
popupId: string;
|
||||
onRecruit: () => void;
|
||||
}
|
||||
|
||||
export function RecruitPopup(props: IRecruitPopupProps): React.ReactElement {
|
||||
const [name, setName] = useState("");
|
||||
|
||||
function recruit(): void {
|
||||
if (name === "") {
|
||||
dialogBoxCreate("You must enter a name for your Gang member!");
|
||||
return;
|
||||
}
|
||||
if (!props.gang.canRecruitMember()) {
|
||||
dialogBoxCreate("You cannot recruit another Gang member!");
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, the only way this can fail is if you already
|
||||
// have a gang member with the same name
|
||||
if (!props.gang.recruitMember(name)) {
|
||||
dialogBoxCreate("You already have a gang member with this name!");
|
||||
return;
|
||||
}
|
||||
|
||||
props.onRecruit();
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
function onKeyUp(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if(event.keyCode === 13) recruit();
|
||||
if(event.keyCode === 27) cancel();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setName(event.target.value);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p className="noselect">Enter a name for your new Gang member:</p><br />
|
||||
<input autoFocus
|
||||
onKeyUp={onKeyUp}
|
||||
onChange={onChange}
|
||||
className="text-input noselect"
|
||||
type="text"
|
||||
placeholder="unique name" />
|
||||
<a className="std-button" onClick={recruit}>Recruit Gang Member</a>
|
||||
<a className="std-button" onClick={cancel}>Cancel</a>
|
||||
</>);
|
||||
}
|
||||
49
src/Gang/ui/Root.tsx
Normal file
49
src/Gang/ui/Root.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* React Component for all the gang stuff.
|
||||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { ManagementSubpage } from "./ManagementSubpage";
|
||||
import { TerritorySubpage } from "./TerritorySubpage";
|
||||
import { IEngine } from "../../IEngine";
|
||||
import { Gang } from "../Gang";
|
||||
import { displayFactionContent } from "../../Faction/FactionHelpers";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
engine: IEngine;
|
||||
}
|
||||
|
||||
export function Root(props: IProps): React.ReactElement {
|
||||
const [management, setManagement] = useState(true);
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function back(): void {
|
||||
props.engine.loadFactionContent();
|
||||
displayFactionContent(props.gang.facName);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<a className="a-link-button" style={{display: "inline-block"}}
|
||||
onClick={back}>Back</a>
|
||||
<a className={management?"a-link-button-inactive":"a-link-button"}
|
||||
style={{display: "inline-block"}}
|
||||
onClick={() => setManagement(true)}>
|
||||
Gang Management
|
||||
</a>
|
||||
<a className={!management?"a-link-button-inactive":"a-link-button"}
|
||||
style={{display: "inline-block"}}
|
||||
onClick={() => setManagement(false)}>
|
||||
Gang Territory
|
||||
</a>
|
||||
{management ?
|
||||
<ManagementSubpage gang={props.gang} player={props.player} /> :
|
||||
<TerritorySubpage gang={props.gang} />}
|
||||
</>);
|
||||
}
|
||||
18
src/Gang/ui/TaskDescription.tsx
Normal file
18
src/Gang/ui/TaskDescription.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* React Component for left side of the gang member accordion, contains the
|
||||
* description of the task that member is currently doing.
|
||||
*/
|
||||
import React from "react";
|
||||
import { GangMemberTasks } from "../GangMemberTasks";
|
||||
import { GangMember } from "../GangMember";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
}
|
||||
|
||||
export function TaskDescription(props: IProps): React.ReactElement {
|
||||
const task = GangMemberTasks[props.member.task];
|
||||
const desc = task ? task.desc: GangMemberTasks["Unassigned"].desc;
|
||||
|
||||
return (<p className="inline noselect" dangerouslySetInnerHTML={{__html: desc}} />);
|
||||
}
|
||||
47
src/Gang/ui/TaskSelector.tsx
Normal file
47
src/Gang/ui/TaskSelector.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* React Component for the middle part of the gang member accordion. Contains
|
||||
* the task selector as well as some stats.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { StatsTable } from "../../ui/React/StatsTable";
|
||||
import { MoneyRate } from "../../ui/React/MoneyRate";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
onTaskChange: () => void;
|
||||
}
|
||||
|
||||
export function TaskSelector(props: IProps): React.ReactElement {
|
||||
const [currentTask, setCurrentTask] = useState(props.member.task);
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLSelectElement>): void {
|
||||
const task = event.target.value;
|
||||
props.member.assignToTask(task);
|
||||
setCurrentTask(task);
|
||||
props.onTaskChange();
|
||||
}
|
||||
|
||||
const tasks = props.gang.getAllTaskNames();
|
||||
|
||||
const data = [
|
||||
[`Money:`, MoneyRate(5*props.member.calculateMoneyGain(props.gang))],
|
||||
[`Respect:`, `${numeralWrapper.formatRespect(5*props.member.calculateRespectGain(props.gang))} / sec`],
|
||||
[`Wanted Level:`, `${numeralWrapper.formatWanted(5*props.member.calculateWantedLevelGain(props.gang))} / sec`],
|
||||
[`Total Respect:`, `${numeralWrapper.formatRespect(props.member.earnedRespect)}`],
|
||||
];
|
||||
|
||||
return (<>
|
||||
<select
|
||||
onChange={onChange}
|
||||
className="dropdown noselect"
|
||||
value={currentTask}>
|
||||
<option key={0} value={"---"}>---</option>
|
||||
{tasks.map((task: string, i: number) => <option key={i+1} value={task}>{task}</option>)}
|
||||
</select>
|
||||
<div>{StatsTable(data)}</div>
|
||||
</>);
|
||||
}
|
||||
130
src/Gang/ui/TerritorySubpage.tsx
Normal file
130
src/Gang/ui/TerritorySubpage.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* React Component for the territory subpage.
|
||||
*/
|
||||
import React from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { AllGangs } from "../AllGangs";
|
||||
import { Gang } from "../Gang";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
}
|
||||
|
||||
export function TerritorySubpage(props: IProps): React.ReactElement {
|
||||
function openWarfareHelp(): void {
|
||||
dialogBoxCreate("This percentage represents the chance you have of " +
|
||||
"'clashing' with with another gang. If you do not " +
|
||||
"wish to gain/lose territory, then keep this " +
|
||||
"percentage at 0% by not engaging in territory warfare.");
|
||||
}
|
||||
|
||||
function formatTerritory(n: number): string {
|
||||
const v = n * 100;
|
||||
if (v <= 0) {
|
||||
return formatNumber(0, 2);
|
||||
} else if (v >= 100) {
|
||||
return formatNumber(100, 2);
|
||||
} else {
|
||||
return formatNumber(v, 2);
|
||||
}
|
||||
}
|
||||
|
||||
const playerPower = AllGangs[props.gang.facName].power;
|
||||
function otherGangTerritory(name: string): React.ReactElement {
|
||||
const power = AllGangs[name].power
|
||||
const clashVictoryChance = playerPower / (power + playerPower);
|
||||
return (<span key={name}>
|
||||
<u>{name}</u><br />
|
||||
Power: {formatNumber(power, 6)}<br />
|
||||
Territory: {formatTerritory(AllGangs[name].territory)}%<br />
|
||||
Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}<br />
|
||||
<br />
|
||||
</span>);
|
||||
}
|
||||
|
||||
const gangNames = Object.keys(AllGangs).filter(g => g != props.gang.facName);
|
||||
|
||||
return (<div style={{width: '70%'}}>
|
||||
<p className="noselect">
|
||||
This page shows how much territory your Gang controls. This
|
||||
statistic is listed as a percentage, which represents how much of
|
||||
the total territory you control.
|
||||
<br />
|
||||
<br />
|
||||
Every ~20 seconds, your gang has a chance to 'clash' with other
|
||||
gangs. Your chance to win a clash depends on your gang's power,
|
||||
which is listed in the display below. Your gang's power slowly
|
||||
accumulates over time. The accumulation rate is determined by the
|
||||
stats of all Gang members you have assigned to the 'Territory
|
||||
Warfare' task. Gang members that are not assigned to this task do
|
||||
not contribute to your gang's power. Your gang also loses a small
|
||||
amount of power whenever you lose a clash.
|
||||
<br />
|
||||
<br />
|
||||
NOTE: Gang members assigned to 'Territory Warfare' can be killed
|
||||
during clashes. This can happen regardless of whether you win or
|
||||
lose the clash. A gang member being killed results in both respect
|
||||
and power loss for your gang.
|
||||
<br />
|
||||
<br />
|
||||
The amount of territory you have affects all aspects of your Gang
|
||||
members' production, including money, respect, and wanted level. It
|
||||
is very beneficial to have high territory control.
|
||||
<br />
|
||||
<br />
|
||||
</p>
|
||||
<input
|
||||
checked={props.gang.territoryWarfareEngaged}
|
||||
id="warfare"
|
||||
type="checkbox"
|
||||
style={{display: "inline-block", margin: "2px"}}
|
||||
onChange={(event)=> props.gang.territoryWarfareEngaged = event.target.checked}/>
|
||||
<label
|
||||
htmlFor="warfare"
|
||||
className="tooltip noselect"
|
||||
style={{color: "white", display: 'inline-block'}}>
|
||||
Engage in Territory Warfare
|
||||
<span className="tooltiptext" style={{display: "inline-block"}}>
|
||||
Engaging in Territory Warfare sets your clash chance to 100%.
|
||||
Disengaging will cause your clash chance to gradually decrease
|
||||
until it reaches 0%.
|
||||
</span>
|
||||
</label>
|
||||
<br />
|
||||
<p style={{display: 'inline-block'}}>
|
||||
Territory Clash Chance: {numeralWrapper.formatPercentage(props.gang.territoryClashChance, 3)}
|
||||
</p>
|
||||
<div
|
||||
className="help-tip noselect"
|
||||
style={{display: "inline-block"}}
|
||||
onClick={openWarfareHelp}>?</div>
|
||||
<br />
|
||||
|
||||
<input
|
||||
checked={props.gang.notifyMemberDeath}
|
||||
id="notify"
|
||||
type="checkbox"
|
||||
style={{display: "inline-block", margin: "2px"}}
|
||||
onChange={(event)=> props.gang.notifyMemberDeath = event.target.checked}/>
|
||||
<label htmlFor="warfare" className="tooltip noselect" style={{color: "white", display: 'inline-block'}}>
|
||||
Notify about Gang Member Deaths
|
||||
<span className="tooltiptext" style={{display: "inline-block"}}>
|
||||
If this is enabled, then you will receive a pop-up notifying you
|
||||
whenever one of your Gang Members dies in a territory clash.
|
||||
</span>
|
||||
</label>
|
||||
<br />
|
||||
<fieldset style={{display: "block", margin: "6px"}}>
|
||||
<p>
|
||||
<b><u>{props.gang.facName}</u></b><br />
|
||||
Power: {formatNumber(AllGangs[props.gang.facName].power, 6)}<br />
|
||||
Territory: {formatTerritory(AllGangs[props.gang.facName].territory)}%<br />
|
||||
<br />
|
||||
{gangNames.map(name => otherGangTerritory(name))}
|
||||
</p>
|
||||
</fieldset>
|
||||
</div>);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user