This commit is contained in:
Olivier Gagnon 2021-10-03 20:34:36 -04:00
commit 7304e5379f
368 changed files with 9561 additions and 23690 deletions

@ -1,61 +0,0 @@
@mixin animation($property) {
-webkit-animation: $property;
-moz-animation: $property;
-ms-animation: $property;
-o-animation: $property;
animation: $property;
}
@mixin borderRadius($property) {
-webkit-border-radius: $property;
-moz-border-radius: $property;
border-radius: $property;
}
@mixin boxShadow($value) {
-webkit-box-shadow: $value;
-moz-box-shadow: $value;
box-shadow: $value;
}
@mixin keyframes($animationName) {
@-webkit-keyframes #{$animationName} {
$browser: "-webkit-" !global;
@content;
}
@-moz-keyframes #{$animationName} {
$browser: "-moz-" !global;
@content;
}
@-ms-keyframes #{$animationName} {
$browser: "-ms-" !global;
@content;
}
@-o-keyframes #{$animationName} {
$browser: "-o-" !global;
@content;
}
@keyframes #{$animationName} {
$browser: "" !global;
@content;
}
}
@mixin transform($property) {
-webkit-transform: $property;
-moz-transform: $property;
-ms-transform: $property;
-o-transform: $property;
transform: $property;
}
@mixin userSelect($value) {
-webkit-user-select: $value;
-moz-user-select: $value;
-ms-user-select: $value;
user-select: $value;
}

@ -1,15 +0,0 @@
@import "theme";
* {
font-size: $defaultFontSize;
font-family: $fontFamily;
}
*,
*:before,
*:after {
margin: 0;
padding: 0;
box-sizing: border-box;
vertical-align: middle;
}

@ -1,18 +0,0 @@
$fontFamily: "Lucida Console", "Lucida Sans Unicode", "Fira Mono", "Consolas", "Courier New", Courier, monospace,
"Times New Roman";
$defaultFontSize: 16px;
/* COLORS */
$hacker-green: #adff2f;
$success-green: #3adb76;
$alert-red: #ff2929;
$money-gold: #ffd700;
$light-yellow: #faffdf;
/* Attributes */
$my-stat-hp-color: #dd3434;
$my-stat-money-color: $money-gold;
$my-stat-hack-color: $hacker-green;
$my-stat-physical: $light-yellow;
$my-stat-cha-color: #a671d1;
$my-stat-int-color: #6495ed;

@ -1,24 +0,0 @@
/**
* Styling for the Augmentations UI. This is the page that displays all of the
* player's owned and purchased Augmentations and Source-Files. It also allows
* the player to install Augmentations
*/
@import "theme";
.augmentations-content {
> p {
font-size: $defaultFontSize * 0.875;
}
}
.augmentations-list {
button,
div {
color: var(--my-font-color);
text-decoration: none;
}
button {
padding: 4px;
}
}

@ -1,135 +0,0 @@
@import "theme";
.bladeburner-container {
a,
div,
p,
pre,
td {
font-size: $defaultFontSize * 0.8125;
}
}
.bladeburner-action {
border: 1px solid #fff;
margin: 7px;
padding: 7px;
white-space: pre-wrap;
pre {
white-space: pre-wrap;
}
}
/* Whatever action is currently active */
.bladeburner-active-action {
border: 4px solid #fff;
}
/* Action & Skills panel navigation button */
%bladeburner-nav-button {
border: 1px solid #fff;
margin: 2px;
padding: 2px;
color: #fff;
}
.bladeburner-nav-button {
@extend %bladeburner-nav-button;
&:hover {
background-color: #3d4044;
}
}
.bladeburner-nav-button-inactive {
@extend %bladeburner-nav-button;
text-decoration: none;
background-color: #555;
cursor: default;
pointer-events: none;
}
/* Checkbox for (de)selecting autoleveling */
.bbcheckbox {
position: relative;
display: inline;
label {
width: 20px;
height: 20px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
background: black;
border-width: 1px;
border-color: white;
border-style: solid;
&:after {
content: "";
width: 9px;
height: 5px;
position: absolute;
top: 5px;
left: 5px;
border: 3px solid white;
border-top: none;
border-right: none;
opacity: 0;
transform: rotate(-45deg);
}
}
input[type="checkbox"] {
margin: 3px;
visibility: hidden;
&:checked + label:after {
opacity: 1;
}
}
}
/* Bladeburner Console */
.bladeburner-console-div {
display: inline-block;
width: 40%;
border: 1px solid #fff;
overflow: auto;
height: 100%;
position: absolute;
}
.bladeburner-console-table {
height: auto;
overflow: auto;
table-layout: fixed;
width: 100%;
}
.bladeburner-console-input-row {
transition: height 1s;
width: 100%;
}
.bladeburner-console-input-cell {
display: flex;
}
.bladeburner-console-input {
display: inline-block;
padding: 0 !important;
margin: 0 !important;
border: 0;
background-color: var(--my-background-color);
font-size: $defaultFontSize * 0.8125;
outline: none;
color: var(--my-font-color);
flex: 1 1 auto;
}
.bladeburner-console-line {
word-wrap: break-word;
hyphens: auto;
-webkit-hyphens: auto;
-moz-hyphens: auto;
}

@ -1,112 +0,0 @@
@import "mixins";
@import "theme";
@import "styles";
/**
* Styling for all buttons
*
* Includes <button> elements as well as classes that are used
* for formatting buttons
*/
/* Remove default <button> styling */
button {
border: none;
background-color: transparent;
}
.a-link-button,
.std-button {
@extend .noselect;
text-decoration: none;
background-color: #555;
color: #fff;
padding: 3px 5px;
margin: 5px;
border: 1px solid #333;
&:hover {
background-color: #666;
}
&:active {
@include boxShadow(inset 0 1px 4px rgba(0, 0, 0, 0.6));
}
}
.a-link-button-inactive,
.std-button-disabled,
.std-button:disabled {
text-decoration: none;
background-color: #333;
color: #fff;
padding: 3px 5px;
margin: 5px;
border: 1px solid #333;
cursor: default;
-moz-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
&:hover {
.tooltiptext,
.tooltiptexthigh,
.tooltiptextleft {
visibility: visible;
}
}
&:active {
pointer-events: none;
}
}
.a-link-button-bought,
.std-button-bought {
@extend .noselect;
text-decoration: none;
background-color: #0a0;
color: #fff;
padding: 3px 5px;
margin: 5px;
border: 1px solid #0a0;
cursor: default;
&:hover {
.tooltiptext,
.tooltiptexthigh,
.tooltiptextleft {
visibility: visible;
}
}
&:active {
pointer-events: none;
}
}
/**
* This is a button that is meant to be used on accordions (accordion-header and accordion-panel classes)
* It has a black background so it does not clash with the default accordion coloring
*/
.accordion-button {
@include borderRadius(12px);
@include boxShadow(1px 1px 3px #000);
color: #aaa;
font-size: $defaultFontSize;
font-weight: bold;
margin: 4px;
padding: 4px;
background-color: #000;
&:hover,
&:active {
color: #fff;
text-decoration: none;
cursor: pointer;
}
/* TODO focus selector? */
}

@ -1,24 +0,0 @@
.casino-card {
padding: 10px;
border: solid 1px #808080;
background-color: white;
display: inline-block;
border-radius: 10px;
font-size: 18.5px;
text-align: center;
margin: 3px;
font-weight: bold;
}
.casino-card .value {
font-size: 20px;
font-family: sans-serif;
}
.casino-card.red {
color: red;
}
.casino-card.black {
color: black;
}

@ -1,12 +0,0 @@
@import "mixins";
@import "theme";
/**
* Styling for the Character Overview Panel (top-right panel)
*/
#character-overview {
position: fixed;
top: 0;
right: 0;
}

@ -1,168 +0,0 @@
@import "mixins";
@import "theme";
/**
* Styling for Corporations
* The names/labels refer to "Company Management", which was the old name
* for the mechanic before it got changed to avoid confusion with normal
* companies
*/
.cmpy-mgmt-container p,
.cmpy-mgmt-container a,
.cmpy-mgmt-container div,
.cmpy-mgmt-container br {
font-size: $defaultFontSize * 0.8125;
}
/* Header tabs */
.cmpy-mgmt-header-tab {
display: inline-block;
color: #fff;
background-color: #555;
border: 1px solid #fff;
padding: 4px;
}
.cmpy-mgmt-header-tab:hover {
background-color: #666;
}
.cmpy-mgmt-header-tab.current {
background-color: #777;
}
/* Switch between Cities */
.cmpy-mgmt-city-tab {
display: inline-block;
color: #fff;
background-color: #555;
border: 1px solid #fff;
padding: 4px;
}
.cmpy-mgmt-city-tab:hover {
background-color: #666;
}
.cmpy-mgmt-city-tab.current {
background-color: #777;
}
/* Panels */
#cmpy-mgmt-panel {
height: 90%;
}
.cmpy-mgmt-industry-left-panel,
.cmpy-mgmt-industry-right-panel {
display: inline-block;
height: 100%;
overflow-y: auto;
overflow-x: auto;
overflow: visible;
top: 10px;
width: 45%;
vertical-align: top;
margin-top: 10px;
}
.cmpy-mgmt-industry-overview-panel {
border: 1px solid #fff;
color: var(--my-font-color);
display: inline-block;
padding: 3px;
width: 100%;
}
.cmpy-mgmt-employee-panel {
border: 1px solid #fff;
display: block;
padding: 3px;
width: 100%;
}
.cmpy-mgmt-warehouse-panel {
border: 1px solid #fff;
display: inline-block;
padding: 3px;
width: 100%;
}
/* Hiring new employees */
.cmpy-mgmt-find-employee-option {
border: 1px solid #fff;
margin: 6px;
}
.cmpy-mgmt-find-employee-option:hover {
background-color: #3d4044;
}
/* Warehouse */
.cmpy-mgmt-warehouse-material-div {
padding: 2px;
border: 1px solid #fff;
}
.cmpy-mgmt-warehouse-product-div {
padding: 2px;
border: 1px solid #fff;
}
/* Exporting materials/products */
.cmpy-mgmt-existing-export {
border: 1px solid #fff;
border-radius: 25px;
margin: 4px;
padding: 4px;
}
.cmpy-mgmt-existing-export:hover {
background-color: #333;
}
/* Corporation Upgrades */
.cmpy-mgmt-upgrade-container {
border: 1px solid #fff;
width: 60%;
margin: 4px;
}
.cmpy-mgmt-upgrade-header {
margin: 6px;
padding: 6px;
}
.cmpy-mgmt-upgrade-div {
text-align: left;
display: inline-block;
border: 1px solid #fff;
margin: 2px;
padding: 6px;
border-radius: 25px;
font-size: $defaultFontSize * 0.75;
color: var(--my-font-color);
}
.cmpy-mgmt-upgrade-div:hover {
background-color: #333;
}
/* Industry Upgrades */
.industry-purchases-and-upgrades-header {
font-size: 14px;
margin: 2px;
padding: 2px;
}
/* Advertising */
.cmpy-mgmt-advertising-info {
font-size: $defaultFontSize * 0.75;
}
/* Research */
#corporation-research-popup-box-content {
overflow-x: auto !important;
overflow-y: auto !important;
}

@ -1,32 +0,0 @@
.add-exp-button {
margin-right: 0;
}
.remove-exp-button {
margin-left: 0;
}
.exp-input {
margin: 5px 0 5px 0;
padding: 2px 5px;
}
.text-center {
margin: auto;
text-align: center;
vertical-align: middle;
}
.touch-right {
margin-right: 0;
}
.touch-left {
margin-left: 0;
}
.touch-sides {
margin-left: 0;
margin-right: 0;
}

@ -1,19 +0,0 @@
/* Styling for the game options/settings
*
* Styling for the actual Game Options popup box can be found in popupboxes.scss
* This stylesheet is for everything inside the Game Options pop-up box
*/
@import "theme";
#game-options-right-panel {
a {
display: block;
width: 46%;
}
button {
display: inline-block;
width: 46%;
}
}

@ -1,46 +0,0 @@
@import "mixins";
@import "theme";
/**
* Styling for the Gang mechanic UI (BitNode-2)
*/
.gang-container {
p,
pre {
font-size: $defaultFontSize * 0.9375;
}
select {
background-color: black;
color: white;
}
}
#gang-management-subpage > p {
padding: 4px;
}
.gang-member-info-div {
background-color: #555;
display: inline;
float: left;
width: 30%;
}
/**
* Showing owned upgrades in the Equipment Box
*/
.gang-owned-upgrades-div {
display: inline-block;
margin-left: 6px;
width: 75%;
}
.gang-owned-upgrade {
border: 1px solid white;
font-size: 12px;
margin: 1px;
padding: 1px;
}

3413
css/grid.min.css vendored

File diff suppressed because it is too large Load Diff

@ -1,69 +0,0 @@
@import "mixins";
@import "theme";
/**
* Styling for the Hacknet Nodes UI Page
*/
.hacknet-general-info {
margin: 10px;
}
#hacknet-nodes-container li {
float: left;
overflow: hidden;
white-space: nowrap;
&.hacknet-node {
$boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
@include boxShadow($boxShadowArgs);
margin: 6px;
padding: 7px;
width: 35vw;
border: 2px solid var(--my-highlight-color);
}
}
#hacknet-nodes-list {
list-style: none;
width: 82vw;
}
#hacknet-nodes-money {
margin: 10px;
float: left;
}
#hacknet-nodes-money-multipliers-div {
display: inline-block;
width: 70vw;
}
#hacknet-nodes-multipliers {
float: right;
}
#hacknet-nodes-purchase-button {
display: inline-block;
}
.hacknet-node-container {
display: inline-table;
.row {
display: table-row;
height: 30px;
p {
display: table-cell;
}
}
.upgradable-info {
display: inline-block;
margin: 0 4px; /* Don't want the vertical margin/padding, just left & right */
padding: 0 4px;
width: $defaultFontSize * 4;
}
}

@ -1,61 +0,0 @@
@import "theme";
.blinking-cursor {
font-weight: 100;
color: #2e3d48;
-webkit-animation: 1s cursorblink step-end infinite;
-moz-animation: 1s cursorblink step-end infinite;
-ms-animation: 1s cursorblink step-end infinite;
-o-animation: 1s cursorblink step-end infinite;
animation: 1s cursorblink step-end infinite;
}
@keyframes cursorblink {
from,
to {
color: transparent;
}
50% {
color: $hacker-green;
}
}
@-moz-keyframes cursorblink {
from,
to {
color: transparent;
}
50% {
color: $hacker-green;
}
}
@-webkit-keyframes cursorblink {
from,
to {
color: transparent;
}
50% {
color: $hacker-green;
}
}
@-ms-keyframes cursorblink {
from,
to {
color: transparent;
}
50% {
color: $hacker-green;
}
}
@-o-keyframes cursorblink {
from,
to {
color: transparent;
}
50% {
color: $hacker-green;
}
}

@ -1,90 +0,0 @@
@import "mixins";
@import "theme";
/* interactivetutorial.css */
#interactive-tutorial-wrapper {
position: relative;
}
#interactive-tutorial-container {
display: none;
position: fixed; /* Stay in place */
right: 0;
top: 0;
height: 450px;
padding: 10px;
border: 5px solid #fff;
width: 23%;
overflow: hidden;
background-color: #444; /* Fallback color */
color: #fff;
> strong {
background-color: #444;
}
}
#interactive-tutorial-text {
padding: 4px;
margin: 4px;
color: #fff;
background-color: #444;
font-size: $defaultFontSize * 0.875;
max-height: 350px;
overflow-y: auto;
}
#interactive-tutorial-exit,
#interactive-tutorial-next,
#interactive-tutorial-back {
@include borderRadius(12px);
@include boxShadow(1px 1px 3px #000);
color: #aaa;
font-size: $defaultFontSize * 1.125;
font-weight: bold;
background-color: #000;
&:hover,
&:focus {
color: #fff;
text-decoration: none;
cursor: pointer;
}
}
#interactive-tutorial-exit {
position: absolute;
bottom: 0;
left: 0;
padding: 4px;
}
#interactive-tutorial-back {
float: left;
padding: 4px;
}
#interactive-tutorial-next {
float: right;
padding: 4px;
}
.interactive-tutorial-command {
background-color: #000;
color: $hacker-green;
white-space: nowrap;
}
.interactive-tutorial-code {
background-color: #272822;
color: white;
padding: 3px;
}
.interactive-tutorial-tab {
background-color: #555;
color: #e6e6e6;
padding: 3px;
box-shadow: 0 0 3px #000;
}

@ -1,111 +0,0 @@
@import "mixins";
@import "reset";
@import "theme";
@include keyframes(LOADERSPINNER) {
0% {
#{$browser}transform: translate(-50%, -50%) rotate(0deg);
}
100% {
#{$browser}transform: translate(-50%, -50%) rotate(360deg);
}
}
@include keyframes(LOADERLABEL) {
0% {
opacity: 1;
#{$browser}transform: translate(-50%, -50%) scale(1);
}
5% {
opacity: 0.5;
#{$browser}transform: translate(-50%, -50%) scale(0.5);
}
95% {
opacity: 0.5;
#{$browser}transform: translate(-50%, -50%) scale(0.5);
}
100% {
opacity: 1;
#{$browser}transform: translate(-50%, -50%) scale(1);
}
}
.loaderoverlay {
$spinnerBoxSize: 200px;
$themeColor: #0c0;
position: absolute;
width: 100%;
height: 100%;
background: #000;
color: $themeColor;
%spinnerBox {
border: 20px solid rgba(0, 0, 0, 0);
border-top-color: $themeColor;
border-bottom-color: $themeColor;
border-radius: 1000px;
position: absolute;
top: 50%;
left: 50%;
}
.loaderspinner:before,
.loaderspinner:after {
content: "";
}
.loaderspinner {
@extend %spinnerBox;
@include animation(LOADERSPINNER 5s linear infinite);
width: $spinnerBoxSize;
height: $spinnerBoxSize;
}
.loaderspinner:before {
@extend %spinnerBox;
@include animation(LOADERSPINNER 10s linear infinite);
width: $spinnerBoxSize * 0.8;
height: $spinnerBoxSize * 0.8;
}
.loaderspinner:after {
@extend %spinnerBox;
@include animation(LOADERSPINNER 5s linear infinite);
width: $spinnerBoxSize * 0.6;
height: $spinnerBoxSize * 0.6;
}
.loaderlabel {
@include animation(LOADERLABEL 5s linear infinite);
text-transform: uppercase;
font-family: sans-serif;
font-size: $defaultFontSize * 1.375;
font-weight: 700;
letter-spacing: 2px;
position: absolute;
top: 50%;
left: 50%;
}
}
.killAllMessage {
position: absolute;
top: 95%;
left: 50%;
-webkit-transform: translateX(-50%);
-moz-transform: translateX(-50%);
-ms-transform: translateX(-50%);
-o-transform: translateX(-50%);
transform: translateX(-50%);
}
.killAllMessageWrapperHidden {
display: none;
}
.killAllMessageWrapperShow {
display: block;
}

@ -1,137 +0,0 @@
@import "mixins";
@import "theme";
/**
* Styling for the main navigation menu on the left-hand-side
*/
.mainmenu {
list-style-type: none;
margin: 0;
padding: 0;
width: 10%;
position: fixed;
height: 100%;
overflow: auto;
border: 0;
border-bottom: 1px solid #000;
border-radius: 0;
background-color: #333;
}
/* Default buttons */
.mainmenu > li a,
.mainmenu > li button {
display: block;
color: #e6e6e6;
background-color: #555;
padding: 12px 8px;
text-decoration: none;
cursor: pointer;
width: 100%;
text-align: left;
}
.mainmenu.classic > li a,
.mainmenu.classic > li button {
padding: 16px;
}
.mainmenu.compact > li a,
.mainmenu.compact > li button {
display: block;
color: #e6e6e6;
background-color: #555;
text-decoration: none;
cursor: pointer;
width: 100%;
text-align: left;
padding: 4px;
}
/* Hovering makes them lighter */
.mainmenu > li a:hover,
.mainmenu > li a:hover:not(.active),
.mainmenu > li a:focus {
background-color: #777;
color: #fff;
}
.mainmenu > li button:hover,
.mainmenu > li button:hover:not(.active) {
background-color: #777;
color: #fff;
}
/* Panel headers can become active, and they are "lighter" than the rest */
.mainmenu > li a.active,
.mainmenu > li button.active {
background-color: #777;
color: #fff;
}
.mainmenu > li a.active:hover,
.mainmenu > li button.active:hover {
background-color: #aaa;
}
.menu-header {
position: relative;
}
#hacking-menu-header-li,
#character-menu-header-li,
#world-menu-header-li,
#help-menu-header-li {
position: relative;
}
/* Accordion Outline */
.mainmenu-accordion-header,
.mainmenu-accordion-header-compact {
outline: 2px solid #fff !important;
}
.mainmenu-accordion-header-classic {
border: 2px solid #fff;
padding: 16px !important;
}
/* Plus and minus signs */
.mainmenu-accordion-header:after,
.mainmenu-accordion-header-compact:after {
content: "\02795";
float: right;
font-size: $defaultFontSize * 0.8125;
position: absolute;
bottom: 25%;
right: 3px;
color: transparent;
text-shadow: 0 0 0 #fff;
}
.mainmenu-accordion-header-classic:after {
content: "\02795";
float: right;
font-size: $defaultFontSize * 0.8125;
color: #fff;
margin-left: 5px;
}
.mainmenu-accordion-header.opened,
.mainmenu-accordion-header-classic.opened,
.mainmenu-accordion-header-compact.opened {
background-color: #222 !important;
&:after {
content: "\2796";
}
}
/* Slide down transition */
.mainmenu-accordion-panel {
transition: max-height 0.2s ease-out;
}

@ -1,128 +0,0 @@
@import "mixins";
@import "theme";
/* CSS for different main menu pages, such as character info, script editor, etc (but excluding
terminal which has its own page) */
#generic-react-container {
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
flex-grow: 1;
}
#generic-react-container::-webkit-scrollbar {
display: none; /* for Chrome, Safari, and Opera */
}
#world-city-name,
#world-city-desc {
padding: 4px;
margin: 4px;
}
#create-program-page-text,
#create-program-list {
width: 70%;
}
.faction-work-div {
width: 70%;
height: 100%;
}
.faction-work-div-wrapper {
overflow: hidden;
border: 2px solid #333;
padding: 6px;
margin: 6px;
width: 70%;
}
.faction-container p,
.faction-container pre {
padding: 4px 6px;
margin: 4px 6px;
}
.faction-container pre {
width: 70%;
white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
/* World */
#world-container li {
margin: 0 0 15px 0;
list-style-type: none;
}
/* Tutorial */
#tutorial-container {
position: fixed;
padding-top: 10px;
}
#tutorial-text {
width: 70%;
margin: 10px;
}
#tutorial-container a {
width: 50%;
}
/* Dev menu */
#dev-menu-container {
position: fixed;
padding-top: 10px;
}
#dev-menu-text {
width: 70%;
margin: 10px;
}
#dev-menu-container a {
width: 50%;
}
/* Location */
#location-container {
position: fixed;
padding: 6px;
overflow-x: hidden;
}
#location-container a {
display: inline-block;
width: 30%;
}
#location-slums-description {
width: 70%;
margin: 10px;
}
#location-return-to-world-button {
margin: 10px;
padding: 6px;
}
#location-container > * {
margin: 10px 5px 10px 5px;
}
#location-job-reputation,
#location-company-favor {
display: inline;
}
/* Infiltration */
#infiltration-container {
position: fixed;
margin: 5px;
width: 70%;
}

@ -1,3 +0,0 @@
.milestones-container {
width: 60%;
}

@ -1,119 +0,0 @@
@import "mixins";
@import "theme";
/* css for Missions */
/* Hacking missions */
#mission-container {
overflow: hidden;
}
.hack-mission-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
grid-gap: 2.5%;
height: 90%;
position: absolute;
width: 100%;
overflow-y: auto;
padding-right: 10px;
&::-webkit-scrollbar {
display: none;
}
}
.hack-mission-node {
z-index: 5;
background-color: #808080;
align-self: center;
justify-self: center;
display: inline-block;
p {
@include userSelect(none);
margin-top: 8px;
color: #fff;
font-size: $defaultFontSize * 0.75;
text-align: center;
}
}
.hack-mission-player-node {
color: #fff;
background-color: #00f;
}
.hack-mission-player-node-active {
border: 2px solid #fff;
background-color: #66f;
}
.hack-mission-enemy-node {
color: #fff;
background-color: #f00;
}
.hack-mission-cpu-node {
@include borderRadius(50%);
width: 100%;
height: 100%;
}
.hack-mission-firewall-node {
width: 90%;
height: 100%;
}
.hack-mission-database-node {
@include transform(skew(20deg));
width: 100%;
height: 90%;
p {
@include transform(skew(-20deg));
@include userSelect(none);
color: #fff;
font-size: $defaultFontSize * 0.75;
margin-top: 8px;
text-align: center;
}
}
.hack-mission-transfer-node {
@include transform(skew(-20deg));
width: 100%;
height: 90%;
p {
@include transform(skew(20deg));
@include userSelect(none);
color: #fff;
font-size: $defaultFontSize * 0.75;
margin-top: 8px;
text-align: center;
}
}
.hack-mission-spam-node,
.hack-mission-shield-node {
height: 100%;
width: 100%;
}
/* Non-map related DOM elements */
/* Element at the top of the Hacking Mission page (intro page, start button, guide buttons, etc.) */
.hack-mission-header-element {
margin: 6px;
}
.hack-mission-action-buttons-container {
border: 2px solid #fff;
}

@ -1,246 +0,0 @@
@import "mixins";
@import "theme";
@import "styles";
/* Pop-up boxes */
.popup-box-container {
display: none; /* Initially hidden */
position: fixed; /* Stay in place */
z-index: 1300; /* Sit on top */
left: 0;
top: 0;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background-color: rbga(var(--my-background-color), 0.4);
}
.popup-box-content {
background-color: var(--my-background-color);
padding: 12px;
border: 2px solid $hacker-green;
width: 70%;
max-height: 80%;
overflow-y: auto;
z-index: 11; /* Sit on top of the container */
color: var(--my-font-color);
box-shadow: 0px 3px 5px -1px #090, 0px 5px 8px 0px #090, 0px 1px 14px 0px #090;
}
.popup-box-input-div {
margin: 2px;
}
.popup-box-button,
.popup-box-button-inactive {
color: #aaa;
float: right;
font-size: $defaultFontSize;
font-weight: bold;
padding: 2px;
margin: 6px;
border: 1px solid #fff;
background-color: #000;
}
.popup-box-button:hover,
.popup-box-button:focus {
color: var(--my-font-color);
text-decoration: none;
cursor: pointer;
}
.popupbox-button-inactive {
pointer-events: none;
cursor: default;
}
#yes-no-text-input-box-input {
color: var(--my-font-color);
border: 1px solid #fff;
background-color: #000;
}
.dialog-box-container {
display: block;
position: absolute;
z-index: 10;
width: 50%;
height: auto;
max-height: 50%;
top: 40%;
left: 50%;
margin: -10% 0 0 -25%;
overflow: auto;
background-color: var(--my-background-color);
border: 5px solid var(--my-highlight-color);
}
.log-box-container {
display: flex;
flex-flow: column;
background-color: gray;
width: 50%;
position: fixed;
left: 50%;
top: 40%;
margin: -10% 0 0 -25%;
height: auto;
max-height: 50%;
z-index: 10;
background-color: var(--my-background-color);
border: 2px solid $hacker-green;
}
.log-box-header {
z-index: 1300;
background-color: #333;
border: 2px solid $hacker-green;
display: flex;
flex: row nowrap;
align-items: center;
justify-content: space-between;
cursor: grab;
}
.log-box-log-container {
overflow-y: auto;
}
.log-box-button {
color: #aaa;
font-size: $defaultFontSize;
font-weight: bold;
padding: 2px;
margin: 6px;
border: 1px solid #fff;
background-color: #000;
}
.log-box-button:hover,
.log-box-button:focus {
color: var(--my-font-color);
text-decoration: none;
cursor: pointer;
}
.dialog-box-content {
z-index: 2;
background-color: var(--my-background-color);
padding: 10px;
p span {
padding: 0;
margin: 0;
}
}
.dialog-box-close-button {
@include borderRadius(12px);
@include boxShadow(1px 1px 3px #000);
@extend .noselect;
float: right;
color: #aaa;
font-size: $defaultFontSize * 1.25;
font-weight: bold;
}
#log-box-close {
position: fixed;
right: 27%;
}
#log-box-kill-script {
right: 11%;
position: relative;
}
#log-box-close,
#log-box-kill-script {
float: right;
display: inline-block;
}
.dialog-box-close-button:hover,
.dialog-box-close-button:focus {
color: #fff;
text-decoration: none;
cursor: pointer;
}
/* Faction invitation box */
#faction-invitation-box-container {
transition: opacity 400ms ease-in;
}
#faction-invitation-box-warning {
margin: 4px;
padding: 4px;
}
/* Infiltration-box */
#infiltration-box-sell,
#infiltration-box-faction {
display: block;
padding: 8px;
margin: 8px;
}
#infiltration-box-content span {
padding: 0;
margin: 0;
}
#infiltration-faction-select {
background-color: #000;
}
/* Game Options */
#game-options-container {
transition: opacity 400ms ease-in;
}
#game-options-content {
background-color: var(--my-background-color);
padding: 10px;
border: 5px solid var(--my-highlight-color);
color: var(--my-font-color);
width: 80%;
max-height: 80%;
overflow-y: auto;
}
#game-options-left-panel,
#game-options-right-panel {
display: inline-block;
width: 49%;
}
#game-options-close-button {
@include borderRadius(12px);
@include boxShadow(1px 1px 3px #000);
color: #aaa;
float: right;
margin: 4px;
padding: 4px;
font-size: $defaultFontSize * 1.25;
font-weight: bold;
}
#game-options-close-button:hover,
#game-options-close-button:focus {
color: #fff;
text-decoration: none;
cursor: pointer;
}
#game-options-left-panel fieldset {
padding: 2px;
margin: 2px;
}
#import-game-file-selector {
display: none;
}

@ -1,34 +0,0 @@
@import "theme";
/**
* Styling for the Red Pill screen (the BitNode selection UI)
*/
#red-pill-container {
position: fixed;
}
.bitnode {
&.level-0 {
color: red;
}
&.level-1 {
color: yellow;
}
&.level-2 {
color: #48d1cc;
}
&.level-3 {
color: blue;
}
&.unimplemented {
color: gray;
}
&:hover {
color: #fff;
}
}

@ -1,28 +0,0 @@
/**
* Styling for the Re-Sleeving Page
*/
@import "theme";
.resleeve-elem {
border: 1px solid white;
margin: 4px;
width: 75%;
p {
font-size: $defaultFontSize * 0.8125;
}
}
.resleeve-panel {
display: inline-block;
margin: 0;
padding: 2px;
}
.resleeve-aug-selector {
font-size: $defaultFontSize * 0.8125;
option {
font-size: $defaultFontSize * 0.8125;
}
}

@ -1,92 +0,0 @@
@import "mixins";
@import "theme";
/**
* Styling for Script Editor (both Ace and CodeMirror)
*/
#script-editor-buttons-wrapper {
width: 100%;
padding-right: 0;
margin-right: 0;
}
.script-editor-wrapper {
height: 110vh;
width: 70%;
background: transparent;
}
#script-editor-filename-wrapper {
background-color: #555;
margin-right: 0;
padding-left: 6px;
width: 100%;
border: 2px solid var(--my-highlight-color);
}
#script-editor-filename-tag {
display: inline-block;
padding-top: 10px;
padding-bottom: 0;
float: center;
background-color: #555;
color: #fff;
}
#script-editor-filename {
$boxShadowArgs: inset 0 0 8px rgba(0, 0, 0, 0.1), 0 0 16px rgba(0, 0, 0, 0.1);
@include boxShadow($boxShadowArgs);
background-color: #555;
border: 2px solid var(--my-highlight-color);
color: #fff;
display: inline-block;
float: center;
margin: 4px;
padding: 2px;
resize: none;
width: 60%;
}
#script-editor-status {
float: left;
color: #fff;
}
#script-editor-options-panel {
position: absolute;
right: 9%;
bottom: 15%;
border: 2px solid #fff;
width: 19%;
background-color: #444;
padding: 2px;
overflow: auto;
z-index: 1;
color: #fff;
max-height: 50%;
}
#script-editor-options-panel fieldset {
margin-top: 8px;
margin-bottom: 8px;
padding: 2px;
font-size: $defaultFontSize * 0.75;
input {
margin: 2px;
}
}
.editor-options-container {
display: flex;
flex-flow: column;
}
.editor-options-line {
display: flex;
flex: row nowrap;
align-items: center;
justify-content: start;
}

@ -1,25 +0,0 @@
/**
* Styling for the Sleeves Management page
*/
@import "theme";
.sleeve-elem {
border: 1px solid white;
margin: 4px;
display: block;
}
.sleeves-page-info {
display: "block";
width: 75%;
}
.sleeve-panel {
display: inline-block;
margin: 0;
padding: 2px;
select {
display: block;
}
}

@ -1,96 +0,0 @@
@import "theme";
.stock-market-container {
p {
font-size: $defaultFontSize * 0.8125;
}
a {
font-size: $defaultFontSize * 0.875;
}
}
.stock-market-info-and-purchases {
> h2 {
display: block;
margin-top: 10px;
margin-left: 10px;
}
> p {
display: block;
margin-left: 10px;
width: 70%;
}
> a,
> button {
margin: 10px;
}
}
#stock-market-list {
list-style: none;
li {
button {
font-size: $defaultFontSize;
}
}
}
#stock-market-watchlist-filter {
display: block;
margin: 5px 5px 5px 10px;
padding: 4px;
width: 50%;
}
.stock-market-input {
display: inline-block;
padding: 4px;
margin: 2px;
background-color: #000;
border: 1px solid #fff;
color: var(--my-font-color);
}
.stock-market-price-movement-warning {
border: 1px solid white;
color: red;
margin: 2px;
padding: 2px;
}
.stock-market-position-text {
color: #fff;
display: block;
p {
color: #fff;
display: inline-block;
margin: 4px;
}
h3 {
margin: 4px;
}
}
.stock-market-order-list {
overflow-y: auto;
max-height: 100px;
li {
color: #fff;
padding: 4px;
}
}
.stock-market-order-cancel-btn {
background-color: #000;
border: 1px solid #fff;
color: var(--my-font-color);
margin: 2px;
padding: 0;
}

@ -1,528 +0,0 @@
@import "mixins";
@import "theme";
@import "reset";
:root {
--my-font-color: #0c0;
--my-background-color: #000;
--my-highlight-color: #fff;
--my-prompt-color: #f92672;
}
body {
background-color: var(--my-background-color);
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
}
body::-webkit-scrollbar {
display: none; /* for Chrome, Safari, and Opera */
}
p,
pre,
h2,
h3,
h4,
.text,
td {
color: var(--my-font-color);
}
h1 {
font-size: $defaultFontSize * 1.375;
color: var(--my-font-color);
}
ul {
padding: 2px;
list-style-type: none;
}
li {
list-style-type: none;
}
br {
@extend .noselect;
}
#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: $defaultFontSize * 0.625;
position: absolute; /* Position the badge within the relatively positioned button */
top: 0;
right: 0;
}
#factions-tab {
position: relative;
}
#factions-notification {
font-size: $defaultFontSize * 0.625;
position: absolute; /* Position the badge within the relatively positioned button */
top: 0;
right: 0;
}
#augmentations-tab {
position: relative;
}
#augmentations-notification {
font-size: $defaultFontSize * 0.625;
position: absolute; /* Position the badge within the relatively positioned button */
top: 0;
right: 0;
}
.notification-on {
background-color: #fa3e3e;
color: #fff;
border-radius: 2px;
padding: 1px 3px;
font-size: $defaultFontSize * 0.625;
top: 0;
right: 0;
position: absolute;
}
.notification-off {
background-color: #333;
color: #333;
border-radius: 0;
padding: 0;
display: "none";
}
.notification {
position: relative;
display: inline-block;
}
.notification .badge {
position: absolute;
top: 0;
right: 0;
padding: 2px;
background: red;
color: white;
}
/* 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 {
@include boxShadow(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: fixed;
top: 0;
left: 50%;
}
#status-text {
background-color: transparent;
bottom: 0;
color: #fff;
display: none;
font-size: $defaultFontSize * 1.25;
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;
&:hover {
text-decoration: none;
}
}
/* Accordion menus (Header with collapsible panel) */
.accordion-header {
background-color: #444;
color: #fff;
font-size: $defaultFontSize * 1.25;
margin: 6px 6px 0 6px;
padding: 4px 6px;
cursor: pointer;
width: 80%;
text-align: left;
border: none;
outline: none;
position: relative;
&.active,
&:hover {
background-color: #555;
}
&.active:hover {
background-color: #666;
}
&:after {
content: "\02795"; /* "plus" sign (+) */
font-size: $defaultFontSize * 0.875;
float: right;
color: transparent;
text-shadow: 0 0 0 #fff;
position: absolute;
bottom: 5px;
right: 6px;
}
&.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;
div,
ul,
p,
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: $hacker-green;
}
.money-gold {
color: $money-gold;
}
.light-yellow {
color: $light-yellow;
}
.unbuyable {
color: #66cfbc;
}
.failure {
color: $alert-red;
text-shadow: 0 0 0 $alert-red;
}
.success {
color: $success-green;
text-shadow: 0 0 0 $success-green;
}
.physical-yellow {
color: $my-stat-physical;
}
.charisma-purple {
color: $my-stat-cha-color;
}
.reputation {
color: $light-yellow;
}
.smallfont {
font-size: $defaultFontSize * 0.8125;
}
.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 {
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}

@ -1,129 +0,0 @@
@import "theme";
/* Styling for tooltip-style elements */
/* Tool tips (when hovering over an element */
.tooltip {
display: inline-block;
position: relative;
.tooltiptext {
visibility: hidden;
width: 300px;
background-color: var(--my-background-color);
border: 2px solid var(--my-highlight-color);
color: #fff;
text-align: center;
padding: 4px;
left: 101%;
pointer-events: none;
position: absolute;
z-index: 99;
}
/* Positioned to left of element rather than right */
.tooltiptextleft {
visibility: hidden;
width: 300px;
background-color: var(--my-background-color);
border: 2px solid var(--my-highlight-color);
color: #fff;
text-align: center;
padding: 4px;
top: 50%;
left: 50%;
transform: translate(-100%, -100%);
/* Backwards compatibility */
-webkit-transform: translate(-100%, -100%);
-moz-transform: translate(-100%, -100%);
-o-transform: translate(-100%, -100%);
-ms-transform: translate(-100%, -100%);
position: absolute;
z-index: 99;
}
/* Tooltip goes below cursor instead of above */
.tooltiptextlow {
visibility: hidden;
width: 300px;
background-color: var(--my-background-color);
border: 2px solid var(--my-highlight-color);
color: #fff;
text-align: center;
padding: 4px;
left: 101%;
pointer-events: none;
position: absolute;
z-index: 99;
bottom: 25%;
}
}
/* Same thing as a normal tooltip except its a bit higher */
.tooltip .tooltiptexthigh {
visibility: hidden;
width: 300px;
background-color: var(--my-background-color);
border: 2px solid var(--my-highlight-color);
color: #fff;
text-align: center;
padding: 4px;
left: 101%;
bottom: -25%;
position: absolute;
z-index: 99;
}
.tooltip:hover .tooltiptext,
.tooltip:hover .tooltiptexthigh,
.tooltip:hover .tooltiptextleft,
.tooltip:hover .tooltiptextlow {
visibility: visible;
}
.copy_tooltip {
position: relative;
display: inline-block;
}
.copy_tooltip_copied {
color: #fff;
transition: color 0.3s;
}
.copy_tooltip .copy_tooltip_text {
visibility: hidden;
font-size: 15px;
padding: 5px;
background-color: var(--my-background-color);
color: #fff;
text-align: center;
position: absolute;
z-index: 1;
top: 120%;
left: 5%;
opacity: 0;
border: 2px solid var(--my-highlight-color);
}
.copy_tooltip .copy_tooltip_text::after {
content: "";
position: absolute;
bottom: 100%;
left: 50%;
margin-left: -6px;
border-width: 8px;
border-style: solid;
border-color: transparent transparent white transparent;
}
.copy_tooltip .copy_tooltip_text_visible {
visibility: visible;
opacity: 1;
transition: opacity 0.3s;
}

@ -1,68 +0,0 @@
/* required LIB STYLES */
/* .Treant se automatski dodaje na svaki chart conatiner */
.Treant {
position: relative;
overflow: hidden;
padding: 0 !important;
}
.Treant > .node,
.Treant > .pseudo {
position: absolute;
display: block;
visibility: hidden;
}
.Treant.Treant-loaded .node,
.Treant.Treant-loaded .pseudo {
visibility: visible;
}
.Treant > .pseudo {
width: 0;
height: 0;
border: none;
padding: 0;
}
.Treant .collapse-switch {
width: 3px;
height: 3px;
display: block;
border: 1px solid black;
position: absolute;
top: 1px;
right: 1px;
cursor: pointer;
}
.Treant .collapsed .collapse-switch {
background-color: #868dee;
}
.Treant > .node img {
border: none;
float: left;
}
.Treant > .node {
cursor: pointer;
padding: 4px;
min-width: 60px;
text-align: center;
border: 2px solid #e8e8e3;
border-radius: 2px;
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
font-size: 12px;
}
.Treant > .researched {
background-color: #666;
font-size: 16px;
}
.Treant > .locked > div {
color: red;
pointer-events: none;
}
.Treant > .node > div {
font-size: 12px;
}
.Treant > .unlocked:hover {
background-color: #666;
}

@ -1,51 +0,0 @@
@import "mixins";
@import "theme";
/* Both Work in progress and BitNode stuff */
.generic-fullscreen-container {
color: var(--my-font-color);
width: 99%;
height: 100%;
overflow-y: hidden;
}
.generic-fullscreen-container-scroll {
height: 100%;
width: 100%;
overflow: auto;
padding-right: 20px;
}
#work-in-progress-container {
position: fixed;
}
#work-in-progress-text {
color: var(--my-font-color);
width: 70%;
margin: 10px;
}
.work-button {
@include borderRadius(12px);
@include boxShadow(1px 1px 3px #000);
color: #aaa;
float: left;
font-size: $defaultFontSize * 1.25;
font-weight: bold;
margin: 10px;
padding: 5px;
border: 3px solid #fff;
}
.work-button:hover,
.work-button:focus {
color: #fff;
text-decoration: none;
cursor: pointer;
}
#cinematic-text-container {
position: fixed;
}

66
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -3,6 +3,28 @@
Changelog Changelog
========= =========
v0.55.0 - 2021-09-20 Material UI (hydroflame & community)
-------------------------------------------
** Global **
* The game is now 100% in typescript, react, and Material-UI
** Misc. **
* Corporations can no longer bribe special factions
* Infiltration can no longer lose focus of the keyboard.
* Fix terminal line limit
* Added theme editor
* Theme applies on game load (@Nolshine)
* Sleeves no longer consume all bonus time for some actions
* Fix a bug where the autocomlete list would get duplicates
* Fix tutorial not scaling properly on small screens
* Import should be more consistent
* Typo with 'help' command
* Fix infinite loop in casino
* nerf noodle bar
v0.54.0 - 2021-09-20 One big react node (hydroflame & community) v0.54.0 - 2021-09-20 One big react node (hydroflame & community)
------------------------------------------- -------------------------------------------

@ -64,9 +64,9 @@ documentation_title = '{0} Documentation'.format(project)
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.54' version = '0.55'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.54.0' release = '0.55.0'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

@ -1,18 +1,18 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8"/>
<title>Bitburner</title> <title>Bitburner</title>
<link rel="apple-touch-icon" sizes="180x180" href="dist/apple-touch-icon.png" /> <link rel="apple-touch-icon" sizes="180x180" href="dist/apple-touch-icon.png"/>
<link rel="icon" type="image/png" sizes="32x32" href="dist/favicon-32x32.png" /> <link rel="icon" type="image/png" sizes="32x32" href="dist/favicon-32x32.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="dist/favicon-16x16.png" /> <link rel="icon" type="image/png" sizes="16x16" href="dist/favicon-16x16.png"/>
<link rel="manifest" href="dist/site.webmanifest" /> <link rel="manifest" href="dist/site.webmanifest"/>
<link rel="mask-icon" href="dist/safari-pinned-tab.svg" color="#000000" /> <link rel="mask-icon" href="dist/safari-pinned-tab.svg" color="#000000"/>
<meta name="apple-mobile-web-app-title" content="Bitburner" /> <meta name="apple-mobile-web-app-title" content="Bitburner"/>
<meta name="application-name" content="Bitburner" /> <meta name="application-name" content="Bitburner"/>
<meta name="msapplication-TileColor" content="#000000" /> <meta name="msapplication-TileColor" content="#000000"/>
<meta name="msapplication-config" content="dist/browserconfig.xml" /> <meta name="msapplication-config" content="dist/browserconfig.xml"/>
<meta name="theme-color" content="#ffffff" /> <meta name="theme-color" content="#ffffff"/>
<!-- Google Analytics --> <!-- Google Analytics -->
<script> <script>
@ -30,31 +30,27 @@
m.parentNode.insertBefore(a, m); m.parentNode.insertBefore(a, m);
})(window, document, "script", "https://www.google-analytics.com/analytics.js", "ga"); })(window, document, "script", "https://www.google-analytics.com/analytics.js", "ga");
</script> </script>
<script> <script>
ga("create", "UA-100157497-1", "auto"); ga("create", "UA-100157497-1", "auto");
ga("send", "pageview"); ga("send", "pageview");
</script> </script>
<style>
body {
background-color: black;
}
* {
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
}
<link rel="shortcut icon" href="favicon.ico" /> *::-webkit-scrollbar {
<link href="dist/vendor.css" rel="stylesheet" /> display: none; /* for Chrome, Safari, and Opera */
<link href="main.css" rel="stylesheet" /> }
</head> </style>
<link rel="shortcut icon" href="favicon.ico"></head>
<body> <body>
<div id="entire-game-container"> <div id="root"/>
<div id="mainmenu-container" style="display: flex; flex-direction: row"></div> <script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="main.bundle.js"></script></body>
<!-- Status text -->
<div id="status-text-container">
<p id="status-text"></p>
</div>
</div>
<div id="modal-portal"></div>
<div id="unclickable" style="display: none">Click on this to upgrade your Source-File -1!</div>
<script type="text/javascript" src="dist/vendor.bundle.js"></script>
<script type="text/javascript" src="main.bundle.js"></script>
</body>
<script src="src/ThirdParty/raphael.min.js"></script>
</html> </html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -2105,13 +2105,14 @@ input[type="checkbox"] {
.popup-box-content { .popup-box-content {
background-color: var(--my-background-color); background-color: var(--my-background-color);
padding: 12px; padding: 12px;
border: 5px solid var(--my-highlight-color); border: 2px solid #adff2f;
width: 70%; width: 70%;
max-height: 80%; max-height: 80%;
overflow-y: auto; overflow-y: auto;
z-index: 11; z-index: 11;
/* Sit on top of the container */ /* Sit on top of the container */
color: var(--my-font-color); } color: var(--my-font-color);
box-shadow: 0 3px 5px -1px #090, 0 5px 8px 0 #090, 0 1px 14px 0 #090; }
.popup-box-input-div { .popup-box-input-div {
margin: 2px; } margin: 2px; }
@ -2169,12 +2170,12 @@ input[type="checkbox"] {
max-height: 50%; max-height: 50%;
z-index: 10; z-index: 10;
background-color: var(--my-background-color); background-color: var(--my-background-color);
border: 2px solid var(--my-highlight-color); } border: 2px solid #adff2f; }
.log-box-header { .log-box-header {
z-index: 1300; z-index: 1300;
background-color: #333; background-color: #333;
border: 1px solid var(--my-highlight-color); border: 2px solid #adff2f;
display: flex; display: flex;
flex: row nowrap; flex: row nowrap;
align-items: center; align-items: center;

File diff suppressed because one or more lines are too long

@ -148,6 +148,7 @@
"format": "prettier --write .", "format": "prettier --write .",
"start": "http-server -p 8000", "start": "http-server -p 8000",
"start:dev": "webpack-dev-server --progress --env.devServer --mode development", "start:dev": "webpack-dev-server --progress --env.devServer --mode development",
"start:dev-fast": "webpack-dev-server --progress --env.devServer --mode development --fast true",
"start:container": "webpack-dev-server --progress --env.devServer --mode development --env.runInContainer", "start:container": "webpack-dev-server --progress --env.devServer --mode development --env.runInContainer",
"build": "webpack --mode production", "build": "webpack --mode production",
"build:dev": "webpack --mode development", "build:dev": "webpack --mode development",

@ -2044,6 +2044,28 @@ function initAugmentations(): void {
} }
AddToAugmentations(SNA); AddToAugmentations(SNA);
const NeuroreceptorManager = new Augmentation({
name: AugmentationNames.NeuroreceptorManager,
repCost: 0.75e5,
moneyCost: 5.5e8,
info:
"A brain implant carefully assembled around the synapses, which " +
"micromanages the activity and levels of various neuroreceptor " +
"chemicals and modulates electrical acvitiy to optimize concentration, " +
"allowing the user to multitask much more effectively.",
stats: (
<>
This augmentation removes the penalty for not focusing on actions such as working in a job or working for a
faction.
</>
),
});
NeuroreceptorManager.addToFactions(["Tian Di Hui"]);
if (augmentationExists(AugmentationNames.NeuroreceptorManager)) {
delete Augmentations[AugmentationNames.NeuroreceptorManager];
}
AddToAugmentations(NeuroreceptorManager);
// Special Bladeburner Augmentations // Special Bladeburner Augmentations
const BladeburnersFactionName = "Bladeburners"; const BladeburnersFactionName = "Bladeburners";
if (factionExists(BladeburnersFactionName)) { if (factionExists(BladeburnersFactionName)) {
@ -2384,6 +2406,7 @@ function initAugmentations(): void {
hacknet_node_core_cost_mult: 1.1, hacknet_node_core_cost_mult: 1.1,
hacknet_node_level_cost_mult: 1.1, hacknet_node_level_cost_mult: 1.1,
work_money_mult: 0.9, work_money_mult: 0.9,
stats: null,
}); });
StaneksGift1.addToFactions([ChurchOfTheMachineGodFactionName]); StaneksGift1.addToFactions([ChurchOfTheMachineGodFactionName]);
resetAugmentation(StaneksGift1); resetAugmentation(StaneksGift1);
@ -2423,6 +2446,7 @@ function initAugmentations(): void {
hacknet_node_core_cost_mult: 1.05 / 1.1, hacknet_node_core_cost_mult: 1.05 / 1.1,
hacknet_node_level_cost_mult: 1.05 / 1.1, hacknet_node_level_cost_mult: 1.05 / 1.1,
work_money_mult: 0.95 / 0.9, work_money_mult: 0.95 / 0.9,
stats: null,
}); });
StaneksGift2.addToFactions([ChurchOfTheMachineGodFactionName]); StaneksGift2.addToFactions([ChurchOfTheMachineGodFactionName]);
resetAugmentation(StaneksGift2); resetAugmentation(StaneksGift2);
@ -2462,6 +2486,7 @@ function initAugmentations(): void {
hacknet_node_core_cost_mult: 1 / 1.05, hacknet_node_core_cost_mult: 1 / 1.05,
hacknet_node_level_cost_mult: 1 / 1.05, hacknet_node_level_cost_mult: 1 / 1.05,
work_money_mult: 1 / 0.95, work_money_mult: 1 / 0.95,
stats: null,
}); });
StaneksGift3.addToFactions([ChurchOfTheMachineGodFactionName]); StaneksGift3.addToFactions([ChurchOfTheMachineGodFactionName]);
resetAugmentation(StaneksGift3); resetAugmentation(StaneksGift3);
@ -2475,6 +2500,7 @@ function initAugmentations(): void {
"(hydro notes: Finishes the BN, eventually)", "(hydro notes: Finishes the BN, eventually)",
prereqs: [AugmentationNames.StaneksGift3], prereqs: [AugmentationNames.StaneksGift3],
isSpecial: true, isSpecial: true,
stats: null,
}); });
StaneksGiftAscension4.addToFactions([ChurchOfTheMachineGodFactionName]); StaneksGiftAscension4.addToFactions([ChurchOfTheMachineGodFactionName]);
resetAugmentation(StaneksGiftAscension4); resetAugmentation(StaneksGiftAscension4);

@ -41,6 +41,7 @@ export const AugmentationNames: IMap<string> = {
CranialSignalProcessorsG4: "Cranial Signal Processors - Gen IV", CranialSignalProcessorsG4: "Cranial Signal Processors - Gen IV",
CranialSignalProcessorsG5: "Cranial Signal Processors - Gen V", CranialSignalProcessorsG5: "Cranial Signal Processors - Gen V",
NeuronalDensification: "Neuronal Densification", NeuronalDensification: "Neuronal Densification",
NeuroreceptorManager: "Neuroreceptor Management Implant",
NuoptimalInjectorImplant: "Nuoptimal Nootropic Injector Implant", NuoptimalInjectorImplant: "Nuoptimal Nootropic Injector Implant",
SpeechEnhancement: "Speech Enhancement", SpeechEnhancement: "Speech Enhancement",
FocusWire: "FocusWire", FocusWire: "FocusWire",

@ -10,6 +10,7 @@ import { PurchasedAugmentations } from "./PurchasedAugmentations";
import { SourceFiles } from "./SourceFiles"; import { SourceFiles } from "./SourceFiles";
import { canGetBonus } from "../../ExportBonus"; import { canGetBonus } from "../../ExportBonus";
import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
@ -22,6 +23,7 @@ interface IProps {
} }
export function AugmentationsRoot(props: IProps): React.ReactElement { export function AugmentationsRoot(props: IProps): React.ReactElement {
const player = use.Player();
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void { function rerender(): void {
setRerender((o) => !o); setRerender((o) => !o);
@ -69,10 +71,14 @@ export function AugmentationsRoot(props: IProps): React.ReactElement {
Purchased Augmentations Purchased Augmentations
</Typography> </Typography>
<Box mx={2}> <Box mx={2}>
<Tooltip title={"'I never asked for this'"}> <Tooltip title={<Typography>'I never asked for this'</Typography>}>
<Button onClick={props.installAugmentationsFn}>Install Augmentations</Button> <span>
<Button disabled={player.queuedAugmentations.length === 0} onClick={props.installAugmentationsFn}>
Install Augmentations
</Button>
</span>
</Tooltip> </Tooltip>
<Tooltip title={"It's always a good idea to backup/export your save!"}> <Tooltip title={<Typography>It's always a good idea to backup/export your save!</Typography>}>
<Button sx={{ mx: 2 }} onClick={doExport} color="error"> <Button sx={{ mx: 2 }} onClick={doExport} color="error">
Backup Save {exportBonusStr()} Backup Save {exportBonusStr()}
</Button> </Button>

@ -396,6 +396,15 @@ BitNodes["BitNode9"] = new BitNode(
<br /> <br />
(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT when installing (Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT when installing
Augmentations) Augmentations)
<br />
<br />
This Source-File also increases your hacknet multipliers by:
<br />
Level 1: 8%
<br />
Level 2: 12%
<br />
Level 3: 14%
</> </>
), ),
); );

@ -0,0 +1,33 @@
import React, { useState, useEffect } from "react";
import { Modal } from "../../ui/React/Modal";
import { use } from "../../ui/Context";
import { EventEmitter } from "../../utils/EventEmitter";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
export const BitFlumeEvent = new EventEmitter<[]>();
export function BitFlumeModal(): React.ReactElement {
const router = use.Router();
const [open, setOpen] = useState(false);
function flume(): void {
router.toBitVerse(true, false);
setOpen(false);
}
useEffect(() => BitFlumeEvent.subscribe(() => setOpen(true)), []);
return (
<Modal open={open} onClose={() => setOpen(false)}>
<Typography>
WARNING: USING THIS PROGRAM WILL CAUSE YOU TO LOSE ALL OF YOUR PROGRESS ON THE CURRENT BITNODE.
<br />
<br />
Do you want to travel to the BitNode Nexus? This allows you to reset the current BitNode and select a new one.
</Typography>
<br />
<br />
<Button onClick={flume}>Travel to the BitVerse</Button>
</Modal>
);
}

@ -1,30 +0,0 @@
import React from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IRouter } from "../../ui/Router";
import { removePopup } from "../../ui/React/createPopup";
interface IProps {
player: IPlayer;
router: IRouter;
popupId: string;
}
export function BitFlumePopup(props: IProps): React.ReactElement {
function flume(): void {
props.router.toBitVerse(true, false);
removePopup(props.popupId);
}
return (
<>
WARNING: USING THIS PROGRAM WILL CAUSE YOU TO LOSE ALL OF YOUR PROGRESS ON THE CURRENT BITNODE.
<br />
<br />
Do you want to travel to the BitNode Nexus? This allows you to reset the current BitNode and select a new one.
<br />
<br />
<button className="std-button" onClick={flume}>
Travel to the BitVerse
</button>
</>
);
}

@ -3,10 +3,42 @@ import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { BitNodes } from "../BitNode"; import { BitNodes } from "../BitNode";
import { enterBitNode, setRedPillFlag } from "../../RedPill"; import { enterBitNode, setRedPillFlag } from "../../RedPill";
import { PortalPopup } from "./PortalPopup"; import { PortalModal } from "./PortalModal";
import { createPopup } from "../../ui/React/createPopup";
import { CinematicText } from "../../ui/React/CinematicText"; import { CinematicText } from "../../ui/React/CinematicText";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
const useStyles = makeStyles(() =>
createStyles({
level0: {
color: "red",
"&:hover": {
color: "#fff",
},
},
level1: {
color: "yellow",
"&:hover": {
color: "#fff",
},
},
level2: {
color: "#48d1cc",
"&:hover": {
color: "#fff",
},
},
level3: {
color: "blue",
"&:hover": {
color: "#fff",
},
},
}),
);
interface IPortalProps { interface IPortalProps {
n: number; n: number;
@ -16,51 +48,53 @@ interface IPortalProps {
enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void; enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
} }
function BitNodePortal(props: IPortalProps): React.ReactElement { function BitNodePortal(props: IPortalProps): React.ReactElement {
const router = use.Router(); const [portalOpen, setPortalOpen] = useState(false);
const classes = useStyles();
const bitNode = BitNodes[`BitNode${props.n}`]; const bitNode = BitNodes[`BitNode${props.n}`];
if (bitNode == null) { if (bitNode == null) {
return <>O</>; return <>O</>;
} }
let cssClass; let cssClass = classes.level0;
if (props.n === 12 && props.level >= 2) { if (props.n === 12 && props.level >= 2) {
// Repeating BitNode // Repeating BitNode
cssClass = "level-2"; cssClass = classes.level2;
} else { } else if (props.level === 1) {
cssClass = `level-${props.level}`; cssClass = classes.level1;
} else if (props.level === 3) {
cssClass = classes.level3;
} }
if (props.level === 2) {
function openPortalPopup(): void { cssClass = classes.level2;
const popupId = "bitverse-portal-popup";
createPopup(popupId, PortalPopup, {
n: props.n,
level: props.level,
enter: props.enter,
router: router,
destroyedBitNode: props.destroyedBitNode,
flume: props.flume,
popupId: popupId,
});
} }
return ( return (
<button <>
className={`bitnode ${cssClass} tooltip`} <Tooltip
aria-label={`enter-bitnode-${bitNode.number.toString()}`} title={
onClick={openPortalPopup} <Typography>
> <strong>
<strong>O</strong> BitNode-{bitNode.number.toString()}: {bitNode.name}
<span className="tooltiptext"> </strong>
<strong> <br />
BitNode-{bitNode.number.toString()} {bitNode.desc}
<br /> </Typography>
{bitNode.name} }
</strong> >
<br /> <span onClick={() => setPortalOpen(true)} className={cssClass}>
{bitNode.desc} <b>O</b>
<br /> </span>
</span> </Tooltip>
</button> <PortalModal
open={portalOpen}
onClose={() => setPortalOpen(false)}
n={props.n}
level={props.level}
enter={props.enter}
destroyedBitNode={props.destroyedBitNode}
flume={props.flume}
/>
</>
); );
} }
@ -115,32 +149,32 @@ export function BitverseRoot(props: IProps): React.ReactElement {
return ( return (
// prettier-ignore // prettier-ignore
<div className="noselect"> <>
<pre> O </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O </Typography>
<pre> | O O | O O | </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | O O | O O | </Typography>
<pre> O | | / __| \ | | O </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | / __| \ | | O </Typography>
<pre> O | O | | O / | O | | O | O </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | O | | O / | O | | O | O </Typography>
<pre> | | | | |_/ |/ | \_ \_| | | | | </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | |_/ |/ | \_ \_| | | | | </Typography>
<pre> O | | | O | | O__/ | / \__ | | O | | | O </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> O | | | O | | O__/ | / \__ | | O | | | O </Typography>
<pre> | | | | | | | / /| O / \| | | | | | | </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | / /| O / \| | | | | | | </Typography>
<pre>O | | | \| | O / _/ | / O | |/ | | | O</pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>O | | | \| | O / _/ | / O | |/ | | | O</Typography>
<pre>| | | |O / | | O / | O O | | \ O| | | |</pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | O / | O O | | \ O| | | |</Typography>
<pre>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</Typography>
<pre> \| O | |_/ |\| \ <BitNodePortal n={13} level={nextSourceFileFlags[13]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \__| \_| | O |/ </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| O | |_/ |\| \ <BitNodePortal n={13} level={nextSourceFileFlags[13]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \__| \_| | O |/ </Typography>
<pre> | | |_/ | | \| / | \_| | | </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | |_/ | | \| / | \_| | | </Typography>
<pre> \| / \| | / / \ |/ </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| / \| | / / \ |/ </Typography>
<pre> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </Typography>
<pre> <BitNodePortal n={9} level={nextSourceFileFlags[9]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={nextSourceFileFlags[12]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={9} level={nextSourceFileFlags[9]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | | | | | | <BitNodePortal n={12} level={nextSourceFileFlags[12]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
<pre> | | | / / \ \ | | | </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | / / \ \ | | | </Typography>
<pre> \| | / <BitNodePortal n={7} level={nextSourceFileFlags[7]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={nextSourceFileFlags[8]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| | / <BitNodePortal n={7} level={nextSourceFileFlags[7]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> / \ <BitNodePortal n={8} level={nextSourceFileFlags[8]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \ | |/ </Typography>
<pre> \ | / / | | \ \ | / </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ | / / | | \ \ | / </Typography>
<pre> \ \JUMP <BitNodePortal n={5} level={nextSourceFileFlags[5]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={nextSourceFileFlags[6]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \JUMP <BitNodePortal n={5} level={nextSourceFileFlags[5]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} />3R | | | | | | R3<BitNodePortal n={6} level={nextSourceFileFlags[6]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> PMUJ/ / </Typography>
<pre> \|| | | | | | | | | ||/ </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \|| | | | | | | | | ||/ </Typography>
<pre> \| \_ | | | | | | _/ |/ </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| \_ | | | | | | _/ |/ </Typography>
<pre> \ \| / \ / \ |/ / </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \ \| / \ / \ |/ / </Typography>
<pre> <BitNodePortal n={1} level={nextSourceFileFlags[1]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={nextSourceFileFlags[2]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={nextSourceFileFlags[3]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={nextSourceFileFlags[4]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> <BitNodePortal n={1} level={nextSourceFileFlags[1]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> |/ <BitNodePortal n={2} level={nextSourceFileFlags[2]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | <BitNodePortal n={3} level={nextSourceFileFlags[3]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \| <BitNodePortal n={4} level={nextSourceFileFlags[4]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> </Typography>
<pre> | | | | | | | | </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | | | | | | | </Typography>
<pre> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </pre> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \JUMP3R|JUMP|3R| |R3|PMUJ|R3PMUJ/ </Typography>
<br /> <br />
<br /> <br />
<br /> <br />
@ -168,7 +202,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
"> ", "> ",
"> (Enter a new BitNode using the image above)", "> (Enter a new BitNode using the image above)",
]} /> ]} />
</div> </>
); );
return <></>; return <></>;

@ -2,18 +2,23 @@ import React from "react";
import { BitNodes } from "../BitNode"; import { BitNodes } from "../BitNode";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { removePopup } from "../../ui/React/createPopup"; import { use } from "../../ui/Context";
import { Modal } from "../../ui/React/Modal";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
interface IProps { interface IProps {
open: boolean;
onClose: () => void;
n: number; n: number;
level: number; level: number;
destroyedBitNode: number; destroyedBitNode: number;
flume: boolean; flume: boolean;
router: IRouter;
enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void; enter: (router: IRouter, flume: boolean, destroyedBitNode: number, newBitNode: number) => void;
popupId: string;
} }
export function PortalPopup(props: IProps): React.ReactElement { export function PortalModal(props: IProps): React.ReactElement {
const router = use.Router();
const bitNodeKey = "BitNode" + props.n; const bitNodeKey = "BitNode" + props.n;
const bitNode = BitNodes[bitNodeKey]; const bitNode = BitNodes[bitNodeKey];
if (bitNode == null) throw new Error(`Could not find BitNode object for number: ${props.n}`); if (bitNode == null) throw new Error(`Could not find BitNode object for number: ${props.n}`);
@ -21,29 +26,30 @@ export function PortalPopup(props: IProps): React.ReactElement {
const newLevel = Math.min(props.level + 1, props.n === 12 ? Infinity : 3); const newLevel = Math.min(props.level + 1, props.n === 12 ? Infinity : 3);
return ( return (
<> <Modal open={props.open} onClose={props.onClose}>
<h1> <Typography variant="h4">
BitNode-{props.n}: {bitNode.name} BitNode-{props.n}: {bitNode.name}
</h1> </Typography>
<br /> <br />
Source-File Level: {props.level} / {maxSourceFileLevel} <Typography>
Source-File Level: {props.level} / {maxSourceFileLevel}
</Typography>
<br /> <br />
<br /> <br />
Difficulty: {["easy", "normal", "hard"][bitNode.difficulty]} <Typography> Difficulty: {["easy", "normal", "hard"][bitNode.difficulty]}</Typography>
<br /> <br />
<br /> <br />
{bitNode.info} <Typography>{bitNode.info}</Typography>
<br /> <br />
<br /> <br />
<button <Button
className="std-button"
onClick={() => { onClick={() => {
props.enter(props.router, props.flume, props.destroyedBitNode, props.n); props.enter(router, props.flume, props.destroyedBitNode, props.n);
removePopup(props.popupId); props.onClose();
}} }}
> >
Enter BN{props.n}.{newLevel} Enter BN{props.n}.{newLevel}
</button> </Button>
</> </Modal>
); );
} }

@ -20,7 +20,6 @@ class StatsMultiplier {
export interface IActionParams { export interface IActionParams {
name?: string; name?: string;
desc?: string;
level?: number; level?: number;
maxLevel?: number; maxLevel?: number;
autoLevel?: boolean; autoLevel?: boolean;
@ -43,7 +42,6 @@ export interface IActionParams {
export class Action implements IAction { export class Action implements IAction {
name = ""; name = "";
desc = "";
// Difficulty scales with level. See getDifficulty() method // Difficulty scales with level. See getDifficulty() method
level = 1; level = 1;
@ -100,7 +98,6 @@ export class Action implements IAction {
constructor(params: IActionParams | null = null) { constructor(params: IActionParams | null = null) {
// | null = null // | null = null
if (params && params.name) this.name = params.name; if (params && params.name) this.name = params.name;
if (params && params.desc) this.desc = params.desc;
if (params && params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10); if (params && params.baseDifficulty) this.baseDifficulty = addOffset(params.baseDifficulty, 10);
if (params && params.difficultyFac) this.difficultyFac = params.difficultyFac; if (params && params.difficultyFac) this.difficultyFac = params.difficultyFac;

@ -1,763 +0,0 @@
import { BlackOperation } from "./BlackOperation";
import { IMap } from "../types";
export const BlackOperations: IMap<BlackOperation> = {};
(function () {
BlackOperations["Operation Typhoon"] = new BlackOperation({
name: "Operation Typhoon",
desc:
"Obadiah Zenyatta is the leader of a RedWater PMC. It has long " +
"been known among the intelligence community that Zenyatta, along " +
"with the rest of the PMC, is a Synthoid.<br><br>" +
"The goal of Operation Typhoon is to find and eliminate " +
"Zenyatta and RedWater by any means necessary. After the task " +
"is completed, the actions must be covered up from the general public.",
baseDifficulty: 2000,
reqdRank: 2.5e3,
rankGain: 50,
rankLoss: 10,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Zero"] = new BlackOperation({
name: "Operation Zero",
desc:
"AeroCorp is one of the world's largest defense contractors. " +
"Its leader, Steve Watataki, is thought to be a supporter of " +
"Synthoid rights. He must be removed.<br><br>" +
"The goal of Operation Zero is to covertly infiltrate AeroCorp and " +
"uncover any incriminating evidence or " +
"information against Watataki that will cause him to be removed " +
"from his position at AeroCorp. Incriminating evidence can be " +
"fabricated as a last resort. Be warned that AeroCorp has some of " +
"the most advanced security measures in the world.",
baseDifficulty: 2500,
reqdRank: 5e3,
rankGain: 60,
rankLoss: 15,
hpLoss: 50,
weights: {
hack: 0.2,
str: 0.15,
def: 0.15,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation X"] = new BlackOperation({
name: "Operation X",
desc:
"We have recently discovered an underground publication " +
"group called Samizdat. Even though most of their publications " +
"are nonsensical conspiracy theories, the average human is " +
"gullible enough to believe them. Many of their works discuss " +
"Synthoids and pose a threat to society. The publications are spreading " +
"rapidly in China and other Eastern countries.<br><br>" +
"Samizdat has done a good job of keeping hidden and anonymous. " +
"However, we've just received intelligence that their base of " +
"operations is in Ishima's underground sewer systems. Your task is to " +
"investigate the sewer systems, and eliminate Samizdat. They must " +
"never publish anything again.",
baseDifficulty: 3000,
reqdRank: 7.5e3,
rankGain: 75,
rankLoss: 15,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Titan"] = new BlackOperation({
name: "Operation Titan",
desc:
"Several months ago Titan Laboratories' Bioengineering department " +
"was infiltrated by Synthoids. As far as we know, Titan Laboratories' " +
"management has no knowledge about this. We don't know what the " +
"Synthoids are up to, but the research that they could " +
"be conducting using Titan Laboraties' vast resources is potentially " +
"very dangerous.<br><br>" +
"Your goal is to enter and destroy the Bioengineering department's " +
"facility in Aevum. The task is not just to retire the Synthoids there, but " +
"also to destroy any information or research at the facility that " +
"is relevant to the Synthoids and their goals.",
baseDifficulty: 4000,
reqdRank: 10e3,
rankGain: 100,
rankLoss: 20,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Ares"] = new BlackOperation({
name: "Operation Ares",
desc:
"One of our undercover agents, Agent Carter, has informed us of a " +
"massive weapons deal going down in Dubai between rogue Russian " +
"militants and a radical Synthoid community. These weapons are next-gen " +
"plasma and energy weapons. It is critical for the safety of humanity " +
"that this deal does not happen.<br><br>" +
"Your task is to intercept the deal. Leave no survivors.",
baseDifficulty: 5000,
reqdRank: 12.5e3,
rankGain: 125,
rankLoss: 20,
hpLoss: 200,
weights: {
hack: 0,
str: 0.25,
def: 0.25,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Archangel"] = new BlackOperation({
name: "Operation Archangel",
desc:
"Our analysts have discovered that the popular Red Rabbit brothel in " +
"Amsterdam is run and 'staffed' by MK-VI Synthoids. Intelligence " +
"suggests that the profit from this brothel is used to fund a large " +
"black market arms trafficking operation.<br><br>" +
"The goal of this operation is to take out the leaders that are running " +
"the Red Rabbit brothel. Try to limit the number of other casualties, " +
"but do what you must to complete the mission.",
baseDifficulty: 7500,
reqdRank: 15e3,
rankGain: 200,
rankLoss: 20,
hpLoss: 25,
weights: {
hack: 0,
str: 0.2,
def: 0.2,
dex: 0.3,
agi: 0.3,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Juggernaut"] = new BlackOperation({
name: "Operation Juggernaut",
desc:
"The CIA has just encountered a new security threat. A new " +
"criminal group, lead by a shadowy operative who calls himself " +
"Juggernaut, has been smuggling drugs and weapons (including " +
"suspected bioweapons) into Sector-12. We also have reason " +
"to believe the tried to break into one of Universal Energy's " +
"facilities in order to cause a city-wide blackout. The CIA " +
"suspects that Juggernaut is a heavily-augmented Synthoid, and " +
"have thus enlisted our help.<br><br>" +
"Your mission is to eradicate Juggernaut and his followers.",
baseDifficulty: 10e3,
reqdRank: 20e3,
rankGain: 300,
rankLoss: 40,
hpLoss: 300,
weights: {
hack: 0,
str: 0.25,
def: 0.25,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Red Dragon"] = new BlackOperation({
name: "Operation Red Dragon",
desc:
"The Tetrads criminal organization is suspected of " +
"reverse-engineering the MK-VI Synthoid design. We believe " +
"they altered and possibly improved the design and began " +
"manufacturing their own Synthoid models in order to bolster " +
"their criminal activities.<br><br>" +
"Your task is to infiltrate and destroy the Tetrads' base of operations " +
"in Los Angeles. Intelligence tells us that their base houses " +
"one of their Synthoid manufacturing units.",
baseDifficulty: 12.5e3,
reqdRank: 25e3,
rankGain: 500,
rankLoss: 50,
hpLoss: 500,
weights: {
hack: 0.05,
str: 0.2,
def: 0.2,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation K"] = new BlackOperation({
name: "Operation K",
desc:
"CODE RED SITUATION. Our intelligence tells us that VitaLife " +
"has discovered a new android cloning technology. This technology " +
"is supposedly capable of cloning Synthoid, not only physically " +
"but also their advanced AI modules. We do not believe that " +
"VitaLife is trying to use this technology illegally or " +
"maliciously, but if any Synthoids were able to infiltrate the " +
"corporation and take advantage of this technology then the " +
"results would be catastrophic.<br><br>" +
"We do not have the power or jurisdiction to shutdown this down " +
"through legal or political means, so we must resort to a covert " +
"operation. Your goal is to destroy this technology and eliminate " +
"anyone who was involved in its creation.",
baseDifficulty: 15e3,
reqdRank: 30e3,
rankGain: 750,
rankLoss: 60,
hpLoss: 1000,
weights: {
hack: 0.05,
str: 0.2,
def: 0.2,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Deckard"] = new BlackOperation({
name: "Operation Deckard",
desc:
"Despite your success in eliminating VitaLife's new android-replicating " +
"technology in Operation K, we've discovered that a small group of " +
"MK-VI Synthoids were able to make off with the schematics and design " +
"of the technology before the Operation. It is almost a certainty that " +
"these Synthoids are some of the rogue MK-VI ones from the Synthoid Uprising.<br><br>" +
"The goal of Operation Deckard is to hunt down these Synthoids and retire " +
"them. I don't need to tell you how critical this mission is.",
baseDifficulty: 20e3,
reqdRank: 40e3,
rankGain: 1e3,
rankLoss: 75,
hpLoss: 200,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Tyrell"] = new BlackOperation({
name: "Operation Tyrell",
desc:
"A week ago Blade Industries reported a small break-in at one " +
"of their Aevum Augmentation storage facitilities. We figured out " +
"that The Dark Army was behind the heist, and didn't think any more " +
"of it. However, we've just discovered that several known MK-VI Synthoids " +
"were part of that break-in group.<br><br>" +
"We cannot have Synthoids upgrading their already-enhanced abilities " +
"with Augmentations. Your task is to hunt down the associated Dark Army " +
"members and eliminate them.",
baseDifficulty: 25e3,
reqdRank: 50e3,
rankGain: 1.5e3,
rankLoss: 100,
hpLoss: 500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Wallace"] = new BlackOperation({
name: "Operation Wallace",
desc:
"Based on information gathered from Operation Tyrell, we've discovered " +
"that The Dark Army was well aware that there were Synthoids amongst " +
"their ranks. Even worse, we believe that The Dark Army is working " +
"together with other criminal organizations such as The Syndicate and " +
"that they are planning some sort of large-scale takeover of multiple major " +
"cities, most notably Aevum. We suspect that Synthoids have infiltrated " +
"the ranks of these criminal factions and are trying to stage another " +
"Synthoid uprising.<br><br>" +
"The best way to deal with this is to prevent it before it even happens. " +
"The goal of Operation Wallace is to destroy the Dark Army and " +
"Syndicate factions in Aevum immediately. Leave no survivors.",
baseDifficulty: 30e3,
reqdRank: 75e3,
rankGain: 2e3,
rankLoss: 150,
hpLoss: 1500,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Shoulder of Orion"] = new BlackOperation({
name: "Operation Shoulder of Orion",
desc:
"China's Solaris Space Systems is secretly launching the first " +
"manned spacecraft in over a decade using Synthoids. We believe " +
"China is trying to establish the first off-world colonies.<br><br>" +
"The mission is to prevent this launch without instigating an " +
"international conflict. When you accept this mission you will be " +
"officially disavowed by the NSA and the national government until after you " +
"successfully return. In the event of failure, all of the operation's " +
"team members must not let themselves be captured alive.",
baseDifficulty: 35e3,
reqdRank: 100e3,
rankGain: 2.5e3,
rankLoss: 500,
hpLoss: 1500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation Hyron"] = new BlackOperation({
name: "Operation Hyron",
desc:
"Our intelligence tells us that Fulcrum Technologies is developing " +
"a quantum supercomputer using human brains as core " +
"processors. This supercomputer " +
"is rumored to be able to store vast amounts of data and " +
"perform computations unmatched by any other supercomputer on the " +
"planet. But more importantly, the use of organic human brains " +
"means that the supercomputer may be able to reason abstractly " +
"and become self-aware.<br><br>" +
"I do not need to remind you why sentient-level AIs pose a serious " +
"threat to all of mankind.<br><br>" +
"The research for this project is being conducted at one of Fulcrum " +
"Technologies secret facilities in Aevum, codenamed 'Alpha Ranch'. " +
"Infiltrate the compound, delete and destroy the work, and then find and kill the " +
"project lead.",
baseDifficulty: 40e3,
reqdRank: 125e3,
rankGain: 3e3,
rankLoss: 1e3,
hpLoss: 500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Morpheus"] = new BlackOperation({
name: "Operation Morpheus",
desc:
"DreamSense Technologies is an advertising company that uses " +
"special technology to transmit their ads into the peoples " +
"dreams and subconcious. They do this using broadcast transmitter " +
"towers. Based on information from our agents and informants in " +
"Chonqging, we have reason to believe that one of the broadcast " +
"towers there has been compromised by Synthoids and is being used " +
"to spread pro-Synthoid propaganda.<br><br>" +
"The mission is to destroy this broadcast tower. Speed and " +
"stealth are of the upmost important for this.",
baseDifficulty: 45e3,
reqdRank: 150e3,
rankGain: 4e3,
rankLoss: 1e3,
hpLoss: 100,
weights: {
hack: 0.05,
str: 0.15,
def: 0.15,
dex: 0.3,
agi: 0.3,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation Ion Storm"] = new BlackOperation({
name: "Operation Ion Storm",
desc:
"Our analysts have uncovered a gathering of MK-VI Synthoids " +
"that have taken up residence in the Sector-12 Slums. We " +
"don't know if they are rogue Synthoids from the Uprising, " +
"but we do know that they have been stockpiling " +
"weapons, money, and other resources. This makes them dangerous.<br><br>" +
"This is a full-scale assault operation to find and retire all of these " +
"Synthoids in the Sector-12 Slums.",
baseDifficulty: 50e3,
reqdRank: 175e3,
rankGain: 5e3,
rankLoss: 1e3,
hpLoss: 5000,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Annihilus"] = new BlackOperation({
name: "Operation Annihilus",
desc:
"Our superiors have ordered us to eradicate everything and everyone " +
"in an underground facility located in Aevum. They tell us " +
"that the facility houses many dangerous Synthoids and " +
"belongs to a terrorist organization called " +
"'The Covenant'. We have no prior intelligence about this " +
"organization, so you are going in blind.",
baseDifficulty: 55e3,
reqdRank: 200e3,
rankGain: 7.5e3,
rankLoss: 1e3,
hpLoss: 10e3,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Ultron"] = new BlackOperation({
name: "Operation Ultron",
desc:
"OmniTek Incorporated, the original designer and manufacturer of Synthoids, " +
"has notified us of a malfunction in their AI design. This malfunction, " +
"when triggered, causes MK-VI Synthoids to become radicalized and seek out " +
"the destruction of humanity. They say that this bug affects all MK-VI Synthoids, " +
"not just the rogue ones from the Uprising.<br><br>" +
"OmniTek has also told us they they believe someone has triggered this " +
"malfunction in a large group of MK-VI Synthoids, and that these newly-radicalized Synthoids " +
"are now amassing in Volhaven to form a terrorist group called Ultron.<br><br>" +
"Intelligence suggests Ultron is heavily armed and that their members are " +
"augmented. We believe Ultron is making moves to take control of " +
"and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).<br><br>" +
"Your task is to find and destroy Ultron.",
baseDifficulty: 60e3,
reqdRank: 250e3,
rankGain: 10e3,
rankLoss: 2e3,
hpLoss: 10e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Centurion"] = new BlackOperation({
name: "Operation Centurion",
desc:
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)<br><br>" +
"Throughout all of humanity's history, we have relied on " +
"technology to survive, conquer, and progress. Its advancement became our primary goal. " +
"And at the peak of human civilization technology turned into " +
"power. Global, absolute power.<br><br>" +
"It seems that the universe is not without a sense of irony.<br><br>" +
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",
baseDifficulty: 70e3,
reqdRank: 300e3,
rankGain: 15e3,
rankLoss: 5e3,
hpLoss: 10e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
BlackOperations["Operation Vindictus"] = new BlackOperation({
name: "Operation Vindictus",
desc:
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)<br><br>" +
"The bits are all around us. The daemons that hold the Node " +
"together can manifest themselves in many different ways.<br><br>" +
"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)",
baseDifficulty: 75e3,
reqdRank: 350e3,
rankGain: 20e3,
rankLoss: 20e3,
hpLoss: 20e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
BlackOperations["Operation Daedalus"] = new BlackOperation({
name: "Operation Daedalus",
desc: "Yesterday we obeyed kings and bent our neck to emperors. " + "Today we kneel only to truth.",
baseDifficulty: 80e3,
reqdRank: 400e3,
rankGain: 40e3,
rankLoss: 10e3,
hpLoss: 100e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
})();

@ -0,0 +1,571 @@
import { BlackOperation } from "./BlackOperation";
import { IMap } from "../types";
export const BlackOperations: IMap<BlackOperation> = {};
(function () {
BlackOperations["Operation Typhoon"] = new BlackOperation({
name: "Operation Typhoon",
baseDifficulty: 2000,
reqdRank: 2.5e3,
rankGain: 50,
rankLoss: 10,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Zero"] = new BlackOperation({
name: "Operation Zero",
baseDifficulty: 2500,
reqdRank: 5e3,
rankGain: 60,
rankLoss: 15,
hpLoss: 50,
weights: {
hack: 0.2,
str: 0.15,
def: 0.15,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation X"] = new BlackOperation({
name: "Operation X",
baseDifficulty: 3000,
reqdRank: 7.5e3,
rankGain: 75,
rankLoss: 15,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Titan"] = new BlackOperation({
name: "Operation Titan",
baseDifficulty: 4000,
reqdRank: 10e3,
rankGain: 100,
rankLoss: 20,
hpLoss: 100,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Ares"] = new BlackOperation({
name: "Operation Ares",
baseDifficulty: 5000,
reqdRank: 12.5e3,
rankGain: 125,
rankLoss: 20,
hpLoss: 200,
weights: {
hack: 0,
str: 0.25,
def: 0.25,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Archangel"] = new BlackOperation({
name: "Operation Archangel",
baseDifficulty: 7500,
reqdRank: 15e3,
rankGain: 200,
rankLoss: 20,
hpLoss: 25,
weights: {
hack: 0,
str: 0.2,
def: 0.2,
dex: 0.3,
agi: 0.3,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Juggernaut"] = new BlackOperation({
name: "Operation Juggernaut",
baseDifficulty: 10e3,
reqdRank: 20e3,
rankGain: 300,
rankLoss: 40,
hpLoss: 300,
weights: {
hack: 0,
str: 0.25,
def: 0.25,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Red Dragon"] = new BlackOperation({
name: "Operation Red Dragon",
baseDifficulty: 12.5e3,
reqdRank: 25e3,
rankGain: 500,
rankLoss: 50,
hpLoss: 500,
weights: {
hack: 0.05,
str: 0.2,
def: 0.2,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation K"] = new BlackOperation({
name: "Operation K",
baseDifficulty: 15e3,
reqdRank: 30e3,
rankGain: 750,
rankLoss: 60,
hpLoss: 1000,
weights: {
hack: 0.05,
str: 0.2,
def: 0.2,
dex: 0.25,
agi: 0.25,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Deckard"] = new BlackOperation({
name: "Operation Deckard",
baseDifficulty: 20e3,
reqdRank: 40e3,
rankGain: 1e3,
rankLoss: 75,
hpLoss: 200,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Tyrell"] = new BlackOperation({
name: "Operation Tyrell",
baseDifficulty: 25e3,
reqdRank: 50e3,
rankGain: 1.5e3,
rankLoss: 100,
hpLoss: 500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Wallace"] = new BlackOperation({
name: "Operation Wallace",
baseDifficulty: 30e3,
reqdRank: 75e3,
rankGain: 2e3,
rankLoss: 150,
hpLoss: 1500,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Shoulder of Orion"] = new BlackOperation({
name: "Operation Shoulder of Orion",
baseDifficulty: 35e3,
reqdRank: 100e3,
rankGain: 2.5e3,
rankLoss: 500,
hpLoss: 1500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation Hyron"] = new BlackOperation({
name: "Operation Hyron",
baseDifficulty: 40e3,
reqdRank: 125e3,
rankGain: 3e3,
rankLoss: 1e3,
hpLoss: 500,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Morpheus"] = new BlackOperation({
name: "Operation Morpheus",
baseDifficulty: 45e3,
reqdRank: 150e3,
rankGain: 4e3,
rankLoss: 1e3,
hpLoss: 100,
weights: {
hack: 0.05,
str: 0.15,
def: 0.15,
dex: 0.3,
agi: 0.3,
cha: 0,
int: 0.05,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isStealth: true,
});
BlackOperations["Operation Ion Storm"] = new BlackOperation({
name: "Operation Ion Storm",
baseDifficulty: 50e3,
reqdRank: 175e3,
rankGain: 5e3,
rankLoss: 1e3,
hpLoss: 5000,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Annihilus"] = new BlackOperation({
name: "Operation Annihilus",
baseDifficulty: 55e3,
reqdRank: 200e3,
rankGain: 7.5e3,
rankLoss: 1e3,
hpLoss: 10e3,
weights: {
hack: 0,
str: 0.24,
def: 0.24,
dex: 0.24,
agi: 0.24,
cha: 0,
int: 0.04,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Ultron"] = new BlackOperation({
name: "Operation Ultron",
baseDifficulty: 60e3,
reqdRank: 250e3,
rankGain: 10e3,
rankLoss: 2e3,
hpLoss: 10e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
isKill: true,
});
BlackOperations["Operation Centurion"] = new BlackOperation({
name: "Operation Centurion",
baseDifficulty: 70e3,
reqdRank: 300e3,
rankGain: 15e3,
rankLoss: 5e3,
hpLoss: 10e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
BlackOperations["Operation Vindictus"] = new BlackOperation({
name: "Operation Vindictus",
baseDifficulty: 75e3,
reqdRank: 350e3,
rankGain: 20e3,
rankLoss: 20e3,
hpLoss: 20e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
BlackOperations["Operation Daedalus"] = new BlackOperation({
name: "Operation Daedalus",
baseDifficulty: 80e3,
reqdRank: 400e3,
rankGain: 40e3,
rankLoss: 10e3,
hpLoss: 100e3,
weights: {
hack: 0.1,
str: 0.2,
def: 0.2,
dex: 0.2,
agi: 0.2,
cha: 0,
int: 0.1,
},
decays: {
hack: 0.6,
str: 0.8,
def: 0.8,
dex: 0.8,
agi: 0.8,
cha: 0,
int: 0.75,
},
});
})();

@ -1586,11 +1586,6 @@ export class Bladeburner implements IBladeburner {
create(): void { create(): void {
this.contracts["Tracking"] = new Contract({ this.contracts["Tracking"] = new Contract({
name: "Tracking", name: "Tracking",
desc:
"Identify and locate Synthoids. This contract involves reconnaissance " +
"and information-gathering ONLY. Do NOT engage. Stealth is of the utmost importance.<br><br>" +
"Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for " +
"whatever city you are currently in.",
baseDifficulty: 125, baseDifficulty: 125,
difficultyFac: 1.02, difficultyFac: 1.02,
rewardFac: 1.041, rewardFac: 1.041,
@ -1619,10 +1614,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.contracts["Bounty Hunter"] = new Contract({ this.contracts["Bounty Hunter"] = new Contract({
name: "Bounty Hunter", name: "Bounty Hunter",
desc:
"Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.<br><br>" +
"Successfully completing a Bounty Hunter contract will lower the population in your " +
"current city, and will also increase its chaos level.",
baseDifficulty: 250, baseDifficulty: 250,
difficultyFac: 1.04, difficultyFac: 1.04,
rewardFac: 1.085, rewardFac: 1.085,
@ -1651,10 +1642,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.contracts["Retirement"] = new Contract({ this.contracts["Retirement"] = new Contract({
name: "Retirement", name: "Retirement",
desc:
"Hunt down and retire (kill) rogue Synthoids.<br><br>" +
"Successfully completing a Retirement contract will lower the population in your current " +
"city, and will also increase its chaos level.",
baseDifficulty: 200, baseDifficulty: 200,
difficultyFac: 1.03, difficultyFac: 1.03,
rewardFac: 1.065, rewardFac: 1.065,
@ -1684,12 +1671,6 @@ export class Bladeburner implements IBladeburner {
this.operations["Investigation"] = new Operation({ this.operations["Investigation"] = new Operation({
name: "Investigation", name: "Investigation",
desc:
"As a field agent, investigate and identify Synthoid " +
"populations, movements, and operations.<br><br>Successful " +
"Investigation ops will increase the accuracy of your " +
"synthoid data.<br><br>" +
"You will NOT lose HP from failed Investigation ops.",
baseDifficulty: 400, baseDifficulty: 400,
difficultyFac: 1.03, difficultyFac: 1.03,
rewardFac: 1.07, rewardFac: 1.07,
@ -1719,11 +1700,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.operations["Undercover Operation"] = new Operation({ this.operations["Undercover Operation"] = new Operation({
name: "Undercover Operation", name: "Undercover Operation",
desc:
"Conduct undercover operations to identify hidden " +
"and underground Synthoid communities and organizations.<br><br>" +
"Successful Undercover ops will increase the accuracy of your synthoid " +
"data.",
baseDifficulty: 500, baseDifficulty: 500,
difficultyFac: 1.04, difficultyFac: 1.04,
rewardFac: 1.09, rewardFac: 1.09,
@ -1754,7 +1730,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.operations["Sting Operation"] = new Operation({ this.operations["Sting Operation"] = new Operation({
name: "Sting Operation", name: "Sting Operation",
desc: "Conduct a sting operation to bait and capture particularly " + "notorious Synthoid criminals.",
baseDifficulty: 650, baseDifficulty: 650,
difficultyFac: 1.04, difficultyFac: 1.04,
rewardFac: 1.095, rewardFac: 1.095,
@ -1785,10 +1760,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.operations["Raid"] = new Operation({ this.operations["Raid"] = new Operation({
name: "Raid", name: "Raid",
desc:
"Lead an assault on a known Synthoid community. Note that " +
"there must be an existing Synthoid community in your current city " +
"in order for this Operation to be successful.",
baseDifficulty: 800, baseDifficulty: 800,
difficultyFac: 1.045, difficultyFac: 1.045,
rewardFac: 1.1, rewardFac: 1.1,
@ -1819,10 +1790,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.operations["Stealth Retirement Operation"] = new Operation({ this.operations["Stealth Retirement Operation"] = new Operation({
name: "Stealth Retirement Operation", name: "Stealth Retirement Operation",
desc:
"Lead a covert operation to retire Synthoids. The " +
"objective is to complete the task without " +
"drawing any attention. Stealth and discretion are key.",
baseDifficulty: 1000, baseDifficulty: 1000,
difficultyFac: 1.05, difficultyFac: 1.05,
rewardFac: 1.11, rewardFac: 1.11,
@ -1854,10 +1821,6 @@ export class Bladeburner implements IBladeburner {
}); });
this.operations["Assassination"] = new Operation({ this.operations["Assassination"] = new Operation({
name: "Assassination", name: "Assassination",
desc:
"Assassinate Synthoids that have been identified as " +
"important, high-profile social and political leaders " +
"in the Synthoid communities.",
baseDifficulty: 1500, baseDifficulty: 1500,
difficultyFac: 1.06, difficultyFac: 1.06,
rewardFac: 1.14, rewardFac: 1.14,
@ -1900,7 +1863,7 @@ export class Bladeburner implements IBladeburner {
if (this.action.type !== ActionTypes["Idle"]) { if (this.action.type !== ActionTypes["Idle"]) {
let msg = "Your Bladeburner action was cancelled because you started doing something else."; let msg = "Your Bladeburner action was cancelled because you started doing something else.";
if (this.automateEnabled) { if (this.automateEnabled) {
msg += `<br><br>Your automation was disabled as well. You will have to re-enable it through the Bladeburner console`; msg += `<br /><br />Your automation was disabled as well. You will have to re-enable it through the Bladeburner console`;
this.automateEnabled = false; this.automateEnabled = false;
} }
if (!Settings.SuppressBladeburnerPopup) { if (!Settings.SuppressBladeburnerPopup) {

@ -1,54 +0,0 @@
import { Action } from "./Action";
import { IMap } from "../types";
export const GeneralActions: IMap<Action> = {};
(function () {
// General Actions
let actionName;
actionName = "Training";
GeneralActions[actionName] = new Action({
name: actionName,
desc:
"Improve your abilities at the Bladeburner unit's specialized training " +
"center. Doing this gives experience for all combat stats and also " +
"increases your max stamina.",
});
actionName = "Field Analysis";
GeneralActions[actionName] = new Action({
name: actionName,
desc:
"Mine and analyze Synthoid-related data. This improves the " +
"Bladeburner's unit intelligence on Synthoid locations and " +
"activities. Completing this action will improve the accuracy " +
"of your Synthoid population estimated in the current city.<br><br>" +
"Does NOT require stamina.",
});
actionName = "Recruitment";
GeneralActions[actionName] = new Action({
name: actionName,
desc:
"Attempt to recruit members for your Bladeburner team. These members " +
"can help you conduct operations.<br><br>" +
"Does NOT require stamina.",
});
actionName = "Diplomacy";
GeneralActions[actionName] = new Action({
name: actionName,
desc:
"Improve diplomatic relations with the Synthoid population. " +
"Completing this action will reduce the Chaos level in your current city.<br><br>" +
"Does NOT require stamina.",
});
actionName = "Hyperbolic Regeneration Chamber";
GeneralActions[actionName] = new Action({
name: actionName,
desc:
"Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. " +
"This will slowly heal your wounds and slightly increase your stamina.<br><br>",
});
})();

@ -0,0 +1,33 @@
import { Action } from "./Action";
import { IMap } from "../types";
export const GeneralActions: IMap<Action> = {};
(function () {
// General Actions
let actionName;
actionName = "Training";
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Field Analysis";
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Recruitment";
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Diplomacy";
GeneralActions[actionName] = new Action({
name: actionName,
});
actionName = "Hyperbolic Regeneration Chamber";
GeneralActions[actionName] = new Action({
name: actionName,
});
})();

@ -18,7 +18,6 @@ export interface ISuccessChanceParams {
export interface IAction { export interface IAction {
name: string; name: string;
desc: string;
// Difficulty scales with level. See getDifficulty() method // Difficulty scales with level. See getDifficulty() method
level: number; level: number;

@ -0,0 +1,299 @@
import React from "react";
interface IBlackOp {
desc: JSX.Element;
}
export const BlackOperations: {
[key: string]: IBlackOp | undefined;
} = {
"Operation Typhoon": {
desc: (
<>
Obadiah Zenyatta is the leader of a RedWater PMC. It has long been known among the intelligence community that
Zenyatta, along with the rest of the PMC, is a Synthoid.
<br />
<br />
The goal of Operation Typhoon is to find and eliminate Zenyatta and RedWater by any means necessary. After the
task is completed, the actions must be covered up from the general public.
</>
),
},
"Operation Zero": {
desc: (
<>
AeroCorp is one of the world's largest defense contractors. Its leader, Steve Watataki, is thought to be a
supporter of Synthoid rights. He must be removed.
<br />
<br />
The goal of Operation Zero is to covertly infiltrate AeroCorp and uncover any incriminating evidence or
information against Watataki that will cause him to be removed from his position at AeroCorp. Incriminating
evidence can be fabricated as a last resort. Be warned that AeroCorp has some of the most advanced security
measures in the world.
</>
),
},
"Operation X": {
desc: (
<>
We have recently discovered an underground publication group called Samizdat. Even though most of their
publications are nonsensical conspiracy theories, the average human is gullible enough to believe them. Many of
their works discuss Synthoids and pose a threat to society. The publications are spreading rapidly in China and
other Eastern countries.
<br />
<br />
Samizdat has done a good job of keeping hidden and anonymous. However, we've just received intelligence that
their base of operations is in Ishima's underground sewer systems. Your task is to investigate the sewer
systems, and eliminate Samizdat. They must never publish anything again.
</>
),
},
"Operation Titan": {
desc: (
<>
Several months ago Titan Laboratories' Bioengineering department was infiltrated by Synthoids. As far as we
know, Titan Laboratories' management has no knowledge about this. We don't know what the Synthoids are up to,
but the research that they could be conducting using Titan Laboraties' vast resources is potentially very
dangerous.
<br />
<br />
Your goal is to enter and destroy the Bioengineering department's facility in Aevum. The task is not just to
retire the Synthoids there, but also to destroy any information or research at the facility that is relevant to
the Synthoids and their goals.
</>
),
},
"Operation Ares": {
desc: (
<>
One of our undercover agents, Agent Carter, has informed us of a massive weapons deal going down in Dubai
between rogue Russian militants and a radical Synthoid community. These weapons are next-gen plasma and energy
weapons. It is critical for the safety of humanity that this deal does not happen.
<br />
<br />
Your task is to intercept the deal. Leave no survivors.
</>
),
},
"Operation Archangel": {
desc: (
<>
Our analysts have discovered that the popular Red Rabbit brothel in Amsterdam is run and 'staffed' by MK-VI
Synthoids. Intelligence suggests that the profit from this brothel is used to fund a large black market arms
trafficking operation.
<br />
<br />
The goal of this operation is to take out the leaders that are running the Red Rabbit brothel. Try to limit the
number of other casualties, but do what you must to complete the mission.
</>
),
},
"Operation Juggernaut": {
desc: (
<>
The CIA has just encountered a new security threat. A new criminal group, lead by a shadowy operative who calls
himself Juggernaut, has been smuggling drugs and weapons (including suspected bioweapons) into Sector-12. We
also have reason to believe the tried to break into one of Universal Energy's facilities in order to cause a
city-wide blackout. The CIA suspects that Juggernaut is a heavily-augmented Synthoid, and have thus enlisted our
help.
<br />
<br />
Your mission is to eradicate Juggernaut and his followers.
</>
),
},
"Operation Red Dragon": {
desc: (
<>
The Tetrads criminal organization is suspected of reverse-engineering the MK-VI Synthoid design. We believe they
altered and possibly improved the design and began manufacturing their own Synthoid models in order to bolster
their criminal activities.
<br />
<br />
Your task is to infiltrate and destroy the Tetrads' base of operations in Los Angeles. Intelligence tells us
that their base houses one of their Synthoid manufacturing units.
</>
),
},
"Operation K": {
desc: (
<>
CODE RED SITUATION. Our intelligence tells us that VitaLife has discovered a new android cloning technology.
This technology is supposedly capable of cloning Synthoid, not only physically but also their advanced AI
modules. We do not believe that VitaLife is trying to use this technology illegally or maliciously, but if any
Synthoids were able to infiltrate the corporation and take advantage of this technology then the results would
be catastrophic.
<br />
<br />
We do not have the power or jurisdiction to shutdown this down through legal or political means, so we must
resort to a covert operation. Your goal is to destroy this technology and eliminate anyone who was involved in
its creation.
</>
),
},
"Operation Deckard": {
desc: (
<>
Despite your success in eliminating VitaLife's new android-replicating technology in Operation K, we've
discovered that a small group of MK-VI Synthoids were able to make off with the schematics and design of the
technology before the Operation. It is almost a certainty that these Synthoids are some of the rogue MK-VI ones
from the Synthoid Uprising.
<br />
<br />
The goal of Operation Deckard is to hunt down these Synthoids and retire them. I don't need to tell you how
critical this mission is.
</>
),
},
"Operation Tyrell": {
desc: (
<>
A week ago Blade Industries reported a small break-in at one of their Aevum Augmentation storage facitilities.
We figured out that The Dark Army was behind the heist, and didn't think any more of it. However, we've just
discovered that several known MK-VI Synthoids were part of that break-in group.
<br />
<br />
We cannot have Synthoids upgrading their already-enhanced abilities with Augmentations. Your task is to hunt
down the associated Dark Army members and eliminate them.
</>
),
},
"Operation Wallace": {
desc: (
<>
Based on information gathered from Operation Tyrell, we've discovered that The Dark Army was well aware that
there were Synthoids amongst their ranks. Even worse, we believe that The Dark Army is working together with
other criminal organizations such as The Syndicate and that they are planning some sort of large-scale takeover
of multiple major cities, most notably Aevum. We suspect that Synthoids have infiltrated the ranks of these
criminal factions and are trying to stage another Synthoid uprising.
<br />
<br />
The best way to deal with this is to prevent it before it even happens. The goal of Operation Wallace is to
destroy the Dark Army and Syndicate factions in Aevum immediately. Leave no survivors.
</>
),
},
"Operation Shoulder of Orion": {
desc: (
<>
China's Solaris Space Systems is secretly launching the first manned spacecraft in over a decade using
Synthoids. We believe China is trying to establish the first off-world colonies.
<br />
<br />
The mission is to prevent this launch without instigating an international conflict. When you accept this
mission you will be officially disavowed by the NSA and the national government until after you successfully
return. In the event of failure, all of the operation's team members must not let themselves be captured alive.
</>
),
},
"Operation Hyron": {
desc: (
<>
Our intelligence tells us that Fulcrum Technologies is developing a quantum supercomputer using human brains as
core processors. This supercomputer is rumored to be able to store vast amounts of data and perform computations
unmatched by any other supercomputer on the planet. But more importantly, the use of organic human brains means
that the supercomputer may be able to reason abstractly and become self-aware.
<br />
<br />
I do not need to remind you why sentient-level AIs pose a serious threat to all of mankind.
<br />
<br />
The research for this project is being conducted at one of Fulcrum Technologies secret facilities in Aevum,
codenamed 'Alpha Ranch'. Infiltrate the compound, delete and destroy the work, and then find and kill the
project lead.
</>
),
},
"Operation Morpheus": {
desc: (
<>
DreamSense Technologies is an advertising company that uses special technology to transmit their ads into the
peoples dreams and subconcious. They do this using broadcast transmitter towers. Based on information from our
agents and informants in Chonqging, we have reason to believe that one of the broadcast towers there has been
compromised by Synthoids and is being used to spread pro-Synthoid propaganda.
<br />
<br />
The mission is to destroy this broadcast tower. Speed and stealth are of the upmost important for this.
</>
),
},
"Operation Ion Storm": {
desc: (
<>
Our analysts have uncovered a gathering of MK-VI Synthoids that have taken up residence in the Sector-12 Slums.
We don't know if they are rogue Synthoids from the Uprising, but we do know that they have been stockpiling
weapons, money, and other resources. This makes them dangerous.
<br />
<br />
This is a full-scale assault operation to find and retire all of these Synthoids in the Sector-12 Slums.
</>
),
},
"Operation Annihilus": {
desc: (
<>
Our superiors have ordered us to eradicate everything and everyone in an underground facility located in Aevum.
They tell us that the facility houses many dangerous Synthoids and belongs to a terrorist organization called
'The Covenant'. We have no prior intelligence about this organization, so you are going in blind.
</>
),
},
"Operation Ultron": {
desc: (
<>
OmniTek Incorporated, the original designer and manufacturer of Synthoids, has notified us of a malfunction in
their AI design. This malfunction, when triggered, causes MK-VI Synthoids to become radicalized and seek out the
destruction of humanity. They say that this bug affects all MK-VI Synthoids, not just the rogue ones from the
Uprising.
<br />
<br />
OmniTek has also told us they they believe someone has triggered this malfunction in a large group of MK-VI
Synthoids, and that these newly-radicalized Synthoids are now amassing in Volhaven to form a terrorist group
called Ultron.
<br />
<br />
Intelligence suggests Ultron is heavily armed and that their members are augmented. We believe Ultron is making
moves to take control of and weaponize DeltaOne's Tactical High-Energy Satellite Laser Array (THESLA).
<br />
<br />
Your task is to find and destroy Ultron.
</>
),
},
"Operation Centurion": {
desc: (
<>
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
<br />
<br />
Throughout all of humanity's history, we have relied on technology to survive, conquer, and progress. Its
advancement became our primary goal. And at the peak of human civilization technology turned into power. Global,
absolute power.
<br />
<br />
It seems that the universe is not without a sense of irony.
<br />
<br />
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
</>
),
},
"Operation Vindictus": {
desc: (
<>
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
<br />
<br />
The bits are all around us. The daemons that hold the Node together can manifest themselves in many different
ways.
<br />
<br />
{"D)@#)($M)C0293c40($*)@#D0JUMP3Rm0C<*@#)*$)#02c94830c(#$*D)"}
</>
),
},
"Operation Daedalus": {
desc: <> Yesterday we obeyed kings and bent our neck to emperors. Today we kneel only to truth.</>,
},
};

@ -0,0 +1,44 @@
import React from "react";
interface IContract {
desc: JSX.Element;
}
export const Contracts: {
[key: string]: IContract | undefined;
} = {
Tracking: {
desc: (
<>
Identify and locate Synthoids. This contract involves reconnaissance and information-gathering ONLY. Do NOT
engage. Stealth is of the utmost importance.
<br />
<br />
Successfully completing Tracking contracts will slightly improve your Synthoid population estimate for whatever
city you are currently in.
</>
),
},
"Bounty Hunter": {
desc: (
<>
Hunt down and capture fugitive Synthoids. These Synthoids are wanted alive.
<br />
<br />
Successfully completing a Bounty Hunter contract will lower the population in your current city, and will also
increase its chaos level.
</>
),
},
Retirement: {
desc: (
<>
Hunt down and retire (kill) rogue Synthoids.
<br />
<br />
Successfully completing a Retirement contract will lower the population in your current city, and will also
increase its chaos level.
</>
),
},
};

@ -0,0 +1,65 @@
import React from "react";
interface IContract {
desc: JSX.Element;
}
export const GeneralActions: {
[key: string]: IContract | undefined;
} = {
Training: {
desc: (
<>
Improve your abilities at the Bladeburner unit's specialized training center. Doing this gives experience for
all combat stats and also increases your max stamina.
</>
),
},
"Field Analysis": {
desc: (
<>
Mine and analyze Synthoid-related data. This improves the Bladeburner's unit intelligence on Synthoid locations
and activities. Completing this action will improve the accuracy of your Synthoid population estimated in the
current city.
<br />
<br />
Does NOT require stamina.
</>
),
},
Recruitment: {
desc: (
<>
Attempt to recruit members for your Bladeburner team. These members can help you conduct operations.
<br />
<br />
Does NOT require stamina.
</>
),
},
Diplomacy: {
desc: (
<>
Improve diplomatic relations with the Synthoid population. Completing this action will reduce the Chaos level in
your current city.
<br />
<br />
Does NOT require stamina.
</>
),
},
"Hyperbolic Regeneration Chamber": {
desc: (
<>
Enter cryogenic stasis using the Bladeburner division's hi-tech Regeneration Chamber. This will slowly heal your
wounds and slightly increase your stamina.
<br />
<br />
</>
),
},
};

@ -0,0 +1,60 @@
import React from "react";
interface IOperation {
desc: JSX.Element;
}
export const Operations: {
[key: string]: IOperation | undefined;
} = {
Investigation: {
desc: (
<>
As a field agent, investigate and identify Synthoid populations, movements, and operations.
<br />
<br />
Successful Investigation ops will increase the accuracy of your synthoid data.
<br />
<br />
You will NOT lose HP from failed Investigation ops.
</>
),
},
"Undercover Operation": {
desc: (
<>
Conduct undercover operations to identify hidden and underground Synthoid communities and organizations.
<br />
<br />
Successful Undercover ops will increase the accuracy of your synthoid data.
</>
),
},
"Sting Operation": {
desc: <>Conduct a sting operation to bait and capture particularly notorious Synthoid criminals.</>,
},
Raid: {
desc: (
<>
Lead an assault on a known Synthoid community. Note that there must be an existing Synthoid community in your
current city in order for this Operation to be successful.
</>
),
},
"Stealth Retirement Operation": {
desc: (
<>
Lead a covert operation to retire Synthoids. The objective is to complete the task without drawing any
attention. Stealth and discretion are key.
</>
),
},
Assassination: {
desc: (
<>
Assassinate Synthoids that have been identified as important, high-profile social and political leaders in the
Synthoid communities.
</>
),
},
};

@ -0,0 +1,69 @@
import React from "react";
import { IAction } from "../IAction";
import { IBladeburner } from "../IBladeburner";
import { BladeburnerConstants } from "../data/Constants";
import { use } from "../../ui/Context";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import IconButton from "@mui/material/IconButton";
import ArrowDropUpIcon from "@mui/icons-material/ArrowDropUp";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
interface IProps {
action: IAction;
isActive: boolean;
bladeburner: IBladeburner;
rerender: () => void;
}
export function ActionLevel({ action, isActive, bladeburner, rerender }: IProps): React.ReactElement {
const player = use.Player();
const canIncrease = action.level < action.maxLevel;
const canDecrease = action.level > 1;
function increaseLevel(): void {
if (!canIncrease) return;
++action.level;
if (isActive) bladeburner.startAction(player, bladeburner.action);
rerender();
}
function decreaseLevel(): void {
if (!canDecrease) return;
--action.level;
if (isActive) bladeburner.startAction(player, bladeburner.action);
rerender();
}
return (
<Box display="flex" flexDirection="row" alignItems="center">
<Box display="flex">
<Tooltip
title={
<Typography>
{action.getSuccessesNeededForNextLevel(BladeburnerConstants.ContractSuccessesPerLevel)} successes needed
for next level
</Typography>
}
>
<Typography>
Level: {action.level} / {action.maxLevel}
</Typography>
</Tooltip>
</Box>
<Tooltip title={isActive ? <Typography>WARNING: changing the level will restart the Operation</Typography> : ""}>
<IconButton disabled={!canIncrease} onClick={increaseLevel}>
<ArrowDropUpIcon />
</IconButton>
</Tooltip>
<Tooltip title={isActive ? <Typography>WARNING: changing the level will restart the Operation</Typography> : ""}>
<IconButton disabled={!canDecrease} onClick={decreaseLevel}>
<ArrowDropDownIcon />
</IconButton>
</Tooltip>
</Box>
);
}

@ -1,54 +1,44 @@
import React, { useState, useEffect } from "react"; import React from "react";
import { GeneralActionPage } from "./GeneralActionPage"; import { GeneralActionPage } from "./GeneralActionPage";
import { ContractPage } from "./ContractPage"; import { ContractPage } from "./ContractPage";
import { OperationPage } from "./OperationPage"; import { OperationPage } from "./OperationPage";
import { BlackOpPage } from "./BlackOpPage"; import { BlackOpPage } from "./BlackOpPage";
import { SkillPage } from "./SkillPage"; import { SkillPage } from "./SkillPage";
import { stealthIcon, killIcon } from "../data/Icons";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import Box from "@mui/material/Box";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
player: IPlayer; player: IPlayer;
} }
export function AllPages(props: IProps): React.ReactElement { export function AllPages(props: IProps): React.ReactElement {
const [page, setPage] = useState("General"); const [value, setValue] = React.useState(0);
const setRerender = useState(false)[1];
useEffect(() => { function handleChange(event: React.SyntheticEvent, tab: number): void {
const id = setInterval(() => setRerender((old) => !old), 1000); setValue(tab);
return () => clearInterval(id);
}, []);
function Header(props: { name: string }): React.ReactElement {
return (
<a
onClick={() => setPage(props.name)}
className={page !== props.name ? "bladeburner-nav-button noselect" : "bladeburner-nav-button-inactive noselect"}
>
{props.name}
</a>
);
} }
return ( return (
<> <>
<Header name={"General"} /> <Tabs variant="fullWidth" value={value} onChange={handleChange}>
<Header name={"Contracts"} /> <Tab label="General" />
<Header name={"Operations"} /> <Tab label="Contracts" />
<Header name={"BlackOps"} /> <Tab label="Operations" />
<Header name={"Skills"} /> <Tab label="BlackOps" />
<div style={{ display: "block", margin: "4px", padding: "4px" }}> <Tab label="Skills" />
{page === "General" && <GeneralActionPage bladeburner={props.bladeburner} player={props.player} />} </Tabs>
{page === "Contracts" && <ContractPage bladeburner={props.bladeburner} player={props.player} />} <Box sx={{ p: 1 }}>
{page === "Operations" && <OperationPage bladeburner={props.bladeburner} player={props.player} />} {value === 0 && <GeneralActionPage bladeburner={props.bladeburner} player={props.player} />}
{page === "BlackOps" && <BlackOpPage bladeburner={props.bladeburner} player={props.player} />} {value === 1 && <ContractPage bladeburner={props.bladeburner} player={props.player} />}
{page === "Skills" && <SkillPage bladeburner={props.bladeburner} />} {value === 2 && <OperationPage bladeburner={props.bladeburner} player={props.player} />}
</div> {value === 3 && <BlackOpPage bladeburner={props.bladeburner} player={props.player} />}
<span className="text"> {value === 4 && <SkillPage bladeburner={props.bladeburner} />}
{stealthIcon} = This action requires stealth, {killIcon} = This action involves retirement </Box>
</span>
</> </>
); );
} }

@ -0,0 +1,26 @@
import React from "react";
import { IAction } from "../IAction";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import Switch from "@mui/material/Switch";
interface IProps {
action: IAction;
rerender: () => void;
}
export function Autolevel(props: IProps): React.ReactElement {
function onAutolevel(event: React.ChangeEvent<HTMLInputElement>): void {
props.action.autoLevel = event.target.checked;
props.rerender();
}
return (
<Box display="flex" flexDirection="row" alignItems="center">
<Tooltip title={<Typography>Automatically increase operation level when possible</Typography>}>
<Typography> Autolevel:</Typography>
</Tooltip>
<Switch checked={props.action.autoLevel} onChange={onAutolevel} />
</Box>
);
}

@ -2,22 +2,29 @@ import React, { useState } from "react";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { ActionTypes } from "../data/ActionTypes"; import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { stealthIcon, killIcon } from "../data/Icons"; import { TeamSizeButton } from "./TeamSizeButton";
import { createPopup } from "../../ui/React/createPopup";
import { TeamSizePopup } from "./TeamSizePopup";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { BlackOperation } from "../BlackOperation";
import { BlackOperations } from "../data/BlackOperations";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { SuccessChance } from "./SuccessChance";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { SuccessChance } from "./SuccessChance";
import { StartButton } from "./StartButton";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
player: IPlayer; player: IPlayer;
action: any; action: BlackOperation;
} }
export function BlackOpElem(props: IProps): React.ReactElement { export function BlackOpElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
const isCompleted = props.bladeburner.blackops[props.action.name] != null; const isCompleted = props.bladeburner.blackops[props.action.name] != null;
if (isCompleted) { if (isCompleted) {
return <h2 style={{ display: "block" }}>{props.action.name} (COMPLETED)</h2>; return <h2 style={{ display: "block" }}>{props.action.name} (COMPLETED)</h2>;
@ -26,7 +33,6 @@ export function BlackOpElem(props: IProps): React.ReactElement {
const isActive = const isActive =
props.bladeburner.action.type === ActionTypes["BlackOperation"] && props.bladeburner.action.type === ActionTypes["BlackOperation"] &&
props.action.name === props.bladeburner.action.name; props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
const actionTime = props.action.getActionTime(props.bladeburner); const actionTime = props.action.getActionTime(props.bladeburner);
const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank; const hasReqdRank = props.bladeburner.rank >= props.action.reqdRank;
const computedActionTimeCurrent = Math.min( const computedActionTimeCurrent = Math.min(
@ -34,70 +40,54 @@ export function BlackOpElem(props: IProps): React.ReactElement {
props.bladeburner.actionTimeToComplete, props.bladeburner.actionTimeToComplete,
); );
function onStart(): void { const actionData = BlackOperations[props.action.name];
props.bladeburner.action.type = ActionTypes.BlackOperation; if (actionData === undefined) {
props.bladeburner.action.name = props.action.name; throw new Error(`Cannot find data for ${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 ( return (
<> <Paper sx={{ my: 1, p: 1 }}>
<h2 style={{ display: "inline-block" }}> <Typography>
{isActive ? ( {isActive ? (
<> <>
<CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "} <>
{formatNumber(props.bladeburner.actionTimeToComplete, 0)}) <CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "}
{formatNumber(props.bladeburner.actionTimeToComplete, 0)})
<p style={{ display: "block" }}>
{createProgressBarText({
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
})}
</p>
</>
</> </>
) : ( ) : (
<CopyableText value={props.action.name} /> <>
<CopyableText value={props.action.name} />
<StartButton
bladeburner={props.bladeburner}
type={ActionTypes.BlackOperation}
name={props.action.name}
rerender={rerender}
/>
<TeamSizeButton action={props.action} bladeburner={props.bladeburner} />
</>
)} )}
</h2> </Typography>
{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 />
<br /> <br />
<p style={{ display: "inline-block" }} dangerouslySetInnerHTML={{ __html: props.action.desc }} /> <Typography>{actionData.desc}</Typography>
<br /> <br />
<br /> <br />
<p style={{ display: "block", color: hasReqdRank ? "white" : "red" }}> <Typography color={hasReqdRank ? "primary" : "error"}>
Required Rank: {formatNumber(props.action.reqdRank, 0)} Required Rank: {formatNumber(props.action.reqdRank, 0)}
</p> </Typography>
<br /> <br />
<pre style={{ display: "inline-block" }}> <Typography>
Estimated Success Chance: <SuccessChance chance={estimatedSuccessChance} />{" "} <SuccessChance action={props.action} bladeburner={props.bladeburner} />
{props.action.isStealth ? stealthIcon : <></>}
{props.action.isKill ? killIcon : <></>}
<br /> <br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)} Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
</pre> </Typography>
</> </Paper>
); );
} }

@ -35,9 +35,7 @@ export function BlackOpList(props: IProps): React.ReactElement {
return ( return (
<> <>
{blackops.map((blackop: BlackOperation) => ( {blackops.map((blackop: BlackOperation) => (
<li key={blackop.name} className="bladeburner-action"> <BlackOpElem key={blackop.name} bladeburner={props.bladeburner} action={blackop} player={props.player} />
<BlackOpElem bladeburner={props.bladeburner} action={blackop} player={props.player} />
</li>
))} ))}
</> </>
); );

@ -2,6 +2,7 @@ import * as React from "react";
import { BlackOpList } from "./BlackOpList"; import { BlackOpList } from "./BlackOpList";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -11,7 +12,7 @@ interface IProps {
export function BlackOpPage(props: IProps): React.ReactElement { export function BlackOpPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<p style={{ display: "block", margin: "4px", padding: "4px" }}> <Typography>
Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked Black Operations (Black Ops) are special, one-time covert operations. Each Black Op must be unlocked
successively by completing the one before it. successively by completing the one before it.
<br /> <br />
@ -21,7 +22,7 @@ export function BlackOpPage(props: IProps): React.ReactElement {
<br /> <br />
Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank Like normal operations, you may use a team for Black Ops. Failing a black op will incur heavy HP and rank
losses. losses.
</p> </Typography>
<BlackOpList bladeburner={props.bladeburner} player={props.player} /> <BlackOpList bladeburner={props.bladeburner} player={props.player} />
</> </>
); );

@ -1,42 +1,39 @@
import React from "react"; import React, { useState, useEffect } from "react";
import { Stats } from "./Stats"; import { Stats } from "./Stats";
import { Console } from "./Console"; import { Console } from "./Console";
import { AllPages } from "./AllPages"; import { AllPages } from "./AllPages";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import Grid from "@mui/material/Grid";
import Box from "@mui/material/Box";
export function BladeburnerRoot(): React.ReactElement { export function BladeburnerRoot(): React.ReactElement {
const player = use.Player(); const player = use.Player();
const router = use.Router(); const router = use.Router();
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, 200);
return () => clearInterval(id);
}, []);
const bladeburner = player.bladeburner; const bladeburner = player.bladeburner;
if (bladeburner === null) return <></>; if (bladeburner === null) return <></>;
return ( return (
<div className="bladeburner-container"> <Box display="flex" flexDirection="column">
<div style={{ height: "60%", display: "block", position: "relative" }}> <Grid container>
<div <Grid item xs={6}>
style={{
height: "100%",
width: "30%",
display: "inline-block",
border: "1px solid white",
}}
>
<Stats bladeburner={bladeburner} player={player} router={router} /> <Stats bladeburner={bladeburner} player={player} router={router} />
</div> </Grid>
<Console bladeburner={bladeburner} player={player} /> <Grid item xs={6}>
</div> <Console bladeburner={bladeburner} player={player} />
<div </Grid>
style={{ </Grid>
width: "70%",
display: "block", <AllPages bladeburner={bladeburner} player={player} />
border: "1px solid white", </Box>
marginTop: "6px",
padding: "6px",
position: "relative",
}}
>
<AllPages bladeburner={bladeburner} player={player} />
</div>
</div>
); );
} }

@ -2,18 +2,48 @@ import React, { useState, useRef, useEffect } from "react";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Paper from "@mui/material/Paper";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
interface ILineProps { interface ILineProps {
content: any; content: any;
} }
const useStyles = makeStyles((theme: Theme) =>
createStyles({
textfield: {
margin: theme.spacing(0),
width: "100%",
},
input: {
backgroundColor: "#000",
},
nopadding: {
padding: theme.spacing(0),
},
preformatted: {
whiteSpace: "pre-wrap",
margin: theme.spacing(0),
},
list: {
padding: theme.spacing(0),
height: "100%",
},
}),
);
function Line(props: ILineProps): React.ReactElement { function Line(props: ILineProps): React.ReactElement {
return ( return (
<tr> <ListItem sx={{ p: 0 }}>
<td className="bladeburner-console-line" style={{ color: "var(--my-font-color)", whiteSpace: "pre-wrap" }}> <Typography>{props.content}</Typography>
{props.content} </ListItem>
</td>
</tr>
); );
} }
@ -23,15 +53,21 @@ interface IProps {
} }
export function Console(props: IProps): React.ReactElement { export function Console(props: IProps): React.ReactElement {
const lastRef = useRef<HTMLDivElement>(null); const classes = useStyles();
const scrollHook = useRef<HTMLDivElement>(null);
const [command, setCommand] = useState("");
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function handleCommandChange(event: React.ChangeEvent<HTMLInputElement>): void {
setCommand(event.target.value);
}
const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length); const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length);
// TODO: Figure out how to actually make the scrolling work correctly. // TODO: Figure out how to actually make the scrolling work correctly.
function scrollToBottom(): void { function scrollToBottom(): void {
if (!lastRef.current) return; if (!scrollHook.current) return;
lastRef.current.scrollTop = lastRef.current.scrollHeight; scrollHook.current.scrollTop = scrollHook.current.scrollHeight;
} }
function rerender(): void { function rerender(): void {
@ -50,13 +86,11 @@ export function Console(props: IProps): React.ReactElement {
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void { function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) { if (event.keyCode === 13) {
event.preventDefault(); event.preventDefault();
const command = event.currentTarget.value;
event.currentTarget.value = "";
if (command.length > 0) { if (command.length > 0) {
props.bladeburner.postToConsole("> " + command); props.bladeburner.postToConsole("> " + command);
props.bladeburner.executeConsoleCommands(props.player, command); props.bladeburner.executeConsoleCommands(props.player, command);
setConsoleHistoryIndex(props.bladeburner.consoleHistory.length); setConsoleHistoryIndex(props.bladeburner.consoleHistory.length);
rerender(); setCommand("");
} }
} }
@ -105,31 +139,34 @@ export function Console(props: IProps): React.ReactElement {
} }
return ( return (
<div ref={lastRef} className="bladeburner-console-div"> <Box height={"60vh"} display={"flex"} alignItems={"stretch"} component={Paper}>
<table className="bladeburner-console-table"> <Box>
<tbody> <List sx={{ height: "100%", overflow: "auto" }}>
{/*
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) => ( {props.bladeburner.consoleLogs.map((log: any, i: number) => (
<Line key={i} content={log} /> <Line key={i} content={log} />
))} ))}
<tr key="input" id="bladeburner-console-input-row" className="bladeburner-console-input-row"> <TextField
<td className="bladeburner-console-input-cell"> classes={{ root: classes.textfield }}
<pre>{"> "}</pre> autoFocus
<input tabIndex={1}
autoFocus type="text"
className="bladeburner-console-input" value={command}
tabIndex={1} onChange={handleCommandChange}
type="text" onKeyDown={handleKeyDown}
onKeyDown={handleKeyDown} InputProps={{
/> // for players to hook in
</td> className: classes.input,
</tr> startAdornment: (
</tbody> <>
</table> <Typography>&gt;&nbsp;</Typography>
</div> </>
),
spellCheck: false,
}}
/>
</List>
<div ref={scrollHook}></div>
</Box>
</Box>
); );
} }

@ -2,113 +2,78 @@ import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes"; import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { stealthIcon, killIcon } from "../data/Icons"; import { Contracts } from "../data/Contracts";
import { BladeburnerConstants } from "../data/Constants";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IAction } from "../IAction";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { SuccessChance } from "./SuccessChance"; import { SuccessChance } from "./SuccessChance";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { ActionLevel } from "./ActionLevel";
import { Autolevel } from "./Autolevel";
import { StartButton } from "./StartButton";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
player: IPlayer; player: IPlayer;
action: any; action: IAction;
} }
export function ContractElem(props: IProps): React.ReactElement { export function ContractElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
const isActive = const isActive =
props.bladeburner.action.type === ActionTypes["Contract"] && props.action.name === props.bladeburner.action.name; props.bladeburner.action.type === ActionTypes["Contract"] && props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
const computedActionTimeCurrent = Math.min( const computedActionTimeCurrent = Math.min(
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete, props.bladeburner.actionTimeToComplete,
); );
const maxLevel = props.action.level >= props.action.maxLevel;
const actionTime = props.action.getActionTime(props.bladeburner); const actionTime = props.action.getActionTime(props.bladeburner);
const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`;
function onStart(): void { const actionData = Contracts[props.action.name];
props.bladeburner.action.type = ActionTypes.Contract; if (actionData === undefined) {
props.bladeburner.action.name = props.action.name; throw new Error(`Cannot find data for ${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 ( return (
<> <Paper sx={{ my: 1, p: 1 }}>
<h2 style={{ display: "inline-block" }}> {isActive ? (
{isActive ? ( <>
<> <Typography>
<CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "} <CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "}
{formatNumber(props.bladeburner.actionTimeToComplete, 0)}) {formatNumber(props.bladeburner.actionTimeToComplete, 0)})
</> </Typography>
) : ( <Typography>
<CopyableText value={props.action.name} /> {createProgressBarText({
)} progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
</h2> })}
{isActive ? ( </Typography>
<p style={{ display: "block" }}> </>
{createProgressBarText({
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
})}
</p>
) : ( ) : (
<> <>
<a onClick={onStart} className="a-link-button" style={{ margin: "3px", padding: "3px" }}> <CopyableText value={props.action.name} />
Start <StartButton
</a> bladeburner={props.bladeburner}
type={ActionTypes.Contract}
name={props.action.name}
rerender={rerender}
/>
</> </>
)} )}
<br /> <br />
<br /> <br />
<pre className="tooltip" style={{ display: "inline-block" }}> <ActionLevel action={props.action} bladeburner={props.bladeburner} isActive={isActive} rerender={rerender} />
<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 />
<br /> <br />
<pre style={{ display: "inline-block" }}> <Typography>
<span dangerouslySetInnerHTML={{ __html: props.action.desc }} /> {actionData.desc}
<br /> <br />
<br /> <br />
Estimated success chance: <SuccessChance chance={estimatedSuccessChance} />{" "} <SuccessChance action={props.action} bladeburner={props.bladeburner} />
{props.action.isStealth ? stealthIcon : <></>}
{props.action.isKill ? killIcon : <></>}
<br /> <br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)} Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
<br /> <br />
@ -117,13 +82,9 @@ export function ContractElem(props: IProps): React.ReactElement {
Successes: {props.action.successes} Successes: {props.action.successes}
<br /> <br />
Failures: {props.action.failures} Failures: {props.action.failures}
</pre> </Typography>
<br /> <br />
<label className="tooltip" style={{ color: "white" }} htmlFor={autolevelCheckboxId}> <Autolevel rerender={rerender} action={props.action} />
Autolevel: </Paper>
<span className="tooltiptext">Automatically increase operation level when possible</span>
</label>
<input type="checkbox" id={autolevelCheckboxId} checked={props.action.autoLevel} onChange={onAutolevel} />
</>
); );
} }

@ -14,9 +14,7 @@ export function ContractList(props: IProps): React.ReactElement {
return ( return (
<> <>
{names.map((name: string) => ( {names.map((name: string) => (
<li key={name} className="bladeburner-action"> <ContractElem key={name} bladeburner={props.bladeburner} action={contracts[name]} player={props.player} />
<ContractElem bladeburner={props.bladeburner} action={contracts[name]} player={props.player} />
</li>
))} ))}
</> </>
); );

@ -2,6 +2,7 @@ import * as React from "react";
import { ContractList } from "./ContractList"; import { ContractList } from "./ContractList";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -11,14 +12,14 @@ interface IProps {
export function ContractPage(props: IProps): React.ReactElement { export function ContractPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<p style={{ display: "block", margin: "4px", padding: "4px" }}> <Typography>
Complete contracts in order to increase your Bladeburner rank and earn money. Failing a contract will cause you 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. to lose HP, which can lead to hospitalization.
<br /> <br />
<br /> <br />
You can unlock higher-level contracts by successfully completing them. Higher-level contracts are more You can unlock higher-level contracts by successfully completing them. Higher-level contracts are more
difficult, but grant more rank, experience, and money. difficult, but grant more rank, experience, and money.
</p> </Typography>
<ContractList bladeburner={props.bladeburner} player={props.player} /> <ContractList bladeburner={props.bladeburner} player={props.player} />
</> </>
); );

@ -3,17 +3,28 @@ import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IAction } from "../IAction";
import { GeneralActions } from "../data/GeneralActions";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import { StartButton } from "./StartButton";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
player: IPlayer; player: IPlayer;
action: any; action: IAction;
} }
export function GeneralActionElem(props: IProps): React.ReactElement { export function GeneralActionElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
const isActive = props.action.name === props.bladeburner.action.name; const isActive = props.action.name === props.bladeburner.action.name;
const computedActionTimeCurrent = Math.min( const computedActionTimeCurrent = Math.min(
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
@ -37,44 +48,44 @@ export function GeneralActionElem(props: IProps): React.ReactElement {
? Math.max(0, Math.min(props.bladeburner.getRecruitmentSuccessChance(props.player), 1)) ? Math.max(0, Math.min(props.bladeburner.getRecruitmentSuccessChance(props.player), 1))
: -1; : -1;
function onStart(): void { const actionData = GeneralActions[props.action.name];
props.bladeburner.action.type = ActionTypes[props.action.name as string]; if (actionData === undefined) {
props.bladeburner.action.name = props.action.name; throw new Error(`Cannot find data for ${props.action.name}`);
props.bladeburner.startAction(props.player, props.bladeburner.action);
setRerender((old) => !old);
} }
return ( return (
<> <Paper sx={{ my: 1, p: 1 }}>
<h2 style={{ display: "inline-block" }}> {isActive ? (
{isActive ? ( <>
<> <Typography>
<CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "} <CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "}
{formatNumber(props.bladeburner.actionTimeToComplete, 0)}) {formatNumber(props.bladeburner.actionTimeToComplete, 0)})
</> </Typography>
) : ( <Typography>
<CopyableText value={props.action.name} /> {createProgressBarText({
)} progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
</h2> })}
{isActive ? ( </Typography>
<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>
</> </>
) : (
<Box display="flex" flexDirection="row" alignItems="center">
<Typography>
<CopyableText value={props.action.name} />
</Typography>
<StartButton
bladeburner={props.bladeburner}
type={ActionTypes[props.action.name as string]}
name={props.action.name}
rerender={rerender}
/>
</Box>
)} )}
<br /> <br />
<br /> <br />
<pre style={{ display: "inline-block" }} dangerouslySetInnerHTML={{ __html: props.action.desc }}></pre> <Typography>{actionData.desc}</Typography>
<br /> <br />
<br /> <br />
<pre style={{ display: "inline-block" }}> <Typography>
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)} Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
{successChance !== -1 && ( {successChance !== -1 && (
<> <>
@ -82,7 +93,7 @@ export function GeneralActionElem(props: IProps): React.ReactElement {
Estimated success chance: {formatNumber(successChance * 100, 1)}% Estimated success chance: {formatNumber(successChance * 100, 1)}%
</> </>
)} )}
</pre> </Typography>
</> </Paper>
); );
} }

@ -20,9 +20,7 @@ export function GeneralActionList(props: IProps): React.ReactElement {
return ( return (
<> <>
{actions.map((action: Action) => ( {actions.map((action: Action) => (
<li key={action.name} className="bladeburner-action"> <GeneralActionElem key={action.name} bladeburner={props.bladeburner} action={action} player={props.player} />
<GeneralActionElem bladeburner={props.bladeburner} action={action} player={props.player} />
</li>
))} ))}
</> </>
); );

@ -2,6 +2,7 @@ import * as React from "react";
import { GeneralActionList } from "./GeneralActionList"; import { GeneralActionList } from "./GeneralActionList";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -11,10 +12,7 @@ interface IProps {
export function GeneralActionPage(props: IProps): React.ReactElement { export function GeneralActionPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<p style={{ display: "block", margin: "4px", padding: "4px" }}> <Typography>These are generic actions that will assist you in your Bladeburner duties.</Typography>
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} /> <GeneralActionList bladeburner={props.bladeburner} player={props.player} />
</> </>
); );

@ -0,0 +1,9 @@
import React from "react";
import { killIcon } from "../data/Icons";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
export function KillIcon(): React.ReactElement {
return <Tooltip title={<Typography>This action involves retirement</Typography>}>{killIcon}</Tooltip>;
}

@ -2,127 +2,81 @@ import React, { useState } from "react";
import { ActionTypes } from "../data/ActionTypes"; import { ActionTypes } from "../data/ActionTypes";
import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { formatNumber, convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { stealthIcon, killIcon } from "../data/Icons";
import { BladeburnerConstants } from "../data/Constants";
import { createPopup } from "../../ui/React/createPopup";
import { TeamSizePopup } from "./TeamSizePopup";
import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { SuccessChance } from "./SuccessChance"; import { SuccessChance } from "./SuccessChance";
import { ActionLevel } from "./ActionLevel";
import { Autolevel } from "./Autolevel";
import { StartButton } from "./StartButton";
import { TeamSizeButton } from "./TeamSizeButton";
import { IBladeburner } from "../IBladeburner";
import { Operation } from "../Operation";
import { Operations } from "../data/Operations";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { CopyableText } from "../../ui/React/CopyableText"; import { CopyableText } from "../../ui/React/CopyableText";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
player: IPlayer; player: IPlayer;
action: any; action: Operation;
} }
export function OperationElem(props: IProps): React.ReactElement { export function OperationElem(props: IProps): React.ReactElement {
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
const isActive = const isActive =
props.bladeburner.action.type === ActionTypes["Operation"] && props.action.name === props.bladeburner.action.name; props.bladeburner.action.type === ActionTypes["Operation"] && props.action.name === props.bladeburner.action.name;
const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
const computedActionTimeCurrent = Math.min( const computedActionTimeCurrent = Math.min(
props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow, props.bladeburner.actionTimeCurrent + props.bladeburner.actionTimeOverflow,
props.bladeburner.actionTimeToComplete, props.bladeburner.actionTimeToComplete,
); );
const maxLevel = props.action.level >= props.action.maxLevel;
const actionTime = props.action.getActionTime(props.bladeburner); const actionTime = props.action.getActionTime(props.bladeburner);
const autolevelCheckboxId = `bladeburner-${props.action.name}-autolevel-checkbox`;
function onStart(): void { const actionData = Operations[props.action.name];
props.bladeburner.action.type = ActionTypes.Operation; if (actionData === undefined) {
props.bladeburner.action.name = props.action.name; throw new Error(`Cannot find data for ${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 ( return (
<> <Paper sx={{ my: 1, p: 1 }}>
<h2 style={{ display: "inline-block" }}> {isActive ? (
{isActive ? ( <>
<> <Typography>
<CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "} <CopyableText value={props.action.name} /> (IN PROGRESS - {formatNumber(computedActionTimeCurrent, 0)} /{" "}
{formatNumber(props.bladeburner.actionTimeToComplete, 0)}) {formatNumber(props.bladeburner.actionTimeToComplete, 0)})
</> </Typography>
) : ( <Typography>
<CopyableText value={props.action.name} /> {createProgressBarText({
)} progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
</h2> })}
{isActive ? ( </Typography>
<p style={{ display: "block" }}> </>
{createProgressBarText({
progress: computedActionTimeCurrent / props.bladeburner.actionTimeToComplete,
})}
</p>
) : ( ) : (
<> <>
<a onClick={onStart} className="a-link-button" style={{ margin: "3px", padding: "3px" }}> <CopyableText value={props.action.name} />
Start <StartButton
</a> bladeburner={props.bladeburner}
<a onClick={onTeam} style={{ margin: "3px", padding: "3px" }} className="a-link-button"> type={ActionTypes.Operation}
Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)}) name={props.action.name}
</a> rerender={rerender}
/>
<TeamSizeButton action={props.action} bladeburner={props.bladeburner} />
</> </>
)} )}
<br /> <br />
<br /> <br />
<pre className="tooltip" style={{ display: "inline-block" }}>
<span className="tooltiptext"> <ActionLevel action={props.action} bladeburner={props.bladeburner} isActive={isActive} rerender={rerender} />
{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 />
<br /> <br />
<pre style={{ display: "inline-block" }}> <Typography>
<span dangerouslySetInnerHTML={{ __html: props.action.desc }} /> {actionData.desc}
<br /> <br />
<br /> <br />
Estimated success chance: <SuccessChance chance={estimatedSuccessChance} />{" "} <SuccessChance action={props.action} bladeburner={props.bladeburner} />
{props.action.isStealth ? stealthIcon : <></>}
{props.action.isKill ? killIcon : <></>}
<br /> <br />
Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)} Time Required: {convertTimeMsToTimeElapsedString(actionTime * 1000)}
<br /> <br />
@ -131,13 +85,9 @@ export function OperationElem(props: IProps): React.ReactElement {
Successes: {props.action.successes} Successes: {props.action.successes}
<br /> <br />
Failures: {props.action.failures} Failures: {props.action.failures}
</pre> </Typography>
<br /> <br />
<label className="tooltip" style={{ color: "white" }} htmlFor={autolevelCheckboxId}> <Autolevel rerender={rerender} action={props.action} />
Autolevel: </Paper>
<span className="tooltiptext">Automatically increase operation level when possible</span>
</label>
<input type="checkbox" id={autolevelCheckboxId} checked={props.action.autoLevel} onChange={onAutolevel} />
</>
); );
} }

@ -14,9 +14,7 @@ export function OperationList(props: IProps): React.ReactElement {
return ( return (
<> <>
{names.map((name: string) => ( {names.map((name: string) => (
<li key={name} className="bladeburner-action"> <OperationElem key={name} bladeburner={props.bladeburner} action={operations[name]} player={props.player} />
<OperationElem bladeburner={props.bladeburner} action={operations[name]} player={props.player} />
</li>
))} ))}
</> </>
); );

@ -2,6 +2,7 @@ import * as React from "react";
import { OperationList } from "./OperationList"; import { OperationList } from "./OperationList";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import Typography from "@mui/material/Typography";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -11,7 +12,7 @@ interface IProps {
export function OperationPage(props: IProps): React.ReactElement { export function OperationPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<p style={{ display: "block", margin: "4px", padding: "4px" }}> <Typography>
Carry out operations for the Bladeburner division. Failing an operation will reduce your Bladeburner rank. It 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 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. punishing than contracts, but are also more rewarding.
@ -27,7 +28,7 @@ export function OperationPage(props: IProps): React.ReactElement {
<br /> <br />
You can unlock higher-level operations by successfully completing them. Higher-level operations are more You can unlock higher-level operations by successfully completing them. Higher-level operations are more
difficult, but grant more rank and experience. difficult, but grant more rank and experience.
</p> </Typography>
<OperationList bladeburner={props.bladeburner} player={props.player} /> <OperationList bladeburner={props.bladeburner} player={props.player} />
</> </>
); );

@ -3,6 +3,13 @@ import { CopyableText } from "../../ui/React/CopyableText";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton";
import Box from "@mui/material/Box";
import Paper from "@mui/material/Paper";
import AddIcon from "@mui/icons-material/Add";
import CloseIcon from "@mui/icons-material/Close";
interface IProps { interface IProps {
skill: any; skill: any;
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -28,26 +35,26 @@ export function SkillElem(props: IProps): React.ReactElement {
} }
return ( return (
<> <Paper sx={{ my: 1, p: 1 }}>
<h2 style={{ display: "inline-block" }}> <Box display="flex" flexDirection="row" alignItems="center">
<CopyableText value={props.skill.name} /> <CopyableText variant="h6" color="primary" value={props.skill.name} />
</h2> {!canLevel || maxLvl ? (
<a <IconButton disabled>
onClick={onClick} <CloseIcon />
style={{ display: "inline-block", margin: "3px", padding: "3px" }} </IconButton>
className={canLevel && !maxLvl ? "a-link-button" : "a-link-button-inactive"} ) : (
> <IconButton onClick={onClick}>
Level <AddIcon />
</a> </IconButton>
<br /> )}
<br /> </Box>
<p style={{ display: "block" }}>Level: {currentLevel}</p> <Typography>Level: {currentLevel}</Typography>
{maxLvl ? ( {maxLvl ? (
<p style={{ color: "red", display: "block" }}>MAX LEVEL</p> <Typography>MAX LEVEL</Typography>
) : ( ) : (
<p style={{ display: "block" }}>Skill Points required: {formatNumber(pointCost, 0)}</p> <Typography>Skill Points required: {formatNumber(pointCost, 0)}</Typography>
)} )}
<p style={{ display: "inline-block" }} dangerouslySetInnerHTML={{ __html: props.skill.desc }} /> <Typography>{props.skill.desc}</Typography>
</> </Paper>
); );
} }

@ -12,9 +12,7 @@ export function SkillList(props: IProps): React.ReactElement {
return ( return (
<> <>
{Object.keys(Skills).map((skill: string) => ( {Object.keys(Skills).map((skill: string) => (
<li key={skill} className="bladeburner-action"> <SkillElem key={skill} bladeburner={props.bladeburner} skill={Skills[skill]} onUpgrade={props.onUpgrade} />
<SkillElem bladeburner={props.bladeburner} skill={Skills[skill]} onUpgrade={props.onUpgrade} />
</li>
))} ))}
</> </>
); );

@ -3,7 +3,8 @@ import { SkillList } from "./SkillList";
import { BladeburnerConstants } from "../data/Constants"; import { BladeburnerConstants } from "../data/Constants";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import Typography from "@mui/material/Typography";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
} }
@ -18,46 +19,45 @@ export function SkillPage(props: IProps): React.ReactElement {
return ( return (
<> <>
<p> <Typography>
<strong>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</strong> <strong>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</strong>
</p> </Typography>
<p> <Typography>
You will gain one skill point every {BladeburnerConstants.RanksPerSkillPoint} ranks. You will gain one skill point every{" "}
<br /> {BladeburnerConstants.RanksPerSkillPoint * BitNodeMultipliers.BladeburnerSkillCost} ranks.
<br /> <br />
Note that when upgrading a skill, the benefit for that skill is additive. However, the effects of different Note that when upgrading a skill, the benefit for that skill is additive. However, the effects of different
skills with each other is multiplicative. skills with each other is multiplicative.
<br /> </Typography>
</p> {valid(mults["successChanceAll"]) && (
<br /> <Typography>Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)}</Typography>
{valid(mults["successChanceAll"]) && <p>Total Success Chance: x{formatNumber(mults["successChanceAll"], 3)}</p>} )}
{valid(mults["successChanceStealth"]) && ( {valid(mults["successChanceStealth"]) && (
<p>Stealth Success Chance: x{formatNumber(mults["successChanceStealth"], 3)}</p> <Typography>Stealth Success Chance: x{formatNumber(mults["successChanceStealth"], 3)}</Typography>
)} )}
{valid(mults["successChanceKill"]) && ( {valid(mults["successChanceKill"]) && (
<p>Retirement Success Chance: x{formatNumber(mults["successChanceKill"], 3)}</p> <Typography>Retirement Success Chance: x{formatNumber(mults["successChanceKill"], 3)}</Typography>
)} )}
{valid(mults["successChanceContract"]) && ( {valid(mults["successChanceContract"]) && (
<p>Contract Success Chance: x{formatNumber(mults["successChanceContract"], 3)}</p> <Typography>Contract Success Chance: x{formatNumber(mults["successChanceContract"], 3)}</Typography>
)} )}
{valid(mults["successChanceOperation"]) && ( {valid(mults["successChanceOperation"]) && (
<p>Operation Success Chance: x{formatNumber(mults["successChanceOperation"], 3)}</p> <Typography>Operation Success Chance: x{formatNumber(mults["successChanceOperation"], 3)}</Typography>
)} )}
{valid(mults["successChanceEstimate"]) && ( {valid(mults["successChanceEstimate"]) && (
<p>Synthoid Data Estimate: x{formatNumber(mults["successChanceEstimate"], 3)}</p> <Typography>Synthoid Data Estimate: x{formatNumber(mults["successChanceEstimate"], 3)}</Typography>
)} )}
{valid(mults["actionTime"]) && <p>Action Time: x{formatNumber(mults["actionTime"], 3)}</p>} {valid(mults["actionTime"]) && <Typography>Action Time: x{formatNumber(mults["actionTime"], 3)}</Typography>}
{valid(mults["effHack"]) && <p>Hacking Skill: x{formatNumber(mults["effHack"], 3)}</p>} {valid(mults["effHack"]) && <Typography>Hacking Skill: x{formatNumber(mults["effHack"], 3)}</Typography>}
{valid(mults["effStr"]) && <p>Strength: x{formatNumber(mults["effStr"], 3)}</p>} {valid(mults["effStr"]) && <Typography>Strength: x{formatNumber(mults["effStr"], 3)}</Typography>}
{valid(mults["effDef"]) && <p>Defense: x{formatNumber(mults["effDef"], 3)}</p>} {valid(mults["effDef"]) && <Typography>Defense: x{formatNumber(mults["effDef"], 3)}</Typography>}
{valid(mults["effDex"]) && <p>Dexterity: x{formatNumber(mults["effDex"], 3)}</p>} {valid(mults["effDex"]) && <Typography>Dexterity: x{formatNumber(mults["effDex"], 3)}</Typography>}
{valid(mults["effAgi"]) && <p>Agility: x{formatNumber(mults["effAgi"], 3)}</p>} {valid(mults["effAgi"]) && <Typography>Agility: x{formatNumber(mults["effAgi"], 3)}</Typography>}
{valid(mults["effCha"]) && <p>Charisma: x{formatNumber(mults["effCha"], 3)}</p>} {valid(mults["effCha"]) && <Typography>Charisma: x{formatNumber(mults["effCha"], 3)}</Typography>}
{valid(mults["effInt"]) && <p>Intelligence: x{formatNumber(mults["effInt"], 3)}</p>} {valid(mults["effInt"]) && <Typography>Intelligence: x{formatNumber(mults["effInt"], 3)}</Typography>}
{valid(mults["stamina"]) && <p>Stamina: x{formatNumber(mults["stamina"], 3)}</p>} {valid(mults["stamina"]) && <Typography>Stamina: x{formatNumber(mults["stamina"], 3)}</Typography>}
{valid(mults["money"]) && <p>Contract Money: x{formatNumber(mults["money"], 3)}</p>} {valid(mults["money"]) && <Typography>Contract Money: x{formatNumber(mults["money"], 3)}</Typography>}
{valid(mults["expGain"]) && <p>Exp Gain: x{formatNumber(mults["expGain"], 3)}</p>} {valid(mults["expGain"]) && <Typography>Exp Gain: x{formatNumber(mults["expGain"], 3)}</Typography>}
<br />
<SkillList bladeburner={props.bladeburner} onUpgrade={() => setRerender((old) => !old)} /> <SkillList bladeburner={props.bladeburner} onUpgrade={() => setRerender((old) => !old)} />
</> </>
); );

@ -0,0 +1,44 @@
import React from "react";
import { IBladeburner } from "../IBladeburner";
import { BlackOperation } from "../BlackOperation";
import { use } from "../../ui/Context";
import Button from "@mui/material/Button";
interface IProps {
bladeburner: IBladeburner;
type: number;
name: string;
rerender: () => void;
}
export function StartButton(props: IProps): React.ReactElement {
const player = use.Player();
const action = props.bladeburner.getActionObject({ name: props.name, type: props.type });
if (action == null) {
throw new Error("Failed to get Operation Object for: " + props.name);
}
let disabled = false;
if (action.count < 1) {
disabled = true;
}
if (props.name === "Raid" && props.bladeburner.getCurrentCity().comms === 0) {
disabled = true;
}
if (action instanceof BlackOperation && props.bladeburner.rank < action.reqdRank) {
disabled = true;
}
function onStart(): void {
if (disabled) return;
props.bladeburner.action.type = props.type;
props.bladeburner.action.name = props.name;
props.bladeburner.startAction(player, props.bladeburner.action);
props.rerender();
}
return (
<Button sx={{ mx: 1 }} disabled={disabled} onClick={onStart}>
Start
</Button>
);
}

@ -5,14 +5,17 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { StatsTable } from "../../ui/React/StatsTable"; import { StatsTable } from "../../ui/React/StatsTable";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { createPopup } from "../../ui/React/createPopup";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { joinFaction } from "../../Faction/FactionHelpers"; import { joinFaction } from "../../Faction/FactionHelpers";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import { TravelPopup } from "./TravelPopup"; import { TravelModal } from "./TravelModal";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
@ -21,131 +24,141 @@ interface IProps {
} }
export function Stats(props: IProps): React.ReactElement { export function Stats(props: IProps): React.ReactElement {
const [travelOpen, setTravelOpen] = useState(false);
const setRerender = useState(false)[1]; const setRerender = useState(false)[1];
const inFaction = props.bladeburner.rank >= BladeburnerConstants.RankNeededForFaction;
useEffect(() => { useEffect(() => {
const id = setInterval(() => setRerender((old) => !old), 1000); const id = setInterval(() => setRerender((old) => !old), 1000);
return () => clearInterval(id); 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 { function openFaction(): void {
if (!inFaction) return;
const faction = Factions["Bladeburners"]; const faction = Factions["Bladeburners"];
if (faction.isMember) { if (!faction.isMember) {
props.router.toFaction(faction); joinFaction(faction);
} 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!");
}
} }
props.router.toFaction(faction);
} }
return ( return (
<> <Paper sx={{ p: 1 }}>
<p className="tooltip" style={{ display: "inline-block" }}> <Box display="flex">
Rank: {formatNumber(props.bladeburner.rank, 2)} <Tooltip title={<Typography>Your rank within the Bladeburner division.</Typography>}>
<span className="tooltiptext">Your rank within the Bladeburner division.</span> <Typography>Rank: {formatNumber(props.bladeburner.rank, 2)}</Typography>
</p> </Tooltip>
</Box>
<br /> <br />
<p> <Box display="flex">
Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)} <Tooltip
</p> title={
<div className="help-tip" onClick={openStaminaHelp}> <Typography>
? Performing actions will use up your stamina.
</div> <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.
</Typography>
}
>
<Typography>
Stamina: {formatNumber(props.bladeburner.stamina, 3)} / {formatNumber(props.bladeburner.maxStamina, 3)}
</Typography>
</Tooltip>
</Box>
<br /> <br />
<p>Stamina Penalty: {formatNumber((1 - props.bladeburner.calculateStaminaPenalty()) * 100, 1)}%</p> <Typography>
Stamina Penalty: {formatNumber((1 - props.bladeburner.calculateStaminaPenalty()) * 100, 1)}%
</Typography>
<br /> <br />
<p>Team Size: {formatNumber(props.bladeburner.teamSize, 0)}</p> <Typography>Team Size: {formatNumber(props.bladeburner.teamSize, 0)}</Typography>
<p>Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}</p> <Typography>Team Members Lost: {formatNumber(props.bladeburner.teamLost, 0)}</Typography>
<br /> <br />
<p>Num Times Hospitalized: {props.bladeburner.numHosp}</p> <Typography>Num Times Hospitalized: {props.bladeburner.numHosp}</Typography>
<p> <Typography>
Money Lost From Hospitalizations: <Money money={props.bladeburner.moneyLost} /> Money Lost From Hospitalizations: <Money money={props.bladeburner.moneyLost} />
</p> </Typography>
<br /> <br />
<p>Current City: {props.bladeburner.city}</p> <Typography>Current City: {props.bladeburner.city}</Typography>
<p className="tooltip" style={{ display: "inline-block" }}> <Box display="flex">
Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)} <Tooltip
<span className="tooltiptext"> title={
This is your Bladeburner division's estimate of how many Synthoids exist in your current city. <Typography>
</span> This is your Bladeburner division's estimate of how many Synthoids exist in your current city. An accurate
</p> population count increases success rate estimates.
<div className="help-tip" onClick={openPopulationHelp}> </Typography>
? }
</div> >
<Typography>
Est. Synthoid Population: {numeralWrapper.formatPopulation(props.bladeburner.getCurrentCity().popEst)}
</Typography>
</Tooltip>
</Box>
<br /> <br />
<p className="tooltip" style={{ display: "inline-block" }}> <Box display="flex">
Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)} <Tooltip
<span className="tooltiptext"> title={
This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city. <Typography>
</span> This is your Bladeburner divison's estimate of how many Synthoid communities exist in your current city.
</p> </Typography>
}
>
<Typography>
Est. Synthoid Communities: {formatNumber(props.bladeburner.getCurrentCity().comms, 0)}
</Typography>
</Tooltip>
</Box>
<br /> <br />
<p className="tooltip" style={{ display: "inline-block" }}> <Box display="flex">
City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)} <Tooltip
<span className="tooltiptext"> title={
The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a chaos <Typography>
level can make contracts and operations harder. The city's chaos level due to tensions and conflicts between humans and Synthoids. Having too high of a
</span> chaos level can make contracts and operations harder.
</p> </Typography>
}
>
<Typography>City Chaos: {formatNumber(props.bladeburner.getCurrentCity().chaos)}</Typography>
</Tooltip>
</Box>
<br /> <br />
<br /> {(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000 > 15000 && (
<p className="tooltip" style={{ display: "inline-block" }}> <>
Bonus time:{" "} <Box display="flex">
{convertTimeMsToTimeElapsedString( <Tooltip
(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000, title={
)} <Typography>
<br /> You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by
<span className="tooltiptext"> browser). Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed.
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by browser). </Typography>
Bonus time makes the Bladeburner mechanic progress faster, up to 5x the normal speed. }
</span> >
</p> <Typography>
<p>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</p> Bonus time:{" "}
{convertTimeMsToTimeElapsedString(
(props.bladeburner.storedCycles / BladeburnerConstants.CyclesPerSecond) * 1000,
)}
</Typography>
</Tooltip>
</Box>
<br />
</>
)}
<Typography>Skill Points: {formatNumber(props.bladeburner.skillPoints, 0)}</Typography>
<br /> <br />
<StatsTable <StatsTable
rows={[ rows={[
@ -156,17 +169,15 @@ export function Stats(props: IProps): React.ReactElement {
]} ]}
/> />
<br /> <br />
<a onClick={openTravel} className="a-link-button" style={{ display: "inline-block" }}> <Button onClick={() => setTravelOpen(true)}>Travel</Button>
Travel <Tooltip title={!inFaction ? <Typography>Rank 25 required.</Typography> : ""}>
</a> <span>
<a onClick={openFaction} className="a-link-button tooltip" style={{ display: "inline-block" }}> <Button disabled={!inFaction} onClick={openFaction}>
<span className="tooltiptext"> Faction
Apply to the Bladeburner Faction, or go to the faction page if you are already a member </Button>
</span> </span>
Faction </Tooltip>
</a> <TravelModal open={travelOpen} onClose={() => setTravelOpen(false)} bladeburner={props.bladeburner} />
<br /> </Paper>
<br />
</>
); );
} }

@ -0,0 +1,9 @@
import React from "react";
import { stealthIcon } from "../data/Icons";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
export function StealthIcon(): React.ReactElement {
return <Tooltip title={<Typography>This action involves stealth</Typography>}>{stealthIcon}</Tooltip>;
}

@ -1,18 +1,33 @@
import React from "react"; import React from "react";
import { formatNumber } from "../../utils/StringHelperFunctions"; import { formatNumber } from "../../utils/StringHelperFunctions";
import { StealthIcon } from "./StealthIcon";
import { KillIcon } from "./KillIcon";
import { IAction } from "../IAction";
import { IBladeburner } from "../IBladeburner";
interface IProps { interface IProps {
chance: number[]; bladeburner: IBladeburner;
action: IAction;
} }
export function SuccessChance(props: IProps): React.ReactElement { export function SuccessChance(props: IProps): React.ReactElement {
if (props.chance[0] === props.chance[1]) { const estimatedSuccessChance = props.action.getEstSuccessChance(props.bladeburner);
return <>{formatNumber(props.chance[0] * 100, 1)}%</>;
let chance = <></>;
if (estimatedSuccessChance[0] === estimatedSuccessChance[1]) {
chance = <>{formatNumber(estimatedSuccessChance[0] * 100, 1)}%</>;
} else {
chance = (
<>
{formatNumber(estimatedSuccessChance[0] * 100, 1)}% ~ {formatNumber(estimatedSuccessChance[1] * 100, 1)}%
</>
);
} }
return ( return (
<> <>
{formatNumber(props.chance[0] * 100, 1)}% ~ {formatNumber(props.chance[1] * 100, 1)}% Estimated success chance: {chance} {props.action.isStealth ? <StealthIcon /> : <></>}
{props.action.isKill ? <KillIcon /> : <></>}
</> </>
); );
} }

@ -0,0 +1,22 @@
import React, { useState } from "react";
import { Operation } from "../Operation";
import { IBladeburner } from "../IBladeburner";
import { TeamSizeModal } from "./TeamSizeModal";
import { formatNumber } from "../../utils/StringHelperFunctions";
import Button from "@mui/material/Button";
interface IProps {
action: Operation;
bladeburner: IBladeburner;
}
export function TeamSizeButton(props: IProps): React.ReactElement {
const [open, setOpen] = useState(false);
return (
<>
<Button disabled={props.bladeburner.teamSize === 0} onClick={() => setOpen(true)}>
Set Team Size (Curr Size: {formatNumber(props.action.teamCount, 0)})
</Button>
<TeamSizeModal open={open} onClose={() => setOpen(false)} action={props.action} bladeburner={props.bladeburner} />
</>
);
}

@ -1,16 +1,20 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { removePopup } from "../../ui/React/createPopup";
import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { Modal } from "../../ui/React/Modal";
import { Action } from "../Action"; import { Action } from "../Action";
import { IBladeburner } from "../IBladeburner"; import { IBladeburner } from "../IBladeburner";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
interface IProps { interface IProps {
bladeburner: IBladeburner; bladeburner: IBladeburner;
action: Action; action: Action;
popupId: string; open: boolean;
onClose: () => void;
} }
export function TeamSizePopup(props: IProps): React.ReactElement { export function TeamSizeModal(props: IProps): React.ReactElement {
const [teamSize, setTeamSize] = useState<number | undefined>(); const [teamSize, setTeamSize] = useState<number | undefined>();
function confirmTeamSize(): void { function confirmTeamSize(): void {
@ -21,25 +25,25 @@ export function TeamSizePopup(props: IProps): React.ReactElement {
} else { } else {
props.action.teamCount = num; props.action.teamCount = num;
} }
removePopup(props.popupId); props.onClose();
}
function onTeamSize(event: React.ChangeEvent<HTMLInputElement>): void {
const x = parseFloat(event.target.value);
if (x > props.bladeburner.teamSize) setTeamSize(props.bladeburner.teamSize);
else setTeamSize(x);
} }
return ( return (
<> <Modal open={props.open} onClose={props.onClose}>
<p> <Typography>
Enter the amount of team members you would like to take on this Op. If you do not have the specified number of 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. team members, then as many as possible will be used. Note that team members may be lost during operations.
</p> </Typography>
<input <TextField autoFocus type="number" placeholder="Team size" value={teamSize} onChange={onTeamSize} />
autoFocus <Button sx={{ mx: 2 }} onClick={confirmTeamSize}>
type="number"
placeholder="Team size"
className="text-input"
onChange={(event) => setTeamSize(parseFloat(event.target.value))}
/>
<a className="a-link-button" onClick={confirmTeamSize}>
Confirm Confirm
</a> </Button>
</> </Modal>
); );
} }

@ -0,0 +1,41 @@
import React from "react";
import { IBladeburner } from "../IBladeburner";
import { WorldMap } from "../../ui/React/WorldMap";
import { Modal } from "../../ui/React/Modal";
import { CityName } from "../../Locations/data/CityNames";
import { Settings } from "../../Settings/Settings";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
interface IProps {
bladeburner: IBladeburner;
open: boolean;
onClose: () => void;
}
export function TravelModal(props: IProps): React.ReactElement {
function travel(city: string): void {
props.bladeburner.city = city;
props.onClose();
}
return (
<Modal open={props.open} onClose={props.onClose}>
<>
<Typography>
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.
</Typography>
{Settings.DisableASCIIArt ? (
Object.values(CityName).map((city: CityName) => (
<Button key={city} onClick={() => travel(city)}>
{city}
</Button>
))
) : (
<WorldMap currentCity={props.bladeburner.city as CityName} onTravel={(city: CityName) => travel(city)} />
)}
</>
</Modal>
);
}

@ -1,36 +0,0 @@
import React from "react";
import { removePopup } from "../../ui/React/createPopup";
import { IBladeburner } from "../IBladeburner";
import { WorldMap } from "../../ui/React/WorldMap";
import { CityName } from "../../Locations/data/CityNames";
import { Settings } from "../../Settings/Settings";
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>
{Settings.DisableASCIIArt ? (
Object.values(CityName).map((city: CityName) => (
<button key={city} className="std-button" onClick={() => travel(city)}>
{city}
</button>
))
) : (
<WorldMap currentCity={props.bladeburner.city as CityName} onTravel={(city: CityName) => travel(city)} />
)}
</>
);
}

@ -7,9 +7,11 @@ import { Deck } from "./CardDeck/Deck";
import { Hand } from "./CardDeck/Hand"; import { Hand } from "./CardDeck/Hand";
import { InputAdornment } from "@mui/material"; import { InputAdornment } from "@mui/material";
import { ReactCard } from "./CardDeck/ReactCard"; import { ReactCard } from "./CardDeck/ReactCard";
import { MuiTextField } from "../ui/React/MuiTextField"; import Button from "@mui/material/Button";
import { MuiButton } from "../ui/React/MuiButton"; import Paper from "@mui/material/Paper";
import { MuiPaper } from "../ui/React/MuiPaper"; import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
const MAX_BET = 100e6; const MAX_BET = 100e6;
@ -305,10 +307,10 @@ export class Blackjack extends Game<Props, State> {
const dealerHandValues = this.getHandDisplayValues(dealerHand); const dealerHandValues = this.getHandDisplayValues(dealerHand);
return ( return (
<div> <>
{/* Wager input */} {/* Wager input */}
<div> <Box>
<MuiTextField <TextField
value={betInput} value={betInput}
label={ label={
<> <>
@ -322,93 +324,88 @@ export class Blackjack extends Game<Props, State> {
error={wagerInvalid} error={wagerInvalid}
helperText={wagerInvalid ? wagerInvalidHelperText : ""} helperText={wagerInvalid ? wagerInvalidHelperText : ""}
type="number" type="number"
variant="filled"
style={{ style={{
width: "200px", width: "200px",
}} }}
InputProps={{ InputProps={{
startAdornment: <InputAdornment position="start">$</InputAdornment>, startAdornment: (
<InputAdornment position="start">
<Typography>$</Typography>
</InputAdornment>
),
}} }}
/> />
<p> <Typography>
{"Total earnings this session: "} {"Total earnings this session: "}
<Money money={gains} /> <Money money={gains} />
</p> </Typography>
</div> </Box>
{/* Buttons */} {/* Buttons */}
{!gameInProgress ? ( {!gameInProgress ? (
<div> <Button onClick={this.startOnClick} disabled={wagerInvalid || !this.canStartGame()}>
<MuiButton onClick={this.startOnClick} disabled={wagerInvalid || !this.canStartGame()}> Start
Start </Button>
</MuiButton>
</div>
) : ( ) : (
<div> <>
<MuiButton onClick={this.playerHit}>Hit</MuiButton> <Button onClick={this.playerHit}>Hit</Button>
<MuiButton color="secondary" onClick={this.playerStay}> <Button color="secondary" onClick={this.playerStay}>
Stay Stay
</MuiButton> </Button>
</div> </>
)} )}
{/* Main game part. Displays both if the game is in progress OR if there's a result so you can see {/* Main game part. Displays both if the game is in progress OR if there's a result so you can see
* the cards that led to that result. */} * the cards that led to that result. */}
{(gameInProgress || result !== Result.Pending) && ( {(gameInProgress || result !== Result.Pending) && (
<div> <>
<MuiPaper variant="outlined" elevation={2}> <Box display="flex">
<pre>Player</pre> <Paper elevation={2}>
{playerHand.cards.map((card, i) => ( <pre>Player</pre>
<ReactCard card={card} key={i} /> {playerHand.cards.map((card, i) => (
))} <ReactCard card={card} key={i} />
))}
<pre>Value(s): </pre> <pre>Value(s): </pre>
{playerHandValues.map((value, i) => ( {playerHandValues.map((value, i) => (
<pre key={i}>{value}</pre> <pre key={i}>{value}</pre>
))} ))}
</MuiPaper> </Paper>
</Box>
<br /> <br />
<MuiPaper variant="outlined" elevation={2}> <Box display="flex">
<pre>Dealer</pre> <Paper elevation={2}>
{dealerHand.cards.map((card, i) => ( <pre>Dealer</pre>
// Hide every card except the first while game is in progress {dealerHand.cards.map((card, i) => (
<ReactCard card={card} hidden={gameInProgress && i !== 0} key={i} /> // Hide every card except the first while game is in progress
))} <ReactCard card={card} hidden={gameInProgress && i !== 0} key={i} />
))}
{!gameInProgress && ( {!gameInProgress && (
<> <>
<pre>Value(s): </pre> <pre>Value(s): </pre>
{dealerHandValues.map((value, i) => ( {dealerHandValues.map((value, i) => (
<pre key={i}>{value}</pre> <pre key={i}>{value}</pre>
))} ))}
</> </>
)} )}
</MuiPaper> </Paper>
</div> </Box>
</>
)} )}
{/* Results from previous round */} {/* Results from previous round */}
{result !== Result.Pending && ( {result !== Result.Pending && (
<p> <Typography>
{result} {result}
{this.isPlayerWinResult(result) && ( {this.isPlayerWinResult(result) && <Money money={this.state.bet} />}
<> {result === Result.DealerWon && <Money money={this.state.bet} />}
{" You gained "} </Typography>
<Money money={this.state.bet} />
</>
)}
{result === Result.DealerWon && (
<>
{" You lost "}
<Money money={this.state.bet} />
</>
)}
</p>
)} )}
</div> </>
); );
} }
} }

@ -1,12 +1,44 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Card, Suit } from "./Card"; import { Card, Suit } from "./Card";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
import Paper from "@mui/material/Paper";
type Props = { type Props = {
card: Card; card: Card;
hidden?: boolean; hidden?: boolean;
}; };
const useStyles = makeStyles(() =>
createStyles({
card: {
padding: "10px",
border: "solid 1px #808080",
backgroundColor: "white",
display: "inline-block",
borderRadius: "10px",
fontSize: "18.5px",
textAlign: "center",
margin: "3px",
fontWeight: "bold",
},
red: {
color: "red",
},
black: {
color: "black",
},
value: {
fontSize: "20px",
fontFamily: "sans-serif",
},
}),
);
export const ReactCard: FC<Props> = ({ card, hidden }) => { export const ReactCard: FC<Props> = ({ card, hidden }) => {
const classes = useStyles();
let suit: React.ReactNode; let suit: React.ReactNode;
switch (card.suit) { switch (card.suit) {
case Suit.Clubs: case Suit.Clubs:
@ -25,11 +57,11 @@ export const ReactCard: FC<Props> = ({ card, hidden }) => {
throw new Error(`MissingCaseException: ${card.suit}`); throw new Error(`MissingCaseException: ${card.suit}`);
} }
return ( return (
<div className={`casino-card ${card.isRedSuit() ? "red" : "black"}`}> <Paper className={`${classes.card} ${card.isRedSuit() ? classes.red : classes.black}`}>
<> <>
<div className="value">{hidden ? " - " : card.formatValue()}</div> <span className={classes.value}>{hidden ? " - " : card.formatValue()}</span>
<div className={`suit`}>{hidden ? " - " : suit}</div> <span>{hidden ? " - " : suit}</span>
</> </>
</div> </Paper>
); );
}; };

@ -3,44 +3,32 @@
* *
* This subcomponent renders all of the buttons for training at the gym * This subcomponent renders all of the buttons for training at the gym
*/ */
import * as React from "react"; import React, { useState } from "react";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { BadRNG } from "./RNG"; import { BadRNG } from "./RNG";
import { Game } from "./Game"; import { win, reachedLimit } from "./Game";
import { trusted } from "./utils"; import { trusted } from "./utils";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
}; };
type IState = {
investment: number;
result: any;
status: string;
playLock: boolean;
};
const minPlay = 0; const minPlay = 0;
const maxPlay = 10e3; const maxPlay = 10e3;
export class CoinFlip extends Game<IProps, IState> { export function CoinFlip(props: IProps): React.ReactElement {
constructor(props: IProps) { const [investment, setInvestment] = useState(1000);
super(props); const [result, setResult] = useState(<span> </span>);
const [status, setStatus] = useState("");
const [playLock, setPlayLock] = useState(false);
this.state = { function updateInvestment(e: React.ChangeEvent<HTMLInputElement>): void {
investment: 1000,
result: <span> </span>,
status: "",
playLock: false,
};
this.play = this.play.bind(this);
this.updateInvestment = this.updateInvestment.bind(this);
}
updateInvestment(e: React.FormEvent<HTMLInputElement>): void {
let investment: number = parseInt(e.currentTarget.value); let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) { if (isNaN(investment)) {
investment = minPlay; investment = minPlay;
@ -51,11 +39,11 @@ export class CoinFlip extends Game<IProps, IState> {
if (investment < minPlay) { if (investment < minPlay) {
investment = minPlay; investment = minPlay;
} }
this.setState({ investment: investment }); setInvestment(investment);
} }
play(guess: string): void { function play(guess: string): void {
if (this.reachedLimit(this.props.p)) return; if (reachedLimit(props.p)) return;
const v = BadRNG.random(); const v = BadRNG.random();
let letter: string; let letter: string;
if (v < 0.5) { if (v < 0.5) {
@ -64,39 +52,48 @@ export class CoinFlip extends Game<IProps, IState> {
letter = "T"; letter = "T";
} }
const correct: boolean = guess === letter; const correct: boolean = guess === letter;
this.setState({
result: <span className={correct ? "text" : "failure"}>{letter}</span>, setResult(
status: correct ? " win!" : "lose!", <Box display="flex">
playLock: true, <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }} color={correct ? "primary" : "error"}>
}); {letter}
setTimeout(() => this.setState({ playLock: false }), 250); </Typography>
</Box>,
);
setStatus(correct ? " win!" : "lose!");
setPlayLock(true);
setTimeout(() => setPlayLock(false), 250);
if (correct) { if (correct) {
this.win(this.props.p, this.state.investment); win(props.p, investment);
} else { } else {
this.win(this.props.p, -this.state.investment); win(props.p, -investment);
} }
if (this.reachedLimit(this.props.p)) return; if (reachedLimit(props.p)) return;
} }
render(): React.ReactNode { return (
return ( <>
<> <Typography>Result:</Typography> {result}
<pre>{`+———————+`}</pre> <Box display="flex" alignItems="center">
<pre>{`| | | |`}</pre> <TextField
<pre> type="number"
{`| | `} onChange={updateInvestment}
{this.state.result} InputProps={{
{` | |`} endAdornment: (
</pre> <>
<pre>{`| | | |`}</pre> <Button onClick={trusted(() => play("H"))} disabled={playLock}>
<pre>{`+———————+`}</pre> Head!
<span className="text">Play for: </span> </Button>
<input type="number" className="text-input" onChange={this.updateInvestment} value={this.state.investment} /> <Button onClick={trusted(() => play("T"))} disabled={playLock}>
<br /> Tail!
<StdButton onClick={trusted(() => this.play("H"))} text={"Head!"} disabled={this.state.playLock} /> </Button>
<StdButton onClick={trusted(() => this.play("T"))} text={"Tail!"} disabled={this.state.playLock} /> </>
<h1>{this.state.status}</h1> ),
</> }}
); />
} </Box>
<Typography variant="h3">{status}</Typography>
</>
);
} }

@ -4,6 +4,19 @@ import { dialogBoxCreate } from "../ui/React/DialogBox";
const gainLimit = 10e9; const gainLimit = 10e9;
export function win(p: IPlayer, n: number): void {
p.gainMoney(n);
p.recordMoneySource(n, "casino");
}
export function reachedLimit(p: IPlayer): boolean {
const reached = p.getCasinoWinnings() > gainLimit;
if (reached) {
dialogBoxCreate(<>Alright cheater get out of here. You're not allowed here anymore.</>);
}
return reached;
}
export class Game<T, U> extends React.Component<T, U> { export class Game<T, U> extends React.Component<T, U> {
win(p: IPlayer, n: number): void { win(p: IPlayer, n: number): void {
p.gainMoney(n); p.gainMoney(n);

@ -1,25 +1,18 @@
import * as React from "react"; import React, { useState, useEffect } from "react";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { Game } from "./Game"; import { win, reachedLimit } from "./Game";
import { WHRNG } from "./RNG"; import { WHRNG } from "./RNG";
import { trusted } from "./utils"; import { trusted } from "./utils";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
}; };
type IState = {
investment: number;
canPlay: boolean;
status: string | JSX.Element;
n: number;
lock: boolean;
strategy: Strategy;
};
const minPlay = 0; const minPlay = 0;
const maxPlay = 1e7; const maxPlay = 1e7;
@ -118,48 +111,32 @@ function Single(s: number): Strategy {
}; };
} }
export class Roulette extends Game<IProps, IState> { export function Roulette(props: IProps): React.ReactElement {
interval = -1; const [rng] = useState(new WHRNG(new Date().getTime()));
rng: WHRNG; const [investment, setInvestment] = useState(1000);
const [canPlay, setCanPlay] = useState(true);
const [status, setStatus] = useState<string | JSX.Element>("waiting");
const [n, setN] = useState(0);
const [lock, setLock] = useState(true);
const [strategy, setStrategy] = useState<Strategy>({
payout: 0,
match: (): boolean => {
return false;
},
});
constructor(props: IProps) { useEffect(() => {
super(props); const i = window.setInterval(step, 50);
return () => clearInterval(i);
});
this.rng = new WHRNG(new Date().getTime()); function step(): void {
this.state = { if (!lock) {
investment: 1000, setN(Math.floor(Math.random() * 37));
canPlay: true,
status: "waiting",
n: 0,
lock: true,
strategy: {
payout: 0,
match: (): boolean => {
return false;
},
},
};
this.step = this.step.bind(this);
this.currentNumber = this.currentNumber.bind(this);
this.updateInvestment = this.updateInvestment.bind(this);
}
componentDidMount(): void {
this.interval = window.setInterval(this.step, 50);
}
step(): void {
if (!this.state.lock) {
this.setState({ n: Math.floor(Math.random() * 37) });
} }
} }
componentWillUnmount(): void { function updateInvestment(e: React.ChangeEvent<HTMLInputElement>): void {
clearInterval(this.interval);
}
updateInvestment(e: React.FormEvent<HTMLInputElement>): void {
let investment: number = parseInt(e.currentTarget.value); let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) { if (isNaN(investment)) {
investment = minPlay; investment = minPlay;
@ -170,265 +147,312 @@ export class Roulette extends Game<IProps, IState> {
if (investment < minPlay) { if (investment < minPlay) {
investment = minPlay; investment = minPlay;
} }
this.setState({ investment: investment }); setInvestment(investment);
} }
currentNumber(): string { function currentNumber(): string {
if (this.state.n === 0) return "0"; if (n === 0) return "0";
const color = isRed(this.state.n) ? "R" : "B"; const color = isRed(n) ? "R" : "B";
return `${this.state.n}${color}`; return `${n}${color}`;
} }
play(s: Strategy): void { function play(s: Strategy): void {
if (this.reachedLimit(this.props.p)) return; if (reachedLimit(props.p)) return;
this.setState({
canPlay: false, setCanPlay(false);
lock: false, setLock(false);
status: "playing", setStatus("playing");
strategy: s, setStrategy(s);
});
setTimeout(() => { setTimeout(() => {
let n = Math.floor(this.rng.random() * 37); let n = Math.floor(rng.random() * 37);
let status = <></>; let status = <></>;
let gain = 0; let gain = 0;
let playerWin = this.state.strategy.match(n); let playerWin = strategy.match(n);
// oh yeah, the house straight up cheats. Try finding the seed now! // oh yeah, the house straight up cheats. Try finding the seed now!
if (playerWin && Math.random() > 0.9) { if (playerWin && Math.random() > 0.9) {
playerWin = false; playerWin = false;
while (this.state.strategy.match(n)) { while (strategy.match(n)) {
n = (n + 1) % 36; n = (n + 1) % 36;
} }
} }
if (playerWin) { if (playerWin) {
gain = this.state.investment * this.state.strategy.payout; gain = investment * strategy.payout;
status = ( status = (
<> <>
won <Money money={gain} /> won <Money money={gain} />
</> </>
); );
} else { } else {
gain = -this.state.investment; gain = -investment;
status = ( status = (
<> <>
lost <Money money={-gain} /> lost <Money money={-gain} />
</> </>
); );
} }
this.win(this.props.p, gain); win(props.p, gain);
this.setState({
canPlay: true, setCanPlay(true);
lock: true, setLock(true);
status: status, setStatus(status);
n: n, setN(n);
});
this.reachedLimit(this.props.p); reachedLimit(props.p);
}, 1600); }, 1600);
} }
render(): React.ReactNode { return (
return ( <>
<> <Typography variant="h4">{currentNumber()}</Typography>
<h1>{this.currentNumber()}</h1> <TextField type="number" onChange={updateInvestment} placeholder={"Amount to play"} disabled={!canPlay} />
<input <Typography variant="h4">{status}</Typography>
type="number" <table>
className="text-input" <tbody>
onChange={this.updateInvestment} <tr>
placeholder={"Amount to play"} <td>
value={this.state.investment} <Button disabled={!canPlay} onClick={trusted(() => play(Single(3)))}>
disabled={!this.state.canPlay} 3
/> </Button>
<h1>{this.state.status}</h1> </td>
<table> <td>
<tbody> <Button disabled={!canPlay} onClick={trusted(() => play(Single(6)))}>
<tr> 6
<td> </Button>
<StdButton text={"3"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(3)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(9)))}>
<StdButton text={"6"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(6)))} /> 9
</td> </Button>
<td> </td>
<StdButton text={"9"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(9)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(12)))}>
<td> 12
<StdButton text={"12"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(12)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"15"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(15)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(15)))}>
</td> 15
<td> </Button>
<StdButton text={"18"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(18)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(18)))}>
<StdButton text={"21"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(21)))} /> 18
</td> </Button>
<td> </td>
<StdButton text={"24"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(24)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(21)))}>
<td> 21
<StdButton text={"27"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(27)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"30"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(30)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(24)))}>
</td> 24
<td> </Button>
<StdButton text={"33"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(33)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(27)))}>
<StdButton text={"36"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(36)))} /> 27
</td> </Button>
</tr> </td>
<tr> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(30)))}>
<StdButton text={"2"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(2)))} /> 30
</td> </Button>
<td> </td>
<StdButton text={"5"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(5)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(33)))}>
<td> 33
<StdButton text={"8"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(8)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"11"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(11)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(36)))}>
</td> 36
<td> </Button>
<StdButton text={"14"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(14)))} /> </td>
</td> </tr>
<td> <tr>
<StdButton text={"17"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(17)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(2)))}>
<td> 2
<StdButton text={"20"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(20)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"23"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(23)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(5)))}>
</td> 5
<td> </Button>
<StdButton text={"26"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(26)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(8)))}>
<StdButton text={"29"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(29)))} /> 8
</td> </Button>
<td> </td>
<StdButton text={"32"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(32)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(11)))}>
<td> 11
<StdButton text={"35"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(35)))} /> </Button>
</td> </td>
</tr> <td>
<tr> <Button disabled={!canPlay} onClick={trusted(() => play(Single(14)))}>
<td> 14
<StdButton text={"1"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(1)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"4"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(4)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(17)))}>
</td> 17
<td> </Button>
<StdButton text={"7"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(7)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(20)))}>
<StdButton text={"10"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(10)))} /> 20
</td> </Button>
<td> </td>
<StdButton text={"13"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(13)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(23)))}>
<td> 23
<StdButton text={"16"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(16)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"19"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(19)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(26)))}>
</td> 26
<td> </Button>
<StdButton text={"22"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(22)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(29)))}>
<StdButton text={"25"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(25)))} /> 29
</td> </Button>
<td> </td>
<StdButton text={"28"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(28)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(32)))}>
<td> 32
<StdButton text={"31"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(31)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"34"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(34)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(35)))}>
</td> 35
</tr> </Button>
<tr> </td>
<td colSpan={4}> </tr>
<StdButton <tr>
text={"1 to 12"} <td>
disabled={!this.state.canPlay} <Button disabled={!canPlay} onClick={trusted(() => play(Single(1)))}>
onClick={trusted(() => this.play(strategies.Third1))} 1
/> </Button>
</td> </td>
<td colSpan={4}> <td>
<StdButton <Button disabled={!canPlay} onClick={trusted(() => play(Single(4)))}>
text={"13 to 24"} 4
disabled={!this.state.canPlay} </Button>
onClick={trusted(() => this.play(strategies.Third2))} </td>
/> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(7)))}>
<td colSpan={4}> 7
<StdButton </Button>
text={"25 to 36"} </td>
disabled={!this.state.canPlay} <td>
onClick={trusted(() => this.play(strategies.Third3))} <Button disabled={!canPlay} onClick={trusted(() => play(Single(10)))}>
/> 10
</td> </Button>
</tr> </td>
<tr> <td>
<td colSpan={2}> <Button disabled={!canPlay} onClick={trusted(() => play(Single(13)))}>
<StdButton 13
text={"Red"} </Button>
disabled={!this.state.canPlay} </td>
onClick={trusted(() => this.play(strategies.Red))} <td>
/> <Button disabled={!canPlay} onClick={trusted(() => play(Single(16)))}>
</td> 16
<td colSpan={2}> </Button>
<StdButton </td>
text={"Black"} <td>
disabled={!this.state.canPlay} <Button disabled={!canPlay} onClick={trusted(() => play(Single(19)))}>
onClick={trusted(() => this.play(strategies.Black))} 19
/> </Button>
</td> </td>
<td colSpan={2}> <td>
<StdButton <Button disabled={!canPlay} onClick={trusted(() => play(Single(22)))}>
text={"Odd"} 22
disabled={!this.state.canPlay} </Button>
onClick={trusted(() => this.play(strategies.Odd))} </td>
/> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(25)))}>
<td colSpan={2}> 25
<StdButton </Button>
text={"Even"} </td>
disabled={!this.state.canPlay} <td>
onClick={trusted(() => this.play(strategies.Even))} <Button disabled={!canPlay} onClick={trusted(() => play(Single(28)))}>
/> 28
</td> </Button>
<td colSpan={2}> </td>
<StdButton <td>
text={"High"} <Button disabled={!canPlay} onClick={trusted(() => play(Single(31)))}>
disabled={!this.state.canPlay} 31
onClick={trusted(() => this.play(strategies.High))} </Button>
/> </td>
</td> <td>
<td colSpan={2}> <Button disabled={!canPlay} onClick={trusted(() => play(Single(34)))}>
<StdButton 34
text={"Low"} </Button>
disabled={!this.state.canPlay} </td>
onClick={trusted(() => this.play(strategies.Low))} </tr>
/> <tr>
</td> <td colSpan={4}>
</tr> <Button disabled={!canPlay} onClick={trusted(() => play(strategies.Third1))}>
<tr> 1 to 12
<td> </Button>
<StdButton text={"0"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(0)))} /> </td>
</td> <td colSpan={4}>
</tr> <Button disabled={!canPlay} onClick={trusted(() => play(strategies.Third2))}>
</tbody> 13 to 24
</table> </Button>
</> </td>
); <td colSpan={4}>
} <Button disabled={!canPlay} onClick={trusted(() => play(strategies.Third3))}>
25 to 36
</Button>
</td>
</tr>
<tr>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.Red))}>
Red
</Button>
</td>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.Black))}>
Black
</Button>
</td>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.Odd))}>
Odd
</Button>
</td>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.Even))}>
Even
</Button>
</td>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.High))}>
High
</Button>
</td>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.Low))}>
Low
</Button>
</td>
</tr>
<tr>
<td>
<Button disabled={!canPlay} onClick={trusted(() => play(Single(0)))}>
0
</Button>
</td>
</tr>
</tbody>
</table>
</>
);
} }

@ -1,24 +1,18 @@
import * as React from "react"; import React, { useState, useEffect } from "react";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { WHRNG } from "./RNG"; import { WHRNG } from "./RNG";
import { Game } from "./Game"; import { win, reachedLimit } from "./Game";
import { trusted } from "./utils"; import { trusted } from "./utils";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
}; };
type IState = {
index: number[];
locks: number[];
investment: number;
canPlay: boolean;
status: string | JSX.Element;
};
// statically shuffled array of symbols. // statically shuffled array of symbols.
const symbols = [ const symbols = [
"D", "D",
@ -147,104 +141,76 @@ const payLines = [
const minPlay = 0; const minPlay = 0;
const maxPlay = 1e6; const maxPlay = 1e6;
export class SlotMachine extends Game<IProps, IState> { export function SlotMachine(props: IProps): React.ReactElement {
rng: WHRNG; const [rng] = useState(new WHRNG(props.p.totalPlaytime));
interval = -1; const [index, setIndex] = useState<number[]>([0, 0, 0, 0, 0]);
const [locks, setLocks] = useState<number[]>([0, 0, 0, 0, 0]);
const [investment, setInvestment] = useState(1000);
const [canPlay, setCanPlay] = useState(true);
const [status, setStatus] = useState<string | JSX.Element>("waiting");
constructor(props: IProps) { useEffect(() => {
super(props); const i = window.setInterval(step, 50);
this.rng = new WHRNG(this.props.p.totalPlaytime); return () => clearInterval(i);
});
this.state = { function step(): void {
index: [0, 0, 0, 0, 0],
investment: 1000,
locks: [0, 0, 0, 0, 0],
canPlay: true,
status: "waiting",
};
this.play = this.play.bind(this);
this.lock = this.lock.bind(this);
this.unlock = this.unlock.bind(this);
this.step = this.step.bind(this);
this.checkWinnings = this.checkWinnings.bind(this);
this.getTable = this.getTable.bind(this);
this.updateInvestment = this.updateInvestment.bind(this);
}
componentDidMount(): void {
this.interval = window.setInterval(this.step, 50);
}
step(): void {
let stoppedOne = false; let stoppedOne = false;
const index = this.state.index.slice(); const copy = index.slice();
for (const i in index) { for (const i in copy) {
if (index[i] === this.state.locks[i] && !stoppedOne) continue; if (copy[i] === locks[i] && !stoppedOne) continue;
index[i] = (index[i] + 1) % symbols.length; copy[i] = (copy[i] + 1) % symbols.length;
stoppedOne = true; stoppedOne = true;
} }
this.setState({ index: index }); setIndex(copy);
if (stoppedOne && index.every((e, i) => e === this.state.locks[i])) { if (stoppedOne && copy.every((e, i) => e === locks[i])) {
this.checkWinnings(); checkWinnings();
} }
} }
componentWillUnmount(): void { function getTable(): string[][] {
clearInterval(this.interval);
}
getTable(): string[][] {
return [ return [
[ [
symbols[(this.state.index[0] + symbols.length - 1) % symbols.length], symbols[(index[0] + symbols.length - 1) % symbols.length],
symbols[(this.state.index[1] + symbols.length - 1) % symbols.length], symbols[(index[1] + symbols.length - 1) % symbols.length],
symbols[(this.state.index[2] + symbols.length - 1) % symbols.length], symbols[(index[2] + symbols.length - 1) % symbols.length],
symbols[(this.state.index[3] + symbols.length - 1) % symbols.length], symbols[(index[3] + symbols.length - 1) % symbols.length],
symbols[(this.state.index[4] + symbols.length - 1) % symbols.length], symbols[(index[4] + symbols.length - 1) % symbols.length],
], ],
[symbols[index[0]], symbols[index[1]], symbols[index[2]], symbols[index[3]], symbols[index[4]]],
[ [
symbols[this.state.index[0]], symbols[(index[0] + 1) % symbols.length],
symbols[this.state.index[1]], symbols[(index[1] + 1) % symbols.length],
symbols[this.state.index[2]], symbols[(index[2] + 1) % symbols.length],
symbols[this.state.index[3]], symbols[(index[3] + 1) % symbols.length],
symbols[this.state.index[4]], symbols[(index[4] + 1) % symbols.length],
],
[
symbols[(this.state.index[0] + 1) % symbols.length],
symbols[(this.state.index[1] + 1) % symbols.length],
symbols[(this.state.index[2] + 1) % symbols.length],
symbols[(this.state.index[3] + 1) % symbols.length],
symbols[(this.state.index[4] + 1) % symbols.length],
], ],
]; ];
} }
play(): void { function play(): void {
if (this.reachedLimit(this.props.p)) return; if (reachedLimit(props.p)) return;
this.setState({ status: "playing" }); setStatus("playing");
this.win(this.props.p, -this.state.investment); win(props.p, -investment);
if (!this.state.canPlay) return; if (!canPlay) return;
this.unlock(); unlock();
setTimeout(this.lock, this.rng.random() * 2000 + 1000); setTimeout(lock, rng.random() * 2000 + 1000);
} }
lock(): void { function lock(): void {
this.setState({ setLocks([
locks: [ Math.floor(rng.random() * symbols.length),
Math.floor(this.rng.random() * symbols.length), Math.floor(rng.random() * symbols.length),
Math.floor(this.rng.random() * symbols.length), Math.floor(rng.random() * symbols.length),
Math.floor(this.rng.random() * symbols.length), Math.floor(rng.random() * symbols.length),
Math.floor(this.rng.random() * symbols.length), Math.floor(rng.random() * symbols.length),
Math.floor(this.rng.random() * symbols.length), ]);
],
});
} }
checkWinnings(): void { function checkWinnings(): void {
const t = this.getTable(); const t = getTable();
const getPaylineData = function (payline: number[][]): string[] { const getPaylineData = function (payline: number[][]): string[] {
const data = []; const data = [];
for (const point of payline) { for (const point of payline) {
@ -263,35 +229,31 @@ export class SlotMachine extends Game<IProps, IState> {
return count; return count;
}; };
let gains = -this.state.investment; let gains = -investment;
for (const payline of payLines) { for (const payline of payLines) {
const data = getPaylineData(payline); const data = getPaylineData(payline);
const count = countSequence(data); const count = countSequence(data);
if (count < 3) continue; if (count < 3) continue;
const payout = getPayout(data[0], count - 3); const payout = getPayout(data[0], count - 3);
gains += this.state.investment * payout; gains += investment * payout;
this.win(this.props.p, this.state.investment * payout); win(props.p, investment * payout);
} }
this.setState({ setStatus(
status: ( <>
<> {gains > 0 ? "gained" : "lost"} <Money money={Math.abs(gains)} />
{gains > 0 ? "gained" : "lost"} <Money money={Math.abs(gains)} /> </>,
</> );
), setCanPlay(true);
canPlay: true, if (reachedLimit(props.p)) return;
});
if (this.reachedLimit(this.props.p)) return;
} }
unlock(): void { function unlock(): void {
this.setState({ setLocks([-1, -1, -1, -1, -1]);
locks: [-1, -1, -1, -1, -1], setCanPlay(false);
canPlay: false,
});
} }
updateInvestment(e: React.FormEvent<HTMLInputElement>): void { function updateInvestment(e: React.ChangeEvent<HTMLInputElement>): void {
let investment: number = parseInt(e.currentTarget.value); let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) { if (isNaN(investment)) {
investment = minPlay; investment = minPlay;
@ -302,53 +264,49 @@ export class SlotMachine extends Game<IProps, IState> {
if (investment < minPlay) { if (investment < minPlay) {
investment = minPlay; investment = minPlay;
} }
this.setState({ investment: investment }); setInvestment(investment);
} }
render(): React.ReactNode { const t = getTable();
const t = this.getTable(); // prettier-ignore
// prettier-ignore return (
return (
<> <>
<pre>++</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>++</Typography>
<pre>| | {t[0][0]} | {t[0][1]} | {t[0][2]} | {t[0][3]} | {t[0][4]} | |</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>| | {t[0][0]} | {t[0][1]} | {t[0][2]} | {t[0][3]} | {t[0][4]} | |</Typography>
<pre>| | | | | | | |</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>| | | | | | | |</Typography>
<pre>| | {symbols[this.state.index[0]]} | {symbols[this.state.index[1]]} | {symbols[this.state.index[2]]} | {symbols[this.state.index[3]]} | {symbols[this.state.index[4]]} | |</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>| | {symbols[index[0]]} | {symbols[index[1]]} | {symbols[index[2]]} | {symbols[index[3]]} | {symbols[index[4]]} | |</Typography>
<pre>| | | | | | | |</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>| | | | | | | |</Typography>
<pre>| | {symbols[(this.state.index[0]+1)%symbols.length]} | {symbols[(this.state.index[1]+1)%symbols.length]} | {symbols[(this.state.index[2]+1)%symbols.length]} | {symbols[(this.state.index[3]+1)%symbols.length]} | {symbols[(this.state.index[4]+1)%symbols.length]} | |</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>| | {symbols[(index[0]+1)%symbols.length]} | {symbols[(index[1]+1)%symbols.length]} | {symbols[(index[2]+1)%symbols.length]} | {symbols[(index[3]+1)%symbols.length]} | {symbols[(index[4]+1)%symbols.length]} | |</Typography>
<pre>++</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>++</Typography>
<input <TextField
type="number" type="number"
className="text-input" onChange={updateInvestment}
onChange={this.updateInvestment}
placeholder={"Amount to play"} placeholder={"Amount to play"}
value={this.state.investment} disabled={!canPlay}
disabled={!this.state.canPlay} InputProps={{endAdornment:(<Button
onClick={trusted(play)}
disabled={!canPlay}
>Spin!</Button>)}}
/> />
<StdButton
onClick={trusted(this.play)} <Typography variant="h4">{status}</Typography>
text={"Spin!"} <Typography>Pay lines</Typography>
disabled={!this.state.canPlay}
/>
<h1>{this.state.status}</h1>
<h2>Pay lines</h2>
<pre>----- ····· ·····</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>----- ····· ·····</Typography>
<pre>····· ----- ·····</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>····· ----- ·····</Typography>
<pre>····· ····· -----</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>····· ····· -----</Typography>
<br /> <br />
<pre>··^·· \···/ \···/</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>··^·· \···/ \···/</Typography>
<pre>·/·\· ·\·/· ·---·</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>·/·\· ·\·/· ·---·</Typography>
<pre>/···\ ··v·· ·····</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>/···\ ··v·· ·····</Typography>
<br /> <br />
<pre>····· ·---· ·····</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>····· ·---· ·····</Typography>
<pre>·---· /···\ \···/</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>·---· /···\ \···/</Typography>
<pre>/···\ ····· ·---·</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>/···\ ····· ·---·</Typography>
</> </>
); );
}
} }
// https://felgo.com/doc/how-to-make-a-slot-game-tutorial/ // https://felgo.com/doc/how-to-make-a-slot-game-tutorial/

@ -3,8 +3,7 @@ import { codingContractTypesMetadata, DescriptionFunc, GeneratorFunc, SolverFunc
import { IMap } from "./types"; import { IMap } from "./types";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "./utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "./utils/JSONReviver";
import { createPopup, removePopup } from "./ui/React/createPopup"; import { CodingContractEvent } from "./ui/React/CodingContractModal";
import { CodingContractPopup } from "./ui/React/CodingContractPopup";
/* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */ /* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */
@ -166,29 +165,21 @@ export class CodingContract {
* Creates a popup to prompt the player to solve the problem * Creates a popup to prompt the player to solve the problem
*/ */
async prompt(): Promise<CodingContractResult> { async prompt(): Promise<CodingContractResult> {
const popupId = `coding-contract-prompt-popup-${this.fn}`;
return new Promise<CodingContractResult>((resolve) => { return new Promise<CodingContractResult>((resolve) => {
createPopup( const props = {
popupId, c: this,
CodingContractPopup, onClose: () => {
{ resolve(CodingContractResult.Cancelled);
c: this,
popupId: popupId,
onClose: () => {
resolve(CodingContractResult.Cancelled);
removePopup(popupId);
},
onAttempt: (val: string) => {
if (this.isSolution(val)) {
resolve(CodingContractResult.Success);
} else {
resolve(CodingContractResult.Failure);
}
removePopup(popupId);
},
}, },
() => resolve(CodingContractResult.Cancelled), onAttempt: (val: string) => {
); if (this.isSolution(val)) {
resolve(CodingContractResult.Success);
} else {
resolve(CodingContractResult.Failure);
}
},
};
CodingContractEvent.emit(props);
}); });
} }

@ -0,0 +1,32 @@
import React from "react";
import { Company } from "../Company";
import { use } from "../../ui/Context";
import { Modal } from "../../ui/React/Modal";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
interface IProps {
open: boolean;
onClose: () => void;
locName: string;
company: Company;
onQuit: () => void;
}
export function QuitJobModal(props: IProps): React.ReactElement {
const player = use.Player();
function quit(): void {
player.quitJob(props.locName);
props.onQuit();
props.onClose();
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Typography> Would you like to quit your job at {props.company.name}?</Typography>
<br />
<br />
<Button onClick={quit}>Quit</Button>
</Modal>
);
}

@ -1,31 +0,0 @@
import React from "react";
import { Company } from "../Company";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { removePopup } from "../../ui/React/createPopup";
interface IProps {
locName: string;
company: Company;
player: IPlayer;
onQuit: () => void;
popupId: string;
}
export function QuitJobPopup(props: IProps): React.ReactElement {
function quit(): void {
props.player.quitJob(props.locName);
props.onQuit();
removePopup(props.popupId);
}
return (
<>
Would you like to quit your job at {props.company.name}?
<br />
<br />
<button autoFocus={true} className="std-button" onClick={quit}>
Quit
</button>
</>
);
}

@ -9,6 +9,7 @@ export const CONSTANTS: {
MaxSkillLevel: number; MaxSkillLevel: number;
MilliPerCycle: number; MilliPerCycle: number;
CorpFactionRepRequirement: number; CorpFactionRepRequirement: number;
BaseFocusBonus: number;
BaseCostFor1GBOfRamHome: number; BaseCostFor1GBOfRamHome: number;
BaseCostFor1GBOfRamServer: number; BaseCostFor1GBOfRamServer: number;
TravelCost: number; TravelCost: number;
@ -46,14 +47,6 @@ export const CONSTANTS: {
IntelligenceTerminalHackBaseExpGain: number; IntelligenceTerminalHackBaseExpGain: number;
IntelligenceSingFnBaseExpGain: number; IntelligenceSingFnBaseExpGain: number;
IntelligenceClassBaseExpGain: number; IntelligenceClassBaseExpGain: number;
IntelligenceHackingMissionBaseExpGain: number;
HackingMissionRepToDiffConversion: number;
HackingMissionRepToRewardConversion: number;
HackingMissionSpamTimeIncrease: number;
HackingMissionTransferAttackIncrease: number;
HackingMissionMiscDefenseIncrease: number;
HackingMissionDifficultyToHacking: number;
HackingMissionHowToPlay: string;
MillisecondsPer20Hours: number; MillisecondsPer20Hours: number;
GameCyclesPer20Hours: number; GameCyclesPer20Hours: number;
MillisecondsPer10Hours: number; MillisecondsPer10Hours: number;
@ -121,7 +114,7 @@ export const CONSTANTS: {
TotalNumBitNodes: number; TotalNumBitNodes: number;
LatestUpdate: string; LatestUpdate: string;
} = { } = {
Version: "0.54.0", Version: "0.55.0",
// Speed (in ms) at which the main loop is updated // Speed (in ms) at which the main loop is updated
_idleSpeed: 200, _idleSpeed: 200,
@ -198,63 +191,6 @@ export const CONSTANTS: {
IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain IntelligenceTerminalHackBaseExpGain: 200, // Hacking exp divided by this to determine int exp gain
IntelligenceSingFnBaseExpGain: 1.5, IntelligenceSingFnBaseExpGain: 1.5,
IntelligenceClassBaseExpGain: 0.01, IntelligenceClassBaseExpGain: 0.01,
IntelligenceHackingMissionBaseExpGain: 3, // Hacking Mission difficulty multiplied by this to get exp gain
// Hacking Missions
// TODO Move this into Hacking Mission implementation
HackingMissionRepToDiffConversion: 10000, // Faction rep is divided by this to get mission difficulty
HackingMissionRepToRewardConversion: 7, // Faction rep divided byt his to get mission rep reward
HackingMissionSpamTimeIncrease: 25000, // How much time limit increase is gained when conquering a Spam Node (ms)
HackingMissionTransferAttackIncrease: 1.05, // Multiplier by which the attack for all Core Nodes is increased when conquering a Transfer Node
HackingMissionMiscDefenseIncrease: 1.05, // The amount by which every misc node's defense is multiplied when one is conquered
HackingMissionDifficultyToHacking: 135, // Difficulty is multiplied by this to determine enemy's "hacking" level (to determine effects of scan/attack, etc)
HackingMissionHowToPlay:
"Hacking missions are a minigame that, if won, will reward you with faction reputation.<br><br>" +
"In this game you control a set of Nodes and use them to try and defeat an enemy. Your Nodes " +
"are colored blue, while the enemy's are red. There are also other nodes on the map colored gray " +
"that initially belong to neither you nor the enemy. The goal of the game is " +
"to capture all of the enemy's Database nodes within the time limit. " +
"If you fail to do this, you will lose.<br><br>" +
"Each Node has three stats: Attack, Defense, and HP. There are five different actions that " +
"a Node can take:<br><br> " +
"Attack - Targets an enemy Node and lowers its HP. The effectiveness is determined by the owner's Attack, the Player's " +
"hacking level, and the enemy's defense.<br><br>" +
"Scan - Targets an enemy Node and lowers its Defense. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the " +
"enemy's defense.<br><br>" +
"Weaken - Targets an enemy Node and lowers its Attack. The effectiveness is determined by the owner's Attack, the Player's hacking level, and the enemy's " +
"defense.<br><br>" +
"Fortify - Raises the Node's Defense. The effectiveness is determined by your hacking level.<br><br>" +
"Overflow - Raises the Node's Attack but lowers its Defense. The effectiveness is determined by your hacking level.<br><br>" +
"Note that when determining the effectiveness of the above actions, the TOTAL Attack or Defense of the team is used, not just the " +
"Attack/Defense of the individual Node that is performing the action.<br><br>" +
"To capture a Node, you must lower its HP down to 0.<br><br>" +
"There are six different types of Nodes:<br><br>" +
"CPU Core - These are your main Nodes that are used to perform actions. Capable of performing every action<br><br>" +
"Firewall - Nodes with high defense. These Nodes can 'Fortify'<br><br>" +
"Database - A special type of Node. The player's objective is to conquer all of the enemy's Database Nodes within " +
"the time limit. These Nodes cannot perform any actions<br><br>" +
"Spam - Conquering one of these Nodes will slow the enemy's trace, giving the player additional time to complete " +
"the mission. These Nodes cannot perform any actions<br><br>" +
"Transfer - Conquering one of these nodes will increase the Attack of all of your CPU Cores by a small fixed percentage. " +
"These Nodes are capable of performing every action except the 'Attack' action<br><br>" +
"Shield - Nodes with high defense. These Nodes can 'Fortify'<br><br>" +
"To assign an action to a Node, you must first select one of your Nodes. This can be done by simply clicking on it. Double-clicking " +
"a node will select all of your Nodes of the same type (e.g. select all CPU Core Nodes or all Transfer Nodes). Note that only Nodes " +
"that can perform actions (CPU Core, Transfer, Shield, Firewall) can be selected. Selected Nodes will be denoted with a white highlight. After selecting a Node or multiple Nodes, " +
"select its action using the Action Buttons near the top of the screen. Every action also has a corresponding keyboard " +
"shortcut.<br><br>" +
"For certain actions such as attacking, scanning, and weakening, the Node performing the action must have a target. To target " +
"another node, simply click-and-drag from the 'source' Node to a target. A Node can only have one target, and you can target " +
"any Node that is adjacent to one of your Nodes (immediately above, below, or to the side. NOT diagonal). Furthermore, only CPU Cores and Transfer Nodes " +
"can target, since they are the only ones that can perform the related actions. To remove a target, you can simply click on the line that represents " +
"the connection between one of your Nodes and its target. Alternatively, you can select the 'source' Node and click the 'Drop Connection' button, " +
"or press 'd'.<br><br>" +
"Other Notes:<br><br>" +
"-Whenever a miscellenaous Node (not owned by the player or enemy) is conquered, the defense of all remaining miscellaneous Nodes that " +
"are not actively being targeted will increase by a fixed percentage.<br><br>" +
"-Whenever a Node is conquered, its stats are significantly reduced<br><br>" +
"-Miscellaneous Nodes slowly raise their defense over time<br><br>" +
"-Nodes slowly regenerate health over time.",
// Time-related constants // Time-related constants
MillisecondsPer20Hours: 72000000, MillisecondsPer20Hours: 72000000,
@ -285,6 +221,7 @@ export const CONSTANTS: {
GameCyclesPerFiveMinutes: 300000 / 200, GameCyclesPerFiveMinutes: 300000 / 200,
// Player Work & Action // Player Work & Action
BaseFocusBonus: 0.8,
FactionWorkHacking: "Faction Hacking Work", FactionWorkHacking: "Faction Hacking Work",
FactionWorkField: "Faction Field Work", FactionWorkField: "Faction Field Work",
FactionWorkSecurity: "Faction Security Work", FactionWorkSecurity: "Faction Security Work",
@ -344,48 +281,26 @@ export const CONSTANTS: {
TotalNumBitNodes: 24, TotalNumBitNodes: 24,
LatestUpdate: ` LatestUpdate: `
v0.54.0 - 2021-09-20 One big react node (hydroflame & community) v0.55.0 - 2021-09-20 Material UI (hydroflame & community)
------------------------------------------- -------------------------------------------
** UI ** ** Global **
* The UI is now completely(ish) in react and I'm starting to implement * The game is now 100% in typescript, react, and Material-UI
Material-UI everywhere. This will help make the game feel more consistent.
* Major help from (@threehams)
* New Terminal
* New Active Scripts page
* New sidebar.
* New Character overview
* New tutorial
* New options page
* New create program page (@Nolshine)
** Netscript **
* Add companyName to getPlayer
** Factions **
* Megacorp factions are no longer removed when installing.
** Corporation **
* All research tooltips are always visible.
* Smart supply is enabled by default if purchased (@Nolshine)
** Misc. ** ** Misc. **
* Fix "Game saved" animation. (@Nolshine) * Corporations can no longer bribe special factions
* Update commitCrime documentation (@Tryneus) * Infiltration can no longer lose focus of the keyboard.
* Fix logbox scrolling weird (@Nolshine) * Fix terminal line limit
* Fix weird scrolling in corporations (@BartKoppelmans) * Added theme editor
* Fix typo (@BartKoppelmans & @Nolshine) * Theme applies on game load (@Nolshine)
* Delete game now has a confirmation modal (@Nolshine) * Sleeves no longer consume all bonus time for some actions
* Fix issue where skills would not get properly updated when entering new * Fix a bug where the autocomlete list would get duplicates
BN. (@Nolshine) * Fix tutorial not scaling properly on small screens
* Convert create gang to popup (@vmesecher) * Import should be more consistent
* Fixed a bug that prevented travel to Sector-12 and New Tokyo when not using * Typo with 'help' command
ASCII art. * Fix infinite loop in casino
* nerf noodle bar * nerf noodle bar
`, `,

@ -1,11 +1,8 @@
import { CorporationConstants } from "./data/Constants"; import { CorporationConstants } from "./data/Constants";
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { createElement } from "../ui/uiHelpers/createElement";
import { EmployeePositions } from "./EmployeePositions"; import { EmployeePositions } from "./EmployeePositions";
import { ICorporation } from "./ICorporation"; import { ICorporation } from "./ICorporation";
import { numeralWrapper } from "../ui/numeralFormat";
import { formatNumber } from "../utils/StringHelperFunctions";
import { OfficeSpace } from "./OfficeSpace"; import { OfficeSpace } from "./OfficeSpace";
import { IIndustry } from "./IIndustry"; import { IIndustry } from "./IIndustry";
@ -139,74 +136,6 @@ export class Employee {
return mult; return mult;
} }
//'panel' is the DOM element on which to create the UI
createUI(panel: HTMLElement, corporation: ICorporation, industry: IIndustry): void {
const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),
effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),
effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(),
effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier();
panel.style.color = "white";
panel.appendChild(
createElement("p", {
id: "cmpy-mgmt-employee-" + this.name + "-panel-text",
innerHTML:
"Morale: " +
formatNumber(this.mor, 3) +
"<br>" +
"Happiness: " +
formatNumber(this.hap, 3) +
"<br>" +
"Energy: " +
formatNumber(this.ene, 3) +
"<br>" +
"Intelligence: " +
formatNumber(effInt, 3) +
"<br>" +
"Charisma: " +
formatNumber(effCha, 3) +
"<br>" +
"Experience: " +
formatNumber(this.exp, 3) +
"<br>" +
"Creativity: " +
formatNumber(effCre, 3) +
"<br>" +
"Efficiency: " +
formatNumber(effEff, 3) +
"<br>" +
"Salary: " +
numeralWrapper.format(this.sal, "$0.000a") +
"/ s<br>",
}),
);
//Selector for employee position
const selector = createElement("select", {}) as HTMLSelectElement;
for (const key in EmployeePositions) {
if (EmployeePositions.hasOwnProperty(key)) {
selector.add(
createElement("option", {
text: EmployeePositions[key],
value: EmployeePositions[key],
}) as HTMLOptionElement,
);
}
}
selector.addEventListener("change", () => {
this.pos = selector.options[selector.selectedIndex].value;
});
//Set initial value of selector
for (let i = 0; i < selector.length; ++i) {
if (selector.options[i].value === this.pos) {
selector.selectedIndex = i;
break;
}
}
panel.appendChild(selector);
}
copy(): Employee { copy(): Employee {
const employee = new Employee(); const employee = new Employee();
employee.name = this.name; employee.name = this.name;

@ -16,7 +16,6 @@ import { Warehouse } from "./Warehouse";
import { ICorporation } from "./ICorporation"; import { ICorporation } from "./ICorporation";
import { IIndustry } from "./IIndustry"; import { IIndustry } from "./IIndustry";
import { IndustryUpgrade, IndustryUpgrades } from "./IndustryUpgrades"; import { IndustryUpgrade, IndustryUpgrades } from "./IndustryUpgrades";
import { formatNumber } from "../utils/StringHelperFunctions";
interface IParams { interface IParams {
name?: string; name?: string;
@ -385,9 +384,6 @@ export class Industry implements IIndustry {
const prod = this.products[prodName]; const prod = this.products[prodName];
if (prod === undefined) continue; if (prod === undefined) continue;
warehouse.sizeUsed += prod.data[warehouse.loc][0] * prod.siz; warehouse.sizeUsed += prod.data[warehouse.loc][0] * prod.siz;
if (prod.data[warehouse.loc][0] > 0) {
warehouse.breakdown += prodName + ": " + formatNumber(prod.data[warehouse.loc][0] * prod.siz, 0) + "<br>";
}
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More