mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 22:38:34 +02:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df457a0c6e | ||
|
|
ee3530d9b9 | ||
|
|
1a1a43c1ce | ||
|
|
d6b349b6ff | ||
|
|
5c92360310 | ||
|
|
1fbb971d6f | ||
|
|
fa78b3f421 | ||
|
|
9af9bf58b6 | ||
|
|
99afb156fa | ||
|
|
8d550157bc | ||
|
|
4865563f26 | ||
|
|
cc8de58cff | ||
|
|
58ada6d128 | ||
|
|
ae6f95b59a | ||
|
|
99d4f17cdb | ||
|
|
33f0efd49c | ||
|
|
988ca37764 | ||
|
|
0e9d7450c9 | ||
|
|
78cd319c21 | ||
|
|
7367167019 | ||
|
|
392f164f8e | ||
|
|
e4b2a6853d | ||
|
|
697a8119b0 | ||
|
|
b31b3dc735 | ||
|
|
5848fa53b7 | ||
|
|
539b206cb5 | ||
|
|
0f92890f0c | ||
|
|
056f0213dd | ||
|
|
41871de26c | ||
|
|
5803ddc613 | ||
|
|
99263309ba | ||
|
|
8a78ee4cf6 | ||
|
|
af46324c6d | ||
|
|
42aa6525a6 | ||
|
|
05b1b55e9a | ||
|
|
febf0835c2 | ||
|
|
94ea0d253c | ||
|
|
31cf02e8e4 | ||
|
|
97fdf7cb7f | ||
|
|
2c7fbc03cf | ||
|
|
07cca48a17 | ||
|
|
43d0fcb9f9 | ||
|
|
99b8dfa0c1 | ||
|
|
4cdd65e96c | ||
|
|
9e345b1375 | ||
|
|
9466017906 | ||
|
|
9c24f1325f | ||
|
|
26401fbb93 | ||
|
|
69dfbb6673 | ||
|
|
5863797b03 | ||
|
|
25f546c691 |
@@ -1,5 +1,6 @@
|
||||
@import "mixins";
|
||||
@import "theme";
|
||||
@import "styles";
|
||||
|
||||
/**
|
||||
* Styling for all buttons
|
||||
@@ -16,6 +17,7 @@ button {
|
||||
|
||||
.a-link-button,
|
||||
.std-button {
|
||||
@extend .noselect;
|
||||
text-decoration: none;
|
||||
background-color: #555;
|
||||
color: #fff;
|
||||
@@ -23,11 +25,6 @@ button {
|
||||
margin: 5px;
|
||||
border: 1px solid #333;
|
||||
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
@@ -68,6 +65,7 @@ button {
|
||||
|
||||
.a-link-button-bought,
|
||||
.std-button-bought {
|
||||
@extend .noselect;
|
||||
text-decoration: none;
|
||||
background-color: #0a0;
|
||||
color: #fff;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import "mixins";
|
||||
@import "theme";
|
||||
@import "styles";
|
||||
|
||||
/* Pop-up boxes */
|
||||
.popup-box-container {
|
||||
@@ -23,6 +24,7 @@
|
||||
width: 70%;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
z-index: 11; /* Sit on top of the container */
|
||||
color: var(--my-font-color);
|
||||
}
|
||||
|
||||
@@ -87,6 +89,7 @@
|
||||
.dialog-box-close-button {
|
||||
@include borderRadius(12px);
|
||||
@include boxShadow(1px 1px 3px #000);
|
||||
@extend .noselect;
|
||||
|
||||
float: right;
|
||||
color: #aaa;
|
||||
|
||||
@@ -36,6 +36,10 @@ li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
br {
|
||||
@extend .noselect;
|
||||
}
|
||||
|
||||
#entire-game-container {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
4
dist/engine.bundle.js
vendored
4
dist/engine.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/engineStyle.bundle.js
vendored
2
dist/engineStyle.bundle.js
vendored
@@ -1,2 +1,2 @@
|
||||
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([853,0]),o()}({790:function(n,t,o){},792:function(n,t,o){},794:function(n,t,o){},796:function(n,t,o){},798:function(n,t,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){},853:function(n,t,o){"use strict";o.r(t);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),o(798),o(796),o(794),o(792),o(790)}});
|
||||
!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([856,0]),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){},851:function(n,t,o){},853:function(n,t,o){},856:function(n,t,o){"use strict";o.r(t);o(855),o(853),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)}});
|
||||
//# sourceMappingURL=engineStyle.bundle.js.map
|
||||
838
dist/engineStyle.css
vendored
838
dist/engineStyle.css
vendored
@@ -404,7 +404,7 @@ input[type="checkbox"] {
|
||||
background: var(--my-font-color);
|
||||
cursor: pointer; }
|
||||
|
||||
.noselect {
|
||||
.noselect, br {
|
||||
-moz-user-select: -moz-none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
@@ -523,6 +523,421 @@ input[type="checkbox"] {
|
||||
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
* {
|
||||
font-size: 16px;
|
||||
font-family: "Lucida Console", "Lucida Sans Unicode", "Fira Mono", "Consolas", "Courier New", Courier, monospace, "Times New Roman"; }
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
vertical-align: top; }
|
||||
|
||||
:root {
|
||||
--my-font-color: #6f3;
|
||||
--my-background-color: #000;
|
||||
--my-highlight-color: #fff;
|
||||
--my-prompt-color: #f92672; }
|
||||
|
||||
body {
|
||||
background-color: var(--my-background-color); }
|
||||
|
||||
p,
|
||||
pre,
|
||||
h2,
|
||||
.text,
|
||||
td {
|
||||
color: var(--my-font-color); }
|
||||
|
||||
h1 {
|
||||
font-size: 22px;
|
||||
color: var(--my-font-color); }
|
||||
|
||||
ul {
|
||||
padding: 2px;
|
||||
list-style-type: none; }
|
||||
|
||||
li {
|
||||
list-style-type: none; }
|
||||
|
||||
#entire-game-container {
|
||||
background-color: transparent; }
|
||||
|
||||
/* Disable border highlight on elements */
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
button:focus,
|
||||
td:focus,
|
||||
tr:focus {
|
||||
outline: none; }
|
||||
|
||||
/* Make html links ("a" elements) nice looking buttons with this class */
|
||||
a:link,
|
||||
a:visited {
|
||||
color: #fff; }
|
||||
|
||||
.dropdown {
|
||||
color: #fff;
|
||||
background-color: #000; }
|
||||
|
||||
.text-input {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: white; }
|
||||
|
||||
/* Notification icon (for create program right now only) */
|
||||
#create-program-tab {
|
||||
position: relative; }
|
||||
|
||||
#create-program-notification {
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
/* Position the badge within the relatively positioned button */
|
||||
top: 0;
|
||||
right: 0; }
|
||||
|
||||
#factions-tab {
|
||||
position: relative; }
|
||||
|
||||
#factions-notification {
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
/* Position the badge within the relatively positioned button */
|
||||
top: 0;
|
||||
right: 0; }
|
||||
|
||||
#augmentations-tab {
|
||||
position: relative; }
|
||||
|
||||
#augmentations-notification {
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
/* Position the badge within the relatively positioned button */
|
||||
top: 0;
|
||||
right: 0; }
|
||||
|
||||
.notification-on {
|
||||
background-color: #fa3e3e;
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
padding: 1px 3px; }
|
||||
|
||||
.notification-off {
|
||||
background-color: #333;
|
||||
color: #333;
|
||||
border-radius: 0;
|
||||
padding: 0; }
|
||||
|
||||
/* help tip. Question mark that opens popup with info/details */
|
||||
.help-tip {
|
||||
background-color: black;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 5px;
|
||||
color: #fff;
|
||||
content: '?';
|
||||
display: inline-block;
|
||||
margin-left: 3px;
|
||||
padding: 1px; }
|
||||
|
||||
.help-tip-big {
|
||||
content: '?';
|
||||
padding: 3px;
|
||||
margin-left: 3px;
|
||||
color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 8px;
|
||||
display: inline-block; }
|
||||
|
||||
.help-tip:hover,
|
||||
.help-tip-big:hover {
|
||||
background-color: #888; }
|
||||
|
||||
.help-tip:active,
|
||||
.help-tip-big:active {
|
||||
-webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
|
||||
-moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
|
||||
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6); }
|
||||
|
||||
/* Flashing button (Red) */
|
||||
@-webkit-keyframes glowing {
|
||||
0% {
|
||||
background-color: #b20000;
|
||||
-webkit-box-shadow: 0 0 3px #b20000; }
|
||||
50% {
|
||||
background-color: #f00;
|
||||
-webkit-box-shadow: 0 0 40px #f00; }
|
||||
100% {
|
||||
background-color: #b20000;
|
||||
-webkit-box-shadow: 0 0 3px #b20000; } }
|
||||
|
||||
@-moz-keyframes glowing {
|
||||
0% {
|
||||
background-color: #b20000;
|
||||
-moz-box-shadow: 0 0 3px #b20000; }
|
||||
50% {
|
||||
background-color: #f00;
|
||||
-moz-box-shadow: 0 0 40px #f00; }
|
||||
100% {
|
||||
background-color: #b20000;
|
||||
-moz-box-shadow: 0 0 3px #b20000; } }
|
||||
|
||||
@-o-keyframes glowing {
|
||||
0% {
|
||||
background-color: #b20000;
|
||||
box-shadow: 0 0 3px #b20000; }
|
||||
50% {
|
||||
background-color: #f00;
|
||||
box-shadow: 0 0 40px #f00; }
|
||||
100% {
|
||||
background-color: #b20000;
|
||||
box-shadow: 0 0 3px #b20000; } }
|
||||
|
||||
@keyframes glowing {
|
||||
0% {
|
||||
background-color: #b20000;
|
||||
box-shadow: 0 0 3px #b20000; }
|
||||
50% {
|
||||
background-color: #f00;
|
||||
box-shadow: 0 0 40px #f00; }
|
||||
100% {
|
||||
background-color: #b20000;
|
||||
box-shadow: 0 0 3px #b20000; } }
|
||||
|
||||
.flashing-button {
|
||||
-webkit-animation: glowing 1500ms infinite;
|
||||
-moz-animation: glowing 1500ms infinite;
|
||||
-o-animation: glowing 1500ms infinite;
|
||||
animation: glowing 1500ms infinite; }
|
||||
|
||||
/* Blinking Cursor */
|
||||
/* ----- blinking cursor animation ----- */
|
||||
.typed-cursor {
|
||||
opacity: 1;
|
||||
-webkit-animation: blink 0.95s infinite;
|
||||
-moz-animation: blink 0.95s infinite;
|
||||
-ms-animation: blink 0.95s infinite;
|
||||
-o-animation: blink 0.95s infinite;
|
||||
animation: blink 0.95s infinite; }
|
||||
|
||||
@-keyframes blink {
|
||||
0% {
|
||||
opacity: 1; }
|
||||
50% {
|
||||
opacity: 0; }
|
||||
100% {
|
||||
opacity: 1; } }
|
||||
|
||||
@-webkit-keyframes blink {
|
||||
0% {
|
||||
opacity: 1; }
|
||||
50% {
|
||||
opacity: 0; }
|
||||
100% {
|
||||
opacity: 1; } }
|
||||
|
||||
@-moz-keyframes blink {
|
||||
0% {
|
||||
opacity: 1; }
|
||||
50% {
|
||||
opacity: 0; }
|
||||
100% {
|
||||
opacity: 1; } }
|
||||
|
||||
@-ms-keyframes blink {
|
||||
0% {
|
||||
opacity: 1; }
|
||||
50% {
|
||||
opacity: 0; }
|
||||
100% {
|
||||
opacity: 1; } }
|
||||
|
||||
@-o-keyframes blink {
|
||||
0% {
|
||||
opacity: 1; }
|
||||
50% {
|
||||
opacity: 0; }
|
||||
100% {
|
||||
opacity: 1; } }
|
||||
|
||||
/* Status text */
|
||||
@-webkit-keyframes status-text {
|
||||
from {
|
||||
opacity: 1; }
|
||||
to {
|
||||
opacity: 0; } }
|
||||
|
||||
.status-text {
|
||||
z-index: 2;
|
||||
-webkit-animation: status-text 3s 1; }
|
||||
|
||||
#status-text-container {
|
||||
background-color: transparent;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%; }
|
||||
|
||||
#status-text {
|
||||
background-color: transparent;
|
||||
bottom: 0;
|
||||
color: #fff;
|
||||
display: none;
|
||||
font-size: 20px;
|
||||
margin-right: 14px;
|
||||
opacity: 0;
|
||||
padding: 4px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: auto; }
|
||||
|
||||
/* Scan analyze links from AutoLink */
|
||||
.scan-analyze-link {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
text-decoration: underline; }
|
||||
.scan-analyze-link:hover {
|
||||
text-decoration: none; }
|
||||
|
||||
/* Accordion menus (Header with collapsible panel) */
|
||||
.accordion-header {
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
margin: 6px 6px 0 6px;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
width: 80%;
|
||||
text-align: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
position: relative; }
|
||||
.accordion-header.active, .accordion-header:hover {
|
||||
background-color: #555; }
|
||||
.accordion-header.active:hover {
|
||||
background-color: #666; }
|
||||
.accordion-header:after {
|
||||
content: '\2795';
|
||||
/* "plus" sign (+) */
|
||||
font-size: 14px;
|
||||
float: right;
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 #fff;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 6px; }
|
||||
.accordion-header.active:after {
|
||||
content: "\2796";
|
||||
/* "minus" sign (-) */ }
|
||||
|
||||
.accordion-panel {
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 6px 6px 6px;
|
||||
width: 75%;
|
||||
margin-left: 5%;
|
||||
display: none;
|
||||
background-color: #555;
|
||||
overflow-y: auto;
|
||||
overflow-x: none; }
|
||||
.accordion-panel div, .accordion-panel ul, .accordion-panel p, .accordion-panel ul > li {
|
||||
background-color: #555; }
|
||||
|
||||
/* override the global <span> styling */
|
||||
#active-scripts-total-production-active,
|
||||
#active-scripts-total-prod-aug-total,
|
||||
#active-scripts-total-prod-aug-avg {
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
|
||||
/* Helper Classes */
|
||||
.hacker-green {
|
||||
color: #adff2f; }
|
||||
|
||||
.money-gold {
|
||||
color: #ffd700; }
|
||||
|
||||
.light-yellow {
|
||||
color: #faffdf; }
|
||||
|
||||
.failure {
|
||||
color: #ff2929;
|
||||
text-shadow: 0 0 0 #ff2929; }
|
||||
|
||||
.success {
|
||||
color: #3adb76;
|
||||
text-shadow: 0 0 0 #3adb76; }
|
||||
|
||||
.physical-yellow {
|
||||
color: #faffdf; }
|
||||
|
||||
.charisma-purple {
|
||||
color: #a671d1; }
|
||||
|
||||
.reputation {
|
||||
color: #faffdf; }
|
||||
|
||||
.smallfont {
|
||||
font-size: 13px; }
|
||||
|
||||
.samefont {
|
||||
font-size: inherit; }
|
||||
|
||||
.noscrollbar {
|
||||
-ms-overflow-style: none;
|
||||
/* IE and Edge */
|
||||
/* stylelint-disable-next-line property-no-unknown */
|
||||
scrollbar-width: none;
|
||||
/* Firefox https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-width */ }
|
||||
|
||||
.noscrollbar::-webkit-scrollbar {
|
||||
display: none; }
|
||||
|
||||
input[type="checkbox"] {
|
||||
filter: invert(1) sepia(1) hue-rotate(41deg) brightness(100%) saturate(10); }
|
||||
|
||||
.optionCheckbox {
|
||||
margin: 5px;
|
||||
float: right; }
|
||||
|
||||
.optionRange {
|
||||
-webkit-appearance: none;
|
||||
background: #777;
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
height: 10px;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
margin: 3px; }
|
||||
|
||||
.optionRange::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--my-font-color);
|
||||
cursor: pointer; }
|
||||
|
||||
.optionRange::-moz-range-thumb {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--my-font-color);
|
||||
cursor: pointer; }
|
||||
|
||||
.noselect, br, .a-link-button,
|
||||
.std-button, .a-link-button-bought,
|
||||
.std-button-bought {
|
||||
-moz-user-select: -moz-none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none; }
|
||||
|
||||
/**
|
||||
* Styling for all buttons
|
||||
*
|
||||
@@ -541,11 +956,7 @@ button {
|
||||
color: #fff;
|
||||
padding: 3px 5px;
|
||||
margin: 5px;
|
||||
border: 1px solid #333;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none; }
|
||||
border: 1px solid #333; }
|
||||
.a-link-button:hover,
|
||||
.std-button:hover {
|
||||
background-color: #666; }
|
||||
@@ -1528,6 +1939,419 @@ button {
|
||||
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
/* COLORS */
|
||||
/* Attributes */
|
||||
* {
|
||||
font-size: 16px;
|
||||
font-family: "Lucida Console", "Lucida Sans Unicode", "Fira Mono", "Consolas", "Courier New", Courier, monospace, "Times New Roman"; }
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
vertical-align: top; }
|
||||
|
||||
:root {
|
||||
--my-font-color: #6f3;
|
||||
--my-background-color: #000;
|
||||
--my-highlight-color: #fff;
|
||||
--my-prompt-color: #f92672; }
|
||||
|
||||
body {
|
||||
background-color: var(--my-background-color); }
|
||||
|
||||
p,
|
||||
pre,
|
||||
h2,
|
||||
.text,
|
||||
td {
|
||||
color: var(--my-font-color); }
|
||||
|
||||
h1 {
|
||||
font-size: 22px;
|
||||
color: var(--my-font-color); }
|
||||
|
||||
ul {
|
||||
padding: 2px;
|
||||
list-style-type: none; }
|
||||
|
||||
li {
|
||||
list-style-type: none; }
|
||||
|
||||
#entire-game-container {
|
||||
background-color: transparent; }
|
||||
|
||||
/* Disable border highlight on elements */
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
button:focus,
|
||||
td:focus,
|
||||
tr:focus {
|
||||
outline: none; }
|
||||
|
||||
/* Make html links ("a" elements) nice looking buttons with this class */
|
||||
a:link,
|
||||
a:visited {
|
||||
color: #fff; }
|
||||
|
||||
.dropdown {
|
||||
color: #fff;
|
||||
background-color: #000; }
|
||||
|
||||
.text-input {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: white; }
|
||||
|
||||
/* Notification icon (for create program right now only) */
|
||||
#create-program-tab {
|
||||
position: relative; }
|
||||
|
||||
#create-program-notification {
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
/* Position the badge within the relatively positioned button */
|
||||
top: 0;
|
||||
right: 0; }
|
||||
|
||||
#factions-tab {
|
||||
position: relative; }
|
||||
|
||||
#factions-notification {
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
/* Position the badge within the relatively positioned button */
|
||||
top: 0;
|
||||
right: 0; }
|
||||
|
||||
#augmentations-tab {
|
||||
position: relative; }
|
||||
|
||||
#augmentations-notification {
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
/* Position the badge within the relatively positioned button */
|
||||
top: 0;
|
||||
right: 0; }
|
||||
|
||||
.notification-on {
|
||||
background-color: #fa3e3e;
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
padding: 1px 3px; }
|
||||
|
||||
.notification-off {
|
||||
background-color: #333;
|
||||
color: #333;
|
||||
border-radius: 0;
|
||||
padding: 0; }
|
||||
|
||||
/* help tip. Question mark that opens popup with info/details */
|
||||
.help-tip {
|
||||
background-color: black;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 5px;
|
||||
color: #fff;
|
||||
content: '?';
|
||||
display: inline-block;
|
||||
margin-left: 3px;
|
||||
padding: 1px; }
|
||||
|
||||
.help-tip-big {
|
||||
content: '?';
|
||||
padding: 3px;
|
||||
margin-left: 3px;
|
||||
color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 8px;
|
||||
display: inline-block; }
|
||||
|
||||
.help-tip:hover,
|
||||
.help-tip-big:hover {
|
||||
background-color: #888; }
|
||||
|
||||
.help-tip:active,
|
||||
.help-tip-big:active {
|
||||
-webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
|
||||
-moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
|
||||
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6); }
|
||||
|
||||
/* Flashing button (Red) */
|
||||
@-webkit-keyframes glowing {
|
||||
0% {
|
||||
background-color: #b20000;
|
||||
-webkit-box-shadow: 0 0 3px #b20000; }
|
||||
50% {
|
||||
background-color: #f00;
|
||||
-webkit-box-shadow: 0 0 40px #f00; }
|
||||
100% {
|
||||
background-color: #b20000;
|
||||
-webkit-box-shadow: 0 0 3px #b20000; } }
|
||||
|
||||
@-moz-keyframes glowing {
|
||||
0% {
|
||||
background-color: #b20000;
|
||||
-moz-box-shadow: 0 0 3px #b20000; }
|
||||
50% {
|
||||
background-color: #f00;
|
||||
-moz-box-shadow: 0 0 40px #f00; }
|
||||
100% {
|
||||
background-color: #b20000;
|
||||
-moz-box-shadow: 0 0 3px #b20000; } }
|
||||
|
||||
@-o-keyframes glowing {
|
||||
0% {
|
||||
background-color: #b20000;
|
||||
box-shadow: 0 0 3px #b20000; }
|
||||
50% {
|
||||
background-color: #f00;
|
||||
box-shadow: 0 0 40px #f00; }
|
||||
100% {
|
||||
background-color: #b20000;
|
||||
box-shadow: 0 0 3px #b20000; } }
|
||||
|
||||
@keyframes glowing {
|
||||
0% {
|
||||
background-color: #b20000;
|
||||
box-shadow: 0 0 3px #b20000; }
|
||||
50% {
|
||||
background-color: #f00;
|
||||
box-shadow: 0 0 40px #f00; }
|
||||
100% {
|
||||
background-color: #b20000;
|
||||
box-shadow: 0 0 3px #b20000; } }
|
||||
|
||||
.flashing-button {
|
||||
-webkit-animation: glowing 1500ms infinite;
|
||||
-moz-animation: glowing 1500ms infinite;
|
||||
-o-animation: glowing 1500ms infinite;
|
||||
animation: glowing 1500ms infinite; }
|
||||
|
||||
/* Blinking Cursor */
|
||||
/* ----- blinking cursor animation ----- */
|
||||
.typed-cursor {
|
||||
opacity: 1;
|
||||
-webkit-animation: blink 0.95s infinite;
|
||||
-moz-animation: blink 0.95s infinite;
|
||||
-ms-animation: blink 0.95s infinite;
|
||||
-o-animation: blink 0.95s infinite;
|
||||
animation: blink 0.95s infinite; }
|
||||
|
||||
@-keyframes blink {
|
||||
0% {
|
||||
opacity: 1; }
|
||||
50% {
|
||||
opacity: 0; }
|
||||
100% {
|
||||
opacity: 1; } }
|
||||
|
||||
@-webkit-keyframes blink {
|
||||
0% {
|
||||
opacity: 1; }
|
||||
50% {
|
||||
opacity: 0; }
|
||||
100% {
|
||||
opacity: 1; } }
|
||||
|
||||
@-moz-keyframes blink {
|
||||
0% {
|
||||
opacity: 1; }
|
||||
50% {
|
||||
opacity: 0; }
|
||||
100% {
|
||||
opacity: 1; } }
|
||||
|
||||
@-ms-keyframes blink {
|
||||
0% {
|
||||
opacity: 1; }
|
||||
50% {
|
||||
opacity: 0; }
|
||||
100% {
|
||||
opacity: 1; } }
|
||||
|
||||
@-o-keyframes blink {
|
||||
0% {
|
||||
opacity: 1; }
|
||||
50% {
|
||||
opacity: 0; }
|
||||
100% {
|
||||
opacity: 1; } }
|
||||
|
||||
/* Status text */
|
||||
@-webkit-keyframes status-text {
|
||||
from {
|
||||
opacity: 1; }
|
||||
to {
|
||||
opacity: 0; } }
|
||||
|
||||
.status-text {
|
||||
z-index: 2;
|
||||
-webkit-animation: status-text 3s 1; }
|
||||
|
||||
#status-text-container {
|
||||
background-color: transparent;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%; }
|
||||
|
||||
#status-text {
|
||||
background-color: transparent;
|
||||
bottom: 0;
|
||||
color: #fff;
|
||||
display: none;
|
||||
font-size: 20px;
|
||||
margin-right: 14px;
|
||||
opacity: 0;
|
||||
padding: 4px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: auto; }
|
||||
|
||||
/* Scan analyze links from AutoLink */
|
||||
.scan-analyze-link {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
text-decoration: underline; }
|
||||
.scan-analyze-link:hover {
|
||||
text-decoration: none; }
|
||||
|
||||
/* Accordion menus (Header with collapsible panel) */
|
||||
.accordion-header {
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
margin: 6px 6px 0 6px;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
width: 80%;
|
||||
text-align: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
position: relative; }
|
||||
.accordion-header.active, .accordion-header:hover {
|
||||
background-color: #555; }
|
||||
.accordion-header.active:hover {
|
||||
background-color: #666; }
|
||||
.accordion-header:after {
|
||||
content: '\2795';
|
||||
/* "plus" sign (+) */
|
||||
font-size: 14px;
|
||||
float: right;
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 #fff;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 6px; }
|
||||
.accordion-header.active:after {
|
||||
content: "\2796";
|
||||
/* "minus" sign (-) */ }
|
||||
|
||||
.accordion-panel {
|
||||
margin: 0 6px 6px 6px;
|
||||
padding: 0 6px 6px 6px;
|
||||
width: 75%;
|
||||
margin-left: 5%;
|
||||
display: none;
|
||||
background-color: #555;
|
||||
overflow-y: auto;
|
||||
overflow-x: none; }
|
||||
.accordion-panel div, .accordion-panel ul, .accordion-panel p, .accordion-panel ul > li {
|
||||
background-color: #555; }
|
||||
|
||||
/* override the global <span> styling */
|
||||
#active-scripts-total-production-active,
|
||||
#active-scripts-total-prod-aug-total,
|
||||
#active-scripts-total-prod-aug-avg {
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
|
||||
/* Helper Classes */
|
||||
.hacker-green {
|
||||
color: #adff2f; }
|
||||
|
||||
.money-gold {
|
||||
color: #ffd700; }
|
||||
|
||||
.light-yellow {
|
||||
color: #faffdf; }
|
||||
|
||||
.failure {
|
||||
color: #ff2929;
|
||||
text-shadow: 0 0 0 #ff2929; }
|
||||
|
||||
.success {
|
||||
color: #3adb76;
|
||||
text-shadow: 0 0 0 #3adb76; }
|
||||
|
||||
.physical-yellow {
|
||||
color: #faffdf; }
|
||||
|
||||
.charisma-purple {
|
||||
color: #a671d1; }
|
||||
|
||||
.reputation {
|
||||
color: #faffdf; }
|
||||
|
||||
.smallfont {
|
||||
font-size: 13px; }
|
||||
|
||||
.samefont {
|
||||
font-size: inherit; }
|
||||
|
||||
.noscrollbar {
|
||||
-ms-overflow-style: none;
|
||||
/* IE and Edge */
|
||||
/* stylelint-disable-next-line property-no-unknown */
|
||||
scrollbar-width: none;
|
||||
/* Firefox https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-width */ }
|
||||
|
||||
.noscrollbar::-webkit-scrollbar {
|
||||
display: none; }
|
||||
|
||||
input[type="checkbox"] {
|
||||
filter: invert(1) sepia(1) hue-rotate(41deg) brightness(100%) saturate(10); }
|
||||
|
||||
.optionCheckbox {
|
||||
margin: 5px;
|
||||
float: right; }
|
||||
|
||||
.optionRange {
|
||||
-webkit-appearance: none;
|
||||
background: #777;
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
height: 10px;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
margin: 3px; }
|
||||
|
||||
.optionRange::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--my-font-color);
|
||||
cursor: pointer; }
|
||||
|
||||
.optionRange::-moz-range-thumb {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: var(--my-font-color);
|
||||
cursor: pointer; }
|
||||
|
||||
.noselect, br, .dialog-box-close-button {
|
||||
-moz-user-select: -moz-none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none; }
|
||||
|
||||
/* Pop-up boxes */
|
||||
.popup-box-container {
|
||||
display: none;
|
||||
@@ -1552,6 +2376,8 @@ button {
|
||||
width: 70%;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
z-index: 11;
|
||||
/* Sit on top of the container */
|
||||
color: var(--my-font-color); }
|
||||
|
||||
.popup-box-button,
|
||||
|
||||
34
dist/vendor.bundle.js
vendored
34
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
@@ -3,6 +3,77 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
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)
|
||||
-------------------------------------------
|
||||
|
||||
@@ -17,6 +88,10 @@ v0.52.2 - 2021-07-15 Oh yeah, BN11 is a thing (drunk hydroflame tbh)
|
||||
programs.
|
||||
* Augmentation descriptions are now more concise and consistent.
|
||||
|
||||
** Misc. **
|
||||
|
||||
* nerf noodle bar
|
||||
|
||||
v0.52.1 - 2021-07-10 bugfixing (hydroflame & community)
|
||||
-------------------------------------------
|
||||
|
||||
@@ -36,6 +111,7 @@ v0.52.1 - 2021-07-10 bugfixing (hydroflame & community)
|
||||
* 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)
|
||||
--------------------------------------------------------------
|
||||
@@ -85,6 +161,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)
|
||||
-----------------------------------------------------
|
||||
@@ -128,6 +205,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)
|
||||
---------------------------------------------------------------
|
||||
@@ -181,6 +259,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)
|
||||
@@ -238,6 +317,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)
|
||||
-----------------------------------------
|
||||
@@ -295,6 +375,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)
|
||||
@@ -345,6 +426,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)
|
||||
@@ -369,6 +451,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)
|
||||
-------------------------------------------------------
|
||||
@@ -404,6 +487,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)
|
||||
-----------------------------------------------------------------
|
||||
@@ -445,6 +529,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)
|
||||
----------------------------------------------
|
||||
@@ -459,6 +544,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)
|
||||
------------------------------------------------------------------------------------------
|
||||
@@ -490,6 +576,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)
|
||||
------------------------------------------
|
||||
@@ -516,6 +603,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)
|
||||
--------------------------------------------------------------
|
||||
|
||||
@@ -533,6 +624,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)
|
||||
---------------------------------
|
||||
@@ -555,6 +647,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)
|
||||
----------------------------------------------
|
||||
@@ -575,6 +668,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)
|
||||
---------------------------------
|
||||
@@ -612,6 +706,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)
|
||||
------------------------------------------------
|
||||
@@ -639,6 +734,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)
|
||||
@@ -686,6 +782,7 @@ v0.48.0 - ASCII - 2021-03-07 (hydroflame)
|
||||
has bought but not installed
|
||||
* Character>Factions has a badge indicating how many factions have pending
|
||||
invites.
|
||||
* nerf noodle bar
|
||||
|
||||
v0.47.2 - 7/15/2019
|
||||
-------------------
|
||||
|
||||
@@ -66,7 +66,7 @@ documentation_title = '{0} Documentation'.format(project)
|
||||
# The short X.Y version.
|
||||
version = '0.52'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.52.2'
|
||||
release = '0.52.5'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -636,7 +636,7 @@ This tells me that I can reach :code:`CSEC` by going through :code:`iron-gym`::
|
||||
from CSEC once you hit 50 hacking, you cannot actually pass their test
|
||||
until your hacking is high enough to install a backdoor on their server.
|
||||
|
||||
After you are connected to the :code:`CSEC` server, you can hack it. Note that this
|
||||
After you are connected to the :code:`CSEC` server, you can backdoor it. Note that this
|
||||
server requires one open port in order to gain root access. We can open the SSH port
|
||||
using the :code:`BruteSSH.exe` program we created earlier. In |Terminal|::
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ getActionCountRemaining() Netscript Function
|
||||
This function will return 'Infinity' for actions such as 'Training' and
|
||||
'Field Analysis'.
|
||||
This function will return 1 for BlackOps not yet completed regardless of
|
||||
wether the player has the required rank to attempt the mission or not.
|
||||
whether the player has the required rank to attempt the mission or not.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -12,4 +12,4 @@ getActionCurrentLevel() Netscript Function
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
bladeburner.getActionCountRemaining("Contracts", "Tracking"); // returns: 7
|
||||
bladeburner.getActionCurrentLevel("Contracts", "Tracking"); // returns: 9
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
60
index.html
60
index.html
@@ -33,83 +33,83 @@
|
||||
<ul id="mainmenu" class="mainmenu noscrollbar">
|
||||
<!-- Hacking dropdown -->
|
||||
<li id="hacking-menu-header-li">
|
||||
<button id="hacking-menu-header" class="mainmenu-accordion-header"> Hacking </button>
|
||||
<button id="hacking-menu-header" class="mainmenu-accordion-header noselect"> Hacking </button>
|
||||
</li>
|
||||
<li id="terminal-tab" class="mainmenu-accordion-panel">
|
||||
<li id="terminal-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="terminal-menu-link"> Terminal </button>
|
||||
</li>
|
||||
<li id="create-script-tab" class="mainmenu-accordion-panel">
|
||||
<li id="create-script-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="create-script-menu-link"> Create Script </button>
|
||||
</li>
|
||||
<li id="active-scripts-tab" class="mainmenu-accordion-panel">
|
||||
<li id="active-scripts-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="active-scripts-menu-link"> Active Scripts </button>
|
||||
</li>
|
||||
<li id="create-program-tab" class="mainmenu-accordion-panel">
|
||||
<li id="create-program-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="create-program-menu-link"> Create Program </button>
|
||||
<span id="create-program-notification" class="notification-off"> </span>
|
||||
</li>
|
||||
|
||||
<!-- Character dropdown -->
|
||||
<li id="character-menu-header-li">
|
||||
<button id="character-menu-header" class="mainmenu-accordion-header"> Character </button>
|
||||
<button id="character-menu-header" class="mainmenu-accordion-header noselect"> Character </button>
|
||||
</li>
|
||||
<li id="stats-tab" class="mainmenu-accordion-panel">
|
||||
<li id="stats-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="stats-menu-link"> Stats </button>
|
||||
</li>
|
||||
<li id="factions-tab" class="mainmenu-accordion-panel">
|
||||
<li id="factions-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="factions-menu-link"> Factions </button>
|
||||
<span id="factions-notification" class="notification-off"> </span>
|
||||
</li>
|
||||
<li id="augmentations-tab" class="mainmenu-accordion-panel">
|
||||
<li id="augmentations-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="augmentations-menu-link" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> Augmentations </button>
|
||||
<span id="augmentations-notification" class="notification-off"> </span>
|
||||
</li>
|
||||
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel">
|
||||
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="hacknet-nodes-menu-link"> Hacknet </button>
|
||||
</li>
|
||||
<li id="sleeves-tab" class="mainmenu-accordion-panel">
|
||||
<li id="sleeves-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="sleeves-menu-link"> Sleeves </button>
|
||||
</li>
|
||||
|
||||
<!-- World dropdown -->
|
||||
<li id="world-menu-header-li">
|
||||
<button id="world-menu-header" class="mainmenu-accordion-header">World</button>
|
||||
<button id="world-menu-header" class="mainmenu-accordion-header noselect">World</button>
|
||||
</li>
|
||||
<li id="city-tab" class="mainmenu-accordion-panel">
|
||||
<li id="city-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="city-menu-link"> City </button>
|
||||
</li>
|
||||
<li id="travel-tab" class="mainmenu-accordion-panel">
|
||||
<li id="travel-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="travel-menu-link"> Travel </button>
|
||||
</li>
|
||||
<li id="job-tab" class="mainmenu-accordion-panel">
|
||||
<li id="job-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="job-menu-link"> Job </button>
|
||||
</li>
|
||||
<li id="stock-market-tab" class="mainmenu-accordion-panel">
|
||||
<li id="stock-market-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="stock-market-menu-link"> Stock Market </button>
|
||||
</li>
|
||||
<li id="bladeburner-tab" class="mainmenu-accordion-panel">
|
||||
<li id="bladeburner-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="bladeburner-menu-link"> Bladeburner </button>
|
||||
</li>
|
||||
<li id="corporation-tab" class="mainmenu-accordion-panel">
|
||||
<li id="corporation-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="corporation-menu-link"> Corp </button>
|
||||
</li>
|
||||
<li id="gang-tab" class="mainmenu-accordion-panel">
|
||||
<li id="gang-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="gang-menu-link"> Gang </button>
|
||||
</li>
|
||||
|
||||
<li id="help-menu-header-li">
|
||||
<button id="help-menu-header" class="mainmenu-accordion-header"> Help </button>
|
||||
<button id="help-menu-header" class="mainmenu-accordion-header noselect"> Help </button>
|
||||
</li>
|
||||
<li id="milestones-tab" class="mainmenu-accordion-panel">
|
||||
<li id="milestones-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="milestones-menu-link"> Milestones </button>
|
||||
</li>
|
||||
<li id="tutorial-tab" class="mainmenu-accordion-panel">
|
||||
<li id="tutorial-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="tutorial-menu-link"> Tutorial </button>
|
||||
</li>
|
||||
<li id="options-tab" class="mainmenu-accordion-panel">
|
||||
<li id="options-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="options-menu-link"> Options </button>
|
||||
</li>
|
||||
<li id="dev-tab" class="mainmenu-accordion-panel">
|
||||
<li id="dev-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="dev-menu-link"> Dev </button>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -230,8 +230,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,6 +273,10 @@
|
||||
<!-- React Component -->
|
||||
</div>
|
||||
|
||||
<div id="gang-container" class="generic-menupage-container">
|
||||
<!-- React Component -->
|
||||
</div>
|
||||
|
||||
<!-- Log Box -->
|
||||
<div id="log-box-container">
|
||||
<div id="log-box-content">
|
||||
@@ -362,7 +368,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>
|
||||
|
||||
@@ -126,5 +126,5 @@
|
||||
"watch": "webpack --watch --mode production",
|
||||
"watch:dev": "webpack --watch --mode development"
|
||||
},
|
||||
"version": "0.52.2"
|
||||
"version": "0.52.5"
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ interface IConstructorParams {
|
||||
}
|
||||
|
||||
function generateStatsDescription(mults: IMap<number>, programs?: string[], startingMoney?: number): JSX.Element {
|
||||
const f = (x: number, decimals: number = 0) => {
|
||||
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%";
|
||||
|
||||
@@ -18,8 +18,6 @@ import { SourceFileFlags } from "../SourceFile/SourceFileFlags";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import { clearObject } from "../../utils/helpers/clearObject";
|
||||
import { Money } from "../ui/React/Money";
|
||||
import { CorruptableText } from "../ui/React/CorruptableText";
|
||||
|
||||
import { WHRNG } from "../Casino/RNG";
|
||||
|
||||
@@ -1865,11 +1863,6 @@ function initAugmentations() {
|
||||
resetAugmentation(BladesSimulacrum);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log([1, 0.96, 0.94, 0.93][SourceFileFlags[11]]);
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
// Update costs based on how many have been purchased
|
||||
mult = Math.pow(CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]], Player.queuedAugmentations.length);
|
||||
for (var name in Augmentations) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
3265
src/Bladeburner.jsx
3265
src/Bladeburner.jsx
File diff suppressed because it is too large
Load Diff
28
src/Bladeburner/ActionIdentifier.ts
Normal file
28
src/Bladeburner/ActionIdentifier.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { IActionIdentifier } from "./IActionIdentifier";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
interface IParams {
|
||||
name?: string;
|
||||
type?: number;
|
||||
}
|
||||
|
||||
export class ActionIdentifier implements IActionIdentifier {
|
||||
name = "";
|
||||
type = -1;
|
||||
|
||||
constructor(params: IParams = {}) {
|
||||
if (params.name) this.name = params.name;
|
||||
if (params.type) this.type = params.type;
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("ActionIdentifier", this);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static fromJSON(value: any): ActionIdentifier {
|
||||
return Generic_fromJSON(ActionIdentifier, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
Reviver.constructors.ActionIdentifier = ActionIdentifier;
|
||||
2005
src/Bladeburner/Bladeburner.ts
Normal file
2005
src/Bladeburner/Bladeburner.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,14 +4,14 @@ import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
import { addOffset } from "../../utils/helpers/addOffset";
|
||||
|
||||
export class ChangePopulationByCountParams {
|
||||
estChange = 0;
|
||||
estOffset = 0;
|
||||
interface IChangePopulationByCountParams {
|
||||
estChange: number;
|
||||
estOffset: number;
|
||||
}
|
||||
|
||||
export class ChangePopulationByPercentageParams {
|
||||
nonZero = false;
|
||||
changeEstEqually = false;
|
||||
interface IChangePopulationByPercentageParams {
|
||||
nonZero: boolean;
|
||||
changeEstEqually: boolean;
|
||||
}
|
||||
|
||||
export class City {
|
||||
@@ -113,7 +113,7 @@ export class City {
|
||||
* estChange(int): How much the estimate should change by
|
||||
* estOffset(int): Add offset to estimate (offset by percentage)
|
||||
*/
|
||||
changePopulationByCount(n: number, params: ChangePopulationByCountParams=new ChangePopulationByCountParams()): void {
|
||||
changePopulationByCount(n: number, params: IChangePopulationByCountParams = {estChange: 0, estOffset: 0}): void {
|
||||
if (isNaN(n)) {throw new Error("NaN passed into City.changePopulationByCount()");}
|
||||
this.pop += n;
|
||||
if (params.estChange && !isNaN(params.estChange)) {this.popEst += params.estChange;}
|
||||
@@ -129,7 +129,7 @@ export class City {
|
||||
* changeEstEqually(bool) - Change the population estimate by an equal amount
|
||||
* nonZero (bool) - Set to true to ensure that population always changes by at least 1
|
||||
*/
|
||||
changePopulationByPercentage(p: number, params: ChangePopulationByPercentageParams=new ChangePopulationByPercentageParams()): number {
|
||||
changePopulationByPercentage(p: number, params: IChangePopulationByPercentageParams={nonZero: false, changeEstEqually: false}): number {
|
||||
if (isNaN(p)) {throw new Error("NaN passed into City.changePopulationByPercentage()");}
|
||||
if (p === 0) {return 0;}
|
||||
let change = Math.round(this.pop * (p/100));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface IActionIdentifier {
|
||||
name: string;
|
||||
type: string;
|
||||
type: number;
|
||||
}
|
||||
@@ -1,20 +1,33 @@
|
||||
import { IActionIdentifier } from "./IActionIdentifier";
|
||||
import { City } from "./City";
|
||||
import { Skill } from "./Skill";
|
||||
import { IAction } from "./IAction";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
|
||||
export interface IBladeburner {
|
||||
numHosp: number;
|
||||
moneyLost: number;
|
||||
rank: number;
|
||||
maxRank: number;
|
||||
|
||||
skillPoints: number;
|
||||
totalSkillPoints: number;
|
||||
|
||||
teamSize: number;
|
||||
teamLost: number;
|
||||
hpLost: number;
|
||||
|
||||
storedCycles: number;
|
||||
|
||||
randomEventCounter: number;
|
||||
|
||||
actionTimeToComplete: number;
|
||||
actionTimeCurrent: number;
|
||||
actionTimeOverflow: number;
|
||||
|
||||
action: IActionIdentifier;
|
||||
|
||||
cities: any;
|
||||
city: string;
|
||||
skills: any;
|
||||
@@ -27,13 +40,66 @@ export interface IBladeburner {
|
||||
blackops: any;
|
||||
logging: any;
|
||||
automateEnabled: boolean;
|
||||
automateActionHigh: number;
|
||||
automateActionHigh: IActionIdentifier;
|
||||
automateThreshHigh: number;
|
||||
automateActionLow: number;
|
||||
automateActionLow: IActionIdentifier;
|
||||
automateThreshLow: number;
|
||||
consoleHistory: string[];
|
||||
consoleLogs: string[];
|
||||
|
||||
getCurrentCity(): City;
|
||||
calculateStaminaPenalty(): number;
|
||||
startAction(player: IPlayer, action: IActionIdentifier): void;
|
||||
upgradeSkill(skill: Skill): void;
|
||||
executeConsoleCommands(player: IPlayer, command: string): void;
|
||||
postToConsole(input: string, saveToLogs?: boolean): void;
|
||||
log(input: string): void;
|
||||
resetAction(): void;
|
||||
clearConsole(): void;
|
||||
|
||||
prestige(): void;
|
||||
storeCycles(numCycles?: number): void;
|
||||
getTypeAndNameFromActionId(actionId: IActionIdentifier): {type: string; name: string};
|
||||
getContractNamesNetscriptFn(): string[];
|
||||
getOperationNamesNetscriptFn(): string[];
|
||||
getBlackOpNamesNetscriptFn(): string[];
|
||||
getGeneralActionNamesNetscriptFn(): string[];
|
||||
getSkillNamesNetscriptFn(): string[];
|
||||
startActionNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): boolean;
|
||||
getActionTimeNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number;
|
||||
getActionEstimatedSuccessChanceNetscriptFn(player: IPlayer, type: string, name: string, workerScript: WorkerScript): number;
|
||||
getActionCountRemainingNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
|
||||
getSkillLevelNetscriptFn(skillName: string, workerScript: WorkerScript): number;
|
||||
getSkillUpgradeCostNetscriptFn(skillName: string, workerScript: WorkerScript): number;
|
||||
upgradeSkillNetscriptFn(skillName: string, workerScript: WorkerScript): boolean;
|
||||
getTeamSizeNetscriptFn(type: string, name: string, workerScript: WorkerScript): number;
|
||||
setTeamSizeNetscriptFn(type: string, name: string, size: number, workerScript: WorkerScript): number;
|
||||
joinBladeburnerFactionNetscriptFn(workerScript: WorkerScript): boolean;
|
||||
getActionIdFromTypeAndName(type: string, name: string): IActionIdentifier | null;
|
||||
executeStartConsoleCommand(player: IPlayer, args: string[]): void;
|
||||
executeSkillConsoleCommand(args: string[]): void;
|
||||
executeLogConsoleCommand(args: string[]): void;
|
||||
executeHelpConsoleCommand(args: string[]): void;
|
||||
executeAutomateConsoleCommand(args: string[]): void;
|
||||
parseCommandArguments(command: string): string[];
|
||||
executeConsoleCommand(player: IPlayer, command: string): void;
|
||||
triggerMigration(sourceCityName: string): void;
|
||||
triggerPotentialMigration(sourceCityName: string, chance: number): void;
|
||||
randomEvent(): void;
|
||||
gainActionStats(player: IPlayer, action: IAction, success: boolean): void;
|
||||
getDiplomacyEffectiveness(player: IPlayer): number;
|
||||
getRecruitmentSuccessChance(player: IPlayer): number;
|
||||
getRecruitmentTime(player: IPlayer): number;
|
||||
resetSkillMultipliers(): void;
|
||||
updateSkillMultipliers(): void;
|
||||
completeOperation(success: boolean): void;
|
||||
getActionObject(actionId: IActionIdentifier): IAction | null;
|
||||
completeContract(success: boolean): void;
|
||||
completeAction(player: IPlayer): void;
|
||||
changeRank(player: IPlayer, change: number): void;
|
||||
processAction(player: IPlayer, seconds: number): void;
|
||||
calculateStaminaGainPerSecond(player: IPlayer): number;
|
||||
calculateMaxStamina(player: IPlayer): void;
|
||||
create(): void;
|
||||
process(player: IPlayer): void;
|
||||
}
|
||||
@@ -103,5 +103,28 @@ export class Skill {
|
||||
calculateCost(currentLevel: number): number {
|
||||
return Math.floor((this.baseCost + (currentLevel * this.costInc)) * BitNodeMultipliers.BladeburnerSkillCost);
|
||||
}
|
||||
|
||||
getMultiplier(name: string): number {
|
||||
if(name === "successChanceAll") return this.successChanceAll;
|
||||
if(name === "successChanceStealth") return this.successChanceStealth;
|
||||
if(name === "successChanceKill") return this.successChanceKill;
|
||||
if(name === "successChanceContract") return this.successChanceContract;
|
||||
if(name === "successChanceOperation") return this.successChanceOperation;
|
||||
if(name === "successChanceEstimate") return this.successChanceEstimate;
|
||||
|
||||
if(name === "actionTime") return this.actionTime;
|
||||
|
||||
if(name === "effHack") return this.effHack;
|
||||
if(name === "effStr") return this.effStr;
|
||||
if(name === "effDef") return this.effDef;
|
||||
if(name === "effDex") return this.effDex;
|
||||
if(name === "effAgi") return this.effAgi;
|
||||
if(name === "effCha") return this.effCha;
|
||||
|
||||
if(name === "stamina") return this.stamina;
|
||||
if(name === "money") return this.money;
|
||||
if(name === "expGain") return this.expGain;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
// Action Identifier enum
|
||||
export const ActionTypes = Object.freeze({
|
||||
export const ActionTypes: {
|
||||
[key: string]: number;
|
||||
"Idle": number;
|
||||
"Contract": number;
|
||||
"Operation": number;
|
||||
"BlackOp": number;
|
||||
"BlackOperation": number;
|
||||
"Training": number;
|
||||
"Recruitment": number;
|
||||
"FieldAnalysis": number;
|
||||
"Field Analysis": number;
|
||||
"Diplomacy": number;
|
||||
"Hyperbolic Regeneration Chamber": number;
|
||||
} = {
|
||||
"Idle": 1,
|
||||
"Contract": 2,
|
||||
"Operation": 3,
|
||||
@@ -11,4 +24,4 @@ export const ActionTypes = Object.freeze({
|
||||
"Field Analysis": 7,
|
||||
"Diplomacy": 8,
|
||||
"Hyperbolic Regeneration Chamber": 9,
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
export const ConsoleHelpText: {
|
||||
[key: string]: string[];
|
||||
helpList: string[];
|
||||
automate: string[];
|
||||
clear: string[];
|
||||
|
||||
14
src/Bladeburner/data/Icons.tsx
Normal file
14
src/Bladeburner/data/Icons.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as React from "react";
|
||||
|
||||
export const stealthIcon = <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 166 132" style={{fill:'#adff2f'}}>
|
||||
<g>
|
||||
<path d="M132.658-0.18l-24.321,24.321c-7.915-2.71-16.342-4.392-25.087-4.392c-45.84,0-83,46-83,46 s14.1,17.44,35.635,30.844L12.32,120.158l12.021,12.021L144.68,11.841L132.658-0.18z M52.033,80.445 c-2.104-4.458-3.283-9.438-3.283-14.695c0-19.054,15.446-34.5,34.5-34.5c5.258,0,10.237,1.179,14.695,3.284L52.033,80.445z" />
|
||||
<path d="M134.865,37.656l-18.482,18.482c0.884,3.052,1.367,6.275,1.367,9.612c0,19.055-15.446,34.5-34.5,34.5 c-3.337,0-6.56-0.483-9.611-1.367l-10.124,10.124c6.326,1.725,12.934,2.743,19.735,2.743c45.84,0,83-46,83-46 S153.987,50.575,134.865,37.656z" />
|
||||
</g>
|
||||
</svg>
|
||||
export const killIcon = <svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="-22 0 511 511.99561" style={{fill:'#adff2f'}}>
|
||||
<path d="m.496094 466.242188 39.902344-39.902344 45.753906 45.753906-39.898438 39.902344zm0 0" />
|
||||
<path d="m468.421875 89.832031-1.675781-89.832031-300.265625 300.265625 45.753906 45.753906zm0 0" />
|
||||
<path d="m95.210938 316.785156 16.84375 16.847656h.003906l83.65625 83.65625 22.753906-22.753906-100.503906-100.503906zm0 0" />
|
||||
<path d="m101.445312 365.300781-39.902343 39.902344 45.753906 45.753906 39.902344-39.902343-39.90625-39.902344zm0 0" />
|
||||
</svg>
|
||||
49
src/Bladeburner/ui/AllPages.tsx
Normal file
49
src/Bladeburner/ui/AllPages.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { GeneralActionPage } from "./GeneralActionPage";
|
||||
import { ContractPage } from "./ContractPage";
|
||||
import { OperationPage } from "./OperationPage";
|
||||
import { BlackOpPage } from "./BlackOpPage";
|
||||
import { SkillPage } from "./SkillPage";
|
||||
import { stealthIcon, killIcon } from "../data/Icons";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function AllPages(props: IProps): React.ReactElement {
|
||||
const [page, setPage] = useState('General');
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function Header(props: {name: string}): React.ReactElement {
|
||||
return (<a
|
||||
onClick={()=>setPage(props.name)}
|
||||
className={page !== props.name ?
|
||||
"bladeburner-nav-button" :
|
||||
"bladeburner-nav-button-inactive"}>
|
||||
{props.name}
|
||||
</a>);
|
||||
}
|
||||
return (<>
|
||||
<Header name={'General'} />
|
||||
<Header name={'Contracts'} />
|
||||
<Header name={'Operations'} />
|
||||
<Header name={'BlackOps'} />
|
||||
<Header name={'Skills'} />
|
||||
<div style={{display:"block", margin:"4px", padding:"4px"}}>
|
||||
{page === 'General' && <GeneralActionPage bladeburner={props.bladeburner} player={props.player} />}
|
||||
{page === 'Contracts' && <ContractPage bladeburner={props.bladeburner} player={props.player} />}
|
||||
{page === 'Operations' && <OperationPage bladeburner={props.bladeburner} player={props.player} />}
|
||||
{page === 'BlackOps' && <BlackOpPage bladeburner={props.bladeburner} player={props.player} />}
|
||||
{page === 'Skills' && <SkillPage bladeburner={props.bladeburner} />}
|
||||
</div>
|
||||
<span className="text">{stealthIcon}= This action requires stealth, {killIcon} = This action involves retirement</span>
|
||||
</>);
|
||||
}
|
||||
87
src/Bladeburner/ui/BlackOpElem.tsx
Normal file
87
src/Bladeburner/ui/BlackOpElem.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
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";
|
||||
|
||||
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.getSuccessChance(props.bladeburner, {est:true});
|
||||
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 />
|
||||
<p style={{display:"inline-block"}}>
|
||||
Estimated Success Chance: {formatNumber(estimatedSuccessChance*100, 1)}% {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}
|
||||
<br />
|
||||
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}
|
||||
</p>
|
||||
</>);
|
||||
}
|
||||
36
src/Bladeburner/ui/BlackOpList.tsx
Normal file
36
src/Bladeburner/ui/BlackOpList.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import { BlackOperations } from "../BlackOperations";
|
||||
import { BlackOperation } from "../BlackOperation";
|
||||
import { BlackOpElem } from "./BlackOpElem";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function BlackOpList(props: IProps): React.ReactElement {
|
||||
let blackops: BlackOperation[] = [];
|
||||
for (const blackopName in BlackOperations) {
|
||||
if (BlackOperations.hasOwnProperty(blackopName)) {
|
||||
blackops.push(BlackOperations[blackopName]);
|
||||
}
|
||||
}
|
||||
blackops.sort(function(a, b) {
|
||||
return (a.reqdRank - b.reqdRank);
|
||||
});
|
||||
|
||||
blackops = blackops.filter((blackop: BlackOperation, i: number) => !(props.bladeburner.blackops[blackops[i].name] == null &&
|
||||
i !== 0 &&
|
||||
props.bladeburner.blackops[blackops[i-1].name] == null));
|
||||
|
||||
blackops = blackops.reverse();
|
||||
|
||||
return (<>
|
||||
{blackops.map((blackop: BlackOperation) => <li key={blackop.name} className="bladeburner-action">
|
||||
<BlackOpElem bladeburner={props.bladeburner} action={blackop} player={props.player} />
|
||||
</li>,
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
28
src/Bladeburner/ui/BlackOpPage.tsx
Normal file
28
src/Bladeburner/ui/BlackOpPage.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as React from "react";
|
||||
import { BlackOpList } from "./BlackOpList";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function BlackOpPage(props: IProps): React.ReactElement {
|
||||
return (<>
|
||||
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
|
||||
Black Operations (Black Ops) are special, one-time covert operations.
|
||||
Each Black Op must be unlocked successively by completing
|
||||
the one before it.
|
||||
<br />
|
||||
<br />
|
||||
<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete
|
||||
all of the Black Ops.</b>
|
||||
<br />
|
||||
<br />
|
||||
Like normal operations, you may use a team for Black Ops. Failing
|
||||
a black op will incur heavy HP and rank losses.
|
||||
</p>
|
||||
<BlackOpList bladeburner={props.bladeburner} player={props.player} />
|
||||
</>);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { BlackOperations } from "../BlackOperations";
|
||||
/*
|
||||
if (DomElems.actionsAndSkillsList == null || DomElems.actionsAndSkillsDesc == null) {
|
||||
throw new Error("Bladeburner.createBlackOpsContent called with either " +
|
||||
"DomElems.actionsAndSkillsList or DomElems.actionsAndSkillsDesc = null");
|
||||
}
|
||||
|
||||
DomElems.actionsAndSkillsDesc.innerHTML =
|
||||
"Black Operations (Black Ops) are special, one-time covert operations. " +
|
||||
"Each Black Op must be unlocked successively by completing " +
|
||||
"the one before it.<br><br>" +
|
||||
"<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete " +
|
||||
"all of the Black Ops.</b><br><br>" +
|
||||
"Like normal operations, you may use a team for Black Ops. Failing " +
|
||||
"a black op will incur heavy HP and rank losses.";
|
||||
|
||||
// Put Black Operations in sequence of required rank
|
||||
var blackops = [];
|
||||
for (var blackopName in BlackOperations) {
|
||||
if (BlackOperations.hasOwnProperty(blackopName)) {
|
||||
blackops.push(BlackOperations[blackopName]);
|
||||
}
|
||||
}
|
||||
blackops.sort(function(a, b) {
|
||||
return (a.reqdRank - b.reqdRank);
|
||||
});
|
||||
|
||||
for (var i = blackops.length-1; i >= 0 ; --i) {
|
||||
if (this.blackops[[blackops[i].name]] == null && i !== 0 && this.blackops[[blackops[i-1].name]] == null) {continue;} // If this one nor the next are completed then this isn't unlocked yet.
|
||||
DomElems.blackops[blackops[i].name] = createElement("div", {
|
||||
class:"bladeburner-action", name:blackops[i].name
|
||||
});
|
||||
DomElems.actionsAndSkillsList.appendChild(DomElems.blackops[blackops[i].name]);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
export function BlackOperationsPage(): React.ReactElement {
|
||||
// Put Black Operations in sequence of required rank
|
||||
const blackops = [];
|
||||
for (const name in BlackOperations) {
|
||||
if (BlackOperations.hasOwnProperty(name)) {
|
||||
blackops.push(BlackOperations[name]);
|
||||
}
|
||||
}
|
||||
blackops.sort(function(a, b) {
|
||||
return (a.reqdRank - b.reqdRank);
|
||||
});
|
||||
|
||||
return (<div>
|
||||
<p>
|
||||
Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked successively by completing the one before it.<br /><br />
|
||||
<b>Your ultimate goal to climb through the ranks of Bladeburners is to complete all of the Black Ops.</b><br /><br />
|
||||
Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank losses.</p>
|
||||
{blackops.map(() => <div className="bladeburner-action">
|
||||
</div>,
|
||||
)}
|
||||
</div>)
|
||||
}
|
||||
125
src/Bladeburner/ui/Console.tsx
Normal file
125
src/Bladeburner/ui/Console.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
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 {
|
||||
function isMaxed(): boolean {
|
||||
if(!lastRef.current) return false;
|
||||
const oldTop = lastRef.current.scrollTop;
|
||||
lastRef.current.scrollTop = lastRef.current.scrollHeight;
|
||||
const maxed = oldTop === lastRef.current.scrollTop;
|
||||
lastRef.current.scrollTop = oldTop;
|
||||
return maxed;
|
||||
}
|
||||
if(lastRef.current) {
|
||||
if(isMaxed())
|
||||
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>);
|
||||
}
|
||||
139
src/Bladeburner/ui/ContractElem.tsx
Normal file
139
src/Bladeburner/ui/ContractElem.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
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";
|
||||
|
||||
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.getSuccessChance(props.bladeburner, {est:true});
|
||||
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: {formatNumber(estimatedSuccessChance*100, 1)}% {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}/>
|
||||
</>);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
// Autolevel Checkbox
|
||||
el.appendChild(createElement("br"));
|
||||
var autolevelCheckboxId = "bladeburner-" + action.name + "-autolevel-checkbox";
|
||||
el.appendChild(createElement("label", {
|
||||
for:autolevelCheckboxId, innerText:"Autolevel: ",color:"white",
|
||||
tooltip:"Automatically increase contract level when possible",
|
||||
}));
|
||||
|
||||
const checkboxInput = createElement("input", {
|
||||
type:"checkbox",
|
||||
id: autolevelCheckboxId,
|
||||
checked: action.autoLevel,
|
||||
changeListener: () => {
|
||||
action.autoLevel = checkboxInput.checked;
|
||||
},
|
||||
});
|
||||
|
||||
el.appendChild(checkboxInput);
|
||||
|
||||
*/
|
||||
20
src/Bladeburner/ui/ContractList.tsx
Normal file
20
src/Bladeburner/ui/ContractList.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import { ContractElem } from "./ContractElem";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function ContractList(props: IProps): React.ReactElement {
|
||||
const names = Object.keys(props.bladeburner.contracts);
|
||||
const contracts = props.bladeburner.contracts;
|
||||
return (<>
|
||||
{names.map((name: string) => <li key={name} className="bladeburner-action">
|
||||
<ContractElem bladeburner={props.bladeburner} action={contracts[name]} player={props.player} />
|
||||
</li>,
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
23
src/Bladeburner/ui/ContractPage.tsx
Normal file
23
src/Bladeburner/ui/ContractPage.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as React from "react";
|
||||
import { ContractList } from "./ContractList";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function ContractPage(props: IProps): React.ReactElement {
|
||||
return (<>
|
||||
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
|
||||
Complete contracts in order to increase your Bladeburner rank and earn money.
|
||||
Failing a contract will cause you to lose HP, which can lead to hospitalization.
|
||||
<br />
|
||||
<br />
|
||||
You can unlock higher-level contracts by successfully completing them.
|
||||
Higher-level contracts are more difficult, but grant more rank, experience, and money.
|
||||
</p>
|
||||
<ContractList bladeburner={props.bladeburner} player={props.player} />
|
||||
</>);
|
||||
}
|
||||
47
src/Bladeburner/ui/GeneralActionElem.tsx
Normal file
47
src/Bladeburner/ui/GeneralActionElem.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React, { useState } from "react";
|
||||
import { ActionTypes } from "../data/ActionTypes";
|
||||
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
||||
import { formatNumber } 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);
|
||||
|
||||
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>
|
||||
</>);
|
||||
}
|
||||
26
src/Bladeburner/ui/GeneralActionList.tsx
Normal file
26
src/Bladeburner/ui/GeneralActionList.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
import { GeneralActionElem } from "./GeneralActionElem";
|
||||
import { Action } from "../Action";
|
||||
import { GeneralActions } from "../GeneralActions";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function GeneralActionList(props: IProps): React.ReactElement {
|
||||
const actions: Action[] = [];
|
||||
for (const name in GeneralActions) {
|
||||
if (GeneralActions.hasOwnProperty(name)) {
|
||||
actions.push(GeneralActions[name]);
|
||||
}
|
||||
}
|
||||
return (<>
|
||||
{actions.map((action: Action) => <li key={action.name} className="bladeburner-action">
|
||||
<GeneralActionElem bladeburner={props.bladeburner} action={action} player={props.player} />
|
||||
</li>,
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
19
src/Bladeburner/ui/GeneralActionPage.tsx
Normal file
19
src/Bladeburner/ui/GeneralActionPage.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as React from "react";
|
||||
import { GeneralActionList } from "./GeneralActionList";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function GeneralActionPage(props: IProps): React.ReactElement {
|
||||
return (<>
|
||||
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
|
||||
These are generic actions that will assist you in your Bladeburner
|
||||
duties. They will not affect your Bladeburner rank in any way.
|
||||
</p>
|
||||
<GeneralActionList bladeburner={props.bladeburner} player={props.player} />
|
||||
</>);
|
||||
}
|
||||
133
src/Bladeburner/ui/OperationElem.tsx
Normal file
133
src/Bladeburner/ui/OperationElem.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
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";
|
||||
|
||||
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.getSuccessChance(props.bladeburner, {est:true});
|
||||
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: {formatNumber(estimatedSuccessChance*100, 1)}% {props.action.isStealth?stealthIcon:<></>}{props.action.isKill?killIcon:<></>}<br />
|
||||
Time Required: {convertTimeMsToTimeElapsedString(actionTime*1000)}<br />
|
||||
Operations remaining: {Math.floor(props.action.count)}<br />
|
||||
Successes: {props.action.successes}<br />
|
||||
Failures: {props.action.failures}
|
||||
</pre>
|
||||
<br />
|
||||
<label
|
||||
className="tooltip"
|
||||
style={{color: 'white'}}
|
||||
htmlFor={autolevelCheckboxId}>
|
||||
Autolevel:
|
||||
<span className="tooltiptext">Automatically increase operation level when possible</span>
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={autolevelCheckboxId}
|
||||
checked={props.action.autoLevel}
|
||||
onChange={onAutolevel}/>
|
||||
</>);
|
||||
}
|
||||
20
src/Bladeburner/ui/OperationList.tsx
Normal file
20
src/Bladeburner/ui/OperationList.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
import { OperationElem } from "./OperationElem";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function OperationList(props: IProps): React.ReactElement {
|
||||
const names = Object.keys(props.bladeburner.operations);
|
||||
const operations = props.bladeburner.operations;
|
||||
return (<>
|
||||
{names.map((name: string) => <li key={name} className="bladeburner-action">
|
||||
<OperationElem bladeburner={props.bladeburner} action={operations[name]} player={props.player} />
|
||||
</li>,
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
34
src/Bladeburner/ui/OperationPage.tsx
Normal file
34
src/Bladeburner/ui/OperationPage.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as React from "react";
|
||||
import { OperationList } from "./OperationList";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function OperationPage(props: IProps): React.ReactElement {
|
||||
return (<>
|
||||
<p style={{display: 'block', margin: '4px', padding: '4px'}}>
|
||||
Carry out operations for the Bladeburner division.
|
||||
Failing an operation will reduce your Bladeburner rank. It will also
|
||||
cause you to lose HP, which can lead to hospitalization. In general,
|
||||
operations are harder and more punishing than contracts,
|
||||
but are also more rewarding.
|
||||
<br />
|
||||
<br />
|
||||
Operations can affect the chaos level and Synthoid population of your
|
||||
current city. The exact effects vary between different Operations.
|
||||
<br />
|
||||
<br />
|
||||
For operations, you can use a team. You must first recruit team members.
|
||||
Having a larger team will improves your chances of success.
|
||||
<br />
|
||||
<br />
|
||||
You can unlock higher-level operations by successfully completing them.
|
||||
Higher-level operations are more difficult, but grant more rank and experience.
|
||||
</p>
|
||||
<OperationList bladeburner={props.bladeburner} player={props.player} />
|
||||
</>);
|
||||
}
|
||||
28
src/Bladeburner/ui/Root.tsx
Normal file
28
src/Bladeburner/ui/Root.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from "react";
|
||||
import { Stats } from "./Stats";
|
||||
import { Console } from "./Console";
|
||||
import { AllPages } from "./AllPages";
|
||||
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { IEngine } from "../../IEngine";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
engine: IEngine;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function Root(props: IProps): React.ReactElement {
|
||||
return (<div id="bladeburner-container">
|
||||
<div style={{height:"60%", display:"block", position:"relative"}}>
|
||||
<div style={{height: '100%', width:"30%", display:"inline-block", border:"1px solid white"}}>
|
||||
<Stats bladeburner={props.bladeburner} player={props.player} engine={props.engine} />
|
||||
</div>
|
||||
<Console bladeburner={props.bladeburner} player={props.player} />
|
||||
</div>
|
||||
<div style={{width:"70%", display:"block", border:"1px solid white", marginTop:"6px", padding: "6px", position:"relative"}}>
|
||||
<AllPages bladeburner={props.bladeburner} player={props.player} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
48
src/Bladeburner/ui/SkillElem.tsx
Normal file
48
src/Bladeburner/ui/SkillElem.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import { CopyableText } from "../../ui/React/CopyableText";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
skill: any;
|
||||
bladeburner: IBladeburner;
|
||||
onUpgrade: () => void;
|
||||
}
|
||||
|
||||
export function SkillElem(props: IProps): React.ReactElement {
|
||||
const skillName = props.skill.name;
|
||||
let currentLevel = 0;
|
||||
if (props.bladeburner.skills[skillName] && !isNaN(props.bladeburner.skills[skillName])) {
|
||||
currentLevel = props.bladeburner.skills[skillName];
|
||||
}
|
||||
const pointCost = props.skill.calculateCost(currentLevel);
|
||||
|
||||
const canLevel = props.bladeburner.skillPoints >= pointCost;
|
||||
const maxLvl = props.skill.maxLvl ? currentLevel >= props.skill.maxLvl : false;
|
||||
|
||||
function onClick(): void {
|
||||
if (props.bladeburner.skillPoints < pointCost) return;
|
||||
props.bladeburner.skillPoints -= pointCost;
|
||||
props.bladeburner.upgradeSkill(props.skill);
|
||||
props.onUpgrade();
|
||||
}
|
||||
|
||||
return (<>
|
||||
<h2 style={{display: 'inline-block'}}>
|
||||
<CopyableText value={props.skill.name} />
|
||||
</h2>
|
||||
<a
|
||||
onClick={onClick}
|
||||
style={{display: "inline-block", margin: "3px", padding: "3px"}}
|
||||
className={canLevel && !maxLvl ? "a-link-button" : "a-link-button-inactive"}>
|
||||
Level
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
<p style={{display: 'block'}}>Level: {currentLevel}</p>
|
||||
{maxLvl ?
|
||||
<p style={{color:"red", display:"block"}}>MAX LEVEL</p> :
|
||||
<p style={{display:"block"}}>Skill Points required: {formatNumber(pointCost, 0)}</p>}
|
||||
<p style={{display:"inline-block"}} dangerouslySetInnerHTML={{__html: props.skill.desc}} />
|
||||
</>);
|
||||
}
|
||||
18
src/Bladeburner/ui/SkillList.tsx
Normal file
18
src/Bladeburner/ui/SkillList.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as React from "react";
|
||||
import { SkillElem } from "./SkillElem";
|
||||
import { Skills } from "../Skills";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
onUpgrade: () => void;
|
||||
}
|
||||
|
||||
export function SkillList(props: IProps): React.ReactElement {
|
||||
return (<>
|
||||
{Object.keys(Skills).map((skill: string) => <li key={skill} className="bladeburner-action">
|
||||
<SkillElem bladeburner={props.bladeburner} skill={Skills[skill]} onUpgrade={props.onUpgrade} />
|
||||
</li>,
|
||||
)}
|
||||
</>);
|
||||
}
|
||||
70
src/Bladeburner/ui/SkillPage.tsx
Normal file
70
src/Bladeburner/ui/SkillPage.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { useState } from "react";
|
||||
import { SkillList } from "./SkillList";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
}
|
||||
|
||||
|
||||
export function SkillPage(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const mults = props.bladeburner.skillMultipliers;
|
||||
|
||||
function valid(mult: any): boolean {
|
||||
return mult && mult !== 1
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p>
|
||||
<strong>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</strong>
|
||||
</p>
|
||||
<p>
|
||||
You will gain one skill point every {BladeburnerConstants.RanksPerSkillPoint} ranks.
|
||||
<br />
|
||||
<br />
|
||||
Note that when upgrading a skill, the benefit for that skill is additive.
|
||||
However, the effects of different skills with each other is multiplicative.
|
||||
<br />
|
||||
</p>
|
||||
<br />
|
||||
{valid(mults["successChanceAll"]) && <p>Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)}</p>}
|
||||
{valid(mults["successChanceStealth"]) && <p>Stealth Success Chance: x{formatNumber(mults["successChanceStealth"], 3)}</p>}
|
||||
{valid(mults["successChanceKill"]) && <p>Retirement Success Chance: x{formatNumber(mults["successChanceKill"], 3)}</p>}
|
||||
{valid(mults["successChanceContract"]) && <p>Contract Success Chance: x{formatNumber(mults["successChanceContract"], 3)}</p>}
|
||||
{valid(mults["successChanceOperation"]) && <p>Operation Success Chance: x{formatNumber(mults["successChanceOperation"], 3)}</p>}
|
||||
{valid(mults["successChanceEstimate"]) && <p>Synthoid Data Estimate: x{formatNumber(mults["successChanceEstimate"], 3)}</p>}
|
||||
{valid(mults["actionTime"]) && <p>Action Time: x{formatNumber(mults["actionTime"], 3)}</p>}
|
||||
{valid(mults["effHack"]) && <p>Hacking Skill: x{formatNumber(mults["effHack"], 3)}</p>}
|
||||
{valid(mults["effStr"]) && <p>Strength: x{formatNumber(mults["effStr"], 3)}</p>}
|
||||
{valid(mults["effDef"]) && <p>Defense: x{formatNumber(mults["effDef"], 3)}</p>}
|
||||
{valid(mults["effDex"]) && <p>Dexterity: x{formatNumber(mults["effDex"], 3)}</p>}
|
||||
{valid(mults["effAgi"]) && <p>Agility: x{formatNumber(mults["effAgi"], 3)}</p>}
|
||||
{valid(mults["effCha"]) && <p>Charisma: x{formatNumber(mults["effCha"], 3)}</p>}
|
||||
{valid(mults["effInt"]) && <p>Intelligence: x{formatNumber(mults["effInt"], 3)}</p>}
|
||||
{valid(mults["stamina"]) && <p>Stamina: x{formatNumber(mults["stamina"], 3)}</p>}
|
||||
{valid(mults["money"]) && <p>Contract Money: x{formatNumber(mults["money"], 3)}</p>}
|
||||
{valid(mults["expGain"]) && <p>Exp Gain: x{formatNumber(mults["expGain"], 3)}</p>}
|
||||
<br />
|
||||
<SkillList bladeburner={props.bladeburner} onUpgrade={() => setRerender(old => !old)} />
|
||||
</>);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
|
||||
var multKeys = Object.keys(this.skillMultipliers);
|
||||
for (var i = 0; i < multKeys.length; ++i) {
|
||||
var mult = this.skillMultipliers[multKeys[i]];
|
||||
if (mult && mult !== 1) {
|
||||
mult = formatNumber(mult, 3);
|
||||
switch(multKeys[i]) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
139
src/Bladeburner/ui/Stats.tsx
Normal file
139
src/Bladeburner/ui/Stats.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
formatNumber,
|
||||
convertTimeMsToTimeElapsedString,
|
||||
} from "../../../utils/StringHelperFunctions";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { IEngine } from "../../IEngine";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { StatsTable } from "../../ui/React/StatsTable";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import {
|
||||
joinFaction,
|
||||
displayFactionContent,
|
||||
} from "../../Faction/FactionHelpers";
|
||||
import { IBladeburner } from "../IBladeburner"
|
||||
|
||||
import { TravelPopup } from "./TravelPopup";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
engine: IEngine;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function Stats(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function openStaminaHelp(): void {
|
||||
dialogBoxCreate("Performing actions will use up your stamina.<br><br>" +
|
||||
"Your max stamina is determined primarily by your agility stat.<br><br>" +
|
||||
"Your stamina gain rate is determined by both your agility and your " +
|
||||
"max stamina. Higher max stamina leads to a higher gain rate.<br><br>" +
|
||||
"Once your " +
|
||||
"stamina falls below 50% of its max value, it begins to negatively " +
|
||||
"affect the success rate of your contracts/operations. This penalty " +
|
||||
"is shown in the overview panel. If the penalty is 15%, then this means " +
|
||||
"your success rate would be multipled by 85% (100 - 15).<br><br>" +
|
||||
"Your max stamina and stamina gain rate can also be increased by " +
|
||||
"training, or through skills and Augmentation upgrades.");
|
||||
}
|
||||
|
||||
function openPopulationHelp(): void {
|
||||
dialogBoxCreate("The success rate of your contracts/operations depends on " +
|
||||
"the population of Synthoids in your current city. " +
|
||||
"The success rate that is shown to you is only an estimate, " +
|
||||
"and it is based on your Synthoid population estimate.<br><br>" +
|
||||
"Therefore, it is important that this Synthoid population estimate " +
|
||||
"is accurate so that you have a better idea of your " +
|
||||
"success rate for contracts/operations. Certain " +
|
||||
"actions will increase the accuracy of your population " +
|
||||
"estimate.<br><br>" +
|
||||
"The Synthoid populations of cities can change due to your " +
|
||||
"actions or random events. If random events occur, they will " +
|
||||
"be logged in the Bladeburner Console.");
|
||||
}
|
||||
|
||||
function openTravel(): void {
|
||||
const popupId = "bladeburner-travel-popup";
|
||||
createPopup(popupId, TravelPopup, {
|
||||
bladeburner: props.bladeburner,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
function openFaction(): void {
|
||||
const faction = Factions["Bladeburners"];
|
||||
if (faction.isMember) {
|
||||
props.engine.loadFactionContent();
|
||||
displayFactionContent("Bladeburners");
|
||||
} else {
|
||||
if (props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction) {
|
||||
joinFaction(faction);
|
||||
dialogBoxCreate("Congratulations! You were accepted into the Bladeburners faction");
|
||||
} else {
|
||||
dialogBoxCreate("You need a rank of 25 to join the Bladeburners Faction!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p className="tooltip" style={{display: 'inline-block'}}>
|
||||
Rank: {formatNumber(props.bladeburner.rank, 2)}
|
||||
<span className="tooltiptext">Your rank within the Bladeburner division.</span>
|
||||
</p><br />
|
||||
<p>Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)}</p>
|
||||
<div className="help-tip" onClick={openStaminaHelp}>?</div><br />
|
||||
<p>Stamina Penalty: {formatNumber((1-props.bladeburner.calculateStaminaPenalty())*100, 1)}%</p><br />
|
||||
<p>Team Size: {formatNumber(props.bladeburner.teamSize, 0)}</p>
|
||||
<p>Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}</p><br />
|
||||
<p>Num Times Hospitalized: {props.bladeburner.numHosp}</p>
|
||||
<p>Money Lost From Hospitalizations: {Money(props.bladeburner.moneyLost)}</p><br />
|
||||
<p>Current City: {props.bladeburner.city}</p>
|
||||
<p className="tooltip" style={{display: 'inline-block'}}>
|
||||
Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)}
|
||||
<span className="tooltiptext">This is your Bladeburner division's estimate of how many Synthoids exist in your current city.</span>
|
||||
</p>
|
||||
<div className="help-tip" onClick={openPopulationHelp}>?</div><br />
|
||||
<p className="tooltip" style={{display: 'inline-block'}}>
|
||||
Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)}
|
||||
<span className="tooltiptext">This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city.</span>
|
||||
</p><br />
|
||||
<p className="tooltip" style={{display: 'inline-block'}}>
|
||||
City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)}
|
||||
<span className="tooltiptext">The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a chaos level can make contracts and operations harder.</span>
|
||||
</p><br />
|
||||
<br />
|
||||
<p className="tooltip" style={{display: 'inline-block'}}>
|
||||
Bonus time: {convertTimeMsToTimeElapsedString(props.bladeburner.storedCycles/BladeburnerConstants.CyclesPerSecond*1000)}<br />
|
||||
<span className="tooltiptext">
|
||||
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by browser).
|
||||
Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed.
|
||||
</span>
|
||||
</p>
|
||||
<p>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</p><br />
|
||||
{StatsTable([
|
||||
["Aug. Success Chance mult: ", formatNumber(props.player.bladeburner_success_chance_mult*100, 1) + "%"],
|
||||
["Aug. Max Stamina mult: ", formatNumber(props.player.bladeburner_max_stamina_mult*100, 1) + "%"],
|
||||
["Aug. Stamina Gain mult: ", formatNumber(props.player.bladeburner_stamina_gain_mult*100, 1) + "%"],
|
||||
["Aug. Field Analysis mult: ", formatNumber(props.player.bladeburner_analysis_mult*100, 1) + "%"],
|
||||
])}
|
||||
<br />
|
||||
<a onClick={openTravel} className="a-link-button" style={{display:"inline-block"}}>Travel</a>
|
||||
<a onClick={openFaction} className="a-link-button tooltip" style={{display:"inline-block"}}>
|
||||
<span className="tooltiptext">Apply to the Bladeburner Faction, or go to the faction page if you are already a member</span>
|
||||
Faction
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
</>);
|
||||
}
|
||||
37
src/Bladeburner/ui/TeamSizePopup.tsx
Normal file
37
src/Bladeburner/ui/TeamSizePopup.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { useState } from "react";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { Action } from "../Action";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
action: Action;
|
||||
popupId: string;
|
||||
}
|
||||
|
||||
export function TeamSizePopup(props: IProps): React.ReactElement {
|
||||
const [teamSize, setTeamSize] = useState<number | undefined>();
|
||||
|
||||
function confirmTeamSize(): void {
|
||||
if(teamSize === undefined) return;
|
||||
const num = Math.round(teamSize);
|
||||
if (isNaN(num) || num < 0) {
|
||||
dialogBoxCreate("Invalid value entered for number of Team Members (must be numeric, positive)")
|
||||
} else {
|
||||
props.action.teamCount = num;
|
||||
}
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p>
|
||||
Enter the amount of team members you would like to take on this
|
||||
Op. If you do not have the specified number of team members,
|
||||
then as many as possible will be used. Note that team members may
|
||||
be lost during operations.
|
||||
</p>
|
||||
<input autoFocus type="number" placeholder= "Team size" className= "text-input" onChange={event => setTeamSize(parseFloat(event.target.value))} />
|
||||
<a className="a-link-button" onClick={confirmTeamSize}>Confirm</a>
|
||||
</>);
|
||||
}
|
||||
32
src/Bladeburner/ui/TravelPopup.tsx
Normal file
32
src/Bladeburner/ui/TravelPopup.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { BladeburnerConstants } from "../data/Constants";
|
||||
import { IBladeburner } from "../IBladeburner";
|
||||
|
||||
interface IProps {
|
||||
bladeburner: IBladeburner;
|
||||
popupId: string;
|
||||
}
|
||||
|
||||
export function TravelPopup(props: IProps): React.ReactElement {
|
||||
function travel(city: string): void {
|
||||
props.bladeburner.city = city;
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p>
|
||||
Travel to a different city for your Bladeburner
|
||||
activities. This does not cost any money. The city you are
|
||||
in for your Bladeburner duties does not affect
|
||||
your location in the game otherwise.
|
||||
</p>
|
||||
{BladeburnerConstants.CityNames.map(city => {
|
||||
// Reusing this css class...it adds a border and makes it
|
||||
// so that background color changes when you hover
|
||||
return <div key={city} className="cmpy-mgmt-find-employee-option"
|
||||
onClick={() => travel(city)}>
|
||||
{city}
|
||||
</div>})}
|
||||
</>);
|
||||
}
|
||||
@@ -172,7 +172,7 @@ export class CodingContract {
|
||||
async prompt(): Promise<CodingContractResult> {
|
||||
const popupId = `coding-contract-prompt-popup-${this.fn}`;
|
||||
return new Promise<CodingContractResult>((resolve) => {
|
||||
const popup = new CodingContractPopup({
|
||||
createPopup(popupId, CodingContractPopup, {
|
||||
c: this,
|
||||
popupId: popupId,
|
||||
onClose: () => {
|
||||
@@ -188,7 +188,6 @@ export class CodingContract {
|
||||
removePopup(popupId);
|
||||
},
|
||||
});
|
||||
createPopup(popupId, CodingContractPopup, popup.props);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
import { IMap } from "./types";
|
||||
|
||||
export const CONSTANTS: IMap<any> = {
|
||||
Version: "0.52.2",
|
||||
Version: "0.52.5",
|
||||
|
||||
// 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
|
||||
@@ -225,18 +228,30 @@ export const CONSTANTS: IMap<any> = {
|
||||
TotalNumBitNodes: 24,
|
||||
|
||||
LatestUpdate: `
|
||||
v0.52.2 - 2021-07-15 Oh yeah, BN11 is a thing (drunk hydroflame tbh)
|
||||
v0.52.5 - 2021-07-19 CPU cores are useful!? (hydroflame)
|
||||
-------------------------------------------
|
||||
|
||||
** Source-Files **
|
||||
** Terminal **
|
||||
|
||||
* Source-File 11 now also provides a small reduction to the price increase
|
||||
multiplier.
|
||||
* When executing 'run SCRIPT' any script can now add '--tail' to
|
||||
automatically bring up the logs.
|
||||
|
||||
** Augmentations **
|
||||
** Netscript **
|
||||
|
||||
* New Augmentation offered by Aevum, themed around 777 and offers some basic
|
||||
programs.
|
||||
* Augmentation descriptions are now more concise and consistent.
|
||||
* 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
|
||||
`,
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
2
src/Faction/FactionHelpers.d.ts
vendored
2
src/Faction/FactionHelpers.d.ts
vendored
@@ -5,3 +5,5 @@ export declare function getNextNeurofluxLevel(): number;
|
||||
export declare function hasAugmentationPrereqs(aug: Augmentation): boolean;
|
||||
export declare function purchaseAugmentationBoxCreate(aug: Augmentation, fac: Faction): void;
|
||||
export declare function purchaseAugmentation(aug: Augmentation, fac: Faction, sing?: boolean): void;
|
||||
export declare function displayFactionContent(factionName: string, initiallyOnAugmentationsPage: boolean=false);
|
||||
export declare function joinFaction(faction: Faction): void;
|
||||
@@ -46,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();
|
||||
|
||||
50
src/Faction/ui/FactionList.tsx
Normal file
50
src/Faction/ui/FactionList.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { useState } from 'react';
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { IEngine } from "../../IEngine";
|
||||
import { Factions } from "../Factions";
|
||||
import { displayFactionContent, joinFaction } from "../FactionHelpers";
|
||||
|
||||
interface IProps {
|
||||
player: IPlayer;
|
||||
engine: IEngine;
|
||||
}
|
||||
|
||||
export function FactionList(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
function openFaction(faction: string): void {
|
||||
props.engine.loadFactionContent();
|
||||
displayFactionContent(faction);
|
||||
}
|
||||
|
||||
function acceptInvitation(event: React.MouseEvent<HTMLElement>, faction: string): void {
|
||||
if (!event.isTrusted) return;
|
||||
joinFaction(Factions[faction]);
|
||||
setRerender(x => !x);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<h1>Factions</h1>
|
||||
<p>Lists all factions you have joined</p>
|
||||
<br />
|
||||
<ul>
|
||||
{props.player.factions.map((faction: string) => <li key={faction}><a
|
||||
className="a-link-button"
|
||||
onClick={() => openFaction(faction)}
|
||||
style={{padding:"4px", margin:"4px", display:"inline-block"}}>{faction}
|
||||
</a></li>)}
|
||||
</ul>
|
||||
<br />
|
||||
<h1>Outstanding Faction Invitations</h1>
|
||||
<p style={{width: '70%'}}>Lists factions you have been invited to. You can accept these faction invitations at any time.</p>
|
||||
<ul>
|
||||
{props.player.factionInvitations.map((faction: string) => <li key={faction} style={{padding:"6px", margin:"6px"}}>
|
||||
<p style={{display:"inline", margin:"4px", padding:"4px"}}>{faction}</p>
|
||||
<a
|
||||
className="a-link-button"
|
||||
onClick={(e) => acceptInvitation(e, faction)}
|
||||
style={{display:"inline", margin:"4px", padding:"4px"}}>Accept Faction Invitation</a>
|
||||
</li>)}
|
||||
</ul>
|
||||
</>);
|
||||
}
|
||||
1991
src/Gang.jsx
1991
src/Gang.jsx
File diff suppressed because it is too large
Load Diff
76
src/Gang/AllGangs.ts
Normal file
76
src/Gang/AllGangs.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
interface GangTerritory {
|
||||
power: number;
|
||||
territory: number;
|
||||
}
|
||||
|
||||
export let AllGangs: {
|
||||
[key: string]: GangTerritory;
|
||||
} = {
|
||||
"Slum Snakes" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Tetrads" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Syndicate" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Dark Army" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Speakers for the Dead" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"NiteSec" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Black Hand" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
}
|
||||
|
||||
export function resetGangs(): void {
|
||||
AllGangs = {
|
||||
"Slum Snakes" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Tetrads" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Syndicate" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Dark Army" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Speakers for the Dead" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"NiteSec" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Black Hand" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function loadAllGangs(saveString: string): void {
|
||||
AllGangs = JSON.parse(saveString, Reviver);
|
||||
}
|
||||
411
src/Gang/Gang.ts
Normal file
411
src/Gang/Gang.ts
Normal file
@@ -0,0 +1,411 @@
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Add police clashes
|
||||
* balance point to keep them from running out of control
|
||||
*/
|
||||
|
||||
import { Faction } from "../Faction/Faction";
|
||||
import { Factions } from "../Faction/Factions";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import {
|
||||
Reviver,
|
||||
Generic_toJSON,
|
||||
Generic_fromJSON,
|
||||
} from "../../utils/JSONReviver";
|
||||
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
import { GangConstants } from "./data/Constants";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { GangMemberTasks } from "./GangMemberTasks";
|
||||
import { IAscensionResult } from "./IAscensionResult";
|
||||
|
||||
import { AllGangs } from "./AllGangs";
|
||||
import { GangMember } from "./GangMember";
|
||||
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
|
||||
|
||||
export class Gang {
|
||||
facName: string;
|
||||
members: GangMember[];
|
||||
wanted: number;
|
||||
respect: number;
|
||||
|
||||
isHackingGang: boolean;
|
||||
|
||||
respectGainRate: number;
|
||||
wantedGainRate: number;
|
||||
moneyGainRate: number;
|
||||
|
||||
storedCycles: number;
|
||||
|
||||
storedTerritoryAndPowerCycles: number;
|
||||
|
||||
territoryClashChance: number;
|
||||
territoryWarfareEngaged: boolean;
|
||||
|
||||
notifyMemberDeath: boolean;
|
||||
|
||||
constructor(facName = "", hacking = false) {
|
||||
this.facName = facName;
|
||||
this.members = [];
|
||||
this.wanted = 1;
|
||||
this.respect = 1;
|
||||
|
||||
this.isHackingGang = hacking;
|
||||
|
||||
this.respectGainRate = 0;
|
||||
this.wantedGainRate = 0;
|
||||
this.moneyGainRate = 0;
|
||||
|
||||
// When processing gains, this stores the number of cycles until some
|
||||
// limit is reached, and then calculates and applies the gains only at that limit
|
||||
this.storedCycles = 0;
|
||||
|
||||
// Separate variable to keep track of cycles for Territry + Power gang, which
|
||||
// happens on a slower "clock" than normal processing
|
||||
this.storedTerritoryAndPowerCycles = 0;
|
||||
|
||||
this.territoryClashChance = 0;
|
||||
this.territoryWarfareEngaged = false;
|
||||
|
||||
this.notifyMemberDeath = true;
|
||||
}
|
||||
|
||||
getPower(): number {
|
||||
return AllGangs[this.facName].power;
|
||||
}
|
||||
|
||||
getTerritory(): number {
|
||||
return AllGangs[this.facName].territory;
|
||||
}
|
||||
|
||||
process(numCycles = 1, player: IPlayer): void {
|
||||
const CyclesPerSecond = 1000 / CONSTANTS._idleSpeed;
|
||||
|
||||
if (isNaN(numCycles)) {
|
||||
console.error(`NaN passed into Gang.process(): ${numCycles}`);
|
||||
}
|
||||
this.storedCycles += numCycles;
|
||||
|
||||
// Only process if there are at least 2 seconds, and at most 5 seconds
|
||||
if (this.storedCycles < 2 * CyclesPerSecond) return;
|
||||
const cycles = Math.min(this.storedCycles, 5 * CyclesPerSecond);
|
||||
|
||||
try {
|
||||
this.processGains(cycles, player);
|
||||
this.processExperienceGains(cycles);
|
||||
this.processTerritoryAndPowerGains(cycles);
|
||||
this.storedCycles -= cycles;
|
||||
} catch(e) {
|
||||
console.error(`Exception caught when processing Gang: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
processGains(numCycles = 1, player: IPlayer): void {
|
||||
// Get gains per cycle
|
||||
let moneyGains = 0;
|
||||
let respectGains = 0;
|
||||
let wantedLevelGains = 0;
|
||||
let justice = 0;
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
respectGains += (this.members[i].calculateRespectGain(this));
|
||||
moneyGains += (this.members[i].calculateMoneyGain(this));
|
||||
const wantedLevelGain = this.members[i].calculateWantedLevelGain(this);
|
||||
wantedLevelGains += wantedLevelGain;
|
||||
if(this.members[i].getTask().baseWanted < 0) justice++; // this member is lowering wanted.
|
||||
}
|
||||
this.respectGainRate = respectGains;
|
||||
this.wantedGainRate = wantedLevelGains;
|
||||
this.moneyGainRate = moneyGains;
|
||||
|
||||
const gain = respectGains * numCycles;
|
||||
this.respect += gain;
|
||||
// Faction reputation gains is respect gain divided by some constant
|
||||
const fac = Factions[this.facName];
|
||||
if (!(fac instanceof Faction)) {
|
||||
dialogBoxCreate("ERROR: Could not get Faction associates with your gang. This is a bug, please report to game dev");
|
||||
throw new Error('Could not find the faction associated with this gang.');
|
||||
}
|
||||
const favorMult = 1 + (fac.favor / 100);
|
||||
fac.playerReputation += ((player.faction_rep_mult * gain * favorMult) / GangConstants.GangRespectToReputationRatio);
|
||||
|
||||
// Keep track of respect gained per member
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
this.members[i].recordEarnedRespect(numCycles, this);
|
||||
}
|
||||
if (!(this.wanted === 1 && wantedLevelGains < 0)) {
|
||||
const oldWanted = this.wanted;
|
||||
let newWanted = oldWanted + (wantedLevelGains * numCycles);
|
||||
newWanted = newWanted * (1 - justice * 0.001); // safeguard
|
||||
// Prevent overflow
|
||||
if (wantedLevelGains <= 0 && newWanted > oldWanted) newWanted = 1;
|
||||
|
||||
this.wanted = newWanted;
|
||||
if (this.wanted < 1) this.wanted = 1;
|
||||
}
|
||||
player.gainMoney(moneyGains * numCycles);
|
||||
player.recordMoneySource(moneyGains * numCycles, "gang");
|
||||
}
|
||||
|
||||
processTerritoryAndPowerGains(numCycles = 1): void {
|
||||
this.storedTerritoryAndPowerCycles += numCycles;
|
||||
if (this.storedTerritoryAndPowerCycles < GangConstants.CyclesPerTerritoryAndPowerUpdate) return;
|
||||
this.storedTerritoryAndPowerCycles -= GangConstants.CyclesPerTerritoryAndPowerUpdate;
|
||||
|
||||
// Process power first
|
||||
const gangName = this.facName;
|
||||
for (const name in AllGangs) {
|
||||
if (AllGangs.hasOwnProperty(name)) {
|
||||
if (name == gangName) {
|
||||
AllGangs[name].power += this.calculatePower();
|
||||
} else {
|
||||
// All NPC gangs get random power gains
|
||||
const gainRoll = Math.random();
|
||||
if (gainRoll < 0.5) {
|
||||
// Multiplicative gain (50% chance)
|
||||
// This is capped per cycle, to prevent it from getting out of control
|
||||
const multiplicativeGain = AllGangs[name].power * 0.005;
|
||||
AllGangs[name].power += Math.min(0.85, multiplicativeGain);
|
||||
} else {
|
||||
// Additive gain (50% chance)
|
||||
const additiveGain = 0.75 * gainRoll * AllGangs[name].territory;
|
||||
AllGangs[name].power += (additiveGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if territory should be processed
|
||||
if (this.territoryWarfareEngaged) {
|
||||
this.territoryClashChance = 1;
|
||||
} else if (this.territoryClashChance > 0) {
|
||||
// Engagement turned off, but still a positive clash chance. So there's
|
||||
// still a chance of clashing but it slowly goes down over time
|
||||
this.territoryClashChance = Math.max(0, this.territoryClashChance - 0.01);
|
||||
}
|
||||
|
||||
// Then process territory
|
||||
for (let i = 0; i < GangConstants.Names.length; ++i) {
|
||||
const others = GangConstants.Names.filter((e) => {
|
||||
return e !== GangConstants.Names[i];
|
||||
});
|
||||
const other = getRandomInt(0, others.length - 1);
|
||||
|
||||
const thisGang = GangConstants.Names[i];
|
||||
const otherGang = others[other];
|
||||
|
||||
// If either of the gangs involved in this clash is the player, determine
|
||||
// whether to skip or process it using the clash chance
|
||||
if (thisGang === gangName || otherGang === gangName) {
|
||||
if (!(Math.random() < this.territoryClashChance)) continue;
|
||||
}
|
||||
|
||||
const thisPwr = AllGangs[thisGang].power;
|
||||
const otherPwr = AllGangs[otherGang].power;
|
||||
const thisChance = thisPwr / (thisPwr + otherPwr);
|
||||
|
||||
function calculateTerritoryGain(winGang: string, loseGang: string): number {
|
||||
const powerBonus = Math.max(1, 1+Math.log(AllGangs[winGang].power/AllGangs[loseGang].power)/Math.log(50));
|
||||
const gains = Math.min(AllGangs[loseGang].territory, powerBonus*0.0001*(Math.random()+.5))
|
||||
return gains;
|
||||
}
|
||||
|
||||
if (Math.random() < thisChance) {
|
||||
if (AllGangs[otherGang].territory <= 0) return;
|
||||
const territoryGain = calculateTerritoryGain(thisGang, otherGang);
|
||||
AllGangs[thisGang].territory += territoryGain;
|
||||
AllGangs[otherGang].territory -= territoryGain;
|
||||
if (thisGang === gangName) {
|
||||
this.clash(true); // Player won
|
||||
AllGangs[otherGang].power *= (1 / 1.01);
|
||||
} else if (otherGang === gangName) {
|
||||
this.clash(false); // Player lost
|
||||
} else {
|
||||
AllGangs[otherGang].power *= (1 / 1.01);
|
||||
}
|
||||
} else {
|
||||
if (AllGangs[thisGang].territory <= 0) return;
|
||||
const territoryGain = calculateTerritoryGain(otherGang, thisGang);
|
||||
AllGangs[thisGang].territory -= territoryGain;
|
||||
AllGangs[otherGang].territory += territoryGain;
|
||||
if (thisGang === gangName) {
|
||||
this.clash(false); // Player lost
|
||||
} else if (otherGang === gangName) {
|
||||
this.clash(true); // Player won
|
||||
AllGangs[thisGang].power *= (1 / 1.01);
|
||||
} else {
|
||||
AllGangs[thisGang].power *= (1 / 1.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processExperienceGains(numCycles = 1): void {
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
this.members[i].gainExperience(numCycles);
|
||||
this.members[i].updateSkillLevels();
|
||||
}
|
||||
}
|
||||
|
||||
clash(won = false): void {
|
||||
// Determine if a gang member should die
|
||||
let baseDeathChance = 0.01;
|
||||
if (won) baseDeathChance /= 2;
|
||||
|
||||
// If the clash was lost, the player loses a small percentage of power
|
||||
else AllGangs[this.facName].power *= (1 / 1.008);
|
||||
|
||||
// Deaths can only occur during X% of clashes
|
||||
if (Math.random() < 0.65) return;
|
||||
|
||||
for (let i = this.members.length - 1; i >= 0; --i) {
|
||||
const member = this.members[i];
|
||||
|
||||
// Only members assigned to Territory Warfare can die
|
||||
if (member.task !== "Territory Warfare") continue;
|
||||
|
||||
// Chance to die is decreased based on defense
|
||||
const modifiedDeathChance = baseDeathChance / Math.pow(member.def, 0.6);
|
||||
if (Math.random() < modifiedDeathChance) {
|
||||
this.killMember(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canRecruitMember(): boolean {
|
||||
if (this.members.length >= GangConstants.MaximumGangMembers) return false;
|
||||
return (this.respect >= this.getRespectNeededToRecruitMember());
|
||||
}
|
||||
|
||||
getRespectNeededToRecruitMember(): number {
|
||||
// First N gang members are free (can be recruited at 0 respect)
|
||||
const numFreeMembers = 3;
|
||||
if (this.members.length < numFreeMembers) return 0;
|
||||
|
||||
const i = this.members.length - (numFreeMembers - 1);
|
||||
return Math.pow(5, i);
|
||||
}
|
||||
|
||||
recruitMember(name: string): boolean {
|
||||
name = String(name);
|
||||
if (name === "" || !this.canRecruitMember()) return false;
|
||||
|
||||
// Check for already-existing names
|
||||
const sameNames = this.members.filter((m) => m.name === name);
|
||||
if (sameNames.length >= 1) return false;
|
||||
|
||||
const member = new GangMember(name);
|
||||
this.members.push(member);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Money and Respect gains multiplied by this number (< 1)
|
||||
getWantedPenalty(): number {
|
||||
return (this.respect) / (this.respect + this.wanted);
|
||||
}
|
||||
|
||||
|
||||
//Calculates power GAIN, which is added onto the Gang's existing power
|
||||
calculatePower(): number {
|
||||
let memberTotal = 0;
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
if (!GangMemberTasks.hasOwnProperty(this.members[i].task) ||
|
||||
this.members[i].task !== "Territory Warfare") continue;
|
||||
memberTotal += this.members[i].calculatePower();
|
||||
}
|
||||
return (0.015 * Math.max(0.002, this.getTerritory()) * memberTotal);
|
||||
}
|
||||
|
||||
|
||||
killMember(member: GangMember): void {
|
||||
// Player loses a percentage of total respect, plus whatever respect that member has earned
|
||||
const totalRespect = this.respect;
|
||||
const lostRespect = (0.05 * totalRespect) + member.earnedRespect;
|
||||
this.respect = Math.max(0, totalRespect - lostRespect);
|
||||
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
if (member.name === this.members[i].name) {
|
||||
this.members.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify of death
|
||||
if (this.notifyMemberDeath) {
|
||||
dialogBoxCreate(`${member.name} was killed in a gang clash! You lost ${lostRespect} respect`);
|
||||
}
|
||||
}
|
||||
|
||||
ascendMember(member: GangMember, workerScript?: WorkerScript): IAscensionResult {
|
||||
try {
|
||||
const res = member.ascend();
|
||||
this.respect = Math.max(1, this.respect - res.respect);
|
||||
if (workerScript) {
|
||||
workerScript.log('ascend', `Ascended Gang member ${member.name}`);
|
||||
}
|
||||
return res;
|
||||
} catch(e) {
|
||||
if (workerScript == null) {
|
||||
exceptionAlert(e);
|
||||
}
|
||||
throw e; // Re-throw, will be caught in the Netscript Function
|
||||
}
|
||||
}
|
||||
|
||||
// Cost of upgrade gets cheaper as gang increases in respect + power
|
||||
getDiscount(): number {
|
||||
const power = this.getPower();
|
||||
const respect = this.respect;
|
||||
|
||||
const respectLinearFac = 5e6;
|
||||
const powerLinearFac = 1e6;
|
||||
const discount = Math.pow(respect, 0.01) +
|
||||
respect / respectLinearFac +
|
||||
Math.pow(power, 0.01) +
|
||||
power / powerLinearFac - 1;
|
||||
return Math.max(1, discount);
|
||||
}
|
||||
|
||||
// Returns only valid tasks for this gang. Excludes 'Unassigned'
|
||||
getAllTaskNames(): string[] {
|
||||
return Object.keys(GangMemberTasks).filter((taskName: string) => {
|
||||
const task = GangMemberTasks[taskName];
|
||||
if (task == null) return false;
|
||||
if (task.name === "Unassigned") return false;
|
||||
// yes you need both checks
|
||||
return this.isHackingGang === task.isHacking ||
|
||||
!this.isHackingGang === task.isCombat;
|
||||
});
|
||||
}
|
||||
|
||||
getUpgradeCost(upg: GangMemberUpgrade | null): number {
|
||||
if (upg == null) { return Infinity; }
|
||||
return upg.cost/this.getDiscount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the current object to a JSON save state.
|
||||
*/
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("Gang", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiatizes a Gang object from a JSON save state.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static fromJSON(value: any): Gang {
|
||||
return Generic_fromJSON(Gang, value.data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reviver.constructors.Gang = Gang;
|
||||
320
src/Gang/GangMember.ts
Normal file
320
src/Gang/GangMember.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import { GangMemberTask } from "./GangMemberTask";
|
||||
import { GangMemberTasks } from "./GangMemberTasks";
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
import { GangMemberUpgrades } from "./GangMemberUpgrades";
|
||||
import { IAscensionResult } from "./IAscensionResult";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { AllGangs } from "./AllGangs";
|
||||
import { IGang } from "./IGang";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
interface IMults {
|
||||
hack: number;
|
||||
str: number;
|
||||
def: number;
|
||||
dex: number;
|
||||
agi: number;
|
||||
cha: number;
|
||||
}
|
||||
|
||||
export class GangMember {
|
||||
name: string;
|
||||
task = "Unassigned";
|
||||
|
||||
earnedRespect = 0;
|
||||
|
||||
hack = 1;
|
||||
str = 1;
|
||||
def = 1;
|
||||
dex = 1;
|
||||
agi = 1;
|
||||
cha = 1;
|
||||
|
||||
hack_exp = 0;
|
||||
str_exp = 0;
|
||||
def_exp = 0;
|
||||
dex_exp = 0;
|
||||
agi_exp = 0;
|
||||
cha_exp = 0;
|
||||
|
||||
hack_mult = 1;
|
||||
str_mult = 1;
|
||||
def_mult = 1;
|
||||
dex_mult = 1;
|
||||
agi_mult = 1;
|
||||
cha_mult = 1;
|
||||
|
||||
hack_asc_points = 0;
|
||||
str_asc_points = 0;
|
||||
def_asc_points = 0;
|
||||
dex_asc_points = 0;
|
||||
agi_asc_points = 0;
|
||||
cha_asc_points = 0;
|
||||
|
||||
upgrades: string[] = []; // Names of upgrades
|
||||
augmentations: string[] = []; // Names of augmentations only
|
||||
|
||||
constructor(name = "") {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
calculateSkill(exp: number, mult = 1): number {
|
||||
return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1);
|
||||
}
|
||||
|
||||
calculateAscensionMult(points: number): number {
|
||||
return Math.max(Math.pow(points/4000, 0.7), 1);
|
||||
}
|
||||
|
||||
updateSkillLevels(): void {
|
||||
this.hack = this.calculateSkill(this.hack_exp, this.hack_mult * this.calculateAscensionMult(this.hack_asc_points));
|
||||
this.str = this.calculateSkill(this.str_exp, this.str_mult * this.calculateAscensionMult(this.str_asc_points));
|
||||
this.def = this.calculateSkill(this.def_exp, this.def_mult * this.calculateAscensionMult(this.def_asc_points));
|
||||
this.dex = this.calculateSkill(this.dex_exp, this.dex_mult * this.calculateAscensionMult(this.dex_asc_points));
|
||||
this.agi = this.calculateSkill(this.agi_exp, this.agi_mult * this.calculateAscensionMult(this.agi_asc_points));
|
||||
this.cha = this.calculateSkill(this.cha_exp, this.cha_mult * this.calculateAscensionMult(this.cha_asc_points));
|
||||
}
|
||||
|
||||
calculatePower(): number {
|
||||
return (this.hack +
|
||||
this.str +
|
||||
this.def +
|
||||
this.dex +
|
||||
this.agi +
|
||||
this.cha) / 95;
|
||||
}
|
||||
|
||||
assignToTask(taskName: string): boolean {
|
||||
if (!GangMemberTasks.hasOwnProperty(taskName)) {
|
||||
this.task = "Unassigned";
|
||||
return false;
|
||||
}
|
||||
this.task = taskName;
|
||||
return true;
|
||||
}
|
||||
|
||||
unassignFromTask(): void {
|
||||
this.task = "Unassigned";
|
||||
}
|
||||
|
||||
getTask(): GangMemberTask {
|
||||
// TODO(hydroflame): transfer that to a save file migration function
|
||||
// Backwards compatibility
|
||||
if ((this.task as any) instanceof GangMemberTask) {
|
||||
this.task = (this.task as any).name;
|
||||
}
|
||||
|
||||
if (GangMemberTasks.hasOwnProperty(this.task)) {
|
||||
return GangMemberTasks[this.task];
|
||||
}
|
||||
return GangMemberTasks["Unassigned"];
|
||||
}
|
||||
|
||||
calculateRespectGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseRespect === 0) return 0;
|
||||
let statWeight = (task.hackWeight/100) * this.hack +
|
||||
(task.strWeight/100) * this.str +
|
||||
(task.defWeight/100) * this.def +
|
||||
(task.dexWeight/100) * this.dex +
|
||||
(task.agiWeight/100) * this.agi +
|
||||
(task.chaWeight/100) * this.cha;
|
||||
statWeight -= (4 * task.difficulty);
|
||||
if (statWeight <= 0) return 0;
|
||||
const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.respect) / 100);
|
||||
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
|
||||
const respectMult = gang.getWantedPenalty();
|
||||
return 11 * task.baseRespect * statWeight * territoryMult * respectMult;
|
||||
}
|
||||
|
||||
calculateWantedLevelGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseWanted === 0) return 0;
|
||||
let statWeight = (task.hackWeight / 100) * this.hack +
|
||||
(task.strWeight / 100) * this.str +
|
||||
(task.defWeight / 100) * this.def +
|
||||
(task.dexWeight / 100) * this.dex +
|
||||
(task.agiWeight / 100) * this.agi +
|
||||
(task.chaWeight / 100) * this.cha;
|
||||
statWeight -= (3.5 * task.difficulty);
|
||||
if (statWeight <= 0) return 0;
|
||||
const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / 100);
|
||||
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
|
||||
if (task.baseWanted < 0) {
|
||||
return 0.4 * task.baseWanted * statWeight * territoryMult;
|
||||
}
|
||||
const calc = 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8));
|
||||
|
||||
// Put an arbitrary cap on this to prevent wanted level from rising too fast if the
|
||||
// denominator is very small. Might want to rethink formula later
|
||||
return Math.min(100, calc);
|
||||
}
|
||||
|
||||
calculateMoneyGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseMoney === 0) return 0;
|
||||
let statWeight = (task.hackWeight/100) * this.hack +
|
||||
(task.strWeight/100) * this.str +
|
||||
(task.defWeight/100) * this.def +
|
||||
(task.dexWeight/100) * this.dex +
|
||||
(task.agiWeight/100) * this.agi +
|
||||
(task.chaWeight/100) * this.cha;
|
||||
|
||||
statWeight -= (3.2 * task.difficulty);
|
||||
if (statWeight <= 0) return 0;
|
||||
const territoryMult = Math.max(0.005, Math.pow(AllGangs[gang.facName].territory * 100, task.territory.money) / 100);
|
||||
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
|
||||
const respectMult = gang.getWantedPenalty();
|
||||
return 5 * task.baseMoney * statWeight * territoryMult * respectMult;
|
||||
}
|
||||
|
||||
expMult(): IMults {
|
||||
return {
|
||||
hack: (this.hack_mult-1)/4+1,
|
||||
str: (this.str_mult-1)/4+1,
|
||||
def: (this.def_mult-1)/4+1,
|
||||
dex: (this.dex_mult-1)/4+1,
|
||||
agi: (this.agi_mult-1)/4+1,
|
||||
cha: (this.cha_mult-1)/4+1,
|
||||
};
|
||||
}
|
||||
|
||||
gainExperience(numCycles = 1): void {
|
||||
const task = this.getTask();
|
||||
if (task === GangMemberTasks["Unassigned"]) return;
|
||||
const difficultyMult = Math.pow(task.difficulty, 0.9);
|
||||
const difficultyPerCycles = difficultyMult * numCycles;
|
||||
const weightDivisor = 1500;
|
||||
const expMult = this.expMult();
|
||||
this.hack_exp += (task.hackWeight / weightDivisor) * difficultyPerCycles * expMult.hack;
|
||||
this.str_exp += (task.strWeight / weightDivisor) * difficultyPerCycles * expMult.str;
|
||||
this.def_exp += (task.defWeight / weightDivisor) * difficultyPerCycles * expMult.def;
|
||||
this.dex_exp += (task.dexWeight / weightDivisor) * difficultyPerCycles * expMult.dex;
|
||||
this.agi_exp += (task.agiWeight / weightDivisor) * difficultyPerCycles * expMult.agi;
|
||||
this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles * expMult.cha;
|
||||
}
|
||||
|
||||
recordEarnedRespect(numCycles = 1, gang: IGang): void {
|
||||
this.earnedRespect += (this.calculateRespectGain(gang) * numCycles);
|
||||
}
|
||||
|
||||
getGainedAscensionPoints(): IMults {
|
||||
return {
|
||||
hack: Math.max(this.hack_exp - 1000, 0),
|
||||
str: Math.max(this.str_exp - 1000, 0),
|
||||
def: Math.max(this.def_exp - 1000, 0),
|
||||
dex: Math.max(this.dex_exp - 1000, 0),
|
||||
agi: Math.max(this.agi_exp - 1000, 0),
|
||||
cha: Math.max(this.cha_exp - 1000, 0),
|
||||
}
|
||||
}
|
||||
|
||||
canAscend(): boolean {
|
||||
const points = this.getGainedAscensionPoints();
|
||||
return points.hack > 0 ||
|
||||
points.str > 0 ||
|
||||
points.def > 0 ||
|
||||
points.dex > 0 ||
|
||||
points.agi > 0 ||
|
||||
points.cha > 0;
|
||||
}
|
||||
|
||||
getAscensionResults(): IMults {
|
||||
const points = this.getGainedAscensionPoints();
|
||||
return {
|
||||
hack: this.calculateAscensionMult(this.hack_asc_points+points.hack)/this.calculateAscensionMult(this.hack_asc_points),
|
||||
str: this.calculateAscensionMult(this.str_asc_points+points.str)/this.calculateAscensionMult(this.str_asc_points),
|
||||
def: this.calculateAscensionMult(this.def_asc_points+points.def)/this.calculateAscensionMult(this.def_asc_points),
|
||||
dex: this.calculateAscensionMult(this.dex_asc_points+points.dex)/this.calculateAscensionMult(this.dex_asc_points),
|
||||
agi: this.calculateAscensionMult(this.agi_asc_points+points.agi)/this.calculateAscensionMult(this.agi_asc_points),
|
||||
cha: this.calculateAscensionMult(this.cha_asc_points+points.cha)/this.calculateAscensionMult(this.cha_asc_points),
|
||||
}
|
||||
}
|
||||
|
||||
ascend(): IAscensionResult {
|
||||
const res = this.getAscensionResults();
|
||||
const points = this.getGainedAscensionPoints();
|
||||
this.hack_asc_points += points.hack;
|
||||
this.str_asc_points += points.str;
|
||||
this.def_asc_points += points.def;
|
||||
this.dex_asc_points += points.dex;
|
||||
this.agi_asc_points += points.agi;
|
||||
this.cha_asc_points += points.cha;
|
||||
|
||||
// Remove upgrades. Then re-calculate multipliers and stats
|
||||
this.upgrades.length = 0;
|
||||
this.hack_mult = 1;
|
||||
this.str_mult = 1;
|
||||
this.def_mult = 1;
|
||||
this.dex_mult = 1;
|
||||
this.agi_mult = 1;
|
||||
this.cha_mult = 1;
|
||||
for (let i = 0; i < this.augmentations.length; ++i) {
|
||||
const aug = GangMemberUpgrades[this.augmentations[i]];
|
||||
this.applyUpgrade(aug);
|
||||
}
|
||||
|
||||
// Clear exp and recalculate stats
|
||||
this.hack_exp = 0;
|
||||
this.str_exp = 0;
|
||||
this.def_exp = 0;
|
||||
this.dex_exp = 0;
|
||||
this.agi_exp = 0;
|
||||
this.cha_exp = 0;
|
||||
this.updateSkillLevels();
|
||||
|
||||
const respectToDeduct = this.earnedRespect;
|
||||
this.earnedRespect = 0;
|
||||
return {
|
||||
respect: respectToDeduct,
|
||||
hack: res.hack,
|
||||
str: res.str,
|
||||
def: res.def,
|
||||
dex: res.dex,
|
||||
agi: res.agi,
|
||||
cha: res.cha,
|
||||
};
|
||||
}
|
||||
|
||||
applyUpgrade(upg: GangMemberUpgrade): void {
|
||||
if (upg.mults.str != null) this.str_mult *= upg.mults.str;
|
||||
if (upg.mults.def != null) this.def_mult *= upg.mults.def;
|
||||
if (upg.mults.dex != null) this.dex_mult *= upg.mults.dex;
|
||||
if (upg.mults.agi != null) this.agi_mult *= upg.mults.agi;
|
||||
if (upg.mults.cha != null) this.cha_mult *= upg.mults.cha;
|
||||
if (upg.mults.hack != null) this.hack_mult *= upg.mults.hack;
|
||||
}
|
||||
|
||||
buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: IGang): boolean {
|
||||
// Prevent purchasing of already-owned upgrades
|
||||
if (this.augmentations.includes(upg.name) ||
|
||||
this.upgrades.includes(upg.name)) return false;
|
||||
|
||||
if (player.money.lt(gang.getUpgradeCost(upg))) return false;
|
||||
player.loseMoney(gang.getUpgradeCost(upg));
|
||||
if (upg.type === "g") {
|
||||
this.augmentations.push(upg.name);
|
||||
} else {
|
||||
this.upgrades.push(upg.name);
|
||||
}
|
||||
this.applyUpgrade(upg);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the current object to a JSON save state.
|
||||
*/
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("GangMember", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiatizes a GangMember object from a JSON save state.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static fromJSON(value: any): GangMember {
|
||||
return Generic_fromJSON(GangMember, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
Reviver.constructors.GangMember = GangMember;
|
||||
62
src/Gang/GangMemberTask.ts
Normal file
62
src/Gang/GangMemberTask.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { ITaskParams, ITerritory } from "./ITaskParams";
|
||||
|
||||
export class GangMemberTask {
|
||||
name: string;
|
||||
desc: string;
|
||||
|
||||
isHacking: boolean;
|
||||
isCombat: boolean;
|
||||
|
||||
baseRespect: number;
|
||||
baseWanted: number;
|
||||
baseMoney: number;
|
||||
|
||||
hackWeight: number;
|
||||
strWeight: number;
|
||||
defWeight: number;
|
||||
dexWeight: number;
|
||||
agiWeight: number;
|
||||
chaWeight: number;
|
||||
|
||||
difficulty: number;
|
||||
|
||||
territory: ITerritory;
|
||||
|
||||
// Defines tasks that Gang Members can work on
|
||||
constructor(name: string, desc: string, isHacking: boolean, isCombat: boolean, params: ITaskParams) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
|
||||
// Flags that describe whether this Task is applicable for Hacking/Combat gangs
|
||||
this.isHacking = isHacking;
|
||||
this.isCombat = isCombat;
|
||||
|
||||
// Base gain rates for respect/wanted/money
|
||||
this.baseRespect = params.baseRespect ? params.baseRespect : 0;
|
||||
this.baseWanted = params.baseWanted ? params.baseWanted : 0;
|
||||
this.baseMoney = params.baseMoney ? params.baseMoney : 0;
|
||||
|
||||
// Weighting for the effect that each stat has on the tasks effectiveness.
|
||||
// Weights must add up to 100
|
||||
this.hackWeight = params.hackWeight ? params.hackWeight : 0;
|
||||
this.strWeight = params.strWeight ? params.strWeight : 0;
|
||||
this.defWeight = params.defWeight ? params.defWeight : 0;
|
||||
this.dexWeight = params.dexWeight ? params.dexWeight : 0;
|
||||
this.agiWeight = params.agiWeight ? params.agiWeight : 0;
|
||||
this.chaWeight = params.chaWeight ? params.chaWeight : 0;
|
||||
|
||||
if (Math.round(this.hackWeight + this.strWeight + this.defWeight + this.dexWeight + this.agiWeight + this.chaWeight) != 100) {
|
||||
console.error(`GangMemberTask ${this.name} weights do not add up to 100`);
|
||||
}
|
||||
|
||||
// 1 - 100
|
||||
this.difficulty = params.difficulty ? params.difficulty : 1;
|
||||
|
||||
// Territory Factors. Exponential factors that dictate how territory affects gains
|
||||
// Formula: Territory Mutiplier = (Territory * 100) ^ factor / 100
|
||||
// So factor should be > 1 if something should scale exponentially with territory
|
||||
// and should be < 1 if it should have diminshing returns
|
||||
this.territory = params.territory ? params.territory : {money: 1, respect: 1, wanted: 1};
|
||||
}
|
||||
|
||||
}
|
||||
12
src/Gang/GangMemberTasks.ts
Normal file
12
src/Gang/GangMemberTasks.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { gangMemberTasksMetadata } from "./data/tasks";
|
||||
import { GangMemberTask } from "./GangMemberTask";
|
||||
|
||||
export const GangMemberTasks: {
|
||||
[key: string]: GangMemberTask;
|
||||
} = {};
|
||||
|
||||
(function() {
|
||||
gangMemberTasksMetadata.forEach((e) => {
|
||||
GangMemberTasks[e.name] = new GangMemberTask(e.name, e.desc, e.isHacking, e.isCombat, e.params);
|
||||
});
|
||||
})();
|
||||
66
src/Gang/GangMemberUpgrade.ts
Normal file
66
src/Gang/GangMemberUpgrade.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { IMults, UpgradeType } from "./data/upgrades";
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
export class GangMemberUpgrade {
|
||||
name: string;
|
||||
cost: number;
|
||||
type: UpgradeType;
|
||||
desc: string;
|
||||
mults: IMults;
|
||||
|
||||
constructor(name = "", cost = 0, type: UpgradeType = UpgradeType.Weapon, mults: IMults = {}) {
|
||||
this.name = name;
|
||||
this.cost = cost;
|
||||
this.type = type;
|
||||
this.mults = mults;
|
||||
|
||||
this.desc = this.createDescription();
|
||||
}
|
||||
|
||||
createDescription(): string {
|
||||
const lines = ["Effects:"];
|
||||
if (this.mults.str != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.str-1, 0)} strength skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.str-1)/4, 2)} strength exp`);
|
||||
}
|
||||
if (this.mults.def != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.def-1, 0)} defense skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.def-1)/4, 2)} defense exp`);
|
||||
}
|
||||
if (this.mults.dex != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.dex-1, 0)} dexterity skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.dex-1)/4, 2)} dexterity exp`);
|
||||
}
|
||||
if (this.mults.agi != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.agi-1, 0)} agility skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.agi-1)/4, 2)} agility exp`);
|
||||
}
|
||||
if (this.mults.cha != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.cha-1, 0)} charisma skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.cha-1)/4, 2)} charisma exp`);
|
||||
}
|
||||
if (this.mults.hack != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.hack-1, 0)} hacking skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.hack-1)/4, 2)} hacking exp`);
|
||||
}
|
||||
return lines.join("<br>");
|
||||
}
|
||||
|
||||
// User friendly version of type.
|
||||
getType(): string {
|
||||
switch (this.type) {
|
||||
case UpgradeType.Weapon:
|
||||
return "Weapon";
|
||||
case UpgradeType.Armor:
|
||||
return "Armor";
|
||||
case UpgradeType.Vehicle:
|
||||
return "Vehicle";
|
||||
case UpgradeType.Rootkit:
|
||||
return "Rootkit";
|
||||
case UpgradeType.Augmentation:
|
||||
return "Augmentation";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/Gang/GangMemberUpgrades.ts
Normal file
12
src/Gang/GangMemberUpgrades.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { gangMemberUpgradesMetadata } from "./data/upgrades";
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
|
||||
export const GangMemberUpgrades: {
|
||||
[key: string]: GangMemberUpgrade;
|
||||
} = {};
|
||||
|
||||
(function() {
|
||||
gangMemberUpgradesMetadata.forEach((e) => {
|
||||
GangMemberUpgrades[e.name] = new GangMemberUpgrade(e.name, e.cost, e.upgType, e.mults);
|
||||
});
|
||||
})();
|
||||
32
src/Gang/Helpers.tsx
Normal file
32
src/Gang/Helpers.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { IEngine } from "../IEngine";
|
||||
import { Root } from "./ui/Root";
|
||||
import { Gang } from "./Gang";
|
||||
import { Page, routing } from ".././ui/navigationTracking";
|
||||
|
||||
let gangContainer: HTMLElement;
|
||||
|
||||
(function() {
|
||||
function set(): void {
|
||||
const c = document.getElementById("gang-container");
|
||||
if(c === null) throw new Error("Could not find element 'gang-container'");
|
||||
gangContainer = c;
|
||||
document.removeEventListener("DOMContentLoaded", set);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", set);
|
||||
})();
|
||||
|
||||
|
||||
export function displayGangContent(engine: IEngine, gang: Gang, player: IPlayer): void {
|
||||
if (!routing.isOn(Page.Gang)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDOM.render(<Root
|
||||
engine={engine}
|
||||
gang={gang}
|
||||
player={player} />, gangContainer);
|
||||
}
|
||||
9
src/Gang/IAscensionResult.ts
Normal file
9
src/Gang/IAscensionResult.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface IAscensionResult {
|
||||
respect: number;
|
||||
hack: number;
|
||||
str: number;
|
||||
def: number;
|
||||
dex: number;
|
||||
agi: number;
|
||||
cha: number;
|
||||
}
|
||||
44
src/Gang/IGang.ts
Normal file
44
src/Gang/IGang.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
import { GangMember } from "./GangMember";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
|
||||
export interface IGang {
|
||||
facName: string;
|
||||
members: GangMember[];
|
||||
wanted: number;
|
||||
respect: number;
|
||||
|
||||
isHackingGang: boolean;
|
||||
|
||||
respectGainRate: number;
|
||||
wantedGainRate: number;
|
||||
moneyGainRate: number;
|
||||
|
||||
storedCycles: number;
|
||||
|
||||
storedTerritoryAndPowerCycles: number;
|
||||
|
||||
territoryClashChance: number;
|
||||
territoryWarfareEngaged: boolean;
|
||||
|
||||
notifyMemberDeath: boolean;
|
||||
|
||||
getPower(): number;
|
||||
getTerritory(): number;
|
||||
process(numCycles: number, player: IPlayer): void;
|
||||
processGains(numCycles: number, player: IPlayer): void;
|
||||
processTerritoryAndPowerGains(numCycles: number): void;
|
||||
processExperienceGains(numCycles: number): void;
|
||||
clash(won: boolean): void;
|
||||
canRecruitMember(): boolean;
|
||||
getRespectNeededToRecruitMember(): number;
|
||||
recruitMember(name: string): boolean;
|
||||
getWantedPenalty(): number;
|
||||
calculatePower(): number;
|
||||
killMember(member: GangMember): void;
|
||||
ascendMember(member: GangMember, workerScript: WorkerScript): void;
|
||||
getDiscount(): number;
|
||||
getAllTaskNames(): string[];
|
||||
getUpgradeCost(upg: GangMemberUpgrade): number;
|
||||
}
|
||||
20
src/Gang/ITaskParams.ts
Normal file
20
src/Gang/ITaskParams.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
export interface ITerritory {
|
||||
money: number;
|
||||
respect: number;
|
||||
wanted: number;
|
||||
}
|
||||
|
||||
export interface ITaskParams {
|
||||
baseRespect?: number;
|
||||
baseWanted?: number;
|
||||
baseMoney?: number;
|
||||
hackWeight?: number;
|
||||
strWeight?: number;
|
||||
defWeight?: number;
|
||||
dexWeight?: number;
|
||||
agiWeight?: number;
|
||||
chaWeight?: number;
|
||||
difficulty?: number;
|
||||
territory?: ITerritory;
|
||||
}
|
||||
24
src/Gang/data/Constants.ts
Normal file
24
src/Gang/data/Constants.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export const GangConstants: {
|
||||
GangRespectToReputationRatio: number;
|
||||
MaximumGangMembers: number;
|
||||
CyclesPerTerritoryAndPowerUpdate: number;
|
||||
AscensionMultiplierRatio: number;
|
||||
Names: string[];
|
||||
} = {
|
||||
// Respect is divided by this to get rep gain
|
||||
GangRespectToReputationRatio: 25,
|
||||
MaximumGangMembers: 12,
|
||||
CyclesPerTerritoryAndPowerUpdate: 100,
|
||||
// Portion of upgrade multiplier that is kept after ascending
|
||||
AscensionMultiplierRatio: .15,
|
||||
// Names of possible Gangs
|
||||
Names: [
|
||||
"Slum Snakes",
|
||||
"Tetrads",
|
||||
"The Syndicate",
|
||||
"The Dark Army",
|
||||
"Speakers for the Dead",
|
||||
"NiteSec",
|
||||
"The Black Hand",
|
||||
],
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ITaskParams } from "../ITaskParams";
|
||||
/* tslint:disable:max-line-length */
|
||||
|
||||
/**
|
||||
@@ -29,7 +30,7 @@ export interface IGangMemberTaskMetadata {
|
||||
* An object containing weighting parameters for the task. These parameters are used for
|
||||
* various calculations (respect gain, wanted gain, etc.)
|
||||
*/
|
||||
params?: any;
|
||||
params: ITaskParams;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,7 +255,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
|
||||
name: "Train Combat",
|
||||
params: {
|
||||
strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 25,
|
||||
difficulty: 5,
|
||||
difficulty: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -262,7 +263,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
|
||||
isCombat: true,
|
||||
isHacking: true,
|
||||
name: "Train Hacking",
|
||||
params: {hackWeight: 100, difficulty: 8},
|
||||
params: {hackWeight: 100, difficulty: 45},
|
||||
},
|
||||
{
|
||||
desc: "Assign this gang member to train their charisma",
|
||||
@@ -281,4 +282,4 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
|
||||
difficulty: 5,
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
@@ -1,12 +1,29 @@
|
||||
export interface IMults {
|
||||
hack?: number;
|
||||
str?: number;
|
||||
def?: number;
|
||||
dex?: number;
|
||||
agi?: number;
|
||||
cha?: number;
|
||||
}
|
||||
|
||||
export enum UpgradeType {
|
||||
Weapon = "w",
|
||||
Armor = "a",
|
||||
Vehicle = "v",
|
||||
Rootkit = "r",
|
||||
Augmentation = "g",
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the parameters that can be used to initialize and describe a GangMemberUpgrade
|
||||
* (defined in Gang.js)
|
||||
*/
|
||||
export interface IGangMemberUpgradeMetadata {
|
||||
cost: number;
|
||||
mults: any;
|
||||
mults: IMults;
|
||||
name: string;
|
||||
upgType: string;
|
||||
upgType: UpgradeType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -18,192 +35,192 @@ export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [
|
||||
cost: 1e6,
|
||||
mults: {str: 1.04, def: 1.04},
|
||||
name: "Baseball Bat",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 12e6,
|
||||
mults: {str: 1.08, def: 1.08, dex: 1.08},
|
||||
name: "Katana",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 25e6,
|
||||
mults: {str: 1.1, def: 1.1, dex: 1.1, agi: 1.1},
|
||||
name: "Glock 18C",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 50e6,
|
||||
mults: {str: 1.12, def: 1.1, agi: 1.1},
|
||||
name: "P90C",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 60e6,
|
||||
mults: {str: 1.2, def: 1.15},
|
||||
name: "Steyr AUG",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 100e6,
|
||||
mults: {str: 1.25, def: 1.2},
|
||||
name: "AK-47",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 150e6,
|
||||
mults: {str: 1.3, def: 1.25},
|
||||
name: "M15A10 Assault Rifle",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 225e6,
|
||||
mults: {str: 1.3, dex: 1.25, agi: 1.3},
|
||||
name: "AWM Sniper Rifle",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 2e6,
|
||||
mults: {def: 1.04},
|
||||
name: "Bulletproof Vest",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 5e6,
|
||||
mults: {def: 1.08},
|
||||
name: "Full Body Armor",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 25e6,
|
||||
mults: {def: 1.15, agi: 1.15},
|
||||
name: "Liquid Body Armor",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 40e6,
|
||||
mults: {def: 1.2},
|
||||
name: "Graphene Plating Armor",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 3e6,
|
||||
mults: {agi: 1.04, cha: 1.04},
|
||||
name: "Ford Flex V20",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 9e6,
|
||||
mults: {agi: 1.08, cha: 1.08},
|
||||
name: "ATX1070 Superbike",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 18e6,
|
||||
mults: {agi: 1.12, cha: 1.12},
|
||||
name: "Mercedes-Benz S9001",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 30e6,
|
||||
mults: {agi: 1.16, cha: 1.16},
|
||||
name: "White Ferrari",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 5e6,
|
||||
mults: {hack: 1.05},
|
||||
name: "NUKE Rootkit",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 25e6,
|
||||
mults: {hack: 1.1},
|
||||
name: "Soulstealer Rootkit",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 75e6,
|
||||
mults: {hack: 1.15},
|
||||
name: "Demon Rootkit",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 40e6,
|
||||
mults: {hack: 1.12},
|
||||
name: "Hmap Node",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 75e6,
|
||||
mults: {hack: 1.15},
|
||||
name: "Jack the Ripper",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 10e9,
|
||||
mults: {str: 1.3, dex: 1.3},
|
||||
name: "Bionic Arms",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 10e9,
|
||||
mults: {agi: 1.6},
|
||||
name: "Bionic Legs",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 15e9,
|
||||
mults: {str: 1.15, def: 1.15, dex: 1.15, agi: 1.15},
|
||||
name: "Bionic Spine",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 20e9,
|
||||
mults: {str: 1.4, def: 1.4},
|
||||
name: "BrachiBlades",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 12e9,
|
||||
mults: {str: 1.2, def: 1.2},
|
||||
name: "Nanofiber Weave",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 25e9,
|
||||
mults: {str: 1.5, agi: 1.5},
|
||||
name: "Synthetic Heart",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 15e9,
|
||||
mults: {str: 1.3, def: 1.3},
|
||||
name: "Synfibril Muscle",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 5e9,
|
||||
mults: {hack: 1.05},
|
||||
name: "BitWire",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 10e9,
|
||||
mults: {hack: 1.15},
|
||||
name: "Neuralstimulator",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 7.5e9,
|
||||
mults: {hack: 1.1},
|
||||
name: "DataJack",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 50e9,
|
||||
mults: {str: 1.7, def: 1.7},
|
||||
name: "Graphene Bone Lacings",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
];
|
||||
70
src/Gang/ui/AscensionPopup.tsx
Normal file
70
src/Gang/ui/AscensionPopup.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* React Component for the content of the popup before the player confirms the
|
||||
* ascension of a gang member.
|
||||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
popupId: string;
|
||||
onAscend: () => void;
|
||||
}
|
||||
|
||||
export function AscensionPopup(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function confirm(): void {
|
||||
props.onAscend();
|
||||
const res = props.gang.ascendMember(props.member);
|
||||
dialogBoxCreate(<p>
|
||||
You ascended {props.member.name}!<br />
|
||||
<br />
|
||||
Your gang lost {numeralWrapper.formatRespect(res.respect)} respect.<br />
|
||||
<br />
|
||||
{props.member.name} gained the following stat multipliers for ascending:<br />
|
||||
Hacking: x{numeralWrapper.format(res.hack, '0.000')}<br />
|
||||
Strength: x{numeralWrapper.format(res.str, '0.000')}<br />
|
||||
Defense: x{numeralWrapper.format(res.def, '0.000')}<br />
|
||||
Dexterity: x{numeralWrapper.format(res.dex, '0.000')}<br />
|
||||
Agility: x{numeralWrapper.format(res.agi, '0.000')}<br />
|
||||
Charisma: x{numeralWrapper.format(res.cha, '0.000')}<br />
|
||||
</p>);
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
const ascendBenefits = props.member.getAscensionResults();
|
||||
|
||||
return (<>
|
||||
<pre>
|
||||
Are you sure you want to ascend this member? They will lose all of<br />
|
||||
their non-Augmentation upgrades and their stats will reset back to 1.<br />
|
||||
<br />
|
||||
Furthermore, your gang will lose {numeralWrapper.formatRespect(props.member.earnedRespect)} respect<br />
|
||||
<br />
|
||||
In return, they will gain the following permanent boost to stat multipliers:<br />
|
||||
Hacking: x{numeralWrapper.format(ascendBenefits.hack, '0.000')}<br />
|
||||
Strength: x{numeralWrapper.format(ascendBenefits.str, '0.000')}<br />
|
||||
Defense: x{numeralWrapper.format(ascendBenefits.def, '0.000')}<br />
|
||||
Dexterity: x{numeralWrapper.format(ascendBenefits.dex, '0.000')}<br />
|
||||
Agility: x{numeralWrapper.format(ascendBenefits.agi, '0.000')}<br />
|
||||
Charisma: x{numeralWrapper.format(ascendBenefits.cha, '0.000')}<br />
|
||||
</pre>
|
||||
<button className="std-button" onClick={confirm}>Ascend</button>
|
||||
<button className="std-button" onClick={cancel}>Cancel</button>
|
||||
</>);
|
||||
}
|
||||
29
src/Gang/ui/BonusTime.tsx
Normal file
29
src/Gang/ui/BonusTime.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* React Component for displaying the bonus time remaining.
|
||||
*/
|
||||
import * as React from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
}
|
||||
|
||||
export function BonusTime(props: IProps): React.ReactElement {
|
||||
const CyclerPerSecond = 1000 / CONSTANTS._idleSpeed;
|
||||
if (props.gang.storedCycles / CyclerPerSecond*1000 <= 5000) return (<></>);
|
||||
const bonusMillis = props.gang.storedCycles / CyclerPerSecond * 1000;
|
||||
return (<>
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Bonus time: {convertTimeMsToTimeElapsedString(bonusMillis)}
|
||||
<span className="tooltiptext noselect">
|
||||
You gain bonus time while offline or when the game is inactive
|
||||
(e.g. when the tab is throttled by the browser). Bonus time
|
||||
makes the Gang mechanic progress faster, up to 5x the normal
|
||||
speed.
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
</>);
|
||||
}
|
||||
22
src/Gang/ui/GangMemberAccordion.tsx
Normal file
22
src/Gang/ui/GangMemberAccordion.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* React Component for a gang member on the management subpage.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { Accordion } from "../../ui/React/Accordion";
|
||||
import { GangMemberAccordionContent } from "./GangMemberAccordionContent";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
member: GangMember;
|
||||
}
|
||||
|
||||
export function GangMemberAccordion(props: IProps): React.ReactElement {
|
||||
return <Accordion
|
||||
panelInitiallyOpened={true}
|
||||
headerContent={<>{props.member.name}</>}
|
||||
panelContent={<GangMemberAccordionContent
|
||||
gang={props.gang}
|
||||
member={props.member} />} />
|
||||
}
|
||||
36
src/Gang/ui/GangMemberAccordionContent.tsx
Normal file
36
src/Gang/ui/GangMemberAccordionContent.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* React Component for the content of the accordion of gang members on the
|
||||
* management subpage.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { GangMemberStats } from "./GangMemberStats";
|
||||
import { TaskSelector } from "./TaskSelector";
|
||||
import { TaskDescription } from "./TaskDescription";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
member: GangMember;
|
||||
}
|
||||
|
||||
export function GangMemberAccordionContent(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
return (<>
|
||||
<div className={"gang-member-info-div tooltip"}>
|
||||
<GangMemberStats
|
||||
onAscend={()=>setRerender(old => !old)}
|
||||
gang={props.gang}
|
||||
member={props.member} />
|
||||
</div>
|
||||
<div className={"gang-member-info-div"}>
|
||||
<TaskSelector
|
||||
onTaskChange={()=>setRerender(old => !old)}
|
||||
gang={props.gang}
|
||||
member={props.member} />
|
||||
</div>
|
||||
<div className={"gang-member-info-div"}>
|
||||
<TaskDescription member={props.member} />
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
58
src/Gang/ui/GangMemberList.tsx
Normal file
58
src/Gang/ui/GangMemberList.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* React Component for the list of gang members on the management subpage.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { GangMemberUpgradePopup } from "./GangMemberUpgradePopup";
|
||||
import { GangMemberAccordion } from "./GangMemberAccordion";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { RecruitButton } from "./RecruitButton";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function GangMemberList(props: IProps): React.ReactElement {
|
||||
const [filter, setFilter] = useState("");
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
function openUpgradePopup(): void {
|
||||
const popupId = `gang-upgrade-popup`;
|
||||
createPopup(popupId, GangMemberUpgradePopup, {
|
||||
gang: props.gang,
|
||||
player: props.player,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
function onFilterChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setFilter(event.target.value);
|
||||
}
|
||||
|
||||
const members = props.gang.members.filter((member: GangMember) => member.name.indexOf(filter) > -1 || member.task.indexOf(filter) > -1);
|
||||
|
||||
return (<>
|
||||
<RecruitButton
|
||||
onRecruit={() => setRerender(old => !old)}
|
||||
gang={props.gang} />
|
||||
<br />
|
||||
<input
|
||||
className="text-input noselect"
|
||||
placeholder="Filter gang member"
|
||||
style={{margin: "5px", padding: "5px"}}
|
||||
value={filter}
|
||||
onChange={onFilterChange} />
|
||||
<a
|
||||
className="a-link-button"
|
||||
style={{display: 'inline-block'}}
|
||||
onClick={openUpgradePopup}>Manage Equipment</a>
|
||||
<ul>
|
||||
{members.map((member: GangMember) => <li key={member.name}>
|
||||
<GangMemberAccordion gang={props.gang} member={member} />
|
||||
</li>)}
|
||||
</ul>
|
||||
</>);
|
||||
}
|
||||
77
src/Gang/ui/GangMemberStats.tsx
Normal file
77
src/Gang/ui/GangMemberStats.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* React Component for the first part of a gang member details.
|
||||
* Contains skills and exp.
|
||||
*/
|
||||
import React from "react";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { AscensionPopup } from "./AscensionPopup";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
onAscend: () => void;
|
||||
}
|
||||
|
||||
export function GangMemberStats(props: IProps): React.ReactElement {
|
||||
function ascend(): void {
|
||||
const popupId = `gang-management-ascend-member ${props.member.name}`;
|
||||
createPopup(popupId, AscensionPopup, {
|
||||
member: props.member,
|
||||
gang: props.gang,
|
||||
popupId: popupId,
|
||||
onAscend: props.onAscend,
|
||||
});
|
||||
}
|
||||
|
||||
function openAscensionHelp(): void {
|
||||
dialogBoxCreate(<>
|
||||
Ascending a Gang Member resets the member's progress and stats in
|
||||
exchange for a permanent boost to their stat multipliers.
|
||||
<br /><br />
|
||||
The additional stat multiplier that the Gang Member gains upon
|
||||
ascension is based on the amount of exp they have.
|
||||
<br /><br />
|
||||
Upon ascension, the member will lose all of its non-Augmentation
|
||||
Equipment and your gang will lose respect equal to the total respect
|
||||
earned by the member.
|
||||
</>);
|
||||
}
|
||||
|
||||
const asc = {
|
||||
hack: props.member.calculateAscensionMult(props.member.hack_asc_points),
|
||||
str: props.member.calculateAscensionMult(props.member.str_asc_points),
|
||||
def: props.member.calculateAscensionMult(props.member.def_asc_points),
|
||||
dex: props.member.calculateAscensionMult(props.member.dex_asc_points),
|
||||
agi: props.member.calculateAscensionMult(props.member.agi_asc_points),
|
||||
cha: props.member.calculateAscensionMult(props.member.cha_asc_points),
|
||||
};
|
||||
|
||||
return (<>
|
||||
<span className="tooltiptext smallfont">
|
||||
Hk: x{numeralWrapper.formatMultiplier(props.member.hack_mult * asc.hack)}(x{numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.hack)} Asc)<br />
|
||||
St: x{numeralWrapper.formatMultiplier(props.member.str_mult * asc.str)}(x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.str)} Asc)<br />
|
||||
Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * asc.def)}(x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.def)} Asc)<br />
|
||||
Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * asc.dex)}(x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.dex)} Asc)<br />
|
||||
Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * asc.agi)}(x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.agi)} Asc)<br />
|
||||
Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * asc.cha)}(x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.cha)} Asc)
|
||||
</span>
|
||||
<pre>
|
||||
Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)<br />
|
||||
Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)<br />
|
||||
Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)<br />
|
||||
Dexterity: {formatNumber(props.member.dex, 0)} ({numeralWrapper.formatExp(props.member.dex_exp)} exp)<br />
|
||||
Agility: {formatNumber(props.member.agi, 0)} ({numeralWrapper.formatExp(props.member.agi_exp)} exp)<br />
|
||||
Charisma: {formatNumber(props.member.cha, 0)} ({numeralWrapper.formatExp(props.member.cha_exp)} exp)<br />
|
||||
</pre>
|
||||
<br />
|
||||
{ props.member.canAscend() && <>
|
||||
<button className="accordion-button noselect" onClick={ascend}>Ascend</button>
|
||||
<div className="help-tip noselect" style={{marginTop: "5px"}} onClick={openAscensionHelp}>?</div>
|
||||
</>}
|
||||
</>);
|
||||
}
|
||||
149
src/Gang/ui/GangMemberUpgradePopup.tsx
Normal file
149
src/Gang/ui/GangMemberUpgradePopup.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* React Component for the popup that manages gang members upgrades
|
||||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { GangMemberUpgrades } from "../GangMemberUpgrades";
|
||||
import { GangMemberUpgrade } from "../GangMemberUpgrade";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { Gang } from "../Gang";
|
||||
import { UpgradeType } from "../data/upgrades";
|
||||
|
||||
interface IPanelProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
function filterUpgrades(list: string[], type: UpgradeType): GangMemberUpgrade[] {
|
||||
return Object.keys(GangMemberUpgrades).filter((upgName: string) => {
|
||||
const upg = GangMemberUpgrades[upgName];
|
||||
if (props.player.money.lt(props.gang.getUpgradeCost(upg)))
|
||||
return false;
|
||||
if(upg.type !== type) return false;
|
||||
if(list.includes(upgName)) return false;
|
||||
return true;
|
||||
}).map((upgName: string) => GangMemberUpgrades[upgName]);
|
||||
}
|
||||
const weaponUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Weapon);
|
||||
const armorUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Armor);
|
||||
const vehicleUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Vehicle);
|
||||
const rootkitUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Rootkit);
|
||||
const augUpgrades = filterUpgrades(props.member.augmentations, UpgradeType.Augmentation);
|
||||
|
||||
function purchasedUpgrade(upgName: string): React.ReactElement {
|
||||
const upg = GangMemberUpgrades[upgName]
|
||||
return (<div key={upgName} className="gang-owned-upgrade tooltip">
|
||||
{upg.name}
|
||||
<span className="tooltiptext" dangerouslySetInnerHTML={{__html: upg.desc}} />
|
||||
</div>);
|
||||
}
|
||||
|
||||
function upgradeButton(upg: GangMemberUpgrade, left = false): React.ReactElement {
|
||||
function onClick(): void {
|
||||
props.member.buyUpgrade(upg, props.player, props.gang);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
return (<a key={upg.name} className="a-link-button tooltip" style={{margin:"2px", padding:"2px", display:"block", fontSize:"11px"}} onClick={onClick}>
|
||||
{upg.name} - {Money(props.gang.getUpgradeCost(upg))}
|
||||
<span className={left?"tooltiptextleft":"tooltiptext"} dangerouslySetInnerHTML={{__html: upg.desc}} />
|
||||
</a>);
|
||||
}
|
||||
|
||||
const asc = {
|
||||
hack: props.member.calculateAscensionMult(props.member.hack_asc_points),
|
||||
str: props.member.calculateAscensionMult(props.member.str_asc_points),
|
||||
def: props.member.calculateAscensionMult(props.member.def_asc_points),
|
||||
dex: props.member.calculateAscensionMult(props.member.dex_asc_points),
|
||||
agi: props.member.calculateAscensionMult(props.member.agi_asc_points),
|
||||
cha: props.member.calculateAscensionMult(props.member.cha_asc_points),
|
||||
};
|
||||
return (<div style={{border: '1px solid white'}}>
|
||||
<h1>{props.member.name}({props.member.task})</h1>
|
||||
<pre style={{fontSize:"14px", display: "inline-block", width:"20%"}}>
|
||||
Hack: {props.member.hack} (x{formatNumber(props.member.hack_mult * asc.hack, 2)})<br />
|
||||
Str: {props.member.str} (x{formatNumber(props.member.str_mult * asc.str, 2)})<br />
|
||||
Def: {props.member.def} (x{formatNumber(props.member.def_mult * asc.def, 2)})<br />
|
||||
Dex: {props.member.dex} (x{formatNumber(props.member.dex_mult * asc.dex, 2)})<br />
|
||||
Agi: {props.member.agi} (x{formatNumber(props.member.agi_mult * asc.agi, 2)})<br />
|
||||
Cha: {props.member.cha} (x{formatNumber(props.member.cha_mult * asc.cha, 2)})
|
||||
</pre>
|
||||
<div className="gang-owned-upgrades-div noselect">
|
||||
Purchased Upgrades: {props.member.upgrades.map((upg: string) => purchasedUpgrade(upg))}
|
||||
{props.member.augmentations.map((upg: string) => purchasedUpgrade(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Weapons</h2>
|
||||
{weaponUpgrades.map(upg => upgradeButton(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Armor</h2>
|
||||
{armorUpgrades.map(upg => upgradeButton(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Vehicles</h2>
|
||||
{vehicleUpgrades.map(upg => upgradeButton(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Rootkits</h2>
|
||||
{rootkitUpgrades.map(upg => upgradeButton(upg, true))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Augmentations</h2>
|
||||
{augUpgrades.map(upg => upgradeButton(upg, true))}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
popupId: string;
|
||||
}
|
||||
|
||||
export function GangMemberUpgradePopup(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
function closePopup(this: Window, ev: KeyboardEvent): void {
|
||||
if(ev.keyCode !== 27) return;
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener<'keydown'>('keydown', closePopup);
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
window.removeEventListener<'keydown'>('keydown', closePopup);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (<>
|
||||
<input
|
||||
className="text-input noselect"
|
||||
value={filter}
|
||||
placeholder="Filter gang member"
|
||||
onChange={event => setFilter(event.target.value)} />
|
||||
<p className="tooltip" style={{marginLeft: '6px', display: 'inline-block'}}>
|
||||
Discount: -{numeralWrapper.formatPercentage(1 - 1 / props.gang.getDiscount())}
|
||||
<span className="tooltiptext noselect">
|
||||
You get a discount on equipment and upgrades based on your
|
||||
gang's respect and power. More respect and power leads to more
|
||||
discounts.
|
||||
</span>
|
||||
</p>
|
||||
{props.gang.members.map((member: GangMember) => <GangMemberUpgradePanel
|
||||
key={member.name}
|
||||
player={props.player}
|
||||
gang={props.gang}
|
||||
member={member} />)
|
||||
}
|
||||
</>);
|
||||
}
|
||||
72
src/Gang/ui/GangStats.tsx
Normal file
72
src/Gang/ui/GangStats.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* React Component for the stats related to the gang, like total respect and
|
||||
* money per second.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { Gang } from "../Gang";
|
||||
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { MoneyRate } from "../../ui/React/MoneyRate";
|
||||
import { Reputation } from "../../ui/React/Reputation";
|
||||
import { AllGangs } from "../AllGangs";
|
||||
import { BonusTime } from "./BonusTime";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
}
|
||||
|
||||
export function GangStats(props: IProps): React.ReactElement {
|
||||
const territoryMult = AllGangs[props.gang.facName].territory * 100;
|
||||
let territoryStr;
|
||||
if (territoryMult <= 0) {
|
||||
territoryStr = formatNumber(0, 2);
|
||||
} else if (territoryMult >= 100) {
|
||||
territoryStr = formatNumber(100, 2);
|
||||
} else {
|
||||
territoryStr = formatNumber(territoryMult, 2);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Respect: {numeralWrapper.formatRespect(props.gang.respect)} ({numeralWrapper.formatRespect(5*props.gang.respectGainRate)} / sec)
|
||||
<span className="tooltiptext">
|
||||
Represents the amount of respect your gang has from other gangs and criminal organizations. Your respect affects the amount of money your gang members will earn, and also determines how much reputation you are earning with your gang's corresponding Faction.
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Wanted Level: {numeralWrapper.formatWanted(props.gang.wanted)} ({numeralWrapper.formatWanted(5*props.gang.wantedGainRate)} / sec)
|
||||
<span className="tooltiptext">
|
||||
Represents how much the gang is wanted by law enforcement. The higher your gang's wanted level, the harder it will be for your gang members to make money and earn respect. Note that the minimum wanted level is 1.
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Wanted Level Penalty: -{formatNumber((1 - props.gang.getWantedPenalty()) * 100, 2)}%
|
||||
<span className="tooltiptext">
|
||||
Penalty for respect and money gain rates due to Wanted Level
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<div>
|
||||
<p style={{display: "inline-block"}}>
|
||||
Money gain rate: {MoneyRate(5 * props.gang.moneyGainRate)}
|
||||
</p>
|
||||
</div>
|
||||
<br />
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Territory: {territoryStr}%
|
||||
<span className="tooltiptext">
|
||||
The percentage of total territory your Gang controls
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<p style={{display: "inline-block"}}>
|
||||
Faction reputation: {Reputation(Factions[props.gang.facName].playerReputation)}
|
||||
</p>
|
||||
<br />
|
||||
<BonusTime gang={props.gang} />
|
||||
</>);
|
||||
}
|
||||
46
src/Gang/ui/ManagementSubpage.tsx
Normal file
46
src/Gang/ui/ManagementSubpage.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* React Component for the subpage that manages gang members, the main page.
|
||||
*/
|
||||
import React from "react";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { GangStats } from "./GangStats";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMemberList } from "./GangMemberList";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function ManagementSubpage(props: IProps): React.ReactElement {
|
||||
return (<div style={{display: 'block'}}>
|
||||
<p className="noselect" style={{width: "70%"}}>
|
||||
This page is used to manage your gang members and get an overview of
|
||||
your gang's stats.
|
||||
<br />
|
||||
<br />
|
||||
If a gang member is not earning much money or respect, the task that
|
||||
you have assigned to that member might be too difficult. Consider
|
||||
training that member's stats or choosing an easier task. The tasks
|
||||
closer to the top of the dropdown list are generally easier.
|
||||
Alternatively, the gang member's low production might be due to the
|
||||
fact that your wanted level is too high. Consider assigning a few
|
||||
members to the '{props.gang.isHackingGang?"Ethical Hacking":"Vigilante Justice"}'
|
||||
task to lower your wanted level.
|
||||
<br />
|
||||
<br />
|
||||
Installing Augmentations does NOT reset your progress with your
|
||||
Gang. Furthermore, after installing Augmentations, you will
|
||||
automatically be a member of whatever Faction you created your gang
|
||||
with.
|
||||
<br />
|
||||
<br />
|
||||
You can also manage your gang programmatically through Netscript
|
||||
using the Gang API
|
||||
</p>
|
||||
<br />
|
||||
<GangStats gang={props.gang} />
|
||||
<br />
|
||||
<GangMemberList gang={props.gang} player={props.player} />
|
||||
</div>);
|
||||
}
|
||||
50
src/Gang/ui/RecruitButton.tsx
Normal file
50
src/Gang/ui/RecruitButton.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* React Component for the recruitment button and text on the gang main page.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { RecruitPopup } from "./RecruitPopup";
|
||||
import { GangConstants } from "../data/Constants";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
onRecruit: () => void;
|
||||
}
|
||||
|
||||
export function RecruitButton(props: IProps): React.ReactElement {
|
||||
if (props.gang.members.length >= GangConstants.MaximumGangMembers) {
|
||||
return (<></>);
|
||||
}
|
||||
|
||||
if (!props.gang.canRecruitMember()) {
|
||||
const respect = props.gang.getRespectNeededToRecruitMember();
|
||||
return (<>
|
||||
<a className="a-link-button-inactive"
|
||||
style={{display: 'inline-block', margin: '10px'}}>
|
||||
Recruit Gang Member
|
||||
</a>
|
||||
<p style={{margin: '10px', color: 'red', display: 'inline-block'}}>
|
||||
{formatNumber(respect, 2)} respect needed to recruit next member
|
||||
</p>
|
||||
</>);
|
||||
}
|
||||
|
||||
function onClick(): void {
|
||||
const popupId = "recruit-gang-member-popup";
|
||||
createPopup(popupId, RecruitPopup, {
|
||||
gang: props.gang,
|
||||
popupId: popupId,
|
||||
onRecruit: props.onRecruit,
|
||||
});
|
||||
}
|
||||
|
||||
return (<>
|
||||
<a className="a-link-button"
|
||||
onClick={onClick}
|
||||
style={{display: 'inline-block', margin: '10px'}}>
|
||||
Recruit Gang Member
|
||||
</a>
|
||||
</>);
|
||||
}
|
||||
63
src/Gang/ui/RecruitPopup.tsx
Normal file
63
src/Gang/ui/RecruitPopup.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* React Component for the popup used to recruit new gang members.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
|
||||
interface IRecruitPopupProps {
|
||||
gang: Gang;
|
||||
popupId: string;
|
||||
onRecruit: () => void;
|
||||
}
|
||||
|
||||
export function RecruitPopup(props: IRecruitPopupProps): React.ReactElement {
|
||||
const [name, setName] = useState("");
|
||||
|
||||
function recruit(): void {
|
||||
if (name === "") {
|
||||
dialogBoxCreate("You must enter a name for your Gang member!");
|
||||
return;
|
||||
}
|
||||
if (!props.gang.canRecruitMember()) {
|
||||
dialogBoxCreate("You cannot recruit another Gang member!");
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, the only way this can fail is if you already
|
||||
// have a gang member with the same name
|
||||
if (!props.gang.recruitMember(name)) {
|
||||
dialogBoxCreate("You already have a gang member with this name!");
|
||||
return;
|
||||
}
|
||||
|
||||
props.onRecruit();
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
function onKeyUp(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if(event.keyCode === 13) recruit();
|
||||
if(event.keyCode === 27) cancel();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setName(event.target.value);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p className="noselect">Enter a name for your new Gang member:</p><br />
|
||||
<input autoFocus
|
||||
onKeyUp={onKeyUp}
|
||||
onChange={onChange}
|
||||
className="text-input noselect"
|
||||
type="text"
|
||||
placeholder="unique name" />
|
||||
<a className="std-button" onClick={recruit}>Recruit Gang Member</a>
|
||||
<a className="std-button" onClick={cancel}>Cancel</a>
|
||||
</>);
|
||||
}
|
||||
49
src/Gang/ui/Root.tsx
Normal file
49
src/Gang/ui/Root.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* React Component for all the gang stuff.
|
||||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { ManagementSubpage } from "./ManagementSubpage";
|
||||
import { TerritorySubpage } from "./TerritorySubpage";
|
||||
import { IEngine } from "../../IEngine";
|
||||
import { Gang } from "../Gang";
|
||||
import { displayFactionContent } from "../../Faction/FactionHelpers";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
engine: IEngine;
|
||||
}
|
||||
|
||||
export function Root(props: IProps): React.ReactElement {
|
||||
const [management, setManagement] = useState(true);
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function back(): void {
|
||||
props.engine.loadFactionContent();
|
||||
displayFactionContent(props.gang.facName);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<a className="a-link-button" style={{display: "inline-block"}}
|
||||
onClick={back}>Back</a>
|
||||
<a className={management?"a-link-button-inactive":"a-link-button"}
|
||||
style={{display: "inline-block"}}
|
||||
onClick={() => setManagement(true)}>
|
||||
Gang Management
|
||||
</a>
|
||||
<a className={!management?"a-link-button-inactive":"a-link-button"}
|
||||
style={{display: "inline-block"}}
|
||||
onClick={() => setManagement(false)}>
|
||||
Gang Territory
|
||||
</a>
|
||||
{management ?
|
||||
<ManagementSubpage gang={props.gang} player={props.player} /> :
|
||||
<TerritorySubpage gang={props.gang} />}
|
||||
</>);
|
||||
}
|
||||
18
src/Gang/ui/TaskDescription.tsx
Normal file
18
src/Gang/ui/TaskDescription.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* React Component for left side of the gang member accordion, contains the
|
||||
* description of the task that member is currently doing.
|
||||
*/
|
||||
import React from "react";
|
||||
import { GangMemberTasks } from "../GangMemberTasks";
|
||||
import { GangMember } from "../GangMember";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
}
|
||||
|
||||
export function TaskDescription(props: IProps): React.ReactElement {
|
||||
const task = GangMemberTasks[props.member.task];
|
||||
const desc = task ? task.desc: GangMemberTasks["Unassigned"].desc;
|
||||
|
||||
return (<p className="inline noselect" dangerouslySetInnerHTML={{__html: desc}} />);
|
||||
}
|
||||
47
src/Gang/ui/TaskSelector.tsx
Normal file
47
src/Gang/ui/TaskSelector.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* React Component for the middle part of the gang member accordion. Contains
|
||||
* the task selector as well as some stats.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { StatsTable } from "../../ui/React/StatsTable";
|
||||
import { MoneyRate } from "../../ui/React/MoneyRate";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
onTaskChange: () => void;
|
||||
}
|
||||
|
||||
export function TaskSelector(props: IProps): React.ReactElement {
|
||||
const [currentTask, setCurrentTask] = useState(props.member.task);
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLSelectElement>): void {
|
||||
const task = event.target.value;
|
||||
props.member.assignToTask(task);
|
||||
setCurrentTask(task);
|
||||
props.onTaskChange();
|
||||
}
|
||||
|
||||
const tasks = props.gang.getAllTaskNames();
|
||||
|
||||
const data = [
|
||||
[`Money:`, MoneyRate(5*props.member.calculateMoneyGain(props.gang))],
|
||||
[`Respect:`, `${numeralWrapper.formatRespect(5*props.member.calculateRespectGain(props.gang))} / sec`],
|
||||
[`Wanted Level:`, `${numeralWrapper.formatWanted(5*props.member.calculateWantedLevelGain(props.gang))} / sec`],
|
||||
[`Total Respect:`, `${numeralWrapper.formatRespect(props.member.earnedRespect)}`],
|
||||
];
|
||||
|
||||
return (<>
|
||||
<select
|
||||
onChange={onChange}
|
||||
className="dropdown noselect"
|
||||
value={currentTask}>
|
||||
<option key={0} value={"---"}>---</option>
|
||||
{tasks.map((task: string, i: number) => <option key={i+1} value={task}>{task}</option>)}
|
||||
</select>
|
||||
<div>{StatsTable(data)}</div>
|
||||
</>);
|
||||
}
|
||||
130
src/Gang/ui/TerritorySubpage.tsx
Normal file
130
src/Gang/ui/TerritorySubpage.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* React Component for the territory subpage.
|
||||
*/
|
||||
import React from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { AllGangs } from "../AllGangs";
|
||||
import { Gang } from "../Gang";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
}
|
||||
|
||||
export function TerritorySubpage(props: IProps): React.ReactElement {
|
||||
function openWarfareHelp(): void {
|
||||
dialogBoxCreate("This percentage represents the chance you have of " +
|
||||
"'clashing' with with another gang. If you do not " +
|
||||
"wish to gain/lose territory, then keep this " +
|
||||
"percentage at 0% by not engaging in territory warfare.");
|
||||
}
|
||||
|
||||
function formatTerritory(n: number): string {
|
||||
const v = n * 100;
|
||||
if (v <= 0) {
|
||||
return formatNumber(0, 2);
|
||||
} else if (v >= 100) {
|
||||
return formatNumber(100, 2);
|
||||
} else {
|
||||
return formatNumber(v, 2);
|
||||
}
|
||||
}
|
||||
|
||||
const playerPower = AllGangs[props.gang.facName].power;
|
||||
function otherGangTerritory(name: string): React.ReactElement {
|
||||
const power = AllGangs[name].power
|
||||
const clashVictoryChance = playerPower / (power + playerPower);
|
||||
return (<span key={name}>
|
||||
<u>{name}</u><br />
|
||||
Power: {formatNumber(power, 6)}<br />
|
||||
Territory: {formatTerritory(AllGangs[name].territory)}%<br />
|
||||
Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}<br />
|
||||
<br />
|
||||
</span>);
|
||||
}
|
||||
|
||||
const gangNames = Object.keys(AllGangs).filter(g => g != props.gang.facName);
|
||||
|
||||
return (<div style={{width: '70%'}}>
|
||||
<p className="noselect">
|
||||
This page shows how much territory your Gang controls. This
|
||||
statistic is listed as a percentage, which represents how much of
|
||||
the total territory you control.
|
||||
<br />
|
||||
<br />
|
||||
Every ~20 seconds, your gang has a chance to 'clash' with other
|
||||
gangs. Your chance to win a clash depends on your gang's power,
|
||||
which is listed in the display below. Your gang's power slowly
|
||||
accumulates over time. The accumulation rate is determined by the
|
||||
stats of all Gang members you have assigned to the 'Territory
|
||||
Warfare' task. Gang members that are not assigned to this task do
|
||||
not contribute to your gang's power. Your gang also loses a small
|
||||
amount of power whenever you lose a clash.
|
||||
<br />
|
||||
<br />
|
||||
NOTE: Gang members assigned to 'Territory Warfare' can be killed
|
||||
during clashes. This can happen regardless of whether you win or
|
||||
lose the clash. A gang member being killed results in both respect
|
||||
and power loss for your gang.
|
||||
<br />
|
||||
<br />
|
||||
The amount of territory you have affects all aspects of your Gang
|
||||
members' production, including money, respect, and wanted level. It
|
||||
is very beneficial to have high territory control.
|
||||
<br />
|
||||
<br />
|
||||
</p>
|
||||
<input
|
||||
checked={props.gang.territoryWarfareEngaged}
|
||||
id="warfare"
|
||||
type="checkbox"
|
||||
style={{display: "inline-block", margin: "2px"}}
|
||||
onChange={(event)=> props.gang.territoryWarfareEngaged = event.target.checked}/>
|
||||
<label
|
||||
htmlFor="warfare"
|
||||
className="tooltip noselect"
|
||||
style={{color: "white", display: 'inline-block'}}>
|
||||
Engage in Territory Warfare
|
||||
<span className="tooltiptext" style={{display: "inline-block"}}>
|
||||
Engaging in Territory Warfare sets your clash chance to 100%.
|
||||
Disengaging will cause your clash chance to gradually decrease
|
||||
until it reaches 0%.
|
||||
</span>
|
||||
</label>
|
||||
<br />
|
||||
<p style={{display: 'inline-block'}}>
|
||||
Territory Clash Chance: {numeralWrapper.formatPercentage(props.gang.territoryClashChance, 3)}
|
||||
</p>
|
||||
<div
|
||||
className="help-tip noselect"
|
||||
style={{display: "inline-block"}}
|
||||
onClick={openWarfareHelp}>?</div>
|
||||
<br />
|
||||
|
||||
<input
|
||||
checked={props.gang.notifyMemberDeath}
|
||||
id="notify"
|
||||
type="checkbox"
|
||||
style={{display: "inline-block", margin: "2px"}}
|
||||
onChange={(event)=> props.gang.notifyMemberDeath = event.target.checked}/>
|
||||
<label htmlFor="warfare" className="tooltip noselect" style={{color: "white", display: 'inline-block'}}>
|
||||
Notify about Gang Member Deaths
|
||||
<span className="tooltiptext" style={{display: "inline-block"}}>
|
||||
If this is enabled, then you will receive a pop-up notifying you
|
||||
whenever one of your Gang Members dies in a territory clash.
|
||||
</span>
|
||||
</label>
|
||||
<br />
|
||||
<fieldset style={{display: "block", margin: "6px"}}>
|
||||
<p>
|
||||
<b><u>{props.gang.facName}</u></b><br />
|
||||
Power: {formatNumber(AllGangs[props.gang.facName].power, 6)}<br />
|
||||
Territory: {formatTerritory(AllGangs[props.gang.facName].territory)}%<br />
|
||||
<br />
|
||||
{gangNames.map(name => otherGangTerritory(name))}
|
||||
</p>
|
||||
</fieldset>
|
||||
</div>);
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ export function purchaseHashUpgrade(upgName, upgTarget) {
|
||||
case "Exchange for Bladeburner Rank": {
|
||||
// This will throw if player isnt in Bladeburner
|
||||
try {
|
||||
Player.bladeburner.changeRank(upg.value);
|
||||
Player.bladeburner.changeRank(Player, upg.value);
|
||||
} catch(e) {
|
||||
Player.hashManager.refundUpgrade(upgName);
|
||||
return false;
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface IConstructorParams {
|
||||
hasTargetServer?: boolean;
|
||||
name: string;
|
||||
value: number;
|
||||
effectText?: (level: number) => JSX.Element | null;
|
||||
}
|
||||
|
||||
export class HashUpgrade {
|
||||
@@ -45,6 +46,7 @@ export class HashUpgrade {
|
||||
|
||||
constructor(p: IConstructorParams) {
|
||||
if (p.cost != null) { this.cost = p.cost; }
|
||||
if (p.effectText != null) { this.effectText = p.effectText; }
|
||||
|
||||
this.costPerLevel = p.costPerLevel;
|
||||
this.desc = p.desc;
|
||||
@@ -53,6 +55,9 @@ export class HashUpgrade {
|
||||
this.value = p.value;
|
||||
}
|
||||
|
||||
// Functions that returns the UI element to display the effect of this upgrade.
|
||||
effectText: (level: number) => JSX.Element | null = () => null;
|
||||
|
||||
getCost(levels: number): number {
|
||||
if (typeof this.cost === "number") { return this.cost; }
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// Metadata used to construct all Hash Upgrades
|
||||
import React from "react";
|
||||
import { IConstructorParams } from "../HashUpgrade";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
|
||||
export const HashUpgradesMetadata: IConstructorParams[] = [
|
||||
{
|
||||
@@ -7,12 +10,14 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
|
||||
costPerLevel: 4,
|
||||
desc: "Sell hashes for $1m",
|
||||
name: "Sell for Money",
|
||||
effectText: (level: number): JSX.Element | null => (<>Sold for {Money(1e6*level)}</>),
|
||||
value: 1e6,
|
||||
},
|
||||
{
|
||||
costPerLevel: 100,
|
||||
desc: "Sell hashes for $1b in Corporation funds",
|
||||
name: "Sell for Corporation Funds",
|
||||
effectText: (level: number): JSX.Element | null => (<>Sold for {Money(1e9*level)} Corporation funds.</>),
|
||||
value: 1e9,
|
||||
},
|
||||
{
|
||||
@@ -38,6 +43,7 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
|
||||
desc: "Use hashes to improve the experience earned when studying at a university by 20%. " +
|
||||
"This effect persists until you install Augmentations",
|
||||
name: "Improve Studying",
|
||||
//effectText: (level: number) => JSX.Element | null = <>Improves studying by ${level*20}%</>,
|
||||
value: 20, // Improves studying by value%
|
||||
},
|
||||
{
|
||||
@@ -45,30 +51,35 @@ export const HashUpgradesMetadata: IConstructorParams[] = [
|
||||
desc: "Use hashes to improve the experience earned when training at the gym by 20%. This effect " +
|
||||
"persists until you install Augmentations",
|
||||
name: "Improve Gym Training",
|
||||
effectText: (level: number): JSX.Element | null => (<>Improves training by ${level*20}%</>),
|
||||
value: 20, // Improves training by value%
|
||||
},
|
||||
{
|
||||
costPerLevel: 200,
|
||||
desc: "Exchange hashes for 1k Scientific Research in all of your Corporation's Industries",
|
||||
name: "Exchange for Corporation Research",
|
||||
effectText: (level: number): JSX.Element | null => (<>Acquired a total of {level}k Scientific Research in your industries.</>),
|
||||
value: 1000,
|
||||
},
|
||||
{
|
||||
costPerLevel: 250,
|
||||
desc: "Exchange hashes for 100 Bladeburner Rank",
|
||||
name: "Exchange for Bladeburner Rank",
|
||||
effectText: (level: number): JSX.Element | null => (<>Acquired a total of {numeralWrapper.format(100*level, '0a')} Bladeburner rank</>),
|
||||
value: 100,
|
||||
},
|
||||
{
|
||||
costPerLevel: 250,
|
||||
desc: "Exchanges hashes for 10 Bladeburner Skill Points",
|
||||
name: "Exchange for Bladeburner SP",
|
||||
effectText: (level: number): JSX.Element | null => (<>Acquired a total of {numeralWrapper.format(10*level, '0a')} Bladeburner Skill Points</>),
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
costPerLevel: 200,
|
||||
desc: "Generate a random Coding Contract somewhere on the network",
|
||||
name: "Generate Coding Contract",
|
||||
effectText: (level: number): JSX.Element | null => (<>Generated {level} contracts.</>),
|
||||
value: 1,
|
||||
},
|
||||
]
|
||||
@@ -11,7 +11,6 @@ import { Player } from "../../Player";
|
||||
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
import { PopupCloseButton } from "../../ui/React/PopupCloseButton";
|
||||
import { ServerDropdown,
|
||||
ServerType } from "../../ui/React/ServerDropdown"
|
||||
|
||||
@@ -54,6 +53,8 @@ class HashUpgrade extends React.Component {
|
||||
const hashManager = this.props.hashManager;
|
||||
const upg = this.props.upg;
|
||||
const cost = hashManager.getUpgradeCost(upg.name);
|
||||
const level = hashManager.upgrades[upg.name];
|
||||
const effect = upg.effectText(level);
|
||||
|
||||
// Purchase button
|
||||
const canPurchase = hashManager.hashes >= cost;
|
||||
@@ -63,11 +64,13 @@ class HashUpgrade extends React.Component {
|
||||
return (
|
||||
<div className={"bladeburner-action"}>
|
||||
<CopyableText value={upg.name} />
|
||||
<p>Cost: {Hashes(cost)}</p>
|
||||
<p>Cost: {Hashes(cost)}, Bought: {level} times</p>
|
||||
|
||||
<p>{upg.desc}</p>
|
||||
<button className={btnClass} onClick={this.purchase}>
|
||||
Purchase
|
||||
</button>
|
||||
{level > 0 && effect && <p>{effect}</p>}
|
||||
{
|
||||
upg.hasTargetServer &&
|
||||
<ServerDropdown
|
||||
@@ -119,7 +122,6 @@ export class HashUpgradePopup extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PopupCloseButton popup={this.props.popupId} text={"Close"} />
|
||||
<p>Spend your hashes on a variety of different upgrades</p>
|
||||
<p>Hashes: {numeralWrapper.formatHashes(this.state.totalHashes)}</p>
|
||||
{upgradeElems}
|
||||
|
||||
@@ -14,13 +14,6 @@ interface IProps {
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
function diffStr(d: number): string {
|
||||
if(d<=0.5) return "trivial";
|
||||
if(d<=1.5) return "normal";
|
||||
if(d<=2.5) return "hard";
|
||||
return "impossible";
|
||||
}
|
||||
|
||||
function arrowPart(color: string, length: number): JSX.Element {
|
||||
let arrow = "";
|
||||
if(length <= 0) length = 0;
|
||||
|
||||
@@ -8,6 +8,11 @@ export function KeyHandler(props: IProps): React.ReactElement {
|
||||
let elem: any;
|
||||
useEffect(() => elem.focus());
|
||||
|
||||
function onKeyDown(event: React.KeyboardEvent<HTMLElement>): void {
|
||||
if(!event.isTrusted) return;
|
||||
props.onKeyDown(event);
|
||||
}
|
||||
|
||||
// invisible autofocused element that eats all the keypress for the minigames.
|
||||
return (<div tabIndex={1} ref={c => elem = c} onKeyDown={props.onKeyDown} />)
|
||||
return (<div tabIndex={1} ref={c => elem = c} onKeyDown={onKeyDown} />)
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
|
||||
return (<Grid container spacing={3}>
|
||||
<GameTimer millis={timer} onExpire={props.onFailure} />
|
||||
<Grid item xs={12}>
|
||||
<h1 className={"noselect"}>Cut the wires with the following properties!</h1>
|
||||
<h1 className={"noselect"}>Cut the wires with the following properties! (keyboard 1 to 9)</h1>
|
||||
{questions.map((question, i) => <h3 key={i}>{question.toString()}</h3>)}
|
||||
<pre>{(new Array(wires.length)).fill(0).map((_, i) => <span key={i}> {i+1} </span>)}</pre>
|
||||
{(new Array(8)).fill(0).map((_, i) => <div key={i}>
|
||||
|
||||
@@ -204,7 +204,7 @@ export const LocationsMetadata: IConstructorParams[] = [
|
||||
startingSecurityLevel: 2.5,
|
||||
},
|
||||
name: LocationName.NewTokyoNoodleBar,
|
||||
types: [LocationType.Company],
|
||||
types: [LocationType.Company, LocationType.Special],
|
||||
},
|
||||
{
|
||||
city: CityName.NewTokyo,
|
||||
@@ -440,4 +440,4 @@ export const LocationsMetadata: IConstructorParams[] = [
|
||||
name: LocationName.WorldStockExchange,
|
||||
types: [LocationType.StockMarket],
|
||||
},
|
||||
];
|
||||
];
|
||||
@@ -65,7 +65,7 @@ export class LocationCity extends React.Component<IProps, any> {
|
||||
elems.push(<pre key={i}>{lineElems(lines[i])}</pre>)
|
||||
}
|
||||
|
||||
return elems;
|
||||
return <div className="noselect">{elems}</div>;
|
||||
}
|
||||
|
||||
listCity(): React.ReactNode {
|
||||
|
||||
@@ -158,7 +158,7 @@ export class GenericLocation extends React.Component<IProps, any> {
|
||||
return (
|
||||
<div>
|
||||
<StdButton onClick={this.props.returnToCity} style={this.btnStyle} text={"Return to World"} />
|
||||
<h1>
|
||||
<h1 className="noselect">
|
||||
{backdoorInstalled && !Settings.DisableTextEffects
|
||||
? <CorruptableText content={this.props.loc.name}/>
|
||||
: this.props.loc.name
|
||||
|
||||
@@ -74,7 +74,7 @@ export class LocationRoot extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="noselect">
|
||||
<h2>{this.state.city}</h2>
|
||||
<LocationCity city={city} enterLocation={this.enterLocation} />
|
||||
</div>
|
||||
@@ -120,7 +120,7 @@ export class LocationRoot extends React.Component<IProps, IState> {
|
||||
|
||||
p.loseMoney(cost);
|
||||
p.travel(to);
|
||||
dialogBoxCreate(`You are now in ${to}!`);
|
||||
dialogBoxCreate(<span className="noselect">You are now in {to}!</span>);
|
||||
|
||||
// Dynamically update main menu
|
||||
if (p.firstTimeTraveled === false) {
|
||||
|
||||
@@ -45,6 +45,7 @@ export class SpecialLocation extends React.Component<IProps, IState> {
|
||||
|
||||
this.btnStyle = { display: "block" };
|
||||
|
||||
this.renderNoodleBar = this.renderNoodleBar.bind(this);
|
||||
this.createCorporationPopup = this.createCorporationPopup.bind(this);
|
||||
this.handleBladeburner = this.handleBladeburner.bind(this);
|
||||
this.handleResleeving = this.handleResleeving.bind(this);
|
||||
@@ -107,6 +108,18 @@ export class SpecialLocation extends React.Component<IProps, IState> {
|
||||
)
|
||||
}
|
||||
|
||||
renderNoodleBar(): React.ReactNode {
|
||||
function EatNoodles(): void {
|
||||
dialogBoxCreate(<>You ate some delicious noodles and feel refreshed.</>)
|
||||
}
|
||||
|
||||
return (<StdButton
|
||||
onClick={EatNoodles}
|
||||
style={this.btnStyle}
|
||||
text={'Eat noodles'}
|
||||
/>)
|
||||
}
|
||||
|
||||
renderCreateCorporation(): React.ReactNode {
|
||||
if (!this.props.p.canAccessCorporation()) {
|
||||
return <>
|
||||
@@ -145,6 +158,9 @@ export class SpecialLocation extends React.Component<IProps, IState> {
|
||||
case LocationName.Sector12NSA: {
|
||||
return this.renderBladeburner();
|
||||
}
|
||||
case LocationName.NewTokyoNoodleBar: {
|
||||
return this.renderNoodleBar();
|
||||
}
|
||||
default:
|
||||
console.error(`Location ${this.props.loc.name} doesn't have any special properties`);
|
||||
break;
|
||||
|
||||
@@ -44,7 +44,7 @@ export class TravelAgencyLocation extends React.Component<IProps, any> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="noselect">
|
||||
<p>
|
||||
From here, you can travel to any other city! A ticket
|
||||
costs {Money(CONSTANTS.TravelCost)}.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user