mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-22 15:43: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> = {
|
||||
Version: "0.52.2",
|
||||
|
||||
// Speed (in ms) at which the main loop is updated
|
||||
_idleSpeed: 200,
|
||||
|
||||
/** Max level for any skill, assuming no multipliers. Determined by max numerical value in javascript for experience
|
||||
* and the skill level formula in Player.js. Note that all this means it that when experience hits MAX_INT, then
|
||||
* the player will have this level assuming no multipliers. Multipliers can cause skills to go above this.
|
||||
|
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 { GangConstants } from "./data/Constants";
|
||||
import { AllGangs } from "./AllGangs";
|
||||
import { IGang } from "./IGang";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
|
||||
|
||||
export class GangMember {
|
||||
@ -92,7 +93,7 @@ export class GangMember {
|
||||
return GangMemberTasks["Unassigned"];
|
||||
}
|
||||
|
||||
calculateRespectGain(gang: any): number {
|
||||
calculateRespectGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseRespect === 0) return 0;
|
||||
let statWeight = (task.hackWeight/100) * this.hack +
|
||||
@ -109,7 +110,7 @@ export class GangMember {
|
||||
return 11 * task.baseRespect * statWeight * territoryMult * respectMult;
|
||||
}
|
||||
|
||||
calculateWantedLevelGain(gang: any): number {
|
||||
calculateWantedLevelGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseWanted === 0) return 0;
|
||||
let statWeight = (task.hackWeight / 100) * this.hack +
|
||||
@ -133,7 +134,7 @@ export class GangMember {
|
||||
}
|
||||
}
|
||||
|
||||
calculateMoneyGain(gang: any): number {
|
||||
calculateMoneyGain(gang: IGang): number {
|
||||
const task = this.getTask();
|
||||
if (task.baseMoney === 0) return 0;
|
||||
let statWeight = (task.hackWeight/100) * this.hack +
|
||||
@ -164,7 +165,7 @@ export class GangMember {
|
||||
this.cha_exp += (task.chaWeight / weightDivisor) * difficultyPerCycles;
|
||||
}
|
||||
|
||||
recordEarnedRespect(numCycles = 1, gang: any): void {
|
||||
recordEarnedRespect(numCycles = 1, gang: IGang): void {
|
||||
this.earnedRespect += (this.calculateRespectGain(gang) * numCycles);
|
||||
}
|
||||
|
||||
@ -239,7 +240,7 @@ export class GangMember {
|
||||
this.cha_mult = 1;
|
||||
for (let i = 0; i < this.augmentations.length; ++i) {
|
||||
const aug = GangMemberUpgrades[this.augmentations[i]];
|
||||
aug.apply(this);
|
||||
this.applyUpgrade(aug);
|
||||
}
|
||||
|
||||
// Clear exp and recalculate stats
|
||||
@ -264,7 +265,16 @@ export class GangMember {
|
||||
};
|
||||
}
|
||||
|
||||
buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: any): boolean {
|
||||
applyUpgrade(upg: GangMemberUpgrade): void {
|
||||
if (upg.mults.str != null) { this.str_mult *= upg.mults.str; }
|
||||
if (upg.mults.def != null) { this.def_mult *= upg.mults.def; }
|
||||
if (upg.mults.dex != null) { this.dex_mult *= upg.mults.dex; }
|
||||
if (upg.mults.agi != null) { this.agi_mult *= upg.mults.agi; }
|
||||
if (upg.mults.cha != null) { this.cha_mult *= upg.mults.cha; }
|
||||
if (upg.mults.hack != null) { this.hack_mult *= upg.mults.hack; }
|
||||
}
|
||||
|
||||
buyUpgrade(upg: GangMemberUpgrade, player: IPlayer, gang: IGang): boolean {
|
||||
if (typeof upg === 'string') {
|
||||
upg = GangMemberUpgrades[upg];
|
||||
}
|
||||
@ -276,14 +286,14 @@ export class GangMember {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (player.money.lt(upg.getCost(gang))) { return false; }
|
||||
player.loseMoney(upg.getCost(gang));
|
||||
if (player.money.lt(gang.getUpgradeCost(upg))) { return false; }
|
||||
player.loseMoney(gang.getUpgradeCost(upg));
|
||||
if (upg.type === "g") {
|
||||
this.augmentations.push(upg.name);
|
||||
} else {
|
||||
this.upgrades.push(upg.name);
|
||||
}
|
||||
upg.apply(this);
|
||||
this.applyUpgrade(upg);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -17,10 +17,6 @@ export class GangMemberUpgrade {
|
||||
this.desc = this.createDescription();
|
||||
}
|
||||
|
||||
getCost(gang: any): number {
|
||||
return this.cost / gang.getDiscount();
|
||||
}
|
||||
|
||||
createDescription(): string {
|
||||
const lines = ["Increases:"];
|
||||
if (this.mults.str != null) {
|
||||
@ -44,16 +40,6 @@ export class GangMemberUpgrade {
|
||||
return lines.join("<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.
|
||||
getType(): string {
|
||||
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,
|
||||
CyclesPerTerritoryAndPowerUpdate: 100,
|
||||
// Portion of upgrade multiplier that is kept after ascending
|
||||
AscensionMultiplierRatio: 15,
|
||||
AscensionMultiplierRatio: .15,
|
||||
// Names of possible Gangs
|
||||
Names: [
|
||||
"Slum Snakes",
|
||||
|
@ -25,7 +25,7 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
||||
for (const upgName in GangMemberUpgrades) {
|
||||
if (GangMemberUpgrades.hasOwnProperty(upgName)) {
|
||||
const upg = GangMemberUpgrades[upgName];
|
||||
if (props.player.money.lt(upg.getCost(props.gang))) continue;
|
||||
if (props.player.money.lt(props.gang.getUpgradeCost(upg))) continue;
|
||||
if (props.member.upgrades.includes(upgName) || props.member.augmentations.includes(upgName)) continue;
|
||||
switch (upg.type) {
|
||||
case "w":
|
||||
@ -63,7 +63,7 @@ function GangMemberUpgradePanel(props: IPanelProps): React.ReactElement {
|
||||
setRerender(old => !old);
|
||||
}
|
||||
return (<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}} />
|
||||
</a>);
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ import {
|
||||
calculateWeakenTime,
|
||||
} from "./Hacking";
|
||||
import { calculateServerGrowth } from "./Server/formulas/grow";
|
||||
import { Gang } from "./Gang";
|
||||
import { Gang } from "./Gang/Gang";
|
||||
import { AllGangs } from "./Gang/AllGangs";
|
||||
import { GangMemberTasks } from "./Gang/GangMemberTasks";
|
||||
import { GangMemberUpgrades } from "./Gang/GangMemberUpgrades";
|
||||
@ -3746,7 +3746,9 @@ function NetscriptFunctions(workerScript) {
|
||||
getEquipmentCost: function(equipName) {
|
||||
updateDynamicRam("getEquipmentCost", getRamCost("gang", "getEquipmentCost"));
|
||||
checkGangApiAccess("getEquipmentCost");
|
||||
return Player.gang.getUpgradeCost(equipName);
|
||||
const upg = GangMemberUpgrades[equipName];
|
||||
if(upg === null) return Infinity;
|
||||
return Player.gang.getUpgradeCost(upg);
|
||||
},
|
||||
getEquipmentType: function(equipName) {
|
||||
updateDynamicRam("getEquipmentType", getRamCost("gang", "getEquipmentType"));
|
||||
@ -3768,7 +3770,9 @@ function NetscriptFunctions(workerScript) {
|
||||
updateDynamicRam("purchaseEquipment", getRamCost("gang", "purchaseEquipment"));
|
||||
checkGangApiAccess("purchaseEquipment");
|
||||
const member = getGangMember("purchaseEquipment", memberName);
|
||||
const res = member.buyUpgrade(equipName, Player, Player.gang);
|
||||
const equipment = GangMemberUpgrades[equipName];
|
||||
if(!equipment) return false;
|
||||
const res = member.buyUpgrade(equipment, Player, Player.gang);
|
||||
if (res) {
|
||||
workerScript.log("purchaseEquipment", `Purchased '${equipName}' for Gang member '${memberName}'`);
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { Gang } from "../../Gang";
|
||||
import { Gang } from "../../Gang/Gang";
|
||||
import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
|
||||
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
|
||||
|
||||
|
@ -14,6 +14,7 @@ import { Engine } from "./engine";
|
||||
import { Faction } from "./Faction/Faction";
|
||||
import { Factions, initFactions } from "./Faction/Factions";
|
||||
import { joinFaction } from "./Faction/FactionHelpers";
|
||||
import { clearGangUI } from "./Gang/Helpers";
|
||||
import { updateHashManagerCapacity } from "./Hacknet/HacknetHelpers";
|
||||
import { initMessages } from "./Message/MessageHelpers";
|
||||
import { prestigeWorkerScripts } from "./NetscriptWorker";
|
||||
@ -333,7 +334,7 @@ function prestigeSourceFile(flume) {
|
||||
deleteStockMarket();
|
||||
}
|
||||
|
||||
if (Player.inGang()) { Player.gang.clearUI(); }
|
||||
if (Player.inGang()) clearGangUI();
|
||||
Player.gang = null;
|
||||
Player.corporation = null; resetIndustryResearchTrees();
|
||||
Player.bladeburner = null;
|
||||
|
@ -32,6 +32,7 @@ import {
|
||||
processPassiveFactionRepGain,
|
||||
inviteToFaction,
|
||||
} from "./Faction/FactionHelpers";
|
||||
import { displayGangContent, clearGangUI } from "./Gang/Helpers";
|
||||
import { displayInfiltrationContent } from "./Infiltration/Helper";
|
||||
import {
|
||||
getHackingWorkRepGain,
|
||||
@ -439,7 +440,7 @@ const Engine = {
|
||||
loadGangContent: function() {
|
||||
Engine.hideAllContent();
|
||||
if (document.getElementById("gang-container") || Player.inGang()) {
|
||||
Player.gang.displayGangContent(Player);
|
||||
displayGangContent(this, Player.gang, Player);
|
||||
routing.navigateTo(Page.Gang);
|
||||
} else {
|
||||
Engine.loadTerminalContent();
|
||||
@ -534,7 +535,7 @@ const Engine = {
|
||||
}
|
||||
|
||||
if (Player.inGang()) {
|
||||
Player.gang.clearUI();
|
||||
clearGangUI();
|
||||
}
|
||||
if (Player.corporation instanceof Corporation) {
|
||||
Player.corporation.clearUI();
|
||||
|
Loading…
Reference in New Issue
Block a user