mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-26 09:33:49 +01:00
converted everything to ts
This commit is contained in:
parent
43d0fcb9f9
commit
07cca48a17
@ -8,6 +8,9 @@ import { IMap } from "./types";
|
|||||||
export const CONSTANTS: IMap<any> = {
|
export const CONSTANTS: IMap<any> = {
|
||||||
Version: "0.52.2",
|
Version: "0.52.2",
|
||||||
|
|
||||||
|
// Speed (in ms) at which the main loop is updated
|
||||||
|
_idleSpeed: 200,
|
||||||
|
|
||||||
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
|
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
|
||||||
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
|
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
|
||||||
* the player will have this level assuming no multipliers. Multipliers can cause skills to go above this.
|
* the player will have this level assuming no multipliers. Multipliers can cause skills to go above this.
|
||||||
|
468
src/Gang.jsx
468
src/Gang.jsx
@ -1,468 +0,0 @@
|
|||||||
/**
|
|
||||||
* TODO
|
|
||||||
* Add police clashes
|
|
||||||
* balance point to keep them from running out of control
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Engine } from "./engine";
|
|
||||||
import { Faction } from "./Faction/Faction";
|
|
||||||
import { Factions } from "./Faction/Factions";
|
|
||||||
|
|
||||||
import { numeralWrapper } from "./ui/numeralFormat";
|
|
||||||
|
|
||||||
import { dialogBoxCreate } from "../utils/DialogBox";
|
|
||||||
import {
|
|
||||||
Reviver,
|
|
||||||
Generic_toJSON,
|
|
||||||
Generic_fromJSON,
|
|
||||||
} from "../utils/JSONReviver";
|
|
||||||
|
|
||||||
import { exceptionAlert } from "../utils/helpers/exceptionAlert";
|
|
||||||
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
|
||||||
|
|
||||||
import { createElement } from "../utils/uiHelpers/createElement";
|
|
||||||
import { removeElement } from "../utils/uiHelpers/removeElement";
|
|
||||||
|
|
||||||
import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades";
|
|
||||||
import { GangConstants } from "./Gang/data/Constants";
|
|
||||||
import { GangMemberTasks } from "./Gang/GangMemberTasks";
|
|
||||||
|
|
||||||
import { AllGangs } from "./Gang/AllGangs";
|
|
||||||
import { Root } from "./Gang/ui/Root";
|
|
||||||
import { GangMember } from "./Gang/GangMember";
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param facName {string} Name of corresponding faction
|
|
||||||
* @param hacking {bollean} Whether or not its a hacking gang
|
|
||||||
*/
|
|
||||||
export function Gang(facName, hacking=false) {
|
|
||||||
this.facName = facName;
|
|
||||||
this.members = [];
|
|
||||||
this.wanted = 1;
|
|
||||||
this.respect = 1;
|
|
||||||
|
|
||||||
this.isHackingGang = hacking;
|
|
||||||
|
|
||||||
this.respectGainRate = 0;
|
|
||||||
this.wantedGainRate = 0;
|
|
||||||
this.moneyGainRate = 0;
|
|
||||||
|
|
||||||
// When processing gains, this stores the number of cycles until some
|
|
||||||
// limit is reached, and then calculates and applies the gains only at that limit
|
|
||||||
this.storedCycles = 0;
|
|
||||||
|
|
||||||
// Separate variable to keep track of cycles for Territry + Power gang, which
|
|
||||||
// happens on a slower "clock" than normal processing
|
|
||||||
this.storedTerritoryAndPowerCycles = 0;
|
|
||||||
|
|
||||||
this.territoryClashChance = 0;
|
|
||||||
this.territoryWarfareEngaged = false;
|
|
||||||
|
|
||||||
this.notifyMemberDeath = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.getPower = function() {
|
|
||||||
return AllGangs[this.facName].power;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.getTerritory = function() {
|
|
||||||
return AllGangs[this.facName].territory;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.process = function(numCycles=1, player) {
|
|
||||||
const CyclesPerSecond = 1000 / Engine._idleSpeed;
|
|
||||||
|
|
||||||
if (isNaN(numCycles)) {
|
|
||||||
console.error(`NaN passed into Gang.process(): ${numCycles}`);
|
|
||||||
}
|
|
||||||
this.storedCycles += numCycles;
|
|
||||||
|
|
||||||
// Only process if there are at least 2 seconds, and at most 5 seconds
|
|
||||||
if (this.storedCycles < 2 * CyclesPerSecond) { return; }
|
|
||||||
const cycles = Math.min(this.storedCycles, 5 * CyclesPerSecond);
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.processGains(cycles, player);
|
|
||||||
this.processExperienceGains(cycles);
|
|
||||||
this.processTerritoryAndPowerGains(cycles);
|
|
||||||
this.storedCycles -= cycles;
|
|
||||||
} catch(e) {
|
|
||||||
exceptionAlert(`Exception caught when processing Gang: ${e}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.processGains = function(numCycles=1, player) {
|
|
||||||
// Get gains per cycle
|
|
||||||
let moneyGains = 0, respectGains = 0, wantedLevelGains = 0;
|
|
||||||
let justice = 0;
|
|
||||||
for (let i = 0; i < this.members.length; ++i) {
|
|
||||||
respectGains += (this.members[i].calculateRespectGain(this));
|
|
||||||
moneyGains += (this.members[i].calculateMoneyGain(this));
|
|
||||||
const wantedLevelGain = this.members[i].calculateWantedLevelGain(this);
|
|
||||||
wantedLevelGains += wantedLevelGain;
|
|
||||||
if(wantedLevelGain < 0) justice++; // this member is lowering wanted.
|
|
||||||
}
|
|
||||||
this.respectGainRate = respectGains;
|
|
||||||
this.wantedGainRate = wantedLevelGains;
|
|
||||||
this.moneyGainRate = moneyGains;
|
|
||||||
|
|
||||||
if (typeof respectGains === "number") {
|
|
||||||
const gain = respectGains * numCycles;
|
|
||||||
this.respect += gain;
|
|
||||||
// Faction reputation gains is respect gain divided by some constant
|
|
||||||
const fac = Factions[this.facName];
|
|
||||||
if (!(fac instanceof Faction)) {
|
|
||||||
dialogBoxCreate("ERROR: Could not get Faction associates with your gang. This is a bug, please report to game dev");
|
|
||||||
} else {
|
|
||||||
let favorMult = 1 + (fac.favor / 100);
|
|
||||||
fac.playerReputation += ((player.faction_rep_mult * gain * favorMult) / GangConstants.GangRespectToReputationRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of respect gained per member
|
|
||||||
for (let i = 0; i < this.members.length; ++i) {
|
|
||||||
this.members[i].recordEarnedRespect(numCycles, this);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn("respectGains calculated to be NaN");
|
|
||||||
}
|
|
||||||
if (typeof wantedLevelGains === "number") {
|
|
||||||
if (this.wanted === 1 && wantedLevelGains < 0) {
|
|
||||||
// At minimum wanted, do nothing
|
|
||||||
} else {
|
|
||||||
const oldWanted = this.wanted;
|
|
||||||
let newWanted = oldWanted + (wantedLevelGains * numCycles);
|
|
||||||
newWanted = newWanted * (1 - justice * 0.001); // safeguard
|
|
||||||
// Prevent overflow
|
|
||||||
if (wantedLevelGains <= 0 && newWanted > oldWanted) {
|
|
||||||
newWanted = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.wanted = newWanted;
|
|
||||||
if (this.wanted < 1) {this.wanted = 1;}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn("ERROR: wantedLevelGains is NaN");
|
|
||||||
}
|
|
||||||
if (typeof moneyGains === "number") {
|
|
||||||
player.gainMoney(moneyGains * numCycles);
|
|
||||||
player.recordMoneySource(moneyGains * numCycles, "gang");
|
|
||||||
} else {
|
|
||||||
console.warn("ERROR: respectGains is NaN");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.processTerritoryAndPowerGains = function(numCycles=1) {
|
|
||||||
this.storedTerritoryAndPowerCycles += numCycles;
|
|
||||||
if (this.storedTerritoryAndPowerCycles < GangConstants.CyclesPerTerritoryAndPowerUpdate) { return; }
|
|
||||||
this.storedTerritoryAndPowerCycles -= GangConstants.CyclesPerTerritoryAndPowerUpdate;
|
|
||||||
|
|
||||||
// Process power first
|
|
||||||
const gangName = this.facName;
|
|
||||||
for (const name in AllGangs) {
|
|
||||||
if (AllGangs.hasOwnProperty(name)) {
|
|
||||||
if (name == gangName) {
|
|
||||||
AllGangs[name].power += this.calculatePower();
|
|
||||||
} else {
|
|
||||||
// All NPC gangs get random power gains
|
|
||||||
const gainRoll = Math.random();
|
|
||||||
if (gainRoll < 0.5) {
|
|
||||||
// Multiplicative gain (50% chance)
|
|
||||||
// This is capped per cycle, to prevent it from getting out of control
|
|
||||||
const multiplicativeGain = AllGangs[name].power * 0.005;
|
|
||||||
AllGangs[name].power += Math.min(0.85, multiplicativeGain);
|
|
||||||
} else {
|
|
||||||
// Additive gain (50% chance)
|
|
||||||
const additiveGain = 0.75 * gainRoll * AllGangs[name].territory;
|
|
||||||
AllGangs[name].power += (additiveGain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if territory should be processed
|
|
||||||
if (this.territoryWarfareEngaged) {
|
|
||||||
this.territoryClashChance = 1;
|
|
||||||
} else if (this.territoryClashChance > 0) {
|
|
||||||
// Engagement turned off, but still a positive clash chance. So there's
|
|
||||||
// still a chance of clashing but it slowly goes down over time
|
|
||||||
this.territoryClashChance = Math.max(0, this.territoryClashChance - 0.01);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then process territory
|
|
||||||
for (let i = 0; i < GangConstants.Names.length; ++i) {
|
|
||||||
const others = GangConstants.Names.filter((e) => {
|
|
||||||
return e !== GangConstants.Names[i];
|
|
||||||
});
|
|
||||||
const other = getRandomInt(0, others.length - 1);
|
|
||||||
|
|
||||||
const thisGang = GangConstants.Names[i];
|
|
||||||
const otherGang = others[other];
|
|
||||||
|
|
||||||
// If either of the gangs involved in this clash is the player, determine
|
|
||||||
// whether to skip or process it using the clash chance
|
|
||||||
if (thisGang === gangName || otherGang === gangName) {
|
|
||||||
if (!(Math.random() < this.territoryClashChance)) { continue; }
|
|
||||||
}
|
|
||||||
|
|
||||||
const thisPwr = AllGangs[thisGang].power;
|
|
||||||
const otherPwr = AllGangs[otherGang].power;
|
|
||||||
const thisChance = thisPwr / (thisPwr + otherPwr);
|
|
||||||
|
|
||||||
|
|
||||||
if (Math.random() < thisChance) {
|
|
||||||
if (AllGangs[otherGang].territory <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const territoryGain = calculateTerritoryGain(thisGang, otherGang);
|
|
||||||
AllGangs[thisGang].territory += territoryGain;
|
|
||||||
AllGangs[otherGang].territory -= territoryGain;
|
|
||||||
if (thisGang === gangName) {
|
|
||||||
this.clash(true); // Player won
|
|
||||||
AllGangs[otherGang].power *= (1 / 1.01);
|
|
||||||
} else if (otherGang === gangName) {
|
|
||||||
this.clash(false); // Player lost
|
|
||||||
} else {
|
|
||||||
AllGangs[otherGang].power *= (1 / 1.01);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (AllGangs[thisGang].territory <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const territoryGain = calculateTerritoryGain(otherGang, thisGang);
|
|
||||||
AllGangs[thisGang].territory -= territoryGain;
|
|
||||||
AllGangs[otherGang].territory += territoryGain;
|
|
||||||
if (thisGang === gangName) {
|
|
||||||
this.clash(false); // Player lost
|
|
||||||
} else if (otherGang === gangName) {
|
|
||||||
this.clash(true); // Player won
|
|
||||||
AllGangs[thisGang].power *= (1 / 1.01);
|
|
||||||
} else {
|
|
||||||
AllGangs[thisGang].power *= (1 / 1.01);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.canRecruitMember = function() {
|
|
||||||
if (this.members.length >= GangConstants.MaximumGangMembers) { return false; }
|
|
||||||
return (this.respect >= this.getRespectNeededToRecruitMember());
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.getRespectNeededToRecruitMember = function() {
|
|
||||||
// First N gang members are free (can be recruited at 0 respect)
|
|
||||||
const numFreeMembers = 3;
|
|
||||||
if (this.members.length < numFreeMembers) { return 0; }
|
|
||||||
|
|
||||||
const i = this.members.length - (numFreeMembers - 1);
|
|
||||||
return Math.round(0.9 * Math.pow(i, 3) + Math.pow(i, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.recruitMember = function(name) {
|
|
||||||
name = String(name);
|
|
||||||
if (name === "" || !this.canRecruitMember()) { return false; }
|
|
||||||
|
|
||||||
// Check for already-existing names
|
|
||||||
let sameNames = this.members.filter((m) => {
|
|
||||||
return m.name === name;
|
|
||||||
});
|
|
||||||
if (sameNames.length >= 1) { return false; }
|
|
||||||
|
|
||||||
let member = new GangMember(name);
|
|
||||||
this.members.push(member);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Money and Respect gains multiplied by this number (< 1)
|
|
||||||
Gang.prototype.getWantedPenalty = function() {
|
|
||||||
return (this.respect) / (this.respect + this.wanted);
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.processExperienceGains = function(numCycles=1) {
|
|
||||||
for (var i = 0; i < this.members.length; ++i) {
|
|
||||||
this.members[i].gainExperience(numCycles);
|
|
||||||
this.members[i].updateSkillLevels();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Calculates power GAIN, which is added onto the Gang's existing power
|
|
||||||
Gang.prototype.calculatePower = function() {
|
|
||||||
var memberTotal = 0;
|
|
||||||
for (var i = 0; i < this.members.length; ++i) {
|
|
||||||
if (GangMemberTasks.hasOwnProperty(this.members[i].task) && this.members[i].task == "Territory Warfare") {
|
|
||||||
const gain = this.members[i].calculatePower();
|
|
||||||
memberTotal += gain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (0.015 * this.getTerritory() * memberTotal);
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.clash = function(won=false) {
|
|
||||||
// Determine if a gang member should die
|
|
||||||
let baseDeathChance = 0.01;
|
|
||||||
if (won) { baseDeathChance /= 2; }
|
|
||||||
|
|
||||||
// If the clash was lost, the player loses a small percentage of power
|
|
||||||
if (!won) {
|
|
||||||
AllGangs[this.facName].power *= (1 / 1.008);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deaths can only occur during X% of clashes
|
|
||||||
if (Math.random() < 0.65) { return; }
|
|
||||||
|
|
||||||
for (let i = this.members.length - 1; i >= 0; --i) {
|
|
||||||
const member = this.members[i];
|
|
||||||
|
|
||||||
// Only members assigned to Territory Warfare can die
|
|
||||||
if (member.task !== "Territory Warfare") { continue; }
|
|
||||||
|
|
||||||
// Chance to die is decreased based on defense
|
|
||||||
const modifiedDeathChance = baseDeathChance / Math.pow(member.def, 0.6);
|
|
||||||
if (Math.random() < modifiedDeathChance) {
|
|
||||||
this.killMember(member);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.killMember = function(memberObj) {
|
|
||||||
// Player loses a percentage of total respect, plus whatever respect that member has earned
|
|
||||||
const totalRespect = this.respect;
|
|
||||||
const lostRespect = (0.05 * totalRespect) + memberObj.earnedRespect;
|
|
||||||
this.respect = Math.max(0, totalRespect - lostRespect);
|
|
||||||
|
|
||||||
for (let i = 0; i < this.members.length; ++i) {
|
|
||||||
if (memberObj.name === this.members[i].name) {
|
|
||||||
this.members.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify of death
|
|
||||||
if (this.notifyMemberDeath) {
|
|
||||||
dialogBoxCreate(`${memberObj.name} was killed in a gang clash! You lost ${lostRespect} respect`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.ascendMember = function(memberObj, workerScript) {
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* res is an object with the following format:
|
|
||||||
* {
|
|
||||||
* respect: Amount of respect to deduct
|
|
||||||
* hack/str/def/dex/agi/cha: Ascension multipliers gained for each stat
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
const res = memberObj.ascend();
|
|
||||||
this.respect = Math.max(1, this.respect - res.respect);
|
|
||||||
if (workerScript == null) {
|
|
||||||
dialogBoxCreate([`You ascended ${memberObj.name}!`,
|
|
||||||
"",
|
|
||||||
`Your gang lost ${numeralWrapper.formatRespect(res.respect)} respect`,
|
|
||||||
"",
|
|
||||||
`${memberObj.name} gained the following stat multipliers for ascending:`,
|
|
||||||
`Hacking: ${numeralWrapper.formatPercentage(res.hack, 3)}`,
|
|
||||||
`Strength: ${numeralWrapper.formatPercentage(res.str, 3)}`,
|
|
||||||
`Defense: ${numeralWrapper.formatPercentage(res.def, 3)}`,
|
|
||||||
`Dexterity: ${numeralWrapper.formatPercentage(res.dex, 3)}`,
|
|
||||||
`Agility: ${numeralWrapper.formatPercentage(res.agi, 3)}`,
|
|
||||||
`Charisma: ${numeralWrapper.formatPercentage(res.cha, 3)}`].join("<br>"));
|
|
||||||
} else {
|
|
||||||
workerScript.log(`Ascended Gang member ${memberObj.name}`);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
} catch(e) {
|
|
||||||
if (workerScript == null) {
|
|
||||||
exceptionAlert(e);
|
|
||||||
} else {
|
|
||||||
throw e; // Re-throw, will be caught in the Netscript Function
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cost of upgrade gets cheaper as gang increases in respect + power
|
|
||||||
Gang.prototype.getDiscount = function() {
|
|
||||||
const power = this.getPower();
|
|
||||||
const respect = this.respect;
|
|
||||||
|
|
||||||
const respectLinearFac = 5e6;
|
|
||||||
const powerLinearFac = 1e6;
|
|
||||||
const discount = Math.pow(respect, 0.01) + respect / respectLinearFac + Math.pow(power, 0.01) + power / powerLinearFac - 1;
|
|
||||||
return Math.max(1, discount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns only valid tasks for this gang. Excludes 'Unassigned'
|
|
||||||
Gang.prototype.getAllTaskNames = function() {
|
|
||||||
let tasks = [];
|
|
||||||
const allTasks = Object.keys(GangMemberTasks);
|
|
||||||
if (this.isHackingGang) {
|
|
||||||
tasks = allTasks.filter((e) => {
|
|
||||||
let task = GangMemberTasks[e];
|
|
||||||
if (task == null) { return false; }
|
|
||||||
if (e === "Unassigned") { return false; }
|
|
||||||
return task.isHacking;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
tasks = allTasks.filter((e) => {
|
|
||||||
let task = GangMemberTasks[e];
|
|
||||||
if (task == null) { return false; }
|
|
||||||
if (e === "Unassigned") { return false; }
|
|
||||||
return task.isCombat;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return tasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.getUpgradeCost = function(upgName) {
|
|
||||||
if (GangMemberUpgrades[upgName] == null) { return Infinity; }
|
|
||||||
return GangMemberUpgrades[upgName].getCost(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.toJSON = function() {
|
|
||||||
return Generic_toJSON("Gang", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.fromJSON = function(value) {
|
|
||||||
return Generic_fromJSON(Gang, value.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
Reviver.constructors.Gang = Gang;
|
|
||||||
|
|
||||||
function calculateTerritoryGain(winGang, loseGang) {
|
|
||||||
const powerBonus = Math.max(1, 1+Math.log(AllGangs[winGang].power/AllGangs[loseGang].power)/Math.log(50));
|
|
||||||
const gains = Math.min(AllGangs[loseGang].territory, powerBonus*0.0001*(Math.random()+.5))
|
|
||||||
return gains;
|
|
||||||
}
|
|
||||||
// Gang UI Dom Elements
|
|
||||||
const UIElems = {
|
|
||||||
gangContentCreated: false,
|
|
||||||
gangContainer: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.displayGangContent = function(player) {
|
|
||||||
if (!UIElems.gangContentCreated || UIElems.gangContainer == null) {
|
|
||||||
UIElems.gangContentCreated = true;
|
|
||||||
|
|
||||||
// Create gang container
|
|
||||||
UIElems.gangContainer = createElement("div", {
|
|
||||||
id:"gang-container", class:"generic-menupage-container",
|
|
||||||
});
|
|
||||||
|
|
||||||
ReactDOM.render(<Root engine={Engine} gang={this} player={player} />, UIElems.gangContainer);
|
|
||||||
|
|
||||||
document.getElementById("entire-game-container").appendChild(UIElems.gangContainer);
|
|
||||||
}
|
|
||||||
UIElems.gangContainer.style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
Gang.prototype.clearUI = function() {
|
|
||||||
if (UIElems.gangContainer instanceof Element) { removeElement(UIElems.gangContainer); }
|
|
||||||
|
|
||||||
for (const prop in UIElems) {
|
|
||||||
UIElems[prop] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
UIElems.gangContentCreated = false;
|
|
||||||
}
|
|
463
src/Gang/Gang.ts
Normal file
463
src/Gang/Gang.ts
Normal file
@ -0,0 +1,463 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
* Add police clashes
|
||||||
|
* balance point to keep them from running out of control
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Faction } from "../Faction/Faction";
|
||||||
|
import { Factions } from "../Faction/Factions";
|
||||||
|
|
||||||
|
import { numeralWrapper } from "../ui/numeralFormat";
|
||||||
|
|
||||||
|
import { dialogBoxCreate } from "../../utils/DialogBox";
|
||||||
|
import {
|
||||||
|
Reviver,
|
||||||
|
Generic_toJSON,
|
||||||
|
Generic_fromJSON,
|
||||||
|
} from "../../utils/JSONReviver";
|
||||||
|
|
||||||
|
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||||
|
import { getRandomInt } from "../../utils/helpers/getRandomInt";
|
||||||
|
|
||||||
|
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||||
|
import { GangConstants } from "./data/Constants";
|
||||||
|
import { CONSTANTS } from "../Constants";
|
||||||
|
import { GangMemberTasks } from "./GangMemberTasks";
|
||||||
|
|
||||||
|
import { AllGangs } from "./AllGangs";
|
||||||
|
import { GangMember } from "./GangMember";
|
||||||
|
|
||||||
|
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||||
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
|
|
||||||
|
|
||||||
|
export class Gang {
|
||||||
|
facName: string;
|
||||||
|
members: GangMember[];
|
||||||
|
wanted: number;
|
||||||
|
respect: number;
|
||||||
|
|
||||||
|
isHackingGang: boolean;
|
||||||
|
|
||||||
|
respectGainRate: number;
|
||||||
|
wantedGainRate: number;
|
||||||
|
moneyGainRate: number;
|
||||||
|
|
||||||
|
storedCycles: number;
|
||||||
|
|
||||||
|
storedTerritoryAndPowerCycles: number;
|
||||||
|
|
||||||
|
territoryClashChance: number;
|
||||||
|
territoryWarfareEngaged: boolean;
|
||||||
|
|
||||||
|
notifyMemberDeath: boolean;
|
||||||
|
|
||||||
|
constructor(facName: string = "", hacking: boolean = false) {
|
||||||
|
this.facName = facName;
|
||||||
|
this.members = [];
|
||||||
|
this.wanted = 1;
|
||||||
|
this.respect = 1;
|
||||||
|
|
||||||
|
this.isHackingGang = hacking;
|
||||||
|
|
||||||
|
this.respectGainRate = 0;
|
||||||
|
this.wantedGainRate = 0;
|
||||||
|
this.moneyGainRate = 0;
|
||||||
|
|
||||||
|
// When processing gains, this stores the number of cycles until some
|
||||||
|
// limit is reached, and then calculates and applies the gains only at that limit
|
||||||
|
this.storedCycles = 0;
|
||||||
|
|
||||||
|
// Separate variable to keep track of cycles for Territry + Power gang, which
|
||||||
|
// happens on a slower "clock" than normal processing
|
||||||
|
this.storedTerritoryAndPowerCycles = 0;
|
||||||
|
|
||||||
|
this.territoryClashChance = 0;
|
||||||
|
this.territoryWarfareEngaged = false;
|
||||||
|
|
||||||
|
this.notifyMemberDeath = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPower(): number {
|
||||||
|
return AllGangs[this.facName].power;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTerritory(): number {
|
||||||
|
return AllGangs[this.facName].territory;
|
||||||
|
}
|
||||||
|
|
||||||
|
process(numCycles: number = 1, player: IPlayer): void {
|
||||||
|
const CyclesPerSecond = 1000 / CONSTANTS._idleSpeed;
|
||||||
|
|
||||||
|
if (isNaN(numCycles)) {
|
||||||
|
console.error(`NaN passed into Gang.process(): ${numCycles}`);
|
||||||
|
}
|
||||||
|
this.storedCycles += numCycles;
|
||||||
|
|
||||||
|
// Only process if there are at least 2 seconds, and at most 5 seconds
|
||||||
|
if (this.storedCycles < 2 * CyclesPerSecond) { return; }
|
||||||
|
const cycles = Math.min(this.storedCycles, 5 * CyclesPerSecond);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.processGains(cycles, player);
|
||||||
|
this.processExperienceGains(cycles);
|
||||||
|
this.processTerritoryAndPowerGains(cycles);
|
||||||
|
this.storedCycles -= cycles;
|
||||||
|
} catch(e) {
|
||||||
|
console.error(`Exception caught when processing Gang: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
processGains(numCycles: number = 1, player: IPlayer): void {
|
||||||
|
// Get gains per cycle
|
||||||
|
let moneyGains = 0, respectGains = 0, wantedLevelGains = 0;
|
||||||
|
let justice = 0;
|
||||||
|
for (let i = 0; i < this.members.length; ++i) {
|
||||||
|
respectGains += (this.members[i].calculateRespectGain(this));
|
||||||
|
moneyGains += (this.members[i].calculateMoneyGain(this));
|
||||||
|
const wantedLevelGain = this.members[i].calculateWantedLevelGain(this);
|
||||||
|
wantedLevelGains += wantedLevelGain;
|
||||||
|
if(wantedLevelGain < 0) justice++; // this member is lowering wanted.
|
||||||
|
}
|
||||||
|
this.respectGainRate = respectGains;
|
||||||
|
this.wantedGainRate = wantedLevelGains;
|
||||||
|
this.moneyGainRate = moneyGains;
|
||||||
|
|
||||||
|
if (typeof respectGains === "number") {
|
||||||
|
const gain = respectGains * numCycles;
|
||||||
|
this.respect += gain;
|
||||||
|
// Faction reputation gains is respect gain divided by some constant
|
||||||
|
const fac = Factions[this.facName];
|
||||||
|
if (!(fac instanceof Faction)) {
|
||||||
|
dialogBoxCreate("ERROR: Could not get Faction associates with your gang. This is a bug, please report to game dev");
|
||||||
|
} else {
|
||||||
|
const favorMult = 1 + (fac.favor / 100);
|
||||||
|
fac.playerReputation += ((player.faction_rep_mult * gain * favorMult) / GangConstants.GangRespectToReputationRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep track of respect gained per member
|
||||||
|
for (let i = 0; i < this.members.length; ++i) {
|
||||||
|
this.members[i].recordEarnedRespect(numCycles, this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("respectGains calculated to be NaN");
|
||||||
|
}
|
||||||
|
if (typeof wantedLevelGains === "number") {
|
||||||
|
if (this.wanted === 1 && wantedLevelGains < 0) {
|
||||||
|
// At minimum wanted, do nothing
|
||||||
|
} else {
|
||||||
|
const oldWanted = this.wanted;
|
||||||
|
let newWanted = oldWanted + (wantedLevelGains * numCycles);
|
||||||
|
newWanted = newWanted * (1 - justice * 0.001); // safeguard
|
||||||
|
// Prevent overflow
|
||||||
|
if (wantedLevelGains <= 0 && newWanted > oldWanted) {
|
||||||
|
newWanted = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wanted = newWanted;
|
||||||
|
if (this.wanted < 1) {this.wanted = 1;}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("ERROR: wantedLevelGains is NaN");
|
||||||
|
}
|
||||||
|
if (typeof moneyGains === "number") {
|
||||||
|
player.gainMoney(moneyGains * numCycles);
|
||||||
|
player.recordMoneySource(moneyGains * numCycles, "gang");
|
||||||
|
} else {
|
||||||
|
console.warn("ERROR: respectGains is NaN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processTerritoryAndPowerGains(numCycles: number = 1): void {
|
||||||
|
this.storedTerritoryAndPowerCycles += numCycles;
|
||||||
|
if (this.storedTerritoryAndPowerCycles < GangConstants.CyclesPerTerritoryAndPowerUpdate) { return; }
|
||||||
|
this.storedTerritoryAndPowerCycles -= GangConstants.CyclesPerTerritoryAndPowerUpdate;
|
||||||
|
|
||||||
|
// Process power first
|
||||||
|
const gangName = this.facName;
|
||||||
|
for (const name in AllGangs) {
|
||||||
|
if (AllGangs.hasOwnProperty(name)) {
|
||||||
|
if (name == gangName) {
|
||||||
|
AllGangs[name].power += this.calculatePower();
|
||||||
|
} else {
|
||||||
|
// All NPC gangs get random power gains
|
||||||
|
const gainRoll = Math.random();
|
||||||
|
if (gainRoll < 0.5) {
|
||||||
|
// Multiplicative gain (50% chance)
|
||||||
|
// This is capped per cycle, to prevent it from getting out of control
|
||||||
|
const multiplicativeGain = AllGangs[name].power * 0.005;
|
||||||
|
AllGangs[name].power += Math.min(0.85, multiplicativeGain);
|
||||||
|
} else {
|
||||||
|
// Additive gain (50% chance)
|
||||||
|
const additiveGain = 0.75 * gainRoll * AllGangs[name].territory;
|
||||||
|
AllGangs[name].power += (additiveGain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if territory should be processed
|
||||||
|
if (this.territoryWarfareEngaged) {
|
||||||
|
this.territoryClashChance = 1;
|
||||||
|
} else if (this.territoryClashChance > 0) {
|
||||||
|
// Engagement turned off, but still a positive clash chance. So there's
|
||||||
|
// still a chance of clashing but it slowly goes down over time
|
||||||
|
this.territoryClashChance = Math.max(0, this.territoryClashChance - 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then process territory
|
||||||
|
for (let i = 0; i < GangConstants.Names.length; ++i) {
|
||||||
|
const others = GangConstants.Names.filter((e) => {
|
||||||
|
return e !== GangConstants.Names[i];
|
||||||
|
});
|
||||||
|
const other = getRandomInt(0, others.length - 1);
|
||||||
|
|
||||||
|
const thisGang = GangConstants.Names[i];
|
||||||
|
const otherGang = others[other];
|
||||||
|
|
||||||
|
// If either of the gangs involved in this clash is the player, determine
|
||||||
|
// whether to skip or process it using the clash chance
|
||||||
|
if (thisGang === gangName || otherGang === gangName) {
|
||||||
|
if (!(Math.random() < this.territoryClashChance)) { continue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
const thisPwr = AllGangs[thisGang].power;
|
||||||
|
const otherPwr = AllGangs[otherGang].power;
|
||||||
|
const thisChance = thisPwr / (thisPwr + otherPwr);
|
||||||
|
|
||||||
|
function calculateTerritoryGain(winGang: string, loseGang: string): number {
|
||||||
|
const powerBonus = Math.max(1, 1+Math.log(AllGangs[winGang].power/AllGangs[loseGang].power)/Math.log(50));
|
||||||
|
const gains = Math.min(AllGangs[loseGang].territory, powerBonus*0.0001*(Math.random()+.5))
|
||||||
|
return gains;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (Math.random() < thisChance) {
|
||||||
|
if (AllGangs[otherGang].territory <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const territoryGain = calculateTerritoryGain(thisGang, otherGang);
|
||||||
|
AllGangs[thisGang].territory += territoryGain;
|
||||||
|
AllGangs[otherGang].territory -= territoryGain;
|
||||||
|
if (thisGang === gangName) {
|
||||||
|
this.clash(true); // Player won
|
||||||
|
AllGangs[otherGang].power *= (1 / 1.01);
|
||||||
|
} else if (otherGang === gangName) {
|
||||||
|
this.clash(false); // Player lost
|
||||||
|
} else {
|
||||||
|
AllGangs[otherGang].power *= (1 / 1.01);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (AllGangs[thisGang].territory <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const territoryGain = calculateTerritoryGain(otherGang, thisGang);
|
||||||
|
AllGangs[thisGang].territory -= territoryGain;
|
||||||
|
AllGangs[otherGang].territory += territoryGain;
|
||||||
|
if (thisGang === gangName) {
|
||||||
|
this.clash(false); // Player lost
|
||||||
|
} else if (otherGang === gangName) {
|
||||||
|
this.clash(true); // Player won
|
||||||
|
AllGangs[thisGang].power *= (1 / 1.01);
|
||||||
|
} else {
|
||||||
|
AllGangs[thisGang].power *= (1 / 1.01);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processExperienceGains(numCycles: number = 1): void {
|
||||||
|
for (let i = 0; i < this.members.length; ++i) {
|
||||||
|
this.members[i].gainExperience(numCycles);
|
||||||
|
this.members[i].updateSkillLevels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clash(won: boolean = false): void {
|
||||||
|
// Determine if a gang member should die
|
||||||
|
let baseDeathChance = 0.01;
|
||||||
|
if (won) { baseDeathChance /= 2; }
|
||||||
|
|
||||||
|
// If the clash was lost, the player loses a small percentage of power
|
||||||
|
if (!won) {
|
||||||
|
AllGangs[this.facName].power *= (1 / 1.008);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deaths can only occur during X% of clashes
|
||||||
|
if (Math.random() < 0.65) { return; }
|
||||||
|
|
||||||
|
for (let i = this.members.length - 1; i >= 0; --i) {
|
||||||
|
const member = this.members[i];
|
||||||
|
|
||||||
|
// Only members assigned to Territory Warfare can die
|
||||||
|
if (member.task !== "Territory Warfare") { continue; }
|
||||||
|
|
||||||
|
// Chance to die is decreased based on defense
|
||||||
|
const modifiedDeathChance = baseDeathChance / Math.pow(member.def, 0.6);
|
||||||
|
if (Math.random() < modifiedDeathChance) {
|
||||||
|
this.killMember(member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canRecruitMember(): boolean {
|
||||||
|
if (this.members.length >= GangConstants.MaximumGangMembers) { return false; }
|
||||||
|
return (this.respect >= this.getRespectNeededToRecruitMember());
|
||||||
|
}
|
||||||
|
|
||||||
|
getRespectNeededToRecruitMember(): number {
|
||||||
|
// First N gang members are free (can be recruited at 0 respect)
|
||||||
|
const numFreeMembers = 3;
|
||||||
|
if (this.members.length < numFreeMembers) { return 0; }
|
||||||
|
|
||||||
|
const i = this.members.length - (numFreeMembers - 1);
|
||||||
|
return Math.round(0.9 * Math.pow(i, 3) + Math.pow(i, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
recruitMember(name: string): boolean {
|
||||||
|
name = String(name);
|
||||||
|
if (name === "" || !this.canRecruitMember()) { return false; }
|
||||||
|
|
||||||
|
// Check for already-existing names
|
||||||
|
const sameNames = this.members.filter((m) => {
|
||||||
|
return m.name === name;
|
||||||
|
});
|
||||||
|
if (sameNames.length >= 1) { return false; }
|
||||||
|
|
||||||
|
const member = new GangMember(name);
|
||||||
|
this.members.push(member);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Money and Respect gains multiplied by this number (< 1)
|
||||||
|
getWantedPenalty(): number {
|
||||||
|
return (this.respect) / (this.respect + this.wanted);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Calculates power GAIN, which is added onto the Gang's existing power
|
||||||
|
calculatePower(): number {
|
||||||
|
let memberTotal = 0;
|
||||||
|
for (let i = 0; i < this.members.length; ++i) {
|
||||||
|
if (GangMemberTasks.hasOwnProperty(this.members[i].task) && this.members[i].task == "Territory Warfare") {
|
||||||
|
const gain = this.members[i].calculatePower();
|
||||||
|
memberTotal += gain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (0.015 * this.getTerritory() * memberTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
killMember(member: GangMember): void {
|
||||||
|
// Player loses a percentage of total respect, plus whatever respect that member has earned
|
||||||
|
const totalRespect = this.respect;
|
||||||
|
const lostRespect = (0.05 * totalRespect) + member.earnedRespect;
|
||||||
|
this.respect = Math.max(0, totalRespect - lostRespect);
|
||||||
|
|
||||||
|
for (let i = 0; i < this.members.length; ++i) {
|
||||||
|
if (member.name === this.members[i].name) {
|
||||||
|
this.members.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify of death
|
||||||
|
if (this.notifyMemberDeath) {
|
||||||
|
dialogBoxCreate(`${member.name} was killed in a gang clash! You lost ${lostRespect} respect`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ascendMember(member: GangMember, workerScript: WorkerScript): void {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// res is an object with the following format:
|
||||||
|
// {
|
||||||
|
// respect: Amount of respect to deduct
|
||||||
|
// hack/str/def/dex/agi/cha: Ascension multipliers gained for each stat
|
||||||
|
// }
|
||||||
|
const res = member.ascend();
|
||||||
|
this.respect = Math.max(1, this.respect - res.respect);
|
||||||
|
if (workerScript == null) {
|
||||||
|
dialogBoxCreate([`You ascended ${member.name}!`,
|
||||||
|
"",
|
||||||
|
`Your gang lost ${numeralWrapper.formatRespect(res.respect)} respect`,
|
||||||
|
"",
|
||||||
|
`${member.name} gained the following stat multipliers for ascending:`,
|
||||||
|
`Hacking: ${numeralWrapper.formatPercentage(res.hack, 3)}`,
|
||||||
|
`Strength: ${numeralWrapper.formatPercentage(res.str, 3)}`,
|
||||||
|
`Defense: ${numeralWrapper.formatPercentage(res.def, 3)}`,
|
||||||
|
`Dexterity: ${numeralWrapper.formatPercentage(res.dex, 3)}`,
|
||||||
|
`Agility: ${numeralWrapper.formatPercentage(res.agi, 3)}`,
|
||||||
|
`Charisma: ${numeralWrapper.formatPercentage(res.cha, 3)}`].join("<br>"));
|
||||||
|
} else {
|
||||||
|
workerScript.log('ascend', `Ascended Gang member ${member.name}`);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
} catch(e) {
|
||||||
|
if (workerScript == null) {
|
||||||
|
exceptionAlert(e);
|
||||||
|
} else {
|
||||||
|
throw e; // Re-throw, will be caught in the Netscript Function
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cost of upgrade gets cheaper as gang increases in respect + power
|
||||||
|
getDiscount(): number {
|
||||||
|
const power = this.getPower();
|
||||||
|
const respect = this.respect;
|
||||||
|
|
||||||
|
const respectLinearFac = 5e6;
|
||||||
|
const powerLinearFac = 1e6;
|
||||||
|
const discount = Math.pow(respect, 0.01) + respect / respectLinearFac + Math.pow(power, 0.01) + power / powerLinearFac - 1;
|
||||||
|
return Math.max(1, discount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns only valid tasks for this gang. Excludes 'Unassigned'
|
||||||
|
getAllTaskNames(): string[] {
|
||||||
|
let tasks = [];
|
||||||
|
const allTasks = Object.keys(GangMemberTasks);
|
||||||
|
if (this.isHackingGang) {
|
||||||
|
tasks = allTasks.filter((e) => {
|
||||||
|
const task = GangMemberTasks[e];
|
||||||
|
if (task == null) { return false; }
|
||||||
|
if (e === "Unassigned") { return false; }
|
||||||
|
return task.isHacking;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
tasks = allTasks.filter((e) => {
|
||||||
|
const task = GangMemberTasks[e];
|
||||||
|
if (task == null) { return false; }
|
||||||
|
if (e === "Unassigned") { return false; }
|
||||||
|
return task.isCombat;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpgradeCost(upg: GangMemberUpgrade): number {
|
||||||
|
if (upg == null) { return Infinity; }
|
||||||
|
return upg.cost/this.getDiscount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize the current object to a JSON save state.
|
||||||
|
*/
|
||||||
|
toJSON(): any {
|
||||||
|
return Generic_toJSON("Gang", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiatizes a Gang object from a JSON save state.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
static fromJSON(value: any): Gang {
|
||||||
|
return Generic_fromJSON(Gang, value.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Reviver.constructors.Gang = Gang;
|
@ -5,6 +5,7 @@ import { GangMemberUpgrades } from "./GangMemberUpgrades";
|
|||||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
import { GangConstants } from "./data/Constants";
|
import { GangConstants } from "./data/Constants";
|
||||||
import { AllGangs } from "./AllGangs";
|
import { AllGangs } from "./AllGangs";
|
||||||
|
import { IGang } from "./IGang";
|
||||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||||
|
|
||||||
export class GangMember {
|
export class GangMember {
|
||||||
@ -92,7 +93,7 @@ export class GangMember {
|
|||||||
return GangMemberTasks["Unassigned"];
|
return GangMemberTasks["Unassigned"];
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateRespectGain(gang: any): number {
|
calculateRespectGain(gang: IGang): number {
|
||||||
const task = this.getTask();
|
const task = this.getTask();
|
||||||
if (task.baseRespect === 0) return 0;
|
if (task.baseRespect === 0) return 0;
|
||||||
let statWeight = (task.hackWeight/100) * this.hack +
|
let statWeight = (task.hackWeight/100) * this.hack +
|
||||||
@ -109,7 +110,7 @@ export class GangMember {
|
|||||||
return 11 * task.baseRespect * statWeight * territoryMult * respectMult;
|
return 11 * task.baseRespect * statWeight * territoryMult * respectMult;
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateWantedLevelGain(gang: any): number {
|
calculateWantedLevelGain(gang: IGang): number {
|
||||||
const task = this.getTask();
|
const task = this.getTask();
|
||||||
if (task.baseWanted === 0) return 0;
|
if (task.baseWanted === 0) return 0;
|
||||||
let statWeight = (task.hackWeight / 100) * this.hack +
|
let statWeight = (task.hackWeight / 100) * this.hack +
|
||||||
@ -133,7 +134,7 @@ export class GangMember {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateMoneyGain(gang: any): number {
|
calculateMoneyGain(gang: IGang): number {
|
||||||
const task = this.getTask();
|
const task = this.getTask();
|
||||||
if (task.baseMoney === 0) return 0;
|
if (task.baseMoney === 0) return 0;
|
||||||
let statWeight = (task.hackWeight/100) * this.hack +
|
let statWeight = (task.hackWeight/100) * this.hack +
|
||||||
@ -164,7 +165,7 @@ export class GangMember {
|
|||||||
this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles;
|
this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles;
|
||||||
}
|
}
|
||||||
|
|
||||||
recordEarnedRespect(numCycles = 1, gang: any): void {
|
recordEarnedRespect(numCycles = 1, gang: IGang): void {
|
||||||
this.earnedRespect += (this.calculateRespectGain(gang) * numCycles);
|
this.earnedRespect += (this.calculateRespectGain(gang) * numCycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +240,7 @@ export class GangMember {
|
|||||||
this.cha_mult = 1;
|
this.cha_mult = 1;
|
||||||
for (let i = 0; i < this.augmentations.length; ++i) {
|
for (let i = 0; i < this.augmentations.length; ++i) {
|
||||||
const aug = GangMemberUpgrades[this.augmentations[i]];
|
const aug = GangMemberUpgrades[this.augmentations[i]];
|
||||||
aug.apply(this);
|
this.applyUpgrade(aug);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear exp and recalculate stats
|
// Clear exp and recalculate stats
|
||||||
@ -264,7 +265,16 @@ export class GangMember {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: any): boolean {
|
applyUpgrade(upg: GangMemberUpgrade): void {
|
||||||
|
if (upg.mults.str != null) { this.str_mult *= upg.mults.str; }
|
||||||
|
if (upg.mults.def != null) { this.def_mult *= upg.mults.def; }
|
||||||
|
if (upg.mults.dex != null) { this.dex_mult *= upg.mults.dex; }
|
||||||
|
if (upg.mults.agi != null) { this.agi_mult *= upg.mults.agi; }
|
||||||
|
if (upg.mults.cha != null) { this.cha_mult *= upg.mults.cha; }
|
||||||
|
if (upg.mults.hack != null) { this.hack_mult *= upg.mults.hack; }
|
||||||
|
}
|
||||||
|
|
||||||
|
buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: IGang): boolean {
|
||||||
if (typeof upg === 'string') {
|
if (typeof upg === 'string') {
|
||||||
upg = GangMemberUpgrades[upg];
|
upg = GangMemberUpgrades[upg];
|
||||||
}
|
}
|
||||||
@ -276,14 +286,14 @@ export class GangMember {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.money.lt(upg.getCost(gang))) { return false; }
|
if (player.money.lt(gang.getUpgradeCost(upg))) { return false; }
|
||||||
player.loseMoney(upg.getCost(gang));
|
player.loseMoney(gang.getUpgradeCost(upg));
|
||||||
if (upg.type === "g") {
|
if (upg.type === "g") {
|
||||||
this.augmentations.push(upg.name);
|
this.augmentations.push(upg.name);
|
||||||
} else {
|
} else {
|
||||||
this.upgrades.push(upg.name);
|
this.upgrades.push(upg.name);
|
||||||
}
|
}
|
||||||
upg.apply(this);
|
this.applyUpgrade(upg);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,10 +17,6 @@ export class GangMemberUpgrade {
|
|||||||
this.desc = this.createDescription();
|
this.desc = this.createDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
getCost(gang: any): number {
|
|
||||||
return this.cost / gang.getDiscount();
|
|
||||||
}
|
|
||||||
|
|
||||||
createDescription(): string {
|
createDescription(): string {
|
||||||
const lines = ["Increases:"];
|
const lines = ["Increases:"];
|
||||||
if (this.mults.str != null) {
|
if (this.mults.str != null) {
|
||||||
@ -44,16 +40,6 @@ export class GangMemberUpgrade {
|
|||||||
return lines.join("<br>");
|
return lines.join("<br>");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Passes in a GangMember object
|
|
||||||
apply(member: any): void {
|
|
||||||
if (this.mults.str != null) { member.str_mult *= this.mults.str; }
|
|
||||||
if (this.mults.def != null) { member.def_mult *= this.mults.def; }
|
|
||||||
if (this.mults.dex != null) { member.dex_mult *= this.mults.dex; }
|
|
||||||
if (this.mults.agi != null) { member.agi_mult *= this.mults.agi; }
|
|
||||||
if (this.mults.cha != null) { member.cha_mult *= this.mults.cha; }
|
|
||||||
if (this.mults.hack != null) { member.hack_mult *= this.mults.hack; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// User friendly version of type.
|
// User friendly version of type.
|
||||||
getType(): string {
|
getType(): string {
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
|
40
src/Gang/Helpers.tsx
Normal file
40
src/Gang/Helpers.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
import { createElement } from "../../utils/uiHelpers/createElement";
|
||||||
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
|
import { IEngine } from "../IEngine";
|
||||||
|
import { Root } from "./ui/Root";
|
||||||
|
import { Gang } from "./Gang";
|
||||||
|
|
||||||
|
// Gang UI Dom Elements
|
||||||
|
const UIElems: {
|
||||||
|
gangContentCreated: boolean;
|
||||||
|
gangContainer: HTMLElement | null;
|
||||||
|
} = {
|
||||||
|
gangContentCreated: false,
|
||||||
|
gangContainer: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function displayGangContent(engine: IEngine, gang: Gang, player: IPlayer): void {
|
||||||
|
if (!UIElems.gangContentCreated || UIElems.gangContainer == null) {
|
||||||
|
UIElems.gangContentCreated = true;
|
||||||
|
|
||||||
|
// Create gang container
|
||||||
|
UIElems.gangContainer = createElement("div", {
|
||||||
|
id:"gang-container", class:"generic-menupage-container",
|
||||||
|
});
|
||||||
|
|
||||||
|
ReactDOM.render(<Root engine={engine} gang={gang} player={player} />, UIElems.gangContainer);
|
||||||
|
|
||||||
|
const container = document.getElementById("entire-game-container");
|
||||||
|
if(!container) throw new Error('entire-game-container was null');
|
||||||
|
container.appendChild(UIElems.gangContainer);
|
||||||
|
}
|
||||||
|
if(UIElems.gangContainer) UIElems.gangContainer.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearGangUI(): void {
|
||||||
|
if (UIElems.gangContainer instanceof Element) ReactDOM.unmountComponentAtNode(UIElems.gangContainer);
|
||||||
|
UIElems.gangContainer = null;
|
||||||
|
UIElems.gangContentCreated = false;
|
||||||
|
}
|
44
src/Gang/IGang.ts
Normal file
44
src/Gang/IGang.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
||||||
|
import { GangMember } from "./GangMember";
|
||||||
|
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||||
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||||
|
|
||||||
|
export interface IGang {
|
||||||
|
facName: string;
|
||||||
|
members: GangMember[];
|
||||||
|
wanted: number;
|
||||||
|
respect: number;
|
||||||
|
|
||||||
|
isHackingGang: boolean;
|
||||||
|
|
||||||
|
respectGainRate: number;
|
||||||
|
wantedGainRate: number;
|
||||||
|
moneyGainRate: number;
|
||||||
|
|
||||||
|
storedCycles: number;
|
||||||
|
|
||||||
|
storedTerritoryAndPowerCycles: number;
|
||||||
|
|
||||||
|
territoryClashChance: number;
|
||||||
|
territoryWarfareEngaged: boolean;
|
||||||
|
|
||||||
|
notifyMemberDeath: boolean;
|
||||||
|
|
||||||
|
getPower(): number;
|
||||||
|
getTerritory(): number;
|
||||||
|
process(numCycles: number, player: IPlayer): void;
|
||||||
|
processGains(numCycles: number, player: IPlayer): void;
|
||||||
|
processTerritoryAndPowerGains(numCycles: number): void;
|
||||||
|
processExperienceGains(numCycles: number): void;
|
||||||
|
clash(won: boolean): void;
|
||||||
|
canRecruitMember(): boolean;
|
||||||
|
getRespectNeededToRecruitMember(): number;
|
||||||
|
recruitMember(name: string): boolean;
|
||||||
|
getWantedPenalty(): number;
|
||||||
|
calculatePower(): number;
|
||||||
|
killMember(member: GangMember): void;
|
||||||
|
ascendMember(member: GangMember, workerScript: WorkerScript): void;
|
||||||
|
getDiscount(): number;
|
||||||
|
getAllTaskNames(): string[];
|
||||||
|
getUpgradeCost(upg: GangMemberUpgrade): number;
|
||||||
|
}
|
@ -10,7 +10,7 @@ export const GangConstants: {
|
|||||||
MaximumGangMembers: 30,
|
MaximumGangMembers: 30,
|
||||||
CyclesPerTerritoryAndPowerUpdate: 100,
|
CyclesPerTerritoryAndPowerUpdate: 100,
|
||||||
// Portion of upgrade multiplier that is kept after ascending
|
// Portion of upgrade multiplier that is kept after ascending
|
||||||
AscensionMultiplierRatio: 15,
|
AscensionMultiplierRatio: .15,
|
||||||
// Names of possible Gangs
|
// Names of possible Gangs
|
||||||
Names: [
|
Names: [
|
||||||
"Slum Snakes",
|
"Slum Snakes",
|
||||||
|
@ -25,7 +25,7 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
|||||||
for (const upgName in GangMemberUpgrades) {
|
for (const upgName in GangMemberUpgrades) {
|
||||||
if (GangMemberUpgrades.hasOwnProperty(upgName)) {
|
if (GangMemberUpgrades.hasOwnProperty(upgName)) {
|
||||||
const upg = GangMemberUpgrades[upgName];
|
const upg = GangMemberUpgrades[upgName];
|
||||||
if (props.player.money.lt(upg.getCost(props.gang))) continue;
|
if (props.player.money.lt(props.gang.getUpgradeCost(upg))) continue;
|
||||||
if (props.member.upgrades.includes(upgName) || props.member.augmentations.includes(upgName)) continue;
|
if (props.member.upgrades.includes(upgName) || props.member.augmentations.includes(upgName)) continue;
|
||||||
switch (upg.type) {
|
switch (upg.type) {
|
||||||
case "w":
|
case "w":
|
||||||
@ -63,7 +63,7 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
|||||||
setRerender(old => !old);
|
setRerender(old => !old);
|
||||||
}
|
}
|
||||||
return (<a key={upg.name} className="a-link-button tooltip" style={{margin:"2px", padding:"2px", display:"block", fontSize:"11px"}} onClick={onClick}>
|
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))}
|
{upg.name} - {Money(props.gang.getUpgradeCost(upg))}
|
||||||
<span className={left?"tooltiptextleft":"tooltiptext"} dangerouslySetInnerHTML={{__html: upg.desc}} />
|
<span className={left?"tooltiptextleft":"tooltiptext"} dangerouslySetInnerHTML={{__html: upg.desc}} />
|
||||||
</a>);
|
</a>);
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ import {
|
|||||||
calculateWeakenTime,
|
calculateWeakenTime,
|
||||||
} from "./Hacking";
|
} from "./Hacking";
|
||||||
import { calculateServerGrowth } from "./Server/formulas/grow";
|
import { calculateServerGrowth } from "./Server/formulas/grow";
|
||||||
import { Gang } from "./Gang";
|
import { Gang } from "./Gang/Gang";
|
||||||
import { AllGangs } from "./Gang/AllGangs";
|
import { AllGangs } from "./Gang/AllGangs";
|
||||||
import { GangMemberTasks } from "./Gang/GangMemberTasks";
|
import { GangMemberTasks } from "./Gang/GangMemberTasks";
|
||||||
import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades";
|
import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades";
|
||||||
@ -3746,7 +3746,9 @@ function NetscriptFunctions(workerScript) {
|
|||||||
getEquipmentCost: function(equipName) {
|
getEquipmentCost: function(equipName) {
|
||||||
updateDynamicRam("getEquipmentCost", getRamCost("gang", "getEquipmentCost"));
|
updateDynamicRam("getEquipmentCost", getRamCost("gang", "getEquipmentCost"));
|
||||||
checkGangApiAccess("getEquipmentCost");
|
checkGangApiAccess("getEquipmentCost");
|
||||||
return Player.gang.getUpgradeCost(equipName);
|
const upg = GangMemberUpgrades[equipName];
|
||||||
|
if(upg === null) return Infinity;
|
||||||
|
return Player.gang.getUpgradeCost(upg);
|
||||||
},
|
},
|
||||||
getEquipmentType: function(equipName) {
|
getEquipmentType: function(equipName) {
|
||||||
updateDynamicRam("getEquipmentType", getRamCost("gang", "getEquipmentType"));
|
updateDynamicRam("getEquipmentType", getRamCost("gang", "getEquipmentType"));
|
||||||
@ -3768,7 +3770,9 @@ function NetscriptFunctions(workerScript) {
|
|||||||
updateDynamicRam("purchaseEquipment", getRamCost("gang", "purchaseEquipment"));
|
updateDynamicRam("purchaseEquipment", getRamCost("gang", "purchaseEquipment"));
|
||||||
checkGangApiAccess("purchaseEquipment");
|
checkGangApiAccess("purchaseEquipment");
|
||||||
const member = getGangMember("purchaseEquipment", memberName);
|
const member = getGangMember("purchaseEquipment", memberName);
|
||||||
const res = member.buyUpgrade(equipName, Player, Player.gang);
|
const equipment = GangMemberUpgrades[equipName];
|
||||||
|
if(!equipment) return false;
|
||||||
|
const res = member.buyUpgrade(equipment, Player, Player.gang);
|
||||||
if (res) {
|
if (res) {
|
||||||
workerScript.log("purchaseEquipment", `Purchased '${equipName}' for Gang member '${memberName}'`);
|
workerScript.log("purchaseEquipment", `Purchased '${equipName}' for Gang member '${memberName}'`);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Factions } from "../../Faction/Factions";
|
import { Factions } from "../../Faction/Factions";
|
||||||
import { Gang } from "../../Gang";
|
import { Gang } from "../../Gang/Gang";
|
||||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import { Engine } from "./engine";
|
|||||||
import { Faction } from "./Faction/Faction";
|
import { Faction } from "./Faction/Faction";
|
||||||
import { Factions, initFactions } from "./Faction/Factions";
|
import { Factions, initFactions } from "./Faction/Factions";
|
||||||
import { joinFaction } from "./Faction/FactionHelpers";
|
import { joinFaction } from "./Faction/FactionHelpers";
|
||||||
|
import { clearGangUI } from "./Gang/Helpers";
|
||||||
import { updateHashManagerCapacity } from "./Hacknet/HacknetHelpers";
|
import { updateHashManagerCapacity } from "./Hacknet/HacknetHelpers";
|
||||||
import { initMessages } from "./Message/MessageHelpers";
|
import { initMessages } from "./Message/MessageHelpers";
|
||||||
import { prestigeWorkerScripts } from "./NetscriptWorker";
|
import { prestigeWorkerScripts } from "./NetscriptWorker";
|
||||||
@ -333,7 +334,7 @@ function prestigeSourceFile(flume) {
|
|||||||
deleteStockMarket();
|
deleteStockMarket();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Player.inGang()) { Player.gang.clearUI(); }
|
if (Player.inGang()) clearGangUI();
|
||||||
Player.gang = null;
|
Player.gang = null;
|
||||||
Player.corporation = null; resetIndustryResearchTrees();
|
Player.corporation = null; resetIndustryResearchTrees();
|
||||||
Player.bladeburner = null;
|
Player.bladeburner = null;
|
||||||
|
@ -32,6 +32,7 @@ import {
|
|||||||
processPassiveFactionRepGain,
|
processPassiveFactionRepGain,
|
||||||
inviteToFaction,
|
inviteToFaction,
|
||||||
} from "./Faction/FactionHelpers";
|
} from "./Faction/FactionHelpers";
|
||||||
|
import { displayGangContent, clearGangUI } from "./Gang/Helpers";
|
||||||
import { displayInfiltrationContent } from "./Infiltration/Helper";
|
import { displayInfiltrationContent } from "./Infiltration/Helper";
|
||||||
import {
|
import {
|
||||||
getHackingWorkRepGain,
|
getHackingWorkRepGain,
|
||||||
@ -439,7 +440,7 @@ const Engine = {
|
|||||||
loadGangContent: function() {
|
loadGangContent: function() {
|
||||||
Engine.hideAllContent();
|
Engine.hideAllContent();
|
||||||
if (document.getElementById("gang-container") || Player.inGang()) {
|
if (document.getElementById("gang-container") || Player.inGang()) {
|
||||||
Player.gang.displayGangContent(Player);
|
displayGangContent(this, Player.gang, Player);
|
||||||
routing.navigateTo(Page.Gang);
|
routing.navigateTo(Page.Gang);
|
||||||
} else {
|
} else {
|
||||||
Engine.loadTerminalContent();
|
Engine.loadTerminalContent();
|
||||||
@ -534,7 +535,7 @@ const Engine = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Player.inGang()) {
|
if (Player.inGang()) {
|
||||||
Player.gang.clearUI();
|
clearGangUI();
|
||||||
}
|
}
|
||||||
if (Player.corporation instanceof Corporation) {
|
if (Player.corporation instanceof Corporation) {
|
||||||
Player.corporation.clearUI();
|
Player.corporation.clearUI();
|
||||||
|
Loading…
Reference in New Issue
Block a user