From c669e473d17b84ad76754d0d0743710fa9bf1f57 Mon Sep 17 00:00:00 2001 From: Snarling <84951833+Snarling@users.noreply.github.com> Date: Thu, 10 Nov 2022 21:56:46 -0500 Subject: [PATCH] SLEEVE: Fixed inconsistencies in how sleeve work rewards are handled. (#211) --- .../Sleeve/Work/SleeveBladeburnerWork.ts | 22 ++++++------------ .../Sleeve/Work/SleeveClassWork.ts | 4 ++-- .../Sleeve/Work/SleeveCompanyWork.ts | 11 +++++---- .../Sleeve/Work/SleeveCrimeWork.ts | 23 ++++++++----------- .../Sleeve/Work/SleeveFactionWork.ts | 12 ++++------ .../Sleeve/Work/SleeveInfiltrateWork.ts | 3 +-- .../Sleeve/Work/SleeveRecoveryWork.ts | 3 +-- .../Sleeve/Work/SleeveSupportWork.ts | 4 ++-- .../Sleeve/Work/SleeveSynchroWork.ts | 3 +-- src/PersonObjects/Sleeve/Work/Work.ts | 18 ++++++++------- src/PersonObjects/Sleeve/ui/StatsElement.tsx | 2 +- src/ScriptEditor/NetscriptDefinitions.d.ts | 6 ++++- src/Work/Formulas.ts | 4 ++++ src/Work/WorkStats.ts | 16 ++++--------- 14 files changed, 58 insertions(+), 73 deletions(-) diff --git a/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts index 349016bee..85ba00070 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts @@ -4,7 +4,7 @@ import { Sleeve } from "../Sleeve"; import { applySleeveGains, Work, WorkType } from "./Work"; import { CONSTANTS } from "../../../Constants"; import { GeneralActions } from "../../../Bladeburner/data/GeneralActions"; -import { WorkStats } from "../../../Work/WorkStats"; +import { scaleWorkStats } from "../../../Work/WorkStats"; interface SleeveBladeburnerWorkParams { type: string; @@ -31,7 +31,7 @@ export class SleeveBladeburnerWork extends Work { return ret / CONSTANTS._idleSpeed; } - process(sleeve: Sleeve, cycles: number): number { + process(sleeve: Sleeve, cycles: number) { if (!Player.bladeburner) throw new Error("sleeve doing blade work without being a member"); this.cyclesWorked += cycles; const actionIdent = Player.bladeburner.getActionIdFromTypeAndName(this.actionType, this.actionName); @@ -39,35 +39,27 @@ export class SleeveBladeburnerWork extends Work { if (this.actionType === "Contracts") { const action = Player.bladeburner.getActionObject(actionIdent); if (!action) throw new Error(`Error getting ${this.actionName} action object`); - if (action.count <= 0) { - sleeve.stopWork(); - return 0; - } + if (action.count <= 0) return sleeve.stopWork(); } while (this.cyclesWorked > this.cyclesNeeded(sleeve)) { if (this.actionType === "Contracts") { const action = Player.bladeburner.getActionObject(actionIdent); if (!action) throw new Error(`Error getting ${this.actionName} action object`); - if (action.count <= 0) { - sleeve.stopWork(); - return 0; - } + if (action.count <= 0) return sleeve.stopWork(); } const retValue = Player.bladeburner.completeAction(sleeve, actionIdent, false); - let exp: WorkStats | undefined; if (this.actionType === "General") { - exp = GeneralActions[this.actionName]?.exp; + const exp = GeneralActions[this.actionName]?.exp; if (!exp) throw new Error(`Somehow there was no exp for action ${this.actionType} ${this.actionName}`); - applySleeveGains(sleeve, exp, 1); + applySleeveGains(sleeve, scaleWorkStats(exp, sleeve.shockBonus(), false)); } if (this.actionType === "Contracts") { - applySleeveGains(sleeve, retValue, 1); + applySleeveGains(sleeve, scaleWorkStats(retValue, sleeve.shockBonus(), false)); } this.cyclesWorked -= this.cyclesNeeded(sleeve); } - return 0; } APICopy(): Record { diff --git a/src/PersonObjects/Sleeve/Work/SleeveClassWork.ts b/src/PersonObjects/Sleeve/Work/SleeveClassWork.ts index 65c766def..74ff09862 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveClassWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveClassWork.ts @@ -33,11 +33,11 @@ export class SleeveClassWork extends Work { ); } - process(sleeve: Sleeve, cycles: number): number { + process(sleeve: Sleeve, cycles: number) { const rate = this.calculateRates(sleeve); applySleeveGains(sleeve, rate, cycles); - return 0; } + APICopy(): Record { return { type: this.type, diff --git a/src/PersonObjects/Sleeve/Work/SleeveCompanyWork.ts b/src/PersonObjects/Sleeve/Work/SleeveCompanyWork.ts index d027b476b..fbeb92415 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveCompanyWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveCompanyWork.ts @@ -5,7 +5,7 @@ import { LocationName } from "../../../Locations/data/LocationNames"; import { Companies } from "../../../Company/Companies"; import { Company } from "../../../Company/Company"; import { calculateCompanyWorkStats } from "../../../Work/Formulas"; -import { WorkStats } from "../../../Work/WorkStats"; +import { scaleWorkStats, WorkStats } from "../../../Work/WorkStats"; import { influenceStockThroughCompanyWork } from "../../../StockMarket/PlayerInfluencing"; import { Player } from "@player"; import { CompanyPositions } from "../../../Company/CompanyPositions"; @@ -33,16 +33,19 @@ export class SleeveCompanyWork extends Work { getGainRates(sleeve: Sleeve): WorkStats { const company = this.getCompany(); - return calculateCompanyWorkStats(sleeve, company, CompanyPositions[Player.jobs[company.name]], company.favor); + return scaleWorkStats( + calculateCompanyWorkStats(sleeve, company, CompanyPositions[Player.jobs[company.name]], company.favor), + sleeve.shockBonus(), + false, + ); } - process(sleeve: Sleeve, cycles: number): number { + process(sleeve: Sleeve, cycles: number) { const company = this.getCompany(); const gains = this.getGainRates(sleeve); applySleeveGains(sleeve, gains, cycles); company.playerReputation += gains.reputation * cycles; influenceStockThroughCompanyWork(company, gains.reputation, cycles); - return 0; } APICopy(): Record { diff --git a/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts b/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts index a5f170869..0cd91016d 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts @@ -26,29 +26,24 @@ export class SleeveCrimeWork extends Work { } getExp(sleeve: Sleeve): WorkStats { - return calculateCrimeWorkStats(sleeve, this.getCrime()); + return scaleWorkStats(calculateCrimeWorkStats(sleeve, this.getCrime()), sleeve.shockBonus(), false); } cyclesNeeded(): number { return this.getCrime().time / CONSTANTS._idleSpeed; } - process(sleeve: Sleeve, cycles: number): number { + process(sleeve: Sleeve, cycles: number) { this.cyclesWorked += cycles; + if (this.cyclesWorked < this.cyclesNeeded()) return; const crime = this.getCrime(); - let gains = this.getExp(sleeve); - if (this.cyclesWorked >= this.cyclesNeeded()) { - if (Math.random() < crime.successRate(sleeve)) { - Player.karma -= crime.karma * sleeve.syncBonus(); - } else { - gains.money = 0; - gains = scaleWorkStats(gains, 0.25); - } - applySleeveGains(sleeve, gains, cycles); - this.cyclesWorked -= this.cyclesNeeded(); - } - return 0; + const gains = this.getExp(sleeve); + const success = Math.random() < crime.successRate(sleeve); + if (success) Player.karma -= crime.karma * sleeve.syncBonus(); + else gains.money = 0; + applySleeveGains(sleeve, gains, success ? 1 : 0.25); + this.cyclesWorked -= this.cyclesNeeded(); } APICopy(): Record { diff --git a/src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts b/src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts index 252b04ea4..b3bc29f9e 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts @@ -28,7 +28,7 @@ export class SleeveFactionWork extends Work { } getExpRates(sleeve: Sleeve): WorkStats { - return scaleWorkStats(calculateFactionExp(sleeve, this.factionWorkType), sleeve.shockBonus()); + return scaleWorkStats(calculateFactionExp(sleeve, this.factionWorkType), sleeve.shockBonus(), false); } getReputationRate(sleeve: Sleeve): number { @@ -41,17 +41,13 @@ export class SleeveFactionWork extends Work { return f; } - process(sleeve: Sleeve, cycles: number): number { - if (this.factionName === Player.gang?.facName) { - sleeve.stopWork(); - return 0; - } + process(sleeve: Sleeve, cycles: number) { + if (this.factionName === Player.gang?.facName) return sleeve.stopWork(); const exp = this.getExpRates(sleeve); applySleeveGains(sleeve, exp, cycles); const rep = this.getReputationRate(sleeve); - this.getFaction().playerReputation += rep; - return 0; + this.getFaction().playerReputation += rep * cycles; } APICopy(): Record { diff --git a/src/PersonObjects/Sleeve/Work/SleeveInfiltrateWork.ts b/src/PersonObjects/Sleeve/Work/SleeveInfiltrateWork.ts index cc56ccfa8..9b1c40dde 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveInfiltrateWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveInfiltrateWork.ts @@ -20,14 +20,13 @@ export class SleeveInfiltrateWork extends Work { return infiltrateCycles; } - process(_sleeve: Sleeve, cycles: number): number { + process(_sleeve: Sleeve, cycles: number) { if (!Player.bladeburner) throw new Error("sleeve doing blade work without being a member"); this.cyclesWorked += cycles; if (this.cyclesWorked > this.cyclesNeeded()) { this.cyclesWorked -= this.cyclesNeeded(); Player.bladeburner.infiltrateSynthoidCommunities(); } - return 0; } APICopy(): Record { diff --git a/src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts b/src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts index 58ef2cb64..8eb993f72 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts @@ -10,10 +10,9 @@ export class SleeveRecoveryWork extends Work { super(WorkType.RECOVERY); } - process(sleeve: Sleeve, cycles: number): number { + process(sleeve: Sleeve, cycles: number) { sleeve.shock = Math.min(100, sleeve.shock + 0.0002 * cycles); if (sleeve.shock >= 100) sleeve.stopWork(); - return 0; } APICopy(): Record { diff --git a/src/PersonObjects/Sleeve/Work/SleeveSupportWork.ts b/src/PersonObjects/Sleeve/Work/SleeveSupportWork.ts index d881b4b33..365f46e86 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveSupportWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveSupportWork.ts @@ -11,8 +11,8 @@ export class SleeveSupportWork extends Work { Player.bladeburner?.sleeveSupport(true); } - process(): number { - return 0; + process() { + return; } finish(): void { diff --git a/src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts b/src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts index 15cecc43f..3e469d357 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts @@ -12,13 +12,12 @@ export class SleeveSynchroWork extends Work { super(WorkType.SYNCHRO); } - process(sleeve: Sleeve, cycles: number): number { + process(sleeve: Sleeve, cycles: number) { sleeve.sync = Math.min( 100, sleeve.sync + calculateIntelligenceBonus(Player.skills.intelligence, 0.5) * 0.0002 * cycles, ); if (sleeve.sync >= 100) sleeve.stopWork(); - return 0; } APICopy(): Record { diff --git a/src/PersonObjects/Sleeve/Work/Work.ts b/src/PersonObjects/Sleeve/Work/Work.ts index 5cb126bde..9a140dbb8 100644 --- a/src/PersonObjects/Sleeve/Work/Work.ts +++ b/src/PersonObjects/Sleeve/Work/Work.ts @@ -1,14 +1,16 @@ import { Player } from "@player"; import { IReviverValue } from "../../../utils/JSONReviver"; import { Sleeve } from "../Sleeve"; -import { applyWorkStats, applyWorkStatsExp, scaleWorkStats, WorkStats } from "../../../Work/WorkStats"; +import { applyWorkStatsExp, WorkStats } from "../../../Work/WorkStats"; -export const applySleeveGains = (sleeve: Sleeve, rawStats: WorkStats, cycles = 1): void => { - const shockedStats = scaleWorkStats(rawStats, sleeve.shockBonus(), rawStats.money > 0); - applyWorkStatsExp(sleeve, shockedStats, cycles); - const syncStats = scaleWorkStats(shockedStats, sleeve.syncBonus(), rawStats.money > 0); - applyWorkStats(Player, syncStats, cycles, "sleeves"); - Player.sleeves.filter((s) => s !== sleeve).forEach((s) => applyWorkStatsExp(s, syncStats, cycles)); +export const applySleeveGains = (sleeve: Sleeve, shockedStats: WorkStats, mult = 1): void => { + applyWorkStatsExp(sleeve, shockedStats, mult); + Player.gainMoney(shockedStats.money * mult, "sleeves"); + const sync = sleeve.syncBonus(); + // The receiving sleeves and the player do not apply their xp multipliers from augs (avoid double dipping xp mults) + applyWorkStatsExp(Player, shockedStats, mult * sync); + // Sleeves apply their own shock bonus to the XP they receive, even though it is also shocked by the working sleeve + Player.sleeves.forEach((s) => s !== sleeve && applyWorkStatsExp(s, shockedStats, mult * sync * s.shockBonus())); }; export abstract class Work { @@ -18,7 +20,7 @@ export abstract class Work { this.type = type; } - abstract process(sleeve: Sleeve, cycles: number): number; + abstract process(sleeve: Sleeve, cycles: number): void; abstract APICopy(): Record; abstract toJSON(): IReviverValue; finish(): void { diff --git a/src/PersonObjects/Sleeve/ui/StatsElement.tsx b/src/PersonObjects/Sleeve/ui/StatsElement.tsx index c999d8178..cd4d472ee 100644 --- a/src/PersonObjects/Sleeve/ui/StatsElement.tsx +++ b/src/PersonObjects/Sleeve/ui/StatsElement.tsx @@ -133,7 +133,7 @@ export function EarningsElement(props: IProps): React.ReactElement { [`Dexterity Exp:`, `${numeralWrapper.formatExp(CYCLES_PER_SEC * rates.dexExp)} / sec`], [`Agility Exp:`, `${numeralWrapper.formatExp(CYCLES_PER_SEC * rates.agiExp)} / sec`], [`Charisma Exp:`, `${numeralWrapper.formatExp(CYCLES_PER_SEC * rates.chaExp)} / sec`], - [`Reputation:`, ], + [`Reputation:`, ], ]; } diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index f79b65c22..30ced172e 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -3814,10 +3814,14 @@ export interface WorkStats { * @public */ interface WorkFormulas { - crimeSuccessChance(person: Person, crimeType: CrimeType | CrimeNames): number; + crimeSuccessChance(person: Person, crimeType: CrimeType | `${CrimeType}`): number; + /** @returns The WorkStats gained when completing one instance of the specified crime. */ crimeGains(person: Person, crimeType: CrimeType | `${CrimeType}`): WorkStats; + /** @returns The WorkStats applied every game cycle (200ms) by taking the specified class. */ classGains(person: Person, classType: ClassType | `${ClassType}`, locationName: string): WorkStats; + /** @returns The WorkStats applied every game cycle (200ms) by performing the specified faction work. */ factionGains(person: Person, workType: FactionWorkType | `${FactionWorkType}`, favor: number): WorkStats; + /** @returns The WorkStats applied every game cycle (200ms) by performing the specified company work. */ companyGains( person: Person, companyName: string, diff --git a/src/Work/Formulas.ts b/src/Work/Formulas.ts index e6d573832..0f421f308 100644 --- a/src/Work/Formulas.ts +++ b/src/Work/Formulas.ts @@ -63,6 +63,7 @@ export function calculateCrimeWorkStats(person: IPerson, crime: Crime): WorkStat return gains; } +/** @returns faction rep rate per cycle */ export const calculateFactionRep = (person: IPerson, type: FactionWorkType, favor: number): number => { const repFormulas = { [FactionWorkType.HACKING]: getHackingWorkRepGain, @@ -72,6 +73,7 @@ export const calculateFactionRep = (person: IPerson, type: FactionWorkType, favo return repFormulas[type](person, favor); }; +/** @returns per-cycle WorkStats */ export function calculateFactionExp(person: IPerson, type: FactionWorkType): WorkStats { return scaleWorkStats( multWorkStats(FactionWorkStats[type], person.mults), @@ -87,6 +89,7 @@ export function calculateCost(classs: Class, location: Location): number { return classs.earnings.money * location.costMult * discount; } +/** @returns per-cycle WorkStats */ export function calculateClassEarnings(person: IPerson, type: ClassType, locationName: LocationName): WorkStats { const hashManager = Player.hashManager; const classs = Classes[type]; @@ -106,6 +109,7 @@ export function calculateClassEarnings(person: IPerson, type: ClassType, locatio return earnings; } +/** @returns per-cycle WorkStats */ export const calculateCompanyWorkStats = ( worker: IPerson, company: Company, diff --git a/src/Work/WorkStats.ts b/src/Work/WorkStats.ts index 9c8090d4c..59a6f3e25 100644 --- a/src/Work/WorkStats.ts +++ b/src/Work/WorkStats.ts @@ -77,18 +77,10 @@ export const applyWorkStats = (target: Person, workStats: WorkStats, cycles: num return gains; }; -export const applyWorkStatsExp = (target: Person, workStats: WorkStats, cycles: number): WorkStats => { - const gains = { - money: 0, - reputation: 0, - hackExp: workStats.hackExp * cycles, - strExp: workStats.strExp * cycles, - defExp: workStats.defExp * cycles, - dexExp: workStats.dexExp * cycles, - agiExp: workStats.agiExp * cycles, - chaExp: workStats.chaExp * cycles, - intExp: workStats.intExp * cycles, - }; +export const applyWorkStatsExp = (target: Person, workStats: WorkStats, mult = 1): WorkStats => { + const gains = scaleWorkStats(workStats, mult, false); + gains.money = 0; + gains.reputation = 0; target.gainHackingExp(gains.hackExp); target.gainStrengthExp(gains.strExp); target.gainDefenseExp(gains.defExp);