mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-23 08:03:48 +01:00
1989 lines
81 KiB
JavaScript
1989 lines
81 KiB
JavaScript
/**
|
|
* TODO
|
|
* Add police clashes
|
|
* balance point to keep them from running out of control
|
|
*/
|
|
|
|
import { gangMemberTasksMetadata } from "./data/gangmembertasks";
|
|
import { gangMemberUpgradesMetadata } from "./data/gangmemberupgrades";
|
|
|
|
import { Engine } from "./engine";
|
|
import { Faction } from "./Faction/Faction";
|
|
import { Factions } from "./Faction/Factions";
|
|
import { displayFactionContent } from "./Faction/FactionHelpers";
|
|
|
|
import { Page, routing } from "./ui/navigationTracking";
|
|
import { numeralWrapper } from "./ui/numeralFormat";
|
|
|
|
import { dialogBoxCreate } from "../utils/DialogBox";
|
|
import {
|
|
Reviver,
|
|
Generic_toJSON,
|
|
Generic_fromJSON,
|
|
} from "../utils/JSONReviver";
|
|
import {
|
|
formatNumber,
|
|
convertTimeMsToTimeElapsedString,
|
|
} from "../utils/StringHelperFunctions";
|
|
|
|
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
|
|
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
|
import { KEY } from "../utils/helpers/keyCodes";
|
|
|
|
import { createAccordionElement } from "../utils/uiHelpers/createAccordionElement";
|
|
import { createElement } from "../utils/uiHelpers/createElement";
|
|
import { createPopup } from "../utils/uiHelpers/createPopup";
|
|
import { removeChildrenFromElement } from "../utils/uiHelpers/removeChildrenFromElement";
|
|
import { removeElement } from "../utils/uiHelpers/removeElement";
|
|
import { removeElementById } from "../utils/uiHelpers/removeElementById";
|
|
|
|
import { StatsTable } from "./ui/React/StatsTable";
|
|
import { Money } from "./ui/React/Money";
|
|
import { MoneyRate } from "./ui/React/MoneyRate";
|
|
import { Reputation } from "./ui/React/Reputation";
|
|
|
|
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) {
|
|
if (UIElems.gangMemberFilter != null && UIElems.gangMemberFilter === document.activeElement) {return;}
|
|
if (event.keyCode === KEY["1"]) {
|
|
if(UIElems.gangTerritorySubpage.style.display === "block") {
|
|
UIElems.managementButton.click();
|
|
}
|
|
} else if (event.keyCode === KEY["2"]) {
|
|
if (UIElems.gangManagementSubpage.style.display === "block") {
|
|
UIElems.territoryButton.click();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// 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",
|
|
"Tetrads",
|
|
"The Syndicate",
|
|
"The Dark Army",
|
|
"Speakers for the Dead",
|
|
"NiteSec",
|
|
"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
|
|
*/
|
|
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
|
|
var moneyGains = 0, respectGains = 0, wantedLevelGains = 0;
|
|
for (var i = 0; i < this.members.length; ++i) {
|
|
respectGains += (this.members[i].calculateRespectGain(this));
|
|
wantedLevelGains += (this.members[i].calculateWantedLevelGain(this));
|
|
moneyGains += (this.members[i].calculateMoneyGain(this));
|
|
}
|
|
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 {
|
|
var favorMult = 1 + (fac.favor / 100);
|
|
fac.playerReputation += ((player.faction_rep_mult * gain * favorMult) / 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);
|
|
|
|
// 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");
|
|
}
|
|
}
|
|
|
|
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.prototype.processTerritoryAndPowerGains = function(numCycles=1) {
|
|
this.storedTerritoryAndPowerCycles += numCycles;
|
|
if (this.storedTerritoryAndPowerCycles < CyclesPerTerritoryAndPowerUpdate) { return; }
|
|
this.storedTerritoryAndPowerCycles -= 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 < GangNames.length; ++i) {
|
|
const others = GangNames.filter((e) => {
|
|
return e !== GangNames[i];
|
|
});
|
|
const other = getRandomInt(0, others.length - 1);
|
|
|
|
const thisGang = GangNames[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 >= 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);
|
|
if (routing.isOn(Page.Gang)) {
|
|
this.createGangMemberDisplayElement(member);
|
|
this.updateGangContent();
|
|
}
|
|
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`);
|
|
}
|
|
|
|
// Update UI
|
|
if (routing.isOn(Page.Gang)) {
|
|
this.displayGangMemberList();
|
|
}
|
|
}
|
|
|
|
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("<br>"));
|
|
} else {
|
|
workerScript.log(`Ascended Gang member ${memberObj.name}`);
|
|
}
|
|
if (routing.isOn(Page.Gang)) {
|
|
this.displayGangMemberList();
|
|
}
|
|
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.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);
|
|
}
|
|
|
|
Gang.fromJSON = function(value) {
|
|
return Generic_fromJSON(Gang, value.data);
|
|
}
|
|
|
|
Reviver.constructors.Gang = Gang;
|
|
|
|
function GangMember(name) {
|
|
this.name = name;
|
|
this.task = "Unassigned";
|
|
|
|
this.earnedRespect = 0;
|
|
|
|
this.hack = 1;
|
|
this.str = 1;
|
|
this.def = 1;
|
|
this.dex = 1;
|
|
this.agi = 1;
|
|
this.cha = 1;
|
|
|
|
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.hack_mult = 1;
|
|
this.str_mult = 1;
|
|
this.def_mult = 1;
|
|
this.dex_mult = 1;
|
|
this.agi_mult = 1;
|
|
this.cha_mult = 1;
|
|
|
|
this.hack_asc_mult = 1;
|
|
this.str_asc_mult = 1;
|
|
this.def_asc_mult = 1;
|
|
this.dex_asc_mult = 1;
|
|
this.agi_asc_mult = 1;
|
|
this.cha_asc_mult = 1;
|
|
|
|
this.upgrades = []; // Names of upgrades
|
|
this.augmentations = []; // Names of augmentations only
|
|
}
|
|
|
|
// Same skill calculation formula as Player
|
|
GangMember.prototype.calculateSkill = function(exp, mult=1) {
|
|
return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1);
|
|
}
|
|
|
|
GangMember.prototype.updateSkillLevels = function() {
|
|
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);
|
|
}
|
|
|
|
GangMember.prototype.calculatePower = function() {
|
|
return (this.hack + this.str + this.def + this.dex + this.agi + this.cha) / 95;
|
|
}
|
|
|
|
GangMember.prototype.assignToTask = function(taskName) {
|
|
if (GangMemberTasks.hasOwnProperty(taskName)) {
|
|
this.task = taskName;
|
|
return true;
|
|
} else {
|
|
this.task = "Unassigned";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
GangMember.prototype.unassignFromTask = function() {
|
|
this.task = "Unassigned";
|
|
}
|
|
|
|
GangMember.prototype.getTask = function() {
|
|
// 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
|
|
GangMember.prototype.calculateRespectGain = function(gang) {
|
|
const task = this.getTask();
|
|
if (task == null || !(task instanceof GangMemberTask) || task.baseRespect === 0) {return 0;}
|
|
var 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; }
|
|
var respectMult = gang.getWantedPenalty();
|
|
return 11 * task.baseRespect * statWeight * territoryMult * respectMult;
|
|
}
|
|
|
|
GangMember.prototype.calculateWantedLevelGain = function(gang) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
GangMember.prototype.calculateMoneyGain = function(gang) {
|
|
const task = this.getTask();
|
|
if (task == null || !(task instanceof GangMemberTask) || task.baseMoney === 0) {return 0;}
|
|
var 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; }
|
|
var respectMult = gang.getWantedPenalty();
|
|
return 5 * task.baseMoney * statWeight * territoryMult * respectMult;
|
|
}
|
|
|
|
GangMember.prototype.gainExperience = function(numCycles=1) {
|
|
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;
|
|
}
|
|
|
|
GangMember.prototype.recordEarnedRespect = function(numCycles=1, gang) {
|
|
this.earnedRespect += (this.calculateRespectGain(gang) * numCycles);
|
|
}
|
|
|
|
GangMember.prototype.ascend = function() {
|
|
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,
|
|
};
|
|
}
|
|
|
|
GangMember.prototype.getAscensionEfficiency = function() {
|
|
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
|
|
GangMember.prototype.getAscensionResults = function() {
|
|
/**
|
|
* 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) {
|
|
let 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),
|
|
}
|
|
}
|
|
|
|
GangMember.prototype.buyUpgrade = function(upg, player, gang) {
|
|
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;
|
|
}
|
|
|
|
GangMember.prototype.toJSON = function() {
|
|
return Generic_toJSON("GangMember", this);
|
|
}
|
|
|
|
GangMember.fromJSON = function(value) {
|
|
return Generic_fromJSON(GangMember, value.data);
|
|
}
|
|
|
|
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("<br>");
|
|
}
|
|
|
|
// 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";
|
|
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
|
|
gangContentCreated: false,
|
|
gangContainer: null,
|
|
managementButton: null,
|
|
territoryButton: null,
|
|
|
|
// Subpages
|
|
gangManagementSubpage: null,
|
|
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,
|
|
}
|
|
|
|
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",
|
|
});
|
|
|
|
// Get variables
|
|
var facName = this.facName;
|
|
|
|
// Back button
|
|
UIElems.gangContainer.appendChild(createElement("a", {
|
|
class:"a-link-button", display:"inline-block", innerText:"Back",
|
|
clickListener:() => {
|
|
Engine.loadFactionContent();
|
|
displayFactionContent(facName);
|
|
return false;
|
|
},
|
|
}));
|
|
|
|
// Buttons to switch between panels
|
|
UIElems.managementButton = createElement("a", {
|
|
id:"gang-management-subpage-button", class:"a-link-button-inactive",
|
|
display:"inline-block", innerHTML: "Gang Management (Alt+1)",
|
|
clickListener:() => {
|
|
UIElems.gangManagementSubpage.style.display = "block";
|
|
UIElems.gangTerritorySubpage.style.display = "none";
|
|
UIElems.managementButton.classList.toggle("a-link-button-inactive");
|
|
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;
|
|
},
|
|
})
|
|
UIElems.territoryButton = createElement("a", {
|
|
id:"gang-territory-subpage-button", class:"a-link-button",
|
|
display:"inline-block", innerHTML:"Gang Territory (Alt+2)",
|
|
clickListener:() => {
|
|
UIElems.gangManagementSubpage.style.display = "none";
|
|
UIElems.gangTerritorySubpage.style.display = "block";
|
|
UIElems.managementButton.classList.toggle("a-link-button-inactive");
|
|
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;
|
|
},
|
|
});
|
|
UIElems.gangContainer.appendChild(UIElems.managementButton);
|
|
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.<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
|
|
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.<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.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 = `<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 {
|
|
// 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 >= 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`;
|
|
}
|
|
|
|
// 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) {
|
|
if (!UIElems.gangContentCreated) { return; }
|
|
const name = memberObj.name;
|
|
|
|
// 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
|
|
const statsDiv = createElement("div", {
|
|
class: "gang-member-info-div",
|
|
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("<br>"),
|
|
});
|
|
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.",
|
|
"<br><br>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.",
|
|
"<br><br>Upon ascension, the member will lose all of its non-Augmentation Equipment and your",
|
|
"gang will lose respect equal to the total respect earned by the member."].join(" "));
|
|
},
|
|
innerText: "?",
|
|
marginTop: "5px",
|
|
});
|
|
|
|
statsDiv.appendChild(statsP);
|
|
statsDiv.appendChild(brElement);
|
|
statsDiv.appendChild(ascendButton);
|
|
statsDiv.appendChild(ascendHelpTip);
|
|
|
|
// 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);
|
|
}
|
|
taskSelector.addEventListener("change", () => {
|
|
var task = taskSelector.options[taskSelector.selectedIndex].text;
|
|
memberObj.assignToTask(task);
|
|
this.setGangMemberTaskDescription(memberObj, task);
|
|
this.updateGangContent();
|
|
});
|
|
|
|
// 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;
|
|
}
|
|
|
|
var gainInfo = createElement("div", {id:name + "gang-member-gain-info"});
|
|
taskDiv.appendChild(taskSelector);
|
|
taskDiv.appendChild(gainInfo);
|
|
|
|
// Panel 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);
|
|
|
|
gangMemberDiv.appendChild(statsDiv);
|
|
gangMemberDiv.appendChild(taskDiv);
|
|
gangMemberDiv.appendChild(taskDescDiv);
|
|
|
|
UIElems.gangMemberList.appendChild(li);
|
|
this.setGangMemberTaskDescription(memberObj, taskName); //Initialize description, TODO doesnt work rn
|
|
this.updateGangMemberDisplayElement(memberObj);
|
|
}
|
|
|
|
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("<br>");
|
|
}
|
|
}
|
|
|
|
// 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() {
|
|
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 = {};
|
|
}
|