From 25f546c691b3d68b7c7733ac2e22ed13256175d0 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 14 Jun 2021 15:42:38 -0400 Subject: [PATCH 01/24] part 1 of converting gang to react --- src/CodingContracts.ts | 3 +- src/Gang.jsx | 171 ++-------- src/Gang/GangMember.ts | 309 ++++++++++++++++++ src/Gang/GangMemberTask.ts | 81 +++++ src/Gang/GangMemberTasks.ts | 12 + src/Gang/GangMemberUpgrade.ts | 56 ++++ src/Gang/GangMemberUpgrades.ts | 12 + src/Gang/data/Constants.ts | 13 + src/Gang/data/tasks.ts | 284 ++++++++++++++++ src/Gang/data/upgrades.ts | 218 ++++++++++++ src/Gang/ui/Panel1.tsx | 143 ++++++++ src/Locations/data/LocationsMetadata.ts | 2 +- src/NetscriptFunctions.js | 4 +- .../Sleeve/SleeveCovenantPurchases.ts | 3 +- .../Sleeve/ui/CovenantPurchasesRoot.tsx | 122 +++---- src/ui/React/CodingContractPopup.tsx | 73 +++-- src/ui/React/Popup.tsx | 10 +- src/ui/React/createPopup.tsx | 2 +- 18 files changed, 1251 insertions(+), 267 deletions(-) create mode 100644 src/Gang/GangMember.ts create mode 100644 src/Gang/GangMemberTask.ts create mode 100644 src/Gang/GangMemberTasks.ts create mode 100644 src/Gang/GangMemberUpgrade.ts create mode 100644 src/Gang/GangMemberUpgrades.ts create mode 100644 src/Gang/data/Constants.ts create mode 100644 src/Gang/data/tasks.ts create mode 100644 src/Gang/data/upgrades.ts create mode 100644 src/Gang/ui/Panel1.tsx diff --git a/src/CodingContracts.ts b/src/CodingContracts.ts index 31dc995c1..9b332d8a2 100644 --- a/src/CodingContracts.ts +++ b/src/CodingContracts.ts @@ -172,7 +172,7 @@ export class CodingContract { async prompt(): Promise { const popupId = `coding-contract-prompt-popup-${this.fn}`; return new Promise((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); }); } diff --git a/src/Gang.jsx b/src/Gang.jsx index 2d2845f76..69b362bae 100644 --- a/src/Gang.jsx +++ b/src/Gang.jsx @@ -4,8 +4,8 @@ * balance point to keep them from running out of control */ -import { gangMemberTasksMetadata } from "./data/gangmembertasks"; -import { gangMemberUpgradesMetadata } from "./data/gangmemberupgrades"; +import { gangMemberTasksMetadata } from "./Gang/data/tasks"; +import { gangMemberUpgradesMetadata } from "./Gang/data/upgrades"; import { Engine } from "./engine"; import { Faction } from "./Faction/Faction"; @@ -42,16 +42,19 @@ import { Money } from "./ui/React/Money"; import { MoneyRate } from "./ui/React/MoneyRate"; import { Reputation } from "./ui/React/Reputation"; +// import { GangMember as GM } from "./Gang/GangMember"; +import { GangMemberUpgrade } from "./Gang/GangMemberUpgrade"; +import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades"; +import { GangConstants } from "./Gang/data/Constants"; +import { GangMemberTasks } from "./Gang/GangMemberTasks"; +import { GangMemberTask } from "./Gang/GangMemberTask"; + +import { Panel1 } from "./Gang/ui/Panel1"; + import React from "react"; import ReactDOM from "react-dom"; import { renderToStaticMarkup } from "react-dom/server" -// Constants -const GangRespectToReputationRatio = 5; // Respect is divided by this to get rep gain -const MaximumGangMembers = 30; -const CyclesPerTerritoryAndPowerUpdate = 100; -const AscensionMultiplierRatio = 15 / 100; // Portion of upgrade multiplier that is kept after ascending - // Switch between territory and management screen with 1 and 2 $(document).keydown(function(event) { if (routing.isOn(Page.Gang) && event.altKey) { @@ -245,7 +248,7 @@ Gang.prototype.processGains = function(numCycles=1, player) { dialogBoxCreate("ERROR: Could not get Faction associates with your gang. This is a bug, please report to game dev"); } else { let favorMult = 1 + (fac.favor / 100); - fac.playerReputation += ((player.faction_rep_mult * gain * favorMult) / GangRespectToReputationRatio); + fac.playerReputation += ((player.faction_rep_mult * gain * favorMult) / GangConstants.GangRespectToReputationRatio); } // Keep track of respect gained per member @@ -289,8 +292,8 @@ function calculateTerritoryGain(winGang, loseGang) { Gang.prototype.processTerritoryAndPowerGains = function(numCycles=1) { this.storedTerritoryAndPowerCycles += numCycles; - if (this.storedTerritoryAndPowerCycles < CyclesPerTerritoryAndPowerUpdate) { return; } - this.storedTerritoryAndPowerCycles -= CyclesPerTerritoryAndPowerUpdate; + if (this.storedTerritoryAndPowerCycles < GangConstants.CyclesPerTerritoryAndPowerUpdate) { return; } + this.storedTerritoryAndPowerCycles -= GangConstants.CyclesPerTerritoryAndPowerUpdate; // Process power first const gangName = this.facName; @@ -380,7 +383,7 @@ Gang.prototype.processTerritoryAndPowerGains = function(numCycles=1) { } Gang.prototype.canRecruitMember = function() { - if (this.members.length >= MaximumGangMembers) { return false; } + if (this.members.length >= GangConstants.MaximumGangMembers) { return false; } return (this.respect >= this.getRespectNeededToRecruitMember()); } @@ -848,12 +851,12 @@ GangMember.prototype.getAscensionResults = function() { // Subtract 1 because we're only interested in the actual "bonus" part const eff = this.getAscensionEfficiency(); return { - hack: (Math.max(0, hack - 1) * AscensionMultiplierRatio * eff.hack), - str: (Math.max(0, str - 1) * AscensionMultiplierRatio * eff.str), - def: (Math.max(0, def - 1) * AscensionMultiplierRatio * eff.def), - dex: (Math.max(0, dex - 1) * AscensionMultiplierRatio * eff.dex), - agi: (Math.max(0, agi - 1) * AscensionMultiplierRatio * eff.agi), - cha: (Math.max(0, cha - 1) * AscensionMultiplierRatio * eff.cha), + hack: (Math.max(0, hack - 1) * GangConstants.AscensionMultiplierRatio * eff.hack), + str: (Math.max(0, str - 1) * GangConstants.AscensionMultiplierRatio * eff.str), + def: (Math.max(0, def - 1) * GangConstants.AscensionMultiplierRatio * eff.def), + dex: (Math.max(0, dex - 1) * GangConstants.AscensionMultiplierRatio * eff.dex), + agi: (Math.max(0, agi - 1) * GangConstants.AscensionMultiplierRatio * eff.agi), + cha: (Math.max(0, cha - 1) * GangConstants.AscensionMultiplierRatio * eff.cha), } } @@ -894,136 +897,6 @@ GangMember.fromJSON = function(value) { Reviver.constructors.GangMember = GangMember; -// Defines tasks that Gang Members can work on -function GangMemberTask(name="", desc="", isHacking=false, isCombat=false, - params={baseRespect: 0, baseWanted: 0, baseMoney: 0, - hackWeight: 0, strWeight: 0, defWeight: 0, - dexWeight: 0, agiWeight: 0, chaWeight: 0, - difficulty: 0}) { - 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}; -} - -GangMemberTask.prototype.toJSON = function() { - return Generic_toJSON("GangMemberTask", this); -} - -GangMemberTask.fromJSON = function(value) { - return Generic_fromJSON(GangMemberTask, value.data); -} - -Reviver.constructors.GangMemberTask = GangMemberTask; - -export const GangMemberTasks = {}; - -function addGangMemberTask(name, desc, isHacking, isCombat, params) { - GangMemberTasks[name] = new GangMemberTask(name, desc, isHacking, isCombat, params); -} - -gangMemberTasksMetadata.forEach((e) => { - addGangMemberTask(e.name, e.desc, e.isHacking, e.isCombat, e.params); -}); - -function GangMemberUpgrade(name="", cost=0, type="w", mults={}) { - this.name = name; - this.cost = cost; - this.type = type; //w = weapon, a = armor, v = vehicle, r = rootkit, g = Aug - this.mults = mults; - - this.createDescription(); -} - -GangMemberUpgrade.prototype.getCost = function(gang) { - const discount = gang.getDiscount(); - return this.cost / discount; -} - -GangMemberUpgrade.prototype.createDescription = function() { - const lines = ["Increases:"]; - if (this.mults.str != null) { - lines.push(`* Strength by ${Math.round((this.mults.str - 1) * 100)}%`); - } - if (this.mults.def != null) { - lines.push(`* Defense by ${Math.round((this.mults.def - 1) * 100)}%`); - } - if (this.mults.dex != null) { - lines.push(`* Dexterity by ${Math.round((this.mults.dex - 1) * 100)}%`); - } - if (this.mults.agi != null) { - lines.push(`* Agility by ${Math.round((this.mults.agi - 1) * 100)}%`); - } - if (this.mults.cha != null) { - lines.push(`* Charisma by ${Math.round((this.mults.cha - 1) * 100)}%`); - } - if (this.mults.hack != null) { - lines.push(`* Hacking by ${Math.round((this.mults.hack - 1) * 100)}%`); - } - this.desc = lines.join("
"); -} - -// Passes in a GangMember object -GangMemberUpgrade.prototype.apply = function(member) { - if (this.mults.str != null) { member.str_mult *= this.mults.str; } - if (this.mults.def != null) { member.def_mult *= this.mults.def; } - if (this.mults.dex != null) { member.dex_mult *= this.mults.dex; } - if (this.mults.agi != null) { member.agi_mult *= this.mults.agi; } - if (this.mults.cha != null) { member.cha_mult *= this.mults.cha; } - if (this.mults.hack != null) { member.hack_mult *= this.mults.hack; } - return; -} - -GangMemberUpgrade.prototype.toJSON = function() { - return Generic_toJSON("GangMemberUpgrade", this); -} - -GangMemberUpgrade.fromJSON = function(value) { - return Generic_fromJSON(GangMemberUpgrade, value.data); -} - -Reviver.constructors.GangMemberUpgrade = GangMemberUpgrade; - -// Initialize Gang Member Upgrades -export const GangMemberUpgrades = {} - -function addGangMemberUpgrade(name, cost, type, mults) { - GangMemberUpgrades[name] = new GangMemberUpgrade(name, cost, type, mults); -} - -gangMemberUpgradesMetadata.forEach((e) => { - addGangMemberUpgrade(e.name, e.cost, e.upgType, e.mults); -}); - // Create a pop-up box that lets player purchase upgrades Gang.prototype.createGangMemberUpgradeBox = function(player, initialFilter="") { const boxId = "gang-member-upgrade-popup-box"; @@ -1721,7 +1594,7 @@ Gang.prototype.updateGangContent = function() { const respectCost = this.getRespectNeededToRecruitMember(); const btn = UIElems.gangRecruitMemberButton; - if (numMembers >= MaximumGangMembers) { + if (numMembers >= GangConstants.MaximumGangMembers) { btn.className = "a-link-button-inactive"; UIElems.gangRecruitRequirementText.style.display = "inline-block"; UIElems.gangRecruitRequirementText.innerHTML = "You have reached the maximum amount of gang members"; diff --git a/src/Gang/GangMember.ts b/src/Gang/GangMember.ts new file mode 100644 index 000000000..9da35353e --- /dev/null +++ b/src/Gang/GangMember.ts @@ -0,0 +1,309 @@ +import { Page, routing } from "../ui/navigationTracking"; +import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; + +export class GangMember { + name: string; + task: string = "Unassigned"; + + earnedRespect: number = 0; + + hack: number = 1; + str: number = 1; + def: number = 1; + dex: number = 1; + agi: number = 1; + cha: number = 1; + + hack_exp: number = 0; + str_exp: number = 0; + def_exp: number = 0; + dex_exp: number = 0; + agi_exp: number = 0; + cha_exp: number = 0; + + hack_mult: number = 1; + str_mult: number = 1; + def_mult: number = 1; + dex_mult: number = 1; + agi_mult: number = 1; + cha_mult: number = 1; + + hack_asc_mult: number = 1; + str_asc_mult: number = 1; + def_asc_mult: number = 1; + dex_asc_mult: number = 1; + agi_asc_mult: number = 1; + cha_asc_mult: number = 1; + + upgrades: string[] = []; // Names of upgrades + augmentations: string[] = []; // Names of augmentations only + + constructor(name: string = "") { + this.name = name; + } + + + // // Same skill calculation formula as Player + // calculateSkill(exp: number, mult: number = 1): number { + // return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1); + // } + + // updateSkillLevels(): void { + // this.hack = this.calculateSkill(this.hack_exp, this.hack_mult * this.hack_asc_mult); + // this.str = this.calculateSkill(this.str_exp, this.str_mult * this.str_asc_mult); + // this.def = this.calculateSkill(this.def_exp, this.def_mult * this.def_asc_mult); + // this.dex = this.calculateSkill(this.dex_exp, this.dex_mult * this.dex_asc_mult); + // this.agi = this.calculateSkill(this.agi_exp, this.agi_mult * this.agi_asc_mult); + // this.cha = this.calculateSkill(this.cha_exp, this.cha_mult * this.cha_asc_mult); + // } + + // 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 = taskName; + // return true; + // } else { + // this.task = "Unassigned"; + // return false; + // } + // } + + // unassignFromTask(): string { + // this.task = "Unassigned"; + // } + + // getTask(): string { + // // Backwards compatibility + // if (this.task instanceof GangMemberTask) { + // this.task = this.task.name; + // } + + // if (GangMemberTasks.hasOwnProperty(this.task)) { + // return GangMemberTasks[this.task]; + // } + // return GangMemberTasks["Unassigned"]; + // } + + // // Gains are per cycle + // calculateRespectGain(gang: any): number { + // const task = this.getTask(); + // if (task == null || !(task instanceof GangMemberTask) || 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: any): number { + // const task = this.getTask(); + // if (task == null || !(task instanceof GangMemberTask) || 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; + // } else { + // 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: any): number { + // const task = this.getTask(); + // if (task == null || !(task instanceof GangMemberTask) || 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; + // } + + // gainExperience(numCycles: number = 1): void { + // const task = this.getTask(); + // if (task == null || !(task instanceof GangMemberTask) || task === GangMemberTasks["Unassigned"]) {return;} + // const difficultyMult = Math.pow(task.difficulty, 0.9); + // const difficultyPerCycles = difficultyMult * numCycles; + // const weightDivisor = 1500; + // this.hack_exp += (task.hackWeight / weightDivisor) * difficultyPerCycles; + // this.str_exp += (task.strWeight / weightDivisor) * difficultyPerCycles; + // this.def_exp += (task.defWeight / weightDivisor) * difficultyPerCycles; + // this.dex_exp += (task.dexWeight / weightDivisor) * difficultyPerCycles; + // this.agi_exp += (task.agiWeight / weightDivisor) * difficultyPerCycles; + // this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles; + // } + + // recordEarnedRespect(numCycles: number = 1, gang: any): void { + // this.earnedRespect += (this.calculateRespectGain(gang) * numCycles); + // } + + // ascend(): any { + // const res = this.getAscensionResults(); + // const hackAscMult = res.hack; + // const strAscMult = res.str; + // const defAscMult = res.def; + // const dexAscMult = res.dex; + // const agiAscMult = res.agi; + // const chaAscMult = res.cha; + // this.hack_asc_mult += hackAscMult; + // this.str_asc_mult += strAscMult; + // this.def_asc_mult += defAscMult; + // this.dex_asc_mult += dexAscMult; + // this.agi_asc_mult += agiAscMult; + // this.cha_asc_mult += chaAscMult; + + // // 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) { + // let aug = GangMemberUpgrades[this.augmentations[i]]; + // aug.apply(this); + // } + + // // 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: hackAscMult, + // str: strAscMult, + // def: defAscMult, + // dex: dexAscMult, + // agi: agiAscMult, + // cha: chaAscMult, + // }; + // } + + // getAscensionEfficiency(): any { + // function formula(mult) { + // return 1/(1+Math.log(mult)/Math.log(20)); + // } + // return { + // hack: formula(this.hack_asc_mult), + // str: formula(this.str_asc_mult), + // def: formula(this.def_asc_mult), + // dex: formula(this.dex_asc_mult), + // agi: formula(this.agi_asc_mult), + // cha: formula(this.cha_asc_mult), + // }; + // } + + // // Returns the multipliers that would be gained from ascension + // getAscensionResults(): any { + // /** + // * Calculate ascension bonus to stat multipliers. + // * This is based on the current number of multipliers from Non-Augmentation upgrades + // * + Ascension Bonus = N% of current bonus from Augmentations + // */ + // let hack = 1; + // let str = 1; + // let def = 1; + // let dex = 1; + // let agi = 1; + // let cha = 1; + // for (let i = 0; i < this.upgrades.length; ++i) { + // const upg = GangMemberUpgrades[this.upgrades[i]]; + // if (upg.mults.hack != null) { hack *= upg.mults.hack; } + // if (upg.mults.str != null) { str *= upg.mults.str; } + // if (upg.mults.def != null) { def *= upg.mults.def; } + // if (upg.mults.dex != null) { dex *= upg.mults.dex; } + // if (upg.mults.agi != null) { agi *= upg.mults.agi; } + // if (upg.mults.cha != null) { cha *= upg.mults.cha; } + // } + + // // Subtract 1 because we're only interested in the actual "bonus" part + // const eff = this.getAscensionEfficiency(); + // return { + // hack: (Math.max(0, hack - 1) * AscensionMultiplierRatio * eff.hack), + // str: (Math.max(0, str - 1) * AscensionMultiplierRatio * eff.str), + // def: (Math.max(0, def - 1) * AscensionMultiplierRatio * eff.def), + // dex: (Math.max(0, dex - 1) * AscensionMultiplierRatio * eff.dex), + // agi: (Math.max(0, agi - 1) * AscensionMultiplierRatio * eff.agi), + // cha: (Math.max(0, cha - 1) * AscensionMultiplierRatio * eff.cha), + // } + // } + + // buyUpgrade(upg: any, player: any, gang: any): boolean { + // if (typeof upg === 'string') { + // upg = GangMemberUpgrades[upg]; + // } + // if (!(upg instanceof GangMemberUpgrade)) { + // return false; + // } + // // Prevent purchasing of already-owned upgrades + // if (this.augmentations.includes(upg.name) || this.upgrades.includes(upg.name)) { + // return false; + // } + + // if (player.money.lt(upg.getCost(gang))) { return false; } + // player.loseMoney(upg.getCost(gang)); + // if (upg.type === "g") { + // this.augmentations.push(upg.name); + // } else { + // this.upgrades.push(upg.name); + // } + // upg.apply(this); + // if (routing.isOn(Page.Gang) && UIElems.gangMemberUpgradeBoxOpened) { + // var initFilterValue = UIElems.gangMemberUpgradeBoxFilter.value.toString(); + // gang.createGangMemberUpgradeBox(player, initFilterValue); + // } + // 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; \ No newline at end of file diff --git a/src/Gang/GangMemberTask.ts b/src/Gang/GangMemberTask.ts new file mode 100644 index 000000000..a684a09cb --- /dev/null +++ b/src/Gang/GangMemberTask.ts @@ -0,0 +1,81 @@ + +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; +} + +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}; + } + +} diff --git a/src/Gang/GangMemberTasks.ts b/src/Gang/GangMemberTasks.ts new file mode 100644 index 000000000..ccc198d98 --- /dev/null +++ b/src/Gang/GangMemberTasks.ts @@ -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); + }); +})(); diff --git a/src/Gang/GangMemberUpgrade.ts b/src/Gang/GangMemberUpgrade.ts new file mode 100644 index 000000000..9d79992ee --- /dev/null +++ b/src/Gang/GangMemberUpgrade.ts @@ -0,0 +1,56 @@ +import { IMults } from "./data/upgrades"; + +export class GangMemberUpgrade { + name: string; + cost: number; + type: string; + desc: string; + mults: IMults; + + constructor(name: string = "", cost: number = 0, type: string = "w", mults: IMults = {}) { + this.name = name; + this.cost = cost; + //w = weapon, a = armor, v = vehicle, r = rootkit, g = Aug + this.type = type; + this.mults = mults; + + this.desc = this.createDescription(); + } + + getCost(gang: any) { + return this.cost / gang.getDiscount(); + } + + createDescription(): string { + const lines = ["Increases:"]; + if (this.mults.str != null) { + lines.push(`* Strength by ${Math.round((this.mults.str - 1) * 100)}%`); + } + if (this.mults.def != null) { + lines.push(`* Defense by ${Math.round((this.mults.def - 1) * 100)}%`); + } + if (this.mults.dex != null) { + lines.push(`* Dexterity by ${Math.round((this.mults.dex - 1) * 100)}%`); + } + if (this.mults.agi != null) { + lines.push(`* Agility by ${Math.round((this.mults.agi - 1) * 100)}%`); + } + if (this.mults.cha != null) { + lines.push(`* Charisma by ${Math.round((this.mults.cha - 1) * 100)}%`); + } + if (this.mults.hack != null) { + lines.push(`* Hacking by ${Math.round((this.mults.hack - 1) * 100)}%`); + } + return lines.join("
"); + } + + // Passes in a GangMember object + apply(member: any) { + if (this.mults.str != null) { member.str_mult *= this.mults.str; } + if (this.mults.def != null) { member.def_mult *= this.mults.def; } + if (this.mults.dex != null) { member.dex_mult *= this.mults.dex; } + if (this.mults.agi != null) { member.agi_mult *= this.mults.agi; } + if (this.mults.cha != null) { member.cha_mult *= this.mults.cha; } + if (this.mults.hack != null) { member.hack_mult *= this.mults.hack; } + } +} \ No newline at end of file diff --git a/src/Gang/GangMemberUpgrades.ts b/src/Gang/GangMemberUpgrades.ts new file mode 100644 index 000000000..0b9637d5f --- /dev/null +++ b/src/Gang/GangMemberUpgrades.ts @@ -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); + }); +})(); diff --git a/src/Gang/data/Constants.ts b/src/Gang/data/Constants.ts new file mode 100644 index 000000000..945f1b0f7 --- /dev/null +++ b/src/Gang/data/Constants.ts @@ -0,0 +1,13 @@ +export const GangConstants: { + GangRespectToReputationRatio: number; + MaximumGangMembers: number; + CyclesPerTerritoryAndPowerUpdate: number; + AscensionMultiplierRatio: number; +} = { + // Respect is divided by this to get rep gain + GangRespectToReputationRatio: 5, + MaximumGangMembers: 30, + CyclesPerTerritoryAndPowerUpdate: 100, + // Portion of upgrade multiplier that is kept after ascending + AscensionMultiplierRatio : 15, +}; \ No newline at end of file diff --git a/src/Gang/data/tasks.ts b/src/Gang/data/tasks.ts new file mode 100644 index 000000000..60c5a6662 --- /dev/null +++ b/src/Gang/data/tasks.ts @@ -0,0 +1,284 @@ +/* tslint:disable:max-line-length */ + +/** + * Defines the parameters that can be used to initialize and describe a GangMemberTask + * (defined in Gang.js) + */ +export interface IGangMemberTaskMetadata { + /** + * Description of the task + */ + desc: string; + + /** + * Whether or not this task is meant for Combat-type gangs + */ + isCombat: boolean; + + /** + * Whether or not this task is for Hacking-type gangs + */ + isHacking: boolean; + + /** + * Name of the task + */ + name: string; + + /** + * An object containing weighting parameters for the task. These parameters are used for + * various calculations (respect gain, wanted gain, etc.) + */ + params?: any; +} + +/** + * Array of metadata for all Gang Member tasks. Used to construct the global GangMemberTask + * objects in Gang.js + */ +export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [ + { + desc: "This gang member is currently idle", + isCombat: true, + isHacking: true, + name: "Unassigned", + params: {hackWeight: 100}, // This is just to get by the weight check in the GangMemberTask constructor + }, + { + desc: "Assign this gang member to create and distribute ransomware

Earns money - Slightly increases respect - Slightly increases wanted level", + isCombat: false, + isHacking: true, + name: "Ransomware", + params: {baseRespect: 0.00005, baseWanted: 0.0001, baseMoney: 1, hackWeight: 100, difficulty: 1}, + }, + { + desc: "Assign this gang member to attempt phishing scams and attacks

Earns money - Slightly increases respect - Slightly increases wanted level", + isCombat: false, + isHacking: true, + name: "Phishing", + params: {baseRespect: 0.00008, baseWanted: 0.003, baseMoney: 2.5, hackWeight: 85, chaWeight: 15, difficulty: 3.5}, + }, + { + desc: "Assign this gang member to attempt identity theft

Earns money - Increases respect - Increases wanted level", + isCombat: false, + isHacking: true, + name: "Identity Theft", + params: {baseRespect: 0.0001, baseWanted: 0.075, baseMoney: 6, hackWeight: 80, chaWeight: 20, difficulty: 5}, + }, + { + desc: "Assign this gang member to carry out DDoS attacks

Increases respect - Increases wanted level", + isCombat: false, + isHacking: true, + name: "DDoS Attacks", + params: {baseRespect: 0.0004, baseWanted: 0.2, hackWeight: 100, difficulty: 8}, + }, + { + desc: "Assign this gang member to create and distribute malicious viruses

Increases respect - Increases wanted level", + isCombat: false, + isHacking: true, + name: "Plant Virus", + params: {baseRespect: 0.0006, baseWanted: 0.4, hackWeight: 100, difficulty: 12}, + }, + { + desc: "Assign this gang member to commit financial fraud and digital counterfeiting

Earns money - Slightly increases respect - Slightly increases wanted level", + isCombat: false, + isHacking: true, + name: "Fraud & Counterfeiting", + params: {baseRespect: 0.0004, baseWanted: 0.3, baseMoney: 15, hackWeight: 80, chaWeight: 20, difficulty: 20}, + }, + { + desc: "Assign this gang member to launder money

Earns money - Increases respect - Increases wanted level", + isCombat: false, + isHacking: true, + name: "Money Laundering", + params: {baseRespect: 0.001, baseWanted: 1.25, baseMoney: 120, hackWeight: 75, chaWeight: 25, difficulty: 25}, + }, + { + desc: "Assign this gang member to commit acts of cyberterrorism

Greatly increases respect - Greatly increases wanted level", + isCombat: false, + isHacking: true, + name: "Cyberterrorism", + params: {baseRespect: 0.01, baseWanted: 6, hackWeight: 80, chaWeight: 20, difficulty: 36}, + }, + { + desc: "Assign this gang member to be an ethical hacker for corporations

Earns money - Lowers wanted level", + isCombat: false, + isHacking: true, + name: "Ethical Hacking", + params: {baseWanted: -0.001, baseMoney: 1, hackWeight: 90, chaWeight: 10, difficulty: 1}, + }, + { + desc: "Assign this gang member to mug random people on the streets

Earns money - Slightly increases respect - Very slightly increases wanted level", + isCombat: true, + isHacking: false, + name: "Mug People", + params: { + baseRespect: 0.00005, baseWanted: 0.00005, baseMoney: 1.2, + strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 10, chaWeight: 15, + difficulty: 1, + }, + }, + { + desc: "Assign this gang member to sell drugs

Earns money - Slightly increases respect - Slightly increases wanted level - Scales slightly with territory", + isCombat: true, + isHacking: false, + name: "Deal Drugs", + params: { + baseRespect: 0.00006, baseWanted: 0.002, baseMoney: 5, + agiWeight: 20, dexWeight: 20, chaWeight: 60, + difficulty: 3.5, + territory: { + money: 1.2, + respect: 1, + wanted: 1.15, + }, + }, + }, + { + desc: "Assign this gang member to extort civilians in your territory

Earns money - Slightly increases respect - Increases wanted - Scales heavily with territory", + isCombat: true, + isHacking: false, + name: "Strongarm Civilians", + params: { + baseRespect: 0.00004, baseWanted: 0.02, baseMoney: 2.5, + hackWeight: 10, strWeight: 25, defWeight: 25, dexWeight: 20, agiWeight: 10, chaWeight: 10, + difficulty: 5, + territory: { + money: 1.6, + respect: 1.1, + wanted: 1.5, + }, + }, + }, + { + desc: "Assign this gang member to run cons

Earns money - Increases respect - Increases wanted level", + isCombat: true, + isHacking: false, + name: "Run a Con", + params: { + baseRespect: 0.00012, baseWanted: 0.05, baseMoney: 15, + strWeight: 5, defWeight: 5, agiWeight: 25, dexWeight: 25, chaWeight: 40, + difficulty: 14, + }, + }, + { + desc: "Assign this gang member to commit armed robbery on stores, banks and armored cars

Earns money - Increases respect - Increases wanted level", + isCombat: true, + isHacking: false, + name: "Armed Robbery", + params: { + baseRespect: 0.00014, baseWanted: 0.1, baseMoney: 38, + hackWeight: 20, strWeight: 15, defWeight: 15, agiWeight: 10, dexWeight: 20, chaWeight: 20, + difficulty: 20, + }, + }, + { + desc: "Assign this gang member to traffick illegal arms

Earns money - Increases respect - Increases wanted level - Scales heavily with territory", + isCombat: true, + isHacking: false, + name: "Traffick Illegal Arms", + params: { + baseRespect: 0.0002, baseWanted: 0.24, baseMoney: 58, + hackWeight: 15, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 25, + difficulty: 32, + territory: { + money: 1.4, + respect: 1.3, + wanted: 1.25, + }, + }, + }, + { + desc: "Assign this gang member to threaten and black mail high-profile targets

Earns money - Slightly increases respect - Slightly increases wanted level", + isCombat: true, + isHacking: false, + name: "Threaten & Blackmail", + params: { + baseRespect: 0.0002, baseWanted: 0.125, baseMoney: 24, + hackWeight: 25, strWeight: 25, dexWeight: 25, chaWeight: 25, + difficulty: 28, + }, + }, + { + desc: "Assign this gang member to engage in human trafficking operations

Earns money - Increases respect - Increases wanted level - Scales heavily with territory", + isCombat: true, + isHacking: false, + name: "Human Trafficking", + params: { + baseRespect: 0.004, baseWanted: 1.25, baseMoney: 120, + hackWeight: 30, strWeight: 5, defWeight: 5, dexWeight: 30, chaWeight: 30, + difficulty: 36, + territory: { + money: 1.5, + respect: 1.5, + wanted: 1.6, + }, + }, + }, + { + desc: "Assign this gang member to commit acts of terrorism

Greatly increases respect - Greatly increases wanted level - Scales heavily with territory", + isCombat: true, + isHacking: false, + name: "Terrorism", + params: { + baseRespect: 0.01, baseWanted: 6, + hackWeight: 20, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 20, + difficulty: 36, + territory: { + money: 1, + respect: 2, + wanted: 2, + }, + }, + }, + { + desc: "Assign this gang member to be a vigilante and protect the city from criminals

Decreases wanted level", + isCombat: true, + isHacking: true, + name: "Vigilante Justice", + params: { + baseWanted: -0.001, + hackWeight: 20, strWeight: 20, defWeight: 20, dexWeight: 20, agiWeight: 20, + difficulty: 1, + territory: { + money: 1, + respect: 1, + wanted: 0.9, // Gets harder with more territory + }, + }, + }, + { + desc: "Assign this gang member to increase their combat stats (str, def, dex, agi)", + isCombat: true, + isHacking: true, + name: "Train Combat", + params: { + strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 25, + difficulty: 5, + }, + }, + { + desc: "Assign this gang member to train their hacking skills", + isCombat: true, + isHacking: true, + name: "Train Hacking", + params: {hackWeight: 100, difficulty: 8}, + }, + { + desc: "Assign this gang member to train their charisma", + isCombat: true, + isHacking: true, + name: "Train Charisma", + params: {chaWeight: 100, difficulty: 8}, + }, + { + desc: "Assign this gang member to engage in territorial warfare with other gangs. Members assigned to this task will help increase your gang's territory and will defend your territory from being taken.", + isCombat: true, + isHacking: true, + name: "Territory Warfare", + params: { + hackWeight: 15, strWeight: 20, defWeight: 20, dexWeight: 20, agiWeight: 20, chaWeight: 5, + difficulty: 5, + }, + }, +]; \ No newline at end of file diff --git a/src/Gang/data/upgrades.ts b/src/Gang/data/upgrades.ts new file mode 100644 index 000000000..5af0dd7ed --- /dev/null +++ b/src/Gang/data/upgrades.ts @@ -0,0 +1,218 @@ +export interface IMults { + hack?: number; + str?: number; + def?: number; + dex?: number; + agi?: number; + cha?: number; +}; + +/** + * Defines the parameters that can be used to initialize and describe a GangMemberUpgrade + * (defined in Gang.js) + */ +export interface IGangMemberUpgradeMetadata { + cost: number; + mults: IMults; + name: string; + upgType: string; +} + +/** + * Array of metadata for all Gang Member upgrades. Used to construct the global GangMemberUpgrade + * objects in Gang.js + */ +export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [ + { + cost: 1e6, + mults: {str: 1.04, def: 1.04}, + name: "Baseball Bat", + upgType: "w", + }, + { + cost: 12e6, + mults: {str: 1.08, def: 1.08, dex: 1.08}, + name: "Katana", + upgType: "w", + }, + { + cost: 25e6, + mults: {str: 1.1, def: 1.1, dex: 1.1, agi: 1.1}, + name: "Glock 18C", + upgType: "w", + }, + { + cost: 50e6, + mults: {str: 1.12, def: 1.1, agi: 1.1}, + name: "P90C", + upgType: "w", + }, + { + cost: 60e6, + mults: {str: 1.2, def: 1.15}, + name: "Steyr AUG", + upgType: "w", + }, + { + cost: 100e6, + mults: {str: 1.25, def: 1.2}, + name: "AK-47", + upgType: "w", + }, + { + cost: 150e6, + mults: {str: 1.3, def: 1.25}, + name: "M15A10 Assault Rifle", + upgType: "w", + }, + { + cost: 225e6, + mults: {str: 1.3, dex: 1.25, agi: 1.3}, + name: "AWM Sniper Rifle", + upgType: "w", + }, + { + cost: 2e6, + mults: {def: 1.04}, + name: "Bulletproof Vest", + upgType: "a", + }, + { + cost: 5e6, + mults: {def: 1.08}, + name: "Full Body Armor", + upgType: "a", + }, + { + cost: 25e6, + mults: {def: 1.15, agi: 1.15}, + name: "Liquid Body Armor", + upgType: "a", + }, + { + cost: 40e6, + mults: {def: 1.2}, + name: "Graphene Plating Armor", + upgType: "a", + }, + { + cost: 3e6, + mults: {agi: 1.04, cha: 1.04}, + name: "Ford Flex V20", + upgType: "v", + }, + { + cost: 9e6, + mults: {agi: 1.08, cha: 1.08}, + name: "ATX1070 Superbike", + upgType: "v", + }, + { + cost: 18e6, + mults: {agi: 1.12, cha: 1.12}, + name: "Mercedes-Benz S9001", + upgType: "v", + }, + { + cost: 30e6, + mults: {agi: 1.16, cha: 1.16}, + name: "White Ferrari", + upgType: "v", + }, + { + cost: 5e6, + mults: {hack: 1.05}, + name: "NUKE Rootkit", + upgType: "r", + }, + { + cost: 25e6, + mults: {hack: 1.1}, + name: "Soulstealer Rootkit", + upgType: "r", + }, + { + cost: 75e6, + mults: {hack: 1.15}, + name: "Demon Rootkit", + upgType: "r", + }, + { + cost: 40e6, + mults: {hack: 1.12}, + name: "Hmap Node", + upgType: "r", + }, + { + cost: 75e6, + mults: {hack: 1.15}, + name: "Jack the Ripper", + upgType: "r", + }, + { + cost: 10e9, + mults: {str: 1.3, dex: 1.3}, + name: "Bionic Arms", + upgType: "g", + }, + { + cost: 10e9, + mults: {agi: 1.6}, + name: "Bionic Legs", + upgType: "g", + }, + { + cost: 15e9, + mults: {str: 1.15, def: 1.15, dex: 1.15, agi: 1.15}, + name: "Bionic Spine", + upgType: "g", + }, + { + cost: 20e9, + mults: {str: 1.4, def: 1.4}, + name: "BrachiBlades", + upgType: "g", + }, + { + cost: 12e9, + mults: {str: 1.2, def: 1.2}, + name: "Nanofiber Weave", + upgType: "g", + }, + { + cost: 25e9, + mults: {str: 1.5, agi: 1.5}, + name: "Synthetic Heart", + upgType: "g", + }, + { + cost: 15e9, + mults: {str: 1.3, def: 1.3}, + name: "Synfibril Muscle", + upgType: "g", + }, + { + cost: 5e9, + mults: {hack: 1.05}, + name: "BitWire", + upgType: "g", + }, + { + cost: 10e9, + mults: {hack: 1.15}, + name: "Neuralstimulator", + upgType: "g", + }, + { + cost: 7.5e9, + mults: {hack: 1.1}, + name: "DataJack", + upgType: "g", + }, + { + cost: 50e9, + mults: {str: 1.7, def: 1.7}, + name: "Graphene Bone Lacings", + upgType: "g", + }, +]; diff --git a/src/Gang/ui/Panel1.tsx b/src/Gang/ui/Panel1.tsx new file mode 100644 index 000000000..3de4ddc5e --- /dev/null +++ b/src/Gang/ui/Panel1.tsx @@ -0,0 +1,143 @@ +import * as React from "react"; +import { GangMember } from "../GangMember"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { formatNumber } from "../../../utils/StringHelperFunctions"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { createPopup, removePopup } from "../../ui/React/createPopup"; + +interface IAscendProps { + member: any; + gang: any; + popupId: string; +} + +function ascendPopup(props: IAscendProps): React.ReactElement { + function confirm() { + props.gang.ascendMember(props.member); + props.gang.updateGangMemberDisplayElement(props.member); + removePopup(props.popupId); + return false; + } + + function cancel() { + removePopup(props.popupId); + return false; + } + + const ascendBenefits = props.member.getAscensionResults(); + + return (<> +
+Are you sure you want to ascend this member? They will lose all of
+their non-Augmentation upgrades and their stats will reset back to 1.
+
+Furthermore, your gang will lose {numeralWrapper.formatRespect(props.member.earnedRespect)} respect
+
+In return, they will gain the following permanent boost to stat multipliers:
+Hacking: +{numeralWrapper.formatPercentage(ascendBenefits.hack)}
+Strength: +{numeralWrapper.formatPercentage(ascendBenefits.str)}
+Defense: +{numeralWrapper.formatPercentage(ascendBenefits.def)}
+Dexterity: +{numeralWrapper.formatPercentage(ascendBenefits.dex)}
+Agility: +{numeralWrapper.formatPercentage(ascendBenefits.agi)}
+Charisma: +{numeralWrapper.formatPercentage(ascendBenefits.cha)}
+        
+ + + ); +} + +interface IProps { + member: any; + gang: any; +} + +export function Panel1(props: IProps): React.ReactElement { + function ascend() { + const popupId = `gang-management-ascend-member ${props.member.name}`; + createPopup(popupId, ascendPopup, { + member: props.member, + gang: props.gang, + popupId: popupId, + }); + } + + return (<> +
+        Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)
+ Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)
+ Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)
+ Dexterity: {formatNumber(props.member.dex, 0)} ({numeralWrapper.formatExp(props.member.dex_exp)} exp)
+ Agility: {formatNumber(props.member.agi, 0)} ({numeralWrapper.formatExp(props.member.agi_exp)} exp)
+ Charisma: {formatNumber(props.member.cha, 0)} ({numeralWrapper.formatExp(props.member.cha_exp)} exp)
+
+
+ + ); +} + + +/* + +const ascendButton = createElement("button", { + class: "accordion-button", + innerText: "Ascend", + clickListener: () => { + const popupId = `gang-management-ascend-member ${memberObj.name}`; + const ascendBenefits = memberObj.getAscensionResults(); + const txt = createElement("pre", { + innerText: ["Are you sure you want to ascend this member? They will lose all of", + "their non-Augmentation upgrades and their stats will reset back to 1.", + "", + `Furthermore, your gang will lose ${numeralWrapper.formatRespect(memberObj.earnedRespect)} respect`, + "", + "In return, they will gain the following permanent boost to stat multipliers:\n", + `Hacking: +${numeralWrapper.formatPercentage(ascendBenefits.hack)}`, + `Strength: +${numeralWrapper.formatPercentage(ascendBenefits.str)}`, + `Defense: +${numeralWrapper.formatPercentage(ascendBenefits.def)}`, + `Dexterity: +${numeralWrapper.formatPercentage(ascendBenefits.dex)}`, + `Agility: +${numeralWrapper.formatPercentage(ascendBenefits.agi)}`, + `Charisma: +${numeralWrapper.formatPercentage(ascendBenefits.cha)}`].join("\n"), + }); + const confirmBtn = createElement("button", { + class: "std-button", + clickListener: () => { + this.ascendMember(memberObj); + this.updateGangMemberDisplayElement(memberObj); + removePopup(popupId); + return false; + }, + innerText: "Ascend", + }); + const cancelBtn = createElement("button", { + class: "std-button", + clickListener: () => { + removePopup(popupId); + return false; + }, + innerText: "Cancel", + }); + createPopup(popupId, [txt, confirmBtn, cancelBtn]); + }, +}); + + +const ascendHelpTip = createElement("div", { + class: "help-tip", + clickListener: () => { + dialogBoxCreate(["Ascending a Gang Member resets the member's progress and stats in exchange", + "for a permanent boost to their stat multipliers.", + "

The additional stat multiplier that the Gang Member gains upon ascension", + "is based on the amount of multipliers the member has from non-Augmentation Equipment.", + "

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."].join(" ")); + }, + innerText: "?", + marginTop: "5px", +}); + + statsDiv.appendChild(statsP); + statsDiv.appendChild(brElement); + statsDiv.appendChild(ascendButton); + statsDiv.appendChild(ascendHelpTip); + +*/ \ No newline at end of file diff --git a/src/Locations/data/LocationsMetadata.ts b/src/Locations/data/LocationsMetadata.ts index dac9f8aa8..1b996c30c 100644 --- a/src/Locations/data/LocationsMetadata.ts +++ b/src/Locations/data/LocationsMetadata.ts @@ -440,4 +440,4 @@ export const LocationsMetadata: IConstructorParams[] = [ name: LocationName.WorldStockExchange, types: [LocationType.StockMarket], }, -]; +]; \ No newline at end of file diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 486b0ddc0..d05380478 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -31,10 +31,10 @@ import { import { calculateServerGrowth } from "./Server/formulas/grow"; import { AllGangs, - GangMemberUpgrades, - GangMemberTasks, Gang, } from "./Gang"; +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"; diff --git a/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts b/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts index 4ccd0a772..662c1ee63 100644 --- a/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts +++ b/src/PersonObjects/Sleeve/SleeveCovenantPurchases.ts @@ -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) }); } diff --git a/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx b/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx index cfe8d9801..91fd3f8f3 100644 --- a/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx +++ b/src/PersonObjects/Sleeve/ui/CovenantPurchasesRoot.tsx @@ -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 { - 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( - , - ) + 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 ( -
- -

- Would you like to purchase an additional Duplicate Sleeve from The Covenant - for {Money(this.purchaseCost())}? -

-
-

- These Duplicate Sleeves are permanent (they persist through BitNodes). You can - purchase a total of {MaxSleevesFromCovenant} from The Covenant. -

- -

-

- Here, you can also purchase upgrades for your Duplicate Sleeves. These upgrades - are also permanent, meaning they persist across BitNodes. -

- {upgradePanels} -
+ // Purchasing Upgrades for Sleeves + const upgradePanels = []; + for (let i = 0; i < props.p.sleeves.length; ++i) { + const sleeve = props.p.sleeves[i]; + upgradePanels.push( + , ) } + + return (
+ +

+ Would you like to purchase an additional Duplicate Sleeve from The Covenant + for {Money(purchaseCost())}? +

+
+

+ These Duplicate Sleeves are permanent (they persist through BitNodes). You can + purchase a total of {MaxSleevesFromCovenant} from The Covenant. +

+ +

+

+ Here, you can also purchase upgrades for your Duplicate Sleeves. These upgrades + are also permanent, meaning they persist across BitNodes. +

+ {upgradePanels} +
); } diff --git a/src/ui/React/CodingContractPopup.tsx b/src/ui/React/CodingContractPopup.tsx index 0337cb16e..d7c2dd58e 100644 --- a/src/ui/React/CodingContractPopup.tsx +++ b/src/ui/React/CodingContractPopup.tsx @@ -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"; @@ -16,19 +16,14 @@ type IState = { answer: string; } -export class CodingContractPopup extends React.Component{ - constructor(props: IProps) { - super(props); - this.state = { answer: ''}; - this.setAnswer = this.setAnswer.bind(this); - this.onInputKeydown = this.onInputKeydown.bind(this); +export function CodingContractPopup(props: IProps): React.ReactElement { + const [answer, setAnswer] = useState(""); + + function onChange(event: React.ChangeEvent): void { + setAnswer(event.target.value); } - setAnswer(event: React.ChangeEvent): void { - this.setState({ answer: event.target.value }); - } - - onInputKeydown(event: React.KeyboardEvent): void { + function onKeyDown(event: React.KeyboardEvent): void { // React just won't cooperate on this one. // "React.KeyboardEvent" seems like the right type but // whatever ... @@ -36,31 +31,41 @@ export class CodingContractPopup extends React.Component{ 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('}}>); - return ( -
- -

-

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.

-
-

{description}

-
- - this.props.onAttempt(this.state.answer)} text={"Solve"} /> - -
- ) - } + const contractType: CodingContractType = CodingContractTypes[props.c.type]; + const description = []; + for (const [i, value] of contractType.desc(props.c.data).split('\n').entries()) + description.push('}}>); + return ( +
+ +

+

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.

+
+

{description}

+
+ + props.onAttempt(answer)} + text={"Solve"} /> + +
+ ) } \ No newline at end of file diff --git a/src/ui/React/Popup.tsx b/src/ui/React/Popup.tsx index 89f2a3c12..cc777eaa9 100644 --- a/src/ui/React/Popup.tsx +++ b/src/ui/React/Popup.tsx @@ -5,15 +5,13 @@ */ import * as React from "react"; -type ReactComponent = new(...args: any[]) => React.Component - -interface IProps { - content: ReactComponent; +interface IProps { + content: (props: T) => React.ReactElement; id: string; - props: any; + props: T; } -export function Popup(props: IProps): React.ReactElement { +export function Popup(props: IProps): React.ReactElement { return (
{React.createElement(props.content, props.props)} diff --git a/src/ui/React/createPopup.tsx b/src/ui/React/createPopup.tsx index 0f1a7d4a3..0060b9df0 100644 --- a/src/ui/React/createPopup.tsx +++ b/src/ui/React/createPopup.tsx @@ -31,7 +31,7 @@ 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(id: string, rootComponent: (props: T) => React.ReactElement, props: T): HTMLElement | null { let container = document.getElementById(id); if (container == null) { container = createElement("div", { From 5863797b03d96a1911e4c61183e17f03f4f016b5 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 14 Jun 2021 17:34:07 -0400 Subject: [PATCH 02/24] minor refactor --- src/Gang.jsx | 67 +---------------------- src/Gang/ui/Panel1.tsx | 117 +++++++++++++---------------------------- 2 files changed, 39 insertions(+), 145 deletions(-) diff --git a/src/Gang.jsx b/src/Gang.jsx index 69b362bae..2f3f4287d 100644 --- a/src/Gang.jsx +++ b/src/Gang.jsx @@ -1635,7 +1635,7 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) { // Gang member content divided into 3 panels: // Panel 1 - Shows member's stats & Ascension stuff const statsDiv = createElement("div", { - class: "gang-member-info-div", + class: "gang-member-info-div tooltip", id: name + "gang-member-stats", tooltipsmall: [`Hk: x${numeralWrapper.formatMultiplier(memberObj.hack_mult * memberObj.hack_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.hack_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.hack_asc_mult)} Asc)`, `St: x${numeralWrapper.formatMultiplier(memberObj.str_mult * memberObj.str_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.str_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.str_asc_mult)} Asc)`, @@ -1645,70 +1645,7 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) { `Ch: x${numeralWrapper.formatMultiplier(memberObj.cha_mult * memberObj.cha_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.cha_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.cha_asc_mult)} Asc)`].join("
"), }); UIElems.gangMemberPanels[name]["statsDiv"] = statsDiv; - const statsP = createElement("pre", { - display: "inline", - id: name + "gang-member-stats-text", - }); - const brElement = createElement("br"); - const ascendButton = createElement("button", { - class: "accordion-button", - innerText: "Ascend", - clickListener: () => { - const popupId = `gang-management-ascend-member ${memberObj.name}`; - const ascendBenefits = memberObj.getAscensionResults(); - const txt = createElement("pre", { - innerText: ["Are you sure you want to ascend this member? They will lose all of", - "their non-Augmentation upgrades and their stats will reset back to 1.", - "", - `Furthermore, your gang will lose ${numeralWrapper.formatRespect(memberObj.earnedRespect)} respect`, - "", - "In return, they will gain the following permanent boost to stat multipliers:\n", - `Hacking: +${numeralWrapper.formatPercentage(ascendBenefits.hack)}`, - `Strength: +${numeralWrapper.formatPercentage(ascendBenefits.str)}`, - `Defense: +${numeralWrapper.formatPercentage(ascendBenefits.def)}`, - `Dexterity: +${numeralWrapper.formatPercentage(ascendBenefits.dex)}`, - `Agility: +${numeralWrapper.formatPercentage(ascendBenefits.agi)}`, - `Charisma: +${numeralWrapper.formatPercentage(ascendBenefits.cha)}`].join("\n"), - }); - const confirmBtn = createElement("button", { - class: "std-button", - clickListener: () => { - this.ascendMember(memberObj); - this.updateGangMemberDisplayElement(memberObj); - removeElementById(popupId); - return false; - }, - innerText: "Ascend", - }); - const cancelBtn = createElement("button", { - class: "std-button", - clickListener: () => { - removeElementById(popupId); - return false; - }, - innerText: "Cancel", - }); - createPopup(popupId, [txt, confirmBtn, cancelBtn]); - }, - }); - const ascendHelpTip = createElement("div", { - class: "help-tip", - clickListener: () => { - dialogBoxCreate(["Ascending a Gang Member resets the member's progress and stats in exchange", - "for a permanent boost to their stat multipliers.", - "

The additional stat multiplier that the Gang Member gains upon ascension", - "is based on the amount of multipliers the member has from non-Augmentation Equipment.", - "

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."].join(" ")); - }, - innerText: "?", - marginTop: "5px", - }); - - statsDiv.appendChild(statsP); - statsDiv.appendChild(brElement); - statsDiv.appendChild(ascendButton); - statsDiv.appendChild(ascendHelpTip); + ReactDOM.render(, statsDiv); // Panel 2 - Task Selection & Info const taskDiv = createElement("div", { diff --git a/src/Gang/ui/Panel1.tsx b/src/Gang/ui/Panel1.tsx index 3de4ddc5e..6f3762983 100644 --- a/src/Gang/ui/Panel1.tsx +++ b/src/Gang/ui/Panel1.tsx @@ -28,18 +28,18 @@ function ascendPopup(props: IAscendProps): React.ReactElement { return (<>
-Are you sure you want to ascend this member? They will lose all of
-their non-Augmentation upgrades and their stats will reset back to 1.
-
-Furthermore, your gang will lose {numeralWrapper.formatRespect(props.member.earnedRespect)} respect
-
-In return, they will gain the following permanent boost to stat multipliers:
-Hacking: +{numeralWrapper.formatPercentage(ascendBenefits.hack)}
-Strength: +{numeralWrapper.formatPercentage(ascendBenefits.str)}
-Defense: +{numeralWrapper.formatPercentage(ascendBenefits.def)}
-Dexterity: +{numeralWrapper.formatPercentage(ascendBenefits.dex)}
-Agility: +{numeralWrapper.formatPercentage(ascendBenefits.agi)}
-Charisma: +{numeralWrapper.formatPercentage(ascendBenefits.cha)}
+Are you sure you want to ascend this member? They will lose all of
+their non-Augmentation upgrades and their stats will reset back to 1.
+
+Furthermore, your gang will lose {numeralWrapper.formatRespect(props.member.earnedRespect)} respect
+
+In return, they will gain the following permanent boost to stat multipliers:
+Hacking: +{numeralWrapper.formatPercentage(ascendBenefits.hack)}
+Strength: +{numeralWrapper.formatPercentage(ascendBenefits.str)}
+Defense: +{numeralWrapper.formatPercentage(ascendBenefits.def)}
+Dexterity: +{numeralWrapper.formatPercentage(ascendBenefits.dex)}
+Agility: +{numeralWrapper.formatPercentage(ascendBenefits.agi)}
+Charisma: +{numeralWrapper.formatPercentage(ascendBenefits.cha)}
@@ -61,8 +61,31 @@ export function Panel1(props: IProps): React.ReactElement { }); } + 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. +

+ The additional stat multiplier that the Gang Member gains upon + ascension is based on the amount of multipliers the member has from + non-Augmentation Equipment. +

+ 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. + ); + } + return (<> -
+        
+Hk: x{numeralWrapper.formatMultiplier(props.member.hack_mult * props.member.hack_asc_mult)}(x{numeralWrapper.formatMultiplier(props.member.hack_mult)} Eq, x{numeralWrapper.formatMultiplier(props.member.hack_asc_mult)} Asc)
+St: x{numeralWrapper.formatMultiplier(props.member.str_mult * props.member.str_asc_mult)}(x{numeralWrapper.formatMultiplier(props.member.str_mult)} Eq, x{numeralWrapper.formatMultiplier(props.member.str_asc_mult)} Asc)
+Df: x{numeralWrapper.formatMultiplier(props.member.def_mult * props.member.def_asc_mult)}(x{numeralWrapper.formatMultiplier(props.member.def_mult)} Eq, x{numeralWrapper.formatMultiplier(props.member.def_asc_mult)} Asc)
+Dx: x{numeralWrapper.formatMultiplier(props.member.dex_mult * props.member.dex_asc_mult)}(x{numeralWrapper.formatMultiplier(props.member.dex_mult)} Eq, x{numeralWrapper.formatMultiplier(props.member.dex_asc_mult)} Asc)
+Ag: x{numeralWrapper.formatMultiplier(props.member.agi_mult * props.member.agi_asc_mult)}(x{numeralWrapper.formatMultiplier(props.member.agi_mult)} Eq, x{numeralWrapper.formatMultiplier(props.member.agi_asc_mult)} Asc)
+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) +
+
         Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)
Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)
Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)
@@ -72,72 +95,6 @@ export function Panel1(props: IProps): React.ReactElement {

+
?
); } - - -/* - -const ascendButton = createElement("button", { - class: "accordion-button", - innerText: "Ascend", - clickListener: () => { - const popupId = `gang-management-ascend-member ${memberObj.name}`; - const ascendBenefits = memberObj.getAscensionResults(); - const txt = createElement("pre", { - innerText: ["Are you sure you want to ascend this member? They will lose all of", - "their non-Augmentation upgrades and their stats will reset back to 1.", - "", - `Furthermore, your gang will lose ${numeralWrapper.formatRespect(memberObj.earnedRespect)} respect`, - "", - "In return, they will gain the following permanent boost to stat multipliers:\n", - `Hacking: +${numeralWrapper.formatPercentage(ascendBenefits.hack)}`, - `Strength: +${numeralWrapper.formatPercentage(ascendBenefits.str)}`, - `Defense: +${numeralWrapper.formatPercentage(ascendBenefits.def)}`, - `Dexterity: +${numeralWrapper.formatPercentage(ascendBenefits.dex)}`, - `Agility: +${numeralWrapper.formatPercentage(ascendBenefits.agi)}`, - `Charisma: +${numeralWrapper.formatPercentage(ascendBenefits.cha)}`].join("\n"), - }); - const confirmBtn = createElement("button", { - class: "std-button", - clickListener: () => { - this.ascendMember(memberObj); - this.updateGangMemberDisplayElement(memberObj); - removePopup(popupId); - return false; - }, - innerText: "Ascend", - }); - const cancelBtn = createElement("button", { - class: "std-button", - clickListener: () => { - removePopup(popupId); - return false; - }, - innerText: "Cancel", - }); - createPopup(popupId, [txt, confirmBtn, cancelBtn]); - }, -}); - - -const ascendHelpTip = createElement("div", { - class: "help-tip", - clickListener: () => { - dialogBoxCreate(["Ascending a Gang Member resets the member's progress and stats in exchange", - "for a permanent boost to their stat multipliers.", - "

The additional stat multiplier that the Gang Member gains upon ascension", - "is based on the amount of multipliers the member has from non-Augmentation Equipment.", - "

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."].join(" ")); - }, - innerText: "?", - marginTop: "5px", -}); - - statsDiv.appendChild(statsP); - statsDiv.appendChild(brElement); - statsDiv.appendChild(ascendButton); - statsDiv.appendChild(ascendHelpTip); - -*/ \ No newline at end of file From 69dfbb6673d6c80e5a8cce7ac00e314cbf2c6e38 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 14 Jun 2021 18:46:23 -0400 Subject: [PATCH 03/24] panel 2 done --- src/Gang.jsx | 138 +++++++++++++++++------------------------ src/Gang/ui/Panel1.tsx | 10 ++- src/Gang/ui/Panel2.tsx | 48 ++++++++++++++ src/Gang/ui/Panel3.tsx | 0 src/engine.jsx | 2 + 5 files changed, 116 insertions(+), 82 deletions(-) create mode 100644 src/Gang/ui/Panel2.tsx create mode 100644 src/Gang/ui/Panel3.tsx diff --git a/src/Gang.jsx b/src/Gang.jsx index 2f3f4287d..5bd7987ad 100644 --- a/src/Gang.jsx +++ b/src/Gang.jsx @@ -50,6 +50,7 @@ import { GangMemberTasks } from "./Gang/GangMemberTasks"; import { GangMemberTask } from "./Gang/GangMemberTask"; import { Panel1 } from "./Gang/ui/Panel1"; +import { Panel2 } from "./Gang/ui/Panel2"; import React from "react"; import ReactDOM from "react-dom"; @@ -1130,6 +1131,18 @@ const UIElems = { gangTerritoryInfoText: null, } +export function unmount() { + for(const name of Object.keys(UIElems.gangMemberPanels)) { + if(!UIElems.gangMemberPanels[name]) continue + if(UIElems.gangMemberPanels[name]["statsDiv"]) { + ReactDOM.unmountComponentAtNode(UIElems.gangMemberPanels[name]["statsDiv"]); + } + if(UIElems.gangMemberPanels[name]["taskDiv"]) { + ReactDOM.unmountComponentAtNode(UIElems.gangMemberPanels[name]["taskDiv"]); + } + } +} + Gang.prototype.displayGangContent = function(player) { if (!UIElems.gangContentCreated || UIElems.gangContainer == null) { UIElems.gangContentCreated = true; @@ -1634,64 +1647,56 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) { // Gang member content divided into 3 panels: // Panel 1 - Shows member's stats & Ascension stuff - const statsDiv = createElement("div", { - class: "gang-member-info-div tooltip", - id: name + "gang-member-stats", - tooltipsmall: [`Hk: x${numeralWrapper.formatMultiplier(memberObj.hack_mult * memberObj.hack_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.hack_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.hack_asc_mult)} Asc)`, - `St: x${numeralWrapper.formatMultiplier(memberObj.str_mult * memberObj.str_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.str_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.str_asc_mult)} Asc)`, - `Df: x${numeralWrapper.formatMultiplier(memberObj.def_mult * memberObj.def_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.def_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.def_asc_mult)} Asc)`, - `Dx: x${numeralWrapper.formatMultiplier(memberObj.dex_mult * memberObj.dex_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.dex_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.dex_asc_mult)} Asc)`, - `Ag: x${numeralWrapper.formatMultiplier(memberObj.agi_mult * memberObj.agi_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.agi_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.agi_asc_mult)} Asc)`, - `Ch: x${numeralWrapper.formatMultiplier(memberObj.cha_mult * memberObj.cha_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.cha_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.cha_asc_mult)} Asc)`].join("
"), - }); - UIElems.gangMemberPanels[name]["statsDiv"] = statsDiv; - ReactDOM.render(, statsDiv); + let statsDiv = document.getElementById(name + "-gang-member-stats"); + if(!statsDiv) { + statsDiv = createElement("div", { + class: "gang-member-info-div tooltip", + id: name + "-gang-member-stats", + }); + UIElems.gangMemberPanels[name]["statsDiv"] = statsDiv; + ReactDOM.render(, statsDiv); + } // Panel 2 - Task Selection & Info - const taskDiv = createElement("div", { - class:"gang-member-info-div", - id: name + "gang-member-task", - }); - const taskSelector = createElement("select", { - class: "dropdown", - id: name + "gang-member-task-selector", - }); - - // Get an array of the name of all tasks that are applicable for this Gang - let tasks = this.getAllTaskNames(); - tasks.unshift("---"); - - // Create selector for Gang member task - for (var i = 0; i < tasks.length; ++i) { - var option = document.createElement("option"); - option.text = tasks[i]; - taskSelector.add(option); + let taskDiv = document.getElementById(name + "-gang-member-task"); + if(!taskDiv) { + taskDiv = createElement("div", { + class:"gang-member-info-div", + id: name + "-gang-member-task", + }); + UIElems.gangMemberPanels[name]["taskDiv"] = taskDiv; + ReactDOM.render(, taskDiv); } - taskSelector.addEventListener("change", () => { - var task = taskSelector.options[taskSelector.selectedIndex].text; - memberObj.assignToTask(task); - this.setGangMemberTaskDescription(memberObj, task); - this.updateGangContent(); - }); + + // const taskSelector = createElement("select", { + // class: "dropdown", + // id: name + "gang-member-task-selector", + // }); - // Set initial task in selector - if (GangMemberTasks.hasOwnProperty(memberObj.task)) { - var taskName = memberObj.task; - var taskIndex = 0; - for (let i = 0; i < tasks.length; ++i) { - if (taskName === tasks[i]) { - taskIndex = i; - break; - } - } - taskSelector.selectedIndex = taskIndex; - } + // // Get an array of the name of all tasks that are applicable for this Gang + // let tasks = this.getAllTaskNames(); + // tasks.unshift("---"); - var gainInfo = createElement("div", {id:name + "gang-member-gain-info"}); - taskDiv.appendChild(taskSelector); - taskDiv.appendChild(gainInfo); + // // Create selector for Gang member task + // for (var i = 0; i < tasks.length; ++i) { + // var option = document.createElement("option"); + // option.text = tasks[i]; + // taskSelector.add(option); + // } + // taskSelector.addEventListener("change", () => { + // var task = taskSelector.options[taskSelector.selectedIndex].text; + // memberObj.assignToTask(task); + // this.setGangMemberTaskDescription(memberObj, task); + // this.updateGangContent(); + // }); - // Panel for Description of task + // // Set initial task in selector + + // var gainInfo = createElement("div", {id:name + "gang-member-gain-info"}); + // taskDiv.appendChild(taskSelector); + // taskDiv.appendChild(gainInfo); + + // Panel 3 - for Description of task var taskDescDiv = createElement("div", { class:"gang-member-info-div", id: name + "gang-member-task-desc", @@ -1708,7 +1713,7 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) { gangMemberDiv.appendChild(taskDescDiv); UIElems.gangMemberList.appendChild(li); - this.setGangMemberTaskDescription(memberObj, taskName); //Initialize description, TODO doesnt work rn + this.setGangMemberTaskDescription(memberObj, memberObj.task); //Initialize description, TODO doesnt work rn this.updateGangMemberDisplayElement(memberObj); } @@ -1716,33 +1721,6 @@ Gang.prototype.updateGangMemberDisplayElement = function(memberObj) { if (!UIElems.gangContentCreated) { return; } var name = memberObj.name; - // Update stats + exp - var stats = document.getElementById(name + "gang-member-stats-text"); - if (stats) { - stats.innerText = - [`Hacking: ${formatNumber(memberObj.hack, 0)} (${numeralWrapper.formatExp(memberObj.hack_exp)} exp)`, - `Strength: ${formatNumber(memberObj.str, 0)} (${numeralWrapper.formatExp(memberObj.str_exp)} exp)`, - `Defense: ${formatNumber(memberObj.def, 0)} (${numeralWrapper.formatExp(memberObj.def_exp)} exp)`, - `Dexterity: ${formatNumber(memberObj.dex, 0)} (${numeralWrapper.formatExp(memberObj.dex_exp)} exp)`, - `Agility: ${formatNumber(memberObj.agi, 0)} (${numeralWrapper.formatExp(memberObj.agi_exp)} exp)`, - `Charisma: ${formatNumber(memberObj.cha, 0)} (${numeralWrapper.formatExp(memberObj.cha_exp)} exp)`].join("\n"); - } - - // Update tooltip for stat multipliers - const panel = UIElems.gangMemberPanels[name]; - if (panel) { - const statsDiv = panel["statsDiv"]; - if (statsDiv) { - statsDiv.firstChild.innerHTML = - [`Hk: x${numeralWrapper.formatMultiplier(memberObj.hack_mult * memberObj.hack_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.hack_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.hack_asc_mult)} Asc)`, - `St: x${numeralWrapper.formatMultiplier(memberObj.str_mult * memberObj.str_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.str_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.str_asc_mult)} Asc)`, - `Df: x${numeralWrapper.formatMultiplier(memberObj.def_mult * memberObj.def_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.def_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.def_asc_mult)} Asc)`, - `Dx: x${numeralWrapper.formatMultiplier(memberObj.dex_mult * memberObj.dex_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.dex_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.dex_asc_mult)} Asc)`, - `Ag: x${numeralWrapper.formatMultiplier(memberObj.agi_mult * memberObj.agi_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.agi_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.agi_asc_mult)} Asc)`, - `Ch: x${numeralWrapper.formatMultiplier(memberObj.cha_mult * memberObj.cha_asc_mult)}(x${numeralWrapper.formatMultiplier(memberObj.cha_mult)} Eq, x${numeralWrapper.formatMultiplier(memberObj.cha_asc_mult)} Asc)`].join("
"); - } - } - // Update info about gang member's earnings/gains var gainInfo = document.getElementById(name + "gang-member-gain-info"); if (gainInfo) { diff --git a/src/Gang/ui/Panel1.tsx b/src/Gang/ui/Panel1.tsx index 6f3762983..872943d2a 100644 --- a/src/Gang/ui/Panel1.tsx +++ b/src/Gang/ui/Panel1.tsx @@ -1,5 +1,4 @@ -import * as React from "react"; -import { GangMember } from "../GangMember"; +import React, { useState, useEffect } from "react"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { formatNumber } from "../../../utils/StringHelperFunctions"; import { numeralWrapper } from "../../ui/numeralFormat"; @@ -52,6 +51,13 @@ interface IProps { } export function Panel1(props: IProps): React.ReactElement { + const [rerender, setRerender] = useState(false); + + useEffect(() => { + const id = setInterval(() => setRerender(old => !old), 1000); + return () => clearInterval(id); + }, []); + function ascend() { const popupId = `gang-management-ascend-member ${props.member.name}`; createPopup(popupId, ascendPopup, { diff --git a/src/Gang/ui/Panel2.tsx b/src/Gang/ui/Panel2.tsx new file mode 100644 index 000000000..eabbc55a8 --- /dev/null +++ b/src/Gang/ui/Panel2.tsx @@ -0,0 +1,48 @@ +import React, { useState, useEffect } from "react"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { StatsTable } from "../../ui/React/StatsTable"; +import { MoneyRate } from "../../ui/React/MoneyRate"; + +interface IProps { + member: any; + gang: any; +} + +export function Panel2(props: IProps): React.ReactElement { + const [rerender, setRerender] = useState(false); + const [currentTask, setCurrentTask] = useState(props.member.task); + + useEffect(() => { + const id = setInterval(() => {setRerender(old => !old); console.log('rendering');}, 1000); + return () => clearInterval(id); + }, []); + + function onChange(event: React.ChangeEvent): void { + const task = event.target.value; + props.member.assignToTask(task); + props.gang.setGangMemberTaskDescription(props.member, task); + props.gang.updateGangContent(); + setCurrentTask(task); + } + + 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 (<> + +
{StatsTable(data, null)}
+ ); +} \ No newline at end of file diff --git a/src/Gang/ui/Panel3.tsx b/src/Gang/ui/Panel3.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/engine.jsx b/src/engine.jsx index de39148e7..5ad2bb153 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -39,6 +39,7 @@ import { getFactionFieldWorkRepGain, } from "./PersonObjects/formulas/reputation"; import { FconfSettings } from "./Fconf/FconfSettings"; +import { unmount as unmountGang } from "./Gang"; import { hasHacknetServers, renderHacknetNodesUI, @@ -531,6 +532,7 @@ const Engine = { Engine.Display.missionContent.style.display = "none"; if (document.getElementById("gang-container")) { document.getElementById("gang-container").style.display = "none"; + unmountGang(); } if (Player.inGang()) { From 26401fbb93a4710260c609171575822b00c4560c Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 14 Jun 2021 19:06:55 -0400 Subject: [PATCH 04/24] more panels! --- src/Gang.jsx | 106 ++++++----------------------------------- src/Gang/ui/Panel2.tsx | 3 +- src/Gang/ui/Panel3.tsx | 25 ++++++++++ 3 files changed, 40 insertions(+), 94 deletions(-) diff --git a/src/Gang.jsx b/src/Gang.jsx index 5bd7987ad..a442ff0fe 100644 --- a/src/Gang.jsx +++ b/src/Gang.jsx @@ -51,6 +51,7 @@ import { GangMemberTask } from "./Gang/GangMemberTask"; import { Panel1 } from "./Gang/ui/Panel1"; import { Panel2 } from "./Gang/ui/Panel2"; +import { Panel3 } from "./Gang/ui/Panel3"; import React from "react"; import ReactDOM from "react-dom"; @@ -1140,6 +1141,9 @@ export function unmount() { if(UIElems.gangMemberPanels[name]["taskDiv"]) { ReactDOM.unmountComponentAtNode(UIElems.gangMemberPanels[name]["taskDiv"]); } + if(UIElems.gangMemberPanels[name]["taskDescDiv"]) { + ReactDOM.unmountComponentAtNode(UIElems.gangMemberPanels[name]["taskDescDiv"]); + } } } @@ -1619,16 +1623,12 @@ Gang.prototype.updateGangContent = function() { UIElems.gangRecruitRequirementText.style.display = "inline-block"; UIElems.gangRecruitRequirementText.innerHTML = `${formatNumber(respectCost, 2)} respect needed to recruit next member`; } - - // Update information for each gang member - for (let i = 0; i < this.members.length; ++i) { - this.updateGangMemberDisplayElement(this.members[i]); - } } } // Takes in a GangMember object Gang.prototype.createGangMemberDisplayElement = function(memberObj) { + // TODO(hydroflame): you're working on this. if (!UIElems.gangContentCreated) { return; } const name = memberObj.name; @@ -1667,101 +1667,23 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) { UIElems.gangMemberPanels[name]["taskDiv"] = taskDiv; ReactDOM.render(, taskDiv); } - - // const taskSelector = createElement("select", { - // class: "dropdown", - // id: name + "gang-member-task-selector", - // }); - - // // Get an array of the name of all tasks that are applicable for this Gang - // let tasks = this.getAllTaskNames(); - // tasks.unshift("---"); - - // // Create selector for Gang member task - // for (var i = 0; i < tasks.length; ++i) { - // var option = document.createElement("option"); - // option.text = tasks[i]; - // taskSelector.add(option); - // } - // taskSelector.addEventListener("change", () => { - // var task = taskSelector.options[taskSelector.selectedIndex].text; - // memberObj.assignToTask(task); - // this.setGangMemberTaskDescription(memberObj, task); - // this.updateGangContent(); - // }); - - // // Set initial task in selector - - // var gainInfo = createElement("div", {id:name + "gang-member-gain-info"}); - // taskDiv.appendChild(taskSelector); - // taskDiv.appendChild(gainInfo); // Panel 3 - for Description of task - var taskDescDiv = createElement("div", { - class:"gang-member-info-div", - id: name + "gang-member-task-desc", - }); - - var taskDescP = createElement("p", { - display:"inline", - id: name + "gang-member-task-description", - }); - taskDescDiv.appendChild(taskDescP); + let taskDescDiv = document.getElementById(name + "-gang-member-task-description"); + if(!taskDescDiv) { + taskDescDiv = createElement("div", { + class:"gang-member-info-div", + id: name + "gang-member-task-desc", + }); + UIElems.gangMemberPanels[name]["taskDescDiv"] = taskDescDiv; + ReactDOM.render(, taskDescDiv); + } gangMemberDiv.appendChild(statsDiv); gangMemberDiv.appendChild(taskDiv); gangMemberDiv.appendChild(taskDescDiv); UIElems.gangMemberList.appendChild(li); - this.setGangMemberTaskDescription(memberObj, memberObj.task); //Initialize description, TODO doesnt work rn - this.updateGangMemberDisplayElement(memberObj); -} - -Gang.prototype.updateGangMemberDisplayElement = function(memberObj) { - if (!UIElems.gangContentCreated) { return; } - var name = memberObj.name; - - // Update info about gang member's earnings/gains - var gainInfo = document.getElementById(name + "gang-member-gain-info"); - if (gainInfo) { - const data = [ - [`Money:`, MoneyRate(5*memberObj.calculateMoneyGain(this))], - [`Respect:`, `${numeralWrapper.formatRespect(5*memberObj.calculateRespectGain(this))} / sec`], - [`Wanted Level:`, `${numeralWrapper.formatWanted(5*memberObj.calculateWantedLevelGain(this))} / sec`], - [`Total Respect:`, `${numeralWrapper.formatRespect(memberObj.earnedRespect)}`], - ]; - ReactDOM.render(StatsTable(data), gainInfo); - } - - // Update selector to have the correct task - const taskSelector = document.getElementById(name + "gang-member-task-selector"); - if (taskSelector) { - let tasks = this.getAllTaskNames(); - tasks.unshift("---"); - - if (GangMemberTasks.hasOwnProperty(memberObj.task)) { - const taskName = memberObj.task; - let taskIndex = 0; - for (let i = 0; i < tasks.length; ++i) { - if (taskName === tasks[i]) { - taskIndex = i; - break; - } - } - taskSelector.selectedIndex = taskIndex; - } - } -} - -Gang.prototype.setGangMemberTaskDescription = function(memberObj, taskName) { - const name = memberObj.name; - const taskDesc = document.getElementById(name + "gang-member-task-description"); - if (taskDesc) { - var task = GangMemberTasks[taskName]; - if (task == null) { task = GangMemberTasks["Unassigned"]; } - var desc = task.desc; - taskDesc.innerHTML = desc; - } } Gang.prototype.clearUI = function() { diff --git a/src/Gang/ui/Panel2.tsx b/src/Gang/ui/Panel2.tsx index eabbc55a8..cb3c3c60d 100644 --- a/src/Gang/ui/Panel2.tsx +++ b/src/Gang/ui/Panel2.tsx @@ -13,14 +13,13 @@ export function Panel2(props: IProps): React.ReactElement { const [currentTask, setCurrentTask] = useState(props.member.task); useEffect(() => { - const id = setInterval(() => {setRerender(old => !old); console.log('rendering');}, 1000); + const id = setInterval(() => setRerender(old => !old), 1000); return () => clearInterval(id); }, []); function onChange(event: React.ChangeEvent): void { const task = event.target.value; props.member.assignToTask(task); - props.gang.setGangMemberTaskDescription(props.member, task); props.gang.updateGangContent(); setCurrentTask(task); } diff --git a/src/Gang/ui/Panel3.tsx b/src/Gang/ui/Panel3.tsx index e69de29bb..5b0427e9d 100644 --- a/src/Gang/ui/Panel3.tsx +++ b/src/Gang/ui/Panel3.tsx @@ -0,0 +1,25 @@ +import React, { useState, useEffect } from "react"; +import { GangMemberTasks } from "../GangMemberTasks"; + +interface IProps { + member: any; +} + +export function Panel3(props: IProps): React.ReactElement { + const [rerender, setRerender] = useState(false); + + useEffect(() => { + const id = setInterval(() => setRerender(old => !old), 1000); + return () => clearInterval(id); + }, []); + + const task = GangMemberTasks[props.member.task]; + const desc = task ? task.desc: GangMemberTasks["Unassigned"].desc; + + return (<> +

+

+ ); +} \ No newline at end of file From 9c24f1325f6bcb70b445985a963bf5d7ba9b071e Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 14 Jun 2021 19:37:31 -0400 Subject: [PATCH 05/24] Gang member accordion done --- src/Gang.jsx | 68 +++------------------- src/Gang/ui/GangMemberAccordion.tsx | 15 +++++ src/Gang/ui/GangMemberAccordionContent.tsx | 23 ++++++++ src/Gang/ui/Panel3.tsx | 5 +- 4 files changed, 51 insertions(+), 60 deletions(-) create mode 100644 src/Gang/ui/GangMemberAccordion.tsx create mode 100644 src/Gang/ui/GangMemberAccordionContent.tsx diff --git a/src/Gang.jsx b/src/Gang.jsx index a442ff0fe..cbd36bad9 100644 --- a/src/Gang.jsx +++ b/src/Gang.jsx @@ -52,6 +52,8 @@ import { GangMemberTask } from "./Gang/GangMemberTask"; import { Panel1 } from "./Gang/ui/Panel1"; import { Panel2 } from "./Gang/ui/Panel2"; import { Panel3 } from "./Gang/ui/Panel3"; +import { GangMemberAccordionContent } from "./Gang/ui/GangMemberAccordionContent"; +import { GangMemberAccordion } from "./Gang/ui/GangMemberAccordion"; import React from "react"; import ReactDOM from "react-dom"; @@ -1134,16 +1136,8 @@ const UIElems = { export function unmount() { for(const name of Object.keys(UIElems.gangMemberPanels)) { - if(!UIElems.gangMemberPanels[name]) continue - if(UIElems.gangMemberPanels[name]["statsDiv"]) { - ReactDOM.unmountComponentAtNode(UIElems.gangMemberPanels[name]["statsDiv"]); - } - if(UIElems.gangMemberPanels[name]["taskDiv"]) { - ReactDOM.unmountComponentAtNode(UIElems.gangMemberPanels[name]["taskDiv"]); - } - if(UIElems.gangMemberPanels[name]["taskDescDiv"]) { - ReactDOM.unmountComponentAtNode(UIElems.gangMemberPanels[name]["taskDescDiv"]); - } + if(!UIElems.gangMemberPanels[name]) continue; + ReactDOM.unmountComponentAtNode(UIElems.gangMemberPanels[name]); } } @@ -1631,58 +1625,14 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) { // TODO(hydroflame): you're working on this. if (!UIElems.gangContentCreated) { return; } const name = memberObj.name; + const id = `${name}-gang-member-accordion`; + if(document.getElementById(id)) return; // Clear/Update the UIElems map to keep track of this gang member's panel UIElems.gangMemberPanels[name] = {}; - - // Create the accordion - var accordion = createAccordionElement({ - id: name + "gang-member", - hdrText: name, - }); - const li = accordion[0]; - const gangMemberDiv = accordion[2]; - - UIElems.gangMemberPanels[name]["panel"] = gangMemberDiv; - - // Gang member content divided into 3 panels: - // Panel 1 - Shows member's stats & Ascension stuff - let statsDiv = document.getElementById(name + "-gang-member-stats"); - if(!statsDiv) { - statsDiv = createElement("div", { - class: "gang-member-info-div tooltip", - id: name + "-gang-member-stats", - }); - UIElems.gangMemberPanels[name]["statsDiv"] = statsDiv; - ReactDOM.render(, statsDiv); - } - - // Panel 2 - Task Selection & Info - let taskDiv = document.getElementById(name + "-gang-member-task"); - if(!taskDiv) { - taskDiv = createElement("div", { - class:"gang-member-info-div", - id: name + "-gang-member-task", - }); - UIElems.gangMemberPanels[name]["taskDiv"] = taskDiv; - ReactDOM.render(, taskDiv); - } - - // Panel 3 - for Description of task - let taskDescDiv = document.getElementById(name + "-gang-member-task-description"); - if(!taskDescDiv) { - taskDescDiv = createElement("div", { - class:"gang-member-info-div", - id: name + "gang-member-task-desc", - }); - UIElems.gangMemberPanels[name]["taskDescDiv"] = taskDescDiv; - ReactDOM.render(, taskDescDiv); - } - - gangMemberDiv.appendChild(statsDiv); - gangMemberDiv.appendChild(taskDiv); - gangMemberDiv.appendChild(taskDescDiv); - + const li = createElement("li", {id: id}); + ReactDOM.render(, li); + UIElems.gangMemberPanels[name] = li; UIElems.gangMemberList.appendChild(li); } diff --git a/src/Gang/ui/GangMemberAccordion.tsx b/src/Gang/ui/GangMemberAccordion.tsx new file mode 100644 index 000000000..af388ecd0 --- /dev/null +++ b/src/Gang/ui/GangMemberAccordion.tsx @@ -0,0 +1,15 @@ +import * as React from "react"; +import { GangMemberAccordionContent } from "./GangMemberAccordionContent" +import { Accordion } from "../../ui/React/Accordion"; + +interface IProps { + gang: any; + member: any; +} + +export function GangMemberAccordion(props: IProps): React.ReactElement { + return ({props.member.name}} + panelContent={} + />); +} diff --git a/src/Gang/ui/GangMemberAccordionContent.tsx b/src/Gang/ui/GangMemberAccordionContent.tsx new file mode 100644 index 000000000..882a0149f --- /dev/null +++ b/src/Gang/ui/GangMemberAccordionContent.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import { Panel1 } from "./Panel1"; +import { Panel2 } from "./Panel2"; +import { Panel3 } from "./Panel3"; + +interface IProps { + gang: any; + member: any; +} + +export function GangMemberAccordionContent(props: IProps): React.ReactElement { + return (<> +
+ +
+
+ +
+
+ +
+ ); +} diff --git a/src/Gang/ui/Panel3.tsx b/src/Gang/ui/Panel3.tsx index 5b0427e9d..8f792811b 100644 --- a/src/Gang/ui/Panel3.tsx +++ b/src/Gang/ui/Panel3.tsx @@ -9,7 +9,10 @@ export function Panel3(props: IProps): React.ReactElement { const [rerender, setRerender] = useState(false); useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); + const id = setInterval(() => { + setRerender(old => !old); + console.log('render'); + }, 1000); return () => clearInterval(id); }, []); From 9466017906ab32ea800ca55c36ef1c74ebc6a65c Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Mon, 14 Jun 2021 20:20:07 -0400 Subject: [PATCH 06/24] modifying gang stats --- src/Gang.jsx | 5 +- src/Gang/ui/GangStats.tsx | 164 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/Gang/ui/GangStats.tsx diff --git a/src/Gang.jsx b/src/Gang.jsx index cbd36bad9..0d47dc13d 100644 --- a/src/Gang.jsx +++ b/src/Gang.jsx @@ -1519,6 +1519,8 @@ Gang.prototype.updateGangContent = function() { } } } else { + // TODO(hydroflame): you're working here + // Update information for overall gang if (UIElems.gangInfo instanceof Element) { var faction = Factions[this.facName]; @@ -1617,12 +1619,13 @@ Gang.prototype.updateGangContent = function() { UIElems.gangRecruitRequirementText.style.display = "inline-block"; UIElems.gangRecruitRequirementText.innerHTML = `${formatNumber(respectCost, 2)} respect needed to recruit next member`; } + + // TODO(hydroflame): TO HERE } } // Takes in a GangMember object Gang.prototype.createGangMemberDisplayElement = function(memberObj) { - // TODO(hydroflame): you're working on this. if (!UIElems.gangContentCreated) { return; } const name = memberObj.name; const id = `${name}-gang-member-accordion`; diff --git a/src/Gang/ui/GangStats.tsx b/src/Gang/ui/GangStats.tsx new file mode 100644 index 000000000..348685008 --- /dev/null +++ b/src/Gang/ui/GangStats.tsx @@ -0,0 +1,164 @@ +import React, { useState, useEffect } from "react"; + +export function GangStats(props: IProps): React.ReactElement { + const [rerender, setRerender] = useState(false); + + useEffect(() => { + const id = setInterval(() => setRerender(old => !old), 1000); + return () => clearInterval(id); + }, []); + + + return (

+

+ Respect: 108.82214 (0.23534 / sec) + + 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. + +

+
+

+ Wanted Level: 1.37503 (0.00002 / sec) + + 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. + +

+
+

+ Wanted Level Penalty: -1.25% + + Penalty for respect and money gain rates due to Wanted Level + +

+
+
+

+ Money gain rate: + + $2.571k / sec + +

+
+
+

+ Territory: 14.29% + + The percentage of total territory your Gang controls + +

+
+
+

+ Faction reputation: + + 28.677 + +

+
+
+

+ Bonus time: 1 hours 30 minutes 58 seconds + + 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 + +

+
+

); +} + + + +/* + +var faction = Factions[this.facName]; +var rep; +if (!(faction instanceof Faction)) { + rep = "ERROR"; +} else { + rep = faction.playerReputation; +} +UIElems.gangInfo.appendChild(createElement("p", { + display: "inline-block", + innerText: "Respect: " + numeralWrapper.formatRespect(this.respect) + + " (" + numeralWrapper.formatRespect(5*this.respectGainRate) + " / sec)", + tooltip: "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.", +})); +UIElems.gangInfo.appendChild(createElement("br")); + +UIElems.gangInfo.appendChild(createElement("p", { + display: "inline-block", + innerText: "Wanted Level: " + numeralWrapper.formatWanted(this.wanted) + + " (" + numeralWrapper.formatWanted(5*this.wantedGainRate) + " / sec)", + tooltip: "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.", +})); +UIElems.gangInfo.appendChild(createElement("br")); + +var wantedPenalty = this.getWantedPenalty(); +wantedPenalty = (1 - wantedPenalty) * 100; +UIElems.gangInfo.appendChild(createElement("p", { + display: "inline-block", + innerText: `Wanted Level Penalty: -${formatNumber(wantedPenalty, 2)}%`, + tooltip: "Penalty for respect and money gain rates due to Wanted Level", +})); +UIElems.gangInfo.appendChild(createElement("br")); + +const d0 = createElement("div"); +ReactDOM.render(

Money gain rate: {MoneyRate(5 * this.moneyGainRate)}

, d0); +UIElems.gangInfo.appendChild(d0); +UIElems.gangInfo.appendChild(createElement("br")); + +var territoryMult = AllGangs[this.facName].territory * 100; +let displayNumber; +if (territoryMult <= 0) { + displayNumber = formatNumber(0, 2); +} else if (territoryMult >= 100) { + displayNumber = formatNumber(100, 2); +} else { + displayNumber = formatNumber(territoryMult, 2); +} +UIElems.gangInfo.appendChild(createElement("p", { + display: "inline-block", + innerText: `Territory: ${formatNumber(displayNumber, 3)}%`, + tooltip: "The percentage of total territory your Gang controls", +})); +UIElems.gangInfo.appendChild(createElement("br")); + +const d1 = createElement("div"); +ReactDOM.render(

Faction reputation: {Reputation(rep)}

, d1); +UIElems.gangInfo.appendChild(d1); +UIElems.gangInfo.appendChild(createElement("br")); + +const CyclesPerSecond = 1000 / Engine._idleSpeed; +if (this.storedCycles / CyclesPerSecond*1000 > 5000) { + UIElems.gangInfo.appendChild(createElement("p", { + innerText: `Bonus time: ${convertTimeMsToTimeElapsedString(this.storedCycles / CyclesPerSecond*1000)}`, + display: "inline-block", + tooltip: "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", + })); + UIElems.gangInfo.appendChild(createElement("br")); +} + +const numMembers = this.members.length; +const respectCost = this.getRespectNeededToRecruitMember(); + +const btn = UIElems.gangRecruitMemberButton; +if (numMembers >= GangConstants.MaximumGangMembers) { + btn.className = "a-link-button-inactive"; + UIElems.gangRecruitRequirementText.style.display = "inline-block"; + UIElems.gangRecruitRequirementText.innerHTML = "You have reached the maximum amount of gang members"; +} else if (this.canRecruitMember()) { + btn.className = "a-link-button"; + UIElems.gangRecruitRequirementText.style.display = "none"; +} else { + btn.className = "a-link-button-inactive"; + UIElems.gangRecruitRequirementText.style.display = "inline-block"; + UIElems.gangRecruitRequirementText.innerHTML = `${formatNumber(respectCost, 2)} respect needed to recruit next member`; +} + +*/ \ No newline at end of file From 9e345b137566d60ffb807d3bdfa8545a2d6a8e60 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Wed, 16 Jun 2021 00:28:20 -0400 Subject: [PATCH 07/24] Mostly done converting Gang UI to React --- src/Gang.jsx | 762 +----------------- src/Gang/AllGangs.ts | 76 ++ src/Gang/ui/GangMemberAccordion.tsx | 15 - src/Gang/ui/GangMemberList.tsx | 51 ++ src/Gang/ui/GangMemberUpgradePopup.tsx | 175 ++++ src/Gang/ui/GangStats.tsx | 281 ++++--- src/Gang/ui/ManagementSubpage.tsx | 38 + src/Gang/ui/Panel1.tsx | 1 - src/Gang/ui/Panel2.tsx | 1 - src/Gang/ui/Panel3.tsx | 1 - src/Gang/ui/TerritorySubpage.tsx | 152 ++++ src/NetscriptFunctions.js | 6 +- .../Player/PlayerObjectGeneralMethods.jsx | 2 +- src/SaveObject.jsx | 2 +- src/engine.jsx | 4 +- src/ui/React/Accordion.tsx | 26 +- 16 files changed, 664 insertions(+), 929 deletions(-) create mode 100644 src/Gang/AllGangs.ts delete mode 100644 src/Gang/ui/GangMemberAccordion.tsx create mode 100644 src/Gang/ui/GangMemberList.tsx create mode 100644 src/Gang/ui/GangMemberUpgradePopup.tsx create mode 100644 src/Gang/ui/ManagementSubpage.tsx create mode 100644 src/Gang/ui/TerritorySubpage.tsx diff --git a/src/Gang.jsx b/src/Gang.jsx index 0d47dc13d..5f6bf9965 100644 --- a/src/Gang.jsx +++ b/src/Gang.jsx @@ -49,11 +49,10 @@ import { GangConstants } from "./Gang/data/Constants"; import { GangMemberTasks } from "./Gang/GangMemberTasks"; import { GangMemberTask } from "./Gang/GangMemberTask"; -import { Panel1 } from "./Gang/ui/Panel1"; -import { Panel2 } from "./Gang/ui/Panel2"; -import { Panel3 } from "./Gang/ui/Panel3"; -import { GangMemberAccordionContent } from "./Gang/ui/GangMemberAccordionContent"; -import { GangMemberAccordion } from "./Gang/ui/GangMemberAccordion"; +import { ManagementSubpage } from "./Gang/ui/ManagementSubpage"; +import { TerritorySubpage } from "./Gang/ui/TerritorySubpage"; +import { GangStats } from "./Gang/ui/GangStats"; +import { AllGangs } from "./Gang/AllGangs"; import React from "react"; import ReactDOM from "react-dom"; @@ -75,20 +74,6 @@ $(document).keydown(function(event) { } }); -// Delete upgrade box when clicking outside -$(document).mousedown(function(event) { - var contentId = "gang-member-upgrade-popup-box-content"; - if (UIElems.gangMemberUpgradeBoxOpened) { - if ( $(event.target).closest("#" + contentId).get(0) == null ) { - //Delete the box - removeElement(UIElems.gangMemberUpgradeBox); - UIElems.gangMemberUpgradeBox = null; - UIElems.gangMemberUpgradeBoxContent = null; - UIElems.gangMemberUpgradeBoxOpened = false; - UIElems.gangMemberUpgradeBoxElements = null; - } - } -}); const GangNames = [ "Slum Snakes", @@ -100,74 +85,6 @@ const GangNames = [ "The Black Hand", ]; -export let 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 resetGangs() { - 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) { - AllGangs = JSON.parse(saveString, Reviver); -} - /** * @param facName {string} Name of corresponding faction * @param hacking {bollean} Whether or not its a hacking gang @@ -412,10 +329,6 @@ Gang.prototype.recruitMember = function(name) { let member = new GangMember(name); this.members.push(member); - if (routing.isOn(Page.Gang)) { - this.createGangMemberDisplayElement(member); - this.updateGangContent(); - } return true; } @@ -488,10 +401,6 @@ Gang.prototype.killMember = function(memberObj) { dialogBoxCreate(`${memberObj.name} was killed in a gang clash! You lost ${lostRespect} respect`); } - // Update UI - if (routing.isOn(Page.Gang)) { - this.displayGangMemberList(); - } } Gang.prototype.ascendMember = function(memberObj, workerScript) { @@ -520,9 +429,6 @@ Gang.prototype.ascendMember = function(memberObj, workerScript) { } else { workerScript.log(`Ascended Gang member ${memberObj.name}`); } - if (routing.isOn(Page.Gang)) { - this.displayGangMemberList(); - } return res; } catch(e) { if (workerScript == null) { @@ -884,10 +790,6 @@ GangMember.prototype.buyUpgrade = function(upg, player, gang) { this.upgrades.push(upg.name); } upg.apply(this); - if (routing.isOn(Page.Gang) && UIElems.gangMemberUpgradeBoxOpened) { - var initFilterValue = UIElems.gangMemberUpgradeBoxFilter.value.toString(); - gang.createGangMemberUpgradeBox(player, initFilterValue); - } return true; } @@ -901,197 +803,6 @@ GangMember.fromJSON = function(value) { Reviver.constructors.GangMember = GangMember; -// Create a pop-up box that lets player purchase upgrades -Gang.prototype.createGangMemberUpgradeBox = function(player, initialFilter="") { - const boxId = "gang-member-upgrade-popup-box"; - if (UIElems.gangMemberUpgradeBoxOpened) { - // Already opened, refreshing - if (UIElems.gangMemberUpgradeBoxElements == null || UIElems.gangMemberUpgradeBox == null || UIElems.gangMemberUpgradeBoxContent == null) { - console.error("Refreshing Gang member upgrade box throws error because required elements are null"); - return; - } - - for (var i = 2; i < UIElems.gangMemberUpgradeBoxElements.length; ++i) { - removeElement(UIElems.gangMemberUpgradeBoxElements[i]); - } - UIElems.gangMemberUpgradeBoxElements = [UIElems.gangMemberUpgradeBoxFilter, UIElems.gangMemberUpgradeBoxDiscount]; - - var filter = UIElems.gangMemberUpgradeBoxFilter.value.toString(); - for (var i = 0; i < this.members.length; ++i) { - if (this.members[i].name.indexOf(filter) > -1 || this.members[i].task.indexOf(filter) > -1) { - var newPanel = this.members[i].createGangMemberUpgradePanel(this, player); - UIElems.gangMemberUpgradeBoxContent.appendChild(newPanel); - UIElems.gangMemberUpgradeBoxElements.push(newPanel); - } - } - } else { - // New popup - UIElems.gangMemberUpgradeBoxFilter = createElement("input", { - type:"text", placeholder:"Filter gang members", - class: "text-input", - value:initialFilter, - onkeyup:() => { - var filterValue = UIElems.gangMemberUpgradeBoxFilter.value.toString(); - this.createGangMemberUpgradeBox(player, filterValue); - }, - }); - - UIElems.gangMemberUpgradeBoxDiscount = createElement("p", { - innerText: "Discount: -" + numeralWrapper.formatPercentage(1 - 1 / this.getDiscount()), - marginLeft: "6px", - tooltip: "You get a discount on equipment and upgrades based on your gang's " + - "respect and power. More respect and power leads to more discounts.", - }); - - UIElems.gangMemberUpgradeBoxElements = [UIElems.gangMemberUpgradeBoxFilter, UIElems.gangMemberUpgradeBoxDiscount]; - - var filter = UIElems.gangMemberUpgradeBoxFilter.value.toString(); - for (var i = 0; i < this.members.length; ++i) { - if (this.members[i].name.indexOf(filter) > -1 || this.members[i].task.indexOf(filter) > -1) { - UIElems.gangMemberUpgradeBoxElements.push(this.members[i].createGangMemberUpgradePanel(this, player)); - } - } - - UIElems.gangMemberUpgradeBox = createPopup(boxId, UIElems.gangMemberUpgradeBoxElements); - UIElems.gangMemberUpgradeBoxContent = document.getElementById(boxId + "-content"); - UIElems.gangMemberUpgradeBoxOpened = true; - } -} - -// Create upgrade panels for each individual Gang Member -GangMember.prototype.createGangMemberUpgradePanel = function(gangObj, player) { - var container = createElement("div", { - border:"1px solid white", - }); - - var header = createElement("h1", { - innerText: this.name + " (" + this.task + ")", - }); - container.appendChild(header); - - var text = createElement("pre", { - fontSize:"14px", display: "inline-block", width:"20%", - innerText: - "Hack: " + this.hack + " (x" + formatNumber(this.hack_mult * this.hack_asc_mult, 2) + ")\n" + - "Str: " + this.str + " (x" + formatNumber(this.str_mult * this.str_asc_mult, 2) + ")\n" + - "Def: " + this.def + " (x" + formatNumber(this.def_mult * this.def_asc_mult, 2) + ")\n" + - "Dex: " + this.dex + " (x" + formatNumber(this.dex_mult * this.dex_asc_mult, 2) + ")\n" + - "Agi: " + this.agi + " (x" + formatNumber(this.agi_mult * this.agi_asc_mult, 2) + ")\n" + - "Cha: " + this.cha + " (x" + formatNumber(this.cha_mult * this.cha_asc_mult, 2) + ")\n", - }); - - // Already purchased upgrades - const ownedUpgradesElements = []; - function pushOwnedUpgrade(upgName) { - const upg = GangMemberUpgrades[upgName]; - if (upg == null) { - console.error(`Could not find GangMemberUpgrade object for name ${upgName}`); - return; - } - ownedUpgradesElements.push(createElement("div", { - class: "gang-owned-upgrade", - innerText: upgName, - tooltip: upg.desc, - })); - } - for (const upgName of this.upgrades) { pushOwnedUpgrade(upgName); } - for (const upgName of this.augmentations) { pushOwnedUpgrade(upgName); } - - var ownedUpgrades = createElement("div", { - class: "gang-owned-upgrades-div", - innerText: "Purchased Upgrades:", - }); - for (const elem of ownedUpgradesElements) { ownedUpgrades.appendChild(elem); } - container.appendChild(text); - container.appendChild(ownedUpgrades); - container.appendChild(createElement("br", {})); - - // Upgrade buttons. Only show upgrades that can be afforded - const weaponUpgrades = []; - const armorUpgrades = []; - const vehicleUpgrades = []; - const rootkitUpgrades = []; - const augUpgrades = []; - - for (let upgName in GangMemberUpgrades) { - if (GangMemberUpgrades.hasOwnProperty(upgName)) { - let upg = GangMemberUpgrades[upgName]; - if (player.money.lt(upg.getCost(gangObj))) { continue; } - if (this.upgrades.includes(upgName) || this.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}`); - } - } - } - - // Create separate columns for each upgrade type - const weaponDiv = createElement("div", {width: "20%", display: "inline-block"}); - const armorDiv = createElement("div", {width: "20%", display: "inline-block"}); - const vehicleDiv = createElement("div", {width: "20%", display: "inline-block"}); - const rootkitDiv = createElement("div", {width: "20%", display: "inline-block"}); - const augDiv = createElement("div", {width: "20%", display: "inline-block"}); - - // Add a title/labe for each column - weaponDiv.appendChild(createElement("h2", {innerText: "Weapons"})); - armorDiv.appendChild(createElement("h2", {innerText: "Armor"})); - vehicleDiv.appendChild(createElement("h2", {innerText: "Vehicles"})); - rootkitDiv.appendChild(createElement("h2", {innerText: "Rootkits"})); - augDiv.appendChild(createElement("h2", {innerText: "Augmentations"})); - - // Add buttons to purchase each upgrade - const upgrades = [weaponUpgrades, armorUpgrades, vehicleUpgrades, rootkitUpgrades, augUpgrades]; - const divs = [weaponDiv, armorDiv, vehicleDiv, rootkitDiv, augDiv]; - for (let i = 0; i < upgrades.length; ++i) { - let upgradeArray = upgrades[i]; - let div = divs[i]; - for (let j = 0; j < upgradeArray.length; ++j) { - let upg = upgradeArray[j]; - (function (upg, div, memberObj, i, gang) { - let createElementParams = { - innerHTML: `${upg.name} - ${renderToStaticMarkup(Money(upg.getCost(gang)))}`, - class: "a-link-button", margin:"2px", padding:"2px", display:"block", - fontSize:"11px", - clickListener:() => { - memberObj.buyUpgrade(upg, player, gangObj); - return false; - }, - } - - // For the last two divs, tooltip should be on the left - if (i >= 3) { - createElementParams.tooltipleft = upg.desc; - } else { - createElementParams.tooltip = upg.desc; - } - div.appendChild(createElement("a", createElementParams)); - })(upg, div, this, i, gangObj); - } - } - - container.appendChild(weaponDiv); - container.appendChild(armorDiv); - container.appendChild(vehicleDiv); - container.appendChild(rootkitDiv); - container.appendChild(augDiv); - return container; -} - // Gang UI Dom Elements const UIElems = { // Main elems @@ -1105,33 +816,8 @@ const UIElems = { gangTerritorySubpage: null, // Gang Management Subpage Elements - gangDesc: null, - gangInfo: null, - gangRecruitMemberButton: null, - gangRecruitRequirementText: null, - gangExpandAllButton: null, - gangCollapseAllButton: null, gangMemberFilter: null, - gangManageEquipmentButton: null, - gangMemberList: null, gangMemberPanels: {}, - - // Gang Equipment Upgrade Elements - gangMemberUpgradeBoxOpened: false, - gangMemberUpgradeBox: null, - gangMemberUpgradeBoxContent: null, - gangMemberUpgradeBoxFilter: null, - gangMemberUpgradeBoxDiscount: null, - gangMemberUpgradeBoxElements: null, - - // Gang Territory Elements - gangTerritoryDescText: null, - gangTerritoryWarfareCheckbox: null, - gangTerritoryWarfareCheckboxLabel: null, - gangTerritoryWarfareClashChance: null, - gangTerritoryDeathNotifyCheckbox: null, - gangTerritoryDeathNotifyCheckboxLabel: null, - gangTerritoryInfoText: null, } export function unmount() { @@ -1174,7 +860,6 @@ Gang.prototype.displayGangContent = function(player) { UIElems.managementButton.classList.toggle("a-link-button"); UIElems.territoryButton.classList.toggle("a-link-button-inactive"); UIElems.territoryButton.classList.toggle("a-link-button"); - this.updateGangContent(); return false; }, }) @@ -1188,7 +873,6 @@ Gang.prototype.displayGangContent = function(player) { UIElems.managementButton.classList.toggle("a-link-button"); UIElems.territoryButton.classList.toggle("a-link-button-inactive"); UIElems.territoryButton.classList.toggle("a-link-button"); - this.updateGangContent(); return false; }, }); @@ -1196,459 +880,31 @@ Gang.prototype.displayGangContent = function(player) { UIElems.gangContainer.appendChild(UIElems.territoryButton); // Subpage for managing gang members - UIElems.gangManagementSubpage = createElement("div", { - display:"block", id:"gang-management-subpage", - }); - - var lowerWantedTask = ""; - if (this.isHackingGang) { - lowerWantedTask = "Ethical Hacking"; - } else { - lowerWantedTask = "Vigilante Justice"; - } - UIElems.gangDesc = createElement("p", {width:"70%", - innerHTML: - "This page is used to manage your gang members and get an overview of your " + - "gang's stats.

" + - "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 '" + lowerWantedTask + "' " + - "task to lower your wanted level.

" + - "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.

" + - "You can also manage your gang programmatically through Netscript using the Gang API", - }); - UIElems.gangManagementSubpage.appendChild(UIElems.gangDesc); - - UIElems.gangInfo = createElement("p", {id:"gang-info", width:"70%"}); - UIElems.gangManagementSubpage.appendChild(UIElems.gangInfo); - - UIElems.gangRecruitMemberButton = createElement("a", { - id: "gang-management-recruit-member-btn", class:"a-link-button-inactive", - innerHTML:"Recruit Gang Member", display:"inline-block", margin:"10px", - clickListener:() => { - const popupId = "recruit-gang-member-popup"; - - let yesBtn; - const txt = createElement("p", { - innerText:"Please enter a name for your new Gang member:", - }); - const br = createElement("br"); - const nameInput = createElement("input", { - onkeyup: (e) => { - if (e.keyCode === KEY.ENTER) { yesBtn.click(); } - }, - placeholder: "Name must be unique", - type: "text", - class:"text-input", - }); - yesBtn = createElement("a", { - class: "std-button", - clickListener: () => { - let name = nameInput.value; - if (name === "") { - dialogBoxCreate("You must enter a name for your Gang member!"); - return false; - } - if (!this.canRecruitMember()) { - dialogBoxCreate("You cannot recruit another Gang member!"); - return false; - } - - // At this point, the only way this can fail is if you already - // have a gang member with the same name - if (!this.recruitMember(name)) { - dialogBoxCreate("You already have a gang member with this name!"); - return false; - } - - removeElementById(popupId); - return false; - }, - innerText: "Recruit Gang Member", - }); - const noBtn = createElement("a", { - class: "std-button", - clickListener: () => { - removeElementById(popupId); - return false; - }, - innerText: "Cancel", - }); - createPopup(popupId, [txt, br, nameInput, yesBtn, noBtn]); - nameInput.focus(); - }, - }); - UIElems.gangManagementSubpage.appendChild(UIElems.gangRecruitMemberButton); - - // Text for how much reputation is required for recruiting next memberList - UIElems.gangRecruitRequirementText = createElement("p", { - color:"red", - id: "gang-recruit-requirement-text", - margin: "10px", - }); - UIElems.gangManagementSubpage.appendChild(UIElems.gangRecruitRequirementText); - - // Gang Member List management buttons (Expand/Collapse All, select a single member) - UIElems.gangManagementSubpage.appendChild(createElement("br", {})); - UIElems.gangExpandAllButton = createElement("a", { - class:"a-link-button", display:"inline-block", - innerHTML:"Expand All", - clickListener:() => { - var allHeaders = UIElems.gangManagementSubpage.getElementsByClassName("accordion-header"); - for (var i = 0; i < allHeaders.length; ++i) { - var hdr = allHeaders[i]; - if (!hdr.classList.contains("active")) { - hdr.click(); - } - } - return false; - }, - }); - UIElems.gangCollapseAllButton = createElement("a", { - class:"a-link-button", display:"inline-block", - innerHTML:"Collapse All", - clickListener:() => { - var allHeaders = UIElems.gangManagementSubpage.getElementsByClassName("accordion-header"); - for (var i = 0; i < allHeaders.length; ++i) { - var hdr = allHeaders[i]; - if (hdr.classList.contains("active")) { - hdr.click(); - } - } - return false; - }, - }); - UIElems.gangMemberFilter = createElement("input", { - type:"text", placeholder:"Filter gang members", margin:"5px", padding:"5px", - class:"text-input", - onkeyup:() => { - this.displayGangMemberList(); - }, - }); - UIElems.gangManageEquipmentButton = createElement("a", { - class:"a-link-button", display:"inline-block", - innerHTML:"Manage Equipment", - clickListener: () => { - this.createGangMemberUpgradeBox(player); - }, - }); - UIElems.gangManagementSubpage.appendChild(UIElems.gangExpandAllButton); - UIElems.gangManagementSubpage.appendChild(UIElems.gangCollapseAllButton); - UIElems.gangManagementSubpage.appendChild(UIElems.gangMemberFilter); - UIElems.gangManagementSubpage.appendChild(UIElems.gangManageEquipmentButton); - - // Gang Member list - UIElems.gangMemberList = createElement("ul", {id:"gang-member-list"}); - this.displayGangMemberList(); - UIElems.gangManagementSubpage.appendChild(UIElems.gangMemberList); + UIElems.gangManagementSubpage = createElement("div"); + UIElems.gangContainer.appendChild(UIElems.gangManagementSubpage); + ReactDOM.render(, UIElems.gangManagementSubpage); // Subpage for seeing gang territory information UIElems.gangTerritorySubpage = createElement("div", { id:"gang-territory-subpage", display:"none", }); - // Info text for territory page - UIElems.gangTerritoryDescText = createElement("p", { - width:"70%", - innerHTML: - "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.

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

" + - "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.

" + - "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.

", - }); - UIElems.gangTerritorySubpage.appendChild(UIElems.gangTerritoryDescText); - - // Checkbox for Engaging in Territory Warfare - UIElems.gangTerritoryWarfareCheckbox = createElement("input", { - display: "inline-block", - id: "gang-management-territory-warfare-checkbox", - changeListener: () => { - this.territoryWarfareEngaged = UIElems.gangTerritoryWarfareCheckbox.checked; - }, - margin: "2px", - type: "checkbox", - }); - UIElems.gangTerritoryWarfareCheckbox.checked = this.territoryWarfareEngaged; - - UIElems.gangTerritoryWarfareCheckboxLabel = createElement("label", { - color: "white", - display: "inline-block", - for: "gang-management-territory-warfare-checkbox", - innerText: "Engage in Territory Warfare", - tooltip: "Engaging in Territory Warfare sets your clash chance to 100%. " + - "Disengaging will cause your clash chance to gradually decrease until " + - "it reaches 0%", - }); - UIElems.gangTerritorySubpage.appendChild(UIElems.gangTerritoryWarfareCheckbox); - UIElems.gangTerritorySubpage.appendChild(UIElems.gangTerritoryWarfareCheckboxLabel); - - // Territory Clash chance - UIElems.gangTerritorySubpage.appendChild(createElement("br")); - UIElems.gangTerritoryWarfareClashChance = createElement("p", {display: "inline-block"}); - UIElems.gangTerritorySubpage.appendChild(UIElems.gangTerritoryWarfareClashChance); - - UIElems.gangTerritorySubpage.appendChild(createElement("div", { - class: "help-tip", - display: "inline-block", - innerText: "?", - clickListener: () => { - 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.") - }, - })); - - // Checkbox for whether player wants to be notified of gang member death - UIElems.gangTerritoryDeathNotifyCheckbox = createElement("input", { - display: "inline-block", - id: "gang-management-notify-member-death-checkbox", - changeListener: () => { - this.notifyMemberDeath = UIElems.gangTerritoryDeathNotifyCheckbox.checked; - }, - margin: "2px", - type: "checkbox", - }); - UIElems.gangTerritoryDeathNotifyCheckbox.checked = this.notifyMemberDeath; - - UIElems.gangTerritoryDeathNotifyCheckboxLabel = createElement("label", { - color: "white", - display: "inline-block", - for: "gang-management-notify-member-death-checkbox", - innerText: "Notify about Gang Member Deaths", - tooltip: "If this is enabled, then you will receive a pop-up notifying you " + - "whenever one of your Gang Members dies in a territory clash.", - }); - UIElems.gangTerritorySubpage.appendChild(createElement("br")); - UIElems.gangTerritorySubpage.appendChild(UIElems.gangTerritoryDeathNotifyCheckbox); - UIElems.gangTerritorySubpage.appendChild(UIElems.gangTerritoryDeathNotifyCheckboxLabel); - - // Territory info (percentages of territory owned for each gang) - UIElems.gangTerritorySubpage.appendChild(createElement("br")); - var territoryBorder = createElement("fieldset", { - display:"block", - margin: "6px", - width:"50%", - }); - - UIElems.gangTerritoryInfoText = createElement("p"); - - territoryBorder.appendChild(UIElems.gangTerritoryInfoText); - UIElems.gangTerritorySubpage.appendChild(territoryBorder); + ReactDOM.render(, UIElems.gangTerritorySubpage); UIElems.gangContainer.appendChild(UIElems.gangTerritorySubpage); - UIElems.gangContainer.appendChild(UIElems.gangManagementSubpage); + document.getElementById("entire-game-container").appendChild(UIElems.gangContainer); } UIElems.gangContainer.style.display = "block"; - this.updateGangContent(); -} - -Gang.prototype.displayGangMemberList = function() { - removeChildrenFromElement(UIElems.gangMemberList); - UIElems.gangMemberPanels = {}; - const members = this.members; - const filter = UIElems.gangMemberFilter.value.toString(); - for (var i = 0; i < members.length; ++i) { - if (members[i].name.indexOf(filter) > -1 || members[i].task.indexOf(filter) > -1) { - this.createGangMemberDisplayElement(members[i]); - } - } -} - -Gang.prototype.updateGangContent = function() { - if (!UIElems.gangContentCreated) { return; } - - if (UIElems.gangMemberUpgradeBoxOpened) { - UIElems.gangMemberUpgradeBoxDiscount.childNodes[0].nodeValue = - "Discount: -" + numeralWrapper.formatPercentage(1 - 1 / this.getDiscount()); - } - - if (UIElems.gangTerritorySubpage.style.display === "block") { - // Territory Warfare Clash Chance - UIElems.gangTerritoryWarfareClashChance.innerText = - `Territory Clash Chance: ${numeralWrapper.formatPercentage(this.territoryClashChance, 3)}`; - - // Engaged in Territory Warfare checkbox - UIElems.gangTerritoryWarfareCheckbox.checked = this.territoryWarfareEngaged; - - // Update territory information - UIElems.gangTerritoryInfoText.innerHTML = ""; - const playerPower = AllGangs[this.facName].power; - let gangNames = Object.keys(AllGangs).filter(g => g != this.facName); - gangNames.unshift(this.facName); - for (const gangname of gangNames) { - if (AllGangs.hasOwnProperty(gangname)) { - const gangTerritoryInfo = AllGangs[gangname]; - let territory = gangTerritoryInfo.territory * 100; - - //Fix some rounding issues graphically - let displayNumber; - if (territory <= 0) { - displayNumber = formatNumber(0, 2); - } else if (territory >= 100) { - displayNumber = formatNumber(100, 2); - } else { - displayNumber = formatNumber(territory, 2); - } - - if (gangname === this.facName) { - let newHTML = `${gangname}
Power: ${formatNumber(gangTerritoryInfo.power, 6)}
`; - newHTML += `Territory: ${displayNumber}%

`; - UIElems.gangTerritoryInfoText.innerHTML += newHTML; - } else { - const clashVictoryChance = playerPower / (gangTerritoryInfo.power + playerPower); - let newHTML = `${gangname}
Power: ${formatNumber(gangTerritoryInfo.power, 6)}
`; - newHTML += `Territory: ${displayNumber}%
`; - newHTML += `Chance to win clash with this gang: ${numeralWrapper.formatPercentage(clashVictoryChance, 3)}

`; - UIElems.gangTerritoryInfoText.innerHTML += newHTML; - } - } - } - } else { - // TODO(hydroflame): you're working here - - // Update information for overall gang - if (UIElems.gangInfo instanceof Element) { - var faction = Factions[this.facName]; - var rep; - if (!(faction instanceof Faction)) { - rep = "ERROR"; - } else { - rep = faction.playerReputation; - } - removeChildrenFromElement(UIElems.gangInfo); - UIElems.gangInfo.appendChild(createElement("p", { // Respect - display: "inline-block", - innerText: "Respect: " + numeralWrapper.formatRespect(this.respect) + - " (" + numeralWrapper.formatRespect(5*this.respectGainRate) + " / sec)", - tooltip: "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.", - })); - UIElems.gangInfo.appendChild(createElement("br")); - - UIElems.gangInfo.appendChild(createElement("p", { // Wanted level - display: "inline-block", - innerText: "Wanted Level: " + numeralWrapper.formatWanted(this.wanted) + - " (" + numeralWrapper.formatWanted(5*this.wantedGainRate) + " / sec)", - tooltip: "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.", - })); - UIElems.gangInfo.appendChild(createElement("br")); - - var wantedPenalty = this.getWantedPenalty(); - wantedPenalty = (1 - wantedPenalty) * 100; - UIElems.gangInfo.appendChild(createElement("p", { // Wanted Level multiplier - display: "inline-block", - innerText: `Wanted Level Penalty: -${formatNumber(wantedPenalty, 2)}%`, - tooltip: "Penalty for respect and money gain rates due to Wanted Level", - })); - UIElems.gangInfo.appendChild(createElement("br")); - - const d0 = createElement("div"); - ReactDOM.render(

Money gain rate: {MoneyRate(5 * this.moneyGainRate)}

, d0); - UIElems.gangInfo.appendChild(d0); - UIElems.gangInfo.appendChild(createElement("br")); - - // Fix some rounding issues graphically - var territoryMult = AllGangs[this.facName].territory * 100; - let displayNumber; - if (territoryMult <= 0) { - displayNumber = formatNumber(0, 2); - } else if (territoryMult >= 100) { - displayNumber = formatNumber(100, 2); - } else { - displayNumber = formatNumber(territoryMult, 2); - } - UIElems.gangInfo.appendChild(createElement("p", { // Territory multiplier - display: "inline-block", - innerText: `Territory: ${formatNumber(displayNumber, 3)}%`, - tooltip: "The percentage of total territory your Gang controls", - })); - UIElems.gangInfo.appendChild(createElement("br")); - - const d1 = createElement("div"); - ReactDOM.render(

Faction reputation: {Reputation(rep)}

, d1); - UIElems.gangInfo.appendChild(d1); - UIElems.gangInfo.appendChild(createElement("br")); - - const CyclesPerSecond = 1000 / Engine._idleSpeed; - if (this.storedCycles / CyclesPerSecond*1000 > 5000) { - UIElems.gangInfo.appendChild(createElement("p", { // Stored Cycles - innerText: `Bonus time: ${convertTimeMsToTimeElapsedString(this.storedCycles / CyclesPerSecond*1000)}`, - display: "inline-block", - tooltip: "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", - })); - UIElems.gangInfo.appendChild(createElement("br")); - } - } else { - console.error("gang-info DOM element DNE"); - } - - // Toggle the 'Recruit member button' if valid - const numMembers = this.members.length; - const respectCost = this.getRespectNeededToRecruitMember(); - - const btn = UIElems.gangRecruitMemberButton; - if (numMembers >= GangConstants.MaximumGangMembers) { - btn.className = "a-link-button-inactive"; - UIElems.gangRecruitRequirementText.style.display = "inline-block"; - UIElems.gangRecruitRequirementText.innerHTML = "You have reached the maximum amount of gang members"; - } else if (this.canRecruitMember()) { - btn.className = "a-link-button"; - UIElems.gangRecruitRequirementText.style.display = "none"; - } else { - btn.className = "a-link-button-inactive"; - UIElems.gangRecruitRequirementText.style.display = "inline-block"; - UIElems.gangRecruitRequirementText.innerHTML = `${formatNumber(respectCost, 2)} respect needed to recruit next member`; - } - - // TODO(hydroflame): TO HERE - } -} - -// Takes in a GangMember object -Gang.prototype.createGangMemberDisplayElement = function(memberObj) { - if (!UIElems.gangContentCreated) { return; } - const name = memberObj.name; - const id = `${name}-gang-member-accordion`; - if(document.getElementById(id)) return; - - // Clear/Update the UIElems map to keep track of this gang member's panel - UIElems.gangMemberPanels[name] = {}; - const li = createElement("li", {id: id}); - ReactDOM.render(, li); - UIElems.gangMemberPanels[name] = li; - UIElems.gangMemberList.appendChild(li); } Gang.prototype.clearUI = function() { if (UIElems.gangContainer instanceof Element) { removeElement(UIElems.gangContainer); } - if (UIElems.gangMemberUpgradeBox instanceof Element) { removeElement(UIElems.gangMemberUpgradeBox); } - for (const prop in UIElems) { UIElems[prop] = null; } UIElems.gangContentCreated = false; - UIElems.gangMemberUpgradeBoxOpened = false; UIElems.gangMemberPanels = {}; } diff --git a/src/Gang/AllGangs.ts b/src/Gang/AllGangs.ts new file mode 100644 index 000000000..c6b078ad0 --- /dev/null +++ b/src/Gang/AllGangs.ts @@ -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() { + 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) { + AllGangs = JSON.parse(saveString, Reviver); +} \ No newline at end of file diff --git a/src/Gang/ui/GangMemberAccordion.tsx b/src/Gang/ui/GangMemberAccordion.tsx deleted file mode 100644 index af388ecd0..000000000 --- a/src/Gang/ui/GangMemberAccordion.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from "react"; -import { GangMemberAccordionContent } from "./GangMemberAccordionContent" -import { Accordion } from "../../ui/React/Accordion"; - -interface IProps { - gang: any; - member: any; -} - -export function GangMemberAccordion(props: IProps): React.ReactElement { - return ({props.member.name}} - panelContent={} - />); -} diff --git a/src/Gang/ui/GangMemberList.tsx b/src/Gang/ui/GangMemberList.tsx new file mode 100644 index 000000000..40b145037 --- /dev/null +++ b/src/Gang/ui/GangMemberList.tsx @@ -0,0 +1,51 @@ +import React, { useState, useEffect } from "react"; +import { Accordion } from "../../ui/React/Accordion"; +import { GangMemberAccordionContent } from "./GangMemberAccordionContent" +import { GangMemberUpgradePopup } from "./GangMemberUpgradePopup" +import { createPopup } from "../../ui/React/createPopup"; +import { IPlayer } from "../../PersonObjects/IPlayer"; + +interface IProps { + gang: any; + player: IPlayer; +} + +export function GangMemberList(props: IProps): React.ReactElement { + const [rerender, setRerender] = useState(false); + const [filter, setFilter] = useState(""); + + useEffect(() => { + const id = setInterval(() => setRerender(old => !old), 1000); + return () => clearInterval(id); + }, []); + + function openUpgradePopup(): void { + const popupId = `gang-upgrade-popup`; + createPopup(popupId, GangMemberUpgradePopup, { + gang: props.gang, + player: props.player, + popupId: popupId, + }); + } + + function onChange(event: React.ChangeEvent): void { + setFilter(event.target.value); + } + + function members(): any { + return props.gang.members.filter((member: any) => member.name.indexOf(filter) > -1 || member.task.indexOf(filter) > -1) + } + + return (<> + + Manage Equipment +
    + {members().map((member: any, i : number) =>
  • + {member.name}} + panelContent={} /> +
  • )} +
+ ); +} \ No newline at end of file diff --git a/src/Gang/ui/GangMemberUpgradePopup.tsx b/src/Gang/ui/GangMemberUpgradePopup.tsx new file mode 100644 index 000000000..4d96242fc --- /dev/null +++ b/src/Gang/ui/GangMemberUpgradePopup.tsx @@ -0,0 +1,175 @@ +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"; + +interface IPanelProps { + member: any; + gang: any; + player: IPlayer; +} + +function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement { + const [rerender, setRerender] = useState(false); + // 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)) { + const upg = GangMemberUpgrades[upgName]; + if (props.player.money.lt(upg.getCost(props.gang))) 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}`); + } + } + } + + function purchased(name: string): React.ReactElement { + const upg = GangMemberUpgrades[name] + return (
+ {upg.name} + +
); + } + + function upgradeButton(upg: GangMemberUpgrade, left: boolean = false): React.ReactElement { + function onClick(): void { + props.member.buyUpgrade(upg, props.player, props.gang); + setRerender(old => !old); + } + return ( + {upg.name} - {Money(upg.getCost(props.gang))} + + ); + } + + return (
+

{props.member.name}({props.member.task})

+
+Hack: {props.member.hack} (x{formatNumber(props.member.hack_mult * props.member.hack_asc_mult, 2)})
+Str: {props.member.str} (x{formatNumber(props.member.str_mult * props.member.str_asc_mult, 2)})
+Def: {props.member.def} (x{formatNumber(props.member.def_mult * props.member.def_asc_mult, 2)})
+Dex: {props.member.dex} (x{formatNumber(props.member.dex_mult * props.member.dex_asc_mult, 2)})
+Agi: {props.member.agi} (x{formatNumber(props.member.agi_mult * props.member.agi_asc_mult, 2)})
+Cha: {props.member.cha} (x{formatNumber(props.member.cha_mult * props.member.cha_asc_mult, 2)}) +
+
+ Purchased Upgrades: {props.member.upgrades.map((upg: any) => purchased(upg))} + {props.member.augmentations.map((upg: any) => purchased(upg))} +
+
+

Weapons

+ {weaponUpgrades.map(upg => upgradeButton(upg))} +
+
+

Armor

+ {armorUpgrades.map(upg => upgradeButton(upg))} +
+
+

Vehicles

+ {vehicleUpgrades.map(upg => upgradeButton(upg))} +
+
+

Rootkits

+ {rootkitUpgrades.map(upg => upgradeButton(upg, true))} +
+
+

Augmentations

+ {augUpgrades.map(upg => upgradeButton(upg, true))} +
+
); +} + +interface IProps { + gang: any; + player: IPlayer; + popupId: string; +} + +export function GangMemberUpgradePopup(props: IProps): React.ReactElement { + const [rerender, setRerender] = useState(false); + const [filter, setFilter] = useState(""); + + function closePopup(): void { + removePopup(props.popupId); + } + + useEffect(() => { + window.addEventListener('keydown', closePopup); + const id = setInterval(() => setRerender(old => !old), 1000); + return () => { + clearInterval(id); + window.removeEventListener('keydown', closePopup); + } + }, []); + + return (<> + setFilter(event.target.value)} /> +

+ Discount: -{numeralWrapper.formatPercentage(1 - 1 / props.gang.getDiscount())} + You get a discount on equipment and upgrades based on your gang's respect and power. More respect and power leads to more discounts. +

+ {props.gang.members.map((member: any) => )} + ); +} + +/* + +// Add buttons to purchase each upgrade +const upgrades = [weaponUpgrades, armorUpgrades, vehicleUpgrades, rootkitUpgrades, augUpgrades]; +const divs = [weaponDiv, armorDiv, vehicleDiv, rootkitDiv, augDiv]; +for (let i = 0; i < upgrades.length; ++i) { + let upgradeArray = upgrades[i]; + let div = divs[i]; + for (let j = 0; j < upgradeArray.length; ++j) { + let upg = upgradeArray[j]; + (function (upg, div, memberObj, i, gang) { + let createElementParams = { + innerHTML: `${upg.name} - ${renderToStaticMarkup(Money(upg.getCost(gang)))}`, + class: "a-link-button", margin:"2px", padding:"2px", display:"block", + fontSize:"11px", + clickListener:() => { + memberObj.buyUpgrade(upg, player, gangObj); + return false; + }, + } + + // For the last two divs, tooltip should be on the left + if (i >= 3) { + createElementParams.tooltipleft = upg.desc; + } else { + createElementParams.tooltip = upg.desc; + } + div.appendChild(createElement("a", createElementParams)); + })(upg, div, this, i, gangObj); + } +} + +createPopup(boxId, UIElems.gangMemberUpgradeBoxElements) + +*/ \ No newline at end of file diff --git a/src/Gang/ui/GangStats.tsx b/src/Gang/ui/GangStats.tsx index 348685008..0035e57e7 100644 --- a/src/Gang/ui/GangStats.tsx +++ b/src/Gang/ui/GangStats.tsx @@ -1,4 +1,123 @@ import React, { useState, useEffect } from "react"; +import { Factions } from "../../Faction/Factions"; + +import { + formatNumber, + convertTimeMsToTimeElapsedString, +} 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: any; + 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: any): void { + if(event.keyCode === 13) recruit(); + if(event.keyCode === 27) cancel(); + } + + function onChange(event: any): void { + setName(event.target.value); + } + + return (<> +

Enter a name for your new Gang member:


+ + Recruit Gang Member + Cancel + ); +} + +interface IProps { + gang: any; +} + +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() { + const popupId = "recruit-gang-member-popup"; + createPopup(popupId, recruitPopup, { + gang: props.gang, + popupId: popupId, + }); + } + return (<> + + Recruit Gang Member + + ); + } + return (<> + + Recruit Gang Member + +

+ {formatNumber(respectCost, 2)} respect needed to recruit next member +

+ ); +} + +function BonusTime(props: IProps): React.ReactElement { + const CyclesPerSecond = 1000 / 200; + if (props.gang.storedCycles / CyclesPerSecond*1000 <= 5000) return <>; + return (<> +

+ Bonus time: {convertTimeMsToTimeElapsedString(props.gang.storedCycles / CyclesPerSecond*1000)} + + 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 + +

+
+ ); +} export function GangStats(props: IProps): React.ReactElement { const [rerender, setRerender] = useState(false); @@ -8,157 +127,57 @@ export function GangStats(props: IProps): React.ReactElement { return () => clearInterval(id); }, []); + 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 (

-

- Respect: 108.82214 (0.23534 / sec) - + return (<> +

+ Respect: {numeralWrapper.formatRespect(props.gang.respect)} ({numeralWrapper.formatRespect(5*props.gang.respectGainRate)} / sec) + 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.


-

- Wanted Level: 1.37503 (0.00002 / sec) - +

+ Wanted Level: {numeralWrapper.formatWanted(props.gang.wanted)} ({numeralWrapper.formatWanted(5*props.gang.wantedGainRate)} / sec) + 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.


-

- Wanted Level Penalty: -1.25% - +

+ Wanted Level Penalty: -{formatNumber((1 - props.gang.getWantedPenalty()) * 100, 2)}% + Penalty for respect and money gain rates due to Wanted Level


-

- Money gain rate: - - $2.571k / sec - +

+ Money gain rate: {MoneyRate(5 * props.gang.moneyGainRate)}


-

- Territory: 14.29% - +

+ Territory: {territoryStr}% + The percentage of total territory your Gang controls


-
-

- Faction reputation: - - 28.677 - -

-
-
-

- Bonus time: 1 hours 30 minutes 58 seconds - - 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 - +

+ Faction reputation: {Reputation(Factions[props.gang.facName].playerReputation)}


-

); -} - - - -/* - -var faction = Factions[this.facName]; -var rep; -if (!(faction instanceof Faction)) { - rep = "ERROR"; -} else { - rep = faction.playerReputation; -} -UIElems.gangInfo.appendChild(createElement("p", { - display: "inline-block", - innerText: "Respect: " + numeralWrapper.formatRespect(this.respect) + - " (" + numeralWrapper.formatRespect(5*this.respectGainRate) + " / sec)", - tooltip: "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.", -})); -UIElems.gangInfo.appendChild(createElement("br")); - -UIElems.gangInfo.appendChild(createElement("p", { - display: "inline-block", - innerText: "Wanted Level: " + numeralWrapper.formatWanted(this.wanted) + - " (" + numeralWrapper.formatWanted(5*this.wantedGainRate) + " / sec)", - tooltip: "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.", -})); -UIElems.gangInfo.appendChild(createElement("br")); - -var wantedPenalty = this.getWantedPenalty(); -wantedPenalty = (1 - wantedPenalty) * 100; -UIElems.gangInfo.appendChild(createElement("p", { - display: "inline-block", - innerText: `Wanted Level Penalty: -${formatNumber(wantedPenalty, 2)}%`, - tooltip: "Penalty for respect and money gain rates due to Wanted Level", -})); -UIElems.gangInfo.appendChild(createElement("br")); - -const d0 = createElement("div"); -ReactDOM.render(

Money gain rate: {MoneyRate(5 * this.moneyGainRate)}

, d0); -UIElems.gangInfo.appendChild(d0); -UIElems.gangInfo.appendChild(createElement("br")); - -var territoryMult = AllGangs[this.facName].territory * 100; -let displayNumber; -if (territoryMult <= 0) { - displayNumber = formatNumber(0, 2); -} else if (territoryMult >= 100) { - displayNumber = formatNumber(100, 2); -} else { - displayNumber = formatNumber(territoryMult, 2); -} -UIElems.gangInfo.appendChild(createElement("p", { - display: "inline-block", - innerText: `Territory: ${formatNumber(displayNumber, 3)}%`, - tooltip: "The percentage of total territory your Gang controls", -})); -UIElems.gangInfo.appendChild(createElement("br")); - -const d1 = createElement("div"); -ReactDOM.render(

Faction reputation: {Reputation(rep)}

, d1); -UIElems.gangInfo.appendChild(d1); -UIElems.gangInfo.appendChild(createElement("br")); - -const CyclesPerSecond = 1000 / Engine._idleSpeed; -if (this.storedCycles / CyclesPerSecond*1000 > 5000) { - UIElems.gangInfo.appendChild(createElement("p", { - innerText: `Bonus time: ${convertTimeMsToTimeElapsedString(this.storedCycles / CyclesPerSecond*1000)}`, - display: "inline-block", - tooltip: "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", - })); - UIElems.gangInfo.appendChild(createElement("br")); -} - -const numMembers = this.members.length; -const respectCost = this.getRespectNeededToRecruitMember(); - -const btn = UIElems.gangRecruitMemberButton; -if (numMembers >= GangConstants.MaximumGangMembers) { - btn.className = "a-link-button-inactive"; - UIElems.gangRecruitRequirementText.style.display = "inline-block"; - UIElems.gangRecruitRequirementText.innerHTML = "You have reached the maximum amount of gang members"; -} else if (this.canRecruitMember()) { - btn.className = "a-link-button"; - UIElems.gangRecruitRequirementText.style.display = "none"; -} else { - btn.className = "a-link-button-inactive"; - UIElems.gangRecruitRequirementText.style.display = "inline-block"; - UIElems.gangRecruitRequirementText.innerHTML = `${formatNumber(respectCost, 2)} respect needed to recruit next member`; -} - -*/ \ No newline at end of file + +
+ + ); +} \ No newline at end of file diff --git a/src/Gang/ui/ManagementSubpage.tsx b/src/Gang/ui/ManagementSubpage.tsx new file mode 100644 index 000000000..703b6a106 --- /dev/null +++ b/src/Gang/ui/ManagementSubpage.tsx @@ -0,0 +1,38 @@ +import React, { useState, useEffect } from "react"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { GangStats } from "./GangStats"; +import { GangMemberList } from "./GangMemberList"; + +interface IProps { + gang: any; + player: IPlayer; +} + +export function ManagementSubpage(props: IProps): React.ReactElement { + return (
+

+ This page is used to manage your gang members and get an overview of your gang's stats. +
+
+ 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. +
+
+ 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. +
+
+ You can also manage your gang programmatically through Netscript using the Gang API +

+
+ +
+ +
); +} diff --git a/src/Gang/ui/Panel1.tsx b/src/Gang/ui/Panel1.tsx index 872943d2a..d9df20011 100644 --- a/src/Gang/ui/Panel1.tsx +++ b/src/Gang/ui/Panel1.tsx @@ -13,7 +13,6 @@ interface IAscendProps { function ascendPopup(props: IAscendProps): React.ReactElement { function confirm() { props.gang.ascendMember(props.member); - props.gang.updateGangMemberDisplayElement(props.member); removePopup(props.popupId); return false; } diff --git a/src/Gang/ui/Panel2.tsx b/src/Gang/ui/Panel2.tsx index cb3c3c60d..01f48bf9b 100644 --- a/src/Gang/ui/Panel2.tsx +++ b/src/Gang/ui/Panel2.tsx @@ -20,7 +20,6 @@ export function Panel2(props: IProps): React.ReactElement { function onChange(event: React.ChangeEvent): void { const task = event.target.value; props.member.assignToTask(task); - props.gang.updateGangContent(); setCurrentTask(task); } diff --git a/src/Gang/ui/Panel3.tsx b/src/Gang/ui/Panel3.tsx index 8f792811b..9ebab90dc 100644 --- a/src/Gang/ui/Panel3.tsx +++ b/src/Gang/ui/Panel3.tsx @@ -11,7 +11,6 @@ export function Panel3(props: IProps): React.ReactElement { useEffect(() => { const id = setInterval(() => { setRerender(old => !old); - console.log('render'); }, 1000); return () => clearInterval(id); }, []); diff --git a/src/Gang/ui/TerritorySubpage.tsx b/src/Gang/ui/TerritorySubpage.tsx new file mode 100644 index 000000000..0077aace0 --- /dev/null +++ b/src/Gang/ui/TerritorySubpage.tsx @@ -0,0 +1,152 @@ +import React, { useState, useEffect } from "react"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { formatNumber } from "../../../utils/StringHelperFunctions"; +import { AllGangs } from "../AllGangs"; + +interface IProps { + gang: any; +} + +export function TerritorySubpage(props: IProps): React.ReactElement { + const [rerender, setRerender] = useState(false); + + useEffect(() => { + const id = setInterval(() => setRerender(old => !old), 1000); + return () => clearInterval(id); + }, []); + + 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 formatTerritoryP(n: number): string { + const v = n * 100; + let displayNumber; + if (n <= 0) { + return formatNumber(0, 2); + } else if (n >= 100) { + return formatNumber(100, 2); + } else { + return formatNumber(n, 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 ( + {name}
+ Power: {formatNumber(power, 6)}
+ Territory: {formatTerritoryP(AllGangs[name].territory)}%
+ Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}
+
+
); + } + + const gangNames = Object.keys(AllGangs).filter(g => g != props.gang.facName); + + return (
+

+ 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. +
+
+ 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. +
+
+ 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. +
+
+ 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. +
+
+

+ props.gang.territoryWarfareEngaged = event.target.checked}/> + +
+

+ Territory Clash Chance: {numeralWrapper.formatPercentage(props.gang.territoryClashChance, 3)} +

+
?
+
+ + props.gang.notifyMemberDeath = event.target.checked}/> + +
+
+

+ {props.gang.facName}
+ Power: {formatNumber(AllGangs[props.gang.facName].power, 6)}
+ Territory: {formatTerritoryP(AllGangs[props.gang.facName].territory)}%
+
+ {gangNames.map(name => otherGangTerritory(name))} +

+
+
); +} + +/* + +let gangNames = Object.keys(AllGangs).filter(g => g != this.facName); +gangNames.unshift(this.facName); +for (const gangname of gangNames) { + if (AllGangs.hasOwnProperty(gangname)) { + const gangTerritoryInfo = AllGangs[gangname]; + let territory = gangTerritoryInfo.territory * 100; + + //Fix some rounding issues graphically + let displayNumber; + if (territory <= 0) { + displayNumber = formatNumber(0, 2); + } else if (territory >= 100) { + displayNumber = formatNumber(100, 2); + } else { + displayNumber = formatNumber(territory, 2); + } + + if (gangname === this.facName) { + let newHTML = `${gangname}
Power: ${formatNumber(gangTerritoryInfo.power, 6)}
`; + newHTML += `Territory: ${displayNumber}%

`; + UIElems.gangTerritoryInfoText.innerHTML += newHTML; + } else { + const clashVictoryChance = playerPower / (gangTerritoryInfo.power + playerPower); + let newHTML = `${gangname}
Power: ${formatNumber(gangTerritoryInfo.power, 6)}
`; + newHTML += `Territory: ${displayNumber}%
`; + newHTML += `Chance to win clash with this gang: ${numeralWrapper.formatPercentage(clashVictoryChance, 3)}

`; + UIElems.gangTerritoryInfoText.innerHTML += newHTML; + } + } +} + +*/ \ No newline at end of file diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index d05380478..4af133c69 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -29,10 +29,8 @@ import { calculateWeakenTime, } from "./Hacking"; import { calculateServerGrowth } from "./Server/formulas/grow"; -import { - AllGangs, - Gang, -} from "./Gang"; +import { Gang } from "./Gang"; +import { AllGangs } from "./Gang/AllGangs"; import { GangMemberTasks } from "./Gang/GangMemberTasks"; import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades"; import { Factions, factionExists } from "./Faction/Factions"; diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.jsx b/src/PersonObjects/Player/PlayerObjectGeneralMethods.jsx index 3df0f9627..f3b76e9d9 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.jsx +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.jsx @@ -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"; diff --git a/src/SaveObject.jsx b/src/SaveObject.jsx index fc17fa8a4..8d860d4d3 100755 --- a/src/SaveObject.jsx +++ b/src/SaveObject.jsx @@ -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"; diff --git a/src/engine.jsx b/src/engine.jsx index 5ad2bb153..01d0b9d3e 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -868,9 +868,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; diff --git a/src/ui/React/Accordion.tsx b/src/ui/React/Accordion.tsx index d45055cfb..d0e033ad2 100644 --- a/src/ui/React/Accordion.tsx +++ b/src/ui/React/Accordion.tsx @@ -23,27 +23,14 @@ export class Accordion extends React.Component { this.handleHeaderClick = this.handleHeaderClick.bind(this); this.state = { - panelOpened: props.panelInitiallyOpened ? true : false, + panelOpened: props.panelInitiallyOpened ? props.panelInitiallyOpened : false, } } handleHeaderClick(e: React.MouseEvent): 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, - }); - } + this.setState({ + panelOpened: !this.state.panelOpened, + }); } render(): React.ReactNode { @@ -52,6 +39,8 @@ export class Accordion extends React.Component { className = this.props.headerClass; } + if(this.state.panelOpened) className += " active" + return ( <>
@@ -50,14 +48,14 @@ interface IProps { } export function Panel1(props: IProps): React.ReactElement { - const [rerender, setRerender] = useState(false); + const setRerender = useState(false)[1]; useEffect(() => { const id = setInterval(() => setRerender(old => !old), 1000); return () => clearInterval(id); }, []); - function ascend() { + function ascend(): void { const popupId = `gang-management-ascend-member ${props.member.name}`; createPopup(popupId, ascendPopup, { member: props.member, diff --git a/src/Gang/ui/Panel2.tsx b/src/Gang/ui/Panel2.tsx index 01f48bf9b..c08144ea4 100644 --- a/src/Gang/ui/Panel2.tsx +++ b/src/Gang/ui/Panel2.tsx @@ -9,7 +9,7 @@ interface IProps { } export function Panel2(props: IProps): React.ReactElement { - const [rerender, setRerender] = useState(false); + const setRerender = useState(false)[1]; const [currentTask, setCurrentTask] = useState(props.member.task); useEffect(() => { diff --git a/src/Gang/ui/Panel3.tsx b/src/Gang/ui/Panel3.tsx index 9ebab90dc..cd889b100 100644 --- a/src/Gang/ui/Panel3.tsx +++ b/src/Gang/ui/Panel3.tsx @@ -6,7 +6,7 @@ interface IProps { } export function Panel3(props: IProps): React.ReactElement { - const [rerender, setRerender] = useState(false); + const setRerender = useState(false)[1]; useEffect(() => { const id = setInterval(() => { diff --git a/src/Gang/ui/Root.tsx b/src/Gang/ui/Root.tsx index 055dcf615..02296884a 100644 --- a/src/Gang/ui/Root.tsx +++ b/src/Gang/ui/Root.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { ManagementSubpage } from "./ManagementSubpage"; import { TerritorySubpage } from "./TerritorySubpage"; diff --git a/src/Gang/ui/TerritorySubpage.tsx b/src/Gang/ui/TerritorySubpage.tsx index 0077aace0..cecbe0c26 100644 --- a/src/Gang/ui/TerritorySubpage.tsx +++ b/src/Gang/ui/TerritorySubpage.tsx @@ -9,7 +9,7 @@ interface IProps { } export function TerritorySubpage(props: IProps): React.ReactElement { - const [rerender, setRerender] = useState(false); + const setRerender = useState(false)[1]; useEffect(() => { const id = setInterval(() => setRerender(old => !old), 1000); @@ -25,13 +25,12 @@ export function TerritorySubpage(props: IProps): React.ReactElement { function formatTerritoryP(n: number): string { const v = n * 100; - let displayNumber; - if (n <= 0) { + if (v <= 0) { return formatNumber(0, 2); - } else if (n >= 100) { + } else if (v >= 100) { return formatNumber(100, 2); } else { - return formatNumber(n, 2); + return formatNumber(v, 2); } } @@ -116,37 +115,3 @@ export function TerritorySubpage(props: IProps): React.ReactElement {
); } -/* - -let gangNames = Object.keys(AllGangs).filter(g => g != this.facName); -gangNames.unshift(this.facName); -for (const gangname of gangNames) { - if (AllGangs.hasOwnProperty(gangname)) { - const gangTerritoryInfo = AllGangs[gangname]; - let territory = gangTerritoryInfo.territory * 100; - - //Fix some rounding issues graphically - let displayNumber; - if (territory <= 0) { - displayNumber = formatNumber(0, 2); - } else if (territory >= 100) { - displayNumber = formatNumber(100, 2); - } else { - displayNumber = formatNumber(territory, 2); - } - - if (gangname === this.facName) { - let newHTML = `${gangname}
Power: ${formatNumber(gangTerritoryInfo.power, 6)}
`; - newHTML += `Territory: ${displayNumber}%

`; - UIElems.gangTerritoryInfoText.innerHTML += newHTML; - } else { - const clashVictoryChance = playerPower / (gangTerritoryInfo.power + playerPower); - let newHTML = `${gangname}
Power: ${formatNumber(gangTerritoryInfo.power, 6)}
`; - newHTML += `Territory: ${displayNumber}%
`; - newHTML += `Chance to win clash with this gang: ${numeralWrapper.formatPercentage(clashVictoryChance, 3)}

`; - UIElems.gangTerritoryInfoText.innerHTML += newHTML; - } - } -} - -*/ \ No newline at end of file diff --git a/src/ui/React/Accordion.tsx b/src/ui/React/Accordion.tsx index d0e033ad2..5a532ccf9 100644 --- a/src/ui/React/Accordion.tsx +++ b/src/ui/React/Accordion.tsx @@ -27,7 +27,7 @@ export class Accordion extends React.Component { } } - handleHeaderClick(e: React.MouseEvent): void { + handleHeaderClick(): void { this.setState({ panelOpened: !this.state.panelOpened, }); diff --git a/src/ui/React/CodingContractPopup.tsx b/src/ui/React/CodingContractPopup.tsx index d7c2dd58e..8ae991835 100644 --- a/src/ui/React/CodingContractPopup.tsx +++ b/src/ui/React/CodingContractPopup.tsx @@ -12,10 +12,6 @@ type IProps = { onAttempt: (answer: string) => void; } -type IState = { - answer: string; -} - export function CodingContractPopup(props: IProps): React.ReactElement { const [answer, setAnswer] = useState(""); diff --git a/src/ui/React/createPopup.tsx b/src/ui/React/createPopup.tsx index 0060b9df0..3cc330257 100644 --- a/src/ui/React/createPopup.tsx +++ b/src/ui/React/createPopup.tsx @@ -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; - let gameContainer: HTMLElement; function getGameContainer(): void { From 43d0fcb9f9d9ebfc0353cdcb90247b36ae5dc4cd Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Wed, 16 Jun 2021 02:26:10 -0400 Subject: [PATCH 10/24] little cleanup --- src/Gang.jsx | 27 ---- src/Gang/GangMember.ts | 12 +- src/Gang/GangMemberUpgrade.ts | 18 +++ src/NetscriptFunctions.js | 6 +- src/data/gangmembertasks.ts | 284 --------------------------------- src/data/gangmemberupgrades.ts | 209 ------------------------ 6 files changed, 28 insertions(+), 528 deletions(-) delete mode 100644 src/data/gangmembertasks.ts delete mode 100644 src/data/gangmemberupgrades.ts diff --git a/src/Gang.jsx b/src/Gang.jsx index 22daf0122..a4da530ed 100644 --- a/src/Gang.jsx +++ b/src/Gang.jsx @@ -23,11 +23,9 @@ import { getRandomInt } from "../utils/helpers/getRandomInt"; import { createElement } from "../utils/uiHelpers/createElement"; import { removeElement } from "../utils/uiHelpers/removeElement"; -import { GangMemberUpgrade } from "./Gang/GangMemberUpgrade"; import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades"; import { GangConstants } from "./Gang/data/Constants"; import { GangMemberTasks } from "./Gang/GangMemberTasks"; -import { GangMemberTask } from "./Gang/GangMemberTask"; import { AllGangs } from "./Gang/AllGangs"; import { Root } from "./Gang/ui/Root"; @@ -417,36 +415,11 @@ Gang.prototype.getAllTaskNames = function() { return tasks; } -Gang.prototype.getAllUpgradeNames = function() { - return Object.keys(GangMemberUpgrades); -} - Gang.prototype.getUpgradeCost = function(upgName) { if (GangMemberUpgrades[upgName] == null) { return Infinity; } return GangMemberUpgrades[upgName].getCost(this); } -// Returns a player-friendly string stating the type of the specified upgrade -Gang.prototype.getUpgradeType = function(upgName) { - const upg = GangMemberUpgrades[upgName]; - if (upg == null) { return ""; } - - switch (upg.type) { - case "w": - return "Weapon"; - case "a": - return "Armor"; - case "v": - return "Vehicle"; - case "r": - return "Rootkit"; - case "g": - return "Augmentation"; - default: - return ""; - } -} - Gang.prototype.toJSON = function() { return Generic_toJSON("Gang", this); } diff --git a/src/Gang/GangMember.ts b/src/Gang/GangMember.ts index f330213b9..84a06914c 100644 --- a/src/Gang/GangMember.ts +++ b/src/Gang/GangMember.ts @@ -48,7 +48,7 @@ export class GangMember { this.name = name; } - calculateSkill(exp: number, mult: number = 1): number { + calculateSkill(exp: number, mult = 1): number { return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1); } @@ -95,7 +95,7 @@ export class GangMember { calculateRespectGain(gang: any): number { const task = this.getTask(); if (task.baseRespect === 0) return 0; - var statWeight = (task.hackWeight/100) * this.hack + + let statWeight = (task.hackWeight/100) * this.hack + (task.strWeight/100) * this.str + (task.defWeight/100) * this.def + (task.dexWeight/100) * this.dex + @@ -150,7 +150,7 @@ export class GangMember { return 5 * task.baseMoney * statWeight * territoryMult * respectMult; } - gainExperience(numCycles: number = 1): void { + gainExperience(numCycles = 1): void { const task = this.getTask(); if (task === GangMemberTasks["Unassigned"]) return; const difficultyMult = Math.pow(task.difficulty, 0.9); @@ -164,7 +164,7 @@ export class GangMember { this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles; } - recordEarnedRespect(numCycles: number = 1, gang: any): void { + recordEarnedRespect(numCycles = 1, gang: any): void { this.earnedRespect += (this.calculateRespectGain(gang) * numCycles); } @@ -179,7 +179,7 @@ export class GangMember { let agi = 1; let cha = 1; for (let i = 0; i < this.upgrades.length; ++i) { - let upg = GangMemberUpgrades[this.upgrades[i]]; + const upg = GangMemberUpgrades[this.upgrades[i]]; if (upg.mults.hack != null) { hack *= upg.mults.hack; } if (upg.mults.str != null) { str *= upg.mults.str; } if (upg.mults.def != null) { def *= upg.mults.def; } @@ -238,7 +238,7 @@ export class GangMember { this.agi_mult = 1; this.cha_mult = 1; for (let i = 0; i < this.augmentations.length; ++i) { - let aug = GangMemberUpgrades[this.augmentations[i]]; + const aug = GangMemberUpgrades[this.augmentations[i]]; aug.apply(this); } diff --git a/src/Gang/GangMemberUpgrade.ts b/src/Gang/GangMemberUpgrade.ts index fa20c7f6e..29fc90af6 100644 --- a/src/Gang/GangMemberUpgrade.ts +++ b/src/Gang/GangMemberUpgrade.ts @@ -53,4 +53,22 @@ export class GangMemberUpgrade { if (this.mults.cha != null) { member.cha_mult *= this.mults.cha; } if (this.mults.hack != null) { member.hack_mult *= this.mults.hack; } } + + // User friendly version of type. + getType(): string { + switch (this.type) { + case "w": + return "Weapon"; + case "a": + return "Armor"; + case "v": + return "Vehicle"; + case "r": + return "Rootkit"; + case "g": + return "Augmentation"; + default: + return ""; + } + } } \ No newline at end of file diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 4af133c69..e20b9f191 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -3741,7 +3741,7 @@ 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")); @@ -3751,7 +3751,9 @@ function NetscriptFunctions(workerScript) { 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")); diff --git a/src/data/gangmembertasks.ts b/src/data/gangmembertasks.ts deleted file mode 100644 index 9aefa0059..000000000 --- a/src/data/gangmembertasks.ts +++ /dev/null @@ -1,284 +0,0 @@ -/* tslint:disable:max-line-length */ - -/** - * Defines the parameters that can be used to initialize and describe a GangMemberTask - * (defined in Gang.js) - */ -export interface IGangMemberTaskMetadata { - /** - * Description of the task - */ - desc: string; - - /** - * Whether or not this task is meant for Combat-type gangs - */ - isCombat: boolean; - - /** - * Whether or not this task is for Hacking-type gangs - */ - isHacking: boolean; - - /** - * Name of the task - */ - name: string; - - /** - * An object containing weighting parameters for the task. These parameters are used for - * various calculations (respect gain, wanted gain, etc.) - */ - params?: any; -} - -/** - * Array of metadata for all Gang Member tasks. Used to construct the global GangMemberTask - * objects in Gang.js - */ -export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [ - { - desc: "This gang member is currently idle", - isCombat: true, - isHacking: true, - name: "Unassigned", - params: {hackWeight: 100}, // This is just to get by the weight check in the GangMemberTask constructor - }, - { - desc: "Assign this gang member to create and distribute ransomware

Earns money - Slightly increases respect - Slightly increases wanted level", - isCombat: false, - isHacking: true, - name: "Ransomware", - params: {baseRespect: 0.00005, baseWanted: 0.0001, baseMoney: 1, hackWeight: 100, difficulty: 1}, - }, - { - desc: "Assign this gang member to attempt phishing scams and attacks

Earns money - Slightly increases respect - Slightly increases wanted level", - isCombat: false, - isHacking: true, - name: "Phishing", - params: {baseRespect: 0.00008, baseWanted: 0.003, baseMoney: 2.5, hackWeight: 85, chaWeight: 15, difficulty: 3.5}, - }, - { - desc: "Assign this gang member to attempt identity theft

Earns money - Increases respect - Increases wanted level", - isCombat: false, - isHacking: true, - name: "Identity Theft", - params: {baseRespect: 0.0001, baseWanted: 0.075, baseMoney: 6, hackWeight: 80, chaWeight: 20, difficulty: 5}, - }, - { - desc: "Assign this gang member to carry out DDoS attacks

Increases respect - Increases wanted level", - isCombat: false, - isHacking: true, - name: "DDoS Attacks", - params: {baseRespect: 0.0004, baseWanted: 0.2, hackWeight: 100, difficulty: 8}, - }, - { - desc: "Assign this gang member to create and distribute malicious viruses

Increases respect - Increases wanted level", - isCombat: false, - isHacking: true, - name: "Plant Virus", - params: {baseRespect: 0.0006, baseWanted: 0.4, hackWeight: 100, difficulty: 12}, - }, - { - desc: "Assign this gang member to commit financial fraud and digital counterfeiting

Earns money - Slightly increases respect - Slightly increases wanted level", - isCombat: false, - isHacking: true, - name: "Fraud & Counterfeiting", - params: {baseRespect: 0.0004, baseWanted: 0.3, baseMoney: 15, hackWeight: 80, chaWeight: 20, difficulty: 20}, - }, - { - desc: "Assign this gang member to launder money

Earns money - Increases respect - Increases wanted level", - isCombat: false, - isHacking: true, - name: "Money Laundering", - params: {baseRespect: 0.001, baseWanted: 1.25, baseMoney: 120, hackWeight: 75, chaWeight: 25, difficulty: 25}, - }, - { - desc: "Assign this gang member to commit acts of cyberterrorism

Greatly increases respect - Greatly increases wanted level", - isCombat: false, - isHacking: true, - name: "Cyberterrorism", - params: {baseRespect: 0.01, baseWanted: 6, hackWeight: 80, chaWeight: 20, difficulty: 36}, - }, - { - desc: "Assign this gang member to be an ethical hacker for corporations

Earns money - Lowers wanted level", - isCombat: false, - isHacking: true, - name: "Ethical Hacking", - params: {baseWanted: -0.001, baseMoney: 1, hackWeight: 90, chaWeight: 10, difficulty: 1}, - }, - { - desc: "Assign this gang member to mug random people on the streets

Earns money - Slightly increases respect - Very slightly increases wanted level", - isCombat: true, - isHacking: false, - name: "Mug People", - params: { - baseRespect: 0.00005, baseWanted: 0.00005, baseMoney: 1.2, - strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 10, chaWeight: 15, - difficulty: 1, - }, - }, - { - desc: "Assign this gang member to sell drugs

Earns money - Slightly increases respect - Slightly increases wanted level - Scales slightly with territory", - isCombat: true, - isHacking: false, - name: "Deal Drugs", - params: { - baseRespect: 0.00006, baseWanted: 0.002, baseMoney: 5, - agiWeight: 20, dexWeight: 20, chaWeight: 60, - difficulty: 3.5, - territory: { - money: 1.2, - respect: 1, - wanted: 1.15, - }, - }, - }, - { - desc: "Assign this gang member to extort civilians in your territory

Earns money - Slightly increases respect - Increases wanted - Scales heavily with territory", - isCombat: true, - isHacking: false, - name: "Strongarm Civilians", - params: { - baseRespect: 0.00004, baseWanted: 0.02, baseMoney: 2.5, - hackWeight: 10, strWeight: 25, defWeight: 25, dexWeight: 20, agiWeight: 10, chaWeight: 10, - difficulty: 5, - territory: { - money: 1.6, - respect: 1.1, - wanted: 1.5, - }, - }, - }, - { - desc: "Assign this gang member to run cons

Earns money - Increases respect - Increases wanted level", - isCombat: true, - isHacking: false, - name: "Run a Con", - params: { - baseRespect: 0.00012, baseWanted: 0.05, baseMoney: 15, - strWeight: 5, defWeight: 5, agiWeight: 25, dexWeight: 25, chaWeight: 40, - difficulty: 14, - }, - }, - { - desc: "Assign this gang member to commit armed robbery on stores, banks and armored cars

Earns money - Increases respect - Increases wanted level", - isCombat: true, - isHacking: false, - name: "Armed Robbery", - params: { - baseRespect: 0.00014, baseWanted: 0.1, baseMoney: 38, - hackWeight: 20, strWeight: 15, defWeight: 15, agiWeight: 10, dexWeight: 20, chaWeight: 20, - difficulty: 20, - }, - }, - { - desc: "Assign this gang member to traffick illegal arms

Earns money - Increases respect - Increases wanted level - Scales heavily with territory", - isCombat: true, - isHacking: false, - name: "Traffick Illegal Arms", - params: { - baseRespect: 0.0002, baseWanted: 0.24, baseMoney: 58, - hackWeight: 15, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 25, - difficulty: 32, - territory: { - money: 1.4, - respect: 1.3, - wanted: 1.25, - }, - }, - }, - { - desc: "Assign this gang member to threaten and black mail high-profile targets

Earns money - Slightly increases respect - Slightly increases wanted level", - isCombat: true, - isHacking: false, - name: "Threaten & Blackmail", - params: { - baseRespect: 0.0002, baseWanted: 0.125, baseMoney: 24, - hackWeight: 25, strWeight: 25, dexWeight: 25, chaWeight: 25, - difficulty: 28, - }, - }, - { - desc: "Assign this gang member to engage in human trafficking operations

Earns money - Increases respect - Increases wanted level - Scales heavily with territory", - isCombat: true, - isHacking: false, - name: "Human Trafficking", - params: { - baseRespect: 0.004, baseWanted: 1.25, baseMoney: 120, - hackWeight: 30, strWeight: 5, defWeight: 5, dexWeight: 30, chaWeight: 30, - difficulty: 36, - territory: { - money: 1.5, - respect: 1.5, - wanted: 1.6, - }, - }, - }, - { - desc: "Assign this gang member to commit acts of terrorism

Greatly increases respect - Greatly increases wanted level - Scales heavily with territory", - isCombat: true, - isHacking: false, - name: "Terrorism", - params: { - baseRespect: 0.01, baseWanted: 6, - hackWeight: 20, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 20, - difficulty: 36, - territory: { - money: 1, - respect: 2, - wanted: 2, - }, - }, - }, - { - desc: "Assign this gang member to be a vigilante and protect the city from criminals

Decreases wanted level", - isCombat: true, - isHacking: true, - name: "Vigilante Justice", - params: { - baseWanted: -0.001, - hackWeight: 20, strWeight: 20, defWeight: 20, dexWeight: 20, agiWeight: 20, - difficulty: 1, - territory: { - money: 1, - respect: 1, - wanted: 0.9, // Gets harder with more territory - }, - }, - }, - { - desc: "Assign this gang member to increase their combat stats (str, def, dex, agi)", - isCombat: true, - isHacking: true, - name: "Train Combat", - params: { - strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 25, - difficulty: 5, - }, - }, - { - desc: "Assign this gang member to train their hacking skills", - isCombat: true, - isHacking: true, - name: "Train Hacking", - params: {hackWeight: 100, difficulty: 8}, - }, - { - desc: "Assign this gang member to train their charisma", - isCombat: true, - isHacking: true, - name: "Train Charisma", - params: {chaWeight: 100, difficulty: 8}, - }, - { - desc: "Assign this gang member to engage in territorial warfare with other gangs. Members assigned to this task will help increase your gang's territory and will defend your territory from being taken.", - isCombat: true, - isHacking: true, - name: "Territory Warfare", - params: { - hackWeight: 15, strWeight: 20, defWeight: 20, dexWeight: 20, agiWeight: 20, chaWeight: 5, - difficulty: 5, - }, - }, -]; diff --git a/src/data/gangmemberupgrades.ts b/src/data/gangmemberupgrades.ts deleted file mode 100644 index 7b06ee20f..000000000 --- a/src/data/gangmemberupgrades.ts +++ /dev/null @@ -1,209 +0,0 @@ -/** - * Defines the parameters that can be used to initialize and describe a GangMemberUpgrade - * (defined in Gang.js) - */ -export interface IGangMemberUpgradeMetadata { - cost: number; - mults: any; - name: string; - upgType: string; -} - -/** - * Array of metadata for all Gang Member upgrades. Used to construct the global GangMemberUpgrade - * objects in Gang.js - */ -export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [ - { - cost: 1e6, - mults: {str: 1.04, def: 1.04}, - name: "Baseball Bat", - upgType: "w", - }, - { - cost: 12e6, - mults: {str: 1.08, def: 1.08, dex: 1.08}, - name: "Katana", - upgType: "w", - }, - { - cost: 25e6, - mults: {str: 1.1, def: 1.1, dex: 1.1, agi: 1.1}, - name: "Glock 18C", - upgType: "w", - }, - { - cost: 50e6, - mults: {str: 1.12, def: 1.1, agi: 1.1}, - name: "P90C", - upgType: "w", - }, - { - cost: 60e6, - mults: {str: 1.2, def: 1.15}, - name: "Steyr AUG", - upgType: "w", - }, - { - cost: 100e6, - mults: {str: 1.25, def: 1.2}, - name: "AK-47", - upgType: "w", - }, - { - cost: 150e6, - mults: {str: 1.3, def: 1.25}, - name: "M15A10 Assault Rifle", - upgType: "w", - }, - { - cost: 225e6, - mults: {str: 1.3, dex: 1.25, agi: 1.3}, - name: "AWM Sniper Rifle", - upgType: "w", - }, - { - cost: 2e6, - mults: {def: 1.04}, - name: "Bulletproof Vest", - upgType: "a", - }, - { - cost: 5e6, - mults: {def: 1.08}, - name: "Full Body Armor", - upgType: "a", - }, - { - cost: 25e6, - mults: {def: 1.15, agi: 1.15}, - name: "Liquid Body Armor", - upgType: "a", - }, - { - cost: 40e6, - mults: {def: 1.2}, - name: "Graphene Plating Armor", - upgType: "a", - }, - { - cost: 3e6, - mults: {agi: 1.04, cha: 1.04}, - name: "Ford Flex V20", - upgType: "v", - }, - { - cost: 9e6, - mults: {agi: 1.08, cha: 1.08}, - name: "ATX1070 Superbike", - upgType: "v", - }, - { - cost: 18e6, - mults: {agi: 1.12, cha: 1.12}, - name: "Mercedes-Benz S9001", - upgType: "v", - }, - { - cost: 30e6, - mults: {agi: 1.16, cha: 1.16}, - name: "White Ferrari", - upgType: "v", - }, - { - cost: 5e6, - mults: {hack: 1.05}, - name: "NUKE Rootkit", - upgType: "r", - }, - { - cost: 25e6, - mults: {hack: 1.1}, - name: "Soulstealer Rootkit", - upgType: "r", - }, - { - cost: 75e6, - mults: {hack: 1.15}, - name: "Demon Rootkit", - upgType: "r", - }, - { - cost: 40e6, - mults: {hack: 1.12}, - name: "Hmap Node", - upgType: "r", - }, - { - cost: 75e6, - mults: {hack: 1.15}, - name: "Jack the Ripper", - upgType: "r", - }, - { - cost: 10e9, - mults: {str: 1.3, dex: 1.3}, - name: "Bionic Arms", - upgType: "g", - }, - { - cost: 10e9, - mults: {agi: 1.6}, - name: "Bionic Legs", - upgType: "g", - }, - { - cost: 15e9, - mults: {str: 1.15, def: 1.15, dex: 1.15, agi: 1.15}, - name: "Bionic Spine", - upgType: "g", - }, - { - cost: 20e9, - mults: {str: 1.4, def: 1.4}, - name: "BrachiBlades", - upgType: "g", - }, - { - cost: 12e9, - mults: {str: 1.2, def: 1.2}, - name: "Nanofiber Weave", - upgType: "g", - }, - { - cost: 25e9, - mults: {str: 1.5, agi: 1.5}, - name: "Synthetic Heart", - upgType: "g", - }, - { - cost: 15e9, - mults: {str: 1.3, def: 1.3}, - name: "Synfibril Muscle", - upgType: "g", - }, - { - cost: 5e9, - mults: {hack: 1.05}, - name: "BitWire", - upgType: "g", - }, - { - cost: 10e9, - mults: {hack: 1.15}, - name: "Neuralstimulator", - upgType: "g", - }, - { - cost: 7.5e9, - mults: {hack: 1.1}, - name: "DataJack", - upgType: "g", - }, - { - cost: 50e9, - mults: {str: 1.7, def: 1.7}, - name: "Graphene Bone Lacings", - upgType: "g", - }, -]; From 07cca48a172122d09e67e8053da88b9d1b98764a Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Wed, 16 Jun 2021 18:38:29 -0400 Subject: [PATCH 11/24] converted everything to ts --- src/Constants.ts | 3 + src/Gang.jsx | 468 ------------------ src/Gang/Gang.ts | 463 +++++++++++++++++ src/Gang/GangMember.ts | 28 +- src/Gang/GangMemberUpgrade.ts | 14 - src/Gang/Helpers.tsx | 40 ++ src/Gang/IGang.ts | 44 ++ src/Gang/data/Constants.ts | 2 +- src/Gang/ui/GangMemberUpgradePopup.tsx | 4 +- src/NetscriptFunctions.js | 10 +- .../Player/PlayerObjectGangMethods.js | 2 +- src/Prestige.js | 3 +- src/engine.jsx | 5 +- 13 files changed, 585 insertions(+), 501 deletions(-) delete mode 100644 src/Gang.jsx create mode 100644 src/Gang/Gang.ts create mode 100644 src/Gang/Helpers.tsx create mode 100644 src/Gang/IGang.ts diff --git a/src/Constants.ts b/src/Constants.ts index 1f2fb3b2e..401458e95 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -8,6 +8,9 @@ import { IMap } from "./types"; export const CONSTANTS: IMap = { Version: "0.52.2", + // 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. diff --git a/src/Gang.jsx b/src/Gang.jsx deleted file mode 100644 index a4da530ed..000000000 --- a/src/Gang.jsx +++ /dev/null @@ -1,468 +0,0 @@ -/** - * TODO - * Add police clashes - * balance point to keep them from running out of control -*/ - -import { Engine } from "./engine"; -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 { createElement } from "../utils/uiHelpers/createElement"; -import { removeElement } from "../utils/uiHelpers/removeElement"; - -import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades"; -import { GangConstants } from "./Gang/data/Constants"; -import { GangMemberTasks } from "./Gang/GangMemberTasks"; - -import { AllGangs } from "./Gang/AllGangs"; -import { Root } from "./Gang/ui/Root"; -import { GangMember } from "./Gang/GangMember"; - -import React from "react"; -import ReactDOM from "react-dom"; - -/** - * @param facName {string} Name of corresponding faction - * @param hacking {bollean} Whether or not its a hacking gang - */ -export function Gang(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; -} - -Gang.prototype.getPower = function() { - return AllGangs[this.facName].power; -} - -Gang.prototype.getTerritory = function() { - return AllGangs[this.facName].territory; -} - -Gang.prototype.process = function(numCycles=1, player) { - const CyclesPerSecond = 1000 / Engine._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) { - exceptionAlert(`Exception caught when processing Gang: ${e}`); - } -} - -Gang.prototype.processGains = function(numCycles=1, player) { - // Get gains per cycle - let moneyGains = 0, respectGains = 0, 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; - - if (typeof respectGains === "number") { - 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"); - } else { - let 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); - } - } else { - console.warn("respectGains calculated to be NaN"); - } - if (typeof wantedLevelGains === "number") { - if (this.wanted === 1 && wantedLevelGains < 0) { - // At minimum wanted, do nothing - } else { - 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;} - } - } else { - console.warn("ERROR: wantedLevelGains is NaN"); - } - if (typeof moneyGains === "number") { - player.gainMoney(moneyGains * numCycles); - player.recordMoneySource(moneyGains * numCycles, "gang"); - } else { - console.warn("ERROR: respectGains is NaN"); - } -} - -Gang.prototype.processTerritoryAndPowerGains = function(numCycles=1) { - 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); - - - 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); - } - } - } -} - -Gang.prototype.canRecruitMember = function() { - if (this.members.length >= GangConstants.MaximumGangMembers) { return false; } - return (this.respect >= this.getRespectNeededToRecruitMember()); -} - -Gang.prototype.getRespectNeededToRecruitMember = function() { - // 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.round(0.9 * Math.pow(i, 3) + Math.pow(i, 2)); -} - -Gang.prototype.recruitMember = function(name) { - name = String(name); - if (name === "" || !this.canRecruitMember()) { return false; } - - // Check for already-existing names - let sameNames = this.members.filter((m) => { - return m.name === name; - }); - if (sameNames.length >= 1) { return false; } - - let member = new GangMember(name); - this.members.push(member); - return true; -} - -// Money and Respect gains multiplied by this number (< 1) -Gang.prototype.getWantedPenalty = function() { - return (this.respect) / (this.respect + this.wanted); -} - -Gang.prototype.processExperienceGains = function(numCycles=1) { - for (var i = 0; i < this.members.length; ++i) { - this.members[i].gainExperience(numCycles); - this.members[i].updateSkillLevels(); - } -} - -//Calculates power GAIN, which is added onto the Gang's existing power -Gang.prototype.calculatePower = function() { - var memberTotal = 0; - for (var i = 0; i < this.members.length; ++i) { - if (GangMemberTasks.hasOwnProperty(this.members[i].task) && this.members[i].task == "Territory Warfare") { - const gain = this.members[i].calculatePower(); - memberTotal += gain; - } - } - return (0.015 * this.getTerritory() * memberTotal); -} - -Gang.prototype.clash = function(won=false) { - // 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 - if (!won) { - 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); - } - } -} - -Gang.prototype.killMember = function(memberObj) { - // Player loses a percentage of total respect, plus whatever respect that member has earned - const totalRespect = this.respect; - const lostRespect = (0.05 * totalRespect) + memberObj.earnedRespect; - this.respect = Math.max(0, totalRespect - lostRespect); - - for (let i = 0; i < this.members.length; ++i) { - if (memberObj.name === this.members[i].name) { - this.members.splice(i, 1); - break; - } - } - - // Notify of death - if (this.notifyMemberDeath) { - dialogBoxCreate(`${memberObj.name} was killed in a gang clash! You lost ${lostRespect} respect`); - } - -} - -Gang.prototype.ascendMember = function(memberObj, workerScript) { - try { - /** - * res is an object with the following format: - * { - * respect: Amount of respect to deduct - * hack/str/def/dex/agi/cha: Ascension multipliers gained for each stat - * } - */ - const res = memberObj.ascend(); - this.respect = Math.max(1, this.respect - res.respect); - if (workerScript == null) { - dialogBoxCreate([`You ascended ${memberObj.name}!`, - "", - `Your gang lost ${numeralWrapper.formatRespect(res.respect)} respect`, - "", - `${memberObj.name} gained the following stat multipliers for ascending:`, - `Hacking: ${numeralWrapper.formatPercentage(res.hack, 3)}`, - `Strength: ${numeralWrapper.formatPercentage(res.str, 3)}`, - `Defense: ${numeralWrapper.formatPercentage(res.def, 3)}`, - `Dexterity: ${numeralWrapper.formatPercentage(res.dex, 3)}`, - `Agility: ${numeralWrapper.formatPercentage(res.agi, 3)}`, - `Charisma: ${numeralWrapper.formatPercentage(res.cha, 3)}`].join("
")); - } else { - workerScript.log(`Ascended Gang member ${memberObj.name}`); - } - return res; - } catch(e) { - if (workerScript == null) { - exceptionAlert(e); - } else { - throw e; // Re-throw, will be caught in the Netscript Function - } - } -} - -// Cost of upgrade gets cheaper as gang increases in respect + power -Gang.prototype.getDiscount = function() { - 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' -Gang.prototype.getAllTaskNames = function() { - let tasks = []; - const allTasks = Object.keys(GangMemberTasks); - if (this.isHackingGang) { - tasks = allTasks.filter((e) => { - let task = GangMemberTasks[e]; - if (task == null) { return false; } - if (e === "Unassigned") { return false; } - return task.isHacking; - }); - } else { - tasks = allTasks.filter((e) => { - let task = GangMemberTasks[e]; - if (task == null) { return false; } - if (e === "Unassigned") { return false; } - return task.isCombat; - }); - } - return tasks; -} - -Gang.prototype.getUpgradeCost = function(upgName) { - if (GangMemberUpgrades[upgName] == null) { return Infinity; } - return GangMemberUpgrades[upgName].getCost(this); -} - -Gang.prototype.toJSON = function() { - return Generic_toJSON("Gang", this); -} - -Gang.fromJSON = function(value) { - return Generic_fromJSON(Gang, value.data); -} - -Reviver.constructors.Gang = Gang; - -function calculateTerritoryGain(winGang, loseGang) { - 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; -} -// Gang UI Dom Elements -const UIElems = { - gangContentCreated: false, - gangContainer: null, -} - -Gang.prototype.displayGangContent = function(player) { - if (!UIElems.gangContentCreated || UIElems.gangContainer == null) { - UIElems.gangContentCreated = true; - - // Create gang container - UIElems.gangContainer = createElement("div", { - id:"gang-container", class:"generic-menupage-container", - }); - - ReactDOM.render(, UIElems.gangContainer); - - document.getElementById("entire-game-container").appendChild(UIElems.gangContainer); - } - UIElems.gangContainer.style.display = "block"; -} - -Gang.prototype.clearUI = function() { - if (UIElems.gangContainer instanceof Element) { removeElement(UIElems.gangContainer); } - - for (const prop in UIElems) { - UIElems[prop] = null; - } - - UIElems.gangContentCreated = false; -} diff --git a/src/Gang/Gang.ts b/src/Gang/Gang.ts new file mode 100644 index 000000000..d0267b8d0 --- /dev/null +++ b/src/Gang/Gang.ts @@ -0,0 +1,463 @@ + +/** + * 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 { 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: string = "", hacking: boolean = 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: number = 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: number = 1, player: IPlayer): void { + // Get gains per cycle + let moneyGains = 0, respectGains = 0, 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; + + if (typeof respectGains === "number") { + 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"); + } else { + 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); + } + } else { + console.warn("respectGains calculated to be NaN"); + } + if (typeof wantedLevelGains === "number") { + if (this.wanted === 1 && wantedLevelGains < 0) { + // At minimum wanted, do nothing + } else { + 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;} + } + } else { + console.warn("ERROR: wantedLevelGains is NaN"); + } + if (typeof moneyGains === "number") { + player.gainMoney(moneyGains * numCycles); + player.recordMoneySource(moneyGains * numCycles, "gang"); + } else { + console.warn("ERROR: respectGains is NaN"); + } + } + + processTerritoryAndPowerGains(numCycles: number = 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: number = 1): void { + for (let i = 0; i < this.members.length; ++i) { + this.members[i].gainExperience(numCycles); + this.members[i].updateSkillLevels(); + } + } + + clash(won: boolean = 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 + if (!won) { + 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.round(0.9 * Math.pow(i, 3) + Math.pow(i, 2)); + } + + recruitMember(name: string): boolean { + name = String(name); + if (name === "" || !this.canRecruitMember()) { return false; } + + // Check for already-existing names + const sameNames = this.members.filter((m) => { + return 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") { + const gain = this.members[i].calculatePower(); + memberTotal += gain; + } + } + 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): void { + try { + + // res is an object with the following format: + // { + // respect: Amount of respect to deduct + // hack/str/def/dex/agi/cha: Ascension multipliers gained for each stat + // } + const res = member.ascend(); + this.respect = Math.max(1, this.respect - res.respect); + if (workerScript == null) { + dialogBoxCreate([`You ascended ${member.name}!`, + "", + `Your gang lost ${numeralWrapper.formatRespect(res.respect)} respect`, + "", + `${member.name} gained the following stat multipliers for ascending:`, + `Hacking: ${numeralWrapper.formatPercentage(res.hack, 3)}`, + `Strength: ${numeralWrapper.formatPercentage(res.str, 3)}`, + `Defense: ${numeralWrapper.formatPercentage(res.def, 3)}`, + `Dexterity: ${numeralWrapper.formatPercentage(res.dex, 3)}`, + `Agility: ${numeralWrapper.formatPercentage(res.agi, 3)}`, + `Charisma: ${numeralWrapper.formatPercentage(res.cha, 3)}`].join("
")); + } else { + workerScript.log('ascend', `Ascended Gang member ${member.name}`); + } + return res; + } catch(e) { + if (workerScript == null) { + exceptionAlert(e); + } else { + 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[] { + let tasks = []; + const allTasks = Object.keys(GangMemberTasks); + if (this.isHackingGang) { + tasks = allTasks.filter((e) => { + const task = GangMemberTasks[e]; + if (task == null) { return false; } + if (e === "Unassigned") { return false; } + return task.isHacking; + }); + } else { + tasks = allTasks.filter((e) => { + const task = GangMemberTasks[e]; + if (task == null) { return false; } + if (e === "Unassigned") { return false; } + return task.isCombat; + }); + } + return tasks; + } + + getUpgradeCost(upg: GangMemberUpgrade): 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; diff --git a/src/Gang/GangMember.ts b/src/Gang/GangMember.ts index 84a06914c..78ebfde8c 100644 --- a/src/Gang/GangMember.ts +++ b/src/Gang/GangMember.ts @@ -5,6 +5,7 @@ import { GangMemberUpgrades } from "./GangMemberUpgrades"; 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"; export class GangMember { @@ -92,7 +93,7 @@ export class GangMember { return GangMemberTasks["Unassigned"]; } - calculateRespectGain(gang: any): number { + calculateRespectGain(gang: IGang): number { const task = this.getTask(); if (task.baseRespect === 0) return 0; let statWeight = (task.hackWeight/100) * this.hack + @@ -109,7 +110,7 @@ export class GangMember { return 11 * task.baseRespect * statWeight * territoryMult * respectMult; } - calculateWantedLevelGain(gang: any): number { + calculateWantedLevelGain(gang: IGang): number { const task = this.getTask(); if (task.baseWanted === 0) return 0; let statWeight = (task.hackWeight / 100) * this.hack + @@ -133,7 +134,7 @@ export class GangMember { } } - calculateMoneyGain(gang: any): number { + calculateMoneyGain(gang: IGang): number { const task = this.getTask(); if (task.baseMoney === 0) return 0; let statWeight = (task.hackWeight/100) * this.hack + @@ -164,7 +165,7 @@ export class GangMember { this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles; } - recordEarnedRespect(numCycles = 1, gang: any): void { + recordEarnedRespect(numCycles = 1, gang: IGang): void { this.earnedRespect += (this.calculateRespectGain(gang) * numCycles); } @@ -239,7 +240,7 @@ export class GangMember { this.cha_mult = 1; for (let i = 0; i < this.augmentations.length; ++i) { const aug = GangMemberUpgrades[this.augmentations[i]]; - aug.apply(this); + this.applyUpgrade(aug); } // Clear exp and recalculate stats @@ -264,7 +265,16 @@ export class GangMember { }; } - buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: any): boolean { + 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 { if (typeof upg === 'string') { upg = GangMemberUpgrades[upg]; } @@ -276,14 +286,14 @@ export class GangMember { return false; } - if (player.money.lt(upg.getCost(gang))) { return false; } - player.loseMoney(upg.getCost(gang)); + 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); } - upg.apply(this); + this.applyUpgrade(upg); return true; } diff --git a/src/Gang/GangMemberUpgrade.ts b/src/Gang/GangMemberUpgrade.ts index 29fc90af6..7cfd6c291 100644 --- a/src/Gang/GangMemberUpgrade.ts +++ b/src/Gang/GangMemberUpgrade.ts @@ -17,10 +17,6 @@ export class GangMemberUpgrade { this.desc = this.createDescription(); } - getCost(gang: any): number { - return this.cost / gang.getDiscount(); - } - createDescription(): string { const lines = ["Increases:"]; if (this.mults.str != null) { @@ -44,16 +40,6 @@ export class GangMemberUpgrade { return lines.join("
"); } - // Passes in a GangMember object - apply(member: any): void { - if (this.mults.str != null) { member.str_mult *= this.mults.str; } - if (this.mults.def != null) { member.def_mult *= this.mults.def; } - if (this.mults.dex != null) { member.dex_mult *= this.mults.dex; } - if (this.mults.agi != null) { member.agi_mult *= this.mults.agi; } - if (this.mults.cha != null) { member.cha_mult *= this.mults.cha; } - if (this.mults.hack != null) { member.hack_mult *= this.mults.hack; } - } - // User friendly version of type. getType(): string { switch (this.type) { diff --git a/src/Gang/Helpers.tsx b/src/Gang/Helpers.tsx new file mode 100644 index 000000000..bf7de1029 --- /dev/null +++ b/src/Gang/Helpers.tsx @@ -0,0 +1,40 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import { createElement } from "../../utils/uiHelpers/createElement"; +import { IPlayer } from "../PersonObjects/IPlayer"; +import { IEngine } from "../IEngine"; +import { Root } from "./ui/Root"; +import { Gang } from "./Gang"; + +// Gang UI Dom Elements +const UIElems: { + gangContentCreated: boolean; + gangContainer: HTMLElement | null; +} = { + gangContentCreated: false, + gangContainer: null, +} + +export function displayGangContent(engine: IEngine, gang: Gang, player: IPlayer): void { + if (!UIElems.gangContentCreated || UIElems.gangContainer == null) { + UIElems.gangContentCreated = true; + + // Create gang container + UIElems.gangContainer = createElement("div", { + id:"gang-container", class:"generic-menupage-container", + }); + + ReactDOM.render(, UIElems.gangContainer); + + const container = document.getElementById("entire-game-container"); + if(!container) throw new Error('entire-game-container was null'); + container.appendChild(UIElems.gangContainer); + } + if(UIElems.gangContainer) UIElems.gangContainer.style.display = "block"; +} + +export function clearGangUI(): void { + if (UIElems.gangContainer instanceof Element) ReactDOM.unmountComponentAtNode(UIElems.gangContainer); + UIElems.gangContainer = null; + UIElems.gangContentCreated = false; +} diff --git a/src/Gang/IGang.ts b/src/Gang/IGang.ts new file mode 100644 index 000000000..f1a40f76d --- /dev/null +++ b/src/Gang/IGang.ts @@ -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; +} \ No newline at end of file diff --git a/src/Gang/data/Constants.ts b/src/Gang/data/Constants.ts index e4ab78b09..5975de58b 100644 --- a/src/Gang/data/Constants.ts +++ b/src/Gang/data/Constants.ts @@ -10,7 +10,7 @@ export const GangConstants: { MaximumGangMembers: 30, CyclesPerTerritoryAndPowerUpdate: 100, // Portion of upgrade multiplier that is kept after ascending - AscensionMultiplierRatio: 15, + AscensionMultiplierRatio: .15, // Names of possible Gangs Names: [ "Slum Snakes", diff --git a/src/Gang/ui/GangMemberUpgradePopup.tsx b/src/Gang/ui/GangMemberUpgradePopup.tsx index e9d19d074..999605d6e 100644 --- a/src/Gang/ui/GangMemberUpgradePopup.tsx +++ b/src/Gang/ui/GangMemberUpgradePopup.tsx @@ -25,7 +25,7 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement { for (const upgName in GangMemberUpgrades) { if (GangMemberUpgrades.hasOwnProperty(upgName)) { const upg = GangMemberUpgrades[upgName]; - if (props.player.money.lt(upg.getCost(props.gang))) continue; + 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": @@ -63,7 +63,7 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement { setRerender(old => !old); } return ( - {upg.name} - {Money(upg.getCost(props.gang))} + {upg.name} - {Money(props.gang.getUpgradeCost(upg))} ); } diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index e20b9f191..5cb34f5e9 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -29,7 +29,7 @@ import { calculateWeakenTime, } from "./Hacking"; import { calculateServerGrowth } from "./Server/formulas/grow"; -import { Gang } from "./Gang"; +import { Gang } from "./Gang/Gang"; import { AllGangs } from "./Gang/AllGangs"; import { GangMemberTasks } from "./Gang/GangMemberTasks"; import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades"; @@ -3746,7 +3746,9 @@ function NetscriptFunctions(workerScript) { 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")); @@ -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 { diff --git a/src/PersonObjects/Player/PlayerObjectGangMethods.js b/src/PersonObjects/Player/PlayerObjectGangMethods.js index 1d071066f..82399d79e 100644 --- a/src/PersonObjects/Player/PlayerObjectGangMethods.js +++ b/src/PersonObjects/Player/PlayerObjectGangMethods.js @@ -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"; diff --git a/src/Prestige.js b/src/Prestige.js index a851b3022..d21bbe182 100755 --- a/src/Prestige.js +++ b/src/Prestige.js @@ -14,6 +14,7 @@ import { Engine } from "./engine"; import { Faction } from "./Faction/Faction"; import { Factions, initFactions } from "./Faction/Factions"; import { joinFaction } from "./Faction/FactionHelpers"; +import { clearGangUI } from "./Gang/Helpers"; import { updateHashManagerCapacity } from "./Hacknet/HacknetHelpers"; import { initMessages } from "./Message/MessageHelpers"; import { prestigeWorkerScripts } from "./NetscriptWorker"; @@ -333,7 +334,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; diff --git a/src/engine.jsx b/src/engine.jsx index c0cb90a1e..ab6181941 100644 --- a/src/engine.jsx +++ b/src/engine.jsx @@ -32,6 +32,7 @@ import { processPassiveFactionRepGain, inviteToFaction, } from "./Faction/FactionHelpers"; +import { displayGangContent, clearGangUI } from "./Gang/Helpers"; import { displayInfiltrationContent } from "./Infiltration/Helper"; import { getHackingWorkRepGain, @@ -439,7 +440,7 @@ const Engine = { loadGangContent: function() { Engine.hideAllContent(); if (document.getElementById("gang-container") || Player.inGang()) { - Player.gang.displayGangContent(Player); + displayGangContent(this, Player.gang, Player); routing.navigateTo(Page.Gang); } else { Engine.loadTerminalContent(); @@ -534,7 +535,7 @@ const Engine = { } if (Player.inGang()) { - Player.gang.clearUI(); + clearGangUI(); } if (Player.corporation instanceof Corporation) { Player.corporation.clearUI(); From 2c7fbc03cf821a2b6fd0c1d0b2f725acee788f1b Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Thu, 17 Jun 2021 11:24:52 -0400 Subject: [PATCH 12/24] Remove all uses of any in Gang, Train Combat and Train Hacking are now the best exp gains, gang equipment and augs now give exp boosts --- src/Gang/Gang.ts | 24 ++++------ src/Gang/GangMember.ts | 53 ++++++++++++++-------- src/Gang/GangMemberTask.ts | 21 +-------- src/Gang/Helpers.tsx | 1 + src/Gang/IAscensionResult.ts | 9 ++++ src/Gang/ITaskParams.ts | 20 ++++++++ src/Gang/data/tasks.ts | 7 +-- src/Gang/ui/GangMemberAccordionContent.tsx | 11 +++-- src/Gang/ui/GangMemberList.tsx | 16 +++---- src/Gang/ui/GangMemberUpgradePopup.tsx | 21 +++++---- src/Gang/ui/GangStats.tsx | 16 ++----- src/Gang/ui/ManagementSubpage.tsx | 5 +- src/Gang/ui/Panel1.tsx | 16 +++---- src/Gang/ui/Panel2.tsx | 16 +++---- src/Gang/ui/Panel3.tsx | 12 +---- src/Gang/ui/Root.tsx | 11 ++++- src/Gang/ui/TerritorySubpage.tsx | 10 +--- 17 files changed, 137 insertions(+), 132 deletions(-) create mode 100644 src/Gang/IAscensionResult.ts create mode 100644 src/Gang/ITaskParams.ts diff --git a/src/Gang/Gang.ts b/src/Gang/Gang.ts index d0267b8d0..30b5adf15 100644 --- a/src/Gang/Gang.ts +++ b/src/Gang/Gang.ts @@ -24,6 +24,7 @@ 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"; @@ -53,7 +54,7 @@ export class Gang { notifyMemberDeath: boolean; - constructor(facName: string = "", hacking: boolean = false) { + constructor(facName = "", hacking = false) { this.facName = facName; this.members = []; this.wanted = 1; @@ -87,7 +88,7 @@ export class Gang { return AllGangs[this.facName].territory; } - process(numCycles: number = 1, player: IPlayer): void { + process(numCycles = 1, player: IPlayer): void { const CyclesPerSecond = 1000 / CONSTANTS._idleSpeed; if (isNaN(numCycles)) { @@ -110,7 +111,7 @@ export class Gang { } - processGains(numCycles: number = 1, player: IPlayer): void { + processGains(numCycles = 1, player: IPlayer): void { // Get gains per cycle let moneyGains = 0, respectGains = 0, wantedLevelGains = 0; let justice = 0; @@ -170,7 +171,7 @@ export class Gang { } } - processTerritoryAndPowerGains(numCycles: number = 1): void { + processTerritoryAndPowerGains(numCycles = 1): void { this.storedTerritoryAndPowerCycles += numCycles; if (this.storedTerritoryAndPowerCycles < GangConstants.CyclesPerTerritoryAndPowerUpdate) { return; } this.storedTerritoryAndPowerCycles -= GangConstants.CyclesPerTerritoryAndPowerUpdate; @@ -268,14 +269,14 @@ export class Gang { } } - processExperienceGains(numCycles: number = 1): void { + processExperienceGains(numCycles = 1): void { for (let i = 0; i < this.members.length; ++i) { this.members[i].gainExperience(numCycles); this.members[i].updateSkillLevels(); } } - clash(won: boolean = false): void { + clash(won = false): void { // Determine if a gang member should die let baseDeathChance = 0.01; if (won) { baseDeathChance /= 2; } @@ -370,14 +371,8 @@ export class Gang { } - ascendMember(member: GangMember, workerScript: WorkerScript): void { + ascendMember(member: GangMember, workerScript?: WorkerScript): IAscensionResult { try { - - // res is an object with the following format: - // { - // respect: Amount of respect to deduct - // hack/str/def/dex/agi/cha: Ascension multipliers gained for each stat - // } const res = member.ascend(); this.respect = Math.max(1, this.respect - res.respect); if (workerScript == null) { @@ -399,9 +394,8 @@ export class Gang { } catch(e) { if (workerScript == null) { exceptionAlert(e); - } else { - throw e; // Re-throw, will be caught in the Netscript Function } + throw e; // Re-throw, will be caught in the Netscript Function } } diff --git a/src/Gang/GangMember.ts b/src/Gang/GangMember.ts index 78ebfde8c..8ec426953 100644 --- a/src/Gang/GangMember.ts +++ b/src/Gang/GangMember.ts @@ -2,12 +2,22 @@ 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"; @@ -67,13 +77,12 @@ export class GangMember { } assignToTask(taskName: string): boolean { - if (GangMemberTasks.hasOwnProperty(taskName)) { - this.task = taskName; - return true; - } else { + if (!GangMemberTasks.hasOwnProperty(taskName)) { this.task = "Unassigned"; return false; } + this.task = taskName; + return true; } unassignFromTask(): void { @@ -151,25 +160,37 @@ export class GangMember { return 5 * task.baseMoney * statWeight * territoryMult * respectMult; } + expMult(): IMults { + return { + hack: (this.hack_mult-1)/10+1, + str: (this.str_mult-1)/10+1, + def: (this.def_mult-1)/10+1, + dex: (this.dex_mult-1)/10+1, + agi: (this.agi_mult-1)/10+1, + cha: (this.cha_mult-1)/10+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; - this.hack_exp += (task.hackWeight / weightDivisor) * difficultyPerCycles; - this.str_exp += (task.strWeight / weightDivisor) * difficultyPerCycles; - this.def_exp += (task.defWeight / weightDivisor) * difficultyPerCycles; - this.dex_exp += (task.dexWeight / weightDivisor) * difficultyPerCycles; - this.agi_exp += (task.agiWeight / weightDivisor) * difficultyPerCycles; - this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles; + 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); } - getAscensionResults(): any { + getAscensionResults(): IMults { //Calculate ascension bonus to stat multipliers. //This is based on the current number of multipliers from Non-Augmentation upgrades //+ Ascension Bonus = N% of current bonus from Augmentations @@ -201,7 +222,7 @@ export class GangMember { } } - getAscensionEfficiency(): any { + getAscensionEfficiency(): IMults { function formula(mult: number): number { return 1/(1+Math.log(mult)/Math.log(20)); } @@ -215,7 +236,7 @@ export class GangMember { }; } - ascend(): any { + ascend(): IAscensionResult { const res = this.getAscensionResults(); const hackAscMult = res.hack; const strAscMult = res.str; @@ -275,12 +296,6 @@ export class GangMember { } buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: IGang): boolean { - if (typeof upg === 'string') { - upg = GangMemberUpgrades[upg]; - } - if (!(upg instanceof GangMemberUpgrade)) { - return false; - } // Prevent purchasing of already-owned upgrades if (this.augmentations.includes(upg.name) || this.upgrades.includes(upg.name)) { return false; diff --git a/src/Gang/GangMemberTask.ts b/src/Gang/GangMemberTask.ts index a684a09cb..053fe0777 100644 --- a/src/Gang/GangMemberTask.ts +++ b/src/Gang/GangMemberTask.ts @@ -1,23 +1,4 @@ - -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; -} +import { ITaskParams, ITerritory } from "./ITaskParams"; export class GangMemberTask { name: string; diff --git a/src/Gang/Helpers.tsx b/src/Gang/Helpers.tsx index bf7de1029..0d7091a52 100644 --- a/src/Gang/Helpers.tsx +++ b/src/Gang/Helpers.tsx @@ -34,6 +34,7 @@ export function displayGangContent(engine: IEngine, gang: Gang, player: IPlayer) } export function clearGangUI(): void { + if(UIElems.gangContainer) UIElems.gangContainer.style.display = 'none'; if (UIElems.gangContainer instanceof Element) ReactDOM.unmountComponentAtNode(UIElems.gangContainer); UIElems.gangContainer = null; UIElems.gangContentCreated = false; diff --git a/src/Gang/IAscensionResult.ts b/src/Gang/IAscensionResult.ts new file mode 100644 index 000000000..f42f7c603 --- /dev/null +++ b/src/Gang/IAscensionResult.ts @@ -0,0 +1,9 @@ +export interface IAscensionResult { + respect: number; + hack: number; + str: number; + def: number; + dex: number; + agi: number; + cha: number; +}; \ No newline at end of file diff --git a/src/Gang/ITaskParams.ts b/src/Gang/ITaskParams.ts new file mode 100644 index 000000000..44178e42f --- /dev/null +++ b/src/Gang/ITaskParams.ts @@ -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; +} \ No newline at end of file diff --git a/src/Gang/data/tasks.ts b/src/Gang/data/tasks.ts index 60c5a6662..c5d83e7c8 100644 --- a/src/Gang/data/tasks.ts +++ b/src/Gang/data/tasks.ts @@ -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: 200, }, }, { @@ -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", diff --git a/src/Gang/ui/GangMemberAccordionContent.tsx b/src/Gang/ui/GangMemberAccordionContent.tsx index 882a0149f..573f95738 100644 --- a/src/Gang/ui/GangMemberAccordionContent.tsx +++ b/src/Gang/ui/GangMemberAccordionContent.tsx @@ -1,20 +1,23 @@ -import * as React from "react"; +import React, { useState } from "react"; import { Panel1 } from "./Panel1"; import { Panel2 } from "./Panel2"; import { Panel3 } from "./Panel3"; +import { Gang } from "../Gang"; +import { GangMember } from "../GangMember"; interface IProps { - gang: any; - member: any; + gang: Gang; + member: GangMember; } export function GangMemberAccordionContent(props: IProps): React.ReactElement { + const setRerender = useState(false)[1]; return (<>
- + setRerender(old => !old)} gang={props.gang} member={props.member} />
diff --git a/src/Gang/ui/GangMemberList.tsx b/src/Gang/ui/GangMemberList.tsx index 44fffdfa8..3accc7163 100644 --- a/src/Gang/ui/GangMemberList.tsx +++ b/src/Gang/ui/GangMemberList.tsx @@ -4,21 +4,17 @@ import { GangMemberAccordionContent } from "./GangMemberAccordionContent" import { GangMemberUpgradePopup } from "./GangMemberUpgradePopup" import { createPopup } from "../../ui/React/createPopup"; import { IPlayer } from "../../PersonObjects/IPlayer"; +import { Gang } from "../Gang"; +import { GangMember } from "../GangMember"; interface IProps { - gang: any; + gang: Gang; player: IPlayer; } export function GangMemberList(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; const [filter, setFilter] = useState(""); - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); - function openUpgradePopup(): void { const popupId = `gang-upgrade-popup`; createPopup(popupId, GangMemberUpgradePopup, { @@ -32,15 +28,15 @@ export function GangMemberList(props: IProps): React.ReactElement { setFilter(event.target.value); } - function members(): any { - return props.gang.members.filter((member: any) => member.name.indexOf(filter) > -1 || member.task.indexOf(filter) > -1) + function members(): GangMember[] { + return props.gang.members.filter((member: GangMember) => member.name.indexOf(filter) > -1 || member.task.indexOf(filter) > -1) } return (<> Manage Equipment
    - {members().map((member: any) =>
  • + {members().map((member: GangMember) =>
  • {member.name}} diff --git a/src/Gang/ui/GangMemberUpgradePopup.tsx b/src/Gang/ui/GangMemberUpgradePopup.tsx index 999605d6e..bd7b80128 100644 --- a/src/Gang/ui/GangMemberUpgradePopup.tsx +++ b/src/Gang/ui/GangMemberUpgradePopup.tsx @@ -6,10 +6,12 @@ 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"; interface IPanelProps { - member: any; - gang: any; + member: GangMember; + gang: Gang; player: IPlayer; } @@ -79,8 +81,8 @@ Agi: {props.member.agi} (x{formatNumber(props.member.agi_mult * props.member.ag Cha: {props.member.cha} (x{formatNumber(props.member.cha_mult * props.member.cha_asc_mult, 2)})
    - Purchased Upgrades: {props.member.upgrades.map((upg: any) => purchased(upg))} - {props.member.augmentations.map((upg: any) => purchased(upg))} + Purchased Upgrades: {props.member.upgrades.map((upg: string) => purchased(upg))} + {props.member.augmentations.map((upg: string) => purchased(upg))}

    Weapons

    @@ -106,7 +108,7 @@ Cha: {props.member.cha} (x{formatNumber(props.member.cha_mult * props.member.ch } interface IProps { - gang: any; + gang: Gang; player: IPlayer; popupId: string; } @@ -115,16 +117,17 @@ export function GangMemberUpgradePopup(props: IProps): React.ReactElement { const setRerender = useState(false)[1]; const [filter, setFilter] = useState(""); - function closePopup(): void { + function closePopup(this: Window, ev: KeyboardEvent): void { + if(ev.keyCode !== 27) return; removePopup(props.popupId); } useEffect(() => { - window.addEventListener('keydown', closePopup); + window.addEventListener<'keydown'>('keydown', closePopup); const id = setInterval(() => setRerender(old => !old), 1000); return () => { clearInterval(id); - window.removeEventListener('keydown', closePopup); + window.removeEventListener<'keydown'>('keydown', closePopup); } }, []); @@ -134,6 +137,6 @@ export function GangMemberUpgradePopup(props: IProps): React.ReactElement { Discount: -{numeralWrapper.formatPercentage(1 - 1 / props.gang.getDiscount())} You get a discount on equipment and upgrades based on your gang's respect and power. More respect and power leads to more discounts.

    - {props.gang.members.map((member: any) => )} + {props.gang.members.map((member: GangMember) => )} ); } diff --git a/src/Gang/ui/GangStats.tsx b/src/Gang/ui/GangStats.tsx index 7ed4eee09..e3d96db23 100644 --- a/src/Gang/ui/GangStats.tsx +++ b/src/Gang/ui/GangStats.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; import { Factions } from "../../Faction/Factions"; +import { Gang } from "../Gang"; import { formatNumber, @@ -14,7 +15,7 @@ import { createPopup, removePopup } from "../../ui/React/createPopup"; import { dialogBoxCreate } from "../../../utils/DialogBox"; interface IRecruitPopupProps { - gang: any; + gang: Gang; popupId: string; } @@ -45,12 +46,12 @@ function recruitPopup(props: IRecruitPopupProps): React.ReactElement { removePopup(props.popupId); } - function onKeyUp(event: any): void { + function onKeyUp(event: React.KeyboardEvent): void { if(event.keyCode === 13) recruit(); if(event.keyCode === 27) cancel(); } - function onChange(event: any): void { + function onChange(event: React.ChangeEvent): void { setName(event.target.value); } @@ -68,7 +69,7 @@ function recruitPopup(props: IRecruitPopupProps): React.ReactElement { } interface IProps { - gang: any; + gang: Gang; } function Recruitment(props: IProps): React.ReactElement { @@ -120,13 +121,6 @@ function BonusTime(props: IProps): React.ReactElement { } export function GangStats(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); - const territoryMult = AllGangs[props.gang.facName].territory * 100; let territoryStr; if (territoryMult <= 0) { diff --git a/src/Gang/ui/ManagementSubpage.tsx b/src/Gang/ui/ManagementSubpage.tsx index 528fd6515..b79c0979a 100644 --- a/src/Gang/ui/ManagementSubpage.tsx +++ b/src/Gang/ui/ManagementSubpage.tsx @@ -1,10 +1,11 @@ -import * as React from "react"; +import React, { useState, useEffect } from "react"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { GangStats } from "./GangStats"; +import { Gang } from "../Gang"; import { GangMemberList } from "./GangMemberList"; interface IProps { - gang: any; + gang: Gang; player: IPlayer; } diff --git a/src/Gang/ui/Panel1.tsx b/src/Gang/ui/Panel1.tsx index d08b03335..dcb6d5601 100644 --- a/src/Gang/ui/Panel1.tsx +++ b/src/Gang/ui/Panel1.tsx @@ -3,10 +3,12 @@ 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: any; - gang: any; + member: GangMember; + gang: Gang; popupId: string; } @@ -43,17 +45,11 @@ Charisma: +{numeralWrapper.formatPercentage(ascendBenefits.cha/100)}
    } interface IProps { - member: any; - gang: any; + member: GangMember; + gang: Gang; } export function Panel1(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); function ascend(): void { const popupId = `gang-management-ascend-member ${props.member.name}`; diff --git a/src/Gang/ui/Panel2.tsx b/src/Gang/ui/Panel2.tsx index c08144ea4..d87e1f73c 100644 --- a/src/Gang/ui/Panel2.tsx +++ b/src/Gang/ui/Panel2.tsx @@ -2,25 +2,23 @@ import React, { useState, useEffect } 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: any; - gang: any; + member: GangMember; + gang: Gang; + onTaskChange: () => void; } export function Panel2(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; const [currentTask, setCurrentTask] = useState(props.member.task); - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); - function onChange(event: React.ChangeEvent): void { const task = event.target.value; props.member.assignToTask(task); setCurrentTask(task); + props.onTaskChange(); } const tasks = props.gang.getAllTaskNames(); @@ -39,7 +37,7 @@ export function Panel2(props: IProps): React.ReactElement { id={`${props.member.name}-gang-member-task-selector`} value={currentTask}> - {tasks.map((task: any, i: number) => )} + {tasks.map((task: string, i: number) => )}
    {StatsTable(data, null)}
    ); diff --git a/src/Gang/ui/Panel3.tsx b/src/Gang/ui/Panel3.tsx index cd889b100..b7e33e742 100644 --- a/src/Gang/ui/Panel3.tsx +++ b/src/Gang/ui/Panel3.tsx @@ -1,20 +1,12 @@ import React, { useState, useEffect } from "react"; import { GangMemberTasks } from "../GangMemberTasks"; +import { GangMember } from "../GangMember"; interface IProps { - member: any; + member: GangMember; } export function Panel3(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - - useEffect(() => { - const id = setInterval(() => { - setRerender(old => !old); - }, 1000); - return () => clearInterval(id); - }, []); - const task = GangMemberTasks[props.member.task]; const desc = task ? task.desc: GangMemberTasks["Unassigned"].desc; diff --git a/src/Gang/ui/Root.tsx b/src/Gang/ui/Root.tsx index 02296884a..2859b41cf 100644 --- a/src/Gang/ui/Root.tsx +++ b/src/Gang/ui/Root.tsx @@ -1,18 +1,25 @@ -import React, { useState } from "react"; +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: any; + 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(); diff --git a/src/Gang/ui/TerritorySubpage.tsx b/src/Gang/ui/TerritorySubpage.tsx index cecbe0c26..f6af15d30 100644 --- a/src/Gang/ui/TerritorySubpage.tsx +++ b/src/Gang/ui/TerritorySubpage.tsx @@ -3,19 +3,13 @@ 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: any; + gang: Gang; } export function TerritorySubpage(props: IProps): React.ReactElement { - const setRerender = useState(false)[1]; - - useEffect(() => { - const id = setInterval(() => setRerender(old => !old), 1000); - return () => clearInterval(id); - }, []); - 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, " + From 97fdf7cb7fc93274476af093a912933cb44a7ff4 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Thu, 17 Jun 2021 17:37:05 -0400 Subject: [PATCH 13/24] Format/cleanup of Gang UI --- src/Gang/GangMemberUpgrade.ts | 17 ++- src/Gang/IAscensionResult.ts | 2 +- src/Gang/data/upgrades.ts | 74 ++++++----- src/Gang/ui/AscensionPopup.tsx | 43 +++++++ src/Gang/ui/BonusTime.tsx | 26 ++++ src/Gang/ui/GangMemberAccordion.tsx | 19 +++ src/Gang/ui/GangMemberAccordionContent.tsx | 15 ++- src/Gang/ui/GangMemberList.tsx | 37 +++--- .../ui/{Panel1.tsx => GangMemberStats.tsx} | 58 ++------- src/Gang/ui/GangMemberUpgradePopup.tsx | 73 ++++++----- src/Gang/ui/GangStats.tsx | 115 +----------------- src/Gang/ui/ManagementSubpage.tsx | 28 +++-- src/Gang/ui/RecruitButton.tsx | 47 +++++++ src/Gang/ui/RecruitPopup.tsx | 60 +++++++++ src/Gang/ui/Root.tsx | 15 ++- .../ui/{Panel3.tsx => TaskDescription.tsx} | 4 +- src/Gang/ui/{Panel2.tsx => TaskSelector.tsx} | 4 +- src/Gang/ui/TerritorySubpage.tsx | 42 +++++-- 18 files changed, 387 insertions(+), 292 deletions(-) create mode 100644 src/Gang/ui/AscensionPopup.tsx create mode 100644 src/Gang/ui/BonusTime.tsx create mode 100644 src/Gang/ui/GangMemberAccordion.tsx rename src/Gang/ui/{Panel1.tsx => GangMemberStats.tsx} (56%) create mode 100644 src/Gang/ui/RecruitButton.tsx create mode 100644 src/Gang/ui/RecruitPopup.tsx rename src/Gang/ui/{Panel3.tsx => TaskDescription.tsx} (80%) rename src/Gang/ui/{Panel2.tsx => TaskSelector.tsx} (93%) diff --git a/src/Gang/GangMemberUpgrade.ts b/src/Gang/GangMemberUpgrade.ts index 7cfd6c291..aa602bc13 100644 --- a/src/Gang/GangMemberUpgrade.ts +++ b/src/Gang/GangMemberUpgrade.ts @@ -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 ""; diff --git a/src/Gang/IAscensionResult.ts b/src/Gang/IAscensionResult.ts index f42f7c603..224573cd1 100644 --- a/src/Gang/IAscensionResult.ts +++ b/src/Gang/IAscensionResult.ts @@ -6,4 +6,4 @@ export interface IAscensionResult { dex: number; agi: number; cha: number; -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/Gang/data/upgrades.ts b/src/Gang/data/upgrades.ts index 73d0d52e0..7b4b80dd0 100644 --- a/src/Gang/data/upgrades.ts +++ b/src/Gang/data/upgrades.ts @@ -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, }, ]; diff --git a/src/Gang/ui/AscensionPopup.tsx b/src/Gang/ui/AscensionPopup.tsx new file mode 100644 index 000000000..622d85222 --- /dev/null +++ b/src/Gang/ui/AscensionPopup.tsx @@ -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 (<> +
    +Are you sure you want to ascend this member? They will lose all of
    +their non-Augmentation upgrades and their stats will reset back to 1.
    +
    +Furthermore, your gang will lose {numeralWrapper.formatRespect(props.member.earnedRespect)} respect
    +
    +In return, they will gain the following permanent boost to stat multipliers:
    +Hacking: +{numeralWrapper.formatPercentage(ascendBenefits.hack/100)}
    +Strength: +{numeralWrapper.formatPercentage(ascendBenefits.str/100)}
    +Defense: +{numeralWrapper.formatPercentage(ascendBenefits.def/100)}
    +Dexterity: +{numeralWrapper.formatPercentage(ascendBenefits.dex/100)}
    +Agility: +{numeralWrapper.formatPercentage(ascendBenefits.agi/100)}
    +Charisma: +{numeralWrapper.formatPercentage(ascendBenefits.cha/100)}
    +
    + + + ); +} \ No newline at end of file diff --git a/src/Gang/ui/BonusTime.tsx b/src/Gang/ui/BonusTime.tsx new file mode 100644 index 000000000..693964daf --- /dev/null +++ b/src/Gang/ui/BonusTime.tsx @@ -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 (<> +

    + Bonus time: {convertTimeMsToTimeElapsedString(bonusMillis)} + + 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. + +

    +
    + ); +} \ No newline at end of file diff --git a/src/Gang/ui/GangMemberAccordion.tsx b/src/Gang/ui/GangMemberAccordion.tsx new file mode 100644 index 000000000..e2daa32eb --- /dev/null +++ b/src/Gang/ui/GangMemberAccordion.tsx @@ -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 {props.member.name}} + panelContent={} /> +} \ No newline at end of file diff --git a/src/Gang/ui/GangMemberAccordionContent.tsx b/src/Gang/ui/GangMemberAccordionContent.tsx index 573f95738..4b0ca2d08 100644 --- a/src/Gang/ui/GangMemberAccordionContent.tsx +++ b/src/Gang/ui/GangMemberAccordionContent.tsx @@ -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 (<>
    - +
    - setRerender(old => !old)} gang={props.gang} member={props.member} /> + setRerender(old => !old)} + gang={props.gang} + member={props.member} />
    - +
    ); } diff --git a/src/Gang/ui/GangMemberList.tsx b/src/Gang/ui/GangMemberList.tsx index 3accc7163..a7eee3edd 100644 --- a/src/Gang/ui/GangMemberList.tsx +++ b/src/Gang/ui/GangMemberList.tsx @@ -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 (<> - - Manage Equipment + setRerender(old => !old)} + gang={props.gang} /> +
    + + Manage Equipment
      - {members().map((member: GangMember) =>
    • - {member.name}} - panelContent={} /> + {members.map((member: GangMember) =>
    • +
    • )}
    ); diff --git a/src/Gang/ui/Panel1.tsx b/src/Gang/ui/GangMemberStats.tsx similarity index 56% rename from src/Gang/ui/Panel1.tsx rename to src/Gang/ui/GangMemberStats.tsx index dcb6d5601..a47eda0da 100644 --- a/src/Gang/ui/Panel1.tsx +++ b/src/Gang/ui/GangMemberStats.tsx @@ -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 (<> -
    -Are you sure you want to ascend this member? They will lose all of
    -their non-Augmentation upgrades and their stats will reset back to 1.
    -
    -Furthermore, your gang will lose {numeralWrapper.formatRespect(props.member.earnedRespect)} respect
    -
    -In return, they will gain the following permanent boost to stat multipliers:
    -Hacking: +{numeralWrapper.formatPercentage(ascendBenefits.hack/100)}
    -Strength: +{numeralWrapper.formatPercentage(ascendBenefits.str/100)}
    -Defense: +{numeralWrapper.formatPercentage(ascendBenefits.def/100)}
    -Dexterity: +{numeralWrapper.formatPercentage(ascendBenefits.dex/100)}
    -Agility: +{numeralWrapper.formatPercentage(ascendBenefits.agi/100)}
    -Charisma: +{numeralWrapper.formatPercentage(ascendBenefits.cha/100)}
    -
    - - - ); -} +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)
    -        Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)
    - Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)
    - Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)
    - Dexterity: {formatNumber(props.member.dex, 0)} ({numeralWrapper.formatExp(props.member.dex_exp)} exp)
    - Agility: {formatNumber(props.member.agi, 0)} ({numeralWrapper.formatExp(props.member.agi_exp)} exp)
    - Charisma: {formatNumber(props.member.cha, 0)} ({numeralWrapper.formatExp(props.member.cha_exp)} exp)
    +Hacking: {formatNumber(props.member.hack, 0)} ({numeralWrapper.formatExp(props.member.hack_exp)} exp)
    +Strength: {formatNumber(props.member.str, 0)} ({numeralWrapper.formatExp(props.member.str_exp)} exp)
    +Defense: {formatNumber(props.member.def, 0)} ({numeralWrapper.formatExp(props.member.def_exp)} exp)
    +Dexterity: {formatNumber(props.member.dex, 0)} ({numeralWrapper.formatExp(props.member.dex_exp)} exp)
    +Agility: {formatNumber(props.member.agi, 0)} ({numeralWrapper.formatExp(props.member.agi_exp)} exp)
    +Charisma: {formatNumber(props.member.cha, 0)} ({numeralWrapper.formatExp(props.member.cha_exp)} exp)

    diff --git a/src/Gang/ui/GangMemberUpgradePopup.tsx b/src/Gang/ui/GangMemberUpgradePopup.tsx index bd7b80128..a1ae8a9d0 100644 --- a/src/Gang/ui/GangMemberUpgradePopup.tsx +++ b/src/Gang/ui/GangMemberUpgradePopup.tsx @@ -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 (
    + function purchased(upgName: string): React.ReactElement { + const upg = GangMemberUpgrades[upgName] + return (
    {upg.name}
    ); } - 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 (<> - setFilter(event.target.value)} /> + setFilter(event.target.value)} />

    Discount: -{numeralWrapper.formatPercentage(1 - 1 / props.gang.getDiscount())} - You get a discount on equipment and upgrades based on your gang's respect and power. More respect and power leads to more discounts. + + You get a discount on equipment and upgrades based on your + gang's respect and power. More respect and power leads to more + discounts. +

    - {props.gang.members.map((member: GangMember) => )} + {props.gang.members.map((member: GangMember) => + ) + } ); } diff --git a/src/Gang/ui/GangStats.tsx b/src/Gang/ui/GangStats.tsx index e3d96db23..1037ceff0 100644 --- a/src/Gang/ui/GangStats.tsx +++ b/src/Gang/ui/GangStats.tsx @@ -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): void { - if(event.keyCode === 13) recruit(); - if(event.keyCode === 27) cancel(); - } - - function onChange(event: React.ChangeEvent): void { - setName(event.target.value); - } - - return (<> -

    Enter a name for your new Gang member:


    - - Recruit Gang Member - Cancel - ); -} +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 (<> - - Recruit Gang Member - - ); - } - return (<> - - Recruit Gang Member - -

    - {formatNumber(respectCost, 2)} respect needed to recruit next member -

    - ); -} - -function BonusTime(props: IProps): React.ReactElement { - const CyclesPerSecond = 1000 / 200; - if (props.gang.storedCycles / CyclesPerSecond*1000 <= 5000) return <>; - return (<> -

    - Bonus time: {convertTimeMsToTimeElapsedString(props.gang.storedCycles / CyclesPerSecond*1000)} - - 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 - -

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


    -
    - ); } \ No newline at end of file diff --git a/src/Gang/ui/ManagementSubpage.tsx b/src/Gang/ui/ManagementSubpage.tsx index b79c0979a..8ea57ad11 100644 --- a/src/Gang/ui/ManagementSubpage.tsx +++ b/src/Gang/ui/ManagementSubpage.tsx @@ -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 (

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

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

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

    - 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


    diff --git a/src/Gang/ui/RecruitButton.tsx b/src/Gang/ui/RecruitButton.tsx new file mode 100644 index 000000000..0f9b1c903 --- /dev/null +++ b/src/Gang/ui/RecruitButton.tsx @@ -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 (<> + + Recruit Gang Member + +

    + {formatNumber(respect, 2)} respect needed to recruit next member +

    + ); + } + + function onClick(): void { + const popupId = "recruit-gang-member-popup"; + createPopup(popupId, RecruitPopup, { + gang: props.gang, + popupId: popupId, + onRecruit: props.onRecruit, + }); + } + + return (<> + + Recruit Gang Member + + ); +} \ No newline at end of file diff --git a/src/Gang/ui/RecruitPopup.tsx b/src/Gang/ui/RecruitPopup.tsx new file mode 100644 index 000000000..ba328e5c3 --- /dev/null +++ b/src/Gang/ui/RecruitPopup.tsx @@ -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): void { + if(event.keyCode === 13) recruit(); + if(event.keyCode === 27) cancel(); + } + + function onChange(event: React.ChangeEvent): void { + setName(event.target.value); + } + + return (<> +

    Enter a name for your new Gang member:


    + + Recruit Gang Member + Cancel + ); +} \ No newline at end of file diff --git a/src/Gang/ui/Root.tsx b/src/Gang/ui/Root.tsx index 2859b41cf..906bf9acc 100644 --- a/src/Gang/ui/Root.tsx +++ b/src/Gang/ui/Root.tsx @@ -27,9 +27,18 @@ export function Root(props: IProps): React.ReactElement { } return (<> - Back - setManagement(true)}>Gang Management - setManagement(false)}>Gang Territory + Back + setManagement(true)}> + Gang Management + + setManagement(false)}> + Gang Territory + {management ? : } diff --git a/src/Gang/ui/Panel3.tsx b/src/Gang/ui/TaskDescription.tsx similarity index 80% rename from src/Gang/ui/Panel3.tsx rename to src/Gang/ui/TaskDescription.tsx index b7e33e742..82715b17a 100644 --- a/src/Gang/ui/Panel3.tsx +++ b/src/Gang/ui/TaskDescription.tsx @@ -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; diff --git a/src/Gang/ui/Panel2.tsx b/src/Gang/ui/TaskSelector.tsx similarity index 93% rename from src/Gang/ui/Panel2.tsx rename to src/Gang/ui/TaskSelector.tsx index d87e1f73c..105e21dc7 100644 --- a/src/Gang/ui/Panel2.tsx +++ b/src/Gang/ui/TaskSelector.tsx @@ -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): void { diff --git a/src/Gang/ui/TerritorySubpage.tsx b/src/Gang/ui/TerritorySubpage.tsx index f6af15d30..d5ea8df91 100644 --- a/src/Gang/ui/TerritorySubpage.tsx +++ b/src/Gang/ui/TerritorySubpage.tsx @@ -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 ( {name}
    Power: {formatNumber(power, 6)}
    - Territory: {formatTerritoryP(AllGangs[name].territory)}%
    + Territory: {formatTerritory(AllGangs[name].territory)}%
    Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}

    ); @@ -72,8 +72,17 @@ export function TerritorySubpage(props: IProps): React.ReactElement {

    - props.gang.territoryWarfareEngaged = event.target.checked}/> -