mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-26 01:23:49 +01:00
Merge branch 'dev' of github.com:danielyxie/bitburner into dev
This commit is contained in:
commit
5848fa53b7
@ -1,5 +1,6 @@
|
||||
@import "mixins";
|
||||
@import "theme";
|
||||
@import "styles";
|
||||
|
||||
/**
|
||||
* Styling for all buttons
|
||||
@ -16,6 +17,7 @@ button {
|
||||
|
||||
.a-link-button,
|
||||
.std-button {
|
||||
@extend .noselect;
|
||||
text-decoration: none;
|
||||
background-color: #555;
|
||||
color: #fff;
|
||||
@ -23,11 +25,6 @@ button {
|
||||
margin: 5px;
|
||||
border: 1px solid #333;
|
||||
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
@ -68,6 +65,7 @@ button {
|
||||
|
||||
.a-link-button-bought,
|
||||
.std-button-bought {
|
||||
@extend .noselect;
|
||||
text-decoration: none;
|
||||
background-color: #0a0;
|
||||
color: #fff;
|
||||
|
@ -1,5 +1,6 @@
|
||||
@import "mixins";
|
||||
@import "theme";
|
||||
@import "styles";
|
||||
|
||||
/* Pop-up boxes */
|
||||
.popup-box-container {
|
||||
@ -23,6 +24,7 @@
|
||||
width: 70%;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
z-index: 11; /* Sit on top of the container */
|
||||
color: var(--my-font-color);
|
||||
}
|
||||
|
||||
@ -87,6 +89,7 @@
|
||||
.dialog-box-close-button {
|
||||
@include borderRadius(12px);
|
||||
@include boxShadow(1px 1px 3px #000);
|
||||
@extend .noselect;
|
||||
|
||||
float: right;
|
||||
color: #aaa;
|
||||
|
@ -36,6 +36,10 @@ li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
br {
|
||||
@extend .noselect;
|
||||
}
|
||||
|
||||
#entire-game-container {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
4
dist/engine.bundle.js
vendored
4
dist/engine.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/engineStyle.bundle.js
vendored
2
dist/engineStyle.bundle.js
vendored
@ -1,2 +1,2 @@
|
||||
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([853,0]),o()}({790:function(n,t,o){},792:function(n,t,o){},794:function(n,t,o){},796:function(n,t,o){},798:function(n,t,o){},800:function(n,t,o){},802:function(n,t,o){},804:function(n,t,o){},806:function(n,t,o){},808:function(n,t,o){},810:function(n,t,o){},812:function(n,t,o){},814:function(n,t,o){},816:function(n,t,o){},818:function(n,t,o){},820:function(n,t,o){},822:function(n,t,o){},824:function(n,t,o){},826:function(n,t,o){},828:function(n,t,o){},830:function(n,t,o){},832:function(n,t,o){},834:function(n,t,o){},836:function(n,t,o){},838:function(n,t,o){},840:function(n,t,o){},842:function(n,t,o){},844:function(n,t,o){},846:function(n,t,o){},848:function(n,t,o){},850:function(n,t,o){},853:function(n,t,o){"use strict";o.r(t);o(852),o(850),o(848),o(846),o(844),o(842),o(840),o(838),o(836),o(834),o(832),o(830),o(828),o(826),o(824),o(822),o(820),o(818),o(816),o(814),o(812),o(810),o(808),o(806),o(804),o(802),o(800),o(798),o(796),o(794),o(792),o(790)}});
|
||||
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([857,0]),o()}({794:function(n,t,o){},796:function(n,t,o){},798:function(n,t,o){},800:function(n,t,o){},802:function(n,t,o){},804:function(n,t,o){},806:function(n,t,o){},808:function(n,t,o){},810:function(n,t,o){},812:function(n,t,o){},814:function(n,t,o){},816:function(n,t,o){},818:function(n,t,o){},820:function(n,t,o){},822:function(n,t,o){},824:function(n,t,o){},826:function(n,t,o){},828:function(n,t,o){},830:function(n,t,o){},832:function(n,t,o){},834:function(n,t,o){},836:function(n,t,o){},838:function(n,t,o){},840:function(n,t,o){},842:function(n,t,o){},844:function(n,t,o){},846:function(n,t,o){},848:function(n,t,o){},850:function(n,t,o){},852:function(n,t,o){},854:function(n,t,o){},857:function(n,t,o){"use strict";o.r(t);o(856),o(854),o(852),o(850),o(848),o(846),o(844),o(842),o(840),o(838),o(836),o(834),o(832),o(830),o(828),o(826),o(824),o(822),o(820),o(818),o(816),o(814),o(812),o(810),o(808),o(806),o(804),o(802),o(800),o(798),o(796),o(794)}});
|
||||
//# sourceMappingURL=engineStyle.bundle.js.map
|
2
dist/engineStyle.css
vendored
2
dist/engineStyle.css
vendored
@ -1552,6 +1552,8 @@ button {
|
||||
width: 70%;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
z-index: 11;
|
||||
/* Sit on top of the container */
|
||||
color: var(--my-font-color); }
|
||||
|
||||
.popup-box-button,
|
||||
|
34
dist/vendor.bundle.js
vendored
34
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
@ -271,6 +271,10 @@
|
||||
<!-- React Component -->
|
||||
</div>
|
||||
|
||||
<div id="gang-container" class="generic-menupage-container">
|
||||
<!-- React Component -->
|
||||
</div>
|
||||
|
||||
<!-- Log Box -->
|
||||
<div id="log-box-container">
|
||||
<div id="log-box-content">
|
||||
|
@ -1865,11 +1865,6 @@ function initAugmentations() {
|
||||
resetAugmentation(BladesSimulacrum);
|
||||
}
|
||||
|
||||
try {
|
||||
console.log([1, 0.96, 0.94, 0.93][SourceFileFlags[11]]);
|
||||
} catch(err) {
|
||||
console.log(err);
|
||||
}
|
||||
// Update costs based on how many have been purchased
|
||||
mult = Math.pow(CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]], Player.queuedAugmentations.length);
|
||||
for (var name in Augmentations) {
|
||||
|
@ -7,7 +7,6 @@ import * as React from "react";
|
||||
import { Augmentations } from "../../Augmentation/Augmentations";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Player } from "../../Player";
|
||||
import { IPlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
|
||||
|
||||
import { AugmentationAccordion } from "../../ui/React/AugmentationAccordion";
|
||||
|
||||
|
@ -172,7 +172,7 @@ export class CodingContract {
|
||||
async prompt(): Promise<CodingContractResult> {
|
||||
const popupId = `coding-contract-prompt-popup-${this.fn}`;
|
||||
return new Promise<CodingContractResult>((resolve) => {
|
||||
const popup = new CodingContractPopup({
|
||||
createPopup(popupId, CodingContractPopup, {
|
||||
c: this,
|
||||
popupId: popupId,
|
||||
onClose: () => {
|
||||
@ -188,7 +188,6 @@ export class CodingContract {
|
||||
removePopup(popupId);
|
||||
},
|
||||
});
|
||||
createPopup(popupId, CodingContractPopup, popup.props);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,9 @@ import { IMap } from "./types";
|
||||
export const CONSTANTS: IMap<any> = {
|
||||
Version: "0.52.3",
|
||||
|
||||
// Speed (in ms) at which the main loop is updated
|
||||
_idleSpeed: 200,
|
||||
|
||||
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
|
||||
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
|
||||
* the player will have this level assuming no multipliers. Multipliers can cause skills to go above this.
|
||||
|
1
src/Faction/FactionHelpers.d.ts
vendored
1
src/Faction/FactionHelpers.d.ts
vendored
@ -5,3 +5,4 @@ export declare function getNextNeurofluxLevel(): number;
|
||||
export declare function hasAugmentationPrereqs(aug: Augmentation): boolean;
|
||||
export declare function purchaseAugmentationBoxCreate(aug: Augmentation, fac: Faction): void;
|
||||
export declare function purchaseAugmentation(aug: Augmentation, fac: Faction, sing?: boolean): void;
|
||||
export declare function displayFactionContent(factionName: string, initiallyOnAugmentationsPage: boolean=false);
|
@ -46,6 +46,7 @@ export function inviteToFaction(faction) {
|
||||
}
|
||||
|
||||
export function joinFaction(faction) {
|
||||
if(faction.isMember) return;
|
||||
faction.isMember = true;
|
||||
Player.factions.push(faction.name);
|
||||
const factionInfo = faction.getInfo();
|
||||
|
1991
src/Gang.jsx
1991
src/Gang.jsx
File diff suppressed because it is too large
Load Diff
76
src/Gang/AllGangs.ts
Normal file
76
src/Gang/AllGangs.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
interface GangTerritory {
|
||||
power: number;
|
||||
territory: number;
|
||||
}
|
||||
|
||||
export let AllGangs: {
|
||||
[key: string]: GangTerritory;
|
||||
} = {
|
||||
"Slum Snakes" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Tetrads" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Syndicate" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Dark Army" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Speakers for the Dead" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"NiteSec" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Black Hand" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
}
|
||||
|
||||
export function resetGangs(): void {
|
||||
AllGangs = {
|
||||
"Slum Snakes" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Tetrads" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Syndicate" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Dark Army" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"Speakers for the Dead" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"NiteSec" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
"The Black Hand" : {
|
||||
power: 1,
|
||||
territory: 1/7,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function loadAllGangs(saveString: string): void {
|
||||
AllGangs = JSON.parse(saveString, Reviver);
|
||||
}
|
413
src/Gang/Gang.ts
Normal file
413
src/Gang/Gang.ts
Normal file
@ -0,0 +1,413 @@
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Add police clashes
|
||||
* balance point to keep them from running out of control
|
||||
*/
|
||||
|
||||
import { Faction } from "../Faction/Faction";
|
||||
import { Factions } from "../Faction/Factions";
|
||||
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||
import {
|
||||
Reviver,
|
||||
Generic_toJSON,
|
||||
Generic_fromJSON,
|
||||
} from "../../utils/JSONReviver";
|
||||
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
import { GangConstants } from "./data/Constants";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { GangMemberTasks } from "./GangMemberTasks";
|
||||
import { IAscensionResult } from "./IAscensionResult";
|
||||
|
||||
import { AllGangs } from "./AllGangs";
|
||||
import { GangMember } from "./GangMember";
|
||||
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
|
||||
|
||||
export class Gang {
|
||||
facName: string;
|
||||
members: GangMember[];
|
||||
wanted: number;
|
||||
respect: number;
|
||||
|
||||
isHackingGang: boolean;
|
||||
|
||||
respectGainRate: number;
|
||||
wantedGainRate: number;
|
||||
moneyGainRate: number;
|
||||
|
||||
storedCycles: number;
|
||||
|
||||
storedTerritoryAndPowerCycles: number;
|
||||
|
||||
territoryClashChance: number;
|
||||
territoryWarfareEngaged: boolean;
|
||||
|
||||
notifyMemberDeath: boolean;
|
||||
|
||||
constructor(facName = "", hacking = false) {
|
||||
this.facName = facName;
|
||||
this.members = [];
|
||||
this.wanted = 1;
|
||||
this.respect = 1;
|
||||
|
||||
this.isHackingGang = hacking;
|
||||
|
||||
this.respectGainRate = 0;
|
||||
this.wantedGainRate = 0;
|
||||
this.moneyGainRate = 0;
|
||||
|
||||
// When processing gains, this stores the number of cycles until some
|
||||
// limit is reached, and then calculates and applies the gains only at that limit
|
||||
this.storedCycles = 0;
|
||||
|
||||
// Separate variable to keep track of cycles for Territry + Power gang, which
|
||||
// happens on a slower "clock" than normal processing
|
||||
this.storedTerritoryAndPowerCycles = 0;
|
||||
|
||||
this.territoryClashChance = 0;
|
||||
this.territoryWarfareEngaged = false;
|
||||
|
||||
this.notifyMemberDeath = true;
|
||||
}
|
||||
|
||||
getPower(): number {
|
||||
return AllGangs[this.facName].power;
|
||||
}
|
||||
|
||||
getTerritory(): number {
|
||||
return AllGangs[this.facName].territory;
|
||||
}
|
||||
|
||||
process(numCycles = 1, player: IPlayer): void {
|
||||
const CyclesPerSecond = 1000 / CONSTANTS._idleSpeed;
|
||||
|
||||
if (isNaN(numCycles)) {
|
||||
console.error(`NaN passed into Gang.process(): ${numCycles}`);
|
||||
}
|
||||
this.storedCycles += numCycles;
|
||||
|
||||
// Only process if there are at least 2 seconds, and at most 5 seconds
|
||||
if (this.storedCycles < 2 * CyclesPerSecond) return;
|
||||
const cycles = Math.min(this.storedCycles, 5 * CyclesPerSecond);
|
||||
|
||||
try {
|
||||
this.processGains(cycles, player);
|
||||
this.processExperienceGains(cycles);
|
||||
this.processTerritoryAndPowerGains(cycles);
|
||||
this.storedCycles -= cycles;
|
||||
} catch(e) {
|
||||
console.error(`Exception caught when processing Gang: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
processGains(numCycles = 1, player: IPlayer): void {
|
||||
// Get gains per cycle
|
||||
let moneyGains = 0;
|
||||
let respectGains = 0;
|
||||
let wantedLevelGains = 0;
|
||||
let justice = 0;
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
respectGains += (this.members[i].calculateRespectGain(this));
|
||||
moneyGains += (this.members[i].calculateMoneyGain(this));
|
||||
const wantedLevelGain = this.members[i].calculateWantedLevelGain(this);
|
||||
wantedLevelGains += wantedLevelGain;
|
||||
if(wantedLevelGain < 0) justice++; // this member is lowering wanted.
|
||||
}
|
||||
this.respectGainRate = respectGains;
|
||||
this.wantedGainRate = wantedLevelGains;
|
||||
this.moneyGainRate = moneyGains;
|
||||
|
||||
const gain = respectGains * numCycles;
|
||||
this.respect += gain;
|
||||
// Faction reputation gains is respect gain divided by some constant
|
||||
const fac = Factions[this.facName];
|
||||
if (!(fac instanceof Faction)) {
|
||||
dialogBoxCreate("ERROR: Could not get Faction associates with your gang. This is a bug, please report to game dev");
|
||||
throw new Error('Could not find the faction associated with this gang.');
|
||||
}
|
||||
const favorMult = 1 + (fac.favor / 100);
|
||||
fac.playerReputation += ((player.faction_rep_mult * gain * favorMult) / GangConstants.GangRespectToReputationRatio);
|
||||
|
||||
// Keep track of respect gained per member
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
this.members[i].recordEarnedRespect(numCycles, this);
|
||||
}
|
||||
if (!(this.wanted === 1 && wantedLevelGains < 0)) {
|
||||
const oldWanted = this.wanted;
|
||||
let newWanted = oldWanted + (wantedLevelGains * numCycles);
|
||||
newWanted = newWanted * (1 - justice * 0.001); // safeguard
|
||||
// Prevent overflow
|
||||
if (wantedLevelGains <= 0 && newWanted > oldWanted) newWanted = 1;
|
||||
|
||||
this.wanted = newWanted;
|
||||
if (this.wanted < 1) this.wanted = 1;
|
||||
}
|
||||
player.gainMoney(moneyGains * numCycles);
|
||||
player.recordMoneySource(moneyGains * numCycles, "gang");
|
||||
}
|
||||
|
||||
processTerritoryAndPowerGains(numCycles = 1): void {
|
||||
this.storedTerritoryAndPowerCycles += numCycles;
|
||||
if (this.storedTerritoryAndPowerCycles < GangConstants.CyclesPerTerritoryAndPowerUpdate) return;
|
||||
this.storedTerritoryAndPowerCycles -= GangConstants.CyclesPerTerritoryAndPowerUpdate;
|
||||
|
||||
// Process power first
|
||||
const gangName = this.facName;
|
||||
for (const name in AllGangs) {
|
||||
if (AllGangs.hasOwnProperty(name)) {
|
||||
if (name == gangName) {
|
||||
AllGangs[name].power += this.calculatePower();
|
||||
} else {
|
||||
// All NPC gangs get random power gains
|
||||
const gainRoll = Math.random();
|
||||
if (gainRoll < 0.5) {
|
||||
// Multiplicative gain (50% chance)
|
||||
// This is capped per cycle, to prevent it from getting out of control
|
||||
const multiplicativeGain = AllGangs[name].power * 0.005;
|
||||
AllGangs[name].power += Math.min(0.85, multiplicativeGain);
|
||||
} else {
|
||||
// Additive gain (50% chance)
|
||||
const additiveGain = 0.75 * gainRoll * AllGangs[name].territory;
|
||||
AllGangs[name].power += (additiveGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if territory should be processed
|
||||
if (this.territoryWarfareEngaged) {
|
||||
this.territoryClashChance = 1;
|
||||
} else if (this.territoryClashChance > 0) {
|
||||
// Engagement turned off, but still a positive clash chance. So there's
|
||||
// still a chance of clashing but it slowly goes down over time
|
||||
this.territoryClashChance = Math.max(0, this.territoryClashChance - 0.01);
|
||||
}
|
||||
|
||||
// Then process territory
|
||||
for (let i = 0; i < GangConstants.Names.length; ++i) {
|
||||
const others = GangConstants.Names.filter((e) => {
|
||||
return e !== GangConstants.Names[i];
|
||||
});
|
||||
const other = getRandomInt(0, others.length - 1);
|
||||
|
||||
const thisGang = GangConstants.Names[i];
|
||||
const otherGang = others[other];
|
||||
|
||||
// If either of the gangs involved in this clash is the player, determine
|
||||
// whether to skip or process it using the clash chance
|
||||
if (thisGang === gangName || otherGang === gangName) {
|
||||
if (!(Math.random() < this.territoryClashChance)) continue;
|
||||
}
|
||||
|
||||
const thisPwr = AllGangs[thisGang].power;
|
||||
const otherPwr = AllGangs[otherGang].power;
|
||||
const thisChance = thisPwr / (thisPwr + otherPwr);
|
||||
|
||||
function calculateTerritoryGain(winGang: string, loseGang: string): number {
|
||||
const powerBonus = Math.max(1, 1+Math.log(AllGangs[winGang].power/AllGangs[loseGang].power)/Math.log(50));
|
||||
const gains = Math.min(AllGangs[loseGang].territory, powerBonus*0.0001*(Math.random()+.5))
|
||||
return gains;
|
||||
}
|
||||
|
||||
if (Math.random() < thisChance) {
|
||||
if (AllGangs[otherGang].territory <= 0) return;
|
||||
const territoryGain = calculateTerritoryGain(thisGang, otherGang);
|
||||
AllGangs[thisGang].territory += territoryGain;
|
||||
AllGangs[otherGang].territory -= territoryGain;
|
||||
if (thisGang === gangName) {
|
||||
this.clash(true); // Player won
|
||||
AllGangs[otherGang].power *= (1 / 1.01);
|
||||
} else if (otherGang === gangName) {
|
||||
this.clash(false); // Player lost
|
||||
} else {
|
||||
AllGangs[otherGang].power *= (1 / 1.01);
|
||||
}
|
||||
} else {
|
||||
if (AllGangs[thisGang].territory <= 0) return;
|
||||
const territoryGain = calculateTerritoryGain(otherGang, thisGang);
|
||||
AllGangs[thisGang].territory -= territoryGain;
|
||||
AllGangs[otherGang].territory += territoryGain;
|
||||
if (thisGang === gangName) {
|
||||
this.clash(false); // Player lost
|
||||
} else if (otherGang === gangName) {
|
||||
this.clash(true); // Player won
|
||||
AllGangs[thisGang].power *= (1 / 1.01);
|
||||
} else {
|
||||
AllGangs[thisGang].power *= (1 / 1.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processExperienceGains(numCycles = 1): void {
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
this.members[i].gainExperience(numCycles);
|
||||
this.members[i].updateSkillLevels();
|
||||
}
|
||||
}
|
||||
|
||||
clash(won = false): void {
|
||||
// Determine if a gang member should die
|
||||
let baseDeathChance = 0.01;
|
||||
if (won) baseDeathChance /= 2;
|
||||
|
||||
// If the clash was lost, the player loses a small percentage of power
|
||||
else AllGangs[this.facName].power *= (1 / 1.008);
|
||||
|
||||
// Deaths can only occur during X% of clashes
|
||||
if (Math.random() < 0.65) return;
|
||||
|
||||
for (let i = this.members.length - 1; i >= 0; --i) {
|
||||
const member = this.members[i];
|
||||
|
||||
// Only members assigned to Territory Warfare can die
|
||||
if (member.task !== "Territory Warfare") continue;
|
||||
|
||||
// Chance to die is decreased based on defense
|
||||
const modifiedDeathChance = baseDeathChance / Math.pow(member.def, 0.6);
|
||||
if (Math.random() < modifiedDeathChance) {
|
||||
this.killMember(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canRecruitMember(): boolean {
|
||||
if (this.members.length >= GangConstants.MaximumGangMembers) return false;
|
||||
return (this.respect >= this.getRespectNeededToRecruitMember());
|
||||
}
|
||||
|
||||
getRespectNeededToRecruitMember(): number {
|
||||
// First N gang members are free (can be recruited at 0 respect)
|
||||
const numFreeMembers = 3;
|
||||
if (this.members.length < numFreeMembers) return 0;
|
||||
|
||||
const i = this.members.length - (numFreeMembers - 1);
|
||||
return Math.pow(5, i);
|
||||
}
|
||||
|
||||
recruitMember(name: string): boolean {
|
||||
name = String(name);
|
||||
if (name === "" || !this.canRecruitMember()) return false;
|
||||
|
||||
// Check for already-existing names
|
||||
const sameNames = this.members.filter((m) => m.name === name);
|
||||
if (sameNames.length >= 1) return false;
|
||||
|
||||
const member = new GangMember(name);
|
||||
this.members.push(member);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Money and Respect gains multiplied by this number (< 1)
|
||||
getWantedPenalty(): number {
|
||||
return (this.respect) / (this.respect + this.wanted);
|
||||
}
|
||||
|
||||
|
||||
//Calculates power GAIN, which is added onto the Gang's existing power
|
||||
calculatePower(): number {
|
||||
let memberTotal = 0;
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
if (!GangMemberTasks.hasOwnProperty(this.members[i].task) ||
|
||||
this.members[i].task !== "Territory Warfare") continue;
|
||||
memberTotal += this.members[i].calculatePower();
|
||||
}
|
||||
return (0.015 * this.getTerritory() * memberTotal);
|
||||
}
|
||||
|
||||
|
||||
killMember(member: GangMember): void {
|
||||
// Player loses a percentage of total respect, plus whatever respect that member has earned
|
||||
const totalRespect = this.respect;
|
||||
const lostRespect = (0.05 * totalRespect) + member.earnedRespect;
|
||||
this.respect = Math.max(0, totalRespect - lostRespect);
|
||||
|
||||
for (let i = 0; i < this.members.length; ++i) {
|
||||
if (member.name === this.members[i].name) {
|
||||
this.members.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify of death
|
||||
if (this.notifyMemberDeath) {
|
||||
dialogBoxCreate(`${member.name} was killed in a gang clash! You lost ${lostRespect} respect`);
|
||||
}
|
||||
}
|
||||
|
||||
ascendMember(member: GangMember, workerScript?: WorkerScript): IAscensionResult {
|
||||
try {
|
||||
const res = member.ascend();
|
||||
this.respect = Math.max(1, this.respect - res.respect);
|
||||
if (workerScript) {
|
||||
workerScript.log('ascend', `Ascended Gang member ${member.name}`);
|
||||
}
|
||||
return res;
|
||||
} catch(e) {
|
||||
if (workerScript == null) {
|
||||
exceptionAlert(e);
|
||||
}
|
||||
throw e; // Re-throw, will be caught in the Netscript Function
|
||||
}
|
||||
}
|
||||
|
||||
// Cost of upgrade gets cheaper as gang increases in respect + power
|
||||
getDiscount(): number {
|
||||
const power = this.getPower();
|
||||
const respect = this.respect;
|
||||
|
||||
const respectLinearFac = 5e6;
|
||||
const powerLinearFac = 1e6;
|
||||
const discount = Math.pow(respect, 0.01) +
|
||||
respect / respectLinearFac +
|
||||
Math.pow(power, 0.01) +
|
||||
power / powerLinearFac - 1;
|
||||
return Math.max(1, discount);
|
||||
}
|
||||
|
||||
// Returns only valid tasks for this gang. Excludes 'Unassigned'
|
||||
getAllTaskNames(): string[] {
|
||||
return Object.keys(GangMemberTasks).filter((taskName: string) => {
|
||||
const task = GangMemberTasks[taskName];
|
||||
if (task == null) return false;
|
||||
if (task.name === "Unassigned") return false;
|
||||
// yes you need both checks
|
||||
return this.isHackingGang === task.isHacking ||
|
||||
!this.isHackingGang === task.isCombat;
|
||||
});
|
||||
}
|
||||
|
||||
getUpgradeCost(upg: GangMemberUpgrade | null): number {
|
||||
if (upg == null) { return Infinity; }
|
||||
return upg.cost/this.getDiscount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the current object to a JSON save state.
|
||||
*/
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("Gang", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiatizes a Gang object from a JSON save state.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static fromJSON(value: any): Gang {
|
||||
return Generic_fromJSON(Gang, value.data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reviver.constructors.Gang = Gang;
|
320
src/Gang/GangMember.ts
Normal file
320
src/Gang/GangMember.ts
Normal file
@ -0,0 +1,320 @@
|
||||
import { GangMemberTask } from "./GangMemberTask";
|
||||
import { GangMemberTasks } from "./GangMemberTasks";
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
import { GangMemberUpgrades } from "./GangMemberUpgrades";
|
||||
import { IAscensionResult } from "./IAscensionResult";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { GangConstants } from "./data/Constants";
|
||||
import { AllGangs } from "./AllGangs";
|
||||
import { IGang } from "./IGang";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
interface IMults {
|
||||
hack: number;
|
||||
str: number;
|
||||
def: number;
|
||||
dex: number;
|
||||
agi: number;
|
||||
cha: number;
|
||||
}
|
||||
|
||||
export class GangMember {
|
||||
name: string;
|
||||
task = "Unassigned";
|
||||
|
||||
earnedRespect = 0;
|
||||
|
||||
hack = 1;
|
||||
str = 1;
|
||||
def = 1;
|
||||
dex = 1;
|
||||
agi = 1;
|
||||
cha = 1;
|
||||
|
||||
hack_exp = 0;
|
||||
str_exp = 0;
|
||||
def_exp = 0;
|
||||
dex_exp = 0;
|
||||
agi_exp = 0;
|
||||
cha_exp = 0;
|
||||
|
||||
hack_mult = 1;
|
||||
str_mult = 1;
|
||||
def_mult = 1;
|
||||
dex_mult = 1;
|
||||
agi_mult = 1;
|
||||
cha_mult = 1;
|
||||
|
||||
hack_asc_points = 0;
|
||||
str_asc_points = 0;
|
||||
def_asc_points = 0;
|
||||
dex_asc_points = 0;
|
||||
agi_asc_points = 0;
|
||||
cha_asc_points = 0;
|
||||
|
||||
upgrades: string[] = []; // Names of upgrades
|
||||
augmentations: string[] = []; // Names of augmentations only
|
||||
|
||||
constructor(name = "") {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
calculateSkill(exp: number, mult = 1): number {
|
||||
return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1);
|
||||
}
|
||||
|
||||
calculateAscensionMult(points: number): number {
|
||||
return Math.max(Math.pow(points/4000, 0.7), 1);
|
||||
}
|
||||
|
||||
updateSkillLevels(): void {
|
||||
this.hack = this.calculateSkill(this.hack_exp, this.hack_mult * this.calculateAscensionMult(this.hack_asc_points));
|
||||
this.str = this.calculateSkill(this.str_exp, this.str_mult * this.calculateAscensionMult(this.str_asc_points));
|
||||
this.def = this.calculateSkill(this.def_exp, this.def_mult * this.calculateAscensionMult(this.def_asc_points));
|
||||
this.dex = this.calculateSkill(this.dex_exp, this.dex_mult * this.calculateAscensionMult(this.dex_asc_points));
|
||||
this.agi = this.calculateSkill(this.agi_exp, this.agi_mult * this.calculateAscensionMult(this.agi_asc_points));
|
||||
this.cha = this.calculateSkill(this.cha_exp, this.cha_mult * this.calculateAscensionMult(this.cha_asc_points));
|
||||
}
|
||||
|
||||
calculatePower(): number {
|
||||
return (this.hack +
|
||||
this.str +
|
||||
this.def +
|
||||
this.dex +
|
||||
this.agi +
|
||||
this.cha) / 95;
|
||||
}
|
||||
|
||||
assignToTask(taskName: string): boolean {
|
||||
if (!GangMemberTasks.hasOwnProperty(taskName)) {
|
||||
this.task = "Unassigned";
|
||||
return false;
|
||||
}
|
||||
this.task = taskName;
|
||||
return true;
|
||||
}
|
||||
|
||||
unassignFromTask(): void {
|
||||
this.task = "Unassigned";
|
||||
}
|
||||
|
||||
getTask(): GangMemberTask {
|
||||
// TODO(hydroflame): transfer that to a save file migration function
|
||||
// Backwards compatibility
|
||||
if ((this.task as any) instanceof GangMemberTask) {
|
||||
this.task = (this.task as any).name;
|
||||
}
|
||||
|
||||
if (GangMemberTasks.hasOwnProperty(this.task)) {
|
||||
return GangMemberTasks[this.task];
|
||||
}
|
||||
return GangMemberTasks["Unassigned"];
|
||||
}
|
||||
|
||||
calculateRespectGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseRespect === 0) return 0;
|
||||
let statWeight = (task.hackWeight/100) * this.hack +
|
||||
(task.strWeight/100) * this.str +
|
||||
(task.defWeight/100) * this.def +
|
||||
(task.dexWeight/100) * this.dex +
|
||||
(task.agiWeight/100) * this.agi +
|
||||
(task.chaWeight/100) * this.cha;
|
||||
statWeight -= (4 * task.difficulty);
|
||||
if (statWeight <= 0) return 0;
|
||||
const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.respect) / 100;
|
||||
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
|
||||
const respectMult = gang.getWantedPenalty();
|
||||
return 11 * task.baseRespect * statWeight * territoryMult * respectMult;
|
||||
}
|
||||
|
||||
calculateWantedLevelGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseWanted === 0) return 0;
|
||||
let statWeight = (task.hackWeight / 100) * this.hack +
|
||||
(task.strWeight / 100) * this.str +
|
||||
(task.defWeight / 100) * this.def +
|
||||
(task.dexWeight / 100) * this.dex +
|
||||
(task.agiWeight / 100) * this.agi +
|
||||
(task.chaWeight / 100) * this.cha;
|
||||
statWeight -= (3.5 * task.difficulty);
|
||||
if (statWeight <= 0) return 0;
|
||||
const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / 100;
|
||||
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
|
||||
if (task.baseWanted < 0) {
|
||||
return 0.4 * task.baseWanted * statWeight * territoryMult;
|
||||
}
|
||||
const calc = 7 * task.baseWanted / (Math.pow(3 * statWeight * territoryMult, 0.8));
|
||||
|
||||
// Put an arbitrary cap on this to prevent wanted level from rising too fast if the
|
||||
// denominator is very small. Might want to rethink formula later
|
||||
return Math.min(100, calc);
|
||||
}
|
||||
|
||||
calculateMoneyGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseMoney === 0) return 0;
|
||||
let statWeight = (task.hackWeight/100) * this.hack +
|
||||
(task.strWeight/100) * this.str +
|
||||
(task.defWeight/100) * this.def +
|
||||
(task.dexWeight/100) * this.dex +
|
||||
(task.agiWeight/100) * this.agi +
|
||||
(task.chaWeight/100) * this.cha;
|
||||
statWeight -= (3.2 * task.difficulty);
|
||||
if (statWeight <= 0) return 0;
|
||||
const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.money) / 100;
|
||||
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
|
||||
const respectMult = gang.getWantedPenalty();
|
||||
return 5 * task.baseMoney * statWeight * territoryMult * respectMult;
|
||||
}
|
||||
|
||||
expMult(): IMults {
|
||||
return {
|
||||
hack: (this.hack_mult-1)/4+1,
|
||||
str: (this.str_mult-1)/4+1,
|
||||
def: (this.def_mult-1)/4+1,
|
||||
dex: (this.dex_mult-1)/4+1,
|
||||
agi: (this.agi_mult-1)/4+1,
|
||||
cha: (this.cha_mult-1)/4+1,
|
||||
};
|
||||
}
|
||||
|
||||
gainExperience(numCycles = 1): void {
|
||||
const task = this.getTask();
|
||||
if (task === GangMemberTasks["Unassigned"]) return;
|
||||
const difficultyMult = Math.pow(task.difficulty, 0.9);
|
||||
const difficultyPerCycles = difficultyMult * numCycles;
|
||||
const weightDivisor = 1500;
|
||||
const expMult = this.expMult();
|
||||
this.hack_exp += (task.hackWeight / weightDivisor) * difficultyPerCycles * expMult.hack;
|
||||
this.str_exp += (task.strWeight / weightDivisor) * difficultyPerCycles * expMult.str;
|
||||
this.def_exp += (task.defWeight / weightDivisor) * difficultyPerCycles * expMult.def;
|
||||
this.dex_exp += (task.dexWeight / weightDivisor) * difficultyPerCycles * expMult.dex;
|
||||
this.agi_exp += (task.agiWeight / weightDivisor) * difficultyPerCycles * expMult.agi;
|
||||
this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles * expMult.cha;
|
||||
}
|
||||
|
||||
recordEarnedRespect(numCycles = 1, gang: IGang): void {
|
||||
this.earnedRespect += (this.calculateRespectGain(gang) * numCycles);
|
||||
}
|
||||
|
||||
getGainedAscensionPoints(): IMults {
|
||||
return {
|
||||
hack: Math.max(this.hack_exp - 1000, 0),
|
||||
str: Math.max(this.str_exp - 1000, 0),
|
||||
def: Math.max(this.def_exp - 1000, 0),
|
||||
dex: Math.max(this.dex_exp - 1000, 0),
|
||||
agi: Math.max(this.agi_exp - 1000, 0),
|
||||
cha: Math.max(this.cha_exp - 1000, 0),
|
||||
}
|
||||
}
|
||||
|
||||
canAscend(): boolean {
|
||||
const points = this.getGainedAscensionPoints();
|
||||
return points.hack > 0 ||
|
||||
points.str > 0 ||
|
||||
points.def > 0 ||
|
||||
points.dex > 0 ||
|
||||
points.agi > 0 ||
|
||||
points.cha > 0;
|
||||
}
|
||||
|
||||
getAscensionResults(): IMults {
|
||||
const points = this.getGainedAscensionPoints();
|
||||
return {
|
||||
hack: this.calculateAscensionMult(this.hack_asc_points+points.hack)/this.calculateAscensionMult(this.hack_asc_points),
|
||||
str: this.calculateAscensionMult(this.str_asc_points+points.str)/this.calculateAscensionMult(this.str_asc_points),
|
||||
def: this.calculateAscensionMult(this.def_asc_points+points.def)/this.calculateAscensionMult(this.def_asc_points),
|
||||
dex: this.calculateAscensionMult(this.dex_asc_points+points.dex)/this.calculateAscensionMult(this.dex_asc_points),
|
||||
agi: this.calculateAscensionMult(this.agi_asc_points+points.agi)/this.calculateAscensionMult(this.agi_asc_points),
|
||||
cha: this.calculateAscensionMult(this.cha_asc_points+points.cha)/this.calculateAscensionMult(this.cha_asc_points),
|
||||
}
|
||||
}
|
||||
|
||||
ascend(): IAscensionResult {
|
||||
const res = this.getAscensionResults();
|
||||
const points = this.getGainedAscensionPoints();
|
||||
this.hack_asc_points += points.hack;
|
||||
this.str_asc_points += points.str;
|
||||
this.def_asc_points += points.def;
|
||||
this.dex_asc_points += points.dex;
|
||||
this.agi_asc_points += points.agi;
|
||||
this.cha_asc_points += points.cha;
|
||||
|
||||
// Remove upgrades. Then re-calculate multipliers and stats
|
||||
this.upgrades.length = 0;
|
||||
this.hack_mult = 1;
|
||||
this.str_mult = 1;
|
||||
this.def_mult = 1;
|
||||
this.dex_mult = 1;
|
||||
this.agi_mult = 1;
|
||||
this.cha_mult = 1;
|
||||
for (let i = 0; i < this.augmentations.length; ++i) {
|
||||
const aug = GangMemberUpgrades[this.augmentations[i]];
|
||||
this.applyUpgrade(aug);
|
||||
}
|
||||
|
||||
// Clear exp and recalculate stats
|
||||
this.hack_exp = 0;
|
||||
this.str_exp = 0;
|
||||
this.def_exp = 0;
|
||||
this.dex_exp = 0;
|
||||
this.agi_exp = 0;
|
||||
this.cha_exp = 0;
|
||||
this.updateSkillLevels();
|
||||
|
||||
const respectToDeduct = this.earnedRespect;
|
||||
this.earnedRespect = 0;
|
||||
return {
|
||||
respect: respectToDeduct,
|
||||
hack: res.hack,
|
||||
str: res.str,
|
||||
def: res.def,
|
||||
dex: res.dex,
|
||||
agi: res.agi,
|
||||
cha: res.cha,
|
||||
};
|
||||
}
|
||||
|
||||
applyUpgrade(upg: GangMemberUpgrade): void {
|
||||
if (upg.mults.str != null) this.str_mult *= upg.mults.str;
|
||||
if (upg.mults.def != null) this.def_mult *= upg.mults.def;
|
||||
if (upg.mults.dex != null) this.dex_mult *= upg.mults.dex;
|
||||
if (upg.mults.agi != null) this.agi_mult *= upg.mults.agi;
|
||||
if (upg.mults.cha != null) this.cha_mult *= upg.mults.cha;
|
||||
if (upg.mults.hack != null) this.hack_mult *= upg.mults.hack;
|
||||
}
|
||||
|
||||
buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: IGang): boolean {
|
||||
// Prevent purchasing of already-owned upgrades
|
||||
if (this.augmentations.includes(upg.name) ||
|
||||
this.upgrades.includes(upg.name)) return false;
|
||||
|
||||
if (player.money.lt(gang.getUpgradeCost(upg))) return false;
|
||||
player.loseMoney(gang.getUpgradeCost(upg));
|
||||
if (upg.type === "g") {
|
||||
this.augmentations.push(upg.name);
|
||||
} else {
|
||||
this.upgrades.push(upg.name);
|
||||
}
|
||||
this.applyUpgrade(upg);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the current object to a JSON save state.
|
||||
*/
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("GangMember", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiatizes a GangMember object from a JSON save state.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static fromJSON(value: any): GangMember {
|
||||
return Generic_fromJSON(GangMember, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
Reviver.constructors.GangMember = GangMember;
|
62
src/Gang/GangMemberTask.ts
Normal file
62
src/Gang/GangMemberTask.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { ITaskParams, ITerritory } from "./ITaskParams";
|
||||
|
||||
export class GangMemberTask {
|
||||
name: string;
|
||||
desc: string;
|
||||
|
||||
isHacking: boolean;
|
||||
isCombat: boolean;
|
||||
|
||||
baseRespect: number;
|
||||
baseWanted: number;
|
||||
baseMoney: number;
|
||||
|
||||
hackWeight: number;
|
||||
strWeight: number;
|
||||
defWeight: number;
|
||||
dexWeight: number;
|
||||
agiWeight: number;
|
||||
chaWeight: number;
|
||||
|
||||
difficulty: number;
|
||||
|
||||
territory: ITerritory;
|
||||
|
||||
// Defines tasks that Gang Members can work on
|
||||
constructor(name: string, desc: string, isHacking: boolean, isCombat: boolean, params: ITaskParams) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
|
||||
// Flags that describe whether this Task is applicable for Hacking/Combat gangs
|
||||
this.isHacking = isHacking;
|
||||
this.isCombat = isCombat;
|
||||
|
||||
// Base gain rates for respect/wanted/money
|
||||
this.baseRespect = params.baseRespect ? params.baseRespect : 0;
|
||||
this.baseWanted = params.baseWanted ? params.baseWanted : 0;
|
||||
this.baseMoney = params.baseMoney ? params.baseMoney : 0;
|
||||
|
||||
// Weighting for the effect that each stat has on the tasks effectiveness.
|
||||
// Weights must add up to 100
|
||||
this.hackWeight = params.hackWeight ? params.hackWeight : 0;
|
||||
this.strWeight = params.strWeight ? params.strWeight : 0;
|
||||
this.defWeight = params.defWeight ? params.defWeight : 0;
|
||||
this.dexWeight = params.dexWeight ? params.dexWeight : 0;
|
||||
this.agiWeight = params.agiWeight ? params.agiWeight : 0;
|
||||
this.chaWeight = params.chaWeight ? params.chaWeight : 0;
|
||||
|
||||
if (Math.round(this.hackWeight + this.strWeight + this.defWeight + this.dexWeight + this.agiWeight + this.chaWeight) != 100) {
|
||||
console.error(`GangMemberTask ${this.name} weights do not add up to 100`);
|
||||
}
|
||||
|
||||
// 1 - 100
|
||||
this.difficulty = params.difficulty ? params.difficulty : 1;
|
||||
|
||||
// Territory Factors. Exponential factors that dictate how territory affects gains
|
||||
// Formula: Territory Mutiplier = (Territory * 100) ^ factor / 100
|
||||
// So factor should be > 1 if something should scale exponentially with territory
|
||||
// and should be < 1 if it should have diminshing returns
|
||||
this.territory = params.territory ? params.territory : {money: 1, respect: 1, wanted: 1};
|
||||
}
|
||||
|
||||
}
|
12
src/Gang/GangMemberTasks.ts
Normal file
12
src/Gang/GangMemberTasks.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { gangMemberTasksMetadata } from "./data/tasks";
|
||||
import { GangMemberTask } from "./GangMemberTask";
|
||||
|
||||
export const GangMemberTasks: {
|
||||
[key: string]: GangMemberTask;
|
||||
} = {};
|
||||
|
||||
(function() {
|
||||
gangMemberTasksMetadata.forEach((e) => {
|
||||
GangMemberTasks[e.name] = new GangMemberTask(e.name, e.desc, e.isHacking, e.isCombat, e.params);
|
||||
});
|
||||
})();
|
66
src/Gang/GangMemberUpgrade.ts
Normal file
66
src/Gang/GangMemberUpgrade.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { IMults, UpgradeType } from "./data/upgrades";
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
|
||||
export class GangMemberUpgrade {
|
||||
name: string;
|
||||
cost: number;
|
||||
type: UpgradeType;
|
||||
desc: string;
|
||||
mults: IMults;
|
||||
|
||||
constructor(name = "", cost = 0, type: UpgradeType = UpgradeType.Weapon, mults: IMults = {}) {
|
||||
this.name = name;
|
||||
this.cost = cost;
|
||||
this.type = type;
|
||||
this.mults = mults;
|
||||
|
||||
this.desc = this.createDescription();
|
||||
}
|
||||
|
||||
createDescription(): string {
|
||||
const lines = ["Effects:"];
|
||||
if (this.mults.str != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.str-1, 0)} strength skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.str-1)/4, 2)} strength exp`);
|
||||
}
|
||||
if (this.mults.def != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.def-1, 0)} defense skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.def-1)/4, 2)} defense exp`);
|
||||
}
|
||||
if (this.mults.dex != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.dex-1, 0)} dexterity skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.dex-1)/4, 2)} dexterity exp`);
|
||||
}
|
||||
if (this.mults.agi != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.agi-1, 0)} agility skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.agi-1)/4, 2)} agility exp`);
|
||||
}
|
||||
if (this.mults.cha != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.cha-1, 0)} charisma skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.cha-1)/4, 2)} charisma exp`);
|
||||
}
|
||||
if (this.mults.hack != null) {
|
||||
lines.push(`+${numeralWrapper.formatPercentage(this.mults.hack-1, 0)} hacking skill`);
|
||||
lines.push(`+${numeralWrapper.formatPercentage((this.mults.hack-1)/4, 2)} hacking exp`);
|
||||
}
|
||||
return lines.join("<br>");
|
||||
}
|
||||
|
||||
// User friendly version of type.
|
||||
getType(): string {
|
||||
switch (this.type) {
|
||||
case UpgradeType.Weapon:
|
||||
return "Weapon";
|
||||
case UpgradeType.Armor:
|
||||
return "Armor";
|
||||
case UpgradeType.Vehicle:
|
||||
return "Vehicle";
|
||||
case UpgradeType.Rootkit:
|
||||
return "Rootkit";
|
||||
case UpgradeType.Augmentation:
|
||||
return "Augmentation";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
12
src/Gang/GangMemberUpgrades.ts
Normal file
12
src/Gang/GangMemberUpgrades.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { gangMemberUpgradesMetadata } from "./data/upgrades";
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
|
||||
export const GangMemberUpgrades: {
|
||||
[key: string]: GangMemberUpgrade;
|
||||
} = {};
|
||||
|
||||
(function() {
|
||||
gangMemberUpgradesMetadata.forEach((e) => {
|
||||
GangMemberUpgrades[e.name] = new GangMemberUpgrade(e.name, e.cost, e.upgType, e.mults);
|
||||
});
|
||||
})();
|
32
src/Gang/Helpers.tsx
Normal file
32
src/Gang/Helpers.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { IEngine } from "../IEngine";
|
||||
import { Root } from "./ui/Root";
|
||||
import { Gang } from "./Gang";
|
||||
import { Page, routing } from ".././ui/navigationTracking";
|
||||
|
||||
let gangContainer: HTMLElement;
|
||||
|
||||
(function() {
|
||||
function set(): void {
|
||||
const c = document.getElementById("gang-container");
|
||||
if(c === null) throw new Error("Could not find element 'gang-container'");
|
||||
gangContainer = c;
|
||||
document.removeEventListener("DOMContentLoaded", set);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", set);
|
||||
})();
|
||||
|
||||
|
||||
export function displayGangContent(engine: IEngine, gang: Gang, player: IPlayer): void {
|
||||
if (!routing.isOn(Page.Gang)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReactDOM.render(<Root
|
||||
engine={engine}
|
||||
gang={gang}
|
||||
player={player} />, gangContainer);
|
||||
}
|
9
src/Gang/IAscensionResult.ts
Normal file
9
src/Gang/IAscensionResult.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export interface IAscensionResult {
|
||||
respect: number;
|
||||
hack: number;
|
||||
str: number;
|
||||
def: number;
|
||||
dex: number;
|
||||
agi: number;
|
||||
cha: number;
|
||||
}
|
44
src/Gang/IGang.ts
Normal file
44
src/Gang/IGang.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||
import { GangMember } from "./GangMember";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
|
||||
export interface IGang {
|
||||
facName: string;
|
||||
members: GangMember[];
|
||||
wanted: number;
|
||||
respect: number;
|
||||
|
||||
isHackingGang: boolean;
|
||||
|
||||
respectGainRate: number;
|
||||
wantedGainRate: number;
|
||||
moneyGainRate: number;
|
||||
|
||||
storedCycles: number;
|
||||
|
||||
storedTerritoryAndPowerCycles: number;
|
||||
|
||||
territoryClashChance: number;
|
||||
territoryWarfareEngaged: boolean;
|
||||
|
||||
notifyMemberDeath: boolean;
|
||||
|
||||
getPower(): number;
|
||||
getTerritory(): number;
|
||||
process(numCycles: number, player: IPlayer): void;
|
||||
processGains(numCycles: number, player: IPlayer): void;
|
||||
processTerritoryAndPowerGains(numCycles: number): void;
|
||||
processExperienceGains(numCycles: number): void;
|
||||
clash(won: boolean): void;
|
||||
canRecruitMember(): boolean;
|
||||
getRespectNeededToRecruitMember(): number;
|
||||
recruitMember(name: string): boolean;
|
||||
getWantedPenalty(): number;
|
||||
calculatePower(): number;
|
||||
killMember(member: GangMember): void;
|
||||
ascendMember(member: GangMember, workerScript: WorkerScript): void;
|
||||
getDiscount(): number;
|
||||
getAllTaskNames(): string[];
|
||||
getUpgradeCost(upg: GangMemberUpgrade): number;
|
||||
}
|
20
src/Gang/ITaskParams.ts
Normal file
20
src/Gang/ITaskParams.ts
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
export interface ITerritory {
|
||||
money: number;
|
||||
respect: number;
|
||||
wanted: number;
|
||||
}
|
||||
|
||||
export interface ITaskParams {
|
||||
baseRespect?: number;
|
||||
baseWanted?: number;
|
||||
baseMoney?: number;
|
||||
hackWeight?: number;
|
||||
strWeight?: number;
|
||||
defWeight?: number;
|
||||
dexWeight?: number;
|
||||
agiWeight?: number;
|
||||
chaWeight?: number;
|
||||
difficulty?: number;
|
||||
territory?: ITerritory;
|
||||
}
|
24
src/Gang/data/Constants.ts
Normal file
24
src/Gang/data/Constants.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export const GangConstants: {
|
||||
GangRespectToReputationRatio: number;
|
||||
MaximumGangMembers: number;
|
||||
CyclesPerTerritoryAndPowerUpdate: number;
|
||||
AscensionMultiplierRatio: number;
|
||||
Names: string[];
|
||||
} = {
|
||||
// Respect is divided by this to get rep gain
|
||||
GangRespectToReputationRatio: 25,
|
||||
MaximumGangMembers: 12,
|
||||
CyclesPerTerritoryAndPowerUpdate: 100,
|
||||
// Portion of upgrade multiplier that is kept after ascending
|
||||
AscensionMultiplierRatio: .15,
|
||||
// Names of possible Gangs
|
||||
Names: [
|
||||
"Slum Snakes",
|
||||
"Tetrads",
|
||||
"The Syndicate",
|
||||
"The Dark Army",
|
||||
"Speakers for the Dead",
|
||||
"NiteSec",
|
||||
"The Black Hand",
|
||||
],
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
import { ITaskParams } from "../ITaskParams";
|
||||
/* tslint:disable:max-line-length */
|
||||
|
||||
/**
|
||||
@ -29,7 +30,7 @@ export interface IGangMemberTaskMetadata {
|
||||
* An object containing weighting parameters for the task. These parameters are used for
|
||||
* various calculations (respect gain, wanted gain, etc.)
|
||||
*/
|
||||
params?: any;
|
||||
params: ITaskParams;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -254,7 +255,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
|
||||
name: "Train Combat",
|
||||
params: {
|
||||
strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 25,
|
||||
difficulty: 5,
|
||||
difficulty: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -262,7 +263,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
|
||||
isCombat: true,
|
||||
isHacking: true,
|
||||
name: "Train Hacking",
|
||||
params: {hackWeight: 100, difficulty: 8},
|
||||
params: {hackWeight: 100, difficulty: 45},
|
||||
},
|
||||
{
|
||||
desc: "Assign this gang member to train their charisma",
|
||||
@ -281,4 +282,4 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [
|
||||
difficulty: 5,
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
@ -1,12 +1,29 @@
|
||||
export interface IMults {
|
||||
hack?: number;
|
||||
str?: number;
|
||||
def?: number;
|
||||
dex?: number;
|
||||
agi?: number;
|
||||
cha?: number;
|
||||
}
|
||||
|
||||
export enum UpgradeType {
|
||||
Weapon = "w",
|
||||
Armor = "a",
|
||||
Vehicle = "v",
|
||||
Rootkit = "r",
|
||||
Augmentation = "g",
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the parameters that can be used to initialize and describe a GangMemberUpgrade
|
||||
* (defined in Gang.js)
|
||||
*/
|
||||
export interface IGangMemberUpgradeMetadata {
|
||||
cost: number;
|
||||
mults: any;
|
||||
mults: IMults;
|
||||
name: string;
|
||||
upgType: string;
|
||||
upgType: UpgradeType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -18,192 +35,192 @@ export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [
|
||||
cost: 1e6,
|
||||
mults: {str: 1.04, def: 1.04},
|
||||
name: "Baseball Bat",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 12e6,
|
||||
mults: {str: 1.08, def: 1.08, dex: 1.08},
|
||||
name: "Katana",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 25e6,
|
||||
mults: {str: 1.1, def: 1.1, dex: 1.1, agi: 1.1},
|
||||
name: "Glock 18C",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 50e6,
|
||||
mults: {str: 1.12, def: 1.1, agi: 1.1},
|
||||
name: "P90C",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 60e6,
|
||||
mults: {str: 1.2, def: 1.15},
|
||||
name: "Steyr AUG",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 100e6,
|
||||
mults: {str: 1.25, def: 1.2},
|
||||
name: "AK-47",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 150e6,
|
||||
mults: {str: 1.3, def: 1.25},
|
||||
name: "M15A10 Assault Rifle",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 225e6,
|
||||
mults: {str: 1.3, dex: 1.25, agi: 1.3},
|
||||
name: "AWM Sniper Rifle",
|
||||
upgType: "w",
|
||||
upgType: UpgradeType.Weapon,
|
||||
},
|
||||
{
|
||||
cost: 2e6,
|
||||
mults: {def: 1.04},
|
||||
name: "Bulletproof Vest",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 5e6,
|
||||
mults: {def: 1.08},
|
||||
name: "Full Body Armor",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 25e6,
|
||||
mults: {def: 1.15, agi: 1.15},
|
||||
name: "Liquid Body Armor",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 40e6,
|
||||
mults: {def: 1.2},
|
||||
name: "Graphene Plating Armor",
|
||||
upgType: "a",
|
||||
upgType: UpgradeType.Armor,
|
||||
},
|
||||
{
|
||||
cost: 3e6,
|
||||
mults: {agi: 1.04, cha: 1.04},
|
||||
name: "Ford Flex V20",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 9e6,
|
||||
mults: {agi: 1.08, cha: 1.08},
|
||||
name: "ATX1070 Superbike",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 18e6,
|
||||
mults: {agi: 1.12, cha: 1.12},
|
||||
name: "Mercedes-Benz S9001",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 30e6,
|
||||
mults: {agi: 1.16, cha: 1.16},
|
||||
name: "White Ferrari",
|
||||
upgType: "v",
|
||||
upgType: UpgradeType.Vehicle,
|
||||
},
|
||||
{
|
||||
cost: 5e6,
|
||||
mults: {hack: 1.05},
|
||||
name: "NUKE Rootkit",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 25e6,
|
||||
mults: {hack: 1.1},
|
||||
name: "Soulstealer Rootkit",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 75e6,
|
||||
mults: {hack: 1.15},
|
||||
name: "Demon Rootkit",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 40e6,
|
||||
mults: {hack: 1.12},
|
||||
name: "Hmap Node",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 75e6,
|
||||
mults: {hack: 1.15},
|
||||
name: "Jack the Ripper",
|
||||
upgType: "r",
|
||||
upgType: UpgradeType.Rootkit,
|
||||
},
|
||||
{
|
||||
cost: 10e9,
|
||||
mults: {str: 1.3, dex: 1.3},
|
||||
name: "Bionic Arms",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 10e9,
|
||||
mults: {agi: 1.6},
|
||||
name: "Bionic Legs",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 15e9,
|
||||
mults: {str: 1.15, def: 1.15, dex: 1.15, agi: 1.15},
|
||||
name: "Bionic Spine",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 20e9,
|
||||
mults: {str: 1.4, def: 1.4},
|
||||
name: "BrachiBlades",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 12e9,
|
||||
mults: {str: 1.2, def: 1.2},
|
||||
name: "Nanofiber Weave",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 25e9,
|
||||
mults: {str: 1.5, agi: 1.5},
|
||||
name: "Synthetic Heart",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 15e9,
|
||||
mults: {str: 1.3, def: 1.3},
|
||||
name: "Synfibril Muscle",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 5e9,
|
||||
mults: {hack: 1.05},
|
||||
name: "BitWire",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 10e9,
|
||||
mults: {hack: 1.15},
|
||||
name: "Neuralstimulator",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 7.5e9,
|
||||
mults: {hack: 1.1},
|
||||
name: "DataJack",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
{
|
||||
cost: 50e9,
|
||||
mults: {str: 1.7, def: 1.7},
|
||||
name: "Graphene Bone Lacings",
|
||||
upgType: "g",
|
||||
upgType: UpgradeType.Augmentation,
|
||||
},
|
||||
];
|
70
src/Gang/ui/AscensionPopup.tsx
Normal file
70
src/Gang/ui/AscensionPopup.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* React Component for the content of the popup before the player confirms the
|
||||
* ascension of a gang member.
|
||||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
popupId: string;
|
||||
onAscend: () => void;
|
||||
}
|
||||
|
||||
export function AscensionPopup(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function confirm(): void {
|
||||
props.onAscend();
|
||||
const res = props.gang.ascendMember(props.member);
|
||||
dialogBoxCreate(<p>
|
||||
You ascended {props.member.name}!<br />
|
||||
<br />
|
||||
Your gang lost {numeralWrapper.formatRespect(res.respect)} respect.<br />
|
||||
<br />
|
||||
{props.member.name} gained the following stat multipliers for ascending:<br />
|
||||
Hacking: x{numeralWrapper.format(res.hack, '0.000')}<br />
|
||||
Strength: x{numeralWrapper.format(res.str, '0.000')}<br />
|
||||
Defense: x{numeralWrapper.format(res.def, '0.000')}<br />
|
||||
Dexterity: x{numeralWrapper.format(res.dex, '0.000')}<br />
|
||||
Agility: x{numeralWrapper.format(res.agi, '0.000')}<br />
|
||||
Charisma: x{numeralWrapper.format(res.cha, '0.000')}<br />
|
||||
</p>);
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
const ascendBenefits = props.member.getAscensionResults();
|
||||
|
||||
return (<>
|
||||
<pre>
|
||||
Are you sure you want to ascend this member? They will lose all of<br />
|
||||
their non-Augmentation upgrades and their stats will reset back to 1.<br />
|
||||
<br />
|
||||
Furthermore, your gang will lose {numeralWrapper.formatRespect(props.member.earnedRespect)} respect<br />
|
||||
<br />
|
||||
In return, they will gain the following permanent boost to stat multipliers:<br />
|
||||
Hacking: x{numeralWrapper.format(ascendBenefits.hack, '0.000')}<br />
|
||||
Strength: x{numeralWrapper.format(ascendBenefits.str, '0.000')}<br />
|
||||
Defense: x{numeralWrapper.format(ascendBenefits.def, '0.000')}<br />
|
||||
Dexterity: x{numeralWrapper.format(ascendBenefits.dex, '0.000')}<br />
|
||||
Agility: x{numeralWrapper.format(ascendBenefits.agi, '0.000')}<br />
|
||||
Charisma: x{numeralWrapper.format(ascendBenefits.cha, '0.000')}<br />
|
||||
</pre>
|
||||
<button className="std-button" onClick={confirm}>Ascend</button>
|
||||
<button className="std-button" onClick={cancel}>Cancel</button>
|
||||
</>);
|
||||
}
|
29
src/Gang/ui/BonusTime.tsx
Normal file
29
src/Gang/ui/BonusTime.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* React Component for displaying the bonus time remaining.
|
||||
*/
|
||||
import * as React from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
}
|
||||
|
||||
export function BonusTime(props: IProps): React.ReactElement {
|
||||
const CyclerPerSecond = 1000 / CONSTANTS._idleSpeed;
|
||||
if (props.gang.storedCycles / CyclerPerSecond*1000 <= 5000) return (<></>);
|
||||
const bonusMillis = props.gang.storedCycles / CyclerPerSecond * 1000;
|
||||
return (<>
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Bonus time: {convertTimeMsToTimeElapsedString(bonusMillis)}
|
||||
<span className="tooltiptext noselect">
|
||||
You gain bonus time while offline or when the game is inactive
|
||||
(e.g. when the tab is throttled by the browser). Bonus time
|
||||
makes the Gang mechanic progress faster, up to 5x the normal
|
||||
speed.
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
</>);
|
||||
}
|
22
src/Gang/ui/GangMemberAccordion.tsx
Normal file
22
src/Gang/ui/GangMemberAccordion.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* React Component for a gang member on the management subpage.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { Accordion } from "../../ui/React/Accordion";
|
||||
import { GangMemberAccordionContent } from "./GangMemberAccordionContent";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
member: GangMember;
|
||||
}
|
||||
|
||||
export function GangMemberAccordion(props: IProps): React.ReactElement {
|
||||
return <Accordion
|
||||
panelInitiallyOpened={true}
|
||||
headerContent={<>{props.member.name}</>}
|
||||
panelContent={<GangMemberAccordionContent
|
||||
gang={props.gang}
|
||||
member={props.member} />} />
|
||||
}
|
36
src/Gang/ui/GangMemberAccordionContent.tsx
Normal file
36
src/Gang/ui/GangMemberAccordionContent.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* React Component for the content of the accordion of gang members on the
|
||||
* management subpage.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { GangMemberStats } from "./GangMemberStats";
|
||||
import { TaskSelector } from "./TaskSelector";
|
||||
import { TaskDescription } from "./TaskDescription";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
member: GangMember;
|
||||
}
|
||||
|
||||
export function GangMemberAccordionContent(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
return (<>
|
||||
<div className={"gang-member-info-div tooltip"}>
|
||||
<GangMemberStats
|
||||
onAscend={()=>setRerender(old => !old)}
|
||||
gang={props.gang}
|
||||
member={props.member} />
|
||||
</div>
|
||||
<div className={"gang-member-info-div"}>
|
||||
<TaskSelector
|
||||
onTaskChange={()=>setRerender(old => !old)}
|
||||
gang={props.gang}
|
||||
member={props.member} />
|
||||
</div>
|
||||
<div className={"gang-member-info-div"}>
|
||||
<TaskDescription member={props.member} />
|
||||
</div>
|
||||
</>);
|
||||
}
|
58
src/Gang/ui/GangMemberList.tsx
Normal file
58
src/Gang/ui/GangMemberList.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* React Component for the list of gang members on the management subpage.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { GangMemberUpgradePopup } from "./GangMemberUpgradePopup";
|
||||
import { GangMemberAccordion } from "./GangMemberAccordion";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { RecruitButton } from "./RecruitButton";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function GangMemberList(props: IProps): React.ReactElement {
|
||||
const [filter, setFilter] = useState("");
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
function openUpgradePopup(): void {
|
||||
const popupId = `gang-upgrade-popup`;
|
||||
createPopup(popupId, GangMemberUpgradePopup, {
|
||||
gang: props.gang,
|
||||
player: props.player,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
function onFilterChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setFilter(event.target.value);
|
||||
}
|
||||
|
||||
const members = props.gang.members.filter((member: GangMember) => member.name.indexOf(filter) > -1 || member.task.indexOf(filter) > -1);
|
||||
|
||||
return (<>
|
||||
<RecruitButton
|
||||
onRecruit={() => setRerender(old => !old)}
|
||||
gang={props.gang} />
|
||||
<br />
|
||||
<input
|
||||
className="text-input noselect"
|
||||
placeholder="Filter gang member"
|
||||
style={{margin: "5px", padding: "5px"}}
|
||||
value={filter}
|
||||
onChange={onFilterChange} />
|
||||
<a
|
||||
className="a-link-button"
|
||||
style={{display: 'inline-block'}}
|
||||
onClick={openUpgradePopup}>Manage Equipment</a>
|
||||
<ul>
|
||||
{members.map((member: GangMember) => <li key={member.name}>
|
||||
<GangMemberAccordion gang={props.gang} member={member} />
|
||||
</li>)}
|
||||
</ul>
|
||||
</>);
|
||||
}
|
77
src/Gang/ui/GangMemberStats.tsx
Normal file
77
src/Gang/ui/GangMemberStats.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* React Component for the first part of a gang member details.
|
||||
* Contains skills and exp.
|
||||
*/
|
||||
import React from "react";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { AscensionPopup } from "./AscensionPopup";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
onAscend: () => void;
|
||||
}
|
||||
|
||||
export function GangMemberStats(props: IProps): React.ReactElement {
|
||||
function ascend(): void {
|
||||
const popupId = `gang-management-ascend-member ${props.member.name}`;
|
||||
createPopup(popupId, AscensionPopup, {
|
||||
member: props.member,
|
||||
gang: props.gang,
|
||||
popupId: popupId,
|
||||
onAscend: props.onAscend,
|
||||
});
|
||||
}
|
||||
|
||||
function openAscensionHelp(): void {
|
||||
dialogBoxCreate(<>
|
||||
Ascending a Gang Member resets the member's progress and stats in
|
||||
exchange for a permanent boost to their stat multipliers.
|
||||
<br /><br />
|
||||
The additional stat multiplier that the Gang Member gains upon
|
||||
ascension is based on the amount of exp they have.
|
||||
<br /><br />
|
||||
Upon ascension, the member will lose all of its non-Augmentation
|
||||
Equipment and your gang will lose respect equal to the total respect
|
||||
earned by the member.
|
||||
</>);
|
||||
}
|
||||
|
||||
const asc = {
|
||||
hack: props.member.calculateAscensionMult(props.member.hack_asc_points),
|
||||
str: props.member.calculateAscensionMult(props.member.str_asc_points),
|
||||
def: props.member.calculateAscensionMult(props.member.def_asc_points),
|
||||
dex: props.member.calculateAscensionMult(props.member.dex_asc_points),
|
||||
agi: props.member.calculateAscensionMult(props.member.agi_asc_points),
|
||||
cha: props.member.calculateAscensionMult(props.member.cha_asc_points),
|
||||
};
|
||||
|
||||
return (<>
|
||||
<span className="tooltiptext smallfont">
|
||||
Hk: x{numeralWrapper.formatMultiplier(props.member.hack_mult * asc.hack)}(x{numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.hack)} Asc)<br />
|
||||
St: x{numeralWrapper.formatMultiplier(props.member.str_mult * asc.str)}(x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.str)} Asc)<br />
|
||||
Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * asc.def)}(x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.def)} Asc)<br />
|
||||
Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * asc.dex)}(x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.dex)} Asc)<br />
|
||||
Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * asc.agi)}(x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.agi)} Asc)<br />
|
||||
Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * asc.cha)}(x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x{numeralWrapper.formatMultiplier(asc.cha)} Asc)
|
||||
</span>
|
||||
<pre>
|
||||
Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)<br />
|
||||
Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)<br />
|
||||
Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)<br />
|
||||
Dexterity: {formatNumber(props.member.dex, 0)} ({numeralWrapper.formatExp(props.member.dex_exp)} exp)<br />
|
||||
Agility: {formatNumber(props.member.agi, 0)} ({numeralWrapper.formatExp(props.member.agi_exp)} exp)<br />
|
||||
Charisma: {formatNumber(props.member.cha, 0)} ({numeralWrapper.formatExp(props.member.cha_exp)} exp)<br />
|
||||
</pre>
|
||||
<br />
|
||||
{ props.member.canAscend() && <>
|
||||
<button className="accordion-button noselect" onClick={ascend}>Ascend</button>
|
||||
<div className="help-tip noselect" style={{marginTop: "5px"}} onClick={openAscensionHelp}>?</div>
|
||||
</>}
|
||||
</>);
|
||||
}
|
149
src/Gang/ui/GangMemberUpgradePopup.tsx
Normal file
149
src/Gang/ui/GangMemberUpgradePopup.tsx
Normal file
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* React Component for the popup that manages gang members upgrades
|
||||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { GangMemberUpgrades } from "../GangMemberUpgrades";
|
||||
import { GangMemberUpgrade } from "../GangMemberUpgrade";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { GangMember } from "../GangMember";
|
||||
import { Gang } from "../Gang";
|
||||
import { UpgradeType } from "../data/upgrades";
|
||||
|
||||
interface IPanelProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
function filterUpgrades(list: string[], type: UpgradeType): GangMemberUpgrade[] {
|
||||
return Object.keys(GangMemberUpgrades).filter((upgName: string) => {
|
||||
const upg = GangMemberUpgrades[upgName];
|
||||
if (props.player.money.lt(props.gang.getUpgradeCost(upg)))
|
||||
return false;
|
||||
if(upg.type !== type) return false;
|
||||
if(list.includes(upgName)) return false;
|
||||
return true;
|
||||
}).map((upgName: string) => GangMemberUpgrades[upgName]);
|
||||
}
|
||||
const weaponUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Weapon);
|
||||
const armorUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Armor);
|
||||
const vehicleUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Vehicle);
|
||||
const rootkitUpgrades = filterUpgrades(props.member.upgrades, UpgradeType.Rootkit);
|
||||
const augUpgrades = filterUpgrades(props.member.augmentations, UpgradeType.Augmentation);
|
||||
|
||||
function purchasedUpgrade(upgName: string): React.ReactElement {
|
||||
const upg = GangMemberUpgrades[upgName]
|
||||
return (<div key={upgName} className="gang-owned-upgrade tooltip">
|
||||
{upg.name}
|
||||
<span className="tooltiptext" dangerouslySetInnerHTML={{__html: upg.desc}} />
|
||||
</div>);
|
||||
}
|
||||
|
||||
function upgradeButton(upg: GangMemberUpgrade, left = false): React.ReactElement {
|
||||
function onClick(): void {
|
||||
props.member.buyUpgrade(upg, props.player, props.gang);
|
||||
setRerender(old => !old);
|
||||
}
|
||||
return (<a key={upg.name} className="a-link-button tooltip" style={{margin:"2px", padding:"2px", display:"block", fontSize:"11px"}} onClick={onClick}>
|
||||
{upg.name} - {Money(props.gang.getUpgradeCost(upg))}
|
||||
<span className={left?"tooltiptextleft":"tooltiptext"} dangerouslySetInnerHTML={{__html: upg.desc}} />
|
||||
</a>);
|
||||
}
|
||||
|
||||
const asc = {
|
||||
hack: props.member.calculateAscensionMult(props.member.hack_asc_points),
|
||||
str: props.member.calculateAscensionMult(props.member.str_asc_points),
|
||||
def: props.member.calculateAscensionMult(props.member.def_asc_points),
|
||||
dex: props.member.calculateAscensionMult(props.member.dex_asc_points),
|
||||
agi: props.member.calculateAscensionMult(props.member.agi_asc_points),
|
||||
cha: props.member.calculateAscensionMult(props.member.cha_asc_points),
|
||||
};
|
||||
return (<div style={{border: '1px solid white'}}>
|
||||
<h1>{props.member.name}({props.member.task})</h1>
|
||||
<pre style={{fontSize:"14px", display: "inline-block", width:"20%"}}>
|
||||
Hack: {props.member.hack} (x{formatNumber(props.member.hack_mult * asc.hack, 2)})<br />
|
||||
Str: {props.member.str} (x{formatNumber(props.member.str_mult * asc.str, 2)})<br />
|
||||
Def: {props.member.def} (x{formatNumber(props.member.def_mult * asc.def, 2)})<br />
|
||||
Dex: {props.member.dex} (x{formatNumber(props.member.dex_mult * asc.dex, 2)})<br />
|
||||
Agi: {props.member.agi} (x{formatNumber(props.member.agi_mult * asc.agi, 2)})<br />
|
||||
Cha: {props.member.cha} (x{formatNumber(props.member.cha_mult * asc.cha, 2)})
|
||||
</pre>
|
||||
<div className="gang-owned-upgrades-div noselect">
|
||||
Purchased Upgrades: {props.member.upgrades.map((upg: string) => purchasedUpgrade(upg))}
|
||||
{props.member.augmentations.map((upg: string) => purchasedUpgrade(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Weapons</h2>
|
||||
{weaponUpgrades.map(upg => upgradeButton(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Armor</h2>
|
||||
{armorUpgrades.map(upg => upgradeButton(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Vehicles</h2>
|
||||
{vehicleUpgrades.map(upg => upgradeButton(upg))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Rootkits</h2>
|
||||
{rootkitUpgrades.map(upg => upgradeButton(upg, true))}
|
||||
</div>
|
||||
<div className="noselect" style={{width: "20%", display: "inline-block"}}>
|
||||
<h2>Augmentations</h2>
|
||||
{augUpgrades.map(upg => upgradeButton(upg, true))}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
popupId: string;
|
||||
}
|
||||
|
||||
export function GangMemberUpgradePopup(props: IProps): React.ReactElement {
|
||||
const setRerender = useState(false)[1];
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
function closePopup(this: Window, ev: KeyboardEvent): void {
|
||||
if(ev.keyCode !== 27) return;
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener<'keydown'>('keydown', closePopup);
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => {
|
||||
clearInterval(id);
|
||||
window.removeEventListener<'keydown'>('keydown', closePopup);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (<>
|
||||
<input
|
||||
className="text-input noselect"
|
||||
value={filter}
|
||||
placeholder="Filter gang member"
|
||||
onChange={event => setFilter(event.target.value)} />
|
||||
<p className="tooltip" style={{marginLeft: '6px', display: 'inline-block'}}>
|
||||
Discount: -{numeralWrapper.formatPercentage(1 - 1 / props.gang.getDiscount())}
|
||||
<span className="tooltiptext noselect">
|
||||
You get a discount on equipment and upgrades based on your
|
||||
gang's respect and power. More respect and power leads to more
|
||||
discounts.
|
||||
</span>
|
||||
</p>
|
||||
{props.gang.members.map((member: GangMember) => <GangMemberUpgradePanel
|
||||
key={member.name}
|
||||
player={props.player}
|
||||
gang={props.gang}
|
||||
member={member} />)
|
||||
}
|
||||
</>);
|
||||
}
|
72
src/Gang/ui/GangStats.tsx
Normal file
72
src/Gang/ui/GangStats.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* React Component for the stats related to the gang, like total respect and
|
||||
* money per second.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { Gang } from "../Gang";
|
||||
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { MoneyRate } from "../../ui/React/MoneyRate";
|
||||
import { Reputation } from "../../ui/React/Reputation";
|
||||
import { AllGangs } from "../AllGangs";
|
||||
import { BonusTime } from "./BonusTime";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
}
|
||||
|
||||
export function GangStats(props: IProps): React.ReactElement {
|
||||
const territoryMult = AllGangs[props.gang.facName].territory * 100;
|
||||
let territoryStr;
|
||||
if (territoryMult <= 0) {
|
||||
territoryStr = formatNumber(0, 2);
|
||||
} else if (territoryMult >= 100) {
|
||||
territoryStr = formatNumber(100, 2);
|
||||
} else {
|
||||
territoryStr = formatNumber(territoryMult, 2);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Respect: {numeralWrapper.formatRespect(props.gang.respect)} ({numeralWrapper.formatRespect(5*props.gang.respectGainRate)} / sec)
|
||||
<span className="tooltiptext">
|
||||
Represents the amount of respect your gang has from other gangs and criminal organizations. Your respect affects the amount of money your gang members will earn, and also determines how much reputation you are earning with your gang's corresponding Faction.
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Wanted Level: {numeralWrapper.formatWanted(props.gang.wanted)} ({numeralWrapper.formatWanted(5*props.gang.wantedGainRate)} / sec)
|
||||
<span className="tooltiptext">
|
||||
Represents how much the gang is wanted by law enforcement. The higher your gang's wanted level, the harder it will be for your gang members to make money and earn respect. Note that the minimum wanted level is 1.
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Wanted Level Penalty: -{formatNumber((1 - props.gang.getWantedPenalty()) * 100, 2)}%
|
||||
<span className="tooltiptext">
|
||||
Penalty for respect and money gain rates due to Wanted Level
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<div>
|
||||
<p style={{display: "inline-block"}}>
|
||||
Money gain rate: {MoneyRate(5 * props.gang.moneyGainRate)}
|
||||
</p>
|
||||
</div>
|
||||
<br />
|
||||
<p className="tooltip" style={{display: "inline-block"}}>
|
||||
Territory: {territoryStr}%
|
||||
<span className="tooltiptext">
|
||||
The percentage of total territory your Gang controls
|
||||
</span>
|
||||
</p>
|
||||
<br />
|
||||
<p style={{display: "inline-block"}}>
|
||||
Faction reputation: {Reputation(Factions[props.gang.facName].playerReputation)}
|
||||
</p>
|
||||
<br />
|
||||
<BonusTime gang={props.gang} />
|
||||
</>);
|
||||
}
|
46
src/Gang/ui/ManagementSubpage.tsx
Normal file
46
src/Gang/ui/ManagementSubpage.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* React Component for the subpage that manages gang members, the main page.
|
||||
*/
|
||||
import React from "react";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { GangStats } from "./GangStats";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMemberList } from "./GangMemberList";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function ManagementSubpage(props: IProps): React.ReactElement {
|
||||
return (<div style={{display: 'block'}}>
|
||||
<p className="noselect" style={{width: "70%"}}>
|
||||
This page is used to manage your gang members and get an overview of
|
||||
your gang's stats.
|
||||
<br />
|
||||
<br />
|
||||
If a gang member is not earning much money or respect, the task that
|
||||
you have assigned to that member might be too difficult. Consider
|
||||
training that member's stats or choosing an easier task. The tasks
|
||||
closer to the top of the dropdown list are generally easier.
|
||||
Alternatively, the gang member's low production might be due to the
|
||||
fact that your wanted level is too high. Consider assigning a few
|
||||
members to the '{props.gang.isHackingGang?"Ethical Hacking":"Vigilante Justice"}'
|
||||
task to lower your wanted level.
|
||||
<br />
|
||||
<br />
|
||||
Installing Augmentations does NOT reset your progress with your
|
||||
Gang. Furthermore, after installing Augmentations, you will
|
||||
automatically be a member of whatever Faction you created your gang
|
||||
with.
|
||||
<br />
|
||||
<br />
|
||||
You can also manage your gang programmatically through Netscript
|
||||
using the Gang API
|
||||
</p>
|
||||
<br />
|
||||
<GangStats gang={props.gang} />
|
||||
<br />
|
||||
<GangMemberList gang={props.gang} player={props.player} />
|
||||
</div>);
|
||||
}
|
50
src/Gang/ui/RecruitButton.tsx
Normal file
50
src/Gang/ui/RecruitButton.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* React Component for the recruitment button and text on the gang main page.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { RecruitPopup } from "./RecruitPopup";
|
||||
import { GangConstants } from "../data/Constants";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
onRecruit: () => void;
|
||||
}
|
||||
|
||||
export function RecruitButton(props: IProps): React.ReactElement {
|
||||
if (props.gang.members.length >= GangConstants.MaximumGangMembers) {
|
||||
return (<></>);
|
||||
}
|
||||
|
||||
if (!props.gang.canRecruitMember()) {
|
||||
const respect = props.gang.getRespectNeededToRecruitMember();
|
||||
return (<>
|
||||
<a className="a-link-button-inactive"
|
||||
style={{display: 'inline-block', margin: '10px'}}>
|
||||
Recruit Gang Member
|
||||
</a>
|
||||
<p style={{margin: '10px', color: 'red', display: 'inline-block'}}>
|
||||
{formatNumber(respect, 2)} respect needed to recruit next member
|
||||
</p>
|
||||
</>);
|
||||
}
|
||||
|
||||
function onClick(): void {
|
||||
const popupId = "recruit-gang-member-popup";
|
||||
createPopup(popupId, RecruitPopup, {
|
||||
gang: props.gang,
|
||||
popupId: popupId,
|
||||
onRecruit: props.onRecruit,
|
||||
});
|
||||
}
|
||||
|
||||
return (<>
|
||||
<a className="a-link-button"
|
||||
onClick={onClick}
|
||||
style={{display: 'inline-block', margin: '10px'}}>
|
||||
Recruit Gang Member
|
||||
</a>
|
||||
</>);
|
||||
}
|
63
src/Gang/ui/RecruitPopup.tsx
Normal file
63
src/Gang/ui/RecruitPopup.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* React Component for the popup used to recruit new gang members.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { Gang } from "../Gang";
|
||||
import { removePopup } from "../../ui/React/createPopup";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
|
||||
interface IRecruitPopupProps {
|
||||
gang: Gang;
|
||||
popupId: string;
|
||||
onRecruit: () => void;
|
||||
}
|
||||
|
||||
export function RecruitPopup(props: IRecruitPopupProps): React.ReactElement {
|
||||
const [name, setName] = useState("");
|
||||
|
||||
function recruit(): void {
|
||||
if (name === "") {
|
||||
dialogBoxCreate("You must enter a name for your Gang member!");
|
||||
return;
|
||||
}
|
||||
if (!props.gang.canRecruitMember()) {
|
||||
dialogBoxCreate("You cannot recruit another Gang member!");
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, the only way this can fail is if you already
|
||||
// have a gang member with the same name
|
||||
if (!props.gang.recruitMember(name)) {
|
||||
dialogBoxCreate("You already have a gang member with this name!");
|
||||
return;
|
||||
}
|
||||
|
||||
props.onRecruit();
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
removePopup(props.popupId);
|
||||
}
|
||||
|
||||
function onKeyUp(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if(event.keyCode === 13) recruit();
|
||||
if(event.keyCode === 27) cancel();
|
||||
}
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setName(event.target.value);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<p className="noselect">Enter a name for your new Gang member:</p><br />
|
||||
<input autoFocus
|
||||
onKeyUp={onKeyUp}
|
||||
onChange={onChange}
|
||||
className="text-input noselect"
|
||||
type="text"
|
||||
placeholder="unique name" />
|
||||
<a className="std-button" onClick={recruit}>Recruit Gang Member</a>
|
||||
<a className="std-button" onClick={cancel}>Cancel</a>
|
||||
</>);
|
||||
}
|
49
src/Gang/ui/Root.tsx
Normal file
49
src/Gang/ui/Root.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* React Component for all the gang stuff.
|
||||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { ManagementSubpage } from "./ManagementSubpage";
|
||||
import { TerritorySubpage } from "./TerritorySubpage";
|
||||
import { IEngine } from "../../IEngine";
|
||||
import { Gang } from "../Gang";
|
||||
import { displayFactionContent } from "../../Faction/FactionHelpers";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
player: IPlayer;
|
||||
engine: IEngine;
|
||||
}
|
||||
|
||||
export function Root(props: IProps): React.ReactElement {
|
||||
const [management, setManagement] = useState(true);
|
||||
const setRerender = useState(false)[1];
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setRerender(old => !old), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
function back(): void {
|
||||
props.engine.loadFactionContent();
|
||||
displayFactionContent(props.gang.facName);
|
||||
}
|
||||
|
||||
return (<>
|
||||
<a className="a-link-button" style={{display: "inline-block"}}
|
||||
onClick={back}>Back</a>
|
||||
<a className={management?"a-link-button-inactive":"a-link-button"}
|
||||
style={{display: "inline-block"}}
|
||||
onClick={() => setManagement(true)}>
|
||||
Gang Management
|
||||
</a>
|
||||
<a className={!management?"a-link-button-inactive":"a-link-button"}
|
||||
style={{display: "inline-block"}}
|
||||
onClick={() => setManagement(false)}>
|
||||
Gang Territory
|
||||
</a>
|
||||
{management ?
|
||||
<ManagementSubpage gang={props.gang} player={props.player} /> :
|
||||
<TerritorySubpage gang={props.gang} />}
|
||||
</>);
|
||||
}
|
18
src/Gang/ui/TaskDescription.tsx
Normal file
18
src/Gang/ui/TaskDescription.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* React Component for left side of the gang member accordion, contains the
|
||||
* description of the task that member is currently doing.
|
||||
*/
|
||||
import React from "react";
|
||||
import { GangMemberTasks } from "../GangMemberTasks";
|
||||
import { GangMember } from "../GangMember";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
}
|
||||
|
||||
export function TaskDescription(props: IProps): React.ReactElement {
|
||||
const task = GangMemberTasks[props.member.task];
|
||||
const desc = task ? task.desc: GangMemberTasks["Unassigned"].desc;
|
||||
|
||||
return (<p className="inline noselect" dangerouslySetInnerHTML={{__html: desc}} />);
|
||||
}
|
47
src/Gang/ui/TaskSelector.tsx
Normal file
47
src/Gang/ui/TaskSelector.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* React Component for the middle part of the gang member accordion. Contains
|
||||
* the task selector as well as some stats.
|
||||
*/
|
||||
import React, { useState } from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { StatsTable } from "../../ui/React/StatsTable";
|
||||
import { MoneyRate } from "../../ui/React/MoneyRate";
|
||||
import { Gang } from "../Gang";
|
||||
import { GangMember } from "../GangMember";
|
||||
|
||||
interface IProps {
|
||||
member: GangMember;
|
||||
gang: Gang;
|
||||
onTaskChange: () => void;
|
||||
}
|
||||
|
||||
export function TaskSelector(props: IProps): React.ReactElement {
|
||||
const [currentTask, setCurrentTask] = useState(props.member.task);
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLSelectElement>): void {
|
||||
const task = event.target.value;
|
||||
props.member.assignToTask(task);
|
||||
setCurrentTask(task);
|
||||
props.onTaskChange();
|
||||
}
|
||||
|
||||
const tasks = props.gang.getAllTaskNames();
|
||||
|
||||
const data = [
|
||||
[`Money:`, MoneyRate(5*props.member.calculateMoneyGain(props.gang))],
|
||||
[`Respect:`, `${numeralWrapper.formatRespect(5*props.member.calculateRespectGain(props.gang))} / sec`],
|
||||
[`Wanted Level:`, `${numeralWrapper.formatWanted(5*props.member.calculateWantedLevelGain(props.gang))} / sec`],
|
||||
[`Total Respect:`, `${numeralWrapper.formatRespect(props.member.earnedRespect)}`],
|
||||
];
|
||||
|
||||
return (<>
|
||||
<select
|
||||
onChange={onChange}
|
||||
className="dropdown noselect"
|
||||
value={currentTask}>
|
||||
<option key={0} value={"---"}>---</option>
|
||||
{tasks.map((task: string, i: number) => <option key={i+1} value={task}>{task}</option>)}
|
||||
</select>
|
||||
<div>{StatsTable(data, null)}</div>
|
||||
</>);
|
||||
}
|
130
src/Gang/ui/TerritorySubpage.tsx
Normal file
130
src/Gang/ui/TerritorySubpage.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* React Component for the territory subpage.
|
||||
*/
|
||||
import React from "react";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { formatNumber } from "../../../utils/StringHelperFunctions";
|
||||
import { AllGangs } from "../AllGangs";
|
||||
import { Gang } from "../Gang";
|
||||
|
||||
interface IProps {
|
||||
gang: Gang;
|
||||
}
|
||||
|
||||
export function TerritorySubpage(props: IProps): React.ReactElement {
|
||||
function openWarfareHelp(): void {
|
||||
dialogBoxCreate("This percentage represents the chance you have of " +
|
||||
"'clashing' with with another gang. If you do not " +
|
||||
"wish to gain/lose territory, then keep this " +
|
||||
"percentage at 0% by not engaging in territory warfare.");
|
||||
}
|
||||
|
||||
function formatTerritory(n: number): string {
|
||||
const v = n * 100;
|
||||
if (v <= 0) {
|
||||
return formatNumber(0, 2);
|
||||
} else if (v >= 100) {
|
||||
return formatNumber(100, 2);
|
||||
} else {
|
||||
return formatNumber(v, 2);
|
||||
}
|
||||
}
|
||||
|
||||
const playerPower = AllGangs[props.gang.facName].power;
|
||||
function otherGangTerritory(name: string): React.ReactElement {
|
||||
const power = AllGangs[name].power
|
||||
const clashVictoryChance = playerPower / (power + playerPower);
|
||||
return (<span key={name}>
|
||||
<u>{name}</u><br />
|
||||
Power: {formatNumber(power, 6)}<br />
|
||||
Territory: {formatTerritory(AllGangs[name].territory)}%<br />
|
||||
Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}<br />
|
||||
<br />
|
||||
</span>);
|
||||
}
|
||||
|
||||
const gangNames = Object.keys(AllGangs).filter(g => g != props.gang.facName);
|
||||
|
||||
return (<div style={{width: '70%'}}>
|
||||
<p className="noselect">
|
||||
This page shows how much territory your Gang controls. This
|
||||
statistic is listed as a percentage, which represents how much of
|
||||
the total territory you control.
|
||||
<br />
|
||||
<br />
|
||||
Every ~20 seconds, your gang has a chance to 'clash' with other
|
||||
gangs. Your chance to win a clash depends on your gang's power,
|
||||
which is listed in the display below. Your gang's power slowly
|
||||
accumulates over time. The accumulation rate is determined by the
|
||||
stats of all Gang members you have assigned to the 'Territory
|
||||
Warfare' task. Gang members that are not assigned to this task do
|
||||
not contribute to your gang's power. Your gang also loses a small
|
||||
amount of power whenever you lose a clash.
|
||||
<br />
|
||||
<br />
|
||||
NOTE: Gang members assigned to 'Territory Warfare' can be killed
|
||||
during clashes. This can happen regardless of whether you win or
|
||||
lose the clash. A gang member being killed results in both respect
|
||||
and power loss for your gang.
|
||||
<br />
|
||||
<br />
|
||||
The amount of territory you have affects all aspects of your Gang
|
||||
members' production, including money, respect, and wanted level. It
|
||||
is very beneficial to have high territory control.
|
||||
<br />
|
||||
<br />
|
||||
</p>
|
||||
<input
|
||||
checked={props.gang.territoryWarfareEngaged}
|
||||
id="warfare"
|
||||
type="checkbox"
|
||||
style={{display: "inline-block", margin: "2px"}}
|
||||
onChange={(event)=> props.gang.territoryWarfareEngaged = event.target.checked}/>
|
||||
<label
|
||||
htmlFor="warfare"
|
||||
className="tooltip noselect"
|
||||
style={{color: "white", display: 'inline-block'}}>
|
||||
Engage in Territory Warfare
|
||||
<span className="tooltiptext" style={{display: "inline-block"}}>
|
||||
Engaging in Territory Warfare sets your clash chance to 100%.
|
||||
Disengaging will cause your clash chance to gradually decrease
|
||||
until it reaches 0%.
|
||||
</span>
|
||||
</label>
|
||||
<br />
|
||||
<p style={{display: 'inline-block'}}>
|
||||
Territory Clash Chance: {numeralWrapper.formatPercentage(props.gang.territoryClashChance, 3)}
|
||||
</p>
|
||||
<div
|
||||
className="help-tip noselect"
|
||||
style={{display: "inline-block"}}
|
||||
onClick={openWarfareHelp}>?</div>
|
||||
<br />
|
||||
|
||||
<input
|
||||
checked={props.gang.notifyMemberDeath}
|
||||
id="notify"
|
||||
type="checkbox"
|
||||
style={{display: "inline-block", margin: "2px"}}
|
||||
onChange={(event)=> props.gang.notifyMemberDeath = event.target.checked}/>
|
||||
<label htmlFor="warfare" className="tooltip noselect" style={{color: "white", display: 'inline-block'}}>
|
||||
Notify about Gang Member Deaths
|
||||
<span className="tooltiptext" style={{display: "inline-block"}}>
|
||||
If this is enabled, then you will receive a pop-up notifying you
|
||||
whenever one of your Gang Members dies in a territory clash.
|
||||
</span>
|
||||
</label>
|
||||
<br />
|
||||
<fieldset style={{display: "block", margin: "6px"}}>
|
||||
<p>
|
||||
<b><u>{props.gang.facName}</u></b><br />
|
||||
Power: {formatNumber(AllGangs[props.gang.facName].power, 6)}<br />
|
||||
Territory: {formatTerritory(AllGangs[props.gang.facName].territory)}%<br />
|
||||
<br />
|
||||
{gangNames.map(name => otherGangTerritory(name))}
|
||||
</p>
|
||||
</fieldset>
|
||||
</div>);
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ export function WireCuttingGame(props: IMinigameProps): React.ReactElement {
|
||||
return (<Grid container spacing={3}>
|
||||
<GameTimer millis={timer} onExpire={props.onFailure} />
|
||||
<Grid item xs={12}>
|
||||
<h1 className={"noselect"}>Cut the wires with the following properties!</h1>
|
||||
<h1 className={"noselect"}>Cut the wires with the following properties! (keyboard 1 to 9)</h1>
|
||||
{questions.map((question, i) => <h3 key={i}>{question.toString()}</h3>)}
|
||||
<pre>{(new Array(wires.length)).fill(0).map((_, i) => <span key={i}> {i+1} </span>)}</pre>
|
||||
{(new Array(8)).fill(0).map((_, i) => <div key={i}>
|
||||
|
@ -440,4 +440,4 @@ export const LocationsMetadata: IConstructorParams[] = [
|
||||
name: LocationName.WorldStockExchange,
|
||||
types: [LocationType.StockMarket],
|
||||
},
|
||||
];
|
||||
];
|
@ -65,7 +65,7 @@ export class LocationCity extends React.Component<IProps, any> {
|
||||
elems.push(<pre key={i}>{lineElems(lines[i])}</pre>)
|
||||
}
|
||||
|
||||
return elems;
|
||||
return <div className="noselect">{elems}</div>;
|
||||
}
|
||||
|
||||
listCity(): React.ReactNode {
|
||||
|
@ -158,7 +158,7 @@ export class GenericLocation extends React.Component<IProps, any> {
|
||||
return (
|
||||
<div>
|
||||
<StdButton onClick={this.props.returnToCity} style={this.btnStyle} text={"Return to World"} />
|
||||
<h1>
|
||||
<h1 className="noselect">
|
||||
{backdoorInstalled && !Settings.DisableTextEffects
|
||||
? <CorruptableText content={this.props.loc.name}/>
|
||||
: this.props.loc.name
|
||||
|
@ -74,7 +74,7 @@ export class LocationRoot extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="noselect">
|
||||
<h2>{this.state.city}</h2>
|
||||
<LocationCity city={city} enterLocation={this.enterLocation} />
|
||||
</div>
|
||||
@ -120,7 +120,7 @@ export class LocationRoot extends React.Component<IProps, IState> {
|
||||
|
||||
p.loseMoney(cost);
|
||||
p.travel(to);
|
||||
dialogBoxCreate(`You are now in ${to}!`);
|
||||
dialogBoxCreate(<span className="noselect">You are now in {to}!</span>);
|
||||
|
||||
// Dynamically update main menu
|
||||
if (p.firstTimeTraveled === false) {
|
||||
|
@ -44,7 +44,7 @@ export class TravelAgencyLocation extends React.Component<IProps, any> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="noselect">
|
||||
<p>
|
||||
From here, you can travel to any other city! A ticket
|
||||
costs {Money(CONSTANTS.TravelCost)}.
|
||||
|
@ -29,12 +29,10 @@ import {
|
||||
calculateWeakenTime,
|
||||
} from "./Hacking";
|
||||
import { calculateServerGrowth } from "./Server/formulas/grow";
|
||||
import {
|
||||
AllGangs,
|
||||
GangMemberUpgrades,
|
||||
GangMemberTasks,
|
||||
Gang,
|
||||
} from "./Gang";
|
||||
import { Gang } from "./Gang/Gang";
|
||||
import { AllGangs } from "./Gang/AllGangs";
|
||||
import { GangMemberTasks } from "./Gang/GangMemberTasks";
|
||||
import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades";
|
||||
import { Factions, factionExists } from "./Faction/Factions";
|
||||
import { joinFaction, purchaseAugmentation } from "./Faction/FactionHelpers";
|
||||
import { FactionWorkType } from "./Faction/FactionWorkTypeEnum";
|
||||
@ -3743,17 +3741,21 @@ function NetscriptFunctions(workerScript) {
|
||||
getEquipmentNames: function() {
|
||||
updateDynamicRam("getEquipmentNames", getRamCost("gang", "getEquipmentNames"));
|
||||
checkGangApiAccess("getEquipmentNames");
|
||||
return Player.gang.getAllUpgradeNames();
|
||||
return Object.keys(GangMemberUpgrades);
|
||||
},
|
||||
getEquipmentCost: function(equipName) {
|
||||
updateDynamicRam("getEquipmentCost", getRamCost("gang", "getEquipmentCost"));
|
||||
checkGangApiAccess("getEquipmentCost");
|
||||
return Player.gang.getUpgradeCost(equipName);
|
||||
const upg = GangMemberUpgrades[equipName];
|
||||
if(upg === null) return Infinity;
|
||||
return Player.gang.getUpgradeCost(upg);
|
||||
},
|
||||
getEquipmentType: function(equipName) {
|
||||
updateDynamicRam("getEquipmentType", getRamCost("gang", "getEquipmentType"));
|
||||
checkGangApiAccess("getEquipmentType");
|
||||
return Player.gang.getUpgradeType(equipName);
|
||||
const upg = GangMemberUpgrades[equipName];
|
||||
if (upg == null) return "";
|
||||
return upg.getType();
|
||||
},
|
||||
getEquipmentStats: function(equipName) {
|
||||
updateDynamicRam("getEquipmentStats", getRamCost("gang", "getEquipmentStats"));
|
||||
@ -3768,7 +3770,9 @@ function NetscriptFunctions(workerScript) {
|
||||
updateDynamicRam("purchaseEquipment", getRamCost("gang", "purchaseEquipment"));
|
||||
checkGangApiAccess("purchaseEquipment");
|
||||
const member = getGangMember("purchaseEquipment", memberName);
|
||||
const res = member.buyUpgrade(equipName, Player, Player.gang);
|
||||
const equipment = GangMemberUpgrades[equipName];
|
||||
if(!equipment) return false;
|
||||
const res = member.buyUpgrade(equipment, Player, Player.gang);
|
||||
if (res) {
|
||||
workerScript.log("purchaseEquipment", `Purchased '${equipName}' for Gang member '${memberName}'`);
|
||||
} else {
|
||||
@ -3781,6 +3785,7 @@ function NetscriptFunctions(workerScript) {
|
||||
updateDynamicRam("ascendMember", getRamCost("gang", "ascendMember"));
|
||||
checkGangApiAccess("ascendMember");
|
||||
const member = getGangMember("ascendMember", name);
|
||||
if(!member.canAscend()) return;
|
||||
return Player.gang.ascendMember(member, workerScript);
|
||||
},
|
||||
setTerritoryWarfare: function(engage) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { Gang } from "../../Gang";
|
||||
import { Gang } from "../../Gang/Gang";
|
||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
|
||||
|
@ -18,7 +18,7 @@ import { Engine } from "../../engine";
|
||||
import { Faction } from "../../Faction/Faction";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { displayFactionContent } from "../../Faction/FactionHelpers";
|
||||
import { resetGangs } from "../../Gang";
|
||||
import { resetGangs } from "../../Gang/AllGangs";
|
||||
import { hasHacknetServers } from "../../Hacknet/HacknetHelpers";
|
||||
import { Cities } from "../../Locations/Cities";
|
||||
import { Locations } from "../../Locations/Locations";
|
||||
|
@ -13,6 +13,5 @@ export const BaseCostPerSleeve = 10e12;
|
||||
export const PopupId = "covenant-sleeve-purchases-popup";
|
||||
|
||||
export function createSleevePurchasesFromCovenantPopup(p: IPlayer): void {
|
||||
const removePopupFn = removePopup.bind(null, PopupId);
|
||||
createPopup(PopupId, CovenantPurchasesRoot, { p: p, closeFn: removePopupFn });
|
||||
createPopup(PopupId, CovenantPurchasesRoot, { p: p, closeFn: () => removePopup(PopupId) });
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Root React component for the popup that lets player purchase Duplicate
|
||||
* Sleeves and Sleeve-related upgrades from The Covenant
|
||||
*/
|
||||
import * as React from "react";
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { CovenantSleeveUpgrades } from "./CovenantSleeveUpgrades";
|
||||
|
||||
@ -21,91 +21,73 @@ import { dialogBoxCreate } from "../../../../utils/DialogBox";
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
p: IPlayer;
|
||||
rerender: () => void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
update: number;
|
||||
}
|
||||
|
||||
export class CovenantPurchasesRoot extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
update: 0,
|
||||
}
|
||||
|
||||
this.rerender = this.rerender.bind(this);
|
||||
}
|
||||
export function CovenantPurchasesRoot(props: IProps): React.ReactElement {
|
||||
const [update, setUpdate] = useState(0);
|
||||
|
||||
/**
|
||||
* Get the cost to purchase a new Duplicate Sleeve
|
||||
*/
|
||||
purchaseCost(): number {
|
||||
return (this.props.p.sleevesFromCovenant + 1) * BaseCostPerSleeve;
|
||||
function purchaseCost(): number {
|
||||
return (props.p.sleevesFromCovenant + 1) * BaseCostPerSleeve;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a rerender by just changing an arbitrary state value
|
||||
*/
|
||||
rerender(): void {
|
||||
this.setState((state: IState) => ({
|
||||
update: state.update + 1,
|
||||
}));
|
||||
function rerender(): void {
|
||||
setUpdate(update + 1);
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
// Purchasing a new Duplicate Sleeve
|
||||
let purchaseDisabled = false;
|
||||
if (!this.props.p.canAfford(this.purchaseCost())) {
|
||||
purchaseDisabled = true;
|
||||
}
|
||||
if (this.props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) {
|
||||
purchaseDisabled = true;
|
||||
}
|
||||
const purchaseOnClick = (): void => {
|
||||
if (this.props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) { return; }
|
||||
|
||||
if (this.props.p.canAfford(this.purchaseCost())) {
|
||||
this.props.p.loseMoney(this.purchaseCost());
|
||||
this.props.p.sleevesFromCovenant += 1;
|
||||
this.props.p.sleeves.push(new Sleeve(this.props.p));
|
||||
this.rerender();
|
||||
} else {
|
||||
dialogBoxCreate(`You cannot afford to purchase a Duplicate Sleeve`, false);
|
||||
}
|
||||
}
|
||||
// Purchasing a new Duplicate Sleeve
|
||||
let purchaseDisabled = false;
|
||||
if (!props.p.canAfford(purchaseCost())) {
|
||||
purchaseDisabled = true;
|
||||
}
|
||||
if (props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) {
|
||||
purchaseDisabled = true;
|
||||
}
|
||||
|
||||
// Purchasing Upgrades for Sleeves
|
||||
const upgradePanels = [];
|
||||
for (let i = 0; i < this.props.p.sleeves.length; ++i) {
|
||||
const sleeve = this.props.p.sleeves[i];
|
||||
upgradePanels.push(
|
||||
<CovenantSleeveUpgrades {...this.props} sleeve={sleeve} index={i} rerender={this.rerender} key={i} />,
|
||||
)
|
||||
function purchaseOnClick(): void {
|
||||
if (props.p.sleevesFromCovenant >= MaxSleevesFromCovenant) return;
|
||||
|
||||
if (props.p.canAfford(purchaseCost())) {
|
||||
props.p.loseMoney(purchaseCost());
|
||||
props.p.sleevesFromCovenant += 1;
|
||||
props.p.sleeves.push(new Sleeve(props.p));
|
||||
rerender();
|
||||
} else {
|
||||
dialogBoxCreate(`You cannot afford to purchase a Duplicate Sleeve`, false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PopupCloseButton popup={PopupId} text={"Close"} />
|
||||
<p>
|
||||
Would you like to purchase an additional Duplicate Sleeve from The Covenant
|
||||
for {Money(this.purchaseCost())}?
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
These Duplicate Sleeves are permanent (they persist through BitNodes). You can
|
||||
purchase a total of {MaxSleevesFromCovenant} from The Covenant.
|
||||
</p>
|
||||
<StdButton disabled={purchaseDisabled} onClick={purchaseOnClick} text={"Purchase"} />
|
||||
<br /><br />
|
||||
<p>
|
||||
Here, you can also purchase upgrades for your Duplicate Sleeves. These upgrades
|
||||
are also permanent, meaning they persist across BitNodes.
|
||||
</p>
|
||||
{upgradePanels}
|
||||
</div>
|
||||
// Purchasing Upgrades for Sleeves
|
||||
const upgradePanels = [];
|
||||
for (let i = 0; i < props.p.sleeves.length; ++i) {
|
||||
const sleeve = props.p.sleeves[i];
|
||||
upgradePanels.push(
|
||||
<CovenantSleeveUpgrades {...props} sleeve={sleeve} index={i} rerender={rerender} key={i} />,
|
||||
)
|
||||
}
|
||||
|
||||
return (<div>
|
||||
<PopupCloseButton popup={PopupId} text={"Close"} />
|
||||
<p>
|
||||
Would you like to purchase an additional Duplicate Sleeve from The Covenant
|
||||
for {Money(purchaseCost())}?
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
These Duplicate Sleeves are permanent (they persist through BitNodes). You can
|
||||
purchase a total of {MaxSleevesFromCovenant} from The Covenant.
|
||||
</p>
|
||||
<StdButton disabled={purchaseDisabled} onClick={purchaseOnClick} text={"Purchase"} />
|
||||
<br /><br />
|
||||
<p>
|
||||
Here, you can also purchase upgrades for your Duplicate Sleeves. These upgrades
|
||||
are also permanent, meaning they persist across BitNodes.
|
||||
</p>
|
||||
{upgradePanels}
|
||||
</div>);
|
||||
}
|
||||
|
@ -333,7 +333,7 @@ function prestigeSourceFile(flume) {
|
||||
deleteStockMarket();
|
||||
}
|
||||
|
||||
if (Player.inGang()) { Player.gang.clearUI(); }
|
||||
if (Player.inGang()) clearGangUI();
|
||||
Player.gang = null;
|
||||
Player.corporation = null; resetIndustryResearchTrees();
|
||||
Player.bladeburner = null;
|
||||
|
@ -10,7 +10,7 @@ import { Engine } from "./engine";
|
||||
import { Factions, loadFactions } from "./Faction/Factions";
|
||||
import { loadFconf } from "./Fconf/Fconf";
|
||||
import { FconfSettings } from "./Fconf/FconfSettings";
|
||||
import { loadAllGangs, AllGangs } from "./Gang";
|
||||
import { loadAllGangs, AllGangs } from "./Gang/AllGangs";
|
||||
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
|
||||
import { Player, loadPlayer } from "./Player";
|
||||
import { AllServers, loadAllServers } from "./Server/AllServers";
|
||||
|
@ -32,6 +32,7 @@ import {
|
||||
processPassiveFactionRepGain,
|
||||
inviteToFaction,
|
||||
} from "./Faction/FactionHelpers";
|
||||
import { displayGangContent } from "./Gang/Helpers";
|
||||
import { displayInfiltrationContent } from "./Infiltration/Helper";
|
||||
import {
|
||||
getHackingWorkRepGain,
|
||||
@ -227,6 +228,7 @@ const Engine = {
|
||||
tutorialContent: null,
|
||||
infiltrationContent: null,
|
||||
stockMarketContent: null,
|
||||
gangContent: null,
|
||||
locationContent: null,
|
||||
workInProgressContent: null,
|
||||
redPillContent: null,
|
||||
@ -438,9 +440,10 @@ const Engine = {
|
||||
|
||||
loadGangContent: function() {
|
||||
Engine.hideAllContent();
|
||||
if (document.getElementById("gang-container") || Player.inGang()) {
|
||||
Player.gang.displayGangContent(Player);
|
||||
if (Player.inGang()) {
|
||||
Engine.Display.gangContent.style.display = "block";
|
||||
routing.navigateTo(Page.Gang);
|
||||
displayGangContent(this, Player.gang, Player);
|
||||
} else {
|
||||
Engine.loadTerminalContent();
|
||||
routing.navigateTo(Page.Terminal);
|
||||
@ -523,6 +526,9 @@ const Engine = {
|
||||
|
||||
Engine.Display.locationContent.style.display = "none";
|
||||
ReactDOM.unmountComponentAtNode(Engine.Display.locationContent);
|
||||
|
||||
Engine.Display.gangContent.style.display = "none";
|
||||
ReactDOM.unmountComponentAtNode(Engine.Display.gangContent);
|
||||
|
||||
Engine.Display.workInProgressContent.style.display = "none";
|
||||
Engine.Display.redPillContent.style.display = "none";
|
||||
@ -533,9 +539,6 @@ const Engine = {
|
||||
document.getElementById("gang-container").style.display = "none";
|
||||
}
|
||||
|
||||
if (Player.inGang()) {
|
||||
Player.gang.clearUI();
|
||||
}
|
||||
if (Player.corporation instanceof Corporation) {
|
||||
Player.corporation.clearUI();
|
||||
}
|
||||
@ -569,6 +572,7 @@ const Engine = {
|
||||
MainMenuLinks.Travel.classList.remove("active");
|
||||
MainMenuLinks.Job.classList.remove("active");
|
||||
MainMenuLinks.StockMarket.classList.remove("active");
|
||||
MainMenuLinks.Gang.classList.remove("active");
|
||||
MainMenuLinks.Bladeburner.classList.remove("active");
|
||||
MainMenuLinks.Corporation.classList.remove("active");
|
||||
MainMenuLinks.Gang.classList.remove("active");
|
||||
@ -866,9 +870,7 @@ const Engine = {
|
||||
}
|
||||
|
||||
if (Engine.Counters.updateDisplaysLong <= 0) {
|
||||
if (routing.isOn(Page.Gang) && Player.inGang()) {
|
||||
Player.gang.updateGangContent();
|
||||
} else if (routing.isOn(Page.ScriptEditor)) {
|
||||
if (routing.isOn(Page.ScriptEditor)) {
|
||||
updateScriptEditorContent();
|
||||
}
|
||||
Engine.Counters.updateDisplaysLong = 15;
|
||||
@ -1322,6 +1324,9 @@ const Engine = {
|
||||
Engine.Display.stockMarketContent = document.getElementById("stock-market-container");
|
||||
Engine.Display.stockMarketContent.style.display = "none";
|
||||
|
||||
Engine.Display.gangContent = document.getElementById("gang-container");
|
||||
Engine.Display.gangContent.style.display = "none";
|
||||
|
||||
Engine.Display.missionContent = document.getElementById("mission-container");
|
||||
Engine.Display.missionContent.style.display = "none";
|
||||
|
||||
@ -1450,6 +1455,7 @@ const Engine = {
|
||||
|
||||
MainMenuLinks.Gang.addEventListener("click", function() {
|
||||
Engine.loadGangContent();
|
||||
MainMenuLinks.Gang.classList.add('active');
|
||||
return false;
|
||||
});
|
||||
|
||||
|
@ -35,83 +35,83 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
|
||||
<ul id="mainmenu" class="mainmenu noscrollbar">
|
||||
<!-- Hacking dropdown -->
|
||||
<li id="hacking-menu-header-li">
|
||||
<button id="hacking-menu-header" class="mainmenu-accordion-header"> Hacking </button>
|
||||
<button id="hacking-menu-header" class="mainmenu-accordion-header noselect"> Hacking </button>
|
||||
</li>
|
||||
<li id="terminal-tab" class="mainmenu-accordion-panel">
|
||||
<li id="terminal-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="terminal-menu-link"> Terminal </button>
|
||||
</li>
|
||||
<li id="create-script-tab" class="mainmenu-accordion-panel">
|
||||
<li id="create-script-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="create-script-menu-link"> Create Script </button>
|
||||
</li>
|
||||
<li id="active-scripts-tab" class="mainmenu-accordion-panel">
|
||||
<li id="active-scripts-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="active-scripts-menu-link"> Active Scripts </button>
|
||||
</li>
|
||||
<li id="create-program-tab" class="mainmenu-accordion-panel">
|
||||
<li id="create-program-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="create-program-menu-link"> Create Program </button>
|
||||
<span id="create-program-notification" class="notification-off"> </span>
|
||||
</li>
|
||||
|
||||
<!-- Character dropdown -->
|
||||
<li id="character-menu-header-li">
|
||||
<button id="character-menu-header" class="mainmenu-accordion-header"> Character </button>
|
||||
<button id="character-menu-header" class="mainmenu-accordion-header noselect"> Character </button>
|
||||
</li>
|
||||
<li id="stats-tab" class="mainmenu-accordion-panel">
|
||||
<li id="stats-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="stats-menu-link"> Stats </button>
|
||||
</li>
|
||||
<li id="factions-tab" class="mainmenu-accordion-panel">
|
||||
<li id="factions-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="factions-menu-link"> Factions </button>
|
||||
<span id="factions-notification" class="notification-off"> </span>
|
||||
</li>
|
||||
<li id="augmentations-tab" class="mainmenu-accordion-panel">
|
||||
<li id="augmentations-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="augmentations-menu-link" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> Augmentations </button>
|
||||
<span id="augmentations-notification" class="notification-off"> </span>
|
||||
</li>
|
||||
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel">
|
||||
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="hacknet-nodes-menu-link"> Hacknet </button>
|
||||
</li>
|
||||
<li id="sleeves-tab" class="mainmenu-accordion-panel">
|
||||
<li id="sleeves-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="sleeves-menu-link"> Sleeves </button>
|
||||
</li>
|
||||
|
||||
<!-- World dropdown -->
|
||||
<li id="world-menu-header-li">
|
||||
<button id="world-menu-header" class="mainmenu-accordion-header">World</button>
|
||||
<button id="world-menu-header" class="mainmenu-accordion-header noselect">World</button>
|
||||
</li>
|
||||
<li id="city-tab" class="mainmenu-accordion-panel">
|
||||
<li id="city-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="city-menu-link"> City </button>
|
||||
</li>
|
||||
<li id="travel-tab" class="mainmenu-accordion-panel">
|
||||
<li id="travel-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="travel-menu-link"> Travel </button>
|
||||
</li>
|
||||
<li id="job-tab" class="mainmenu-accordion-panel">
|
||||
<li id="job-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="job-menu-link"> Job </button>
|
||||
</li>
|
||||
<li id="stock-market-tab" class="mainmenu-accordion-panel">
|
||||
<li id="stock-market-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="stock-market-menu-link"> Stock Market </button>
|
||||
</li>
|
||||
<li id="bladeburner-tab" class="mainmenu-accordion-panel">
|
||||
<li id="bladeburner-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="bladeburner-menu-link"> Bladeburner </button>
|
||||
</li>
|
||||
<li id="corporation-tab" class="mainmenu-accordion-panel">
|
||||
<li id="corporation-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="corporation-menu-link"> Corp </button>
|
||||
</li>
|
||||
<li id="gang-tab" class="mainmenu-accordion-panel">
|
||||
<li id="gang-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="gang-menu-link"> Gang </button>
|
||||
</li>
|
||||
|
||||
<li id="help-menu-header-li">
|
||||
<button id="help-menu-header" class="mainmenu-accordion-header"> Help </button>
|
||||
<button id="help-menu-header" class="mainmenu-accordion-header noselect"> Help </button>
|
||||
</li>
|
||||
<li id="milestones-tab" class="mainmenu-accordion-panel">
|
||||
<li id="milestones-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="milestones-menu-link"> Milestones </button>
|
||||
</li>
|
||||
<li id="tutorial-tab" class="mainmenu-accordion-panel">
|
||||
<li id="tutorial-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="tutorial-menu-link"> Tutorial </button>
|
||||
</li>
|
||||
<li id="options-tab" class="mainmenu-accordion-panel">
|
||||
<li id="options-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="options-menu-link"> Options </button>
|
||||
</li>
|
||||
<li id="dev-tab" class="mainmenu-accordion-panel">
|
||||
<li id="dev-tab" class="mainmenu-accordion-panel noselect">
|
||||
<button id="dev-menu-link"> Dev </button>
|
||||
</li>
|
||||
</ul>
|
||||
@ -284,6 +284,10 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
|
||||
<!-- React Component -->
|
||||
</div>
|
||||
|
||||
<div id="gang-container" class="generic-menupage-container">
|
||||
<!-- React Component -->
|
||||
</div>
|
||||
|
||||
<!-- Log Box -->
|
||||
<div id="log-box-container">
|
||||
<div id="log-box-content">
|
||||
@ -375,7 +379,7 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
|
||||
<div id="character-overview-text">
|
||||
<!-- ReactJS Component -->
|
||||
</div>
|
||||
<div class="character-quick-options">
|
||||
<div class="character-quick-options noselect">
|
||||
<button id="character-overview-save-button" class="character-overview-btn">Save Game</button>
|
||||
<button id="character-overview-options-button" class="character-overview-btn">Options</button>
|
||||
</div>
|
||||
|
@ -23,27 +23,14 @@ export class Accordion extends React.Component<IProps, IState> {
|
||||
this.handleHeaderClick = this.handleHeaderClick.bind(this);
|
||||
|
||||
this.state = {
|
||||
panelOpened: props.panelInitiallyOpened ? true : false,
|
||||
panelOpened: props.panelInitiallyOpened ? props.panelInitiallyOpened : false,
|
||||
}
|
||||
}
|
||||
|
||||
handleHeaderClick(e: React.MouseEvent<HTMLButtonElement>): void {
|
||||
const elem = e.currentTarget;
|
||||
elem.classList.toggle("active");
|
||||
|
||||
const panel: HTMLElement = elem.nextElementSibling as HTMLElement;
|
||||
const active = elem.classList.contains("active");
|
||||
if (active) {
|
||||
panel.style.display = "block";
|
||||
this.setState({
|
||||
panelOpened: true,
|
||||
});
|
||||
} else {
|
||||
panel.style.display = "none";
|
||||
this.setState({
|
||||
panelOpened: false,
|
||||
});
|
||||
}
|
||||
handleHeaderClick(): void {
|
||||
this.setState({
|
||||
panelOpened: !this.state.panelOpened,
|
||||
});
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
@ -52,6 +39,8 @@ export class Accordion extends React.Component<IProps, IState> {
|
||||
className = this.props.headerClass;
|
||||
}
|
||||
|
||||
if(this.state.panelOpened) className += " active"
|
||||
|
||||
return (
|
||||
<>
|
||||
<button className={className} onClick={this.handleHeaderClick}>
|
||||
@ -84,8 +73,9 @@ class AccordionPanel extends React.Component<IPanelProps, any> {
|
||||
className = this.props.panelClass;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={className} style={{display: this.props.opened ? "block" : "none"}}>
|
||||
{this.props.panelContent}
|
||||
</div>
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as React from "react";
|
||||
import React, { useState } from 'react';
|
||||
import { KEY } from "../../../utils/helpers/keyCodes";
|
||||
|
||||
import { CodingContract, CodingContractType, CodingContractTypes } from "../../CodingContracts";
|
||||
@ -12,23 +12,14 @@ type IProps = {
|
||||
onAttempt: (answer: string) => void;
|
||||
}
|
||||
|
||||
type IState = {
|
||||
answer: string;
|
||||
}
|
||||
export function CodingContractPopup(props: IProps): React.ReactElement {
|
||||
const [answer, setAnswer] = useState("");
|
||||
|
||||
export class CodingContractPopup extends React.Component<IProps, IState>{
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = { answer: ''};
|
||||
this.setAnswer = this.setAnswer.bind(this);
|
||||
this.onInputKeydown = this.onInputKeydown.bind(this);
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
setAnswer(event.target.value);
|
||||
}
|
||||
|
||||
setAnswer(event: React.ChangeEvent<HTMLInputElement>): void {
|
||||
this.setState({ answer: event.target.value });
|
||||
}
|
||||
|
||||
onInputKeydown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
// React just won't cooperate on this one.
|
||||
// "React.KeyboardEvent<HTMLInputElement>" seems like the right type but
|
||||
// whatever ...
|
||||
@ -36,31 +27,41 @@ export class CodingContractPopup extends React.Component<IProps, IState>{
|
||||
|
||||
if (event.keyCode === KEY.ENTER && value !== "") {
|
||||
event.preventDefault();
|
||||
this.props.onAttempt(this.state.answer);
|
||||
props.onAttempt(answer);
|
||||
} else if (event.keyCode === KEY.ESC) {
|
||||
event.preventDefault();
|
||||
this.props.onClose();
|
||||
props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const contractType: CodingContractType = CodingContractTypes[this.props.c.type];
|
||||
const description = [];
|
||||
for (const [i, value] of contractType.desc(this.props.c.data).split('\n').entries())
|
||||
description.push(<span key={i} dangerouslySetInnerHTML={{__html: value+'<br />'}}></span>);
|
||||
return (
|
||||
<div>
|
||||
<CopyableText value={this.props.c.type} tag={ClickableTag.Tag_h1} />
|
||||
<br/><br/>
|
||||
<p>You are attempting to solve a Coding Contract. You have {this.props.c.getMaxNumTries() - this.props.c.tries} tries remaining, after which the contract will self-destruct.</p>
|
||||
<br/>
|
||||
<p>{description}</p>
|
||||
<br/>
|
||||
<input className="text-input" style={{ width:"50%",marginTop:"8px" }} autoFocus={true} placeholder="Enter Solution here" value={this.state.answer}
|
||||
onChange={this.setAnswer} onKeyDown={this.onInputKeydown} />
|
||||
<PopupCloseButton popup={this.props.popupId} onClose={() => this.props.onAttempt(this.state.answer)} text={"Solve"} />
|
||||
<PopupCloseButton popup={this.props.popupId} onClose={this.props.onClose} text={"Close"} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const contractType: CodingContractType = CodingContractTypes[props.c.type];
|
||||
const description = [];
|
||||
for (const [i, value] of contractType.desc(props.c.data).split('\n').entries())
|
||||
description.push(<span key={i} dangerouslySetInnerHTML={{__html: value+'<br />'}}></span>);
|
||||
return (
|
||||
<div>
|
||||
<CopyableText value={props.c.type} tag={ClickableTag.Tag_h1} />
|
||||
<br/><br/>
|
||||
<p>You are attempting to solve a Coding Contract. You have {props.c.getMaxNumTries() - props.c.tries} tries remaining, after which the contract will self-destruct.</p>
|
||||
<br/>
|
||||
<p>{description}</p>
|
||||
<br/>
|
||||
<input
|
||||
className="text-input"
|
||||
style={{ width:"50%",marginTop:"8px" }}
|
||||
autoFocus={true}
|
||||
placeholder="Enter Solution here"
|
||||
value={answer}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown} />
|
||||
<PopupCloseButton
|
||||
popup={props.popupId}
|
||||
onClose={() => props.onAttempt(answer)}
|
||||
text={"Solve"} />
|
||||
<PopupCloseButton
|
||||
popup={props.popupId}
|
||||
onClose={props.onClose}
|
||||
text={"Close"} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -5,15 +5,13 @@
|
||||
*/
|
||||
import * as React from "react";
|
||||
|
||||
type ReactComponent = new(...args: any[]) => React.Component<any, any>
|
||||
|
||||
interface IProps {
|
||||
content: ReactComponent;
|
||||
interface IProps<T> {
|
||||
content: (props: T) => React.ReactElement;
|
||||
id: string;
|
||||
props: any;
|
||||
props: T;
|
||||
}
|
||||
|
||||
export function Popup(props: IProps): React.ReactElement {
|
||||
export function Popup<T>(props: IProps<T>): React.ReactElement {
|
||||
return (
|
||||
<div className={"popup-box-content"} id={`${props.id}-content`}>
|
||||
{React.createElement(props.content, props.props)}
|
||||
|
@ -14,8 +14,6 @@ import { Popup } from "./Popup";
|
||||
import { createElement } from "../../../utils/uiHelpers/createElement";
|
||||
import { removeElementById } from "../../../utils/uiHelpers/removeElementById";
|
||||
|
||||
type ReactComponent = new(...args: any[]) => React.Component<any, any>;
|
||||
|
||||
let gameContainer: HTMLElement;
|
||||
|
||||
function getGameContainer(): void {
|
||||
@ -31,16 +29,26 @@ function getGameContainer(): void {
|
||||
document.addEventListener("DOMContentLoaded", getGameContainer);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function createPopup(id: string, rootComponent: ReactComponent, props: any): HTMLElement | null {
|
||||
export function createPopup<T>(id: string, rootComponent: (props: T) => React.ReactElement, props: T): HTMLElement | null {
|
||||
let container = document.getElementById(id);
|
||||
if (container == null) {
|
||||
function onClick(this: HTMLElement, event: MouseEvent): any {
|
||||
if(!event.srcElement) return;
|
||||
if(!(event.srcElement instanceof HTMLElement)) return;
|
||||
const clickedId = (event.srcElement as HTMLElement).id;
|
||||
if(clickedId !== id) return;
|
||||
removePopup(id);
|
||||
}
|
||||
container = createElement("div", {
|
||||
class: "popup-box-container",
|
||||
display: "flex",
|
||||
id: id,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
clickListener: onClick,
|
||||
});
|
||||
|
||||
gameContainer.appendChild(container);
|
||||
|
||||
}
|
||||
|
||||
ReactDOM.render(<Popup content={rootComponent} id={id} props={props} />, container);
|
||||
@ -53,9 +61,10 @@ export function createPopup(id: string, rootComponent: ReactComponent, props: an
|
||||
*/
|
||||
export function removePopup(id: string): void {
|
||||
const content = document.getElementById(`${id}`);
|
||||
if (content == null) { return; }
|
||||
if (content == null) return;
|
||||
|
||||
ReactDOM.unmountComponentAtNode(content);
|
||||
|
||||
removeElementById(id);
|
||||
removeElementById(`${id}-close`);
|
||||
}
|
||||
|
@ -62,6 +62,9 @@ interface ICreateElementStyleOptions {
|
||||
visibility?: string;
|
||||
whiteSpace?: string;
|
||||
width?: string;
|
||||
height?: string;
|
||||
top?: string;
|
||||
left?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -210,6 +213,15 @@ function setElementStyle(el: HTMLElement, params: ICreateElementStyleOptions): v
|
||||
if (params.width !== undefined) {
|
||||
el.style.width = params.width;
|
||||
}
|
||||
if (params.height !== undefined) {
|
||||
el.style.height = params.height;
|
||||
}
|
||||
if (params.top !== undefined) {
|
||||
el.style.top = params.top;
|
||||
}
|
||||
if (params.left !== undefined) {
|
||||
el.style.left = params.left;
|
||||
}
|
||||
if (params.backgroundColor !== undefined) {
|
||||
el.style.backgroundColor = params.backgroundColor;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user