2021-06-16 08:10:47 +02:00
|
|
|
import { GangMemberTask } from "./GangMemberTask";
|
|
|
|
import { GangMemberTasks } from "./GangMemberTasks";
|
|
|
|
import { GangMemberUpgrade } from "./GangMemberUpgrade";
|
|
|
|
import { GangMemberUpgrades } from "./GangMemberUpgrades";
|
2021-06-17 17:24:52 +02:00
|
|
|
import { IAscensionResult } from "./IAscensionResult";
|
2021-06-16 08:10:47 +02:00
|
|
|
import { IPlayer } from "../PersonObjects/IPlayer";
|
|
|
|
import { GangConstants } from "./data/Constants";
|
|
|
|
import { AllGangs } from "./AllGangs";
|
2021-06-17 00:38:29 +02:00
|
|
|
import { IGang } from "./IGang";
|
2021-06-14 21:42:38 +02:00
|
|
|
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
|
|
|
|
2021-06-17 17:24:52 +02:00
|
|
|
interface IMults {
|
|
|
|
hack: number;
|
|
|
|
str: number;
|
|
|
|
def: number;
|
|
|
|
dex: number;
|
|
|
|
agi: number;
|
|
|
|
cha: number;
|
|
|
|
}
|
|
|
|
|
2021-06-14 21:42:38 +02:00
|
|
|
export class GangMember {
|
|
|
|
name: string;
|
2021-06-16 08:10:47 +02:00
|
|
|
task = "Unassigned";
|
|
|
|
|
|
|
|
earnedRespect = 0;
|
|
|
|
|
|
|
|
hack = 1;
|
|
|
|
str = 1;
|
|
|
|
def = 1;
|
|
|
|
dex = 1;
|
|
|
|
agi = 1;
|
|
|
|
cha = 1;
|
|
|
|
|
|
|
|
hack_exp = 0;
|
|
|
|
str_exp = 0;
|
|
|
|
def_exp = 0;
|
|
|
|
dex_exp = 0;
|
|
|
|
agi_exp = 0;
|
|
|
|
cha_exp = 0;
|
|
|
|
|
|
|
|
hack_mult = 1;
|
|
|
|
str_mult = 1;
|
|
|
|
def_mult = 1;
|
|
|
|
dex_mult = 1;
|
|
|
|
agi_mult = 1;
|
|
|
|
cha_mult = 1;
|
|
|
|
|
|
|
|
hack_asc_mult = 1;
|
|
|
|
str_asc_mult = 1;
|
|
|
|
def_asc_mult = 1;
|
|
|
|
dex_asc_mult = 1;
|
|
|
|
agi_asc_mult = 1;
|
|
|
|
cha_asc_mult = 1;
|
2021-06-14 21:42:38 +02:00
|
|
|
|
|
|
|
upgrades: string[] = []; // Names of upgrades
|
|
|
|
augmentations: string[] = []; // Names of augmentations only
|
|
|
|
|
2021-06-16 08:10:47 +02:00
|
|
|
constructor(name = "") {
|
2021-06-14 21:42:38 +02:00
|
|
|
this.name = name;
|
|
|
|
}
|
|
|
|
|
2021-06-16 08:26:10 +02:00
|
|
|
calculateSkill(exp: number, mult = 1): number {
|
2021-06-16 08:10:47 +02:00
|
|
|
return Math.max(Math.floor(mult * (32 * Math.log(exp + 534.5) - 200)), 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
updateSkillLevels(): void {
|
|
|
|
this.hack = this.calculateSkill(this.hack_exp, this.hack_mult * this.hack_asc_mult);
|
|
|
|
this.str = this.calculateSkill(this.str_exp, this.str_mult * this.str_asc_mult);
|
|
|
|
this.def = this.calculateSkill(this.def_exp, this.def_mult * this.def_asc_mult);
|
|
|
|
this.dex = this.calculateSkill(this.dex_exp, this.dex_mult * this.dex_asc_mult);
|
|
|
|
this.agi = this.calculateSkill(this.agi_exp, this.agi_mult * this.agi_asc_mult);
|
|
|
|
this.cha = this.calculateSkill(this.cha_exp, this.cha_mult * this.cha_asc_mult);
|
|
|
|
}
|
|
|
|
|
|
|
|
calculatePower(): number {
|
2021-06-18 00:59:45 +02:00
|
|
|
return (this.hack +
|
|
|
|
this.str +
|
|
|
|
this.def +
|
|
|
|
this.dex +
|
|
|
|
this.agi +
|
|
|
|
this.cha) / 95;
|
2021-06-16 08:10:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
assignToTask(taskName: string): boolean {
|
2021-06-17 17:24:52 +02:00
|
|
|
if (!GangMemberTasks.hasOwnProperty(taskName)) {
|
2021-06-16 08:10:47 +02:00
|
|
|
this.task = "Unassigned";
|
|
|
|
return false;
|
|
|
|
}
|
2021-06-17 17:24:52 +02:00
|
|
|
this.task = taskName;
|
|
|
|
return true;
|
2021-06-16 08:10:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
unassignFromTask(): void {
|
|
|
|
this.task = "Unassigned";
|
|
|
|
}
|
|
|
|
|
|
|
|
getTask(): GangMemberTask {
|
|
|
|
// TODO(hydroflame): transfer that to a save file migration function
|
|
|
|
// Backwards compatibility
|
|
|
|
if ((this.task as any) instanceof GangMemberTask) {
|
|
|
|
this.task = (this.task as any).name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (GangMemberTasks.hasOwnProperty(this.task)) {
|
|
|
|
return GangMemberTasks[this.task];
|
|
|
|
}
|
|
|
|
return GangMemberTasks["Unassigned"];
|
|
|
|
}
|
|
|
|
|
2021-06-17 00:38:29 +02:00
|
|
|
calculateRespectGain(gang: IGang): number {
|
2021-06-16 08:10:47 +02:00
|
|
|
const task = this.getTask();
|
|
|
|
if (task.baseRespect === 0) return 0;
|
2021-06-16 08:26:10 +02:00
|
|
|
let statWeight = (task.hackWeight/100) * this.hack +
|
2021-06-16 08:10:47 +02:00
|
|
|
(task.strWeight/100) * this.str +
|
|
|
|
(task.defWeight/100) * this.def +
|
|
|
|
(task.dexWeight/100) * this.dex +
|
|
|
|
(task.agiWeight/100) * this.agi +
|
|
|
|
(task.chaWeight/100) * this.cha;
|
|
|
|
statWeight -= (4 * task.difficulty);
|
|
|
|
if (statWeight <= 0) return 0;
|
|
|
|
const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.respect) / 100;
|
|
|
|
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
|
|
|
|
const respectMult = gang.getWantedPenalty();
|
|
|
|
return 11 * task.baseRespect * statWeight * territoryMult * respectMult;
|
|
|
|
}
|
|
|
|
|
2021-06-17 00:38:29 +02:00
|
|
|
calculateWantedLevelGain(gang: IGang): number {
|
2021-06-16 08:10:47 +02:00
|
|
|
const task = this.getTask();
|
|
|
|
if (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;
|
|
|
|
}
|
2021-06-18 00:59:45 +02:00
|
|
|
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);
|
2021-06-16 08:10:47 +02:00
|
|
|
}
|
|
|
|
|
2021-06-17 00:38:29 +02:00
|
|
|
calculateMoneyGain(gang: IGang): number {
|
2021-06-16 08:10:47 +02:00
|
|
|
const task = this.getTask();
|
|
|
|
if (task.baseMoney === 0) return 0;
|
|
|
|
let statWeight = (task.hackWeight/100) * this.hack +
|
|
|
|
(task.strWeight/100) * this.str +
|
|
|
|
(task.defWeight/100) * this.def +
|
|
|
|
(task.dexWeight/100) * this.dex +
|
|
|
|
(task.agiWeight/100) * this.agi +
|
|
|
|
(task.chaWeight/100) * this.cha;
|
|
|
|
statWeight -= (3.2 * task.difficulty);
|
|
|
|
if (statWeight <= 0) return 0;
|
|
|
|
const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.money) / 100;
|
|
|
|
if (isNaN(territoryMult) || territoryMult <= 0) return 0;
|
|
|
|
const respectMult = gang.getWantedPenalty();
|
|
|
|
return 5 * task.baseMoney * statWeight * territoryMult * respectMult;
|
|
|
|
}
|
|
|
|
|
2021-06-17 17:24:52 +02:00
|
|
|
expMult(): IMults {
|
|
|
|
return {
|
|
|
|
hack: (this.hack_mult-1)/10+1,
|
|
|
|
str: (this.str_mult-1)/10+1,
|
|
|
|
def: (this.def_mult-1)/10+1,
|
|
|
|
dex: (this.dex_mult-1)/10+1,
|
|
|
|
agi: (this.agi_mult-1)/10+1,
|
|
|
|
cha: (this.cha_mult-1)/10+1,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-06-16 08:26:10 +02:00
|
|
|
gainExperience(numCycles = 1): void {
|
2021-06-16 08:10:47 +02:00
|
|
|
const task = this.getTask();
|
|
|
|
if (task === GangMemberTasks["Unassigned"]) return;
|
|
|
|
const difficultyMult = Math.pow(task.difficulty, 0.9);
|
|
|
|
const difficultyPerCycles = difficultyMult * numCycles;
|
|
|
|
const weightDivisor = 1500;
|
2021-06-17 17:24:52 +02:00
|
|
|
const expMult = this.expMult();
|
|
|
|
this.hack_exp += (task.hackWeight / weightDivisor) * difficultyPerCycles * expMult.hack;
|
|
|
|
this.str_exp += (task.strWeight / weightDivisor) * difficultyPerCycles * expMult.str;
|
|
|
|
this.def_exp += (task.defWeight / weightDivisor) * difficultyPerCycles * expMult.def;
|
|
|
|
this.dex_exp += (task.dexWeight / weightDivisor) * difficultyPerCycles * expMult.dex;
|
|
|
|
this.agi_exp += (task.agiWeight / weightDivisor) * difficultyPerCycles * expMult.agi;
|
|
|
|
this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles * expMult.cha;
|
2021-06-16 08:10:47 +02:00
|
|
|
}
|
|
|
|
|
2021-06-17 00:38:29 +02:00
|
|
|
recordEarnedRespect(numCycles = 1, gang: IGang): void {
|
2021-06-16 08:10:47 +02:00
|
|
|
this.earnedRespect += (this.calculateRespectGain(gang) * numCycles);
|
|
|
|
}
|
2021-06-14 21:42:38 +02:00
|
|
|
|
2021-06-17 17:24:52 +02:00
|
|
|
getAscensionResults(): IMults {
|
2021-06-16 08:10:47 +02:00
|
|
|
return {
|
2021-06-18 01:45:36 +02:00
|
|
|
hack: this.hack_exp,
|
|
|
|
str: this.str_exp,
|
|
|
|
def: this.def_exp,
|
|
|
|
dex: this.dex_exp,
|
|
|
|
agi: this.agi_exp,
|
|
|
|
cha: this.cha_exp,
|
2021-06-16 08:10:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-17 17:24:52 +02:00
|
|
|
ascend(): IAscensionResult {
|
2021-06-16 08:10:47 +02:00
|
|
|
const res = this.getAscensionResults();
|
2021-06-18 01:45:36 +02:00
|
|
|
this.hack_asc_mult += res.hack;
|
|
|
|
this.str_asc_mult += res.str;
|
|
|
|
this.def_asc_mult += res.def;
|
|
|
|
this.dex_asc_mult += res.dex;
|
|
|
|
this.agi_asc_mult += res.agi;
|
|
|
|
this.cha_asc_mult += res.cha;
|
2021-06-16 08:10:47 +02:00
|
|
|
|
|
|
|
// 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) {
|
2021-06-16 08:26:10 +02:00
|
|
|
const aug = GangMemberUpgrades[this.augmentations[i]];
|
2021-06-17 00:38:29 +02:00
|
|
|
this.applyUpgrade(aug);
|
2021-06-16 08:10:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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,
|
2021-06-18 01:45:36 +02:00
|
|
|
hack: res.hack,
|
|
|
|
str: res.str,
|
|
|
|
def: res.def,
|
|
|
|
dex: res.dex,
|
|
|
|
agi: res.agi,
|
|
|
|
cha: res.cha,
|
2021-06-16 08:10:47 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-06-17 00:38:29 +02:00
|
|
|
applyUpgrade(upg: GangMemberUpgrade): void {
|
2021-06-18 00:59:45 +02:00
|
|
|
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;
|
2021-06-17 00:38:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: IGang): boolean {
|
2021-06-16 08:10:47 +02:00
|
|
|
// Prevent purchasing of already-owned upgrades
|
2021-06-18 00:59:45 +02:00
|
|
|
if (this.augmentations.includes(upg.name) ||
|
|
|
|
this.upgrades.includes(upg.name)) return false;
|
2021-06-16 08:10:47 +02:00
|
|
|
|
2021-06-18 00:59:45 +02:00
|
|
|
if (player.money.lt(gang.getUpgradeCost(upg))) return false;
|
2021-06-17 00:38:29 +02:00
|
|
|
player.loseMoney(gang.getUpgradeCost(upg));
|
2021-06-16 08:10:47 +02:00
|
|
|
if (upg.type === "g") {
|
|
|
|
this.augmentations.push(upg.name);
|
|
|
|
} else {
|
|
|
|
this.upgrades.push(upg.name);
|
|
|
|
}
|
2021-06-17 00:38:29 +02:00
|
|
|
this.applyUpgrade(upg);
|
2021-06-16 08:10:47 +02:00
|
|
|
return true;
|
|
|
|
}
|
2021-06-14 21:42:38 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Serialize the current object to a JSON save state.
|
|
|
|
*/
|
|
|
|
toJSON(): any {
|
|
|
|
return Generic_toJSON("GangMember", this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initiatizes a GangMember object from a JSON save state.
|
|
|
|
*/
|
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
|
|
static fromJSON(value: any): GangMember {
|
|
|
|
return Generic_fromJSON(GangMember, value.data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Reviver.constructors.GangMember = GangMember;
|