work on sleeve new work system

This commit is contained in:
Olivier Gagnon 2022-07-27 20:37:32 -04:00
parent 315b2adf30
commit ebe953b498
19 changed files with 816 additions and 467 deletions

@ -37,8 +37,18 @@ import { BladeburnerConstants } from "../../Bladeburner/data/Constants";
import { numeralWrapper } from "../../ui/numeralFormat";
import { capitalizeFirstLetter, capitalizeEachWord } from "../../utils/StringHelperFunctions";
import { FactionWorkType } from "../../Work/data/FactionWorkType";
import { Work } from "./Work/Work";
import { SleeveClassWork } from "./Work/SleeveClassWork";
import { ClassType } from "../../Work/ClassWork";
import { SleeveSynchroWork } from "./Work/SleeveSynchroWork";
import { SleeveRecoveryWork } from "./Work/SleeveRecoveryWork";
import { SleeveFactionWork } from "./Work/SleeveFactionWork";
import { SleeveCompanyWork } from "./Work/SleeveCompanyWork";
import { SleeveBladeburnerGeneralWork } from "./Work/SleeveBladeburnerGeneralActionWork";
import { SleeveInfiltrateWork } from "./Work/SleeveInfiltrateWork";
export class Sleeve extends Person {
currentWork: Work | null = null;
/**
* Stores the name of the class that the player is currently taking
*/
@ -149,6 +159,13 @@ export class Sleeve extends Person {
}
}
shockBonus(): number {
return this.shock / 100;
}
syncBonus(): number {
return this.sync / 100;
}
/**
* Commit crimes
*/
@ -184,9 +201,8 @@ export class Sleeve extends Person {
/**
* Called to stop the current task
*/
finishTask(p: IPlayer): ITaskTracker {
let retValue: ITaskTracker = createTaskTracker(); // Amount of exp to be gained by other sleeves
finishTask(p: IPlayer): void {
this.currentWork = null;
if (this.currentTask === SleeveTaskType.Crime) {
// For crimes, all experience and money is gained at the end
if (this.currentTaskTime >= this.currentTaskMaxTime) {
@ -194,7 +210,7 @@ export class Sleeve extends Person {
if (!crime) {
console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`);
this.resetTaskStatus(p);
return retValue;
return;
}
if (Math.random() < crime.successRate(this)) {
// Success
@ -205,22 +221,22 @@ export class Sleeve extends Person {
const key = keysForIteration[i];
successGainRates[key] = this.gainRatesForTask[key] * 2;
}
retValue = this.gainExperience(p, successGainRates);
this.gainExperience(p, successGainRates);
this.gainMoney(p, this.gainRatesForTask);
p.karma -= crime.karma * (this.sync / 100);
} else {
retValue = this.gainExperience(p, this.gainRatesForTask);
this.gainExperience(p, this.gainRatesForTask);
}
// Do not reset task to IDLE
this.currentTaskTime = 0;
return retValue;
return;
}
} else if (this.currentTask === SleeveTaskType.Bladeburner) {
if (this.currentTaskMaxTime === 0) {
this.currentTaskTime = 0;
return retValue;
return;
}
// For bladeburner, all experience and money is gained at the end
const bb = p.bladeburner;
@ -228,14 +244,14 @@ export class Sleeve extends Person {
const errorLogText = `bladeburner is null`;
console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`);
this.resetTaskStatus(p);
return retValue;
return;
}
if (this.currentTaskTime >= this.currentTaskMaxTime) {
if (this.bbAction === "Infiltrate synthoids") {
bb.infiltrateSynthoidCommunities(p);
this.currentTaskTime = 0;
return retValue;
return;
}
let type: string;
let name: string;
@ -252,19 +268,19 @@ export class Sleeve extends Person {
const errorLogText = `Invalid action: type='${type}' name='${name}'`;
console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`);
this.resetTaskStatus(p);
return retValue;
return;
}
const action = bb.getActionObject(actionIdent);
if ((action?.count ?? 0) > 0) {
const bbRetValue = bb.completeAction(p, this, actionIdent, false);
if (bbRetValue) {
retValue = this.gainExperience(p, bbRetValue);
this.gainExperience(p, bbRetValue);
this.gainMoney(p, bbRetValue);
// Do not reset task to IDLE
this.currentTaskTime = 0;
return retValue;
return;
}
}
}
@ -272,7 +288,7 @@ export class Sleeve extends Person {
this.resetTaskStatus(p);
return retValue;
return;
}
/**
@ -431,25 +447,7 @@ export class Sleeve extends Person {
* Only applicable when working for company or faction
*/
getRepGain(p: IPlayer): number {
if (this.currentTask === SleeveTaskType.Faction) {
let favorMult = 1;
const fac: Faction | null = Factions[this.currentTaskLocation];
if (fac != null) {
favorMult = 1 + fac.favor / 100;
}
switch (this.factionWorkType) {
case FactionWorkType.HACKING:
return this.getFactionHackingWorkRepGain() * (this.shock / 100) * favorMult;
case FactionWorkType.FIELD:
return this.getFactionFieldWorkRepGain() * (this.shock / 100) * favorMult;
case FactionWorkType.SECURITY:
return this.getFactionSecurityWorkRepGain() * (this.shock / 100) * favorMult;
default:
console.warn(`Invalid Sleeve.factionWorkType property in Sleeve.getRepGain(): ${this.factionWorkType}`);
return 0;
}
} else if (this.currentTask === SleeveTaskType.Company) {
if (this.currentTask === SleeveTaskType.Company) {
const companyName: string = this.currentTaskLocation;
const company: Company | null = Companies[companyName];
if (company == null) {
@ -532,12 +530,16 @@ export class Sleeve extends Person {
// Only process once every second (5 cycles)
const CyclesPerSecond = 1000 / CONSTANTS.MilliPerCycle;
this.storedCycles += numCycles;
if (this.storedCycles < CyclesPerSecond) {
return;
}
if (this.storedCycles < CyclesPerSecond) return;
let cyclesUsed = this.storedCycles;
cyclesUsed = Math.min(cyclesUsed, 15);
if (this.currentWork) {
this.currentWork.process(p, this, cyclesUsed);
this.storedCycles -= cyclesUsed;
return;
}
let time = cyclesUsed * CONSTANTS.MilliPerCycle;
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime + time > this.currentTaskMaxTime) {
time = this.currentTaskMaxTime - this.currentTaskTime;
@ -556,36 +558,6 @@ export class Sleeve extends Person {
this.shock = Math.min(100, this.shock + 0.0001 * cyclesUsed);
switch (this.currentTask) {
case SleeveTaskType.Idle:
break;
case SleeveTaskType.Class:
case SleeveTaskType.Gym:
this.updateTaskGainRates(p);
this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
break;
case SleeveTaskType.Faction: {
this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
// Gain faction reputation
const fac: Faction = Factions[this.currentTaskLocation];
if (!(fac instanceof Faction)) {
console.error(`Invalid faction for Sleeve task: ${this.currentTaskLocation}`);
break;
}
// If the player has a gang with the faction the sleeve is working
// for, we need to reset the sleeve's task
if (p.gang) {
if (fac.name === p.gang.facName) {
this.resetTaskStatus(p);
}
}
fac.playerReputation += this.getRepGain(p) * cyclesUsed;
break;
}
case SleeveTaskType.Company: {
this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
@ -599,16 +571,6 @@ export class Sleeve extends Person {
company.playerReputation += this.getRepGain(p) * cyclesUsed;
break;
}
case SleeveTaskType.Recovery:
this.shock = Math.min(100, this.shock + 0.0002 * cyclesUsed);
if (this.shock >= 100) this.resetTaskStatus(p);
break;
case SleeveTaskType.Synchro:
this.sync = Math.min(100, this.sync + p.getIntelligenceBonus(0.5) * 0.0002 * cyclesUsed);
if (this.sync >= 100) this.resetTaskStatus(p);
break;
default:
break;
}
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime >= this.currentTaskMaxTime) {
@ -630,15 +592,10 @@ export class Sleeve extends Person {
* Resets all parameters used to keep information about the current task
*/
resetTaskStatus(p: IPlayer): void {
this.currentWork = null;
if (this.bbAction == "Support main sleeve") {
p.bladeburner?.sleeveSupport(false);
}
if (this.currentTask == SleeveTaskType.Class) {
const retVal = createTaskTracker();
retVal.int = CONSTANTS.IntelligenceClassBaseExpGain * Math.round(this.currentTaskTime / 1000);
const r = this.gainExperience(p, retVal);
p.sleeves.filter((s) => s != this).forEach((s) => s.gainExperience(p, r, 1, true));
}
this.earningsForTask = createTaskTracker();
this.gainRatesForTask = createTaskTracker();
this.currentTask = SleeveTaskType.Idle;
@ -654,24 +611,22 @@ export class Sleeve extends Person {
}
shockRecovery(p: IPlayer): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
if (this.currentTask !== SleeveTaskType.Idle || this.currentWork === null) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
}
this.currentTask = SleeveTaskType.Recovery;
this.currentWork = new SleeveRecoveryWork();
return true;
}
synchronize(p: IPlayer): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
if (this.currentTask !== SleeveTaskType.Idle || this.currentWork !== null) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
}
this.currentTask = SleeveTaskType.Synchro;
this.currentWork = new SleeveSynchroWork();
return true;
}
@ -679,7 +634,7 @@ export class Sleeve extends Person {
* Take a course at a university
*/
takeUniversityCourse(p: IPlayer, universityName: string, className: string): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
if (this.currentTask !== SleeveTaskType.Idle || this.currentWork) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
@ -687,58 +642,54 @@ export class Sleeve extends Person {
// Set exp/money multipliers based on which university.
// Also check that the sleeve is in the right city
let costMult = 1;
let loc: LocationName | undefined;
switch (universityName.toLowerCase()) {
case LocationName.AevumSummitUniversity.toLowerCase():
if (this.city !== CityName.Aevum) {
return false;
}
this.currentTaskLocation = LocationName.AevumSummitUniversity;
costMult = 4;
case LocationName.AevumSummitUniversity.toLowerCase(): {
if (this.city !== CityName.Aevum) return false;
loc = LocationName.AevumSummitUniversity;
break;
case LocationName.Sector12RothmanUniversity.toLowerCase():
if (this.city !== CityName.Sector12) {
return false;
}
this.currentTaskLocation = LocationName.Sector12RothmanUniversity;
costMult = 3;
}
case LocationName.Sector12RothmanUniversity.toLowerCase(): {
if (this.city !== CityName.Sector12) return false;
loc = LocationName.Sector12RothmanUniversity;
break;
case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():
if (this.city !== CityName.Volhaven) {
return false;
}
this.currentTaskLocation = LocationName.VolhavenZBInstituteOfTechnology;
costMult = 5;
}
case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase(): {
if (this.city !== CityName.Volhaven) return false;
loc = LocationName.VolhavenZBInstituteOfTechnology;
break;
default:
return false;
}
}
if (!loc) return false;
// Set experience/money gains based on class
let classType: ClassType | undefined;
switch (className.toLowerCase()) {
case "study computer science":
classType = ClassType.StudyComputerScience;
break;
case "data structures":
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassDataStructuresBaseCost * costMult);
classType = ClassType.DataStructures;
break;
case "networks":
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassNetworksBaseCost * costMult);
classType = ClassType.Networks;
break;
case "algorithms":
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassAlgorithmsBaseCost * costMult);
classType = ClassType.Algorithms;
break;
case "management":
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassManagementBaseCost * costMult);
classType = ClassType.Management;
break;
case "leadership":
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassLeadershipBaseCost * costMult);
classType = ClassType.Leadership;
break;
default:
return false;
}
if (!classType) return false;
this.className = className;
this.currentTask = SleeveTaskType.Class;
this.currentWork = new SleeveClassWork({
classType: classType,
location: loc,
});
return true;
}
@ -767,99 +718,6 @@ export class Sleeve extends Person {
return true;
}
updateTaskGainRates(p: IPlayer): void {
if (this.currentTask === SleeveTaskType.Class) {
let expMult = 1;
switch (this.currentTaskLocation.toLowerCase()) {
case LocationName.AevumSummitUniversity.toLowerCase():
expMult = 3;
break;
case LocationName.Sector12RothmanUniversity.toLowerCase():
expMult = 2;
break;
case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():
expMult = 4;
break;
default:
return;
}
const totalExpMult = expMult * p.hashManager.getStudyMult();
switch (this.className.toLowerCase()) {
case "study computer science":
this.gainRatesForTask.hack =
CONSTANTS.ClassStudyComputerScienceBaseExp * totalExpMult * this.mults.hacking_exp;
break;
case "data structures":
this.gainRatesForTask.hack = CONSTANTS.ClassDataStructuresBaseExp * totalExpMult * this.mults.hacking_exp;
break;
case "networks":
this.gainRatesForTask.hack = CONSTANTS.ClassNetworksBaseExp * totalExpMult * this.mults.hacking_exp;
break;
case "algorithms":
this.gainRatesForTask.hack = CONSTANTS.ClassAlgorithmsBaseExp * totalExpMult * this.mults.hacking_exp;
break;
case "management":
this.gainRatesForTask.cha = CONSTANTS.ClassManagementBaseExp * totalExpMult * this.mults.charisma_exp;
break;
case "leadership":
this.gainRatesForTask.cha = CONSTANTS.ClassLeadershipBaseExp * totalExpMult * this.mults.charisma_exp;
break;
default:
break;
}
return;
}
if (this.currentTask === SleeveTaskType.Gym) {
// Get gym exp multiplier
let expMult = 1;
switch (this.currentTaskLocation.toLowerCase()) {
case LocationName.AevumCrushFitnessGym.toLowerCase():
expMult = 2;
break;
case LocationName.AevumSnapFitnessGym.toLowerCase():
expMult = 5;
break;
case LocationName.Sector12IronGym.toLowerCase():
expMult = 1;
break;
case LocationName.Sector12PowerhouseGym.toLowerCase():
expMult = 10;
break;
case LocationName.VolhavenMilleniumFitnessGym.toLowerCase():
expMult = 4;
break;
default:
return;
}
// Set stat gain rate
const baseGymExp = 1;
const totalExpMultiplier = p.hashManager.getTrainingMult() * expMult;
switch (this.gymStatType) {
case "none": // Note : due to the way Sleeve.workOutAtGym() is currently designed, this should never happend.
break;
case "str":
this.gainRatesForTask.str = baseGymExp * totalExpMultiplier * this.mults.strength_exp;
break;
case "def":
this.gainRatesForTask.def = baseGymExp * totalExpMultiplier * this.mults.defense_exp;
break;
case "dex":
this.gainRatesForTask.dex = baseGymExp * totalExpMultiplier * this.mults.dexterity_exp;
break;
case "agi":
this.gainRatesForTask.agi = baseGymExp * totalExpMultiplier * this.mults.agility_exp;
break;
}
return;
}
console.warn(`Sleeve.updateTaskGainRates() called for unexpected task type ${this.currentTask}`);
}
upgradeMemory(n: number): void {
if (n < 0) {
console.warn(`Sleeve.upgradeMemory() called with negative value: ${n}`);
@ -878,7 +736,7 @@ export class Sleeve extends Person {
return false;
}
if (this.currentTask !== SleeveTaskType.Idle) {
if (this.currentTask !== SleeveTaskType.Idle || this.currentWork !== null) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
@ -886,50 +744,10 @@ export class Sleeve extends Person {
const company: Company | null = Companies[companyName];
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
if (company == null) {
return false;
}
if (companyPosition == null) {
return false;
}
this.gainRatesForTask.money =
companyPosition.baseSalary *
company.salaryMultiplier *
this.mults.work_money *
BitNodeMultipliers.CompanyWorkMoney;
this.gainRatesForTask.hack =
companyPosition.hackingExpGain *
company.expMultiplier *
this.mults.hacking_exp *
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.str =
companyPosition.strengthExpGain *
company.expMultiplier *
this.mults.strength_exp *
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.def =
companyPosition.defenseExpGain *
company.expMultiplier *
this.mults.defense_exp *
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.dex =
companyPosition.dexterityExpGain *
company.expMultiplier *
this.mults.dexterity_exp *
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.agi =
companyPosition.agilityExpGain *
company.expMultiplier *
this.mults.agility_exp *
BitNodeMultipliers.CompanyWorkExpGain;
this.gainRatesForTask.cha =
companyPosition.charismaExpGain *
company.expMultiplier *
this.mults.charisma_exp *
BitNodeMultipliers.CompanyWorkExpGain;
if (company == null) return false;
if (companyPosition == null) return false;
this.currentTaskLocation = companyName;
this.currentTask = SleeveTaskType.Company;
this.currentWork = new SleeveCompanyWork({ companyName: companyName });
return true;
}
@ -944,7 +762,7 @@ export class Sleeve extends Person {
return false;
}
if (this.currentTask !== SleeveTaskType.Idle) {
if (this.currentTask !== SleeveTaskType.Idle || this.currentWork === null) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
@ -953,40 +771,25 @@ export class Sleeve extends Person {
const factionInfo = faction.getInfo();
// Set type of work (hacking/field/security), and the experience gains
const sanitizedWorkType: string = workType.toLowerCase();
const sanitizedWorkType = workType.toLowerCase();
let factionWorkType: FactionWorkType;
if (sanitizedWorkType.includes("hack")) {
if (!factionInfo.offerHackingWork) {
return false;
}
this.factionWorkType = FactionWorkType.HACKING;
this.gainRatesForTask.hack = 0.15 * this.mults.hacking_exp * BitNodeMultipliers.FactionWorkExpGain;
if (!factionInfo.offerHackingWork) return false;
factionWorkType = FactionWorkType.HACKING;
} else if (sanitizedWorkType.includes("field")) {
if (!factionInfo.offerFieldWork) {
return false;
}
this.factionWorkType = FactionWorkType.FIELD;
this.gainRatesForTask.hack = 0.1 * this.mults.hacking_exp * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.str = 0.1 * this.mults.strength_exp * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.def = 0.1 * this.mults.defense_exp * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.dex = 0.1 * this.mults.dexterity_exp * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.agi = 0.1 * this.mults.agility_exp * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.cha = 0.1 * this.mults.charisma_exp * BitNodeMultipliers.FactionWorkExpGain;
if (!factionInfo.offerFieldWork) return false;
factionWorkType = FactionWorkType.FIELD;
} else if (sanitizedWorkType.includes("security")) {
if (!factionInfo.offerSecurityWork) {
return false;
}
this.factionWorkType = FactionWorkType.SECURITY;
this.gainRatesForTask.hack = 0.1 * this.mults.hacking_exp * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.str = 0.15 * this.mults.strength_exp * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.def = 0.15 * this.mults.defense_exp * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.dex = 0.15 * this.mults.dexterity_exp * BitNodeMultipliers.FactionWorkExpGain;
this.gainRatesForTask.agi = 0.15 * this.mults.agility_exp * BitNodeMultipliers.FactionWorkExpGain;
if (!factionInfo.offerSecurityWork) return false;
factionWorkType = FactionWorkType.SECURITY;
} else {
return false;
}
this.currentTaskLocation = factionName;
this.currentTask = SleeveTaskType.Faction;
this.currentWork = new SleeveFactionWork({
factionWorkType: factionWorkType,
factionName: faction.name,
});
return true;
}
@ -995,7 +798,7 @@ export class Sleeve extends Person {
* Begin a gym workout task
*/
workoutAtGym(p: IPlayer, gymName: string, stat: string): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
if (this.currentTask !== SleeveTaskType.Idle || this.currentWork) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
@ -1003,73 +806,60 @@ export class Sleeve extends Person {
// Set exp/money multipliers based on which university.
// Also check that the sleeve is in the right city
let costMult = 1;
let loc: LocationName | undefined;
switch (gymName.toLowerCase()) {
case LocationName.AevumCrushFitnessGym.toLowerCase():
if (this.city != CityName.Aevum) {
return false;
}
this.currentTaskLocation = LocationName.AevumCrushFitnessGym;
costMult = 3;
case LocationName.AevumCrushFitnessGym.toLowerCase(): {
if (this.city != CityName.Aevum) return false;
loc = LocationName.AevumCrushFitnessGym;
break;
case LocationName.AevumSnapFitnessGym.toLowerCase():
if (this.city != CityName.Aevum) {
return false;
}
this.currentTaskLocation = LocationName.AevumSnapFitnessGym;
costMult = 10;
}
case LocationName.AevumSnapFitnessGym.toLowerCase(): {
if (this.city != CityName.Aevum) return false;
loc = LocationName.AevumSnapFitnessGym;
break;
case LocationName.Sector12IronGym.toLowerCase():
if (this.city != CityName.Sector12) {
return false;
}
this.currentTaskLocation = LocationName.Sector12IronGym;
costMult = 1;
}
case LocationName.Sector12IronGym.toLowerCase(): {
if (this.city != CityName.Sector12) return false;
loc = LocationName.Sector12IronGym;
break;
case LocationName.Sector12PowerhouseGym.toLowerCase():
if (this.city != CityName.Sector12) {
return false;
}
this.currentTaskLocation = LocationName.Sector12PowerhouseGym;
costMult = 20;
}
case LocationName.Sector12PowerhouseGym.toLowerCase(): {
if (this.city != CityName.Sector12) return false;
loc = LocationName.Sector12PowerhouseGym;
break;
case LocationName.VolhavenMilleniumFitnessGym.toLowerCase():
if (this.city != CityName.Volhaven) {
return false;
}
this.currentTaskLocation = LocationName.VolhavenMilleniumFitnessGym;
costMult = 7;
}
case LocationName.VolhavenMilleniumFitnessGym.toLowerCase(): {
if (this.city != CityName.Volhaven) return false;
loc = LocationName.VolhavenMilleniumFitnessGym;
break;
default:
return false;
}
}
if (!loc) return false;
// Set experience/money gains based on class
const sanitizedStat: string = stat.toLowerCase();
// set stat to a default value.
stat = "none";
let classType: ClassType | undefined;
if (sanitizedStat.includes("str")) {
stat = "str";
classType = ClassType.GymStrength;
}
if (sanitizedStat.includes("def")) {
stat = "def";
classType = ClassType.GymDefense;
}
if (sanitizedStat.includes("dex")) {
stat = "dex";
classType = ClassType.GymDexterity;
}
if (sanitizedStat.includes("agi")) {
stat = "agi";
classType = ClassType.GymAgility;
}
// if stat is still equals its default value, then validation has failed.
if (stat === "none") {
return false;
}
if (!classType) return false;
// Set cost
this.gainRatesForTask.money = -1 * (CONSTANTS.ClassGymBaseCost * costMult);
this.gymStatType = stat;
this.currentTask = SleeveTaskType.Gym;
this.currentWork = new SleeveClassWork({
classType: classType,
location: loc,
});
return true;
}
@ -1078,7 +868,7 @@ export class Sleeve extends Person {
* Begin a bladeburner task
*/
bladeburner(p: IPlayer, action: string, contract: string): boolean {
if (this.currentTask !== SleeveTaskType.Idle) {
if (this.currentTask !== SleeveTaskType.Idle || this.currentWork === null) {
this.finishTask(p);
} else {
this.resetTaskStatus(p);
@ -1098,24 +888,26 @@ export class Sleeve extends Person {
this.bbContract = "------";
switch (action) {
case "Field analysis":
time = this.getBladeburnerActionTime(p, "General", action);
this.gainRatesForTask.hack = 20 * this.mults.hacking_exp;
this.gainRatesForTask.cha = 20 * this.mults.charisma_exp;
break;
// time = this.getBladeburnerActionTime(p, "General", action);
// this.gainRatesForTask.hack = 20 * this.mults.hacking_exp;
// this.gainRatesForTask.cha = 20 * this.mults.charisma_exp;
this.currentWork = new SleeveBladeburnerGeneralWork("Field analysis");
return true;
case "Recruitment":
time = this.getBladeburnerActionTime(p, "General", action);
this.gainRatesForTask.cha =
2 * BladeburnerConstants.BaseStatGain * (p.bladeburner?.getRecruitmentTime(this) ?? 0) * 1000;
this.currentTaskLocation = `(Success Rate: ${numeralWrapper.formatPercentage(
this.recruitmentSuccessChance(p),
)})`;
// time = this.getBladeburnerActionTime(p, "General", action);
// this.gainRatesForTask.cha =
// 2 * BladeburnerConstants.BaseStatGain * (p.bladeburner?.getRecruitmentTime(this) ?? 0) * 1000;
// this.currentTaskLocation = `(Success Rate: ${numeralWrapper.formatPercentage(
// this.recruitmentSuccessChance(p),
// )})`;
this.currentWork = new SleeveBladeburnerGeneralWork("Recruitment");
break;
case "Diplomacy":
time = this.getBladeburnerActionTime(p, "General", action);
// time = this.getBladeburnerActionTime(p, "General", action);
this.currentWork = new SleeveBladeburnerGeneralWork("Diplomacy");
break;
case "Infiltrate synthoids":
time = 60000;
this.currentTaskLocation = "This will generate additional contracts and operations";
this.currentWork = new SleeveInfiltrateWork();
break;
case "Support main sleeve":
p.bladeburner?.sleeveSupport(true);

@ -0,0 +1,59 @@
import { IPlayer } from "../../IPlayer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work";
import { CONSTANTS } from "../../../Constants";
export const isSleeveBladeburnerGeneralWork = (w: Work | null): w is SleeveBladeburnerGeneralWork =>
w !== null && w.type === WorkType.BLADEBURNER_GENERAL;
export class SleeveBladeburnerGeneralWork extends Work {
cyclesWorked = 0;
action: string;
constructor(action?: string) {
super(WorkType.BLADEBURNER_GENERAL);
this.action = action ?? "Field analysis";
}
cyclesNeeded(player: IPlayer, sleeve: Sleeve): number {
const ret = player.bladeburner?.getActionTimeNetscriptFn(sleeve, "General", this.action);
if (!ret || typeof ret === "string") throw new Error(`Error querying ${this.action} time`);
return ret / CONSTANTS._idleSpeed;
}
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
if (!player.bladeburner) throw new Error("sleeve doing blade work without being a member");
this.cyclesWorked += cycles;
while (this.cyclesWorked > this.cyclesNeeded(player, sleeve)) {
const actionIdent = player.bladeburner.getActionIdFromTypeAndName("General", this.action);
if (!actionIdent) throw new Error(`Error getting ${this.action} action`);
player.bladeburner.completeAction(player, sleeve, actionIdent, false);
this.cyclesWorked -= this.cyclesNeeded(player, sleeve);
}
return 0;
}
APICopy(): Record<string, unknown> {
return {
type: this.type,
action: this.action,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveBladeburnerGeneralWork", this);
}
/**
* Initiatizes a BladeburnerWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveBladeburnerGeneralWork {
return Generic_fromJSON(SleeveBladeburnerGeneralWork, value.data);
}
}
Reviver.constructors.SleeveBladeburnerGeneralWork = SleeveBladeburnerGeneralWork;

@ -0,0 +1,71 @@
import { IPlayer } from "../../IPlayer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver";
import { Work, WorkType } from "./Work";
import { ClassType } from "../../../Work/ClassWork";
import { LocationName } from "../../../Locations/data/LocationNames";
import { calculateClassEarnings } from "../../../Work/formulas/Class";
import { Sleeve } from "../Sleeve";
import { applyWorkStats, applyWorkStatsExp, scaleWorkStats, WorkStats } from "../../../Work/WorkStats";
export const isSleeveClassWork = (w: Work | null): w is SleeveClassWork => w !== null && w.type === WorkType.CLASS;
interface ClassWorkParams {
classType: ClassType;
location: LocationName;
}
export class SleeveClassWork extends Work {
classType: ClassType;
location: LocationName;
constructor(params?: ClassWorkParams) {
super(WorkType.CLASS);
this.classType = params?.classType ?? ClassType.StudyComputerScience;
this.location = params?.location ?? LocationName.Sector12RothmanUniversity;
}
calculateRates(player: IPlayer, sleeve: Sleeve): WorkStats {
return scaleWorkStats(
calculateClassEarnings(player, sleeve, this.classType, this.location),
sleeve.shockBonus(),
false,
);
}
isGym(): boolean {
return [ClassType.GymAgility, ClassType.GymDefense, ClassType.GymDexterity, ClassType.GymStrength].includes(
this.classType,
);
}
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
let rate = this.calculateRates(player, sleeve);
applyWorkStatsExp(sleeve, rate, cycles);
rate = scaleWorkStats(rate, sleeve.syncBonus(), false);
applyWorkStats(player, player, rate, cycles, "sleeves");
player.sleeves.filter((s) => s != sleeve).forEach((s) => applyWorkStatsExp(s, rate, cycles));
return 0;
}
APICopy(): Record<string, unknown> {
return {
type: this.type,
classType: this.classType,
location: this.location,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveClassWork", this);
}
/**
* Initiatizes a ClassWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveClassWork {
return Generic_fromJSON(SleeveClassWork, value.data);
}
}
Reviver.constructors.SleeveClassWork = SleeveClassWork;

@ -0,0 +1,70 @@
import { IPlayer } from "../../IPlayer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work";
import { LocationName } from "../../../Locations/data/LocationNames";
import { Companies } from "../../../Company/Companies";
import { Company } from "../../../Company/Company";
import { calculateCompanyWorkStats } from "../../../Work/formulas/Company";
import { applyWorkStats, applyWorkStatsExp, WorkStats } from "../../../Work/WorkStats";
import { influenceStockThroughCompanyWork } from "../../../StockMarket/PlayerInfluencing";
interface SleeveCompanyWorkParams {
companyName: string;
}
export const isSleeveCompanyWork = (w: Work | null): w is SleeveCompanyWork =>
w !== null && w.type === WorkType.COMPANY;
export class SleeveCompanyWork extends Work {
companyName: string;
constructor(params?: SleeveCompanyWorkParams) {
super(WorkType.COMPANY);
this.companyName = params?.companyName ?? LocationName.NewTokyoNoodleBar;
}
getCompany(): Company {
const c = Companies[this.companyName];
if (!c) throw new Error(`Company not found: '${this.companyName}'`);
return c;
}
getGainRates(player: IPlayer, sleeve: Sleeve): WorkStats {
return calculateCompanyWorkStats(player, sleeve, this.getCompany());
}
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
const company = this.getCompany();
const gains = this.getGainRates(player, sleeve);
applyWorkStatsExp(sleeve, gains, cycles);
applyWorkStats(player, player, gains, cycles, "sleeves");
player.sleeves.filter((s) => s != sleeve).forEach((s) => applyWorkStatsExp(s, gains, cycles));
company.playerReputation += gains.reputation * cycles;
influenceStockThroughCompanyWork(company, gains.reputation, cycles);
return 0;
}
APICopy(): Record<string, unknown> {
return {
type: this.type,
companyName: this.companyName,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveCompanyWork", this);
}
/**
* Initiatizes a CompanyWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveCompanyWork {
return Generic_fromJSON(SleeveCompanyWork, value.data);
}
}
Reviver.constructors.SleeveCompanyWork = SleeveCompanyWork;

@ -0,0 +1,96 @@
import { IPlayer } from "../../IPlayer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work";
import { FactionWorkType } from "../../../Work/data/FactionWorkType";
import { FactionNames } from "../../../Faction/data/FactionNames";
import { Factions } from "../../../Faction/Factions";
import { calculateFactionExp } from "../../../Work/formulas/Faction";
import { applyWorkStatsExp, scaleWorkStats, WorkStats } from "../../../Work/WorkStats";
import { Faction } from "../../../Faction/Faction";
import {
getFactionFieldWorkRepGain,
getFactionSecurityWorkRepGain,
getHackingWorkRepGain,
} from "../../../PersonObjects/formulas/reputation";
interface SleeveFactionWorkParams {
factionWorkType: FactionWorkType;
factionName: string;
}
export const isSleeveFactionWork = (w: Work | null): w is SleeveFactionWork =>
w !== null && w.type === WorkType.FACTION;
export class SleeveFactionWork extends Work {
factionWorkType: FactionWorkType;
factionName: string;
constructor(params?: SleeveFactionWorkParams) {
super(WorkType.FACTION);
this.factionWorkType = params?.factionWorkType ?? FactionWorkType.HACKING;
this.factionName = params?.factionName ?? FactionNames.Sector12;
}
getExpRates(sleeve: Sleeve): WorkStats {
return scaleWorkStats(calculateFactionExp(sleeve, this.factionWorkType), sleeve.shockBonus());
}
getReputationRate(sleeve: Sleeve): number {
const faction = this.getFaction();
const repFormulas = {
[FactionWorkType.HACKING]: getHackingWorkRepGain,
[FactionWorkType.FIELD]: getFactionFieldWorkRepGain,
[FactionWorkType.SECURITY]: getFactionSecurityWorkRepGain,
};
return repFormulas[this.factionWorkType](sleeve, faction) * sleeve.shockBonus();
}
getFaction(): Faction {
const f = Factions[this.factionName];
if (!f) throw new Error(`Faction work started with invalid / unknown faction: '${this.factionName}'`);
return f;
}
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
if (player.gang) {
if (this.factionName === player.gang.facName) {
sleeve.currentWork = null;
return 0;
}
}
let exp = this.getExpRates(sleeve);
applyWorkStatsExp(sleeve, exp, cycles);
exp = scaleWorkStats(exp, sleeve.syncBonus());
applyWorkStatsExp(player, exp, cycles);
player.sleeves.filter((s) => s != sleeve).forEach((s) => applyWorkStatsExp(s, exp, cycles));
const rep = this.getReputationRate(sleeve);
this.getFaction().playerReputation += rep;
return 0;
}
APICopy(): Record<string, unknown> {
return {
type: this.type,
factionWorkType: this.factionWorkType,
factionName: this.factionName,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveFactionWork", this);
}
/**
* Initiatizes a FactionWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveFactionWork {
return Generic_fromJSON(SleeveFactionWork, value.data);
}
}
Reviver.constructors.SleeveFactionWork = SleeveFactionWork;

@ -0,0 +1,54 @@
import { IPlayer } from "../../IPlayer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work";
import { CONSTANTS } from "../../../Constants";
const infiltrateCycles = 600000 / CONSTANTS._idleSpeed;
export const isSleeveInfiltrateWork = (w: Work | null): w is SleeveInfiltrateWork =>
w !== null && w.type === WorkType.INFILTRATE;
export class SleeveInfiltrateWork extends Work {
cyclesWorked = 0;
constructor() {
super(WorkType.INFILTRATE);
}
cyclesNeeded(): number {
return infiltrateCycles;
}
process(player: IPlayer, sleeve: Sleeve, cycles: number): 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(player);
}
return 0;
}
APICopy(): Record<string, unknown> {
return {
type: this.type,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveInfiltrateWork", this);
}
/**
* Initiatizes a BladeburnerWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveInfiltrateWork {
return Generic_fromJSON(SleeveInfiltrateWork, value.data);
}
}
Reviver.constructors.SleeveInfiltrateWork = SleeveInfiltrateWork;

@ -0,0 +1,41 @@
import { IPlayer } from "../../IPlayer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work";
export const isSleeveRecoveryWork = (w: Work | null): w is SleeveRecoveryWork =>
w !== null && w.type === WorkType.RECOVERY;
export class SleeveRecoveryWork extends Work {
constructor() {
super(WorkType.RECOVERY);
}
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
sleeve.shock = Math.min(100, sleeve.shock + 0.0002 * cycles);
if (sleeve.shock >= 100) sleeve.currentWork = null;
return 0;
}
APICopy(): Record<string, unknown> {
return {
type: this.type,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveRecoveryWork", this);
}
/**
* Initiatizes a RecoveryWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveRecoveryWork {
return Generic_fromJSON(SleeveRecoveryWork, value.data);
}
}
Reviver.constructors.SleeveRecoveryWork = SleeveRecoveryWork;

@ -0,0 +1,41 @@
import { IPlayer } from "../../IPlayer";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve";
import { Work, WorkType } from "./Work";
export const isSleeveSynchroWork = (w: Work | null): w is SleeveSynchroWork =>
w !== null && w.type === WorkType.SYNCHRO;
export class SleeveSynchroWork extends Work {
constructor() {
super(WorkType.SYNCHRO);
}
process(player: IPlayer, sleeve: Sleeve, cycles: number): number {
sleeve.sync = Math.min(100, sleeve.sync + player.getIntelligenceBonus(0.5) * 0.0002 * cycles);
if (sleeve.sync >= 100) sleeve.currentWork = null;
return 0;
}
APICopy(): Record<string, unknown> {
return {
type: this.type,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveSynchroWork", this);
}
/**
* Initiatizes a SynchroWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveSynchroWork {
return Generic_fromJSON(SleeveSynchroWork, value.data);
}
}
Reviver.constructors.SleeveSynchroWork = SleeveSynchroWork;

@ -0,0 +1,28 @@
import { IPlayer } from "../../IPlayer";
import { IReviverValue } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve";
export abstract class Work {
type: WorkType;
constructor(type: WorkType) {
this.type = type;
}
abstract process(player: IPlayer, sleeve: Sleeve, cycles: number): number;
abstract APICopy(): Record<string, unknown>;
abstract toJSON(): IReviverValue;
}
export enum WorkType {
COMPANY = "COMPANY",
FACTION = "FACTION",
CRIME = "CRIME",
CLASS = "CLASS",
RECOVERY = "RECOVERY",
SYNCHRO = "SYNCHRO",
BLADEBURNER_GENERAL = "BLADEBURNER_GENERAL",
INFILTRATE = "INFILTRATE",
BLADEBURNER_SUPPORT = "SUPPORT",
BLADEBURNER_CONTRACTS = "CONTRACTS",
}

@ -14,6 +14,13 @@ import { SleeveAugmentationsModal } from "./SleeveAugmentationsModal";
import { EarningsElement, StatsElement } from "./StatsElement";
import { TaskSelector } from "./TaskSelector";
import { TravelModal } from "./TravelModal";
import { isSleeveClassWork } from "../Work/SleeveClassWork";
import { isSleeveSynchroWork } from "../Work/SleeveSynchroWork";
import { isSleeveRecoveryWork } from "../Work/SleeveRecoveryWork";
import { isSleeveFactionWork } from "../Work/SleeveFactionWork";
import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork";
import { isSleeveBladeburnerGeneralWork } from "../Work/SleeveBladeburnerGeneralActionWork";
import { isSleeveInfiltrateWork } from "../Work/SleeveInfiltrateWork";
interface IProps {
sleeve: Sleeve;
@ -69,29 +76,6 @@ export function SleeveElem(props: IProps): React.ReactElement {
case SleeveTaskType.Idle:
desc = <>This sleeve is currently idle</>;
break;
case SleeveTaskType.Company:
desc = <>This sleeve is currently working your job at {props.sleeve.currentTaskLocation}.</>;
break;
case SleeveTaskType.Faction: {
let doing = "nothing";
switch (props.sleeve.factionWorkType) {
case FactionWorkType.FIELD:
doing = "Field work";
break;
case FactionWorkType.HACKING:
doing = "Hacking contracts";
break;
case FactionWorkType.SECURITY:
doing = "Security work";
break;
}
desc = (
<>
This sleeve is currently doing {doing} for {props.sleeve.currentTaskLocation}.
</>
);
break;
}
case SleeveTaskType.Crime: {
const crime = Object.values(Crimes).find((crime) => crime.name === props.sleeve.crimeType);
if (!crime) throw new Error("crime should not be undefined");
@ -106,9 +90,6 @@ export function SleeveElem(props: IProps): React.ReactElement {
case SleeveTaskType.Class:
desc = <>This sleeve is currently studying/taking a course at {props.sleeve.currentTaskLocation}.</>;
break;
case SleeveTaskType.Gym:
desc = <>This sleeve is currently working out at {props.sleeve.currentTaskLocation}.</>;
break;
case SleeveTaskType.Bladeburner: {
let message = "";
if (props.sleeve.bbContract !== "------") {
@ -123,26 +104,75 @@ export function SleeveElem(props: IProps): React.ReactElement {
);
break;
}
case SleeveTaskType.Recovery:
desc = (
<>
This sleeve is currently set to focus on shock recovery. This causes the Sleeve's shock to decrease at a
faster rate.
</>
);
break;
case SleeveTaskType.Synchro:
desc = (
<>
This sleeve is currently set to synchronize with the original consciousness. This causes the Sleeve's
synchronization to increase.
</>
);
break;
default:
console.error(`Invalid/Unrecognized taskValue in updateSleeveTaskDescription(): ${abc[0]}`);
}
if (isSleeveClassWork(props.sleeve.currentWork)) {
if (props.sleeve.currentWork.isGym())
desc = <>This sleeve is currently working out at {props.sleeve.currentWork.location}.</>;
else desc = <>This sleeve is currently studying at {props.sleeve.currentWork.location}.</>;
}
if (isSleeveSynchroWork(props.sleeve.currentWork)) {
desc = (
<>
This sleeve is currently set to synchronize with the original consciousness. This causes the Sleeve's
synchronization to increase.
</>
);
}
if (isSleeveRecoveryWork(props.sleeve.currentWork)) {
desc = (
<>
This sleeve is currently set to focus on shock recovery. This causes the Sleeve's shock to decrease at a faster
rate.
</>
);
}
if (isSleeveFactionWork(props.sleeve.currentWork)) {
let doing = "nothing";
switch (props.sleeve.currentWork.factionWorkType) {
case FactionWorkType.FIELD:
doing = "Field work";
break;
case FactionWorkType.HACKING:
doing = "Hacking contracts";
break;
case FactionWorkType.SECURITY:
doing = "Security work";
break;
}
desc = (
<>
This sleeve is currently doing {doing} for {props.sleeve.currentWork.factionName}.
</>
);
}
if (isSleeveCompanyWork(props.sleeve.currentWork)) {
desc = <>This sleeve is currently working your job at {props.sleeve.currentWork.companyName}.</>;
}
if (isSleeveBladeburnerGeneralWork(props.sleeve.currentWork)) {
const w = props.sleeve.currentWork;
desc = (
<>
This sleeve is currently attempting to perform {w.action}. (
{((100 * w.cyclesWorked) / w.cyclesNeeded(player, props.sleeve)).toFixed(2)}%)
</>
);
}
if (isSleeveInfiltrateWork(props.sleeve.currentWork)) {
const w = props.sleeve.currentWork;
desc = (
<>
This sleeve is currently attempting to infiltrate synthoids communities. (
{((100 * w.cyclesWorked) / w.cyclesNeeded()).toFixed(2)}%)
</>
);
}
return (
<>
<Paper sx={{ p: 1, display: "grid", gridTemplateColumns: "1fr 1fr", width: "auto", gap: 1 }}>

@ -13,6 +13,9 @@ import { use } from "../../../ui/Context";
import { Sleeve } from "../Sleeve";
import { SleeveTaskType } from "../SleeveTaskTypesEnum";
import { isSleeveClassWork } from "../Work/SleeveClassWork";
import { isSleeveFactionWork } from "../Work/SleeveFactionWork";
import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork";
interface IProps {
sleeve: Sleeve;
@ -124,13 +127,52 @@ export function EarningsElement(props: IProps): React.ReactElement {
data.push([`Reputation:`, <ReputationRate reputation={5 * repGain} />]);
}
}
if (isSleeveClassWork(props.sleeve.currentWork)) {
const rates = props.sleeve.currentWork.calculateRates(player, props.sleeve);
data = [
[`Money:`, <MoneyRate money={5 * rates.money} />],
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * rates.hackExp)} / sec`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * rates.strExp)} / sec`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * rates.defExp)} / sec`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * rates.dexExp)} / sec`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * rates.agiExp)} / sec`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * rates.chaExp)} / sec`],
];
}
if (isSleeveFactionWork(props.sleeve.currentWork)) {
const rates = props.sleeve.currentWork.getExpRates(props.sleeve);
const repGain = props.sleeve.currentWork.getReputationRate(props.sleeve);
data = [
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * rates.hackExp)} / sec`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * rates.strExp)} / sec`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * rates.defExp)} / sec`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * rates.dexExp)} / sec`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * rates.agiExp)} / sec`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * rates.chaExp)} / sec`],
[`Reputation:`, <ReputationRate reputation={5 * repGain} />],
];
}
if (isSleeveCompanyWork(props.sleeve.currentWork)) {
const rates = props.sleeve.currentWork.getGainRates(player, props.sleeve);
data = [
[`Money:`, <MoneyRate money={5 * rates.money} />],
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * rates.hackExp)} / sec`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * rates.strExp)} / sec`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * rates.defExp)} / sec`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * rates.dexExp)} / sec`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * rates.agiExp)} / sec`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * rates.chaExp)} / sec`],
[`Reputation:`, <ReputationRate reputation={5 * rates.reputation} />],
];
}
return (
<Table sx={{ display: "table", mb: 1, width: "100%", lineHeight: 0 }}>
<TableBody>
<TableRow>
<TableCell classes={{ root: classes.cellNone }}>
<Typography variant="h6">Earnings</Typography>
<Typography variant="h6">Earnings {props.sleeve.storedCycles > 50 ? "(overclock)" : ""}</Typography>
</TableCell>
</TableRow>
{data.map(([a, b]) => (

@ -1,8 +1,8 @@
import { IPlayer } from "../IPlayer";
import { Faction } from "../../Faction/Faction";
import { CONSTANTS } from "../../Constants";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { CalculateShareMult } from "../../NetworkShare/Share";
import { IPerson } from "../IPerson";
function mult(f: Faction): number {
let favorMult = 1 + f.favor / 100;
@ -12,7 +12,7 @@ function mult(f: Faction): number {
return favorMult * BitNodeMultipliers.FactionWorkRepGain;
}
export function getHackingWorkRepGain(p: IPlayer, f: Faction): number {
export function getHackingWorkRepGain(p: IPerson, f: Faction): number {
return (
((p.skills.hacking + p.skills.intelligence / 3) / CONSTANTS.MaxSkillLevel) *
p.mults.faction_rep *
@ -22,7 +22,7 @@ export function getHackingWorkRepGain(p: IPlayer, f: Faction): number {
);
}
export function getFactionSecurityWorkRepGain(p: IPlayer, f: Faction): number {
export function getFactionSecurityWorkRepGain(p: IPerson, f: Faction): number {
const t =
(0.9 *
(p.skills.strength +
@ -35,7 +35,7 @@ export function getFactionSecurityWorkRepGain(p: IPlayer, f: Faction): number {
return t * p.mults.faction_rep * mult(f) * p.getIntelligenceBonus(1);
}
export function getFactionFieldWorkRepGain(p: IPlayer, f: Faction): number {
export function getFactionFieldWorkRepGain(p: IPerson, f: Faction): number {
const t =
(0.9 *
(p.skills.strength +

@ -147,13 +147,13 @@ export class ClassWork extends Work {
}
calculateRates(player: IPlayer): WorkStats {
return calculateClassEarningsRate(player, this);
return calculateClassEarningsRate(player, player, this.classType, this.location);
}
process(player: IPlayer, cycles: number): boolean {
this.cyclesWorked += cycles;
const rate = this.calculateRates(player);
const earnings = applyWorkStats(player, rate, cycles, "class");
const earnings = applyWorkStats(player, player, rate, cycles, "class");
this.earnings = sumWorkStats(this.earnings, earnings);
return false;
}

@ -10,6 +10,8 @@ import { applyWorkStats, WorkStats } from "./WorkStats";
import { Company } from "../Company/Company";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reputation } from "../ui/React/Reputation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { CONSTANTS } from "../Constants";
interface CompanyWorkParams {
companyName: string;
@ -32,14 +34,18 @@ export class CompanyWork extends Work {
}
getGainRates(player: IPlayer): WorkStats {
return calculateCompanyWorkStats(player, this.getCompany());
let focusBonus = 1;
if (!player.hasAugmentation(AugmentationNames.NeuroreceptorManager)) {
focusBonus = player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
return calculateCompanyWorkStats(player, player, this.getCompany());
}
process(player: IPlayer, cycles: number): boolean {
this.cyclesWorked += cycles;
const company = this.getCompany();
const gains = this.getGainRates(player);
applyWorkStats(player, gains, cycles, "work");
applyWorkStats(player, player, gains, cycles, "work");
company.playerReputation += gains.reputation * cycles;
influenceStockThroughCompanyWork(company, gains.reputation, cycles);
return false;

@ -5,7 +5,7 @@ import { IPlayer } from "../PersonObjects/IPlayer";
import { FactionNames } from "../Faction/data/FactionNames";
import { Factions } from "../Faction/Factions";
import { Faction } from "../Faction/Faction";
import { applyWorkStats, WorkStats } from "./WorkStats";
import { applyWorkStats, scaleWorkStats, WorkStats } from "./WorkStats";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reputation } from "../ui/React/Reputation";
import {
@ -58,7 +58,12 @@ export class FactionWork extends Work {
}
getExpRates(player: IPlayer): WorkStats {
return calculateFactionExp(player, this.factionWorkType);
let focusBonus = 1;
if (!player.hasAugmentation(AugmentationNames.NeuroreceptorManager)) {
focusBonus = player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
const rate = calculateFactionExp(player, this.factionWorkType);
return scaleWorkStats(rate, focusBonus, false);
}
process(player: IPlayer, cycles: number): boolean {
@ -66,7 +71,7 @@ export class FactionWork extends Work {
this.getFaction().playerReputation += this.getReputationRate(player) * cycles;
const rate = this.getExpRates(player);
applyWorkStats(player, rate, cycles, "class");
applyWorkStats(player, player, rate, cycles, "class");
return false;
}

@ -1,3 +1,4 @@
import { IPerson } from "src/PersonObjects/IPerson";
import { IPlayer } from "../PersonObjects/IPlayer";
export interface WorkStats {
@ -52,9 +53,10 @@ export const sumWorkStats = (w0: WorkStats, w1: WorkStats): WorkStats => {
};
};
export const scaleWorkStats = (w: WorkStats, n: number): WorkStats => {
export const scaleWorkStats = (w: WorkStats, n: number, scaleMoney = true): WorkStats => {
const m = scaleMoney ? n : 1;
return {
money: w.money * n,
money: w.money * m,
reputation: w.reputation * n,
hackExp: w.hackExp * n,
strExp: w.strExp * n,
@ -66,10 +68,34 @@ export const scaleWorkStats = (w: WorkStats, n: number): WorkStats => {
};
};
export const applyWorkStats = (player: IPlayer, workStats: WorkStats, cycles: number, source: string): WorkStats => {
export const applyWorkStats = (
player: IPlayer,
target: IPerson,
workStats: WorkStats,
cycles: number,
source: string,
): WorkStats => {
const expStats = applyWorkStatsExp(target, workStats, cycles);
const gains = {
money: workStats.money * cycles,
reputation: 0,
hackExp: expStats.hackExp,
strExp: expStats.strExp,
defExp: expStats.defExp,
dexExp: expStats.dexExp,
agiExp: expStats.agiExp,
chaExp: expStats.chaExp,
intExp: expStats.intExp,
};
player.gainMoney(gains.money, source);
return gains;
};
export const applyWorkStatsExp = (target: IPerson, workStats: WorkStats, cycles: number): WorkStats => {
const gains = {
money: 0,
reputation: 0,
hackExp: workStats.hackExp * cycles,
strExp: workStats.strExp * cycles,
defExp: workStats.defExp * cycles,
@ -78,13 +104,12 @@ export const applyWorkStats = (player: IPlayer, workStats: WorkStats, cycles: nu
chaExp: workStats.chaExp * cycles,
intExp: workStats.intExp * cycles,
};
player.gainHackingExp(gains.hackExp);
player.gainStrengthExp(gains.strExp);
player.gainDefenseExp(gains.defExp);
player.gainDexterityExp(gains.dexExp);
player.gainAgilityExp(gains.agiExp);
player.gainCharismaExp(gains.chaExp);
player.gainIntelligenceExp(gains.intExp);
player.gainMoney(gains.money, source);
target.gainHackingExp(gains.hackExp);
target.gainStrengthExp(gains.strExp);
target.gainDefenseExp(gains.defExp);
target.gainDexterityExp(gains.dexExp);
target.gainAgilityExp(gains.agiExp);
target.gainCharismaExp(gains.chaExp);
target.gainIntelligenceExp(gains.intExp);
return gains;
};

@ -3,11 +3,13 @@ import { Location } from "../../Locations/Location";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Class, Classes, ClassWork } from "../ClassWork";
import { Class, Classes, ClassType } from "../ClassWork";
import { WorkStats } from "../WorkStats";
import { Server } from "../../Server/Server";
import { GetServer } from "../../Server/AllServers";
import { serverMetadata } from "../../Server/data/servers";
import { IPerson } from "../../PersonObjects/IPerson";
import { LocationName } from "../../Locations/data/LocationNames";
const gameCPS = 1000 / CONSTANTS._idleSpeed; // 5 cycles per second
@ -18,13 +20,22 @@ export function calculateCost(classs: Class, location: Location): number {
return classs.earnings.money * location.costMult * discount;
}
export function calculateClassEarnings(player: IPlayer, work: ClassWork): WorkStats {
export function calculateClassEarnings(
player: IPlayer,
target: IPerson,
type: ClassType,
locationName: LocationName,
): WorkStats {
//Find cost and exp gain per game cycle
const hashManager = player.hashManager;
const classs = Classes[work.classType];
const location = Locations[work.location];
const classs = Classes[type];
const location = Locations[locationName];
const hashMult = work.isGym() ? hashManager.getTrainingMult() : hashManager.getStudyMult();
const hashMult = [ClassType.GymAgility, ClassType.GymDefense, ClassType.GymStrength, ClassType.GymDexterity].includes(
type,
)
? hashManager.getTrainingMult()
: hashManager.getStudyMult();
const cost = calculateCost(classs, location) / gameCPS;
const hackExp = ((classs.earnings.hackExp * location.expMult) / gameCPS) * hashMult;
@ -36,12 +47,12 @@ export function calculateClassEarnings(player: IPlayer, work: ClassWork): WorkSt
return {
money: cost,
reputation: 0,
hackExp: hackExp * player.mults.hacking_exp * BitNodeMultipliers.ClassGymExpGain,
strExp: strExp * player.mults.strength_exp * BitNodeMultipliers.ClassGymExpGain,
defExp: defExp * player.mults.defense_exp * BitNodeMultipliers.ClassGymExpGain,
dexExp: dexExp * player.mults.dexterity_exp * BitNodeMultipliers.ClassGymExpGain,
agiExp: agiExp * player.mults.agility_exp * BitNodeMultipliers.ClassGymExpGain,
chaExp: chaExp * player.mults.charisma_exp * BitNodeMultipliers.ClassGymExpGain,
hackExp: hackExp * target.mults.hacking_exp * BitNodeMultipliers.ClassGymExpGain,
strExp: strExp * target.mults.strength_exp * BitNodeMultipliers.ClassGymExpGain,
defExp: defExp * target.mults.defense_exp * BitNodeMultipliers.ClassGymExpGain,
dexExp: dexExp * target.mults.dexterity_exp * BitNodeMultipliers.ClassGymExpGain,
agiExp: agiExp * target.mults.agility_exp * BitNodeMultipliers.ClassGymExpGain,
chaExp: chaExp * target.mults.charisma_exp * BitNodeMultipliers.ClassGymExpGain,
intExp: 0,
};
}

@ -5,16 +5,12 @@ import { WorkStats } from "../WorkStats";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../../Constants";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { IPerson } from "src/PersonObjects/IPerson";
export const calculateCompanyWorkStats = (player: IPlayer, company: Company): WorkStats => {
export const calculateCompanyWorkStats = (player: IPlayer, worker: IPerson, company: Company): WorkStats => {
const companyPositionName = player.jobs[company.name];
const companyPosition = CompanyPositions[companyPositionName];
let focusBonus = 1;
if (!player.hasAugmentation(AugmentationNames.NeuroreceptorManager)) {
focusBonus = player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
// If player has SF-11, calculate salary multiplier from favor
let favorMult = 1 + company.favor / 100;
if (isNaN(favorMult)) {
@ -27,60 +23,53 @@ export const calculateCompanyWorkStats = (player: IPlayer, company: Company): Wo
}
let jobPerformance = companyPosition.calculateJobPerformance(
player.skills.hacking,
player.skills.strength,
player.skills.defense,
player.skills.dexterity,
player.skills.agility,
player.skills.charisma,
worker.skills.hacking,
worker.skills.strength,
worker.skills.defense,
worker.skills.dexterity,
worker.skills.agility,
worker.skills.charisma,
);
jobPerformance += player.skills.intelligence / CONSTANTS.MaxSkillLevel;
jobPerformance += worker.skills.intelligence / CONSTANTS.MaxSkillLevel;
return {
money:
focusBonus *
companyPosition.baseSalary *
company.salaryMultiplier *
player.mults.work_money *
worker.mults.work_money *
BitNodeMultipliers.CompanyWorkMoney *
bn11Mult,
reputation: focusBonus * jobPerformance * player.mults.company_rep * favorMult,
reputation: jobPerformance * worker.mults.company_rep * favorMult,
hackExp:
focusBonus *
companyPosition.hackingExpGain *
company.expMultiplier *
player.mults.hacking_exp *
worker.mults.hacking_exp *
BitNodeMultipliers.CompanyWorkExpGain,
strExp:
focusBonus *
companyPosition.strengthExpGain *
company.expMultiplier *
player.mults.strength_exp *
worker.mults.strength_exp *
BitNodeMultipliers.CompanyWorkExpGain,
defExp:
focusBonus *
companyPosition.defenseExpGain *
company.expMultiplier *
player.mults.defense_exp *
worker.mults.defense_exp *
BitNodeMultipliers.CompanyWorkExpGain,
dexExp:
focusBonus *
companyPosition.dexterityExpGain *
company.expMultiplier *
player.mults.dexterity_exp *
worker.mults.dexterity_exp *
BitNodeMultipliers.CompanyWorkExpGain,
agiExp:
focusBonus *
companyPosition.agilityExpGain *
company.expMultiplier *
player.mults.agility_exp *
worker.mults.agility_exp *
BitNodeMultipliers.CompanyWorkExpGain,
chaExp:
focusBonus *
companyPosition.charismaExpGain *
company.expMultiplier *
player.mults.charisma_exp *
worker.mults.charisma_exp *
BitNodeMultipliers.CompanyWorkExpGain,
intExp: 0,
};

@ -1,7 +1,6 @@
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { IPerson } from "../../PersonObjects/IPerson";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { FactionWorkType } from "../data/FactionWorkType";
import { newWorkStats, WorkStats } from "../WorkStats";
@ -26,27 +25,17 @@ export const FactionWorkStats: Record<FactionWorkType, WorkStats> = {
}),
};
export function calculateFactionExp(player: IPlayer, tpe: FactionWorkType): WorkStats {
let focusBonus = 1;
if (!player.hasAugmentation(AugmentationNames.NeuroreceptorManager)) {
focusBonus = player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
export function calculateFactionExp(person: IPerson, tpe: FactionWorkType): WorkStats {
const baseStats = FactionWorkStats[tpe];
return {
money: 0,
reputation: 0,
hackExp:
(focusBonus * (baseStats.hackExp * player.mults.hacking_exp * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
strExp:
(focusBonus * (baseStats.strExp * player.mults.strength_exp * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
defExp:
(focusBonus * (baseStats.defExp * player.mults.defense_exp * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
dexExp:
(focusBonus * (baseStats.dexExp * player.mults.dexterity_exp * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
agiExp:
(focusBonus * (baseStats.agiExp * player.mults.agility_exp * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
chaExp:
(focusBonus * (baseStats.chaExp * player.mults.charisma_exp * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
hackExp: (baseStats.hackExp * person.mults.hacking_exp * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
strExp: (baseStats.strExp * person.mults.strength_exp * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
defExp: (baseStats.defExp * person.mults.defense_exp * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
dexExp: (baseStats.dexExp * person.mults.dexterity_exp * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
agiExp: (baseStats.agiExp * person.mults.agility_exp * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
chaExp: (baseStats.chaExp * person.mults.charisma_exp * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
intExp: 0,
};
}