Mostly done converting Gang UI to React

This commit is contained in:
Olivier Gagnon 2021-06-16 00:28:20 -04:00
parent 9466017906
commit 9e345b1375
16 changed files with 664 additions and 929 deletions

@ -49,11 +49,10 @@ import { GangConstants } from "./Gang/data/Constants";
import { GangMemberTasks } from "./Gang/GangMemberTasks"; import { GangMemberTasks } from "./Gang/GangMemberTasks";
import { GangMemberTask } from "./Gang/GangMemberTask"; import { GangMemberTask } from "./Gang/GangMemberTask";
import { Panel1 } from "./Gang/ui/Panel1"; import { ManagementSubpage } from "./Gang/ui/ManagementSubpage";
import { Panel2 } from "./Gang/ui/Panel2"; import { TerritorySubpage } from "./Gang/ui/TerritorySubpage";
import { Panel3 } from "./Gang/ui/Panel3"; import { GangStats } from "./Gang/ui/GangStats";
import { GangMemberAccordionContent } from "./Gang/ui/GangMemberAccordionContent"; import { AllGangs } from "./Gang/AllGangs";
import { GangMemberAccordion } from "./Gang/ui/GangMemberAccordion";
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; 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 = [ const GangNames = [
"Slum Snakes", "Slum Snakes",
@ -100,74 +85,6 @@ const GangNames = [
"The Black Hand", "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 facName {string} Name of corresponding faction
* @param hacking {bollean} Whether or not its a hacking gang * @param hacking {bollean} Whether or not its a hacking gang
@ -412,10 +329,6 @@ Gang.prototype.recruitMember = function(name) {
let member = new GangMember(name); let member = new GangMember(name);
this.members.push(member); this.members.push(member);
if (routing.isOn(Page.Gang)) {
this.createGangMemberDisplayElement(member);
this.updateGangContent();
}
return true; return true;
} }
@ -488,10 +401,6 @@ Gang.prototype.killMember = function(memberObj) {
dialogBoxCreate(`${memberObj.name} was killed in a gang clash! You lost ${lostRespect} respect`); 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) { Gang.prototype.ascendMember = function(memberObj, workerScript) {
@ -520,9 +429,6 @@ Gang.prototype.ascendMember = function(memberObj, workerScript) {
} else { } else {
workerScript.log(`Ascended Gang member ${memberObj.name}`); workerScript.log(`Ascended Gang member ${memberObj.name}`);
} }
if (routing.isOn(Page.Gang)) {
this.displayGangMemberList();
}
return res; return res;
} catch(e) { } catch(e) {
if (workerScript == null) { if (workerScript == null) {
@ -884,10 +790,6 @@ GangMember.prototype.buyUpgrade = function(upg, player, gang) {
this.upgrades.push(upg.name); this.upgrades.push(upg.name);
} }
upg.apply(this); upg.apply(this);
if (routing.isOn(Page.Gang) && UIElems.gangMemberUpgradeBoxOpened) {
var initFilterValue = UIElems.gangMemberUpgradeBoxFilter.value.toString();
gang.createGangMemberUpgradeBox(player, initFilterValue);
}
return true; return true;
} }
@ -901,197 +803,6 @@ GangMember.fromJSON = function(value) {
Reviver.constructors.GangMember = GangMember; 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 // Gang UI Dom Elements
const UIElems = { const UIElems = {
// Main elems // Main elems
@ -1105,33 +816,8 @@ const UIElems = {
gangTerritorySubpage: null, gangTerritorySubpage: null,
// Gang Management Subpage Elements // Gang Management Subpage Elements
gangDesc: null,
gangInfo: null,
gangRecruitMemberButton: null,
gangRecruitRequirementText: null,
gangExpandAllButton: null,
gangCollapseAllButton: null,
gangMemberFilter: null, gangMemberFilter: null,
gangManageEquipmentButton: null,
gangMemberList: null,
gangMemberPanels: {}, 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() { export function unmount() {
@ -1174,7 +860,6 @@ Gang.prototype.displayGangContent = function(player) {
UIElems.managementButton.classList.toggle("a-link-button"); UIElems.managementButton.classList.toggle("a-link-button");
UIElems.territoryButton.classList.toggle("a-link-button-inactive"); UIElems.territoryButton.classList.toggle("a-link-button-inactive");
UIElems.territoryButton.classList.toggle("a-link-button"); UIElems.territoryButton.classList.toggle("a-link-button");
this.updateGangContent();
return false; return false;
}, },
}) })
@ -1188,7 +873,6 @@ Gang.prototype.displayGangContent = function(player) {
UIElems.managementButton.classList.toggle("a-link-button"); UIElems.managementButton.classList.toggle("a-link-button");
UIElems.territoryButton.classList.toggle("a-link-button-inactive"); UIElems.territoryButton.classList.toggle("a-link-button-inactive");
UIElems.territoryButton.classList.toggle("a-link-button"); UIElems.territoryButton.classList.toggle("a-link-button");
this.updateGangContent();
return false; return false;
}, },
}); });
@ -1196,459 +880,31 @@ Gang.prototype.displayGangContent = function(player) {
UIElems.gangContainer.appendChild(UIElems.territoryButton); UIElems.gangContainer.appendChild(UIElems.territoryButton);
// Subpage for managing gang members // Subpage for managing gang members
UIElems.gangManagementSubpage = createElement("div", { UIElems.gangManagementSubpage = createElement("div");
display:"block", id:"gang-management-subpage", UIElems.gangContainer.appendChild(UIElems.gangManagementSubpage);
}); ReactDOM.render(<ManagementSubpage gang={this} player={player} />, UIElems.gangManagementSubpage);
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.<br><br>" +
"If a gang member is not earning much money or respect, the task that you " +
"have assigned to that member might be too difficult. Consider training that " +
"member's stats or choosing an easier task. The tasks closer to the " +
"top of the dropdown list are generally easier. Alternatively, the gang member's " +
"low production might be due to the fact that your wanted level is too high. " +
"Consider assigning a few members to the '" + lowerWantedTask + "' " +
"task to lower your wanted level. <br><br>" +
"Installing Augmentations does NOT reset your progress with your Gang. " +
"Furthermore, after installing Augmentations, you will " +
"automatically be a member of whatever Faction you created your gang with.<br><br>" +
"You can also manage your gang programmatically through Netscript using the Gang API",
});
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);
// Subpage for seeing gang territory information // Subpage for seeing gang territory information
UIElems.gangTerritorySubpage = createElement("div", { UIElems.gangTerritorySubpage = createElement("div", {
id:"gang-territory-subpage", display:"none", id:"gang-territory-subpage", display:"none",
}); });
// Info text for territory page ReactDOM.render(<TerritorySubpage gang={this} player={player} />, UIElems.gangTerritorySubpage);
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.<br><br>" +
"Every ~20 seconds, your gang has a chance to 'clash' with other gangs. Your chance " +
"to win a clash depends on your gang's power, which is listed in the display below. " +
"Your gang's power slowly accumulates over time. The accumulation rate is determined by the stats " +
"of all Gang members you have assigned to the 'Territory Warfare' task. Gang members that are not " +
"assigned to this task do not contribute to your gang's power. Your gang also loses a small amount " +
"of power whenever you lose a clash<br><br>" +
"NOTE: Gang members assigned to 'Territory Warfare' can be killed during clashes. This can happen regardless of whether you win " +
"or lose the clash. A gang member being killed results in both respect and power loss for your gang.<br><br>" +
"The amount of territory you have affects all aspects of your Gang members' production, including " +
"money, respect, and wanted level. It is very beneficial to have high territory control.<br><br>",
});
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);
UIElems.gangContainer.appendChild(UIElems.gangTerritorySubpage); UIElems.gangContainer.appendChild(UIElems.gangTerritorySubpage);
UIElems.gangContainer.appendChild(UIElems.gangManagementSubpage);
document.getElementById("entire-game-container").appendChild(UIElems.gangContainer); document.getElementById("entire-game-container").appendChild(UIElems.gangContainer);
} }
UIElems.gangContainer.style.display = "block"; 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 = `<b><u>${gangname}</u></b><br>Power: ${formatNumber(gangTerritoryInfo.power, 6)}<br>`;
newHTML += `Territory: ${displayNumber}%<br><br>`;
UIElems.gangTerritoryInfoText.innerHTML += newHTML;
} else {
const clashVictoryChance = playerPower / (gangTerritoryInfo.power + playerPower);
let newHTML = `<u>${gangname}</u><br>Power: ${formatNumber(gangTerritoryInfo.power, 6)}<br>`;
newHTML += `Territory: ${displayNumber}%<br>`;
newHTML += `Chance to win clash with this gang: ${numeralWrapper.formatPercentage(clashVictoryChance, 3)}<br><br>`;
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(<p style={{'display': 'inline-block'}}>Money gain rate: {MoneyRate(5 * this.moneyGainRate)}</p>, 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(<p style={{'display': 'inline-block'}}>Faction reputation: {Reputation(rep)}</p>, 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(<GangMemberAccordion gang={this} member={memberObj} />, li);
UIElems.gangMemberPanels[name] = li;
UIElems.gangMemberList.appendChild(li);
} }
Gang.prototype.clearUI = function() { Gang.prototype.clearUI = function() {
if (UIElems.gangContainer instanceof Element) { removeElement(UIElems.gangContainer); } if (UIElems.gangContainer instanceof Element) { removeElement(UIElems.gangContainer); }
if (UIElems.gangMemberUpgradeBox instanceof Element) { removeElement(UIElems.gangMemberUpgradeBox); }
for (const prop in UIElems) { for (const prop in UIElems) {
UIElems[prop] = null; UIElems[prop] = null;
} }
UIElems.gangContentCreated = false; UIElems.gangContentCreated = false;
UIElems.gangMemberUpgradeBoxOpened = false;
UIElems.gangMemberPanels = {}; UIElems.gangMemberPanels = {};
} }

76
src/Gang/AllGangs.ts Normal file

@ -0,0 +1,76 @@
import { Reviver } from "../../utils/JSONReviver";
interface GangTerritory {
power: number;
territory: number;
}
export let AllGangs: {
[key: string]: GangTerritory;
} = {
"Slum Snakes" : {
power: 1,
territory: 1/7,
},
"Tetrads" : {
power: 1,
territory: 1/7,
},
"The Syndicate" : {
power: 1,
territory: 1/7,
},
"The Dark Army" : {
power: 1,
territory: 1/7,
},
"Speakers for the Dead" : {
power: 1,
territory: 1/7,
},
"NiteSec" : {
power: 1,
territory: 1/7,
},
"The Black Hand" : {
power: 1,
territory: 1/7,
},
}
export function resetGangs() {
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);
}

@ -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 (<Accordion
headerContent={<>{props.member.name}</>}
panelContent={<GangMemberAccordionContent gang={props.gang} member={props.member} />}
/>);
}

@ -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<HTMLInputElement>): 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 (<>
<input className="text-input" placeholder="Filter gang member" style={{margin: "5px", padding: "5px"}} value={filter} onChange={onChange} />
<a className="a-link-button" style={{display: 'inline-block'}} onClick={openUpgradePopup}>Manage Equipment</a>
<ul>
{members().map((member: any, i : number) => <li key={member.name}>
<Accordion
panelInitiallyOpened={true}
headerContent={<>{member.name}</>}
panelContent={<GangMemberAccordionContent gang={props.gang} member={member} />} />
</li>)}
</ul>
</>);
}

@ -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 (<div key={name} className="gang-owned-upgrade tooltip">
{upg.name}
<span className="tooltiptext" dangerouslySetInnerHTML={{__html: upg.desc}} />
</div>);
}
function upgradeButton(upg: GangMemberUpgrade, left: boolean = false): React.ReactElement {
function onClick(): void {
props.member.buyUpgrade(upg, props.player, props.gang);
setRerender(old => !old);
}
return (<a key={upg.name} className="a-link-button tooltip" style={{margin:"2px", padding:"2px", display:"block", fontSize:"11px"}} onClick={onClick}>
{upg.name} - {Money(upg.getCost(props.gang))}
<span className={left?"tooltiptextleft":"tooltiptext"} dangerouslySetInnerHTML={{__html: upg.desc}} />
</a>);
}
return (<div style={{border: '1px solid white'}}>
<h1>{props.member.name}({props.member.task})</h1>
<pre style={{fontSize:"14px", display: "inline-block", width:"20%"}}>
Hack: {props.member.hack} (x{formatNumber(props.member.hack_mult * props.member.hack_asc_mult, 2)})<br />
Str: {props.member.str} (x{formatNumber(props.member.str_mult * props.member.str_asc_mult, 2)})<br />
Def: {props.member.def} (x{formatNumber(props.member.def_mult * props.member.def_asc_mult, 2)})<br />
Dex: {props.member.dex} (x{formatNumber(props.member.dex_mult * props.member.dex_asc_mult, 2)})<br />
Agi: {props.member.agi} (x{formatNumber(props.member.agi_mult * props.member.agi_asc_mult, 2)})<br />
Cha: {props.member.cha} (x{formatNumber(props.member.cha_mult * props.member.cha_asc_mult, 2)})
</pre>
<div className="gang-owned-upgrades-div">
Purchased Upgrades: {props.member.upgrades.map((upg: any) => purchased(upg))}
{props.member.augmentations.map((upg: any) => purchased(upg))}
</div>
<div style={{width: "20%", display: "inline-block"}}>
<h2>Weapons</h2>
{weaponUpgrades.map(upg => upgradeButton(upg))}
</div>
<div style={{width: "20%", display: "inline-block"}}>
<h2>Armor</h2>
{armorUpgrades.map(upg => upgradeButton(upg))}
</div>
<div style={{width: "20%", display: "inline-block"}}>
<h2>Vehicles</h2>
{vehicleUpgrades.map(upg => upgradeButton(upg))}
</div>
<div style={{width: "20%", display: "inline-block"}}>
<h2>Rootkits</h2>
{rootkitUpgrades.map(upg => upgradeButton(upg, true))}
</div>
<div style={{width: "20%", display: "inline-block"}}>
<h2>Augmentations</h2>
{augUpgrades.map(upg => upgradeButton(upg, true))}
</div>
</div>);
}
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 (<>
<input className="text-input" value={filter} placeholder="Filter gang member" onChange={event => setFilter(event.target.value)} />
<p className="tooltip" style={{marginLeft: '6px', display: 'inline-block'}}>
Discount: -{numeralWrapper.formatPercentage(1 - 1 / props.gang.getDiscount())}
<span className="tooltiptext">You get a discount on equipment and upgrades based on your gang's respect and power. More respect and power leads to more discounts.</span>
</p>
{props.gang.members.map((member: any) => <GangMemberUpgradePanel key={member.name} player={props.player} gang={props.gang} member={member} />)}
</>);
}
/*
// 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)
*/

@ -1,4 +1,123 @@
import React, { useState, useEffect } from "react"; 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 (<>
<p>Enter a name for your new Gang member:</p><br />
<input autoFocus
onKeyUp={onKeyUp}
onChange={onChange}
className="text-input"
type="text"
placeholder="unique name" />
<a className="std-button" onClick={recruit}>Recruit Gang Member</a>
<a className="std-button" onClick={cancel}>Cancel</a>
</>);
}
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 (<>
<a className="a-link-button"
onClick={onClick}
style={{display: 'inline-block', margin: '10px'}}>
Recruit Gang Member
</a>
</>);
}
return (<>
<a className="a-link-button-inactive"
style={{display: 'inline-block', margin: '10px'}}>
Recruit Gang Member
</a>
<p style={{margin: '10px', color: 'red', display: 'inline-block'}}>
{formatNumber(respectCost, 2)} respect needed to recruit next member
</p>
</>);
}
function BonusTime(props: IProps): React.ReactElement {
const CyclesPerSecond = 1000 / 200;
if (props.gang.storedCycles / CyclesPerSecond*1000 <= 5000) return <></>;
return (<>
<p className="tooltip" style={{display: "inline-block"}}>
Bonus time: {convertTimeMsToTimeElapsedString(props.gang.storedCycles / CyclesPerSecond*1000)}
<span className="tooltiptext">
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by the browser). Bonus time makes the Gang mechanic progress faster, up to 5x the normal speed
</span>
</p>
<br />
</>);
}
export function GangStats(props: IProps): React.ReactElement { export function GangStats(props: IProps): React.ReactElement {
const [rerender, setRerender] = useState(false); const [rerender, setRerender] = useState(false);
@ -8,157 +127,57 @@ export function GangStats(props: IProps): React.ReactElement {
return () => clearInterval(id); 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 (<p id="gang-info" style="width: 70%;"> return (<>
<p class=" tooltip" style="display: inline-block;"> <p className="tooltip" style={{display: "inline-block"}}>
Respect: 108.82214 (0.23534 / sec) Respect: {numeralWrapper.formatRespect(props.gang.respect)} ({numeralWrapper.formatRespect(5*props.gang.respectGainRate)} / sec)
<span class="tooltiptext"> <span className="tooltiptext">
Represents the amount of respect your gang has from other gangs and criminal organizations. Your respect affects the amount of money your gang members will earn, and also determines how much reputation you are earning with your gang's corresponding Faction. Represents the amount of respect your gang has from other gangs and criminal organizations. Your respect affects the amount of money your gang members will earn, and also determines how much reputation you are earning with your gang's corresponding Faction.
</span> </span>
</p> </p>
<br /> <br />
<p class=" tooltip" style="display: inline-block;"> <p className="tooltip" style={{display: "inline-block"}}>
Wanted Level: 1.37503 (0.00002 / sec) Wanted Level: {numeralWrapper.formatWanted(props.gang.wanted)} ({numeralWrapper.formatWanted(5*props.gang.wantedGainRate)} / sec)
<span class="tooltiptext"> <span className="tooltiptext">
Represents how much the gang is wanted by law enforcement. The higher your gang's wanted level, the harder it will be for your gang members to make money and earn respect. Note that the minimum wanted level is 1. Represents how much the gang is wanted by law enforcement. The higher your gang's wanted level, the harder it will be for your gang members to make money and earn respect. Note that the minimum wanted level is 1.
</span> </span>
</p> </p>
<br /> <br />
<p class=" tooltip" style="display: inline-block;"> <p className="tooltip" style={{display: "inline-block"}}>
Wanted Level Penalty: -1.25% Wanted Level Penalty: -{formatNumber((1 - props.gang.getWantedPenalty()) * 100, 2)}%
<span class="tooltiptext"> <span className="tooltiptext">
Penalty for respect and money gain rates due to Wanted Level Penalty for respect and money gain rates due to Wanted Level
</span> </span>
</p> </p>
<br /> <br />
<div> <div>
<p style="display: inline-block;"> <p style={{display: "inline-block"}}>
Money gain rate: Money gain rate: {MoneyRate(5 * props.gang.moneyGainRate)}
<span class="money-gold samefont">
$2.571k / sec
</span>
</p> </p>
</div> </div>
<br /> <br />
<p class=" tooltip" style="display: inline-block;"> <p className="tooltip" style={{display: "inline-block"}}>
Territory: 14.29% Territory: {territoryStr}%
<span class="tooltiptext"> <span className="tooltiptext">
The percentage of total territory your Gang controls The percentage of total territory your Gang controls
</span> </span>
</p> </p>
<br /> <br />
<div> <p style={{display: "inline-block"}}>
<p style="display: inline-block;"> Faction reputation: {Reputation(Factions[props.gang.facName].playerReputation)}
Faction reputation:
<span class="reputation samefont">
28.677
</span>
</p>
</div>
<br />
<p class=" tooltip" style="display: inline-block;">
Bonus time: 1 hours 30 minutes 58 seconds
<span class="tooltiptext">
You gain bonus time while offline or when the game is inactive (e.g. when the tab is throttled by the browser). Bonus time makes the Gang mechanic progress faster, up to 5x the normal speed
</span>
</p> </p>
<br /> <br />
</p>); <BonusTime gang={props.gang} />
<br />
<Recruitment gang={props.gang} />
</>);
} }
/*
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(<p style={{'display': 'inline-block'}}>Money gain rate: {MoneyRate(5 * this.moneyGainRate)}</p>, 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(<p style={{'display': 'inline-block'}}>Faction reputation: {Reputation(rep)}</p>, 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`;
}
*/

@ -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 (<div style={{display: 'block'}}>
<p style={{width: "70%"}}>
This page is used to manage your gang members and get an overview of your gang's stats.
<br />
<br />
If a gang member is not earning much money or respect, the task that you
have assigned to that member might be too difficult. Consider training that
member's stats or choosing an easier task. The tasks closer to the
top of the dropdown list are generally easier. Alternatively, the gang member's
low production might be due to the fact that your wanted level is too high.
Consider assigning a few members to the '{props.gang.isHackingGang?"Ethical Hacking":"Vigilante Justice"}'
task to lower your wanted level.
<br />
<br />
Installing Augmentations does NOT reset your progress with your Gang.
Furthermore, after installing Augmentations, you will
automatically be a member of whatever Faction you created your gang with.
<br />
<br />
You can also manage your gang programmatically through Netscript using the Gang API
</p>
<br />
<GangStats gang={props.gang} />
<br />
<GangMemberList gang={props.gang} player={props.player} />
</div>);
}

@ -13,7 +13,6 @@ interface IAscendProps {
function ascendPopup(props: IAscendProps): React.ReactElement { function ascendPopup(props: IAscendProps): React.ReactElement {
function confirm() { function confirm() {
props.gang.ascendMember(props.member); props.gang.ascendMember(props.member);
props.gang.updateGangMemberDisplayElement(props.member);
removePopup(props.popupId); removePopup(props.popupId);
return false; return false;
} }

@ -20,7 +20,6 @@ export function Panel2(props: IProps): React.ReactElement {
function onChange(event: React.ChangeEvent<HTMLSelectElement>): void { function onChange(event: React.ChangeEvent<HTMLSelectElement>): void {
const task = event.target.value; const task = event.target.value;
props.member.assignToTask(task); props.member.assignToTask(task);
props.gang.updateGangContent();
setCurrentTask(task); setCurrentTask(task);
} }

@ -11,7 +11,6 @@ export function Panel3(props: IProps): React.ReactElement {
useEffect(() => { useEffect(() => {
const id = setInterval(() => { const id = setInterval(() => {
setRerender(old => !old); setRerender(old => !old);
console.log('render');
}, 1000); }, 1000);
return () => clearInterval(id); return () => clearInterval(id);
}, []); }, []);

@ -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 (<span key={name}>
<u>{name}</u><br />
Power: {formatNumber(power, 6)}<br />
Territory: {formatTerritoryP(AllGangs[name].territory)}%<br />
Chance to win clash with this gang: {numeralWrapper.formatPercentage(clashVictoryChance, 3)}<br />
<br />
</span>);
}
const gangNames = Object.keys(AllGangs).filter(g => g != props.gang.facName);
return (<div style={{width: '70%'}}>
<p>
This page shows how much territory your Gang controls. This
statistic is listed as a percentage, which represents how much of
the total territory you control.
<br />
<br />
Every ~20 seconds, your gang has a chance to 'clash' with other
gangs. Your chance to win a clash depends on your gang's power,
which is listed in the display below. Your gang's power slowly
accumulates over time. The accumulation rate is determined by the
stats of all Gang members you have assigned to the 'Territory
Warfare' task. Gang members that are not assigned to this task do
not contribute to your gang's power. Your gang also loses a small
amount of power whenever you lose a clash.
<br />
<br />
NOTE: Gang members assigned to 'Territory Warfare' can be killed
during clashes. This can happen regardless of whether you win or
lose the clash. A gang member being killed results in both respect
and power loss for your gang.
<br />
<br />
The amount of territory you have affects all aspects of your Gang
members' production, including money, respect, and wanted level. It
is very beneficial to have high territory control.
<br />
<br />
</p>
<input checked={props.gang.territoryWarfareEngaged} id="warfare" type="checkbox" style={{display: "inline-block", margin: "2px"}} onChange={(event)=> props.gang.territoryWarfareEngaged = event.target.checked}/>
<label htmlFor="warfare" className="tooltip" style={{color: "white", display: 'inline-block'}}>
Engage in Territory Warfare
<span className="tooltiptext" style={{display: "inline-block"}}>
Engaging in Territory Warfare sets your clash chance to 100%.
Disengaging will cause your clash chance to gradually decrease
until it reaches 0%.
</span>
</label>
<br />
<p style={{display: 'inline-block'}}>
Territory Clash Chance: {numeralWrapper.formatPercentage(props.gang.territoryClashChance, 3)}
</p>
<div className="help-tip" style={{display: "inline-block"}} onClick={openWarfareHelp}>?</div>
<br />
<input checked={props.gang.notifyMemberDeath} id="notify" type="checkbox" style={{display: "inline-block", margin: "2px"}} onChange={(event)=> props.gang.notifyMemberDeath = event.target.checked}/>
<label htmlFor="warfare" className="tooltip" style={{color: "white", display: 'inline-block'}}>
Notify about Gang Member Deaths
<span className="tooltiptext" style={{display: "inline-block"}}>
If this is enabled, then you will receive a pop-up notifying you
whenever one of your Gang Members dies in a territory clash.
</span>
</label>
<br />
<fieldset style={{display: "block", margin: "6px"}}>
<p>
<b><u>{props.gang.facName}</u></b><br />
Power: {formatNumber(AllGangs[props.gang.facName].power, 6)}<br />
Territory: {formatTerritoryP(AllGangs[props.gang.facName].territory)}%<br />
<br />
{gangNames.map(name => otherGangTerritory(name))}
</p>
</fieldset>
</div>);
}
/*
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 = `<b><u>${gangname}</u></b><br>Power: ${formatNumber(gangTerritoryInfo.power, 6)}<br>`;
newHTML += `Territory: ${displayNumber}%<br><br>`;
UIElems.gangTerritoryInfoText.innerHTML += newHTML;
} else {
const clashVictoryChance = playerPower / (gangTerritoryInfo.power + playerPower);
let newHTML = `<u>${gangname}</u><br>Power: ${formatNumber(gangTerritoryInfo.power, 6)}<br>`;
newHTML += `Territory: ${displayNumber}%<br>`;
newHTML += `Chance to win clash with this gang: ${numeralWrapper.formatPercentage(clashVictoryChance, 3)}<br><br>`;
UIElems.gangTerritoryInfoText.innerHTML += newHTML;
}
}
}
*/

@ -29,10 +29,8 @@ import {
calculateWeakenTime, calculateWeakenTime,
} from "./Hacking"; } from "./Hacking";
import { calculateServerGrowth } from "./Server/formulas/grow"; import { calculateServerGrowth } from "./Server/formulas/grow";
import { import { Gang } from "./Gang";
AllGangs, import { AllGangs } from "./Gang/AllGangs";
Gang,
} from "./Gang";
import { GangMemberTasks } from "./Gang/GangMemberTasks"; import { GangMemberTasks } from "./Gang/GangMemberTasks";
import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades"; import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades";
import { Factions, factionExists } from "./Faction/Factions"; import { Factions, factionExists } from "./Faction/Factions";

@ -18,7 +18,7 @@ import { Engine } from "../../engine";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
import { displayFactionContent } from "../../Faction/FactionHelpers"; import { displayFactionContent } from "../../Faction/FactionHelpers";
import { resetGangs } from "../../Gang"; import { resetGangs } from "../../Gang/AllGangs";
import { hasHacknetServers } from "../../Hacknet/HacknetHelpers"; import { hasHacknetServers } from "../../Hacknet/HacknetHelpers";
import { Cities } from "../../Locations/Cities"; import { Cities } from "../../Locations/Cities";
import { Locations } from "../../Locations/Locations"; import { Locations } from "../../Locations/Locations";

@ -10,7 +10,7 @@ import { Engine } from "./engine";
import { Factions, loadFactions } from "./Faction/Factions"; import { Factions, loadFactions } from "./Faction/Factions";
import { loadFconf } from "./Fconf/Fconf"; import { loadFconf } from "./Fconf/Fconf";
import { FconfSettings } from "./Fconf/FconfSettings"; import { FconfSettings } from "./Fconf/FconfSettings";
import { loadAllGangs, AllGangs } from "./Gang"; import { loadAllGangs, AllGangs } from "./Gang/AllGangs";
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers"; import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
import { Player, loadPlayer } from "./Player"; import { Player, loadPlayer } from "./Player";
import { AllServers, loadAllServers } from "./Server/AllServers"; import { AllServers, loadAllServers } from "./Server/AllServers";

@ -868,9 +868,7 @@ const Engine = {
} }
if (Engine.Counters.updateDisplaysLong <= 0) { if (Engine.Counters.updateDisplaysLong <= 0) {
if (routing.isOn(Page.Gang) && Player.inGang()) { if (routing.isOn(Page.ScriptEditor)) {
Player.gang.updateGangContent();
} else if (routing.isOn(Page.ScriptEditor)) {
updateScriptEditorContent(); updateScriptEditorContent();
} }
Engine.Counters.updateDisplaysLong = 15; Engine.Counters.updateDisplaysLong = 15;

@ -23,27 +23,14 @@ export class Accordion extends React.Component<IProps, IState> {
this.handleHeaderClick = this.handleHeaderClick.bind(this); this.handleHeaderClick = this.handleHeaderClick.bind(this);
this.state = { this.state = {
panelOpened: props.panelInitiallyOpened ? true : false, panelOpened: props.panelInitiallyOpened ? props.panelInitiallyOpened : false,
} }
} }
handleHeaderClick(e: React.MouseEvent<HTMLButtonElement>): void { handleHeaderClick(e: React.MouseEvent<HTMLButtonElement>): void {
const elem = e.currentTarget;
elem.classList.toggle("active");
const panel: HTMLElement = elem.nextElementSibling as HTMLElement;
const active = elem.classList.contains("active");
if (active) {
panel.style.display = "block";
this.setState({ this.setState({
panelOpened: true, panelOpened: !this.state.panelOpened,
}); });
} else {
panel.style.display = "none";
this.setState({
panelOpened: false,
});
}
} }
render(): React.ReactNode { render(): React.ReactNode {
@ -52,6 +39,8 @@ export class Accordion extends React.Component<IProps, IState> {
className = this.props.headerClass; className = this.props.headerClass;
} }
if(this.state.panelOpened) className += " active"
return ( return (
<> <>
<button className={className} onClick={this.handleHeaderClick}> <button className={className} onClick={this.handleHeaderClick}>
@ -84,8 +73,9 @@ class AccordionPanel extends React.Component<IPanelProps, any> {
className = this.props.panelClass; className = this.props.panelClass;
} }
return ( return (
<div className={className}> <div className={className} style={{display: this.props.opened ? "block" : "none"}}>
{this.props.panelContent} {this.props.panelContent}
</div> </div>
) )