Merge pull request #1011 from danielyxie/dev

v0.52.0 Infiltration 2.0
This commit is contained in:
hydroflame 2021-06-13 11:15:47 -04:00 committed by GitHub
commit 37ec5c733f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 1956 additions and 1452 deletions

@ -1,13 +1,13 @@
Collection of Quotes
The past is relevant only as data
The past is relevant only as data.
Pull on the new flesh like borrowed gloves and burn your fingers once again.
A weapon is a tool. A tool for killing and destroying. And there will be times
when you must kill and destroy. Then you will choose and equip yourself with the tools
that you need. But remember the weakness of weapons. They are an extension --
You are the killer and destroyer. You are whole, with or without them.
you are the killer and destroyer. You are whole, with or without them.
For all that we have done, as a civilization, as individuals, the universe is
not stable, and nor is any single thing within it. Stars consume themselves,

56
css/infiltration.scss Normal file

@ -0,0 +1,56 @@
@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;
}
}

@ -69,3 +69,22 @@
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;
}

@ -150,38 +150,6 @@
/* Infiltration */
#infiltration-container {
position: fixed;
padding: 6px;
span {
margin: 0;
padding: 0;
}
}
#infiltration-left-panel,
#infiltration-right-panel {
display: inline-block;
border: 1px solid #fff;
width: 35%;
height: 75%;
top: 10px;
overflow-y: auto;
overflow-x: auto;
}
#infiltration-faction-select {
color: #fff;
}
#infiltration-left-panel p,
#infiltration-right-panel p {
margin: 4px;
}
#infiltration-buttons {
margin-top: 20px;
}
#infiltration-buttons .a-link-button {
display: inline;
margin: 5px;
width: 70%;
}

File diff suppressed because one or more lines are too long

@ -1,2 +1,2 @@
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([845,0]),o()}({784:function(n,t,o){},786:function(n,t,o){},788:function(n,t,o){},790:function(n,t,o){},792:function(n,t,o){},794:function(n,t,o){},796:function(n,t,o){},798:function(n,t,o){},800:function(n,t,o){},802:function(n,t,o){},804:function(n,t,o){},806:function(n,t,o){},808:function(n,t,o){},810:function(n,t,o){},812:function(n,t,o){},814:function(n,t,o){},816:function(n,t,o){},818:function(n,t,o){},820:function(n,t,o){},822:function(n,t,o){},824:function(n,t,o){},826:function(n,t,o){},828:function(n,t,o){},830:function(n,t,o){},832:function(n,t,o){},834:function(n,t,o){},836:function(n,t,o){},838:function(n,t,o){},840:function(n,t,o){},842:function(n,t,o){},845:function(n,t,o){"use strict";o.r(t);o(844),o(842),o(840),o(838),o(836),o(834),o(832),o(830),o(828),o(826),o(824),o(822),o(820),o(818),o(816),o(814),o(812),o(810),o(808),o(806),o(804),o(802),o(800),o(798),o(796),o(794),o(792),o(790),o(788),o(786),o(784)}});
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([852,0]),o()}({789:function(n,t,o){},791:function(n,t,o){},793:function(n,t,o){},795:function(n,t,o){},797:function(n,t,o){},799:function(n,t,o){},801:function(n,t,o){},803:function(n,t,o){},805:function(n,t,o){},807:function(n,t,o){},809:function(n,t,o){},811:function(n,t,o){},813:function(n,t,o){},815:function(n,t,o){},817:function(n,t,o){},819:function(n,t,o){},821:function(n,t,o){},823:function(n,t,o){},825:function(n,t,o){},827:function(n,t,o){},829:function(n,t,o){},831:function(n,t,o){},833:function(n,t,o){},835:function(n,t,o){},837:function(n,t,o){},839:function(n,t,o){},841:function(n,t,o){},843:function(n,t,o){},845:function(n,t,o){},847:function(n,t,o){},849:function(n,t,o){},852:function(n,t,o){"use strict";o.r(t);o(851),o(849),o(847),o(845),o(843),o(841),o(839),o(837),o(835),o(833),o(831),o(829),o(827),o(825),o(823),o(821),o(819),o(817),o(815),o(813),o(811),o(809),o(807),o(805),o(803),o(801),o(799),o(797),o(795),o(793),o(791),o(789)}});
//# sourceMappingURL=engineStyle.bundle.js.map

86
dist/engineStyle.css vendored

@ -1355,33 +1355,8 @@ button {
/* Infiltration */
#infiltration-container {
position: fixed;
padding: 6px; }
#infiltration-container span {
margin: 0;
padding: 0; }
#infiltration-left-panel,
#infiltration-right-panel {
display: inline-block;
border: 1px solid #fff;
width: 35%;
height: 75%;
top: 10px;
overflow-y: auto;
overflow-x: auto; }
#infiltration-faction-select {
color: #fff; }
#infiltration-left-panel p,
#infiltration-right-panel p {
margin: 4px; }
#infiltration-buttons {
margin-top: 20px; }
#infiltration-buttons .a-link-button {
display: inline; }
margin: 5px;
width: 70%; }
/**
* Styling for the Augmentations UI. This is the page that displays all of the
@ -1814,6 +1789,22 @@ button {
float: right;
padding: 4px; }
.interactive-tutorial-command {
background-color: #000;
color: #adff2f;
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; }
/* COLORS */
/* Attributes */
* {
@ -5037,5 +5028,46 @@ html {
padding: 6px;
width: 60%; }
/* COLORS */
/* Attributes */
.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: #adff2f; } }
@-moz-keyframes cursorblink {
from, to {
color: transparent; }
50% {
color: #adff2f; } }
@-webkit-keyframes "cursorblink" {
from, to {
color: transparent; }
50% {
color: #adff2f; } }
@-ms-keyframes "cursorblink" {
from, to {
color: transparent; }
50% {
color: #adff2f; } }
@-o-keyframes "cursorblink" {
from, to {
color: transparent; }
50% {
color: #adff2f; } }
/*# sourceMappingURL=engineStyle.css.map*/

48
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -3,6 +3,55 @@
Changelog
=========
v0.52.0 - 2021-06-13 Infiltration 2.0 (hydroflame & community)
--------------------------------------------------------------
**Infiltration**
* Completely reworked. Not the same mechanic at all.
**Terminal**
* tail is smarter. It automatically assume the only possible options in some
cases.
**Intelligence**
* Now available when starting BN5 instead of after beating it for the first
time.
* Nerf the effect of intelligence on reputation gain.
**Augmentation**
* Added a new augmentation, the 'Unstable Circadian Modulator', whose
gimmick is that its stats are randomized every hour.
**Netscript**
* 'getPlayer' is not a singularity function anymore.
* 'hacknetNodes.constants' returns the correct values.
* 'createGang' has been added.
* 'inGang' has been added.
**Tutorial**
* Updated the tutorial. Made it look cleaner, fixed typos, etc.
**Misc.**
* Fix many typos in literature (@kwazygloo)
* Fix being able to unfocus from gym and university.
* Fix being able to do hacking missions while unfocused.
* Fix many typos in Augmentation descriptions (@kwazygloo)
* More numbers handle absurdly large values. (@Tesseract1234567890)
* Fix many typos (@Tesseract1234567890)
* Fixed an issue that caused a UI desync when sleeves were set to workout
stats other than strength at the gym.
* Fix weird alignment of donation text box and button. (@Tesseract1234567890)
* Fixed an issue where reputation could be transfered to new jobs when unfocused.
* Empty stack traces should no longer appear.
* Purchasing anything with Infinity money doesn't result in NaN.
v0.51.10 - 2021-05-31 Focus Mark, Focus! (hydroflame)
-----------------------------------------------------

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

@ -0,0 +1,12 @@
createGang() Netscript Function
======================================
.. js:function:: createGang(faction)
:RAM cost: 1 GB
:param string faction: Name of faction
:returns: ``true`` if a Gang was created with that faction.
Creates a Gang with that faction. You need to have access to Gangs, the
faction must be one of the approved gang factions, and you must be a member
of that faction for the creation to be successful.

@ -0,0 +1,7 @@
inGang() Netscript Function
======================================
.. js:function:: inGang()
:RAM cost: 1 GB
:returns: ``true`` if the player is already in a gang.

@ -25,6 +25,8 @@ In :ref:`netscriptjs`::
.. toctree::
:caption: API Functions:
createGang() <gangapi/createGang>
inGang() <gangapi/inGang>
getMemberNames() <gangapi/getMemberNames>
getGangInformation() <gangapi/getGangInformation>
getOtherGangInformation() <gangapi/getOtherGangInformation>

@ -65,7 +65,7 @@
<span id="augmentations-notification" class="notification-off"> </span>
</li>
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel">
<button id="hacknet-nodes-menu-link"> Hacknet Nodes </button>
<button id="hacknet-nodes-menu-link"> Hacknet </button>
</li>
<li id="sleeves-tab" class="mainmenu-accordion-panel">
<button id="sleeves-menu-link"> Sleeves </button>
@ -264,25 +264,7 @@
<div id="location-container" class="generic-menupage-container">
</div>
<div id="infiltration-container" class="generic-menupage-container">
<div id="infiltration-left-panel">
<p id="infiltration-level-text"> </p>
<div id="infiltration-buttons">
<button class="a-link-button tooltip" id="infiltration-kill"> </button>
<button class="a-link-button tooltip" id="infiltration-knockout"> </button>
<button class="a-link-button tooltip" id="infiltration-stealthknockout"> </button>
<button class="a-link-button tooltip" id="infiltration-assassinate"> </button>
<button class="a-link-button tooltip" id="infiltration-hacksecurity"> </button>
<button class="a-link-button tooltip" id="infiltration-destroysecurity"> </button>
<button class="a-link-button tooltip" id="infiltration-sneak"> </button>
<button class="a-link-button tooltip" id="infiltration-pickdoor"> </button>
<button class="a-link-button tooltip" id="infiltration-bribe"> </button>
<button class="a-link-button tooltip" id="infiltration-escape"> </button>
</div>
</div>
<div id="infiltration-right-panel">
<p id="infiltration-status-text"></p>
</div>
<div id="infiltration-container" class="generic-fullscreen-container">
</div>
<div id="stock-market-container" class="generic-menupage-container">

@ -126,5 +126,5 @@
"watch": "webpack --watch --mode production",
"watch:dev": "webpack --watch --mode development"
},
"version": "0.51.10"
"version": "0.52.0"
}

@ -2157,15 +2157,31 @@ function installAugmentations() {
dialogBoxCreate("You have not purchased any Augmentations to install!");
return false;
}
var augmentationList = "";
for (var i = 0; i < Player.queuedAugmentations.length; ++i) {
var aug = Augmentations[Player.queuedAugmentations[i].name];
let augmentationList = "";
let nfgIndex = -1;
for(let i = Player.queuedAugmentations.length-1; i >= 0; i--) {
if(Player.queuedAugmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
nfgIndex = i;
break;
}
}
for (let i = 0; i < Player.queuedAugmentations.length; ++i) {
const ownedAug = Player.queuedAugmentations[i];
const aug = Augmentations[ownedAug.name];
if (aug == null) {
console.error(`Invalid augmentation: ${Player.queuedAugmentations[i].name}`);
console.error(`Invalid augmentation: ${ownedAug.name}`);
continue;
}
if(ownedAug.name === AugmentationNames.NeuroFluxGovernor
&& i !== nfgIndex) continue;
let level = null;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {
level = ` - ${ownedAug.level}`;
}
applyAugmentation(Player.queuedAugmentations[i]);
augmentationList += (aug.name + "<br>");
augmentationList += (aug.name + level + "<br>");
}
Player.queuedAugmentations = [];
dialogBoxCreate("You slowly drift to sleep as scientists put you under in order " +

@ -7,12 +7,23 @@ import * as React from "react";
import { Augmentations } from "../../Augmentation/Augmentations";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { Player } from "../../Player";
import { IPlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
export function PurchasedAugmentations(): React.ReactElement {
const augs: React.ReactElement[] = [];
for (const ownedAug of Player.queuedAugmentations) {
// Only render the last NeuroFlux (there are no findLastIndex btw)
let nfgIndex = -1;
for(let i = Player.queuedAugmentations.length-1; i >= 0; i--) {
if(Player.queuedAugmentations[i].name === AugmentationNames.NeuroFluxGovernor) {
nfgIndex = i;
break;
}
}
for (let i = 0; i < Player.queuedAugmentations.length; i++) {
const ownedAug = Player.queuedAugmentations[i];
if(ownedAug.name === AugmentationNames.NeuroFluxGovernor && i !== nfgIndex) continue;
const aug = Augmentations[ownedAug.name];
let level = null;
if (ownedAug.name === AugmentationNames.NeuroFluxGovernor) {

@ -6,7 +6,7 @@
import { IMap } from "./types";
export const CONSTANTS: IMap<any> = {
Version: "0.51.10",
Version: "0.52.0",
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
@ -226,40 +226,46 @@ export const CONSTANTS: IMap<any> = {
LatestUpdate:
`
v0.51.10 - 2021-05-31 Focus Mark, Focus! (hydroflame)
v0.52.0 - 2021-06-13 Infiltration 2.0 (hydroflame)
-------
Focus
* You can now use the terminal and write scripts while working for factions
but you will gain reputation at a slower rate.
Infiltration
* Completely reworked. Not the same mechanic at all.
SF -1
* Added a new SF -1: Bypass
Terminal
* tail is smarter. It automatically assume the only possible options in some
cases.
Gang
* "Vigilante justice"/"Ethical hacking" now reduces wanted level by a very
small percentage as well an absolute value.
Intelligence
* Now available when starting BN5 instead of after beating it for the first
time.
* Nerf the effect of intelligence on reputation gain.
Augmentation
* Added a new augmentation, the 'Unstable Circadian Modulator', whose
gimmick is that its stats are randomized every hour.
Netscript
* 'tFormat' now has a second argument to display with millisecond precision.
* 'purchaseSleeveAug' can no longer purchase the same aug over and over for
the same sleeve.
* fix typo in logging for 'getServerSecurityLevel'
* Fixed some weird issue where very rarely you would get 0 exp from 'grow'
* 'getActionTime' now returns correct values for Diplomacy and Regeneration.
* 'getPlayer' is not a singularity function anymore.
* 'hacknetNodes.constants' returns the correct values.
* 'createGang' has been added.
* 'inGang' has been added.
Corporations
* Fixed an exploit where you could get nearly infinite corporation funds by
entering negative numbers in textboxes.
* Fixed an exploit where shares could be sold again by clicking the
"sell share" button via scripts.
Documentation
* typo fix in purchaseTor
* typo fix in basicgameplay/stats
Tutorial
* Updated the tutorial. Made it look cleaner, fixed typos, etc.
Misc.
* Very large number will no longer appear as "$NaNt"
* Hash capacity now displays in the "big number" format.
* Fix many typos in literature (@kwazygloo)
* Fix being able to unfocus from gym and university.
* Fix being able to do hacking missions while unfocused.
* Fix many typos in Augmentation descriptions (@kwazygloo)
* More numbers handle absurdly large values. (@Tesseract1234567890)
* Fix many typos (@Tesseract1234567890)
* Fixed an issue that caused a UI desync when sleeves were set to workout
stats other than strength at the gym.
* Fix weird alignment of donation text box and button. (@Tesseract1234567890)
* Fixed an issue where reputation could be transfered to new jobs when unfocused.
* Empty stack traces should no longer appear.
* Purchasing anything with Infinity money doesn't result in NaN.
`,
}

@ -726,7 +726,7 @@ class DevMenuComponent extends Component {
<button className="std-button" onClick={this.addMoney(1e9)}>Add $1b</button>
<button className="std-button" onClick={this.addMoney(1e12)}>Add $1t</button>
<button className="std-button" onClick={this.addMoney(1e15)}>Add $1000t</button>
<button className="std-button" onClick={this.addMoney(1e99)}>Add $1e99</button>
<button className="std-button" onClick={this.addMoney(Infinity)}>Add $Infinity</button>
<button className="std-button" onClick={this.upgradeRam}>Upgrade Home Computer's RAM</button>
</div>
<div className="row">

@ -31,6 +31,7 @@ type IState = {
const inputStyleMarkup = {
margin: "5px",
height: "26px",
}
export class DonateOption extends React.Component<IProps, IState> {

@ -79,8 +79,7 @@ export class Info extends React.Component<IProps, any> {
By doing so you will earn reputation for your faction. You will also gain
reputation passively over time, although at a very slow rate. Earning
reputation will allow you to purchase Augmentations through this faction, which
are powerful upgrades that enhance your abilities. Note that you cannot use your
terminal or create scripts when you are performing a task!
are powerful upgrades that enhance your abilities.
</p>
</div>
)

@ -29,11 +29,10 @@ export function PlayerInfo(props) {
{Money(Player.money.toNumber())}<br />
{
hasServers &&
<><span>Hashes: {Hashes(Player.hashManager.hashes)} / {Hashes(Player.hashManager.capacity)}</span><br /></>
hasServers && <><span>Hashes: {Hashes(Player.hashManager.hashes)} / {Hashes(Player.hashManager.capacity)}</span><br /></>
}
<span>Total Hacknet Node Production: </span>
<span>Total Hacknet {hasServers ? 'Server' : 'Node'} Production: </span>
{prod}
</p>
)

@ -129,7 +129,7 @@ export class HacknetRoot extends React.Component {
return (
<div>
<h1>Hacknet Nodes</h1>
<h1>Hacknet {hasHacknetServers() ? "Servers" : "Nodes"}</h1>
<GeneralInfo />
<PurchaseButton cost={purchaseCost} multiplier={this.state.purchaseMultiplier} onClick={this.handlePurchaseButtonClick} />

@ -8,7 +8,8 @@ export interface IEngine {
loadFactionContent: () => void;
loadFactionsContent: () => void;
loadGangContent: () => void;
loadInfiltrationContent: () => void;
loadInfiltrationContent: (name: string, difficulty: number, maxLevel: number) => void;
loadLocationContent: () => void;
loadMissionContent: () => void;
loadResleevingContent: () => void;
loadStockMarketContent: () => void;

@ -1,7 +0,0 @@
import { LocationName } from "./Locations/data/LocationNames";
export declare function beginInfiltration(companyName: LocationName,
startLevel: number,
rewardVal: number,
maxClearance: number,
diff: number): void;

@ -1,864 +0,0 @@
import { BitNodeMultipliers } from "./BitNode/BitNodeMultipliers";
import { CONSTANTS } from "./Constants";
import { Engine } from "./engine";
import { Player } from "./Player";
import { dialogBoxCreate } from "../utils/DialogBox";
import { clearEventListeners } from "../utils/uiHelpers/clearEventListeners";
import { getRandomInt } from "../utils/helpers/getRandomInt";
import { infiltrationBoxCreate } from "../utils/InfiltrationBox";
import { formatNumber } from "../utils/StringHelperFunctions";
import { numeralWrapper } from "./ui/numeralFormat";
let InfiltrationScenarios = {
Guards: "You see an armed security guard patrolling the area.",
TechOnly: "The area is equipped with a state-of-the-art security system: cameras, laser tripwires, and sentry turrets.",
TechOrLockedDoor: "The area is equipped with a state-of-the-art security system. There is a locked door on the side of the " +
"room that can be used to bypass security.",
Bots: "You see a few security bots patrolling the area.",
}
function InfiltrationInstance(companyName, startLevel, val, maxClearance, diff) {
this.companyName = companyName;
this.clearanceLevel = 0;
this.maxClearanceLevel = maxClearance;
this.securityLevel = startLevel;
this.difficulty = diff; // Affects how much security level increases. Represents a percentage
this.baseValue = val; // Base value of company secrets
this.secretsStolen = []; // Numbers representing value of stolen secrets
this.hackingExpGained = 0;
this.strExpGained = 0;
this.defExpGained = 0;
this.dexExpGained = 0;
this.agiExpGained = 0;
this.chaExpGained = 0;
this.intExpGained = 0;
}
InfiltrationInstance.prototype.expMultiplier = function() {
if (!this.clearanceLevel || isNaN(this.clearanceLevel) || !this.maxClearanceLevel ||isNaN(this.maxClearanceLevel)) return 1;
return 2.5 * this.clearanceLevel / this.maxClearanceLevel;
}
InfiltrationInstance.prototype.gainHackingExp = function(amt) {
if (isNaN(amt)) {return;}
this.hackingExpGained += amt;
}
InfiltrationInstance.prototype.calcGainedHackingExp = function() {
if(!this.hackingExpGained || isNaN(this.hackingExpGained)) return 0;
return Math.pow(this.hackingExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainStrengthExp = function(amt) {
if (isNaN(amt)) {return;}
this.strExpGained += amt;
}
InfiltrationInstance.prototype.calcGainedStrengthExp = function() {
if (!this.strExpGained || isNaN(this.strExpGained)) return 0;
return Math.pow(this.strExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainDefenseExp = function(amt) {
if (isNaN(amt)) {return;}
this.defExpGained += amt;
}
InfiltrationInstance.prototype.calcGainedDefenseExp = function() {
if (!this.defExpGained || isNaN(this.defExpGained)) return 0;
return Math.pow(this.defExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainDexterityExp = function(amt) {
if (isNaN(amt)) {return;}
this.dexExpGained += amt;
}
InfiltrationInstance.prototype.calcGainedDexterityExp = function() {
if (!this.dexExpGained || isNaN(this.dexExpGained)) return 0;
return Math.pow(this.dexExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainAgilityExp = function(amt) {
if (isNaN(amt)) {return;}
this.agiExpGained += amt;
}
InfiltrationInstance.prototype.calcGainedAgilityExp = function() {
if (!this.agiExpGained || isNaN(this.agiExpGained)) return 0;
return Math.pow(this.agiExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainCharismaExp = function(amt) {
if (isNaN(amt)) {return;}
this.chaExpGained += amt;
}
InfiltrationInstance.prototype.calcGainedCharismaExp = function() {
if (!this.chaExpGained || isNaN(this.chaExpGained)) return 0;
return Math.pow(this.chaExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
InfiltrationInstance.prototype.gainIntelligenceExp = function(amt) {
if (isNaN(amt)) {return;}
this.intExpGained += amt;
}
InfiltrationInstance.prototype.calcGainedIntelligenceExp = function() {
if(!this.intExpGained || isNaN(this.intExpGained)) return 0;
return Math.pow(this.intExpGained * this.expMultiplier(), CONSTANTS.InfiltrationExpPow);
}
function beginInfiltration(companyName, startLevel, val, maxClearance, diff) {
var inst = new InfiltrationInstance(companyName, startLevel, val, maxClearance, diff);
clearInfiltrationStatusText();
nextInfiltrationLevel(inst);
}
function endInfiltration(inst, success) {
if (success) {infiltrationBoxCreate(inst);}
clearEventListeners("infiltration-kill");
clearEventListeners("infiltration-knockout");
clearEventListeners("infiltration-stealthknockout");
clearEventListeners("infiltration-assassinate");
clearEventListeners("infiltration-hacksecurity");
clearEventListeners("infiltration-destroysecurity");
clearEventListeners("infiltration-sneak");
clearEventListeners("infiltration-pickdoor");
clearEventListeners("infiltration-bribe");
clearEventListeners("infiltration-escape");
Engine.loadLocationContent(false);
}
function nextInfiltrationLevel(inst) {
++inst.clearanceLevel;
updateInfiltrationLevelText(inst);
//Buttons
var killButton = clearEventListeners("infiltration-kill");
var knockoutButton = clearEventListeners("infiltration-knockout");
var stealthKnockoutButton = clearEventListeners("infiltration-stealthknockout");
var assassinateButton = clearEventListeners("infiltration-assassinate");
var hackSecurityButton = clearEventListeners("infiltration-hacksecurity");
var destroySecurityButton = clearEventListeners("infiltration-destroysecurity");
var sneakButton = clearEventListeners("infiltration-sneak");
var pickdoorButton = clearEventListeners("infiltration-pickdoor");
var bribeButton = clearEventListeners("infiltration-bribe");
var escapeButton = clearEventListeners("infiltration-escape");
killButton.style.display = "none";
knockoutButton.style.display = "none";
stealthKnockoutButton.style.display = "none";
assassinateButton.style.display = "none";
hackSecurityButton.style.display = "none";
destroySecurityButton.style.display = "none";
sneakButton.style.display = "none";
pickdoorButton.style.display = "none";
bribeButton.style.display = "none";
escapeButton.style.display = "none";
var rand = getRandomInt(0, 5); // This needs to change if more scenarios are added
var scenario = null;
switch (rand) {
case 1:
scenario = InfiltrationScenarios.TechOnly;
hackSecurityButton.style.display = "block";
destroySecurityButton.style.display = "block";
sneakButton.style.display = "block";
escapeButton.style.display = "block";
break;
case 2:
scenario = InfiltrationScenarios.TechOrLockedDoor;
hackSecurityButton.style.display = "block";
destroySecurityButton.style.display = "block";
sneakButton.style.display = "block";
pickdoorButton.style.display = "block";
escapeButton.style.display = "block";
break;
case 3:
scenario = InfiltrationScenarios.Bots;
killButton.style.display = "block";
killButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var res = attemptInfiltrationKill(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> killed the security bots! Unfortunately you alerted the " +
"rest of the facility's security. The facility's security " +
"level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
Player.karma -= 1;
endInfiltrationLevel(inst);
return false;
} else {
var dmgTaken = Math.max(1, Math.round(1.5 * inst.securityLevel / Player.defense));
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> to kill the security bots. The bots fight back " +
"and raise the alarm! You take " + dmgTaken + " damage and " +
"the facility's security level increases by " +
formatNumber((res[1]*100)-100, 2).toString() + "%");
if (Player.takeDamage(dmgTaken)) {
endInfiltration(inst, false);
}
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
});
assassinateButton.style.display = "block";
assassinateButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var res = attemptInfiltrationAssassinate(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> assassinated the security bots without being detected!");
Player.karma -= 1;
endInfiltrationLevel(inst);
return false;
} else {
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> to assassinate the security bots. The bots have not detected " +
"you but are now more alert for an intruder. The facility's security level " +
"has increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
});
hackSecurityButton.style.display = "block";
sneakButton.style.display = "block";
escapeButton.style.display = "block";
break;
default: //0, 4-5
scenario = InfiltrationScenarios.Guards;
killButton.style.display = "block";
killButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var res = attemptInfiltrationKill(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> killed the security guard! Unfortunately you alerted the " +
"rest of the facility's security. The facility's security " +
"level has increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
Player.karma -= 3;
++Player.numPeopleKilled;
endInfiltrationLevel(inst);
return false;
} else {
var dmgTaken = Math.max(1, Math.round(inst.securityLevel / Player.defense));
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> to kill the security guard. The guard fights back " +
"and raises the alarm! You take " + dmgTaken + " damage and " +
"the facility's security level has increased by " +
formatNumber((res[1]*100)-100, 2).toString() + "%");
if (Player.takeDamage(dmgTaken)) {
endInfiltration(inst, false);
}
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
});
knockoutButton.style.display = "block";
stealthKnockoutButton.style.display = "block";
assassinateButton.style.display = "block";
assassinateButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var res = attemptInfiltrationAssassinate(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> assassinated the security guard without being detected!");
Player.karma -= 3;
++Player.numPeopleKilled;
endInfiltrationLevel(inst);
return false;
} else {
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> to assassinate the security guard. The guard has not detected " +
"you but is now more alert for an intruder. The facility's security level " +
"has increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
});
sneakButton.style.display = "block";
bribeButton.style.display = "block";
escapeButton.style.display = "block";
break;
}
knockoutButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var res = attemptInfiltrationKnockout(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> knocked out the security guard! " +
"Unfortunately you made a lot of noise and alerted other security.");
writeInfiltrationStatusText("The facility's security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
endInfiltrationLevel(inst);
return false;
} else {
var dmgTaken = Math.max(1, Math.round(inst.securityLevel / Player.defense));
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> to knockout the security guard. The guard " +
"raises the alarm and fights back! You take " + dmgTaken + " damage and " +
"the facility's security level increases by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
if (Player.takeDamage(dmgTaken)) {
endInfiltration(inst, false);
}
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
return false;
});
stealthKnockoutButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var res = attemptInfiltrationStealthKnockout(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> knocked out the security guard without making " +
"any noise!");
endInfiltrationLevel(inst);
return false;
} else {
var dmgTaken = Math.max(1, Math.round(inst.securityLevel / Player.defense));
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> to stealthily knockout the security guard. The guard " +
"raises the alarm and fights back! You take " + dmgTaken + " damage and " +
"the facility's security level increases by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
if (Player.takeDamage(dmgTaken)) {
endInfiltration(inst, false);
}
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
return false;
});
hackSecurityButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var res = attemptInfiltrationHack(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> hacked and disabled the security system!");
writeInfiltrationStatusText("The facility's security level increased by " + ((res[1]*100) - 100).toString() + "%");
endInfiltrationLevel(inst);
return false;
} else {
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> to hack the security system. The facility's " +
"security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
return false;
});
destroySecurityButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var res = attemptInfiltrationDestroySecurity(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> and violently destroy the security system!");
writeInfiltrationStatusText("The facility's security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
endInfiltrationLevel(inst);
return false;
} else {
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> to destroy the security system. The facility's " +
"security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
return false;
});
sneakButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var res = attemptInfiltrationSneak(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> sneak past the security undetected!");
endInfiltrationLevel(inst);
return false;
} else {
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> and were detected while trying to sneak past security! The facility's " +
"security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
return false;
});
pickdoorButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var res = attemptInfiltrationPickLockedDoor(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> pick the locked door!");
writeInfiltrationStatusText("The facility's security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
endInfiltrationLevel(inst);
return false;
} else {
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> to pick the locked door. The facility's security level " +
"increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
return false;
});
bribeButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var bribeAmt = CONSTANTS.InfiltrationBribeBaseAmount * inst.clearanceLevel;
if (Player.money.lt(bribeAmt)) {
writeInfiltrationStatusText("You do not have enough money to bribe the guard. " +
"You need $" + bribeAmt);
return false;
}
var res = attemptInfiltrationBribe(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> bribed a guard to let you through " +
"to the next clearance level for $" + bribeAmt);
Player.loseMoney(bribeAmt);
endInfiltrationLevel(inst);
return false;
} else {
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> to bribe a guard! The guard is alerting " +
"other security guards about your presence! The facility's " +
"security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
return false;
});
escapeButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var res = attemptInfiltrationEscape(inst);
if (res[0]) {
writeInfiltrationStatusText("You <span class='success'>SUCCESSFULLY</span> escape from the facility with the stolen classified " +
"documents and company secrets!");
endInfiltration(inst, true);
return false;
} else {
writeInfiltrationStatusText("You <span class='failure'>FAILED</span> to escape from the facility. You took 1 damage. The facility's " +
"security level increased by " + formatNumber((res[1]*100)-100, 2).toString() + "%");
if (Player.takeDamage(1)) {
endInfiltration(inst, false);
}
}
updateInfiltrationButtons(inst, scenario);
updateInfiltrationLevelText(inst);
return false;
});
updateInfiltrationButtons(inst, scenario);
writeInfiltrationStatusText("");
writeInfiltrationStatusText("You are now on clearance level " + inst.clearanceLevel + ".<br>" +
scenario);
}
function endInfiltrationLevel(inst) {
// Check if you gained any secrets
if (inst.clearanceLevel % 5 == 0) {
var baseSecretValue = inst.baseValue * inst.clearanceLevel / 2;
var secretValue = baseSecretValue * Player.faction_rep_mult *
CONSTANTS.InfiltrationRepValue * BitNodeMultipliers.InfiltrationRep;
var secretMoneyValue = baseSecretValue * CONSTANTS.InfiltrationMoneyValue *
BitNodeMultipliers.InfiltrationMoney;
inst.secretsStolen.push(baseSecretValue);
dialogBoxCreate("You found and stole a set of classified documents from the company. " +
"These classified secrets could probably be sold for money (<span class='money-gold'>" +
numeralWrapper.formatMoney(secretMoneyValue) + "</span>), or they " +
"could be given to factions for reputation (<span class='light-yellow'>" + numeralWrapper.formatReputation(secretValue) + " rep</span>)");
}
// Increase security level based on difficulty
inst.securityLevel *= (1 + (inst.difficulty / 100));
writeInfiltrationStatusText("You move on to the facility's next clearance level. This " +
"clearance level has " + inst.difficulty + "% higher security");
// If this is max level, force endInfiltration
if (inst.clearanceLevel >= inst.maxClearanceLevel) {
endInfiltration(inst, true);
} else {
nextInfiltrationLevel(inst);
}
}
function writeInfiltrationStatusText(txt) {
var statusTxt = document.getElementById("infiltration-status-text");
statusTxt.innerHTML += (txt + "<br>");
statusTxt.parentElement.scrollTop = statusTxt.scrollHeight;
}
function clearInfiltrationStatusText() {
document.getElementById("infiltration-status-text").innerHTML = "";
}
function updateInfiltrationLevelText(inst) {
var totalValue = 0;
var totalMoneyValue = 0;
for (var i = 0; i < inst.secretsStolen.length; ++i) {
totalValue += (inst.secretsStolen[i] * Player.faction_rep_mult *
CONSTANTS.InfiltrationRepValue * BitNodeMultipliers.InfiltrationRep);
totalMoneyValue += inst.secretsStolen[i] * CONSTANTS.InfiltrationMoneyValue *
BitNodeMultipliers.InfiltrationMoney;
}
// TODO: fix this to not rely on <pre> and whitespace for formatting...
/* eslint-disable no-irregular-whitespace */
document.getElementById("infiltration-level-text").innerHTML =
"Facility name:    " + inst.companyName + "<br>" +
"Clearance Level:  " + inst.clearanceLevel + "<br>" +
"Security Level:   " + numeralWrapper.formatInfiltrationSecurity(inst.securityLevel) + "<br><br>" +
"Total value of stolen secrets<br>" +
"Reputation:       <span class='light-yellow'>" + numeralWrapper.formatReputation(totalValue, 3) + "</span><br>" +
"Money:           <span class='money-gold'>" + numeralWrapper.formatMoney(totalMoneyValue, 2) + "</span><br><br>" +
"Hack exp gained:  " + numeralWrapper.formatExp(inst.calcGainedHackingExp(), 3) + "<br>" +
"Str exp gained:   " + numeralWrapper.formatExp(inst.calcGainedStrengthExp(), 3) + "<br>" +
"Def exp gained:   " + numeralWrapper.formatExp(inst.calcGainedDefenseExp(), 3) + "<br>" +
"Dex exp gained:   " + numeralWrapper.formatExp(inst.calcGainedDexterityExp(), 3) + "<br>" +
"Agi exp gained:   " + numeralWrapper.formatExp(inst.calcGainedAgilityExp(), 3) + "<br>" +
"Cha exp gained:   " + numeralWrapper.formatExp(inst.calcGainedCharismaExp(), 3);
/* eslint-enable no-irregular-whitespace */
}
function updateInfiltrationButtons(inst, scenario) {
var killChance = getInfiltrationKillChance(inst);
var knockoutChance = getInfiltrationKnockoutChance(inst);
var stealthKnockoutChance = getInfiltrationStealthKnockoutChance(inst);
var assassinateChance = getInfiltrationAssassinateChance(inst);
var destroySecurityChance = getInfiltrationDestroySecurityChance(inst);
var hackChance = getInfiltrationHackChance(inst);
var sneakChance = getInfiltrationSneakChance(inst);
var lockpickChance = getInfiltrationPickLockedDoorChance(inst);
var bribeChance = getInfiltrationBribeChance(inst);
var escapeChance = getInfiltrationEscapeChance(inst);
document.getElementById("infiltration-escape").innerHTML = "Escape" +
"<span class='tooltiptext'>" +
"Attempt to escape the facility with the classified secrets and " +
"documents you have stolen. You have a " +
numeralWrapper.formatPercentage(escapeChance, 2) + " chance of success. If you fail, " +
"the security level will increase by 5%.</span>";
switch(scenario) {
case InfiltrationScenarios.TechOrLockedDoor:
document.getElementById("infiltration-pickdoor").innerHTML = "Lockpick" +
"<span class='tooltiptext'>" +
"Attempt to pick the locked door. You have a " +
numeralWrapper.formatPercentage(lockpickChance, 2) + " chance of success. " +
"If you succeed, the security level will increased by 1%. If you fail, the " +
"security level will increase by 3%.</span>";
case InfiltrationScenarios.TechOnly:
document.getElementById("infiltration-hacksecurity").innerHTML = "Hack" +
"<span class='tooltiptext'>" +
"Attempt to hack and disable the security system. You have a " +
numeralWrapper.formatPercentage(hackChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 3%. If you fail, " +
"the security level will increase by 5%.</span>";
document.getElementById("infiltration-destroysecurity").innerHTML = "Destroy security" +
"<span class='tooltiptext'>" +
"Attempt to violently destroy the security system. You have a " +
numeralWrapper.formatPercentage(destroySecurityChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 5%. If you fail, the " +
"security level will increase by 10%. </span>";
document.getElementById("infiltration-sneak").innerHTML = "Sneak" +
"<span class='tooltiptext'>" +
"Attempt to sneak past the security system. You have a " +
numeralWrapper.formatPercentage(sneakChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 8%. </span>";
break;
case InfiltrationScenarios.Bots:
document.getElementById("infiltration-kill").innerHTML = "Destroy bots" +
"<span class='tooltiptext'>" +
"Attempt to destroy the security bots through combat. You have a " +
numeralWrapper.formatPercentage(killChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 5%. If you fail, " +
"the security level will increase by 10%. </span>";
document.getElementById("infiltration-assassinate").innerHTML = "Assassinate bots" +
"<span class='tooltiptext'>" +
"Attempt to stealthily destroy the security bots through assassination. You have a " +
numeralWrapper.formatPercentage(assassinateChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 10%. </span>";
document.getElementById("infiltration-hacksecurity").innerHTML = "Hack bots" +
"<span class='tooltiptext'>" +
"Attempt to disable the security bots by hacking them. You have a " +
numeralWrapper.formatPercentage(hackChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 3%. If you fail, " +
"the security level will increase by 5%. </span>";
document.getElementById("infiltration-sneak").innerHTML = "Sneak" +
"<span class='tooltiptext'>" +
"Attempt to sneak past the security bots. You have a " +
numeralWrapper.formatPercentage(sneakChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 8%. </span>";
break;
case InfiltrationScenarios.Guards:
default:
document.getElementById("infiltration-kill").innerHTML = "Kill" +
"<span class='tooltiptext'>" +
"Attempt to kill the security guard. You have a " +
numeralWrapper.formatPercentage(killChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 5%. If you fail, " +
"the security level will decrease by 10%. </span>";
document.getElementById("infiltration-knockout").innerHTML = "Knockout" +
"<span class='tooltiptext'>" +
"Attempt to knockout the security guard. You have a " +
numeralWrapper.formatPercentage(knockoutChance, 2) + " chance of success. " +
"If you succeed, the security level will increase by 3%. If you fail, the " +
"security level will increase by 10%. </span>";
document.getElementById("infiltration-stealthknockout").innerHTML = "Stealth Knockout" +
"<span class='tooltiptext'>" +
"Attempt to stealthily knockout the security guard. You have a " +
numeralWrapper.formatPercentage(stealthKnockoutChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 10%. </span>";
document.getElementById("infiltration-assassinate").innerHTML = "Assassinate" +
"<span class='tooltiptext'>" +
"Attempt to assassinate the security guard. You have a " +
numeralWrapper.formatPercentage(assassinateChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 5%. </span>";
document.getElementById("infiltration-sneak").innerHTML = "Sneak" +
"<span class='tooltiptext'>" +
"Attempt to sneak past the security guard. You have a " +
numeralWrapper.formatPercentage(sneakChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 8%. </span>";
document.getElementById("infiltration-bribe").innerHTML = "Bribe" +
"<span class='tooltiptext'>" +
"Attempt to bribe the security guard. You have a " +
numeralWrapper.formatPercentage(bribeChance, 2) + " chance of success. " +
"If you fail, the security level will increase by 15%. </span>";
break;
}
}
let intWgt = CONSTANTS.IntelligenceInfiltrationWeight;
// Kill
// Success: 5%, Failure 10%, -Karma
function attemptInfiltrationKill(inst) {
var chance = getInfiltrationKillChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
inst.gainDefenseExp(inst.securityLevel / 75) * Player.defense_exp_mult;
inst.gainDexterityExp(inst.securityLevel / 75) * Player.dexterity_exp_mult;
inst.gainAgilityExp(inst.securityLevel / 75) * Player.agility_exp_mult;
if (Math.random() <= chance) {
inst.securityLevel *= 1.05;
return [true, 1.05];
} else {
inst.securityLevel *= 1.1;
return [false, 1.1];
}
}
function getInfiltrationKillChance(inst) {
var lvl = inst.securityLevel;
return Math.min(0.95,
(Player.strength +
Player.dexterity +
Player.agility) / (1.45 * lvl));
}
// Knockout
// Success: 3%, Failure: 10%
function attemptInfiltrationKnockout(inst) {
var chance = getInfiltrationKnockoutChance(inst);
inst.gainStrengthExp(inst.securityLevel / 70) * Player.strength_exp_mult;
inst.gainDefenseExp(inst.securityLevel / 70) * Player.defense_exp_mult;
inst.gainDexterityExp(inst.securityLevel / 70) * Player.dexterity_exp_mult;
inst.gainAgilityExp(inst.securityLevel / 70) * Player.agility_exp_mult;
if (Math.random() <= chance) {
inst.securityLevel *= 1.03;
return [true, 1.03];
} else {
inst.securityLevel *= 1.1;
return [false, 1.1];
}
}
function getInfiltrationKnockoutChance(inst) {
var lvl = inst.securityLevel;
return Math.min(0.95,
(Player.strength +
Player.dexterity +
Player.agility) / (1.7 * lvl));
}
// Stealth knockout
// Success: 0%, Failure: 10%
function attemptInfiltrationStealthKnockout(inst) {
var chance = getInfiltrationStealthKnockoutChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
inst.gainDexterityExp(inst.securityLevel / 60) * Player.dexterity_exp_mult;
inst.gainAgilityExp(inst.securityLevel / 60) * Player.agility_exp_mult;
if (Math.random() <= chance) {
return [true, 1];
} else {
inst.securityLevel *= 1.1;
return [false, 1.1];
}
}
function getInfiltrationStealthKnockoutChance(inst) {
var lvl = inst.securityLevel;
return Math.min(0.95,
(0.55 * Player.strength +
2 * Player.dexterity +
2 * Player.agility +
intWgt * Player.intelligence) / (3 * lvl));
}
// Assassination
// Success: 0%, Failure: 5%, -Karma
function attemptInfiltrationAssassinate(inst) {
var chance = getInfiltrationAssassinateChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
inst.gainDexterityExp(inst.securityLevel / 55) * Player.dexterity_exp_mult;
inst.gainAgilityExp(inst.securityLevel / 55) * Player.agility_exp_mult;
if (Math.random() <= chance) {
return [true, 1];
} else {
inst.securityLevel *= 1.05;
return [false, 1.05];
}
}
function getInfiltrationAssassinateChance(inst) {
var lvl = inst.securityLevel;
return Math.min(0.95,
(Player.dexterity +
0.5 * Player.agility +
intWgt * Player.intelligence) / (2 * lvl));
}
// Destroy security
// Success: 5%, Failure: 10%
function attemptInfiltrationDestroySecurity(inst) {
var chance = getInfiltrationDestroySecurityChance(inst);
inst.gainStrengthExp(inst.securityLevel / 75) * Player.strength_exp_mult;
inst.gainDefenseExp(inst.securityLevel / 75) * Player.defense_exp_mult;
inst.gainDexterityExp(inst.securityLevel / 75) * Player.dexterity_exp_mult;
inst.gainAgilityExp(inst.securityLevel / 75) * Player.agility_exp_mult;
if (Math.random() <= chance) {
inst.securityLevel *= 1.05;
return [true, 1.05];
} else {
inst.securityLevel *= 1.1;
return [false, 1.1];
}
}
function getInfiltrationDestroySecurityChance(inst) {
var lvl = inst.securityLevel;
return Math.min(0.95,
(Player.strength +
Player.dexterity +
Player.agility) / (2 * lvl));
}
// Hack security
// Success: 3%, Failure: 5%
function attemptInfiltrationHack(inst) {
var chance = getInfiltrationHackChance(inst);
inst.gainHackingExp(inst.securityLevel / 30) * Player.hacking_exp_mult;
inst.gainIntelligenceExp(inst.securityLevel / 680);
if (Math.random() <= chance) {
inst.securityLevel *= 1.03;
return [true, 1.03];
} else {
inst.securityLevel *= 1.05;
return [false, 1.05];
}
}
function getInfiltrationHackChance(inst) {
var lvl = inst.securityLevel;
return Math.min(0.95,
(Player.hacking_skill +
(intWgt * Player.intelligence)) / lvl);
}
// Sneak past security
// Success: 0%, Failure: 8%
function attemptInfiltrationSneak(inst) {
var chance = getInfiltrationSneakChance(inst);
inst.gainAgilityExp(inst.securityLevel / 30) * Player.agility_exp_mult;
if (Math.random() <= chance) {
return [true, 1];
} else {
inst.securityLevel *= 1.08;
return [false, 1.08];
}
}
function getInfiltrationSneakChance(inst) {
var lvl = inst.securityLevel;
return Math.min(0.95,
(Player.agility +
0.5 * Player.dexterity +
intWgt * Player.intelligence) / (2 * lvl));
}
// Pick locked door
// Success: 1%, Failure: 3%
function attemptInfiltrationPickLockedDoor(inst) {
var chance = getInfiltrationPickLockedDoorChance(inst);
inst.gainDexterityExp(inst.securityLevel / 25) * Player.dexterity_exp_mult;
if (Math.random() <= chance) {
inst.securityLevel *= 1.01;
return [true, 1.01];
} else {
inst.securityLevel *= 1.03;
return [false, 1.03];
}
}
function getInfiltrationPickLockedDoorChance(inst) {
var lvl = inst.securityLevel;
return Math.min(0.95,
(Player.dexterity +
intWgt * Player.intelligence) / lvl);
}
// Bribe
// Success: 0%, Failure: 15%,
function attemptInfiltrationBribe(inst) {
var chance = getInfiltrationBribeChance(inst);
inst.gainCharismaExp(inst.securityLevel / 8) * Player.charisma_exp_mult;
if (Math.random() <= chance) {
return [true, 1];
} else {
inst.securityLevel *= 1.15;
return [false, 1.15];
}
}
function getInfiltrationBribeChance(inst) {
var lvl = inst.securityLevel;
return Math.min(0.95,
(Player.charisma) / lvl);
}
// Escape
// Failure: 5%
function attemptInfiltrationEscape(inst) {
var chance = getInfiltrationEscapeChance(inst);
inst.gainAgilityExp(inst.securityLevel / 30) * Player.agility_exp_mult;
inst.gainDexterityExp(inst.securityLevel / 30) * Player.dexterity_exp_mult;
if (Math.random() <= chance) {
return [true, 1];
} else {
inst.securityLevel *= 1.05;
return [false, 1.05];
}
}
function getInfiltrationEscapeChance(inst) {
var lvl = inst.securityLevel;
return Math.min(0.95,
(2 * Player.agility +
Player.dexterity +
intWgt * Player.intelligence) / lvl);
}
export {beginInfiltration};

@ -0,0 +1,48 @@
import { Page, routing } from ".././ui/navigationTracking";
import { Root } from "./ui/Root";
import { IPlayer } from "../PersonObjects/IPlayer";
import { IEngine } from "../IEngine";
import * as React from "react";
import * as ReactDOM from "react-dom";
let container: HTMLElement = document.createElement('div');
(function() {
function setContainer(): void {
const c = document.getElementById("infiltration-container");
if(c === null) throw new Error("huh?");
container = c;
document.removeEventListener("DOMContentLoaded", setContainer);
}
document.addEventListener("DOMContentLoaded", setContainer);
})();
function calcDifficulty(player: IPlayer, startingDifficulty: number): number {
const totalStats = player.strength+
player.defense+
player.dexterity+
player.agility+
player.charisma;
const difficulty = startingDifficulty-
Math.pow(totalStats, 0.9)/250-
(player.intelligence/1600);
if(difficulty < 0) return 0;
if(difficulty > 3) return 3;
return difficulty;
}
export function displayInfiltrationContent(engine: IEngine, player: IPlayer, location: string, startingDifficulty: number, maxLevel: number): void {
if (!routing.isOn(Page.Infiltration)) return;
const difficulty = calcDifficulty(player, startingDifficulty);
ReactDOM.render(<Root
Engine={engine}
Player={player}
Location={location}
StartingDifficulty={startingDifficulty}
Difficulty={difficulty}
MaxLevel={maxLevel}
/>, container);
}

@ -0,0 +1,107 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { random } from "../utils";
import { interpolate } from "./Difficulty";
import { BlinkingCursor } from "./BlinkingCursor";
interface Difficulty {
[key: string]: number;
timer: number;
min: number;
max: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 16000, min: 3, max: 4},
Normal: {timer: 12500, min: 2, max: 3},
Hard: {timer: 15000, min: 3, max: 4},
Impossible: {timer: 8000, min: 4, max: 4},
}
export function BackwardGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, min: 0, max: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [answer] = useState(makeAnswer(difficulty));
const [guess, setGuess] = useState("");
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if(event.keyCode === 16) return;
const nextGuess = guess + event.key.toUpperCase();
if(!answer.startsWith(nextGuess)) props.onFailure();
else if (answer === nextGuess) props.onSuccess();
else setGuess(nextGuess);
}
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Type it backward</h1>
<KeyHandler onKeyDown={press} />
</Grid>
<Grid item xs={6}>
<p style={{transform: 'scaleX(-1)'}}>{answer}</p>
</Grid>
<Grid item xs={6}>
<p>{guess}<BlinkingCursor /></p>
</Grid>
</Grid>)
}
function makeAnswer(difficulty: Difficulty): string {
const length = random(difficulty.min, difficulty.max);
let answer = "";
for(let i = 0; i < length; i++) {
if(i > 0) answer += " "
answer += words[Math.floor(Math.random() * words.length)];
}
return answer;
}
const words = ["ALGORITHM", "ANALOG", "APP", "APPLICATION", "ARRAY", "BACKUP",
"BANDWIDTH", "BINARY", "BIT", "BITE", "BITMAP", "BLOG", "BLOGGER",
"BOOKMARK", "BOOT", "BROADBAND", "BROWSER", "BUFFER", "BUG", "BUS", "BYTE",
"CACHE", "CAPS LOCK", "CAPTCHA", "CD", "CD-ROM", "CLIENT",
"CLIPBOARD", "CLOUD", "COMPUTING", "COMMAND", "COMPILE", "COMPRESS",
"COMPUTER", "CONFIGURE", "COOKIE", "COPY", "CPU",
"CYBERCRIME", "CYBERSPACE", "DASHBOARD", "DATA", "MINING", "DATABASE",
"DEBUG", "DECOMPRESS", "DELETE", "DESKTOP", "DEVELOPMENT", "DIGITAL",
"DISK", "DNS", "DOCUMENT", "DOMAIN", "DOMAIN NAME", "DOT", "DOT MATRIX",
"DOWNLOAD", "DRAG", "DVD", "DYNAMIC", "EMAIL", "EMOTICON", "ENCRYPT",
"ENCRYPTION", "ENTER", "EXABYTE", "FAQ", "FILE", "FINDER", "FIREWALL",
"FIRMWARE", "FLAMING", "FLASH", "FLASH DRIVE", "FLOPPY DISK", "FLOWCHART",
"FOLDER", "FONT", "FORMAT", "FRAME", "FREEWARE", "GIGABYTE", "GRAPHICS",
"HACK", "HACKER", "HARDWARE", "HOME PAGE", "HOST", "HTML", "HYPERLINK",
"HYPERTEXT", "ICON", "INBOX", "INTEGER", "INTERFACE", "INTERNET",
"IP ADDRESS", "ITERATION", "JAVA", "JOYSTICK", "JUNKMAIL", "KERNEL",
"KEY", "KEYBOARD", "KEYWORD", "LAPTOP", "LASER PRINTER", "LINK", "LINUX",
"LOG OUT", "LOGIC", "LOGIN", "LURKING", "MACINTOSH", "MACRO", "MAINFRAME",
"MALWARE", "MEDIA", "MEMORY", "MIRROR", "MODEM", "MONITOR", "MOTHERBOARD",
"MOUSE", "MULTIMEDIA", "NET", "NETWORK", "NODE", "NOTEBOOK", "COMPUTER",
"OFFLINE", "ONLINE", "OPENSOURCE", "OPERATING", "SYSTEM", "OPTION", "OUTPUT",
"PAGE", "PASSWORD", "PASTE", "PATH", "PHISHING", "PIRACY", "PIRATE",
"PLATFORM", "PLUGIN", "PODCAST", "POPUP", "PORTAL", "PRINT", "PRINTER",
"PRIVACY", "PROCESS", "PROGRAM", "PROGRAMMER", "PROTOCOL", "QUEUE",
"QWERTY", "RAM", "REALTIME", "REBOOT", "RESOLUTION", "RESTORE", "ROM",
"ROOT", "ROUTER", "RUNTIME", "SAVE", "SCAN", "SCANNER", "SCREEN",
"SCREENSHOT", "SCRIPT", "SCROLL", "SCROLL", "SEARCH", "ENGINE",
"SECURITY", "SERVER", "SHAREWARE", "SHELL", "SHIFT", "SHIFT KEY",
"SNAPSHOT", "SOCIAL NETWORKING", "SOFTWARE", "SPAM", "SPAMMER",
"SPREADSHEET", "SPYWARE", "STATUS", "STORAGE", "SUPERCOMPUTER", "SURF",
"SYNTAX", "TABLE", "TAG", "TERMINAL", "TEMPLATE", "TERABYTE", "TEXT EDITOR",
"THREAD", "TOOLBAR", "TRASH", "TROJAN HORSE", "TYPEFACE", "UNDO", "UNIX",
"UPLOAD", "URL", "USER", "USER INTERFACE", "USERNAME", "UTILITY", "VERSION",
"VIRTUAL", "VIRTUAL MEMORY", "VIRUS", "WEB", "WEBMASTER",
"WEBSITE", "WIDGET", "WIKI", "WINDOW", "WINDOWS", "WIRELESS",
"PROCESSOR", "WORKSTATION", "WEB", "WORM", "WWW", "XML",
"ZIP"];

@ -0,0 +1,5 @@
import React from 'react';
export function BlinkingCursor(): React.ReactElement {
return (<span style={{fontSize: "1em"}} className={"blinking-cursor"}>|</span>);
}

@ -0,0 +1,84 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { random } from "../utils";
import { interpolate } from "./Difficulty";
import { BlinkingCursor } from "./BlinkingCursor";
interface Difficulty {
[key: string]: number;
timer: number;
min: number;
max: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer:8000, min: 2, max: 3},
Normal: {timer:6000, min: 4, max: 5},
Hard: {timer:4000, min: 4, max: 6},
Impossible: {timer: 2500, min: 7, max: 7},
}
function generateLeftSide(difficulty: Difficulty): string {
let str = "";
const length = random(difficulty.min, difficulty.max);
for(let i = 0; i < length; i++) {
str += ["[", '<', '(', '{'][Math.floor(Math.random()*4)];
}
return str;
}
function getChar(event: React.KeyboardEvent<HTMLElement>): string {
if(event.keyCode == 48 && event.shiftKey) return ")";
if(event.keyCode == 221 && !event.shiftKey) return "]";
if(event.keyCode == 221 && event.shiftKey) return "}";
if(event.keyCode == 190 && event.shiftKey) return ">";
return "";
}
function match(left: string, right: string): boolean {
return (left === '[' && right === ']') ||
(left === '<' && right === '>') ||
(left === '(' && right === ')') ||
(left === '{' && right === '}');
}
export function BracketGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer:0, min: 0, max: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [right, setRight] = useState("");
const [left] = useState(generateLeftSide(difficulty));
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const char = getChar(event);
if(!char) return;
if(!match(left[left.length-right.length-1], char)) {
props.onFailure();
return;
}
if(left.length === right.length+1) {
props.onSuccess();
return;
}
setRight(right+char);
}
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Close the brackets</h1>
<p style={{fontSize: '5em'}}>{`${left}${right}`}<BlinkingCursor /></p>
<KeyHandler onKeyDown={press} />
</Grid>
</Grid>)
}

@ -0,0 +1,96 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
interface Difficulty {
[key: string]: number;
timer: number;
size: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 12000, size: 6},
Normal: {timer: 9000, size: 8},
Hard: {timer: 5000, size: 9},
Impossible: {timer: 2500, size: 12},
}
export function BribeGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, size: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [choices] = useState(makeChoices(difficulty));
const [index, setIndex] = useState(0);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const k = event.keyCode;
if(k === 32) {
if(positive.includes(choices[index])) props.onSuccess();
else props.onFailure();
return;
}
let newIndex = index;
if([38, 87, 68, 39].includes(k)) newIndex++;
if([65, 37, 83, 40].includes(k)) newIndex--;
while(newIndex < 0) newIndex += choices.length;
while(newIndex > choices.length-1) newIndex -= choices.length;
setIndex(newIndex);
}
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1>Say something nice about the guard.</h1>
<KeyHandler onKeyDown={press} />
</Grid>
<Grid item xs={6}>
<h2 style={{fontSize: "2em"}}></h2>
<h2 style={{fontSize: "2em"}}>{choices[index]}</h2>
<h2 style={{fontSize: "2em"}}></h2>
</Grid>
</Grid>)
}
function shuffleArray(array: string[]): void {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
function makeChoices(difficulty: Difficulty): string[] {
const choices = [];
choices.push(positive[Math.floor(Math.random()*positive.length)]);
for(let i = 0; i < difficulty.size; i++) {
const option = negative[Math.floor(Math.random()*negative.length)];
if(choices.includes(option)) {
i--;
continue;
}
choices.push(option);
}
shuffleArray(choices);
return choices;
}
const positive = ["affectionate","agreeable","bright","charming","creative",
"determined","energetic","friendly","funny","generous","polite","likable",
"diplomatic","helpful","giving","kind","hardworking","patient","dynamic",
"loyal"];
const negative = ["aggressive","aloof","arrogant","big-headed","boastful",
"boring","bossy","careless","clingy","couch potato","cruel","cynical",
"grumpy","hot air","know it all","obnoxious","pain in the neck","picky",
"tactless","thoughtless"];

@ -0,0 +1,65 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { random, getArrow } from "../utils";
import { interpolate } from "./Difficulty";
interface Difficulty {
[key: string]: number;
timer: number;
min: number;
max: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 13000, min: 6, max: 8},
Normal: {timer: 7000, min: 7, max: 8},
Hard: {timer: 5000, min: 8, max: 9},
Impossible: {timer: 3000, min: 9, max: 9},
}
export function CheatCodeGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, min: 0, max: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [code] = useState(generateCode(difficulty));
const [index, setIndex] = useState(0);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if(code[index] !== getArrow(event)) {
props.onFailure();
return;
}
setIndex(index+1);
if(index+1 >= code.length) props.onSuccess();
}
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Enter the Code!</h1>
<p style={{fontSize: '5em'}}>{code[index]}</p>
<KeyHandler onKeyDown={press} />
</Grid>
</Grid>)
}
function generateCode(difficulty: Difficulty): string {
const arrows = ['←', '→', '↑', '↓'];
let code = '';
for(let i = 0; i < random(difficulty.min, difficulty.max); i++) {
let arrow = arrows[Math.floor(4*Math.random())];
while(arrow === code[code.length-1]) arrow = arrows[Math.floor(4*Math.random())];
code += arrow;
}
return code;
}

@ -0,0 +1,26 @@
import React, { useState, useEffect } from 'react';
import Grid from '@material-ui/core/Grid';
interface IProps {
onFinish: () => void;
}
export function Countdown(props: IProps): React.ReactElement {
const [x, setX] = useState(3);
useEffect(() => {
if(x === 0) {
props.onFinish();
return;
}
setTimeout(()=>setX(x-1), 200);
});
return (<>
<Grid container spacing={3}>
<Grid item xs={12}>
<h1>Get Ready!</h1>
<h1>{x}</h1>
</Grid>
</Grid>
</>)
}

@ -0,0 +1,116 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
import { getArrow } from "../utils";
interface Difficulty {
[key: string]: number;
timer: number;
width: number;
height: number;
symbols: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 12500, width: 3, height: 3, symbols: 6},
Normal: {timer: 15000, width: 4, height: 4, symbols: 7},
Hard: {timer: 12500, width: 5, height: 5, symbols: 8},
Impossible: {timer: 10000, width: 6, height: 6, symbols: 9},
}
export function Cyberpunk2077Game(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, width: 0, height: 0, symbols: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [grid] = useState(generatePuzzle(difficulty));
const [answer] = useState(generateAnswer(grid, difficulty));
const [index, setIndex] = useState(0);
const [pos, setPos] = useState([0, 0]);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const move = [0, 0];
const arrow = getArrow(event);
switch(arrow) {
case "↑":
move[1]--;
break;
case "←":
move[0]--;
break;
case "↓":
move[1]++;
break;
case "→":
move[0]++;
break;
}
const next = [pos[0]+move[0], pos[1]+move[1]];
next[0] = (next[0]+grid[0].length)%grid[0].length;
next[1] = (next[1]+grid.length)%grid.length;
setPos(next);
if(event.keyCode == 32) {
const selected = grid[pos[1]][pos[0]];
const expected = answer[index];
if(selected !== expected) {
props.onFailure();
return;
}
setIndex(index+1);
if(answer.length === index+1) props.onSuccess();
}
}
const fontSize = "2em";
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Match the symbols!</h1>
<h2 style={{fontSize: fontSize}}>Targets: {answer.map((a, i) => {
if(i == index)
return <span key={`${i}`} style={{fontSize: "1em", color: 'blue'}}>{a}&nbsp;</span>
return <span key={`${i}`} style={{fontSize: "1em"}}>{a}&nbsp;</span>
})}</h2>
<br />
{grid.map((line, y) => <div key={y}><pre>{line.map((cell, x) => {
if(x == pos[0] && y == pos[1])
return <span key={`${x}${y}`} style={{fontSize: fontSize, color: 'blue'}}>{cell}&nbsp;</span>
return <span key={`${x}${y}`} style={{fontSize: fontSize}}>{cell}&nbsp;</span>
})}</pre><br /></div>)}
<KeyHandler onKeyDown={press} />
</Grid>
</Grid>)
}
function generateAnswer(grid: string[][], difficulty: Difficulty): string[] {
const answer = [];
for(let i = 0; i < Math.round(difficulty.symbols); i++) {
answer.push(grid[Math.floor(Math.random()*grid.length)][Math.floor(Math.random()*grid[0].length)]);
}
return answer;
}
function randChar(): string {
return "ABCDEF0123456789"[Math.floor(Math.random()*16)];
}
function generatePuzzle(difficulty: Difficulty): string[][] {
const puzzle = [];
for(let i = 0; i < Math.round(difficulty.height); i++) {
const line = [];
for(let j = 0; j < Math.round(difficulty.width); j++) {
line.push(randChar()+randChar());
}
puzzle.push(line);
}
return puzzle;
}

@ -0,0 +1,32 @@
interface DifficultySetting {
[key: string]: number;
}
interface DifficultySettings {
Trivial: DifficultySetting;
Normal: DifficultySetting;
Hard: DifficultySetting;
Impossible: DifficultySetting;
}
// I could use `any` to simply some of this but I also want to take advantage
// of the type safety that typescript provides. I'm just not sure how in this
// case.
export function interpolate(settings: DifficultySettings, n: number, out: DifficultySetting): DifficultySetting {
// interpolates between 2 difficulties.
function lerpD(a: DifficultySetting, b: DifficultySetting, t: number): DifficultySetting {
// interpolates between 2 numbers.
function lerp(x: number, y: number, t: number): number {
return (1 - t) * x + t * y;
}
for(const key of Object.keys(a)) {
out[key] = lerp(a[key], b[key], t);
}
return a;
}
if(n < 0) return lerpD(settings.Trivial, settings.Trivial, 0);
if(n >= 0 && n < 1) return lerpD(settings.Trivial, settings.Normal, n);
if(n >= 1 && n < 2) return lerpD(settings.Normal, settings.Hard, n-1);
if(n >= 2 && n < 3) return lerpD(settings.Hard, settings.Impossible, n-2);
return lerpD(settings.Impossible, settings.Impossible, 0);
}

@ -0,0 +1,132 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import { Countdown } from "./Countdown";
import { BracketGame } from "./BracketGame";
import { SlashGame } from "./SlashGame";
import { BackwardGame } from "./BackwardGame";
import { BribeGame } from "./BribeGame";
import { CheatCodeGame } from "./CheatCodeGame";
import { Cyberpunk2077Game } from "./Cyberpunk2077Game";
import { MinesweeperGame } from "./MinesweeperGame";
import { WireCuttingGame } from "./WireCuttingGame";
import { Victory } from "./Victory";
interface IProps {
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
}
enum Stage {
Countdown = 0,
Minigame,
Result,
Sell,
}
const minigames = [
SlashGame,
BracketGame,
BackwardGame,
BribeGame,
CheatCodeGame,
Cyberpunk2077Game,
MinesweeperGame,
WireCuttingGame,
]
export function Game(props: IProps): React.ReactElement {
const [level, setLevel] = useState(1);
const [stage, setStage] = useState(Stage.Countdown);
const [results, setResults] = useState('');
const [gameIds, setGameIds] = useState({
lastGames: [-1, -1],
id: Math.floor(Math.random()*minigames.length),
});
function nextGameId(): number {
let id = gameIds.lastGames[0];
const ids = [gameIds.lastGames[0], gameIds.lastGames[1], gameIds.id];
while(ids.includes(id)) {
id = Math.floor(Math.random()*minigames.length);
}
return id;
}
function setupNextGame(): void {
setGameIds({
lastGames: [gameIds.lastGames[1], gameIds.id],
id: nextGameId(),
})
}
function success(): void {
pushResult(true);
if(level === props.MaxLevel) {
setStage(Stage.Sell);
} else {
setStage(Stage.Countdown);
setLevel(level+1);
}
setupNextGame();
}
function pushResult(win: boolean): void {
setResults(old => {
let next = old;
next += win ? "✓" : "✗";
if(next.length > 15) next = next.slice(1);
return next;
})
}
function failure(): void {
setStage(Stage.Countdown);
pushResult(false);
if(props.Player.takeDamage(props.StartingDifficulty*3)) {
const menu = document.getElementById("mainmenu-container");
if(menu === null) throw new Error("mainmenu-container not found");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
}
setupNextGame();
}
let stageComponent: React.ReactNode;
switch(stage) {
case Stage.Countdown:
stageComponent = (<Countdown onFinish={() =>setStage(Stage.Minigame)} />);
break;
case Stage.Minigame: {
const MiniGame = minigames[gameIds.id];
stageComponent = (<MiniGame onSuccess={success} onFailure={failure} difficulty={props.Difficulty+level/50} />);
break;
}
case Stage.Sell:
stageComponent = (<Victory Player={props.Player} Engine={props.Engine} StartingDifficulty={props.StartingDifficulty} Difficulty={props.Difficulty} MaxLevel={props.MaxLevel} />);
break;
}
function Progress(): React.ReactElement {
return <h4><span style={{color: "gray"}}>{results.slice(0, results.length-1)}</span>{results[results.length-1]}</h4>
}
return (<>
<Grid container spacing={3}>
<Grid item xs={3}>
<h3>Level: {level}&nbsp;/&nbsp;{props.MaxLevel}</h3>
<Progress />
</Grid>
<Grid item xs={12}>
{stageComponent}
</Grid>
</Grid>
</>)
}

@ -0,0 +1,41 @@
import LinearProgress from '@material-ui/core/LinearProgress';
import React, { useState, useEffect } from 'react';
import { withStyles } from "@material-ui/core/styles";
import Grid from '@material-ui/core/Grid';
const TimerProgress = withStyles(() => ({
bar: {
transition: "none",
backgroundColor: "#adff2f",
},
}))(LinearProgress);
interface IProps {
millis: number;
onExpire: () => void;
}
export function GameTimer(props: IProps): React.ReactElement {
const [v, setV] = useState(100);
const tick = 200;
useEffect(() => {
const intervalId = setInterval(() => {
setV(old => {
if(old <= 0) props.onExpire();
return old - tick / props.millis * 100;
});
}, tick);
return () => {
clearInterval(intervalId);
}
}, []);
// https://stackoverflow.com/questions/55593367/disable-material-uis-linearprogress-animation
// TODO(hydroflame): there's like a bug where it triggers the end before the
// bar physically reaches the end
return (<Grid item xs={12}>
<TimerProgress variant="determinate" value={v} />
</Grid>);
}

@ -0,0 +1,5 @@
export interface IMinigameProps {
onSuccess: () => void;
onFailure: () => void;
difficulty: number;
}

@ -0,0 +1,58 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React from 'react';
import { StdButton } from "../../ui/React/StdButton";
import Grid from '@material-ui/core/Grid';
interface IProps {
Player: IPlayer;
Engine: IEngine;
Location: string;
Difficulty: number;
MaxLevel: number;
start: () => void;
cancel: () => void;
}
function diffStr(d: number): string {
if(d<=0.5) return "trivial";
if(d<=1.5) return "normal";
if(d<=2.5) return "hard";
return "impossible";
}
export function Intro(props: IProps): React.ReactElement {
return (<>
<Grid container spacing={3}>
<Grid item xs={10}>
<h1>Infiltrating {props.Location}</h1>
</Grid>
<Grid item xs={10}>
<h2>Difficulty: {diffStr(props.Difficulty)}, Maximum level: {props.MaxLevel}</h2>
</Grid>
<Grid item xs={10}>
<p>Infiltration is a series of short minigames that get
progressively harder. You take damage for failing them. Reaching
the maximum level rewards you with intel you can trade for money
or reputation.</p>
<br />
<p>The minigames you play are randomly selected. It might take you
few tries to get used to them.</p>
<br />
<p>No game require use of the mouse.</p>
<br />
<p>Spacebar is the default action/confirm button.</p>
<br />
<p>Everything that uses arrow can also use WASD</p>
<br />
<p>Sometimes the rest of the keyboard is used.</p>
</Grid>
<Grid item xs={3}>
<StdButton onClick={props.start} text={"Start"} />
</Grid>
<Grid item xs={3}>
<StdButton onClick={props.cancel} text={"Cancel"} />
</Grid>
</Grid>
</>)
}

@ -0,0 +1,13 @@
import React, { useEffect } from 'react';
interface IProps {
onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;
}
export function KeyHandler(props: IProps): React.ReactElement {
let elem: any;
useEffect(() => elem.focus());
// invisible autofocused element that eats all the keypress for the minigames.
return (<div tabIndex={1} ref={c => elem = c} onKeyDown={props.onKeyDown} />)
}

@ -0,0 +1,129 @@
import React, { useState, useEffect } from 'react';
import Grid from '@material-ui/core/Grid';
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
import { getArrow } from "../utils";
interface Difficulty {
[key: string]: number;
timer: number;
width: number;
height: number;
mines: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 15000, width: 3, height: 3, mines: 4},
Normal: {timer: 15000, width: 4, height: 4, mines: 7},
Hard: {timer: 15000, width: 5, height: 5, mines: 11},
Impossible: {timer: 15000, width: 6, height: 6, mines: 15},
}
export function MinesweeperGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, width: 0, height: 0, mines: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [minefield] = useState(generateMinefield(difficulty));
const [answer, setAnswer] = useState(generateEmptyField(difficulty));
const [pos, setPos] = useState([0, 0]);
const [memoryPhase, setMemoryPhase] = useState(true);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if(memoryPhase) return;
const move = [0, 0];
const arrow = getArrow(event);
switch(arrow) {
case "↑":
move[1]--;
break;
case "←":
move[0]--;
break;
case "↓":
move[1]++;
break;
case "→":
move[0]++;
break;
}
const next = [pos[0]+move[0], pos[1]+move[1]];
next[0] = (next[0]+minefield[0].length)%minefield[0].length;
next[1] = (next[1]+minefield.length)%minefield.length;
setPos(next);
if(event.keyCode == 32) {
if(!minefield[pos[1]][pos[0]]) {
props.onFailure();
return;
}
setAnswer(old => {
old[pos[1]][pos[0]] = true;
if(fieldEquals(minefield, old)) props.onSuccess();
return old;
});
}
}
useEffect(() => {
const id = setTimeout(() => setMemoryPhase(false), 2000);
return () => clearInterval(id);
}, []);
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>{memoryPhase?"Remember all the mines!": "Mark all the mines!"}</h1>
{minefield.map((line, y) => <div key={y}><pre>{line.map((cell, x) => {
if(memoryPhase) {
if(minefield[y][x])
return <span key={x}>[?]&nbsp;</span>
return <span key={x}>[&nbsp;]&nbsp;</span>
} else {
if(x == pos[0] && y == pos[1])
return <span key={x}>[X]&nbsp;</span>
if(answer[y][x])
return <span key={x}>[.]&nbsp;</span>
return <span key={x}>[&nbsp;]&nbsp;</span>
}
})}</pre><br /></div>)}
<KeyHandler onKeyDown={press} />
</Grid>
</Grid>)
}
function fieldEquals(a: boolean[][], b: boolean[][]): boolean {
function count(field: boolean[][]): number {
return field.flat().reduce((a, b) => a + (b?1:0), 0);
}
return count(a) === count(b);
}
function generateEmptyField(difficulty: Difficulty): boolean[][] {
const field = [];
for(let i = 0; i < difficulty.height; i++) {
field.push((new Array(Math.round(difficulty.width))).fill(false));
}
return field;
}
function generateMinefield(difficulty: Difficulty): boolean[][] {
const field = generateEmptyField(difficulty);
for(let i = 0; i < difficulty.mines; i++) {
const x = Math.floor(Math.random()*field.length);
const y = Math.floor(Math.random()*field[0].length);
if (field[x][y]) {
i--;
continue;
}
field[x][y] = true;
}
return field;
}

@ -0,0 +1,45 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import React, { useState } from 'react';
import { Intro } from "./Intro";
import { Game } from "./Game";
interface IProps {
Player: IPlayer;
Engine: IEngine;
Location: string;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
}
export function Root(props: IProps): React.ReactElement {
const [start, setStart] = useState(false);
function cancel(): void {
const menu = document.getElementById("mainmenu-container");
if(menu === null) throw new Error("mainmenu-container not found");
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
}
if(!start) {
return (<Intro
Player={props.Player}
Engine={props.Engine}
Location={props.Location}
Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel}
start={() => setStart(true)}
cancel={cancel}
/>)
}
return (<Game
Player={props.Player}
Engine={props.Engine}
StartingDifficulty={props.StartingDifficulty}
Difficulty={props.Difficulty}
MaxLevel={props.MaxLevel}
/>);
}

@ -0,0 +1,60 @@
import React, { useState, useEffect } from 'react';
import Grid from '@material-ui/core/Grid';
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { interpolate } from "./Difficulty";
interface Difficulty {
[key: string]: number;
window: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {window: 600},
Normal: {window: 325},
Hard: {window: 250},
Impossible: {window: 150},
}
export function SlashGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {window: 0};
interpolate(difficulties, props.difficulty, difficulty);
const [guarding, setGuarding] = useState(true);
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
if(event.keyCode !== 32) return;
if(guarding) {
props.onFailure();
} else {
props.onSuccess();
}
}
useEffect(() => {
let id2 = -1;
const id = setTimeout(() => {
setGuarding(false);
id2 = setTimeout(()=>setGuarding(true), difficulty.window)
}, Math.random()*3250+1500);
return () => {
clearInterval(id);
if(id2 !== -1) clearInterval(id2);
}
}, []);
return (<Grid container spacing={3}>
<GameTimer millis={5000} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Slash when his guard is down!</h1>
<p style={{fontSize: '5em'}}>{guarding ? "!Guarding!" : "!ATTACKING!"}</p>
<KeyHandler onKeyDown={press} />
</Grid>
</Grid>)
}

@ -0,0 +1,77 @@
import { IPlayer } from "../../PersonObjects/IPlayer";
import { IEngine } from "../../IEngine";
import { Factions } from "../../Faction/Factions";
import React, { useState } from 'react';
import { StdButton } from "../../ui/React/StdButton";
import Grid from '@material-ui/core/Grid';
import { Money } from "../../ui/React/Money";
import { Reputation } from "../../ui/React/Reputation";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
interface IProps {
Player: IPlayer;
Engine: IEngine;
StartingDifficulty: number;
Difficulty: number;
MaxLevel: number;
}
export function Victory(props: IProps): React.ReactElement {
const [faction, setFaction] = useState('none');
function quitInfiltration(): void {
const menu = document.getElementById("mainmenu-container");
if(!menu) throw new Error('mainmenu-container somehow null');
menu.style.visibility = "visible";
props.Engine.loadLocationContent();
}
const levelBonus = props.MaxLevel*Math.pow(1.01, props.MaxLevel);
const repGain = Math.pow(props.Difficulty+1, 1.1)*
Math.pow(props.StartingDifficulty, 1.2)*
30*levelBonus*BitNodeMultipliers.InfiltrationRep;
const moneyGain = Math.pow(props.Difficulty+1, 2)*
Math.pow(props.StartingDifficulty, 3)*
3e3*levelBonus*BitNodeMultipliers.InfiltrationMoney;
function sell(): void {
props.Player.gainMoney(moneyGain);
props.Player.recordMoneySource(moneyGain, 'infiltration');
quitInfiltration();
}
function trade(): void {
if(faction === 'none') return;
Factions[faction].playerReputation += repGain;
quitInfiltration();
}
function changeDropdown(event: React.ChangeEvent<HTMLSelectElement>): void {
setFaction(event.target.value);
}
return (<>
<Grid container spacing={3}>
<Grid item xs={10}>
<h1>Infiltration successful!</h1>
</Grid>
<Grid item xs={10}>
<h2>You can trade the confidential information you found for money or reputation.</h2>
<select className={"dropdown"} onChange={changeDropdown}>
<option key={'none'} value={'none'}>{'none'}</option>
{props.Player.factions.filter(f => Factions[f].getInfo().offersWork()).map(f => <option key={f} value={f}>{f}</option>)}
</select>
<StdButton onClick={trade} text={<>{"Trade for "}{Reputation(repGain)}{" reputation"}</>} />
</Grid>
<Grid item xs={3}>
<StdButton onClick={sell} text={<>{"Sell for "}{Money(moneyGain)}</>} />
</Grid>
<Grid item xs={3}>
<StdButton onClick={quitInfiltration} text={"Quit"} />
</Grid>
</Grid>
</>)
}

@ -0,0 +1,171 @@
import React, { useState } from 'react';
import Grid from '@material-ui/core/Grid';
import { IMinigameProps } from "./IMinigameProps";
import { KeyHandler } from "./KeyHandler";
import { GameTimer } from "./GameTimer";
import { random } from "../utils";
import { interpolate } from "./Difficulty";
interface Difficulty {
[key: string]: number;
timer: number;
wiresmin: number;
wiresmax: number;
rules: number;
}
const difficulties: {
Trivial: Difficulty;
Normal: Difficulty;
Hard: Difficulty;
Impossible: Difficulty;
} = {
Trivial: {timer: 9000, wiresmin: 4, wiresmax: 4, rules: 2},
Normal: {timer: 7000, wiresmin: 6, wiresmax: 6, rules: 2},
Hard: {timer: 5000, wiresmin: 8, wiresmax: 8, rules: 3},
Impossible: {timer: 4000, wiresmin: 9, wiresmax: 9, rules: 4},
}
const types = [
"|",
".",
"/",
"-",
"█",
"#",
]
const colors = [
"red",
"#FFC107",
"blue",
"white",
]
const colorNames: any = {
"red": "red",
"#FFC107": "yellow",
"blue": "blue",
"white": "white",
}
interface Wire {
tpe: string;
colors: string[];
}
interface Question {
toString: () => string;
shouldCut: (wire: Wire, index: number) => boolean;
}
export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
const difficulty: Difficulty = {timer: 0, wiresmin: 0, wiresmax: 0, rules: 0};
interpolate(difficulties, props.difficulty, difficulty);
const timer = difficulty.timer;
const [wires] = useState(generateWires(difficulty));
const [cutWires, setCutWires] = useState((new Array(wires.length)).fill(false));
const [questions] = useState(generateQuestion(wires, difficulty));
function press(event: React.KeyboardEvent<HTMLElement>): void {
event.preventDefault();
const wireNum = parseInt(event.key);
if(wireNum < 1 || wireNum > wires.length || isNaN(wireNum)) return;
setCutWires(old => {
const next = [...old];
next[wireNum-1] = true;
if(!questions.some((q => q.shouldCut(wires[wireNum-1], wireNum-1)))) {
props.onFailure();
}
// check if we won
const wiresToBeCut = [];
for(let j = 0; j < wires.length; j++) {
let shouldBeCut = false;
for(let i = 0; i < questions.length; i++) {
shouldBeCut = shouldBeCut || questions[i].shouldCut(wires[j], j)
}
wiresToBeCut.push(shouldBeCut);
}
if(wiresToBeCut.every((b, i) => b === next[i])) {
props.onSuccess();
}
return next;
});
}
return (<Grid container spacing={3}>
<GameTimer millis={timer} onExpire={props.onFailure} />
<Grid item xs={12}>
<h1 className={"noselect"}>Cut the wires with the following properties!</h1>
{questions.map((question, i) => <h3 key={i}>{question.toString()}</h3>)}
<pre>{(new Array(wires.length)).fill(0).map((_, i) => <span key={i}>&nbsp;{i+1}&nbsp;&nbsp;&nbsp;&nbsp;</span>)}</pre>
{(new Array(8)).fill(0).map((_, i) => <div key={i}>
<pre>
{wires.map((wire, j) => {
if((i === 3 || i === 4) && cutWires[j]) return <span key={j}>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>;
return <span key={j} style={{color: wire.colors[i%wire.colors.length]}}>|{wire.tpe}|&nbsp;&nbsp;&nbsp;</span>
})}
</pre>
</div>)}
<KeyHandler onKeyDown={press} />
</Grid>
</Grid>)
}
function randomPositionQuestion(wires: Wire[]): Question {
const index = Math.floor(Math.random() * wires.length);
return {
toString: (): string => {
return `Cut wires number ${index+1}.`;
},
shouldCut: (wire: Wire, i: number): boolean => {
return index === i;
},
}
}
function randomColorQuestion(wires: Wire[]): Question {
const index = Math.floor(Math.random() * wires.length);
const cutColor = wires[index].colors[0];
return {
toString: (): string => {
return `Cut all wires colored ${colorNames[cutColor]}.`;
},
shouldCut: (wire: Wire): boolean => {
return wire.colors.includes(cutColor);
},
}
}
function generateQuestion(wires: Wire[], difficulty: Difficulty): Question[] {
const numQuestions = difficulty.rules;
const questionGenerators = [
randomPositionQuestion,
randomColorQuestion,
]
const questions = [];
for(let i = 0; i < numQuestions; i++) {
questions.push(questionGenerators[i%2](wires));
}
return questions;
}
function generateWires(difficulty: Difficulty): Wire[] {
const wires = [];
const numWires = random(difficulty.wiresmin, difficulty.wiresmax);
for(let i = 0; i < numWires; i++) {
const wireColors = [colors[Math.floor(Math.random()*colors.length)]];
if(Math.random() < 0.15) {
wireColors.push(colors[Math.floor(Math.random()*colors.length)]);
}
wires.push({
tpe: types[Math.floor(Math.random()*types.length)],
colors: wireColors,
});
}
return wires;
}

23
src/Infiltration/utils.ts Normal file

@ -0,0 +1,23 @@
import React from 'react';
export function random(min: number, max: number): number {
return Math.random()*(max-min)+min;
}
export function getArrow(event: React.KeyboardEvent<HTMLElement>): string {
switch(event.keyCode) {
case 38:
case 87:
return "↑";
case 65:
case 37:
return "←";
case 40:
case 83:
return "↓";
case 39:
case 68:
return "→";
}
return '';
}

@ -11,7 +11,6 @@ import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
// Ordered array of keys to Interactive Tutorial Steps
const orderedITutorialSteps = [
"Start",
@ -24,10 +23,10 @@ const orderedITutorialSteps = [
"TerminalScan", // Using 'scan' Terminal command
"TerminalScanAnalyze1", // Using 'scan-analyze' Terminal command
"TerminalScanAnalyze2", // Using 'scan-analyze 3' Terminal command
"TerminalConnect", // Connecting to foodnstuff
"TerminalAnalyze", // Analyzing foodnstuff
"TerminalNuke", // NUKE foodnstuff
"TerminalManualHack", // Hack foodnstuff
"TerminalConnect", // Connecting to n00dles
"TerminalAnalyze", // Analyzing n00dles
"TerminalNuke", // NUKE n00dles
"TerminalManualHack", // Hack n00dles
"TerminalHackingMechanics", // Explanation of hacking mechanics
"TerminalCreateScript", // Create a script using 'nano'
"TerminalTypeScript", // Script Editor page - Type script and then save & close
@ -51,7 +50,7 @@ for (let i = 0; i < orderedITutorialSteps.length; ++i) {
iTutorialSteps[orderedITutorialSteps[i]] = i;
}
var ITutorial = {
const ITutorial = {
currStep: 0, // iTutorialSteps.Start
isRunning: false,
@ -76,21 +75,21 @@ function iTutorialStart() {
document.getElementById("interactive-tutorial-container").style.display = "block";
// Exit tutorial button
var exitButton = clearEventListeners("interactive-tutorial-exit");
const exitButton = clearEventListeners("interactive-tutorial-exit");
exitButton.addEventListener("click", function() {
iTutorialEnd();
return false;
});
// Back button
var backButton = clearEventListeners("interactive-tutorial-back");
const backButton = clearEventListeners("interactive-tutorial-back");
backButton.addEventListener("click", function() {
iTutorialPrevStep();
return false;
});
// Next button
var nextButton = clearEventListeners("interactive-tutorial-next");
const nextButton = clearEventListeners("interactive-tutorial-next");
nextButton.addEventListener("click", function() {
iTutorialNextStep();
return false;
@ -103,12 +102,12 @@ function iTutorialEvaluateStep() {
if (!ITutorial.isRunning) {return;}
// Disable and clear main menu
var terminalMainMenu = clearEventListeners("terminal-menu-link");
var statsMainMenu = clearEventListeners("stats-menu-link");
var activeScriptsMainMenu = clearEventListeners("active-scripts-menu-link");
var hacknetMainMenu = clearEventListeners("hacknet-nodes-menu-link");
var cityMainMenu = clearEventListeners("city-menu-link");
var tutorialMainMenu = clearEventListeners("tutorial-menu-link");
const terminalMainMenu = clearEventListeners("terminal-menu-link");
const statsMainMenu = clearEventListeners("stats-menu-link");
const activeScriptsMainMenu = clearEventListeners("active-scripts-menu-link");
const hacknetMainMenu = clearEventListeners("hacknet-nodes-menu-link");
const cityMainMenu = clearEventListeners("city-menu-link");
const tutorialMainMenu = clearEventListeners("tutorial-menu-link");
terminalMainMenu.removeAttribute("class");
statsMainMenu.removeAttribute("class");
activeScriptsMainMenu.removeAttribute("class");
@ -117,20 +116,20 @@ function iTutorialEvaluateStep() {
tutorialMainMenu.removeAttribute("class");
// Interactive Tutorial Next button
var nextBtn = document.getElementById("interactive-tutorial-next");
const nextBtn = document.getElementById("interactive-tutorial-next");
switch(ITutorial.currStep) {
case iTutorialSteps.Start:
Engine.loadTerminalContent();
iTutorialSetText("Welcome to Bitburner, a cyberpunk-themed incremental RPG! " +
"The game takes place in a dark, dystopian future...The year is 2077...<br><br>" +
"The game takes place in a dark, dystopian future... The year is 2077...<br><br>" +
"This tutorial will show you the basics of the game. " +
"You may skip the tutorial at any time.");
nextBtn.style.display = "inline-block";
break;
case iTutorialSteps.GoToCharacterPage:
Engine.loadTerminalContent();
iTutorialSetText("Let's start by heading to the Stats page. Click the 'Stats' tab on " +
iTutorialSetText("Let's start by heading to the Stats page. Click the <code class='interactive-tutorial-tab flashing-button'>Stats</code> tab on " +
"the main navigation menu (left-hand side of the screen)");
nextBtn.style.display = "none";
@ -144,13 +143,13 @@ function iTutorialEvaluateStep() {
break;
case iTutorialSteps.CharacterPage:
Engine.loadCharacterContent();
iTutorialSetText("The Stats page shows a lot of important information about your progress, " +
"such as your skills, money, and bonuses/multipliers. ")
iTutorialSetText("The <code class='interactive-tutorial-tab'>Stats</code> page shows a lot of important information about your progress, " +
"such as your skills, money, and bonuses. ")
nextBtn.style.display = "inline-block";
break;
case iTutorialSteps.CharacterGoToTerminalPage:
Engine.loadCharacterContent();
iTutorialSetText("Let's head to your computer's terminal by clicking the 'Terminal' tab on the " +
iTutorialSetText("Let's head to your computer's terminal by clicking the <code class='interactive-tutorial-tab flashing-button'>Terminal</code> tab on the " +
"main navigation menu.");
nextBtn.style.display = "none";
@ -164,57 +163,56 @@ function iTutorialEvaluateStep() {
break;
case iTutorialSteps.TerminalIntro:
Engine.loadTerminalContent();
iTutorialSetText("The Terminal is used to interface with your home computer as well as " +
iTutorialSetText("The <code class='interactive-tutorial-tab'>Terminal</code> is used to interface with your home computer as well as " +
"all of the other machines around the world.");
nextBtn.style.display = "inline-block";
break;
case iTutorialSteps.TerminalHelp:
Engine.loadTerminalContent();
iTutorialSetText("Let's try it out. Start by entering the 'help' command into the Terminal " +
iTutorialSetText("Let's try it out. Start by entering the <code class='interactive-tutorial-command'>help</code> command into the <code class='interactive-tutorial-tab'>Terminal</code> " +
"(Don't forget to press Enter after typing the command)");
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalLs:
Engine.loadTerminalContent();
iTutorialSetText("The 'help' command displays a list of all available Terminal commands, how to use them, " +
"and a description of what they do. <br><br>Let's try another command. Enter the 'ls' command");
iTutorialSetText("The <code class='interactive-tutorial-command'>help</code> command displays a list of all available <code class='interactive-tutorial-tab'>Terminal</code> commands, how to use them, " +
"and a description of what they do. <br><br>Let's try another command. Enter the <code class='interactive-tutorial-command'>ls</code> command.");
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScan:
Engine.loadTerminalContent();
iTutorialSetText("'ls' is a basic command that shows all of the contents (programs/scripts) " +
"on the computer. Right now, it shows that you have a program called 'NUKE.exe' on your computer. " +
iTutorialSetText(" <code class='interactive-tutorial-command'>ls</code> is a basic command that shows files " +
"on the computer. Right now, it shows that you have a program called <code class='interactive-tutorial-command'>NUKE.exe</code> on your computer. " +
"We'll get to what this does later. <br><br>Using your home computer's terminal, you can connect " +
"to other machines throughout the world. Let's do that now by first entering " +
"the 'scan' command.");
"the <code class='interactive-tutorial-command'>scan</code> command.");
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScanAnalyze1:
Engine.loadTerminalContent();
iTutorialSetText("The 'scan' command shows all available network connections. In other words, " +
iTutorialSetText("The <code class='interactive-tutorial-command'>scan</code> command shows all available network connections. In other words, " +
"it displays a list of all servers that can be connected to from your " +
"current machine. A server is identified by either its IP or its hostname. <br><br> " +
"current machine. A server is identified by its hostname. <br><br> " +
"That's great and all, but there's so many servers. Which one should you go to? " +
"The 'scan-analyze' command gives some more detailed information about servers on the " +
"network. Try it now");
"The <code class='interactive-tutorial-command'>scan-analyze</code> command gives some more detailed information about servers on the " +
"network. Try it now!");
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalScanAnalyze2:
Engine.loadTerminalContent();
iTutorialSetText("You just ran 'scan-analyze' with a depth of one. This command shows more detailed " +
iTutorialSetText("You just ran <code class='interactive-tutorial-command'>scan-analyze</code> with a depth of one. This command shows more detailed " +
"information about each server that you can connect to (servers that are a distance of " +
"one node away). <br><br> It is also possible to run 'scan-analyze' with " +
"a higher depth. Let's try a depth of two with the following command: 'scan-analyze 2'.")
"one node away). <br><br> It is also possible to run <code class='interactive-tutorial-command'>scan-analyze</code> with " +
"a higher depth. Let's try a depth of two with the following command: <code class='interactive-tutorial-command'>scan-analyze 2</code>.")
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalConnect:
Engine.loadTerminalContent();
iTutorialSetText("Now you can see information about all servers that are up to two nodes away, as well " +
"as figure out how to navigate to those servers through the network. You can only connect to " +
"a server that is one node away. To connect to a machine, use the 'connect [ip/hostname]' command. You can type in " +
"the ip or the hostname, but dont use both.<br><br>" +
"From the results of the 'scan-analyze' command, we can see that the 'foodnstuff' server is " +
"only one node away. Let's connect so it now using: 'connect foodnstuff'");
"a server that is one node away. To connect to a machine, use the <code class='interactive-tutorial-command'>connect [hostname]</code> command.<br><br>" +
"From the results of the <code class='interactive-tutorial-command'>scan-analyze</code> command, we can see that the <code class='interactive-tutorial-command'>n00dles</code> server is " +
"only one node away. Let's connect so it now using: <code class='interactive-tutorial-command'>connect n00dles</code>");
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalAnalyze:
@ -223,30 +221,30 @@ function iTutorialEvaluateStep() {
"become digital and decentralized. People and corporations store their money " +
"on servers and computers. Using your hacking abilities, you can hack servers " +
"to steal money and gain experience. <br><br> " +
"Before you try to hack a server, you should run diagnostics using the 'analyze' command");
"Before you try to hack a server, you should run diagnostics using the <code class='interactive-tutorial-command'>analyze</code> command.");
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalNuke:
Engine.loadTerminalContent();
iTutorialSetText("When the 'analyze' command finishes running it will show useful information " +
"about hacking the server. <br><br> For this server, the required hacking skill is only 1, " +
iTutorialSetText("When the <code class='interactive-tutorial-command'>analyze</code> command finishes running it will show useful information " +
"about hacking the server. <br><br> For this server, the required hacking skill is only <span class='character-hack-cell'>1</span>, " +
"which means you can hack it right now. However, in order to hack a server " +
"you must first gain root access. The 'NUKE.exe' program that we saw earlier on your " +
"you must first gain root access. The <code class='interactive-tutorial-command'>NUKE.exe</code> program that we saw earlier on your " +
"home computer is a virus that will grant you root access to a machine if there are enough " +
"open ports.<br><br> The 'analyze' results shows that there do not need to be any open ports " +
"open ports.<br><br> The <code class='interactive-tutorial-command'>analyze</code> results shows that there do not need to be any open ports " +
"on this machine for the NUKE virus to work, so go ahead and run the virus using the " +
"'run NUKE.exe' command.");
"<code class='interactive-tutorial-command'>run NUKE.exe</code> command.");
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalManualHack:
Engine.loadTerminalContent();
iTutorialSetText("You now have root access! You can hack the server using the 'hack' command. " +
iTutorialSetText("You now have root access! You can hack the server using the <code class='interactive-tutorial-command'>hack</code> command. " +
"Try doing that now.");
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalHackingMechanics:
Engine.loadTerminalContent();
iTutorialSetText("You are now attempting to hack the server. Note that performing a hack takes time and " +
iTutorialSetText("You are now attempting to hack the server. Performing a hack takes time and " +
"only has a certain percentage chance " +
"of success. This time and success chance is determined by a variety of factors, including " +
"your hacking skill and the server's security level.<br><br>" +
@ -261,25 +259,22 @@ function iTutorialEvaluateStep() {
Engine.loadTerminalContent();
iTutorialSetText("Hacking is the core mechanic of the game and is necessary for progressing. However, " +
"you don't want to be hacking manually the entire time. You can automate your hacking " +
"by writing scripts!<br><br>To create a new script or edit an existing one, you can use the 'nano' " +
"command. Scripts must end with the '.script' extension. Let's make a script now by " +
"entering 'nano foodnstuff.script' after the hack command finishes running (Sidenote: Pressing ctrl + c" +
"by writing scripts!<br><br>To create a new script or edit an existing one, you can use the <code class='interactive-tutorial-command'>nano</code> " +
"command. Scripts must end with the <code class='interactive-tutorial-command'>.script</code> extension. Let's make a script now by " +
"entering <code class='interactive-tutorial-command'>nano n00dles.script</code> after the hack command finishes running (Sidenote: Pressing ctrl + c" +
" will end a command like hack early)");
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalTypeScript:
Engine.loadScriptEditorContent("foodnstuff.script", "");
Engine.loadScriptEditorContent("n00dles.script", "");
iTutorialSetText("This is the script editor. You can use it to program your scripts. Scripts are " +
"written in the Netscript language, a programming language created for " +
"this game. <strong style='background-color:#444;'>There are details about the Netscript language in the documentation, which " +
"can be accessed in the 'Tutorial' tab on the main navigation menu. I highly suggest you check " +
"it out after this tutorial.</strong> For now, just copy " +
"and paste the following code into the script editor: <br><br>" +
"while(true) { <br>" +
"&nbsp;&nbsp;hack('foodnstuff'); <br>" +
"}<br><br> " +
"written in a simplified version of javascript. Copy and paste the following code into the script editor: <br><br>" +
"<pre class='interactive-tutorial-code'>" +
"while(true) {\n" +
" hack('n00dles');\n" +
"}</pre>" +
"For anyone with basic programming experience, this code should be straightforward. " +
"This script will continuously hack the 'foodnstuff' server.<br><br>" +
"This script will continuously hack the <code class='interactive-tutorial-command'>n00dles</code> server.<br><br>" +
"To save and close the script editor, press the button in the bottom left, or press ctrl + b.");
nextBtn.style.display = "none"; // next step triggered in saveAndCloseScriptEditor() (Script.js)
break;
@ -288,25 +283,25 @@ function iTutorialEvaluateStep() {
iTutorialSetText("Now we'll run the script. Scripts require a certain amount of RAM to run, and can be " +
"run on any machine which you have root access to. Different servers have different " +
"amounts of RAM. You can also purchase more RAM for your home server.<br><br>To check how much " +
"RAM is available on this machine, enter the 'free' command.");
"RAM is available on this machine, enter the <code class='interactive-tutorial-command'>free</code> command.");
nextBtn.style.display = "none"; // next step triggered by terminal commmand
break;
case iTutorialSteps.TerminalRunScript:
Engine.loadTerminalContent();
iTutorialSetText("We have 16GB of free RAM on this machine, which is enough to run our " +
"script. Let's run our script using 'run foodnstuff.script'.");
"script. Let's run our script using <code class='interactive-tutorial-command'>run n00dles.script</code>.");
nextBtn.style.display = "none"; // next step triggered by terminal commmand
break;
case iTutorialSteps.TerminalGoToActiveScriptsPage:
Engine.loadTerminalContent();
iTutorialSetText("Your script is now running! The script might take a few seconds to 'fully start up'. " +
"Your scripts will continuously run in the background and will automatically stop if " +
"the code ever completes (the 'foodnstuff.script' will never complete because it " +
iTutorialSetText("Your script is now running! " +
"It will continuously run in the background and will automatically stop if " +
"the code ever completes (the <code class='interactive-tutorial-command'>n00dles.script</code> will never complete because it " +
"runs an infinite loop). <br><br>These scripts can passively earn you income and hacking experience. " +
"Your scripts will also earn money and experience while you are offline, although at a " +
"much slower rate. <br><br> " +
"slightly slower rate. <br><br> " +
"Let's check out some statistics for our running scripts by clicking the " +
"'Active Scripts' link in the main navigation menu.");
"<code class='interactive-tutorial-tab flashing-button'>Active Scripts</code> link in the main navigation menu.");
nextBtn.style.display = "none";
// Flash 'Active Scripts' menu and set its tutorial click handler
@ -319,10 +314,9 @@ function iTutorialEvaluateStep() {
break;
case iTutorialSteps.ActiveScriptsPage:
Engine.loadActiveScriptsContent();
iTutorialSetText("This page displays stats/information about all of your scripts that are " +
"running across every existing server. You can use this to gauge how well " +
"your scripts are doing. Let's go back to the Terminal now using the 'Terminal' " +
"link.");
iTutorialSetText("This page displays information about all of your scripts that are " +
"running across every server. You can use this to gauge how well " +
"your scripts are doing. Let's go back to the <code class='interactive-tutorial-tab flashing-button'>Terminal</code>");
nextBtn.style.display = "none";
// Flash 'Terminal' button and set its tutorial click handler
@ -336,27 +330,27 @@ function iTutorialEvaluateStep() {
case iTutorialSteps.ActiveScriptsToTerminal:
Engine.loadTerminalContent();
iTutorialSetText("One last thing about scripts, each active script contains logs that detail " +
"what it's doing. We can check these logs using the 'tail' command. Do that " +
"now for the script we just ran by typing 'tail foodnstuff.script'");
"what it's doing. We can check these logs using the <code class='interactive-tutorial-command'>tail</code> command. Do that " +
"now for the script we just ran by typing <code class='interactive-tutorial-command'>tail n00dles.script</code>");
nextBtn.style.display = "none"; // next step triggered by terminal command
break;
case iTutorialSteps.TerminalTailScript:
Engine.loadTerminalContent();
iTutorialSetText("The log for this script won't show much right now (it might show nothing at all) because it " +
"just started running...but check back again in a few minutes! <br><br>" +
"This pretty much covers the basics of hacking. To learn more about writing " +
"scripts using the Netscript language, select the 'Tutorial' link in the " +
"This covers the basics of hacking. To learn more about writing " +
"scripts, select the <code class='interactive-tutorial-tab'>Tutorial</code> link in the " +
"main navigation menu to look at the documentation. " +
"<strong style='background-color:#444;'>If you are an experienced JavaScript " +
"developer, I would highly suggest you check out the section on " +
"NetscriptJS/Netscript 2.0.</strong><br><br>For now, let's move on to something else!");
"NetscriptJS/Netscript 2.0, it's faster and more powerful.</strong><br><br>For now, let's move on to something else!");
nextBtn.style.display = "inline-block";
break;
case iTutorialSteps.GoToHacknetNodesPage:
Engine.loadTerminalContent();
iTutorialSetText("Hacking is not the only way to earn money. One other way to passively " +
"earn money is by purchasing and upgrading Hacknet Nodes. Let's go to " +
"the 'Hacknet Nodes' page through the main navigation menu now.");
"the <code class='interactive-tutorial-tab flashing-button'>Hacknet</code> page through the main navigation menu now.");
nextBtn.style.display = "none";
// Flash 'Hacknet' menu and set its tutorial click handler
@ -369,7 +363,7 @@ function iTutorialEvaluateStep() {
break;
case iTutorialSteps.HacknetNodesIntroduction:
Engine.loadHacknetNodesContent();
iTutorialSetText("From this page you can purchase new Hacknet Nodes and upgrade your " +
iTutorialSetText("here you can purchase new Hacknet Nodes and upgrade your " +
"existing ones. Let's purchase a new one now.");
nextBtn.style.display = "none"; // Next step triggered by purchaseHacknet() (HacknetNode.js)
break;
@ -379,7 +373,7 @@ function iTutorialEvaluateStep() {
"earn you money over time, both online and offline. When you get enough " +
" money, you can upgrade " +
"your newly-purchased Hacknet Node below.<br><br>" +
"Let's go to the 'City' page through the main navigation menu.");
"Let's go to the <code class='interactive-tutorial-tab flashing-button'>City</code> page through the main navigation menu.");
nextBtn.style.display = "none";
// Flash 'City' menu and set its tutorial click handler
@ -396,7 +390,7 @@ function iTutorialEvaluateStep() {
"travel to. Each location has something that you can do. " +
"There's a lot of content out in the world, make sure " +
"you explore and discover!<br><br>" +
"Lastly, click on the 'Tutorial' link in the main navigation menu.");
"Lastly, click on the <code class='interactive-tutorial-tab flashing-button'>Tutorial</code> link in the main navigation menu.");
nextBtn.style.display = "none";
// Flash 'Tutorial' menu and set its tutorial click handler
@ -493,8 +487,8 @@ function iTutorialEnd() {
document.getElementById("interactive-tutorial-container").style.display = "none";
// Create a popup with final introductory stuff
var popupId = "interactive-tutorial-ending-popup";
var txt = createElement("p", {
const popupId = "interactive-tutorial-ending-popup";
const txt = createElement("p", {
innerHTML:
"If you are new to the game, the following links may be useful for you!<br><br>" +
"<a class='a-link-button' href='https://bitburner.readthedocs.io/en/latest/guidesandtips/gettingstartedguideforbeginnerprogrammers.html' target='_blank'>Getting Started Guide</a>" +
@ -502,7 +496,7 @@ function iTutorialEnd() {
"The Beginner's Guide to Hacking was added to your home computer! It contains some tips/pointers for starting out with the game. " +
"To read it, go to Terminal and enter<br><br>cat " + LiteratureNames.HackersStartingHandbook,
});
var gotitBtn = createElement("a", {
const gotitBtn = createElement("a", {
class:"a-link-button", float:"right", padding:"6px", innerText:"Got it!",
clickListener:()=>{
removeElementById(popupId);
@ -513,9 +507,16 @@ function iTutorialEnd() {
Player.getHomeComputer().messages.push(LiteratureNames.HackersStartingHandbook);
}
let textBox = null;
(function() {
function set() {
textBox = document.getElementById("interactive-tutorial-text");
document.removeEventListener("DOMContentLoaded", set);
}
document.addEventListener("DOMContentLoaded", set);
})();
function iTutorialSetText(txt) {
var textBox = document.getElementById("interactive-tutorial-text");
if (textBox == null) {throw new Error("Could not find text box"); return;}
textBox.innerHTML = txt;
textBox.parentElement.scrollTop = 0; // this resets scroll position
}

@ -6,8 +6,6 @@ import { LocationName } from "./data/LocationNames";
import { LocationType } from "./LocationTypeEnum";
interface IInfiltrationMetadata {
baseRewardValue: number;
difficulty: number;
maxClearanceLevel: number;
startingSecurityLevel: number;
}

@ -11,10 +11,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 32,
difficulty: 4.4,
maxClearanceLevel: 50,
startingSecurityLevel: 1350,
maxClearanceLevel: 12,
startingSecurityLevel: 8.18,
},
name: LocationName.AevumAeroCorp,
types: [LocationType.Company],
@ -22,10 +20,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 42,
difficulty: 4.1,
maxClearanceLevel: 60,
startingSecurityLevel: 1350,
maxClearanceLevel: 15,
startingSecurityLevel: 8.19,
},
name: LocationName.AevumBachmanAndAssociates,
types: [LocationType.Company],
@ -33,10 +29,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 34,
difficulty: 3.6,
maxClearanceLevel: 75,
startingSecurityLevel: 1800,
maxClearanceLevel: 18,
startingSecurityLevel: 9.55,
},
name: LocationName.AevumClarkeIncorporated,
types: [LocationType.Company],
@ -51,10 +45,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 116,
difficulty: 6,
maxClearanceLevel: 150,
startingSecurityLevel: 4800,
maxClearanceLevel: 37,
startingSecurityLevel: 17.02,
},
name: LocationName.AevumECorp,
types: [LocationType.Company, LocationType.TechVendor],
@ -64,10 +56,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 96,
difficulty: 6.2,
maxClearanceLevel: 100,
startingSecurityLevel: 4140,
maxClearanceLevel: 25,
startingSecurityLevel: 15.54,
},
name: LocationName.AevumFulcrumTechnologies,
types: [LocationType.Company, LocationType.TechVendor],
@ -77,10 +67,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 30,
difficulty: 3.95,
maxClearanceLevel: 50,
startingSecurityLevel: 1260,
maxClearanceLevel: 12,
startingSecurityLevel: 7.89,
},
name: LocationName.AevumGalacticCybersystems,
types: [LocationType.Company],
@ -88,10 +76,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 10,
difficulty: 1.4,
maxClearanceLevel: 25,
startingSecurityLevel: 144,
maxClearanceLevel: 6,
startingSecurityLevel: 3.29,
},
name: LocationName.AevumNetLinkTechnologies,
types: [LocationType.Company, LocationType.TechVendor],
@ -101,10 +87,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 18,
difficulty: 2.2,
maxClearanceLevel: 25,
startingSecurityLevel: 565,
maxClearanceLevel: 6,
startingSecurityLevel: 5.35,
},
name: LocationName.AevumPolice,
types: [LocationType.Company],
@ -112,10 +96,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 16,
difficulty: 1.9,
maxClearanceLevel: 20,
startingSecurityLevel: 485,
maxClearanceLevel: 5,
startingSecurityLevel: 5.02,
},
name: LocationName.AevumRhoConstruction,
types: [LocationType.Company],
@ -137,10 +119,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Aevum,
infiltrationData: {
baseRewardValue: 20,
difficulty: 3,
maxClearanceLevel: 30,
startingSecurityLevel: 690,
maxClearanceLevel: 7,
startingSecurityLevel: 5.85,
},
name: LocationName.AevumWatchdogSecurity,
types: [LocationType.Company],
@ -153,10 +133,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Chongqing,
infiltrationData: {
baseRewardValue: 100,
difficulty: 6.1,
maxClearanceLevel: 100,
startingSecurityLevel: 4450,
maxClearanceLevel: 25,
startingSecurityLevel: 16.25,
},
name: LocationName.ChongqingKuaiGongInternational,
types: [LocationType.Company],
@ -164,10 +142,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Chongqing,
infiltrationData: {
baseRewardValue: 52,
difficulty: 6,
maxClearanceLevel: 75,
startingSecurityLevel: 2915,
maxClearanceLevel: 18,
startingSecurityLevel: 12.59,
},
name: LocationName.ChongqingSolarisSpaceSystems,
types: [LocationType.Company],
@ -175,10 +151,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Ishima,
infiltrationData: {
baseRewardValue: 20,
difficulty: 3.2,
maxClearanceLevel: 50,
startingSecurityLevel: 485,
maxClearanceLevel: 12,
startingSecurityLevel: 5.02,
},
name: LocationName.IshimaNovaMedical,
types: [LocationType.Company],
@ -186,10 +160,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Ishima,
infiltrationData: {
baseRewardValue: 10,
difficulty: 1.6,
maxClearanceLevel: 40,
startingSecurityLevel: 130,
maxClearanceLevel: 10,
startingSecurityLevel: 3.2,
},
name: LocationName.IshimaOmegaSoftware,
types: [LocationType.Company, LocationType.TechVendor],
@ -199,10 +171,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Ishima,
infiltrationData: {
baseRewardValue: 24,
difficulty: 4.1,
maxClearanceLevel: 100,
startingSecurityLevel: 570,
maxClearanceLevel: 25,
startingSecurityLevel: 5.38,
},
name: LocationName.IshimaStormTechnologies,
types: [LocationType.Company, LocationType.TechVendor],
@ -212,10 +182,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.NewTokyo,
infiltrationData: {
baseRewardValue: 28,
difficulty: 4,
maxClearanceLevel: 70,
startingSecurityLevel: 1050,
maxClearanceLevel: 17,
startingSecurityLevel: 7.18,
},
name: LocationName.NewTokyoDefComm,
types: [LocationType.Company],
@ -223,10 +191,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.NewTokyo,
infiltrationData: {
baseRewardValue: 24,
difficulty: 3.8,
maxClearanceLevel: 80,
startingSecurityLevel: 700,
maxClearanceLevel: 20,
startingSecurityLevel: 5.9,
},
name: LocationName.NewTokyoGlobalPharmaceuticals,
types: [LocationType.Company],
@ -239,10 +205,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.NewTokyo,
infiltrationData: {
baseRewardValue: 22,
difficulty: 3.5,
maxClearanceLevel: 100,
startingSecurityLevel: 605,
maxClearanceLevel: 25,
startingSecurityLevel: 5.52,
},
name: LocationName.NewTokyoVitaLife,
types: [LocationType.Company, LocationType.Special],
@ -250,10 +214,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 14,
difficulty: 2.25,
maxClearanceLevel: 40,
startingSecurityLevel: 200,
maxClearanceLevel: 10,
startingSecurityLevel: 3.62,
},
name: LocationName.Sector12AlphaEnterprises,
types: [LocationType.Company, LocationType.TechVendor],
@ -263,10 +225,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 46,
difficulty: 4.2,
maxClearanceLevel: 100,
startingSecurityLevel: 2160,
maxClearanceLevel: 25,
startingSecurityLevel: 10.59,
},
name: LocationName.Sector12BladeIndustries,
types: [LocationType.Company],
@ -279,10 +239,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 18,
difficulty: 2.5,
maxClearanceLevel: 60,
startingSecurityLevel: 405,
maxClearanceLevel: 15,
startingSecurityLevel: 4.66,
},
name: LocationName.Sector12CarmichaelSecurity,
types: [LocationType.Company],
@ -295,10 +253,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 24,
difficulty: 4.3,
maxClearanceLevel: 50,
startingSecurityLevel: 700,
maxClearanceLevel: 12,
startingSecurityLevel: 5.9,
},
name: LocationName.Sector12DeltaOne,
types: [LocationType.Company],
@ -311,10 +267,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 58,
difficulty: 7,
maxClearanceLevel: 100,
startingSecurityLevel: 1350,
maxClearanceLevel: 25,
startingSecurityLevel: 8.18,
},
name: LocationName.Sector12FourSigma,
types: [LocationType.Company],
@ -322,10 +276,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 32,
difficulty: 5.4,
maxClearanceLevel: 70,
startingSecurityLevel: 730,
maxClearanceLevel: 17,
startingSecurityLevel: 6.02,
},
name: LocationName.Sector12IcarusMicrosystems,
types: [LocationType.Company],
@ -340,10 +292,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 8,
difficulty: 1.8,
maxClearanceLevel: 20,
startingSecurityLevel: 120,
maxClearanceLevel: 5,
startingSecurityLevel: 3.13,
},
name: LocationName.Sector12JoesGuns,
types: [LocationType.Company],
@ -351,10 +301,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 114,
difficulty: 6.75,
maxClearanceLevel: 125,
startingSecurityLevel: 4500,
maxClearanceLevel: 31,
startingSecurityLevel: 16.36,
},
name: LocationName.Sector12MegaCorp,
types: [LocationType.Company],
@ -381,10 +329,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Sector12,
infiltrationData: {
baseRewardValue: 24,
difficulty: 4.3,
maxClearanceLevel: 50,
startingSecurityLevel: 700,
maxClearanceLevel: 12,
startingSecurityLevel: 5.9,
},
name: LocationName.Sector12UniversalEnergy,
types: [LocationType.Company],
@ -392,10 +338,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 12,
difficulty: 2.1,
maxClearanceLevel: 60,
startingSecurityLevel: 195,
maxClearanceLevel: 15,
startingSecurityLevel: 3.59,
},
name: LocationName.VolhavenCompuTek,
types: [LocationType.Company, LocationType.TechVendor],
@ -405,10 +349,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 28,
difficulty: 3,
maxClearanceLevel: 75,
startingSecurityLevel: 1080,
maxClearanceLevel: 18,
startingSecurityLevel: 7.28,
},
name: LocationName.VolhavenHeliosLabs,
types: [LocationType.Company],
@ -416,10 +358,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 14,
difficulty: 2,
maxClearanceLevel: 60,
startingSecurityLevel: 340,
maxClearanceLevel: 15,
startingSecurityLevel: 4.35,
},
name: LocationName.VolhavenLexoCorp,
types: [LocationType.Company],
@ -434,10 +374,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 56,
difficulty: 6.8,
maxClearanceLevel: 200,
startingSecurityLevel: 1460,
maxClearanceLevel: 50,
startingSecurityLevel: 8.53,
},
name: LocationName.VolhavenNWO,
types: [LocationType.Company],
@ -445,10 +383,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 44,
difficulty: 4.4,
maxClearanceLevel: 100,
startingSecurityLevel: 1215,
maxClearanceLevel: 25,
startingSecurityLevel: 7.74,
},
name: LocationName.VolhavenOmniTekIncorporated,
types: [LocationType.Company, LocationType.TechVendor],
@ -458,10 +394,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 28,
difficulty: 4.9,
maxClearanceLevel: 90,
startingSecurityLevel: 725,
maxClearanceLevel: 22,
startingSecurityLevel: 6,
},
name: LocationName.VolhavenOmniaCybersystems,
types: [LocationType.Company],
@ -469,10 +403,8 @@ export const LocationsMetadata: IConstructorParams[] = [
{
city: CityName.Volhaven,
infiltrationData: {
baseRewardValue: 18,
difficulty: 2.4,
maxClearanceLevel: 75,
startingSecurityLevel: 430,
maxClearanceLevel: 18,
startingSecurityLevel: 4.77,
},
name: LocationName.VolhavenSysCoreSecurities,
types: [LocationType.Company],
@ -505,3 +437,10 @@ export const LocationsMetadata: IConstructorParams[] = [
types: [LocationType.StockMarket],
},
];
(function(){
for(const loc of LocationsMetadata) {
if(!loc || !loc.infiltrationData) continue
console.log(loc.infiltrationData.startingSecurityLevel+2);
}
})();

@ -12,7 +12,6 @@ import { Locations } from "../Locations";
import { LocationName } from "../data/LocationNames";
import { IEngine } from "../../IEngine";
import { beginInfiltration } from "../../Infiltration";
import { Companies } from "../../Company/Companies";
import { Company } from "../../Company/Company";
@ -193,13 +192,15 @@ export class CompanyLocation extends React.Component<IProps, IState> {
startInfiltration(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { return; }
const loc = this.location;
if (!loc.infiltrationData) {
console.error(`trying to start infiltration at ${this.props.locName} but the infiltrationData is null`);
return;
}
this.props.engine.loadInfiltrationContent();
this.props.engine.loadInfiltrationContent(this.props.locName, loc.infiltrationData.startingSecurityLevel, loc.infiltrationData.maxClearanceLevel);
const data = loc.infiltrationData;
if (data == null) { return; }
this.props.p.singularityStopWork();
beginInfiltration(this.props.locName, data.startingSecurityLevel, data.baseRewardValue, data.maxClearanceLevel, data.difficulty);
}
work(e: React.MouseEvent<HTMLElement>): void {

@ -223,6 +223,8 @@ export const RamCosts: IMap<any> = {
// Gang API
gang : {
createGang: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
inGang: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
getMemberNames: () => RamCostConstants.ScriptGangApiBaseRamCost / 4,
getGangInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,
getOtherGangInformation: () => RamCostConstants.ScriptGangApiBaseRamCost / 2,

@ -376,7 +376,7 @@ function NetscriptFunctions(workerScript) {
const makeRuntimeErrorMsg = function(caller, msg) {
const stack = (new Error()).stack.split('\n').slice(1);
const scripts = workerScript.getServer().scripts;
let userstack = [];
const userstack = [];
for(const stackline of stack) {
let filename;
for(const script of scripts) {
@ -398,13 +398,12 @@ function NetscriptFunctions(workerScript) {
const lineMatch = line.match(lineRe);
const funcMatch = line.match(funcRe);
if(lineMatch && funcMatch) {
let func = funcMatch[1];
return {line: lineMatch[1], func: func};
return {line: lineMatch[1], func: funcMatch[1]};
}
return null;
}
let call = {line: "-1", func: "unknown"};
let chromeCall = parseChromeStackline(stackline);
const chromeCall = parseChromeStackline(stackline);
if (chromeCall) {
call = chromeCall;
}
@ -430,7 +429,8 @@ function NetscriptFunctions(workerScript) {
}
workerScript.log(caller, msg);
const rejectMsg = `${caller}: ${msg}<br><br>Stack:<br>${userstack.join('<br>')}`
let rejectMsg = `${caller}: ${msg}`
if(userstack.length !== 0) rejectMsg += `<br><br>Stack:<br>${userstack.join('<br>')}`;
return makeRuntimeRejectMsg(workerScript, rejectMsg);
}
@ -3603,6 +3603,30 @@ function NetscriptFunctions(workerScript) {
// Gang API
gang: {
createGang: function(faction) {
updateDynamicRam("createGang", getRamCost("gang", "createGang"));
// this list is copied from Faction/ui/Root.tsx
const GangNames = [
"Slum Snakes",
"Tetrads",
"The Syndicate",
"The Dark Army",
"Speakers for the Dead",
"NiteSec",
"The Black Hand",
];
if(!Player.canAccessGang() || !GangNames.includes(faction)) return false;
if (Player.inGang()) return false;
if(!Player.factions.includes(faction)) return false;
const isHacking = (faction === "NiteSec" || faction === "The Black Hand");
Player.startGang(faction, isHacking);
return true;
},
inGang: function() {
updateDynamicRam("inGang", getRamCost("gang", "inGang"));
return Player.inGang();
},
getMemberNames: function() {
updateDynamicRam("getMemberNames", getRamCost("gang", "getMemberNames"));
checkGangApiAccess("getMemberNames");
@ -4386,7 +4410,7 @@ function NetscriptFunctions(workerScript) {
},
constants: function() {
checkFormulasAccess("hacknetNodes.constants", 5);
return Object.assign({}, HacknetNodeConstants, HacknetServerConstants);
return Object.assign({}, HacknetNodeConstants);
},
},
hacknetServers: {

@ -176,6 +176,7 @@ export interface IPlayer {
startGang(facName: string, isHacking: boolean): void;
startWork(companyName: string): void;
startWorkPartTime(companyName: string): void;
takeDamage(amt: number): boolean;
travel(to: CityName): boolean;
giveExploit(exploit: Exploit): void;
queryStatFromString(str: string): number;

@ -394,6 +394,7 @@ export function loseMoney(money) {
console.error("NaN passed into Player.loseMoney()");
return;
}
if(this.money.eq(Infinity) && money === Infinity) return;
this.money = this.money.minus(money);
}
@ -660,7 +661,7 @@ export function work(numCycles) {
const penalty = this.cancelationPenalty();
const penaltyString = penalty === 0.5 ? 'half' : 'three quarter'
const penaltyString = penalty === 0.5 ? 'half' : 'three-quarters'
var elem = document.getElementById("work-in-progress-text");
ReactDOM.render(<>
@ -1740,7 +1741,6 @@ export function applyForJob(entryPosType, sing=false) {
}
}
this.companyName = company.name;
this.jobs[company.name] = pos.name;
document.getElementById("world-menu-header").click();
@ -1863,7 +1863,6 @@ export function applyForEmployeeJob(sing=false) {
export function applyForPartTimeEmployeeJob(sing=false) {
var company = Companies[this.location]; //Company being applied to
if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[1]])) {
this.companyName = company.name;
this.jobs[company.name] = posNames.PartTimeCompanyPositions[1];
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();

@ -561,7 +561,7 @@ function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSlee
elems.taskDetailsSelector.add(createOptionElement(gymSelectorOptions[i]));
// Set initial value
if (sleeve.gymStatType === gymSelectorOptions[i]) {
if (sleeve.gymStatType === gymSelectorOptions[i].substring(6, 9).toLowerCase()) {
elems.taskDetailsSelector.selectedIndex = i;
}
}

@ -10,9 +10,9 @@ function mult(f: Faction): number {
}
export function getHackingWorkRepGain(p: IPlayer, f: Faction): number {
return (p.hacking_skill + p.intelligence) /
return (p.hacking_skill + p.intelligence/3) /
CONSTANTS.MaxSkillLevel * p.faction_rep_mult *
p.getIntelligenceBonus(0.25) * mult(f);
p.getIntelligenceBonus(1) * mult(f);
}
export function getFactionSecurityWorkRepGain(p: IPlayer, f: Faction): number {
@ -20,8 +20,9 @@ export function getFactionSecurityWorkRepGain(p: IPlayer, f: Faction): number {
p.strength / CONSTANTS.MaxSkillLevel +
p.defense / CONSTANTS.MaxSkillLevel +
p.dexterity / CONSTANTS.MaxSkillLevel +
p.agility / CONSTANTS.MaxSkillLevel) / 4.5;
return t * p.faction_rep_mult * mult(f);
p.agility / CONSTANTS.MaxSkillLevel +
p.intelligence / CONSTANTS.MaxSkillLevel) / 4.5;
return t * p.faction_rep_mult * mult(f) * p.getIntelligenceBonus(1);
}
export function getFactionFieldWorkRepGain(p: IPlayer, f: Faction): number {
@ -32,5 +33,5 @@ export function getFactionFieldWorkRepGain(p: IPlayer, f: Faction): number {
p.agility / CONSTANTS.MaxSkillLevel +
p.charisma / CONSTANTS.MaxSkillLevel +
p.intelligence / CONSTANTS.MaxSkillLevel) / 5.5;
return t * p.faction_rep_mult * mult(f);
return t * p.faction_rep_mult * mult(f) * p.getIntelligenceBonus(1);
}

@ -231,12 +231,12 @@ function saveAndCloseScriptEditor() {
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
//Make sure filename + code properly follow tutorial
if (filename !== "foodnstuff.script") {
dialogBoxCreate("Leave the script name as 'foodnstuff'!");
if (filename !== "n00dles.script") {
dialogBoxCreate("Leave the script name as 'n00dles'!");
return;
}
code = code.replace(/\s/g, "");
if (code.indexOf("while(true){hack('foodnstuff');}") == -1) {
if (code.indexOf("while(true){hack('n00dles');}") == -1) {
dialogBoxCreate("Please copy and paste the code from the tutorial!");
return;
}

@ -53,6 +53,7 @@ import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStart
import { Player } from "./Player";
import { hackWorldDaemon } from "./RedPill";
import { RunningScript } from "./Script/RunningScript";
import { compareArrays } from "../utils/helpers/compareArrays";
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
import {
getCurrentEditor,
@ -742,8 +743,8 @@ let Terminal = {
/****************** Interactive Tutorial Terminal Commands ******************/
if (ITutorial.isRunning) {
var foodnstuffServ = GetServerByHostname("foodnstuff");
if (foodnstuffServ == null) {throw new Error("Could not get foodnstuff server"); return;}
var n00dlesServ = GetServerByHostname("n00dles");
if (n00dlesServ == null) {throw new Error("Could not get n00dles server"); return;}
switch(ITutorial.currStep) {
case iTutorialSteps.TerminalHelp:
@ -780,11 +781,11 @@ let Terminal = {
case iTutorialSteps.TerminalConnect:
if (commandArray.length == 2) {
if ((commandArray[0] == "connect") &&
(commandArray[1] == "foodnstuff" || commandArray[1] == foodnstuffServ.ip)) {
(commandArray[1] == "n00dles" || commandArray[1] == n00dlesServ.ip)) {
Player.getCurrentServer().isConnectedTo = false;
Player.currentServer = foodnstuffServ.ip;
Player.currentServer = n00dlesServ.ip;
Player.getCurrentServer().isConnectedTo = true;
post("Connected to foodnstuff");
post("Connected to n00dles");
iTutorialNextStep();
} else {post("Wrong command! Try again!"); return;}
} else {post("Bad command. Please follow the tutorial");}
@ -804,8 +805,8 @@ let Terminal = {
case iTutorialSteps.TerminalNuke:
if (commandArray.length == 2 &&
commandArray[0] == "run" && commandArray[1] == "NUKE.exe") {
foodnstuffServ.hasAdminRights = true;
post("NUKE successful! Gained root access to foodnstuff");
n00dlesServ.hasAdminRights = true;
post("NUKE successful! Gained root access to n00dles");
iTutorialNextStep();
} else {post("Bad command. Please follow the tutorial");}
break;
@ -817,8 +818,8 @@ let Terminal = {
break;
case iTutorialSteps.TerminalCreateScript:
if (commandArray.length == 2 &&
commandArray[0] == "nano" && commandArray[1] == "foodnstuff.script") {
Engine.loadScriptEditorContent("foodnstuff.script", "");
commandArray[0] == "nano" && commandArray[1] == "n00dles.script") {
Engine.loadScriptEditorContent("n00dles.script", "");
iTutorialNextStep();
} else {post("Bad command. Please follow the tutorial");}
break;
@ -830,16 +831,16 @@ let Terminal = {
break;
case iTutorialSteps.TerminalRunScript:
if (commandArray.length == 2 &&
commandArray[0] == "run" && commandArray[1] == "foodnstuff.script") {
commandArray[0] == "run" && commandArray[1] == "n00dles.script") {
Terminal.runScript(commandArray);
iTutorialNextStep();
} else {post("Bad command. Please follow the tutorial");}
break;
case iTutorialSteps.ActiveScriptsToTerminal:
if (commandArray.length == 2 &&
commandArray[0] == "tail" && commandArray[1] == "foodnstuff.script") {
commandArray[0] == "tail" && commandArray[1] == "n00dles.script") {
// Check that the script exists on this machine
var runningScript = findRunningScript("foodnstuff.script", [], Player.getCurrentServer());
var runningScript = findRunningScript("n00dles.script", [], Player.getCurrentServer());
if (runningScript == null) {
post("Error: No such script exists");
return;
@ -1422,13 +1423,46 @@ let Terminal = {
args.push(commandArray[i]);
}
// Check that the script exists on this machine
const runningScript = findRunningScript(scriptName, args, s);
if (runningScript == null) {
postError("No such script exists");
// go over all the running scripts. If there's a perfect
// match, use it!
for (var i = 0; i < s.runningScripts.length; ++i) {
if (s.runningScripts[i].filename === scriptName &&
compareArrays(s.runningScripts[i].args, args)) {
logBoxCreate(s.runningScripts[i]);
return;
}
}
// Find all scripts that are potential candidates.
const candidates = [];
for (var i = 0; i < s.runningScripts.length; ++i) {
// only scripts that have more arguments (equal arguments is already caught)
if(s.runningScripts[i].args.length < args.length) continue;
// make a smaller copy of the args.
const args2 = s.runningScripts[i].args.slice(0, args.length);
if (s.runningScripts[i].filename === scriptName &&
compareArrays(args2, args)) {
candidates.push(s.runningScripts[i]);
}
}
// If there's only 1 possible choice, use that.
if(candidates.length === 1) {
logBoxCreate(candidates[0]);
return;
}
logBoxCreate(runningScript);
// otherwise lists all possible conflicting choices.
if(candidates.length > 1) {
postError("Found several potential candidates:");
for(const candidate of candidates)
postError(`${candidate.filename} ${candidate.args.join(' ')}`);
postError("Script arguments need to be specified.");
return;
}
// if there's no candidate then we just don't know.
postError("No such script exists.");
} else {
const runningScript = findRunningScriptByPid(commandArray[1], Player.getCurrentServer());
if (runningScript == null) {

@ -32,6 +32,7 @@ import {
processPassiveFactionRepGain,
inviteToFaction,
} from "./Faction/FactionHelpers";
import { displayInfiltrationContent } from "./Infiltration/Helper";
import {
getHackingWorkRepGain,
getFactionSecurityWorkRepGain,
@ -419,10 +420,13 @@ const Engine = {
routing.navigateTo(Page.CinematicText);
},
loadInfiltrationContent: function() {
loadInfiltrationContent: function(name, difficulty, maxLevel) {
Engine.hideAllContent();
const mainMenu = document.getElementById("mainmenu-container");
mainMenu.style.visibility = "hidden";
Engine.Display.infiltrationContent.style.display = "block";
routing.navigateTo(Page.Infiltration);
displayInfiltrationContent(this, Player, name, difficulty, maxLevel);
},
loadStockMarketContent: function() {
@ -501,6 +505,8 @@ const Engine = {
Engine.Display.activeScriptsContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.activeScriptsContent);
Engine.Display.infiltrationContent.style.display = "none";
ReactDOM.unmountComponentAtNode(Engine.Display.infiltrationContent);
clearHacknetNodesUI();
Engine.Display.createProgramContent.style.display = "none";
@ -522,7 +528,6 @@ const Engine = {
Engine.Display.workInProgressContent.style.display = "none";
Engine.Display.redPillContent.style.display = "none";
Engine.Display.cinematicTextContent.style.display = "none";
Engine.Display.infiltrationContent.style.display = "none";
Engine.Display.stockMarketContent.style.display = "none";
Engine.Display.missionContent.style.display = "none";
if (document.getElementById("gang-container")) {

@ -31,3 +31,4 @@ import "../css/grid.min.css";
import "../css/dev-menu.css";
import "../css/casino.scss";
import "../css/milestones.scss";
import "../css/infiltration.scss";

@ -67,7 +67,7 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<span id="augmentations-notification" class="notification-off"> </span>
</li>
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel">
<button id="hacknet-nodes-menu-link"> Hacknet Nodes </button>
<button id="hacknet-nodes-menu-link"> Hacknet </button>
</li>
<li id="sleeves-tab" class="mainmenu-accordion-panel">
<button id="sleeves-menu-link"> Sleeves </button>
@ -277,25 +277,7 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<div id="location-container" class="generic-menupage-container">
</div>
<div id="infiltration-container" class="generic-menupage-container">
<div id="infiltration-left-panel">
<p id="infiltration-level-text"> </p>
<div id="infiltration-buttons">
<button class="a-link-button tooltip" id="infiltration-kill"> </button>
<button class="a-link-button tooltip" id="infiltration-knockout"> </button>
<button class="a-link-button tooltip" id="infiltration-stealthknockout"> </button>
<button class="a-link-button tooltip" id="infiltration-assassinate"> </button>
<button class="a-link-button tooltip" id="infiltration-hacksecurity"> </button>
<button class="a-link-button tooltip" id="infiltration-destroysecurity"> </button>
<button class="a-link-button tooltip" id="infiltration-sneak"> </button>
<button class="a-link-button tooltip" id="infiltration-pickdoor"> </button>
<button class="a-link-button tooltip" id="infiltration-bribe"> </button>
<button class="a-link-button tooltip" id="infiltration-escape"> </button>
</div>
</div>
<div id="infiltration-right-panel">
<p id="infiltration-status-text"></p>
</div>
<div id="infiltration-container" class="generic-fullscreen-container">
</div>
<div id="stock-market-container" class="generic-menupage-container">

@ -16,6 +16,10 @@ import 'numeral/locales/ru';
/* eslint-disable class-methods-use-this */
const extraFormats = [1e15, 1e18, 1e21, 1e24, 1e27, 1e30];
const extraNotations = ['q', 'Q', 's', 'S', 'o', 'n'];
class NumeralFormatter {
// Default Locale
defaultLocale = "en";
@ -48,42 +52,52 @@ class NumeralFormatter {
return this.format(n, "0.000a");
}
formatHp(n: number): string {
return this.format(n, "0");
}
formatMoney(n: number): string {
if(numeral.options.currentLocale === "en") {
const extraFormats = [1e15, 1e18, 1e21, 1e24, 1e27, 1e30];
const extraNotations = ['q', 'Q', 's', 'S', 'o', 'n'];
for(let i = 0; i < extraFormats.length; i++) {
if(extraFormats[i] < n && n <= extraFormats[i]*1000) {
return '$'+this.format(n/extraFormats[i], '0.000')+extraNotations[i];
}
// TODO: leverage numeral.js to do it. This function also implies you can
// use this format in some text field but you can't. ( "1t" will parse but
// "1s" will not)
formatReallyBigNumber(n: number, decimalPlaces = 3): string {
if(n === Infinity) return "∞";
for(let i = 0; i < extraFormats.length; i++) {
if(extraFormats[i] < n && n <= extraFormats[i]*1000) {
return this.format(n/extraFormats[i], '0.'+'0'.repeat(decimalPlaces))+extraNotations[i];
}
}
if(Math.abs(n) < 1000) {
return this.format(n, "$0.00");
return this.format(n, '0.'+'0'.repeat(decimalPlaces));
}
const str = this.format(n, "$0.000a");
if(str === "$NaNt") return '$'+this.format(n, '0.000e+0');
const str = this.format(n, '0.'+'0'.repeat(decimalPlaces) + 'a');
if(str === "NaNt") return this.format(n, '0.' + ' '.repeat(decimalPlaces) + 'e+0');
return str;
}
formatHp(n: number): string {
if(n < 1e6){
return this.format(n, "0,0");
}
return this.formatReallyBigNumber(n);
}
formatMoney(n: number): string {
return "$" + this.formatReallyBigNumber(n);
}
formatSkill(n: number): string {
return this.format(n, "0,0");
if(n < 1e15){
return this.format(n, "0,0");
}
return this.formatReallyBigNumber(n);
}
formatExp(n: number): string {
return this.format(n, "0.000a");
return this.formatReallyBigNumber(n);
}
formatHashes(n: number): string {
return this.format(n, "0.000a");
return this.formatReallyBigNumber(n);
}
formatReputation(n: number): string {
return this.format(n, "0.000a");
return this.formatReallyBigNumber(n);
}
formatFavor(n: number): string {
@ -104,11 +118,11 @@ class NumeralFormatter {
}
formatRespect(n: number): string {
return this.format(n, "0.00000a");
return this.formatReallyBigNumber(n, 5);
}
formatWanted(n: number): string {
return this.format(n, "0.00000a");
return this.formatReallyBigNumber(n, 5);
}
formatMultiplier(n: number): string {
@ -139,11 +153,11 @@ class NumeralFormatter {
if (n < 1000) {
return this.format(n, "0");
}
return this.format(n, "0.000a");
return this.formatReallyBigNumber(n);
}
formatInfiltrationSecurity(n: number): string {
return this.format(n, "0.000a");
return this.formatReallyBigNumber(n);
}
formatThreads(n: number): string {

@ -1,120 +0,0 @@
import { dialogBoxCreate } from "./DialogBox";
import { clearEventListeners } from "./uiHelpers/clearEventListeners";
import { numeralWrapper } from "../src/ui/numeralFormat";
import { BitNodeMultipliers } from "../src/BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../src/Constants";
import { Factions } from "../src/Faction/Factions";
import { Player } from "../src/Player";
//Keep track of last faction
var lastFac = "";
/* InfiltrationBox.js */
function infiltrationBoxClose() {
var box = document.getElementById("infiltration-box-container");
box.style.display = "none";
}
function infiltrationBoxOpen() {
var box = document.getElementById("infiltration-box-container");
box.style.display = "flex";
}
function infiltrationSetText(txt) {
var textBox = document.getElementById("infiltration-box-text");
textBox.innerHTML = txt;
}
//ram argument is in GB
function infiltrationBoxCreate(inst) {
//Gain exp
Player.gainHackingExp(inst.calcGainedHackingExp());
Player.gainStrengthExp(inst.calcGainedStrengthExp());
Player.gainDefenseExp(inst.calcGainedDefenseExp());
Player.gainDexterityExp(inst.calcGainedDexterityExp());
Player.gainAgilityExp(inst.calcGainedAgilityExp());
Player.gainCharismaExp(inst.calcGainedCharismaExp());
Player.gainIntelligenceExp(inst.calcGainedIntelligenceExp());
const expGainText = ["You gained:",
`${numeralWrapper.formatExp(inst.calcGainedHackingExp(), 3)} hacking exp`,
`${numeralWrapper.formatExp(inst.calcGainedStrengthExp(), 3)} str exp`,
`${numeralWrapper.formatExp(inst.calcGainedDefenseExp(), 3)} def exp`,
`${numeralWrapper.formatExp(inst.calcGainedDexterityExp(), 3)} dex exp`,
`${numeralWrapper.formatExp(inst.calcGainedAgilityExp(), 3)} agi exp`,
`${numeralWrapper.formatExp(inst.calcGainedCharismaExp(), 3)} cha exp`].join("\n");
var totalValue = 0;
for (var i = 0; i < inst.secretsStolen.length; ++i) {
totalValue += inst.secretsStolen[i];
}
if (totalValue == 0) {
dialogBoxCreate("You successfully escaped the facility but you did not steal " +
"anything of worth when infiltrating.<br><br>" + expGainText);
return;
}
var facValue = totalValue * Player.faction_rep_mult *
CONSTANTS.InfiltrationRepValue * BitNodeMultipliers.InfiltrationRep;
var moneyValue = totalValue * CONSTANTS.InfiltrationMoneyValue * BitNodeMultipliers.InfiltrationMoney;
infiltrationSetText("You can sell the classified documents and secrets " +
"you stole from " + inst.companyName + " for <span class='money-gold'>" +
numeralWrapper.formatMoney(moneyValue) + "</span> on the black market or you can give it " +
"to a faction to gain <span class='light-yellow'>" + numeralWrapper.formatReputation(facValue) + " reputation</span> with " +
"that faction.");
var selector = document.getElementById("infiltration-faction-select");
selector.innerHTML = "";
for (let i = 0; i < Player.factions.length; ++i) {
if (Player.factions[i] === "Bladeburners") { continue; }
if (Player.inGang() && Player.gang.facName === Player.factions[i]) { continue; }
selector.innerHTML += "<option value='" + Player.factions[i] +
"'>" + Player.factions[i] + "</option>";
}
//Set initial value, if applicable
if (lastFac !== "") {
for (let i = 0; i < selector.options.length; ++i) {
if (selector.options[i].value === lastFac) {
selector.selectedIndex = i;
break;
}
}
}
var sellButton = clearEventListeners("infiltration-box-sell");
setTimeout(function() {
sellButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
Player.gainMoney(moneyValue);
Player.recordMoneySource(moneyValue, "infiltration");
dialogBoxCreate("You sold the classified information you stole from " + inst.companyName +
" for <span class='money-gold'>" + numeralWrapper.formatMoney(moneyValue) + "</span> on the black market!<br><br>" +
expGainText);
infiltrationBoxClose();
return false;
});
}, 750);
var factionButton = clearEventListeners("infiltration-box-faction");
setTimeout(function() {
factionButton.addEventListener("click", function(e) {
if (!e.isTrusted) {return false;}
var facName = selector.options[selector.selectedIndex].value;
lastFac = facName;
var faction = Factions[facName];
if (faction == null) {
dialogBoxCreate("Error finding faction. This is a bug please report to developer");
return false;
}
faction.playerReputation += facValue;
dialogBoxCreate("You gave the classified information you stole from " + inst.companyName +
" to " + facName + " and gained <span class='light-yellow'>" + numeralWrapper.formatReputation(facValue) + " reputation</span> with the faction. <br><br>" +
expGainText);
infiltrationBoxClose();
return false;
});
}, 750);
infiltrationBoxOpen();
}
export {infiltrationBoxCreate};