SLEEVE: Fixed inconsistencies in how sleeve work rewards are handled. (#211)

This commit is contained in:
Snarling 2022-11-10 21:56:46 -05:00 committed by GitHub
parent 426ad5f296
commit c669e473d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 58 additions and 73 deletions

@ -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<string, unknown> {

@ -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<string, unknown> {
return {
type: this.type,

@ -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<string, unknown> {

@ -26,30 +26,25 @@ 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);
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();
}
return 0;
}
APICopy(): Record<string, unknown> {
return {

@ -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<string, unknown> {

@ -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<string, unknown> {

@ -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<string, unknown> {

@ -11,8 +11,8 @@ export class SleeveSupportWork extends Work {
Player.bladeburner?.sleeveSupport(true);
}
process(): number {
return 0;
process() {
return;
}
finish(): void {

@ -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<string, unknown> {

@ -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<string, unknown>;
abstract toJSON(): IReviverValue;
finish(): void {

@ -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:`, <ReputationRate reputation={repGain} />],
[`Reputation:`, <ReputationRate reputation={CYCLES_PER_SEC * repGain} />],
];
}

@ -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,

@ -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,

@ -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);