Compare commits

..

92 Commits

Author SHA1 Message Date
Olivier Gagnon
6d2b8b4f6f v0.52.8 2021-08-23 02:09:49 -04:00
Olivier Gagnon
b148b2f0b5 logbox close on escape now 2021-08-23 01:15:20 -04:00
Olivier Gagnon
0b3c114cd0 Fix monaco jumping to end of file. 2021-08-22 23:57:00 -04:00
Olivier Gagnon
e0d631f8b3 trying to fix the jumping bug 2021-08-22 23:46:30 -04:00
Olivier Gagnon
d66e36b637 Fixed Script Editor not loading the same file after manually clicking it 2021-08-22 01:30:28 -04:00
Olivier Gagnon
a564957092 v0.52.7 2021-08-21 14:00:28 -04:00
Olivier Gagnon
4b8e63f342 Fix a few other bugs 2021-08-21 11:30:31 -04:00
Olivier Gagnon
4de20f8cce Made logbox drag a little smoother. 2021-08-21 02:31:37 -04:00
Olivier Gagnon
4b38d296a8 Fix corp industry wrong initial value. 2021-08-21 02:10:58 -04:00
Olivier Gagnon
6561413137 Fix Corp research popup box appearing behind one another. 2021-08-21 02:06:48 -04:00
Olivier Gagnon
b67c03ff8a hotfix broken editor shortcuts 2021-08-21 01:54:39 -04:00
Olivier Gagnon
ee5a70901b hotfix logbox width 2021-08-21 00:58:24 -04:00
Olivier Gagnon
63b2c77907 hotfix the tutorial 2021-08-21 00:51:07 -04:00
Olivier Gagnon
474befa091 v0.52.6 2021-08-21 00:31:42 -04:00
hydroflame
cd1c1ce145 Merge pull request #1067 from danielyxie/monaco
Monaco
2021-08-21 00:17:58 -04:00
Olivier Gagnon
5aa24f22c4 final changes for monac 2021-08-21 00:17:26 -04:00
Olivier Gagnon
f02c6443cc ok figured out how to make the javascript autocomplete. 2021-08-20 17:14:20 -04:00
Olivier Gagnon
4497143785 basic autocomplete working 2021-08-20 16:11:49 -04:00
Olivier Gagnon
0b3c48827b Ok we can load a thing but it has no effect. 2021-08-20 15:46:59 -04:00
Olivier Gagnon
86de11e794 link for monaco 2021-08-20 15:46:59 -04:00
Olivier Gagnon
fb87385704 Added function definition for netscritp in typescript 2021-08-20 15:46:59 -04:00
Olivier Gagnon
b1caea796a another link 2021-08-20 15:46:59 -04:00
Olivier Gagnon
2dfd19c9e0 rename, kinda add option for tabs vs space 2021-08-20 15:46:59 -04:00
Olivier Gagnon
0e24020796 Move monaco UI back where it belongs ish 2021-08-20 15:46:59 -04:00
Olivier Gagnon
ed62a3ebc2 deleted ace and monaco 2021-08-20 15:46:59 -04:00
Olivier Gagnon
258716388e focus works 2021-08-20 15:46:59 -04:00
Olivier Gagnon
73ec97db87 first pass at monaco. 2021-08-20 15:46:58 -04:00
Olivier Gagnon
567c5dc230 lint 2021-08-20 15:45:21 -04:00
Olivier Gagnon
980665b77c Fix job bug 2021-08-20 15:41:15 -04:00
Olivier Gagnon
dcddc0c2d5 fix a few things 2021-08-20 14:39:24 -04:00
Olivier Gagnon
6e1100750e script log boxes can now be dragged around and multiple of them can be on screen at once. 2021-08-19 22:22:21 -04:00
Olivier Gagnon
df457a0c6e v0.52.5 2021-08-19 16:37:59 -04:00
Olivier Gagnon
ee3530d9b9 hotfix some blade netscript functions not working 2021-08-19 11:04:01 -04:00
Olivier Gagnon
1a1a43c1ce v0.52.4 2021-08-19 01:45:26 -04:00
Olivier Gagnon
d6b349b6ff dialogBoxCreate now uses the same logic as other popups, now all popup can be dismissed with escape. 2021-08-18 00:51:51 -04:00
Olivier Gagnon
5c92360310 convert a few variables to const. 2021-08-18 00:08:23 -04:00
hydroflame
1fbb971d6f Merge pull request #1049 from danielyxie/react-blade
React blade
2021-08-17 23:54:52 -04:00
Olivier Gagnon
fa78b3f421 Fix React list without keys, fix int miscalculation of blade skills. 2021-08-17 23:52:18 -04:00
Olivier Gagnon
9af9bf58b6 fix final bugs 2021-08-17 23:28:40 -04:00
Olivier Gagnon
99afb156fa Refactoring mostly done, still a few bugs and test to do. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
8d550157bc Blade is fully converted to React but now it needs refactoring. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
4865563f26 Almost done converting blade to react. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
cc8de58cff More converting blade to react. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
58ada6d128 converting the giant Bladeburner object. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
ae6f95b59a The blade UI is fully converted to React, the business logic is left to do. 2021-08-17 23:28:40 -04:00
Olivier Gagnon
99d4f17cdb work on blade to react 2021-08-17 23:28:40 -04:00
Olivier Gagnon
33f0efd49c converting more blade to react 2021-08-17 23:28:40 -04:00
Olivier Gagnon
988ca37764 converting more blade to react/ts 2021-08-17 23:28:40 -04:00
Olivier Gagnon
0e9d7450c9 Converting bladeburner to react 2021-08-17 23:28:40 -04:00
Olivier Gagnon
78cd319c21 hotfix 0 territory being softlocked. 2021-08-17 17:47:22 -04:00
Olivier Gagnon
7367167019 hotfix blocked in Gang 2021-08-17 17:13:32 -04:00
Olivier Gagnon
392f164f8e Hotfix weird bladeburner ui bug 2021-08-15 17:14:05 -04:00
Olivier Gagnon
e4b2a6853d v0.52.3 2021-08-15 16:23:54 -04:00
Olivier Gagnon
697a8119b0 update Changelog. 2021-08-15 16:20:34 -04:00
Olivier Gagnon
b31b3dc735 Factions list screen converted to React. 2021-08-15 16:20:10 -04:00
Olivier Gagnon
5848fa53b7 Merge branch 'dev' of github.com:danielyxie/bitburner into dev 2021-08-15 15:24:23 -04:00
Olivier Gagnon
539b206cb5 update patch notes. 2021-08-15 15:23:39 -04:00
hydroflame
0f92890f0c Merge pull request #1045 from danielyxie/gang2
Gang rework
2021-08-15 15:01:27 -04:00
Olivier Gagnon
056f0213dd Fixed an issue where a faction could be joined twice. 2021-08-15 14:31:38 -04:00
Olivier Gagnon
41871de26c More adjustments to gang rework 2021-08-15 14:09:58 -04:00
Olivier Gagnon
5803ddc613 popup now all can be dismissed by clicking outside the window and have grey background 2021-08-15 12:16:16 -04:00
Olivier Gagnon
99263309ba Fix gaining too much asc pts, reduce reputation gain in gangs. 2021-08-15 12:14:56 -04:00
Olivier Gagnon
8a78ee4cf6 build dev for beta branch 2021-08-15 12:11:18 -04:00
Olivier Gagnon
af46324c6d Changed the ascension mechanic 2021-08-15 12:09:44 -04:00
Olivier Gagnon
42aa6525a6 the gang UI has a force update on ascension. 2021-08-15 12:09:44 -04:00
Olivier Gagnon
05b1b55e9a bit more code cleanups 2021-08-15 12:09:44 -04:00
Olivier Gagnon
febf0835c2 tweaks to how the gang ui is loaded, making it more like stock market 2021-08-15 12:09:44 -04:00
Olivier Gagnon
94ea0d253c Removed all DOM id 2021-08-15 12:09:44 -04:00
Olivier Gagnon
31cf02e8e4 some comments 2021-08-15 12:09:44 -04:00
Olivier Gagnon
97fdf7cb7f Format/cleanup of Gang UI 2021-08-15 12:09:44 -04:00
Olivier Gagnon
2c7fbc03cf Remove all uses of any in Gang, Train Combat and Train Hacking are now the best exp gains, gang equipment and augs now give exp boosts 2021-08-15 12:09:43 -04:00
Olivier Gagnon
07cca48a17 converted everything to ts 2021-08-15 12:09:43 -04:00
Olivier Gagnon
43d0fcb9f9 little cleanup 2021-08-15 12:09:43 -04:00
Olivier Gagnon
99b8dfa0c1 Converted GangMember to tsx, only Gang left 2021-08-15 12:09:43 -04:00
Olivier Gagnon
4cdd65e96c more gang react conversion 2021-08-15 12:09:43 -04:00
Olivier Gagnon
9e345b1375 Mostly done converting Gang UI to React 2021-08-15 12:09:43 -04:00
Olivier Gagnon
9466017906 modifying gang stats 2021-08-15 12:09:43 -04:00
Olivier Gagnon
9c24f1325f Gang member accordion done 2021-08-15 12:09:43 -04:00
Olivier Gagnon
26401fbb93 more panels! 2021-08-15 12:09:43 -04:00
Olivier Gagnon
69dfbb6673 panel 2 done 2021-08-15 12:09:43 -04:00
Olivier Gagnon
5863797b03 minor refactor 2021-08-15 12:09:43 -04:00
Olivier Gagnon
25f546c691 part 1 of converting gang to react 2021-08-15 12:09:43 -04:00
Olivier Gagnon
67e5e413e4 v0.52.2 2021-08-15 02:14:07 -04:00
Olivier Gagnon
796d91835c Simplify all the augmentation effects. 2021-08-15 00:49:19 -04:00
Olivier Gagnon
be8d56ced9 rewrite augmentation stats description so it's autogenerated and consistent with the actual effects. 2021-08-15 00:24:31 -04:00
hydroflame
0671c48c86 Merge pull request #1029 from Kwazygloo/patch-1
Unique  Aevum Augment
2021-08-14 14:28:37 -04:00
Olivier Gagnon
94388ba6af hotfix revert tutorial instructing the player to make a script on n00dles 2021-08-11 01:05:11 -04:00
Olivier Gagnon
306fe97ed8 rebuild with the version inside the game correctly udpated 2021-08-10 21:09:37 -04:00
Olivier Gagnon
c9b47ac3a6 Update changelog. 2021-08-10 21:03:40 -04:00
Kwazygloo
1aa141b87e Update AugmentationHelpers.jsx 2021-07-03 11:13:45 -04:00
Kwazygloo
abe204109e Update AugmentationHelpers.jsx 2021-06-18 16:11:12 -04:00
Kwazygloo
434dd2b58d Update AugmentationHelpers.jsx 2021-06-18 12:09:56 -04:00
164 changed files with 8279 additions and 16910 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,2 @@
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([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

File diff suppressed because it is too large Load Diff

77
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

5536
dist/vendor.css vendored

File diff suppressed because one or more lines are too long

View File

@@ -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
-------------------

View File

@@ -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.

View File

@@ -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|::

View File

@@ -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:

View File

@@ -12,4 +12,4 @@ getActionCurrentLevel() Netscript Function
.. code-block:: javascript
bladeburner.getActionCountRemaining("Contracts", "Tracking"); // returns: 7
bladeburner.getActionCurrentLevel("Contracts", "Tracking"); // returns: 9

View File

@@ -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]

View File

@@ -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.

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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",

View File

@@ -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";

View File

@@ -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 " +

File diff suppressed because it is too large Load Diff

View File

@@ -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:

View 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;

File diff suppressed because it is too large Load Diff

View File

@@ -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));

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
export interface IActionIdentifier {
name: string;
type: string;
type: number;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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,
});
};

View File

@@ -1,4 +1,5 @@
export const ConsoleHelpText: {
[key: string]: string[];
helpList: string[];
automate: string[];
clear: string[];

View 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>

View 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>
</>);
}

View 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>
</>);
}

View 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>,
)}
</>);
}

View 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} />
</>);
}

View File

@@ -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>)
}

View 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>);
}

View 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}/>
</>);
}

View 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>,
)}
</>);
}

View 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} />
</>);
}

View 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>
</>);
}

View 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>,
)}
</>);
}

View 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} />
</>);
}

View 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}/>
</>);
}

View 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>,
)}
</>);
}

View 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} />
</>);
}

View 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>);
}

View 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}} />
</>);
}

View 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>,
)}
</>);
}

View 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]) {
}
}
}
*/

View 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 />
</>);
}

View 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)}%</>);
}

View 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>
</>);
}

View 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>})}
</>);
}

View File

@@ -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);
});
}

View File

@@ -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.
`,
/*
*/
}

View File

@@ -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}`);
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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]]);
}
}

View 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>
</>);
}

View File

@@ -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
View File

@@ -0,0 +1 @@
export declare function parseFconfSettings(config: string): void;

File diff suppressed because it is too large Load Diff

76
src/Gang/AllGangs.ts Normal file
View 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
View 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
View 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;

View 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};
}
}

View 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);
});
})();

View 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 "";
}
}
}

View 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
View 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);
}

View 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
View 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
View 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;
}

View 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",
],
};

View File

@@ -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,
},
},
];
];

View File

@@ -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,
},
];

View 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
View 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 />
</>);
}

View 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} />} />
}

View 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>
</>);
}

View 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>
</>);
}

View 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>
</>}
</>);
}

View 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
View 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} />
</>);
}

View 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>);
}

View 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>
</>);
}

View 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
View 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} />}
</>);
}

View 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}} />);
}

View 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>
</>);
}

View 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