Infiltration rework (#1010)

Infiltration 2
This commit is contained in:
hydroflame 2021-06-13 11:05:40 -04:00 committed by GitHub
parent 39b4048603
commit 19f51b684b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1555 additions and 1263 deletions

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

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

70
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
@ -5037,5 +5012,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

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

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

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

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

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

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

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

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