Format/cleanup of Gang UI

This commit is contained in:
Olivier Gagnon 2021-06-17 17:37:05 -04:00
parent 2c7fbc03cf
commit 97fdf7cb7f
18 changed files with 387 additions and 292 deletions

@ -1,16 +1,15 @@
import { IMults } from "./data/upgrades";
import { IMults, UpgradeType } from "./data/upgrades";
export class GangMemberUpgrade {
name: string;
cost: number;
type: string;
type: UpgradeType;
desc: string;
mults: IMults;
constructor(name = "", cost = 0, type = "w", mults: IMults = {}) {
constructor(name = "", cost = 0, type: UpgradeType = UpgradeType.Weapon, mults: IMults = {}) {
this.name = name;
this.cost = cost;
//w = weapon, a = armor, v = vehicle, r = rootkit, g = Aug
this.type = type;
this.mults = mults;
@ -43,15 +42,15 @@ export class GangMemberUpgrade {
// User friendly version of type.
getType(): string {
switch (this.type) {
case "w":
case UpgradeType.Weapon:
return "Weapon";
case "a":
case UpgradeType.Armor:
return "Armor";
case "v":
case UpgradeType.Vehicle:
return "Vehicle";
case "r":
case UpgradeType.Rootkit:
return "Rootkit";
case "g":
case UpgradeType.Augmentation:
return "Augmentation";
default:
return "";

@ -6,4 +6,4 @@ export interface IAscensionResult {
dex: number;
agi: number;
cha: number;
};
}

@ -7,6 +7,14 @@ export interface IMults {
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)
@ -15,7 +23,7 @@ export interface IGangMemberUpgradeMetadata {
cost: number;
mults: IMults;
name: string;
upgType: string;
upgType: UpgradeType;
}
/**
@ -27,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,
},
];

@ -0,0 +1,43 @@
import React from "react";
import { Gang } from "../Gang";
import { GangMember } from "../GangMember";
import { numeralWrapper } from "../../ui/numeralFormat";
import { removePopup } from "../../ui/React/createPopup";
interface IProps {
member: GangMember;
gang: Gang;
popupId: string;
}
export function AscensionPopup(props: IProps): React.ReactElement {
function confirm(): void {
props.gang.ascendMember(props.member);
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: +{numeralWrapper.formatPercentage(ascendBenefits.hack/100)}<br />
Strength: +{numeralWrapper.formatPercentage(ascendBenefits.str/100)}<br />
Defense: +{numeralWrapper.formatPercentage(ascendBenefits.def/100)}<br />
Dexterity: +{numeralWrapper.formatPercentage(ascendBenefits.dex/100)}<br />
Agility: +{numeralWrapper.formatPercentage(ascendBenefits.agi/100)}<br />
Charisma: +{numeralWrapper.formatPercentage(ascendBenefits.cha/100)}<br />
</pre>
<button className="std-button" onClick={confirm}>Ascend</button>
<button className="std-button" onClick={cancel}>Cancel</button>
</>);
}

26
src/Gang/ui/BonusTime.tsx Normal file

@ -0,0 +1,26 @@
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">
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 />
</>);
}

@ -0,0 +1,19 @@
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} />} />
}

@ -1,7 +1,7 @@
import React, { useState } from "react";
import { Panel1 } from "./Panel1";
import { Panel2 } from "./Panel2";
import { Panel3 } from "./Panel3";
import { GangMemberStats } from "./GangMemberStats";
import { TaskSelector } from "./TaskSelector";
import { TaskDescription } from "./TaskDescription";
import { Gang } from "../Gang";
import { GangMember } from "../GangMember";
@ -14,13 +14,16 @@ export function GangMemberAccordionContent(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
return (<>
<div className={"gang-member-info-div tooltip"}>
<Panel1 gang={props.gang} member={props.member} />
<GangMemberStats gang={props.gang} member={props.member} />
</div>
<div className={"gang-member-info-div"}>
<Panel2 onTaskChange={()=>setRerender(old => !old)} gang={props.gang} member={props.member} />
<TaskSelector
onTaskChange={()=>setRerender(old => !old)}
gang={props.gang}
member={props.member} />
</div>
<div className={"gang-member-info-div"}>
<Panel3 member={props.member} />
<TaskDescription member={props.member} />
</div>
</>);
}

@ -1,11 +1,11 @@
import React, { useState, useEffect } from "react";
import { Accordion } from "../../ui/React/Accordion";
import { GangMemberAccordionContent } from "./GangMemberAccordionContent"
import { GangMemberUpgradePopup } from "./GangMemberUpgradePopup"
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;
@ -14,6 +14,7 @@ interface IProps {
export function GangMemberList(props: IProps): React.ReactElement {
const [filter, setFilter] = useState("");
const setRerender = useState(false)[1];
function openUpgradePopup(): void {
const popupId = `gang-upgrade-popup`;
@ -28,19 +29,27 @@ export function GangMemberList(props: IProps): React.ReactElement {
setFilter(event.target.value);
}
function members(): GangMember[] {
return props.gang.members.filter((member: GangMember) => member.name.indexOf(filter) > -1 || member.task.indexOf(filter) > -1)
}
const members = props.gang.members.filter((member: GangMember) =>
member.name.indexOf(filter) > -1 || member.task.indexOf(filter) > -1);
return (<>
<input className="text-input" placeholder="Filter gang member" style={{margin: "5px", padding: "5px"}} value={filter} onChange={onChange} />
<a className="a-link-button" style={{display: 'inline-block'}} onClick={openUpgradePopup}>Manage Equipment</a>
<RecruitButton
onRecruit={() => setRerender(old => !old)}
gang={props.gang} />
<br />
<input
className="text-input"
placeholder="Filter gang member"
style={{margin: "5px", padding: "5px"}}
value={filter}
onChange={onChange} />
<a
className="a-link-button"
style={{display: 'inline-block'}}
onClick={openUpgradePopup}>Manage Equipment</a>
<ul>
{members().map((member: GangMember) => <li key={member.name}>
<Accordion
panelInitiallyOpened={true}
headerContent={<>{member.name}</>}
panelContent={<GangMemberAccordionContent gang={props.gang} member={member} />} />
{members.map((member: GangMember) => <li key={member.name}>
<GangMemberAccordion gang={props.gang} member={member} />
</li>)}
</ul>
</>);

@ -1,59 +1,21 @@
import React, { useState, useEffect } from "react";
import React from "react";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { formatNumber } from "../../../utils/StringHelperFunctions";
import { numeralWrapper } from "../../ui/numeralFormat";
import { createPopup, removePopup } from "../../ui/React/createPopup";
import { Gang } from "../Gang";
import { GangMember } from "../GangMember";
interface IAscendProps {
member: GangMember;
gang: Gang;
popupId: string;
}
function ascendPopup(props: IAscendProps): React.ReactElement {
function confirm(): void {
props.gang.ascendMember(props.member);
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: +{numeralWrapper.formatPercentage(ascendBenefits.hack/100)}<br />
Strength: +{numeralWrapper.formatPercentage(ascendBenefits.str/100)}<br />
Defense: +{numeralWrapper.formatPercentage(ascendBenefits.def/100)}<br />
Dexterity: +{numeralWrapper.formatPercentage(ascendBenefits.dex/100)}<br />
Agility: +{numeralWrapper.formatPercentage(ascendBenefits.agi/100)}<br />
Charisma: +{numeralWrapper.formatPercentage(ascendBenefits.cha/100)}<br />
</pre>
<button className="std-button" onClick={confirm}>Ascend</button>
<button className="std-button" onClick={cancel}>Cancel</button>
</>);
}
import { AscensionPopup } from "./AscensionPopup";
interface IProps {
member: GangMember;
gang: Gang;
}
export function Panel1(props: IProps): React.ReactElement {
export function GangMemberStats(props: IProps): React.ReactElement {
function ascend(): void {
const popupId = `gang-management-ascend-member ${props.member.name}`;
createPopup(popupId, ascendPopup, {
createPopup(popupId, AscensionPopup, {
member: props.member,
gang: props.gang,
popupId: popupId,
@ -85,12 +47,12 @@ Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * props.member.agi_a
Ch: x{numeralWrapper.formatMultiplier(props.member.cha_mult * props.member.cha_asc_mult)}(x{numeralWrapper.formatMultiplier(props.member.cha_mult)} Eq, x{numeralWrapper.formatMultiplier(props.member.cha_asc_mult)} Asc)
</span>
<pre id={`${props.member.name}gang-member-stats-text`}>
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 />
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 />
<button className="accordion-button" onClick={ascend}>Ascend</button>

@ -8,6 +8,7 @@ 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;
@ -17,49 +18,31 @@ interface IPanelProps {
function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
const setRerender = useState(false)[1];
// Upgrade buttons. Only show upgrades that can be afforded
const weaponUpgrades: GangMemberUpgrade[] = [];
const armorUpgrades: GangMemberUpgrade[] = [];
const vehicleUpgrades: GangMemberUpgrade[] = [];
const rootkitUpgrades: GangMemberUpgrade[] = [];
const augUpgrades: GangMemberUpgrade[] = [];
for (const upgName in GangMemberUpgrades) {
if (GangMemberUpgrades.hasOwnProperty(upgName)) {
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))) continue;
if (props.member.upgrades.includes(upgName) || props.member.augmentations.includes(upgName)) continue;
switch (upg.type) {
case "w":
weaponUpgrades.push(upg);
break;
case "a":
armorUpgrades.push(upg);
break;
case "v":
vehicleUpgrades.push(upg);
break;
case "r":
rootkitUpgrades.push(upg);
break;
case "g":
augUpgrades.push(upg);
break;
default:
console.error(`ERROR: Invalid Gang Member Upgrade Type: ${upg.type}`);
}
}
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 purchased(name: string): React.ReactElement {
const upg = GangMemberUpgrades[name]
return (<div key={name} className="gang-owned-upgrade tooltip">
function purchased(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 upgradeButton(upg: GangMemberUpgrade, left: boolean = false): React.ReactElement {
function onClick(): void {
props.member.buyUpgrade(upg, props.player, props.gang);
setRerender(old => !old);
@ -132,11 +115,25 @@ export function GangMemberUpgradePopup(props: IProps): React.ReactElement {
}, []);
return (<>
<input className="text-input" value={filter} placeholder="Filter gang member" onChange={event => setFilter(event.target.value)} />
<input
className="text-input"
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">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>
<span className="tooltiptext">
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} />)}
{props.gang.members.map((member: GangMember) =>
<GangMemberUpgradePanel
key={member.name}
player={props.player}
gang={props.gang}
member={member} />)
}
</>);
}

@ -1,125 +1,18 @@
import React, { useState, useEffect } from "react";
import React from "react";
import { Factions } from "../../Faction/Factions";
import { Gang } from "../Gang";
import {
formatNumber,
convertTimeMsToTimeElapsedString,
} from "../../../utils/StringHelperFunctions";
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 { GangConstants } from "../data/Constants";
import { createPopup, removePopup } from "../../ui/React/createPopup";
import { dialogBoxCreate } from "../../../utils/DialogBox";
interface IRecruitPopupProps {
gang: Gang;
popupId: string;
}
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;
}
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>Enter a name for your new Gang member:</p><br />
<input autoFocus
onKeyUp={onKeyUp}
onChange={onChange}
className="text-input"
type="text"
placeholder="unique name" />
<a className="std-button" onClick={recruit}>Recruit Gang Member</a>
<a className="std-button" onClick={cancel}>Cancel</a>
</>);
}
import { BonusTime } from "./BonusTime";
interface IProps {
gang: Gang;
}
function Recruitment(props: IProps): React.ReactElement {
// Toggle the 'Recruit member button' if valid
const numMembers = props.gang.members.length;
const respectCost = props.gang.getRespectNeededToRecruitMember();
if (numMembers >= GangConstants.MaximumGangMembers) {
return (<></>);
} else if (props.gang.canRecruitMember()) {
function onClick(): void {
const popupId = "recruit-gang-member-popup";
createPopup(popupId, recruitPopup, {
gang: props.gang,
popupId: popupId,
});
}
return (<>
<a className="a-link-button"
onClick={onClick}
style={{display: 'inline-block', margin: '10px'}}>
Recruit Gang Member
</a>
</>);
}
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(respectCost, 2)} respect needed to recruit next member
</p>
</>);
}
function BonusTime(props: IProps): React.ReactElement {
const CyclesPerSecond = 1000 / 200;
if (props.gang.storedCycles / CyclesPerSecond*1000 <= 5000) return <></>;
return (<>
<p className="tooltip" style={{display: "inline-block"}}>
Bonus time: {convertTimeMsToTimeElapsedString(props.gang.storedCycles / CyclesPerSecond*1000)}
<span className="tooltiptext">
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 />
</>);
}
export function GangStats(props: IProps): React.ReactElement {
const territoryMult = AllGangs[props.gang.facName].territory * 100;
let territoryStr;
@ -171,7 +64,5 @@ export function GangStats(props: IProps): React.ReactElement {
</p>
<br />
<BonusTime gang={props.gang} />
<br />
<Recruitment gang={props.gang} />
</>);
}

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { GangStats } from "./GangStats";
import { Gang } from "../Gang";
@ -12,24 +12,28 @@ interface IProps {
export function ManagementSubpage(props: IProps): React.ReactElement {
return (<div style={{display: 'block'}}>
<p style={{width: "70%"}}>
This page is used to manage your gang members and get an overview of your gang's stats.
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"}'
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.
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
You can also manage your gang programmatically through Netscript
using the Gang API
</p>
<br />
<GangStats gang={props.gang} />

@ -0,0 +1,47 @@
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>
</>);
}

@ -0,0 +1,60 @@
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>Enter a name for your new Gang member:</p><br />
<input autoFocus
onKeyUp={onKeyUp}
onChange={onChange}
className="text-input"
type="text"
placeholder="unique name" />
<a className="std-button" onClick={recruit}>Recruit Gang Member</a>
<a className="std-button" onClick={cancel}>Cancel</a>
</>);
}

@ -27,9 +27,18 @@ export function Root(props: IProps): React.ReactElement {
}
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>
<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} />}

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React from "react";
import { GangMemberTasks } from "../GangMemberTasks";
import { GangMember } from "../GangMember";
@ -6,7 +6,7 @@ interface IProps {
member: GangMember;
}
export function Panel3(props: IProps): React.ReactElement {
export function TaskDescription(props: IProps): React.ReactElement {
const task = GangMemberTasks[props.member.task];
const desc = task ? task.desc: GangMemberTasks["Unassigned"].desc;

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState } from "react";
import { numeralWrapper } from "../../ui/numeralFormat";
import { StatsTable } from "../../ui/React/StatsTable";
import { MoneyRate } from "../../ui/React/MoneyRate";
@ -11,7 +11,7 @@ interface IProps {
onTaskChange: () => void;
}
export function Panel2(props: IProps): React.ReactElement {
export function TaskSelector(props: IProps): React.ReactElement {
const [currentTask, setCurrentTask] = useState(props.member.task);
function onChange(event: React.ChangeEvent<HTMLSelectElement>): void {

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React from "react";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { formatNumber } from "../../../utils/StringHelperFunctions";
@ -11,13 +11,13 @@ interface IProps {
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.")
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 formatTerritoryP(n: number): string {
function formatTerritory(n: number): string {
const v = n * 100;
if (v <= 0) {
return formatNumber(0, 2);
@ -35,7 +35,7 @@ export function TerritorySubpage(props: IProps): React.ReactElement {
return (<span key={name}>
<u>{name}</u><br />
Power: {formatNumber(power, 6)}<br />
Territory: {formatTerritoryP(AllGangs[name].territory)}%<br />
Territory: {formatTerritory(AllGangs[name].territory)}%<br />
Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}<br />
<br />
</span>);
@ -72,8 +72,17 @@ export function TerritorySubpage(props: IProps): React.ReactElement {
<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" style={{color: "white", display: 'inline-block'}}>
<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"
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%.
@ -85,10 +94,19 @@ export function TerritorySubpage(props: IProps): React.ReactElement {
<p style={{display: 'inline-block'}}>
Territory Clash Chance: {numeralWrapper.formatPercentage(props.gang.territoryClashChance, 3)}
</p>
<div className="help-tip" style={{display: "inline-block"}} onClick={openWarfareHelp}>?</div>
<div
className="help-tip"
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}/>
<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" style={{color: "white", display: 'inline-block'}}>
Notify about Gang Member Deaths
<span className="tooltiptext" style={{display: "inline-block"}}>
@ -101,7 +119,7 @@ export function TerritorySubpage(props: IProps): React.ReactElement {
<p>
<b><u>{props.gang.facName}</u></b><br />
Power: {formatNumber(AllGangs[props.gang.facName].power, 6)}<br />
Territory: {formatTerritoryP(AllGangs[props.gang.facName].territory)}%<br />
Territory: {formatTerritory(AllGangs[props.gang.facName].territory)}%<br />
<br />
{gangNames.map(name => otherGangTerritory(name))}
</p>