From ebe953b498d20e180c8f9d19c7bcf6eefc485dc1 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Wed, 27 Jul 2022 20:37:32 -0400 Subject: [PATCH 1/3] work on sleeve new work system --- src/PersonObjects/Sleeve/Sleeve.ts | 480 +++++------------- .../SleeveBladeburnerGeneralActionWork.ts | 59 +++ .../Sleeve/Work/SleeveClassWork.ts | 71 +++ .../Sleeve/Work/SleeveCompanyWork.ts | 70 +++ .../Sleeve/Work/SleeveFactionWork.ts | 96 ++++ .../Sleeve/Work/SleeveInfiltrateWork.ts | 54 ++ .../Sleeve/Work/SleeveRecoveryWork.ts | 41 ++ .../Sleeve/Work/SleeveSynchroWork.ts | 41 ++ src/PersonObjects/Sleeve/Work/Work.ts | 28 + src/PersonObjects/Sleeve/ui/SleeveElem.tsx | 114 +++-- src/PersonObjects/Sleeve/ui/StatsElement.tsx | 44 +- src/PersonObjects/formulas/reputation.ts | 8 +- src/Work/ClassWork.tsx | 4 +- src/Work/CompanyWork.tsx | 10 +- src/Work/FactionWork.tsx | 11 +- src/Work/WorkStats.ts | 47 +- src/Work/formulas/Class.ts | 33 +- src/Work/formulas/Company.ts | 45 +- src/Work/formulas/Faction.ts | 27 +- 19 files changed, 816 insertions(+), 467 deletions(-) create mode 100644 src/PersonObjects/Sleeve/Work/SleeveBladeburnerGeneralActionWork.ts create mode 100644 src/PersonObjects/Sleeve/Work/SleeveClassWork.ts create mode 100644 src/PersonObjects/Sleeve/Work/SleeveCompanyWork.ts create mode 100644 src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts create mode 100644 src/PersonObjects/Sleeve/Work/SleeveInfiltrateWork.ts create mode 100644 src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts create mode 100644 src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts create mode 100644 src/PersonObjects/Sleeve/Work/Work.ts diff --git a/src/PersonObjects/Sleeve/Sleeve.ts b/src/PersonObjects/Sleeve/Sleeve.ts index 4f6f5cf30..0088a7acc 100644 --- a/src/PersonObjects/Sleeve/Sleeve.ts +++ b/src/PersonObjects/Sleeve/Sleeve.ts @@ -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); diff --git a/src/PersonObjects/Sleeve/Work/SleeveBladeburnerGeneralActionWork.ts b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerGeneralActionWork.ts new file mode 100644 index 000000000..7297a3455 --- /dev/null +++ b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerGeneralActionWork.ts @@ -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 { + 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; diff --git a/src/PersonObjects/Sleeve/Work/SleeveClassWork.ts b/src/PersonObjects/Sleeve/Work/SleeveClassWork.ts new file mode 100644 index 000000000..6c6be3b8c --- /dev/null +++ b/src/PersonObjects/Sleeve/Work/SleeveClassWork.ts @@ -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 { + 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; diff --git a/src/PersonObjects/Sleeve/Work/SleeveCompanyWork.ts b/src/PersonObjects/Sleeve/Work/SleeveCompanyWork.ts new file mode 100644 index 000000000..9aeb1ca41 --- /dev/null +++ b/src/PersonObjects/Sleeve/Work/SleeveCompanyWork.ts @@ -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 { + 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; diff --git a/src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts b/src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts new file mode 100644 index 000000000..a372c3aaa --- /dev/null +++ b/src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts @@ -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 { + 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; diff --git a/src/PersonObjects/Sleeve/Work/SleeveInfiltrateWork.ts b/src/PersonObjects/Sleeve/Work/SleeveInfiltrateWork.ts new file mode 100644 index 000000000..4d196165b --- /dev/null +++ b/src/PersonObjects/Sleeve/Work/SleeveInfiltrateWork.ts @@ -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 { + 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; diff --git a/src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts b/src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts new file mode 100644 index 000000000..db5a08a93 --- /dev/null +++ b/src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts @@ -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 { + 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; diff --git a/src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts b/src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts new file mode 100644 index 000000000..b70665401 --- /dev/null +++ b/src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts @@ -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 { + 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; diff --git a/src/PersonObjects/Sleeve/Work/Work.ts b/src/PersonObjects/Sleeve/Work/Work.ts new file mode 100644 index 000000000..89bb26d14 --- /dev/null +++ b/src/PersonObjects/Sleeve/Work/Work.ts @@ -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; + 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", +} diff --git a/src/PersonObjects/Sleeve/ui/SleeveElem.tsx b/src/PersonObjects/Sleeve/ui/SleeveElem.tsx index 64853d7f9..1f961f5a8 100644 --- a/src/PersonObjects/Sleeve/ui/SleeveElem.tsx +++ b/src/PersonObjects/Sleeve/ui/SleeveElem.tsx @@ -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 ( <> diff --git a/src/PersonObjects/Sleeve/ui/StatsElement.tsx b/src/PersonObjects/Sleeve/ui/StatsElement.tsx index 5851319b0..8f5e6edac 100644 --- a/src/PersonObjects/Sleeve/ui/StatsElement.tsx +++ b/src/PersonObjects/Sleeve/ui/StatsElement.tsx @@ -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:`, ]); } } + if (isSleeveClassWork(props.sleeve.currentWork)) { + const rates = props.sleeve.currentWork.calculateRates(player, props.sleeve); + data = [ + [`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:`, ], + ]; + } + + if (isSleeveCompanyWork(props.sleeve.currentWork)) { + const rates = props.sleeve.currentWork.getGainRates(player, props.sleeve); + data = [ + [`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:`, ], + ]; + } return ( - Earnings + Earnings {props.sleeve.storedCycles > 50 ? "(overclock)" : ""} {data.map(([a, b]) => ( diff --git a/src/PersonObjects/formulas/reputation.ts b/src/PersonObjects/formulas/reputation.ts index d9a500c70..e404bdd5e 100644 --- a/src/PersonObjects/formulas/reputation.ts +++ b/src/PersonObjects/formulas/reputation.ts @@ -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 + diff --git a/src/Work/ClassWork.tsx b/src/Work/ClassWork.tsx index d71c0bf1a..1a5d7dc63 100644 --- a/src/Work/ClassWork.tsx +++ b/src/Work/ClassWork.tsx @@ -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; } diff --git a/src/Work/CompanyWork.tsx b/src/Work/CompanyWork.tsx index ef495dd89..9b164e566 100644 --- a/src/Work/CompanyWork.tsx +++ b/src/Work/CompanyWork.tsx @@ -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; diff --git a/src/Work/FactionWork.tsx b/src/Work/FactionWork.tsx index 87b087aef..5c0f67b19 100644 --- a/src/Work/FactionWork.tsx +++ b/src/Work/FactionWork.tsx @@ -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; } diff --git a/src/Work/WorkStats.ts b/src/Work/WorkStats.ts index f0783617e..07472df41 100644 --- a/src/Work/WorkStats.ts +++ b/src/Work/WorkStats.ts @@ -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; }; diff --git a/src/Work/formulas/Class.ts b/src/Work/formulas/Class.ts index 37cd6db43..b5263498c 100644 --- a/src/Work/formulas/Class.ts +++ b/src/Work/formulas/Class.ts @@ -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, }; } diff --git a/src/Work/formulas/Company.ts b/src/Work/formulas/Company.ts index 489121290..bec83b884 100644 --- a/src/Work/formulas/Company.ts +++ b/src/Work/formulas/Company.ts @@ -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, }; diff --git a/src/Work/formulas/Faction.ts b/src/Work/formulas/Faction.ts index 0e3cfb345..409dc0d52 100644 --- a/src/Work/formulas/Faction.ts +++ b/src/Work/formulas/Faction.ts @@ -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 = { }), }; -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, }; } From 4549b0d467040421b9a5213f3561d0e8594298b6 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Thu, 28 Jul 2022 02:46:34 -0400 Subject: [PATCH 2/3] finish sleeve rework --- doc/source/v2.0.0_migration.rst | 6 +- src/Bladeburner/Bladeburner.tsx | 2 +- src/Bladeburner/data/GeneralActions.tsx | 30 ++- src/Crime/Crimes.ts | 24 +- src/Netscript/RamCostGenerator.ts | 2 +- src/NetscriptFunctions/Sleeve.ts | 12 +- src/NetscriptFunctions/StockMarket.ts | 2 +- src/PersonObjects/Sleeve/Sleeve.ts | 215 +++++------------- .../SleeveBladeburnerGeneralActionWork.ts | 59 ----- .../Sleeve/Work/SleeveBladeburnerWork.ts | 75 ++++++ .../Sleeve/Work/SleeveCrimeWork.ts | 80 +++++++ .../Sleeve/Work/SleeveFactionWork.ts | 2 +- .../Sleeve/Work/SleeveRecoveryWork.ts | 2 +- .../Sleeve/Work/SleeveSupportWork.ts | 43 ++++ .../Sleeve/Work/SleeveSynchroWork.ts | 2 +- src/PersonObjects/Sleeve/Work/Work.ts | 8 +- src/PersonObjects/Sleeve/ui/SleeveElem.tsx | 57 ++--- src/PersonObjects/Sleeve/ui/StatsElement.tsx | 37 +-- src/ScriptEditor/NetscriptDefinitions.d.ts | 27 +-- src/Work/CrimeWork.ts | 26 +-- src/utils/WorkType.ts | 24 +- src/utils/v2APIBreak.ts | 6 + 22 files changed, 376 insertions(+), 365 deletions(-) delete mode 100644 src/PersonObjects/Sleeve/Work/SleeveBladeburnerGeneralActionWork.ts create mode 100644 src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts create mode 100644 src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts create mode 100644 src/PersonObjects/Sleeve/Work/SleeveSupportWork.ts diff --git a/doc/source/v2.0.0_migration.rst b/doc/source/v2.0.0_migration.rst index e8bba2d80..97777cfe4 100644 --- a/doc/source/v2.0.0_migration.rst +++ b/doc/source/v2.0.0_migration.rst @@ -95,10 +95,10 @@ Singularity This means calls like 'ns.connect' need to be changed to 'ns.singularity.connect' -stock.buy and stock.sell ------------------------- +stock.buy, stock.sell, stock.short +---------------------------------- - These 2 functions were renamed to stock.buyStock and stock.sellStock because 'buy' and 'sell' + These functions were renamed to stock.buyStock, stock.sellStock, and stock.buyShort because 'buy', 'sell', and 'short' are very common tokens that would trick the ram calculation. corporation.bribe diff --git a/src/Bladeburner/Bladeburner.tsx b/src/Bladeburner/Bladeburner.tsx index 73b6c21b1..2b76fdde2 100644 --- a/src/Bladeburner/Bladeburner.tsx +++ b/src/Bladeburner/Bladeburner.tsx @@ -1452,7 +1452,7 @@ export class Bladeburner implements IBladeburner { } } } catch (e: unknown) { - exceptionAlert(e); + exceptionAlert(String(e)); } break; } diff --git a/src/Bladeburner/data/GeneralActions.tsx b/src/Bladeburner/data/GeneralActions.tsx index 3cc393612..1725b21cf 100644 --- a/src/Bladeburner/data/GeneralActions.tsx +++ b/src/Bladeburner/data/GeneralActions.tsx @@ -1,11 +1,13 @@ import React from "react"; +import { newWorkStats, WorkStats } from "../../Work/WorkStats"; -interface IContract { +interface IGeneral { desc: JSX.Element; + exp: WorkStats; } export const GeneralActions: { - [key: string]: IContract | undefined; + [key: string]: IGeneral | undefined; } = { Training: { desc: ( @@ -14,6 +16,12 @@ export const GeneralActions: { all combat stats and also increases your max stamina. ), + exp: newWorkStats({ + strExp: 30, + defExp: 30, + dexExp: 30, + agiExp: 30, + }), }, "Field Analysis": { @@ -27,6 +35,10 @@ export const GeneralActions: { Does NOT require stamina. ), + exp: newWorkStats({ + hackExp: 20, + chaExp: 20, + }), }, Recruitment: { @@ -38,6 +50,9 @@ export const GeneralActions: { Does NOT require stamina. ), + exp: newWorkStats({ + chaExp: 120, + }), }, Diplomacy: { @@ -50,6 +65,9 @@ export const GeneralActions: { Does NOT require stamina. ), + exp: newWorkStats({ + chaExp: 120, + }), }, "Hyperbolic Regeneration Chamber": { @@ -61,6 +79,7 @@ export const GeneralActions: {
), + exp: newWorkStats(), }, "Incite Violence": { desc: ( @@ -69,5 +88,12 @@ export const GeneralActions: { additional contracts and operations, at the cost of increased Chaos. ), + exp: newWorkStats({ + strExp: 10, + defExp: 10, + dexExp: 10, + agiExp: 10, + chaExp: 10, + }), }, }; diff --git a/src/Crime/Crimes.ts b/src/Crime/Crimes.ts index 66d100d62..407eb85ad 100644 --- a/src/Crime/Crimes.ts +++ b/src/Crime/Crimes.ts @@ -6,7 +6,7 @@ import { IMap } from "../types"; import { CrimeType } from "../utils/WorkType"; export const Crimes: IMap = { - Shoplift: new Crime("Shoplift", CrimeType.Shoplift, 2e3, 15e3, 1 / 20, 0.1, { + Shoplift: new Crime("Shoplift", CrimeType.SHOPLIFT, 2e3, 15e3, 1 / 20, 0.1, { dexterity_success_weight: 1, agility_success_weight: 1, @@ -14,7 +14,7 @@ export const Crimes: IMap = { agility_exp: 2, }), - RobStore: new Crime("Rob Store", CrimeType.RobStore, 60e3, 400e3, 1 / 5, 0.5, { + RobStore: new Crime("Rob Store", CrimeType.ROB_STORE, 60e3, 400e3, 1 / 5, 0.5, { hacking_exp: 30, dexterity_exp: 45, agility_exp: 45, @@ -26,7 +26,7 @@ export const Crimes: IMap = { intelligence_exp: 7.5 * CONSTANTS.IntelligenceCrimeBaseExpGain, }), - Mug: new Crime("Mug", CrimeType.Mug, 4e3, 36e3, 1 / 5, 0.25, { + Mug: new Crime("Mug", CrimeType.MUG, 4e3, 36e3, 1 / 5, 0.25, { strength_exp: 3, defense_exp: 3, dexterity_exp: 3, @@ -38,7 +38,7 @@ export const Crimes: IMap = { agility_success_weight: 0.5, }), - Larceny: new Crime("Larceny", CrimeType.Larceny, 90e3, 800e3, 1 / 3, 1.5, { + Larceny: new Crime("Larceny", CrimeType.LARCENY, 90e3, 800e3, 1 / 3, 1.5, { hacking_exp: 45, dexterity_exp: 60, agility_exp: 60, @@ -50,7 +50,7 @@ export const Crimes: IMap = { intelligence_exp: 15 * CONSTANTS.IntelligenceCrimeBaseExpGain, }), - DealDrugs: new Crime("Deal Drugs", CrimeType.Drugs, 10e3, 120e3, 1, 0.5, { + DealDrugs: new Crime("Deal Drugs", CrimeType.DRUGS, 10e3, 120e3, 1, 0.5, { dexterity_exp: 5, agility_exp: 5, charisma_exp: 10, @@ -60,7 +60,7 @@ export const Crimes: IMap = { agility_success_weight: 1, }), - BondForgery: new Crime("Bond Forgery", CrimeType.BondForgery, 300e3, 4.5e6, 1 / 2, 0.1, { + BondForgery: new Crime("Bond Forgery", CrimeType.BOND_FORGERY, 300e3, 4.5e6, 1 / 2, 0.1, { hacking_exp: 100, dexterity_exp: 150, charisma_exp: 15, @@ -71,7 +71,7 @@ export const Crimes: IMap = { intelligence_exp: 60 * CONSTANTS.IntelligenceCrimeBaseExpGain, }), - TraffickArms: new Crime("Traffick Arms", CrimeType.TraffickArms, 40e3, 600e3, 2, 1, { + TraffickArms: new Crime("Traffick Arms", CrimeType.TRAFFIC_ARMS, 40e3, 600e3, 2, 1, { strength_exp: 20, defense_exp: 20, dexterity_exp: 20, @@ -85,7 +85,7 @@ export const Crimes: IMap = { agility_success_weight: 1, }), - Homicide: new Crime("Homicide", CrimeType.Homicide, 3e3, 45e3, 1, 3, { + Homicide: new Crime("Homicide", CrimeType.HOMICIDE, 3e3, 45e3, 1, 3, { strength_exp: 2, defense_exp: 2, dexterity_exp: 2, @@ -99,7 +99,7 @@ export const Crimes: IMap = { kills: 1, }), - GrandTheftAuto: new Crime("Grand Theft Auto", CrimeType.GrandTheftAuto, 80e3, 1.6e6, 8, 5, { + GrandTheftAuto: new Crime("Grand Theft Auto", CrimeType.GRAND_THEFT_AUTO, 80e3, 1.6e6, 8, 5, { strength_exp: 20, defense_exp: 20, dexterity_exp: 20, @@ -115,7 +115,7 @@ export const Crimes: IMap = { intelligence_exp: 16 * CONSTANTS.IntelligenceCrimeBaseExpGain, }), - Kidnap: new Crime("Kidnap", CrimeType.Kidnap, 120e3, 3.6e6, 5, 6, { + Kidnap: new Crime("Kidnap", CrimeType.KIDNAP, 120e3, 3.6e6, 5, 6, { strength_exp: 80, defense_exp: 80, dexterity_exp: 80, @@ -130,7 +130,7 @@ export const Crimes: IMap = { intelligence_exp: 26 * CONSTANTS.IntelligenceCrimeBaseExpGain, }), - Assassination: new Crime("Assassination", CrimeType.Assassination, 300e3, 12e6, 8, 10, { + Assassination: new Crime("Assassination", CrimeType.ASSASSINATION, 300e3, 12e6, 8, 10, { strength_exp: 300, defense_exp: 300, dexterity_exp: 300, @@ -145,7 +145,7 @@ export const Crimes: IMap = { kills: 1, }), - Heist: new Crime("Heist", CrimeType.Heist, 600e3, 120e6, 18, 15, { + Heist: new Crime("Heist", CrimeType.HEIST, 600e3, 120e6, 18, 15, { hacking_exp: 450, strength_exp: 450, defense_exp: 450, diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index aac166b3e..80a854886 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -136,7 +136,7 @@ const stock = { getSaleGain: RamCostConstants.ScriptGetStockRamCost, buyStock: RamCostConstants.ScriptBuySellStockRamCost, sellStock: RamCostConstants.ScriptBuySellStockRamCost, - short: RamCostConstants.ScriptBuySellStockRamCost, + buyShort: RamCostConstants.ScriptBuySellStockRamCost, sellShort: RamCostConstants.ScriptBuySellStockRamCost, placeOrder: RamCostConstants.ScriptBuySellStockRamCost, cancelOrder: RamCostConstants.ScriptBuySellStockRamCost, diff --git a/src/NetscriptFunctions/Sleeve.ts b/src/NetscriptFunctions/Sleeve.ts index a6b88e580..001fb863c 100644 --- a/src/NetscriptFunctions/Sleeve.ts +++ b/src/NetscriptFunctions/Sleeve.ts @@ -180,20 +180,14 @@ export function NetscriptSleeve(player: IPlayer): InternalAPI { }, getTask: (ctx: NetscriptContext) => - (_sleeveNumber: unknown): SleeveTask => { + (_sleeveNumber: unknown): SleeveTask | null => { const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber); checkSleeveAPIAccess(ctx); checkSleeveNumber(ctx, sleeveNumber); const sl = player.sleeves[sleeveNumber]; - return { - task: SleeveTaskType[sl.currentTask], - crime: sl.crimeType, - location: sl.currentTaskLocation, - gymStatType: sl.gymStatType, - factionWorkType: FactionWorkType[sl.factionWorkType], - className: sl.className, - }; + if (sl.currentWork === null) return null; + return sl.currentWork.APICopy(); }, getInformation: (ctx: NetscriptContext) => diff --git a/src/NetscriptFunctions/StockMarket.ts b/src/NetscriptFunctions/StockMarket.ts index 26ab02e37..041450038 100644 --- a/src/NetscriptFunctions/StockMarket.ts +++ b/src/NetscriptFunctions/StockMarket.ts @@ -165,7 +165,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript return res ? stock.getBidPrice() : 0; }, - short: + buyShort: (ctx: NetscriptContext) => (_symbol: unknown, _shares: unknown): number => { const symbol = ctx.helper.string("symbol", _symbol); diff --git a/src/PersonObjects/Sleeve/Sleeve.ts b/src/PersonObjects/Sleeve/Sleeve.ts index 0088a7acc..879d8593e 100644 --- a/src/PersonObjects/Sleeve/Sleeve.ts +++ b/src/PersonObjects/Sleeve/Sleeve.ts @@ -35,7 +35,7 @@ import { LocationName } from "../../Locations/data/LocationNames"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../utils/JSONReviver"; import { BladeburnerConstants } from "../../Bladeburner/data/Constants"; import { numeralWrapper } from "../../ui/numeralFormat"; -import { capitalizeFirstLetter, capitalizeEachWord } from "../../utils/StringHelperFunctions"; +import { capitalizeFirstLetter } from "../../utils/StringHelperFunctions"; import { FactionWorkType } from "../../Work/data/FactionWorkType"; import { Work } from "./Work/Work"; import { SleeveClassWork } from "./Work/SleeveClassWork"; @@ -44,8 +44,10 @@ 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"; +import { SleeveSupportWork } from "./Work/SleeveSupportWork"; +import { SleeveBladeburnerWork } from "./Work/SleeveBladeburnerWork"; +import { SleeveCrimeWork } from "./Work/SleeveCrimeWork"; export class Sleeve extends Person { currentWork: Work | null = null; @@ -166,6 +168,16 @@ export class Sleeve extends Person { return this.sync / 100; } + startWork(player: IPlayer, w: Work): void { + if (this.currentWork) this.currentWork.finish(player); + this.currentWork = w; + } + + stopWork(player: IPlayer): void { + if (this.currentWork) this.currentWork.finish(player); + this.currentWork = null; + } + /** * Commit crimes */ @@ -175,26 +187,13 @@ 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); } - this.gainRatesForTask.hack = crime.hacking_exp * this.mults.hacking_exp * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.str = crime.strength_exp * this.mults.strength_exp * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.def = crime.defense_exp * this.mults.defense_exp * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.dex = crime.dexterity_exp * this.mults.dexterity_exp * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.agi = crime.agility_exp * this.mults.agility_exp * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.cha = crime.charisma_exp * this.mults.charisma_exp * BitNodeMultipliers.CrimeExpGain; - this.gainRatesForTask.int = crime.intelligence_exp; - this.gainRatesForTask.money = crime.money * this.mults.crime_money * BitNodeMultipliers.CrimeMoney; - - this.currentTaskLocation = String(this.gainRatesForTask.money); - - this.crimeType = crime.name; - this.currentTaskMaxTime = crime.time; - this.currentTask = SleeveTaskType.Crime; + this.startWork(p, new SleeveCrimeWork(crime.type)); return true; } @@ -202,89 +201,7 @@ export class Sleeve extends Person { * Called to stop the current task */ 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) { - const crime: Crime | undefined = Object.values(Crimes).find((crime) => crime.name === this.crimeType); - if (!crime) { - console.error(`Invalid data stored in sleeve.crimeType: ${this.crimeType}`); - this.resetTaskStatus(p); - return; - } - if (Math.random() < crime.successRate(this)) { - // Success - const successGainRates: ITaskTracker = createTaskTracker(); - - const keysForIteration: (keyof ITaskTracker)[] = Object.keys(successGainRates) as (keyof ITaskTracker)[]; - for (let i = 0; i < keysForIteration.length; ++i) { - const key = keysForIteration[i]; - successGainRates[key] = this.gainRatesForTask[key] * 2; - } - this.gainExperience(p, successGainRates); - this.gainMoney(p, this.gainRatesForTask); - - p.karma -= crime.karma * (this.sync / 100); - } else { - this.gainExperience(p, this.gainRatesForTask); - } - - // Do not reset task to IDLE - this.currentTaskTime = 0; - return; - } - } else if (this.currentTask === SleeveTaskType.Bladeburner) { - if (this.currentTaskMaxTime === 0) { - this.currentTaskTime = 0; - return; - } - // For bladeburner, all experience and money is gained at the end - const bb = p.bladeburner; - if (bb === null) { - const errorLogText = `bladeburner is null`; - console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`); - this.resetTaskStatus(p); - return; - } - - if (this.currentTaskTime >= this.currentTaskMaxTime) { - if (this.bbAction === "Infiltrate synthoids") { - bb.infiltrateSynthoidCommunities(p); - this.currentTaskTime = 0; - return; - } - let type: string; - let name: string; - if (this.bbAction === "Take on contracts") { - type = "Contracts"; - name = this.bbContract; - } else { - type = "General"; - name = this.bbAction; - } - - const actionIdent = bb.getActionIdFromTypeAndName(type, name); - if (actionIdent === null) { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - console.error(`Function: sleeves.finishTask; Message: '${errorLogText}'`); - this.resetTaskStatus(p); - return; - } - - const action = bb.getActionObject(actionIdent); - if ((action?.count ?? 0) > 0) { - const bbRetValue = bb.completeAction(p, this, actionIdent, false); - if (bbRetValue) { - this.gainExperience(p, bbRetValue); - this.gainMoney(p, bbRetValue); - - // Do not reset task to IDLE - this.currentTaskTime = 0; - return; - } - } - } - } + this.stopWork(p); this.resetTaskStatus(p); @@ -557,30 +474,6 @@ export class Sleeve extends Person { // Shock gradually goes towards 100 this.shock = Math.min(100, this.shock + 0.0001 * cyclesUsed); - switch (this.currentTask) { - case SleeveTaskType.Company: { - this.gainExperience(p, this.gainRatesForTask, cyclesUsed); - this.gainMoney(p, this.gainRatesForTask, cyclesUsed); - - const company: Company = Companies[this.currentTaskLocation]; - if (!(company instanceof Company)) { - console.error(`Invalid company for Sleeve task: ${this.currentTaskLocation}`); - break; - } - - company.playerReputation += this.getRepGain(p) * cyclesUsed; - break; - } - } - - if (this.currentTaskMaxTime !== 0 && this.currentTaskTime >= this.currentTaskMaxTime) { - if (this.currentTask === SleeveTaskType.Crime || this.currentTask === SleeveTaskType.Bladeburner) { - this.finishTask(p); - } else { - this.finishTask(p); - } - } - this.updateStatLevels(); this.storedCycles -= cyclesUsed; @@ -592,7 +485,7 @@ export class Sleeve extends Person { * Resets all parameters used to keep information about the current task */ resetTaskStatus(p: IPlayer): void { - this.currentWork = null; + this.stopWork(p); if (this.bbAction == "Support main sleeve") { p.bladeburner?.sleeveSupport(false); } @@ -616,7 +509,7 @@ export class Sleeve extends Person { } else { this.resetTaskStatus(p); } - this.currentWork = new SleeveRecoveryWork(); + this.startWork(p, new SleeveRecoveryWork()); return true; } @@ -626,7 +519,7 @@ export class Sleeve extends Person { } else { this.resetTaskStatus(p); } - this.currentWork = new SleeveSynchroWork(); + this.startWork(p, new SleeveSynchroWork()); return true; } @@ -686,10 +579,13 @@ export class Sleeve extends Person { } if (!classType) return false; - this.currentWork = new SleeveClassWork({ - classType: classType, - location: loc, - }); + this.startWork( + p, + new SleeveClassWork({ + classType: classType, + location: loc, + }), + ); return true; } @@ -747,7 +643,7 @@ export class Sleeve extends Person { if (company == null) return false; if (companyPosition == null) return false; - this.currentWork = new SleeveCompanyWork({ companyName: companyName }); + this.startWork(p, new SleeveCompanyWork({ companyName: companyName })); return true; } @@ -786,10 +682,13 @@ export class Sleeve extends Person { return false; } - this.currentWork = new SleeveFactionWork({ - factionWorkType: factionWorkType, - factionName: faction.name, - }); + this.startWork( + p, + new SleeveFactionWork({ + factionWorkType: factionWorkType, + factionName: faction.name, + }), + ); return true; } @@ -856,10 +755,13 @@ export class Sleeve extends Person { // if stat is still equals its default value, then validation has failed. if (!classType) return false; - this.currentWork = new SleeveClassWork({ - classType: classType, - location: loc, - }); + this.startWork( + p, + new SleeveClassWork({ + classType: classType, + location: loc, + }), + ); return true; } @@ -883,7 +785,7 @@ export class Sleeve extends Person { this.gainRatesForTask.money = 0; this.currentTaskLocation = ""; - let time = 0; + const time = 0; this.bbContract = "------"; switch (action) { @@ -891,7 +793,7 @@ export class Sleeve extends Person { // 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"); + this.startWork(p, new SleeveBladeburnerWork({ type: "General", name: "Field Analysis" })); return true; case "Recruitment": // time = this.getBladeburnerActionTime(p, "General", action); @@ -900,25 +802,26 @@ export class Sleeve extends Person { // this.currentTaskLocation = `(Success Rate: ${numeralWrapper.formatPercentage( // this.recruitmentSuccessChance(p), // )})`; - this.currentWork = new SleeveBladeburnerGeneralWork("Recruitment"); - break; + this.startWork(p, new SleeveBladeburnerWork({ type: "General", name: "Recruitment" })); + return true; case "Diplomacy": // time = this.getBladeburnerActionTime(p, "General", action); - this.currentWork = new SleeveBladeburnerGeneralWork("Diplomacy"); - break; + this.startWork(p, new SleeveBladeburnerWork({ type: "General", name: "Diplomacy" })); + return true; case "Infiltrate synthoids": - this.currentWork = new SleeveInfiltrateWork(); - break; + this.startWork(p, new SleeveInfiltrateWork()); + return true; case "Support main sleeve": - p.bladeburner?.sleeveSupport(true); - time = 0; - break; + this.startWork(p, new SleeveSupportWork(p)); + return true; case "Take on contracts": - time = this.getBladeburnerActionTime(p, "Contracts", contract); - this.contractGainRates(p, "Contracts", contract); - this.currentTaskLocation = this.contractSuccessChance(p, "Contracts", contract); - this.bbContract = capitalizeEachWord(contract.toLowerCase()); - break; + this.startWork(p, new SleeveBladeburnerWork({ type: "Contracts", name: contract })); + return true; + // time = this.getBladeburnerActionTime(p, "Contracts", contract); + // this.contractGainRates(p, "Contracts", contract); + // this.currentTaskLocation = this.contractSuccessChance(p, "Contracts", contract); + // this.bbContract = capitalizeEachWord(contract.toLowerCase()); + // break; } this.bbAction = capitalizeFirstLetter(action.toLowerCase()); diff --git a/src/PersonObjects/Sleeve/Work/SleeveBladeburnerGeneralActionWork.ts b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerGeneralActionWork.ts deleted file mode 100644 index 7297a3455..000000000 --- a/src/PersonObjects/Sleeve/Work/SleeveBladeburnerGeneralActionWork.ts +++ /dev/null @@ -1,59 +0,0 @@ -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 { - 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; diff --git a/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts new file mode 100644 index 000000000..62b74d908 --- /dev/null +++ b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts @@ -0,0 +1,75 @@ +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"; +import { GeneralActions } from "../../../Bladeburner/data/GeneralActions"; +import { applyWorkStatsExp, WorkStats } from "../../../Work/WorkStats"; +import { Contracts } from "src/Bladeburner/data/Contracts"; + +interface SleeveBladeburnerWorkParams { + type: string; + name: string; +} + +export const isSleeveBladeburnerWork = (w: Work | null): w is SleeveBladeburnerWork => + w !== null && w.type === WorkType.BLADEBURNER; + +export class SleeveBladeburnerWork extends Work { + cyclesWorked = 0; + actionType: string; + actionName: string; + + constructor(params?: SleeveBladeburnerWorkParams) { + super(WorkType.BLADEBURNER); + this.actionType = params?.type ?? "General"; + this.actionName = params?.name ?? "Field analysis"; + } + + cyclesNeeded(player: IPlayer, sleeve: Sleeve): number { + const ret = player.bladeburner?.getActionTimeNetscriptFn(sleeve, this.actionType, this.actionName); + if (!ret || typeof ret === "string") throw new Error(`Error querying ${this.actionName} 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(this.actionType, this.actionName); + if (!actionIdent) throw new Error(`Error getting ${this.actionName} action`); + player.bladeburner.completeAction(player, sleeve, actionIdent, false); + let exp: WorkStats | undefined; + if (this.actionType === "General") { + exp = GeneralActions[this.actionName]?.exp; + if (!exp) throw new Error(`Somehow there was no exp for action ${this.actionType} ${this.actionName}`); + applyWorkStatsExp(sleeve, exp, 1); + } + this.cyclesWorked -= this.cyclesNeeded(player, sleeve); + } + return 0; + } + + APICopy(): Record { + return { + actionType: this.actionType, + actionName: this.actionName, + }; + } + + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): IReviverValue { + return Generic_toJSON("SleeveBladeburnerWork", this); + } + + /** + * Initiatizes a BladeburnerWork object from a JSON save state. + */ + static fromJSON(value: IReviverValue): SleeveBladeburnerWork { + return Generic_fromJSON(SleeveBladeburnerWork, value.data); + } +} + +Reviver.constructors.SleeveBladeburnerWork = SleeveBladeburnerWork; diff --git a/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts b/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts new file mode 100644 index 000000000..e86e7ab5d --- /dev/null +++ b/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts @@ -0,0 +1,80 @@ +import { IPlayer } from "../../IPlayer"; +import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver"; +import { Sleeve } from "../Sleeve"; +import { Work, WorkType } from "./Work"; +import { CrimeType } from "../../../utils/WorkType"; +import { Crimes } from "../../../Crime/Crimes"; +import { Crime } from "../../../Crime/Crime"; +import { applyWorkStats, newWorkStats, scaleWorkStats, WorkStats } from "../../../Work/WorkStats"; +import { CONSTANTS } from "../../../Constants"; + +export const isSleeveCrimeWork = (w: Work | null): w is SleeveCrimeWork => w !== null && w.type === WorkType.CRIME; + +export class SleeveCrimeWork extends Work { + crimeType: CrimeType; + cyclesWorked = 0; + constructor(crimeType?: CrimeType) { + super(WorkType.CRIME); + this.crimeType = crimeType ?? CrimeType.SHOPLIFT; + } + + getCrime(): Crime { + const crime = Object.values(Crimes).find((crime) => crime.type === this.crimeType); + if (!crime) throw new Error("crime should not be undefined"); + return crime; + } + + getExp(): WorkStats { + const crime = this.getCrime(); + return newWorkStats({ + money: crime.money, + hackExp: crime.hacking_exp, + strExp: crime.strength_exp, + defExp: crime.defense_exp, + dexExp: crime.dexterity_exp, + agiExp: crime.agility_exp, + chaExp: crime.charisma_exp, + intExp: crime.intelligence_exp, + }); + } + + process(player: IPlayer, sleeve: Sleeve, cycles: number): number { + this.cyclesWorked += cycles; + + const crime = this.getCrime(); + const gains = this.getExp(); + if (this.cyclesWorked >= crime.time / CONSTANTS._idleSpeed) { + if (Math.random() < crime.successRate(sleeve)) { + applyWorkStats(player, sleeve, gains, 1, "sleeves"); + + player.karma -= crime.karma * sleeve.syncBonus(); + } else { + applyWorkStats(player, sleeve, scaleWorkStats(gains, 0.25), 1, "sleeves"); + } + this.cyclesWorked -= crime.time / CONSTANTS._idleSpeed; + } + return 0; + } + + APICopy(): Record { + return { + type: this.type, + }; + } + + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): IReviverValue { + return Generic_toJSON("SleeveCrimeWork", this); + } + + /** + * Initiatizes a RecoveryWork object from a JSON save state. + */ + static fromJSON(value: IReviverValue): SleeveCrimeWork { + return Generic_fromJSON(SleeveCrimeWork, value.data); + } +} + +Reviver.constructors.SleeveCrimeWork = SleeveCrimeWork; diff --git a/src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts b/src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts index a372c3aaa..ec732cdb5 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveFactionWork.ts @@ -55,7 +55,7 @@ export class SleeveFactionWork extends Work { process(player: IPlayer, sleeve: Sleeve, cycles: number): number { if (player.gang) { if (this.factionName === player.gang.facName) { - sleeve.currentWork = null; + sleeve.stopWork(player); return 0; } } diff --git a/src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts b/src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts index db5a08a93..5f0de0a5d 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveRecoveryWork.ts @@ -13,7 +13,7 @@ export class SleeveRecoveryWork extends Work { 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; + if (sleeve.shock >= 100) sleeve.stopWork(player); return 0; } diff --git a/src/PersonObjects/Sleeve/Work/SleeveSupportWork.ts b/src/PersonObjects/Sleeve/Work/SleeveSupportWork.ts new file mode 100644 index 000000000..5dfa02b7c --- /dev/null +++ b/src/PersonObjects/Sleeve/Work/SleeveSupportWork.ts @@ -0,0 +1,43 @@ +import { IPlayer } from "../../../PersonObjects/IPlayer"; +import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../../utils/JSONReviver"; +import { Work, WorkType } from "./Work"; + +export const isSleeveSupportWork = (w: Work | null): w is SleeveSupportWork => + w !== null && w.type === WorkType.SUPPORT; + +export class SleeveSupportWork extends Work { + constructor(player?: IPlayer) { + super(WorkType.SUPPORT); + if (player) player.bladeburner?.sleeveSupport(true); + } + + process(): number { + return 0; + } + + finish(player: IPlayer): void { + player.bladeburner?.sleeveSupport(false); + } + + APICopy(): Record { + return { + type: this.type, + }; + } + + /** + * Serialize the current object to a JSON save state. + */ + toJSON(): IReviverValue { + return Generic_toJSON("SleeveSupportWork", this); + } + + /** + * Initiatizes a BladeburnerWork object from a JSON save state. + */ + static fromJSON(value: IReviverValue): SleeveSupportWork { + return Generic_fromJSON(SleeveSupportWork, value.data); + } +} + +Reviver.constructors.SleeveSupportWork = SleeveSupportWork; diff --git a/src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts b/src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts index b70665401..09b5584d3 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveSynchroWork.ts @@ -13,7 +13,7 @@ export class SleeveSynchroWork extends Work { 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; + if (sleeve.sync >= 100) sleeve.stopWork(player); return 0; } diff --git a/src/PersonObjects/Sleeve/Work/Work.ts b/src/PersonObjects/Sleeve/Work/Work.ts index 89bb26d14..37703df1a 100644 --- a/src/PersonObjects/Sleeve/Work/Work.ts +++ b/src/PersonObjects/Sleeve/Work/Work.ts @@ -12,6 +12,9 @@ export abstract class Work { abstract process(player: IPlayer, sleeve: Sleeve, cycles: number): number; abstract APICopy(): Record; abstract toJSON(): IReviverValue; + finish(_player: IPlayer): void { + /* left for children to implement */ + } } export enum WorkType { @@ -21,8 +24,7 @@ export enum WorkType { CLASS = "CLASS", RECOVERY = "RECOVERY", SYNCHRO = "SYNCHRO", - BLADEBURNER_GENERAL = "BLADEBURNER_GENERAL", + BLADEBURNER = "BLADEBURNER", INFILTRATE = "INFILTRATE", - BLADEBURNER_SUPPORT = "SUPPORT", - BLADEBURNER_CONTRACTS = "CONTRACTS", + SUPPORT = "SUPPORT", } diff --git a/src/PersonObjects/Sleeve/ui/SleeveElem.tsx b/src/PersonObjects/Sleeve/ui/SleeveElem.tsx index 1f961f5a8..de0bfd4d7 100644 --- a/src/PersonObjects/Sleeve/ui/SleeveElem.tsx +++ b/src/PersonObjects/Sleeve/ui/SleeveElem.tsx @@ -19,8 +19,10 @@ 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"; +import { isSleeveSupportWork } from "../Work/SleeveSupportWork"; +import { isSleeveBladeburnerWork } from "../Work/SleeveBladeburnerWork"; +import { isSleeveCrimeWork } from "../Work/SleeveCrimeWork"; interface IProps { sleeve: Sleeve; @@ -71,42 +73,17 @@ export function SleeveElem(props: IProps): React.ReactElement { props.rerender(); } - let desc = <>; - switch (props.sleeve.currentTask) { - case SleeveTaskType.Idle: - desc = <>This sleeve is currently idle; - 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"); - desc = ( - <> - This sleeve is currently attempting to {crime.type} (Success Rate:{" "} - {numeralWrapper.formatPercentage(crime.successRate(props.sleeve))}). - - ); - break; - } - case SleeveTaskType.Class: - desc = <>This sleeve is currently studying/taking a course at {props.sleeve.currentTaskLocation}.; - break; - case SleeveTaskType.Bladeburner: { - let message = ""; - if (props.sleeve.bbContract !== "------") { - message = ` - ${props.sleeve.bbContract} (Success Rate: ${props.sleeve.currentTaskLocation})`; - } else if (props.sleeve.currentTaskLocation !== "") { - message = props.sleeve.currentTaskLocation; - } - desc = ( - <> - This sleeve is currently attempting to {props.sleeve.bbAction}. {message} - - ); - break; - } + let desc = <>This sleeve is currently idle; - default: - console.error(`Invalid/Unrecognized taskValue in updateSleeveTaskDescription(): ${abc[0]}`); + if (isSleeveCrimeWork(props.sleeve.currentWork)) { + const w = props.sleeve.currentWork; + const crime = w.getCrime(); + desc = ( + <> + This sleeve is currently attempting to {crime.type} (Success Rate:{" "} + {numeralWrapper.formatPercentage(crime.successRate(props.sleeve))}). + + ); } if (isSleeveClassWork(props.sleeve.currentWork)) { @@ -153,11 +130,11 @@ export function SleeveElem(props: IProps): React.ReactElement { desc = <>This sleeve is currently working your job at {props.sleeve.currentWork.companyName}.; } - if (isSleeveBladeburnerGeneralWork(props.sleeve.currentWork)) { + if (isSleeveBladeburnerWork(props.sleeve.currentWork)) { const w = props.sleeve.currentWork; desc = ( <> - This sleeve is currently attempting to perform {w.action}. ( + This sleeve is currently attempting to perform {w.actionName}. ( {((100 * w.cyclesWorked) / w.cyclesNeeded(player, props.sleeve)).toFixed(2)}%) ); @@ -173,6 +150,10 @@ export function SleeveElem(props: IProps): React.ReactElement { ); } + if (isSleeveSupportWork(props.sleeve.currentWork)) { + desc = <>This sleeve is currently supporting you in your bladeburner activities.; + } + return ( <> diff --git a/src/PersonObjects/Sleeve/ui/StatsElement.tsx b/src/PersonObjects/Sleeve/ui/StatsElement.tsx index 8f5e6edac..4000a8348 100644 --- a/src/PersonObjects/Sleeve/ui/StatsElement.tsx +++ b/src/PersonObjects/Sleeve/ui/StatsElement.tsx @@ -16,6 +16,7 @@ import { SleeveTaskType } from "../SleeveTaskTypesEnum"; import { isSleeveClassWork } from "../Work/SleeveClassWork"; import { isSleeveFactionWork } from "../Work/SleeveFactionWork"; import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork"; +import { isSleeveCrimeWork } from "../Work/SleeveCrimeWork"; interface IProps { sleeve: Sleeve; @@ -97,35 +98,17 @@ export function EarningsElement(props: IProps): React.ReactElement { const player = use.Player(); let data: (string | JSX.Element)[][] = []; - if (props.sleeve.currentTask === SleeveTaskType.Crime) { + if (isSleeveCrimeWork(props.sleeve.currentWork)) { + const gains = props.sleeve.currentWork.getExp(); data = [ - [ - `Money`, - <> - (on success) - , - ], - [`Hacking Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.hack)} (2x on success)`], - [`Strength Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.str)} (2x on success)`], - [`Defense Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.def)} (2x on success)`], - [`Dexterity Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.dex)} (2x on success)`], - [`Agility Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.agi)} (2x on success)`], - [`Charisma Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.cha)} (2x on success)`], + [`Money:`, ], + [`Hacking Exp:`, `${numeralWrapper.formatExp(5 * gains.hackExp)}`], + [`Strength Exp:`, `${numeralWrapper.formatExp(5 * gains.strExp)}`], + [`Defense Exp:`, `${numeralWrapper.formatExp(5 * gains.defExp)}`], + [`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * gains.dexExp)}`], + [`Agility Exp:`, `${numeralWrapper.formatExp(5 * gains.agiExp)}`], + [`Charisma Exp:`, `${numeralWrapper.formatExp(5 * gains.chaExp)}`], ]; - } else { - data = [ - [`Money:`, ], - [`Hacking Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.hack)} / sec`], - [`Strength Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.str)} / sec`], - [`Defense Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.def)} / sec`], - [`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.dex)} / sec`], - [`Agility Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.agi)} / sec`], - [`Charisma Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.cha)} / sec`], - ]; - if (props.sleeve.currentTask === SleeveTaskType.Company || props.sleeve.currentTask === SleeveTaskType.Faction) { - const repGain: number = props.sleeve.getRepGain(player); - data.push([`Reputation:`, ]); - } } if (isSleeveClassWork(props.sleeve.currentWork)) { const rates = props.sleeve.currentWork.calculateRates(player, props.sleeve); diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 65cc2b00d..b94170d26 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -1016,36 +1016,13 @@ export interface SleeveInformation { tor: boolean; /** Sleeve multipliers */ mult: CharacterMult; - /** Time spent on the current task in milliseconds */ - timeWorked: number; - /** Earnings synchronized to other sleeves */ - earningsForSleeves: SleeveWorkGains; - /** Earnings synchronized to the player */ - earningsForPlayer: SleeveWorkGains; - /** Earnings for this sleeve */ - earningsForTask: SleeveWorkGains; - /** Faction or company reputation gained for the current task */ - workRepGain: number; } /** * Object representing a sleeve current task. * @public */ -export interface SleeveTask { - /** Task type */ - task: string; - /** Crime currently attempting, if any */ - crime: string; - /** Location of the task, if any */ - location: string; - /** Stat being trained at the gym, if any */ - gymStatType: string; - /** Faction work type being performed, if any */ - factionWorkType: string; - /** Class being taken at university, if any */ - className: string; -} +export interface SleeveTask = any; /** * Object representing a port. A port is a serialized queue. @@ -1310,7 +1287,7 @@ export interface TIX { * @param shares - Number of shares to short. Must be positive. Will be rounded to nearest integer. * @returns The stock price at which each share was purchased, otherwise 0 if the shares weren't purchased. */ - short(sym: string, shares: number): number; + buyShort(sym: string, shares: number): number; /** * Sell short stock. diff --git a/src/Work/CrimeWork.ts b/src/Work/CrimeWork.ts index 356d932e1..3c7bc8933 100644 --- a/src/Work/CrimeWork.ts +++ b/src/Work/CrimeWork.ts @@ -26,29 +26,29 @@ enum newCrimeType { const convertCrimeType = (crimeType: CrimeType): newCrimeType => { switch (crimeType) { - case CrimeType.Shoplift: + case CrimeType.SHOPLIFT: return newCrimeType.SHOPLIFT; - case CrimeType.RobStore: + case CrimeType.ROB_STORE: return newCrimeType.ROBSTORE; - case CrimeType.Mug: + case CrimeType.MUG: return newCrimeType.MUG; - case CrimeType.Larceny: + case CrimeType.LARCENY: return newCrimeType.LARCENY; - case CrimeType.Drugs: + case CrimeType.DRUGS: return newCrimeType.DRUGS; - case CrimeType.BondForgery: + case CrimeType.BOND_FORGERY: return newCrimeType.BONDFORGERY; - case CrimeType.TraffickArms: + case CrimeType.TRAFFIC_ARMS: return newCrimeType.TRAFFICKARMS; - case CrimeType.Homicide: + case CrimeType.HOMICIDE: return newCrimeType.HOMICIDE; - case CrimeType.GrandTheftAuto: + case CrimeType.GRAND_THEFT_AUTO: return newCrimeType.GRANDTHEFTAUTO; - case CrimeType.Kidnap: + case CrimeType.KIDNAP: return newCrimeType.KIDNAP; - case CrimeType.Assassination: + case CrimeType.ASSASSINATION: return newCrimeType.ASSASSINATION; - case CrimeType.Heist: + case CrimeType.HEIST: return newCrimeType.HEIST; } return newCrimeType.SHOPLIFT; @@ -67,7 +67,7 @@ export class CrimeWork extends Work { constructor(params?: CrimeWorkParams) { super(WorkType.CRIME, params?.singularity ?? true); - this.crimeType = params?.crimeType ?? CrimeType.Shoplift; + this.crimeType = params?.crimeType ?? CrimeType.SHOPLIFT; this.unitCompleted = 0; } diff --git a/src/utils/WorkType.ts b/src/utils/WorkType.ts index 58c003fd7..f4cd1a285 100644 --- a/src/utils/WorkType.ts +++ b/src/utils/WorkType.ts @@ -1,15 +1,15 @@ export enum CrimeType { None = "", - Shoplift = "shoplift", - RobStore = "rob a store", - Mug = "mug someone", - Larceny = "commit larceny", - Drugs = "deal drugs", - BondForgery = "forge corporate bonds", - TraffickArms = "traffick illegal arms", - Homicide = "commit homicide", - GrandTheftAuto = "commit grand theft auto", - Kidnap = "kidnap someone for ransom", - Assassination = "assassinate a high-profile target", - Heist = "pull off the ultimate heist", + SHOPLIFT = "SHOPLIFT", //"shoplift", + ROB_STORE = "ROBSTORE", //"rob a store", + MUG = "MUG", //"mug someone", + LARCENY = "LARCENY", //"commit larceny", + DRUGS = "DRUGS", //"deal drugs", + BOND_FORGERY = "BONDFORGERY", //"forge corporate bonds", + TRAFFIC_ARMS = "TRAFFICKARMS", //"traffick illegal arms", + HOMICIDE = "HOMICIDE", //"commit homicide", + GRAND_THEFT_AUTO = "GRANDTHEFTAUTO", //"commit grand theft auto", + KIDNAP = "KIDNAP", //"kidnap someone for ransom", + ASSASSINATION = "ASSASSINATION", //"assassinate a high-profile target", + HEIST = "HEIST", //"pull off the ultimate heist", } diff --git a/src/utils/v2APIBreak.ts b/src/utils/v2APIBreak.ts index 4285dd7ef..41a41abe6 100644 --- a/src/utils/v2APIBreak.ts +++ b/src/utils/v2APIBreak.ts @@ -185,6 +185,12 @@ export const v2APIBreak = () => { reason: "sell is a very common word so in order to avoid ram costs it was renamed ns.stock.sellStock", offenders: [], }, + { + matchJS: /ns\.stock\.short/g, + matchScript: /stock\.short/g, + reason: "short is a very common word so in order to avoid ram costs it was renamed ns.stock.buyShort", + offenders: [], + }, { matchJS: /ns\.corporation\.bribe/g, matchScript: /corporation\.bribe/g, From 3b35b07860487538ed1589ccdc45d2ae0b366ae5 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Thu, 28 Jul 2022 03:08:39 -0400 Subject: [PATCH 3/3] remove unused fields on sleeves --- src/Bladeburner/Bladeburner.tsx | 8 +- src/NetscriptFunctions/Sleeve.ts | 45 +-- src/PersonObjects/Sleeve/Sleeve.ts | 338 ++++-------------- .../Sleeve/Work/SleeveBladeburnerWork.ts | 1 - .../Sleeve/Work/SleeveCrimeWork.ts | 8 +- .../Sleeve/ui/MoreEarningsModal.tsx | 58 --- src/PersonObjects/Sleeve/ui/SleeveElem.tsx | 33 +- src/PersonObjects/Sleeve/ui/TaskSelector.tsx | 94 +++-- src/ScriptEditor/NetscriptDefinitions.d.ts | 2 +- 9 files changed, 142 insertions(+), 445 deletions(-) delete mode 100644 src/PersonObjects/Sleeve/ui/MoreEarningsModal.tsx diff --git a/src/Bladeburner/Bladeburner.tsx b/src/Bladeburner/Bladeburner.tsx index 2b76fdde2..c57e47beb 100644 --- a/src/Bladeburner/Bladeburner.tsx +++ b/src/Bladeburner/Bladeburner.tsx @@ -36,6 +36,8 @@ import { joinFaction } from "../Faction/FactionHelpers"; import { WorkerScript } from "../Netscript/WorkerScript"; import { FactionNames } from "../Faction/data/FactionNames"; import { KEY } from "../utils/helpers/keyCodes"; +import { isSleeveInfiltrateWork } from "../PersonObjects/Sleeve/Work/SleeveInfiltrateWork"; +import { isSleeveSupportWork } from "../PersonObjects/Sleeve/Work/SleeveSupportWork"; interface BlackOpsAttempt { error?: string; @@ -1124,7 +1126,7 @@ export class Bladeburner implements IBladeburner { const losses = getRandomInt(0, max); this.teamSize -= losses; if (this.teamSize < this.sleeveSize) { - const sup = player.sleeves.filter((x) => x.bbAction == "Support main sleeve"); + const sup = player.sleeves.filter((x) => isSleeveSupportWork(x.currentWork)); for (let i = 0; i > this.teamSize - this.sleeveSize; i--) { const r = Math.floor(Math.random() * sup.length); sup[r].takeDamage(sup[r].hp.max); @@ -1438,7 +1440,7 @@ export class Bladeburner implements IBladeburner { const losses = getRandomInt(1, teamLossMax); this.teamSize -= losses; if (this.teamSize < this.sleeveSize) { - const sup = player.sleeves.filter((x) => x.bbAction == "Support main sleeve"); + const sup = player.sleeves.filter((x) => isSleeveSupportWork(x.currentWork)); for (let i = 0; i > this.teamSize - this.sleeveSize; i--) { const r = Math.floor(Math.random() * sup.length); sup[r].takeDamage(sup[r].hp.max); @@ -1602,7 +1604,7 @@ export class Bladeburner implements IBladeburner { } infiltrateSynthoidCommunities(p: IPlayer): void { - const infilSleeves = p.sleeves.filter((s) => s.bbAction === "Infiltrate synthoids").length; + const infilSleeves = p.sleeves.filter((s) => isSleeveInfiltrateWork(s.currentWork)).length; const amt = Math.pow(infilSleeves, -0.5) / 2; for (const contract of Object.keys(this.contracts)) { this.contracts[contract].count += amt; diff --git a/src/NetscriptFunctions/Sleeve.ts b/src/NetscriptFunctions/Sleeve.ts index 001fb863c..eff3d882b 100644 --- a/src/NetscriptFunctions/Sleeve.ts +++ b/src/NetscriptFunctions/Sleeve.ts @@ -1,5 +1,4 @@ import { IPlayer } from "../PersonObjects/IPlayer"; -import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum"; import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers"; import { StaticAugmentations } from "../Augmentation/StaticAugmentations"; import { CityName } from "../Locations/data/CityNames"; @@ -15,7 +14,9 @@ import { } from "../ScriptEditor/NetscriptDefinitions"; import { checkEnum } from "../utils/helpers/checkEnum"; import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper"; -import { FactionWorkType } from "../Work/data/FactionWorkType"; +import { isSleeveBladeburnerWork } from "../PersonObjects/Sleeve/Work/SleeveBladeburnerWork"; +import { isSleeveFactionWork } from "../PersonObjects/Sleeve/Work/SleeveFactionWork"; +import { isSleeveCompanyWork } from "../PersonObjects/Sleeve/Work/SleeveCompanyWork"; export function NetscriptSleeve(player: IPlayer): InternalAPI { const checkSleeveAPIAccess = function (ctx: NetscriptContext): void { @@ -120,7 +121,7 @@ export function NetscriptSleeve(player: IPlayer): InternalAPI { continue; } const other = player.sleeves[i]; - if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) { + if (isSleeveCompanyWork(other.currentWork) && other.currentWork.companyName === companyName) { throw ctx.makeRuntimeErrorMsg( `Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`, ); @@ -144,7 +145,7 @@ export function NetscriptSleeve(player: IPlayer): InternalAPI { continue; } const other = player.sleeves[i]; - if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) { + if (isSleeveFactionWork(other.currentWork) && other.currentWork.factionName === factionName) { throw ctx.makeRuntimeErrorMsg( `Sleeve ${sleeveNumber} cannot work for faction ${factionName} because Sleeve ${i} is already working for them.`, ); @@ -223,36 +224,6 @@ export function NetscriptSleeve(player: IPlayer): InternalAPI { strengthExp: sl.mults.strength_exp, workMoney: sl.mults.work_money, }, - - timeWorked: sl.currentTaskTime, - earningsForSleeves: { - workHackExpGain: sl.earningsForSleeves.hack, - workStrExpGain: sl.earningsForSleeves.str, - workDefExpGain: sl.earningsForSleeves.def, - workDexExpGain: sl.earningsForSleeves.dex, - workAgiExpGain: sl.earningsForSleeves.agi, - workChaExpGain: sl.earningsForSleeves.cha, - workMoneyGain: sl.earningsForSleeves.money, - }, - earningsForPlayer: { - workHackExpGain: sl.earningsForPlayer.hack, - workStrExpGain: sl.earningsForPlayer.str, - workDefExpGain: sl.earningsForPlayer.def, - workDexExpGain: sl.earningsForPlayer.dex, - workAgiExpGain: sl.earningsForPlayer.agi, - workChaExpGain: sl.earningsForPlayer.cha, - workMoneyGain: sl.earningsForPlayer.money, - }, - earningsForTask: { - workHackExpGain: sl.earningsForTask.hack, - workStrExpGain: sl.earningsForTask.str, - workDefExpGain: sl.earningsForTask.def, - workDexExpGain: sl.earningsForTask.dex, - workAgiExpGain: sl.earningsForTask.agi, - workChaExpGain: sl.earningsForTask.cha, - workMoneyGain: sl.earningsForTask.money, - }, - workRepGain: sl.getRepGain(player), }; }, getSleeveAugmentations: @@ -343,11 +314,7 @@ export function NetscriptSleeve(player: IPlayer): InternalAPI { continue; } const other = player.sleeves[i]; - if ( - other.currentTask === SleeveTaskType.Bladeburner && - other.bbAction === action && - other.bbContract === contract - ) { + if (isSleeveBladeburnerWork(other.currentWork) && other.currentWork.actionName === contract) { throw ctx.helper.makeRuntimeErrorMsg( `Sleeve ${sleeveNumber} cannot take on contracts because Sleeve ${i} is already performing that action.`, ); diff --git a/src/PersonObjects/Sleeve/Sleeve.ts b/src/PersonObjects/Sleeve/Sleeve.ts index 879d8593e..5f98e4fab 100644 --- a/src/PersonObjects/Sleeve/Sleeve.ts +++ b/src/PersonObjects/Sleeve/Sleeve.ts @@ -6,7 +6,6 @@ * * Sleeves are unlocked in BitNode-10. */ -import { SleeveTaskType } from "./SleeveTaskTypesEnum"; import { IPlayer } from "../IPlayer"; import { Person } from "../Person"; @@ -14,8 +13,6 @@ import { ITaskTracker, createTaskTracker } from "../ITaskTracker"; import { Augmentation } from "../../Augmentation/Augmentation"; -import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; - import { Crime } from "../../Crime/Crime"; import { Crimes } from "../../Crime/Crimes"; @@ -33,9 +30,7 @@ import { CityName } from "../../Locations/data/CityNames"; import { LocationName } from "../../Locations/data/LocationNames"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../utils/JSONReviver"; -import { BladeburnerConstants } from "../../Bladeburner/data/Constants"; import { numeralWrapper } from "../../ui/numeralFormat"; -import { capitalizeFirstLetter } from "../../utils/StringHelperFunctions"; import { FactionWorkType } from "../../Work/data/FactionWorkType"; import { Work } from "./Work/Work"; import { SleeveClassWork } from "./Work/SleeveClassWork"; @@ -51,82 +46,6 @@ import { SleeveCrimeWork } from "./Work/SleeveCrimeWork"; export class Sleeve extends Person { currentWork: Work | null = null; - /** - * Stores the name of the class that the player is currently taking - */ - className = ""; - - /** - * Stores the type of crime the sleeve is currently attempting - * Must match the name of a Crime object - */ - crimeType = ""; - - /** - * Enum value for current task - */ - currentTask: SleeveTaskType = SleeveTaskType.Idle; - - /** - * Contains details about the sleeve's current task. The info stored - * in this depends on the task type - * - * Faction/Company Work: Name of Faction/Company - * Crime: Money earned if successful - * Class/Gym: Name of university/gym - * Bladeburner: success chance - */ - currentTaskLocation = ""; - - /** - * Maximum amount of time (in milliseconds) that can be spent on current task. - */ - currentTaskMaxTime = 0; - - /** - * Milliseconds spent on current task - */ - currentTaskTime = 0; - - /** - * Keeps track of experience earned for other sleeves - */ - earningsForSleeves: ITaskTracker = createTaskTracker(); - - /** - * Keeps track of experience + money earned for player - */ - earningsForPlayer: ITaskTracker = createTaskTracker(); - - /** - * Keeps track of experienced earned in the current task/action - */ - earningsForTask: ITaskTracker = createTaskTracker(); - - /** - * Keeps track of what type of work sleeve is doing for faction, if applicable - */ - factionWorkType: FactionWorkType = FactionWorkType.HACKING; - - /** - * Records experience gain rate for the current task - */ - gainRatesForTask: ITaskTracker = createTaskTracker(); - - /** - * String that stores what stat the sleeve is training at the gym - */ - gymStatType = ""; - - /** - * String that stores what stat the sleeve is training at the gym - */ - bbAction = ""; - - /** - * String that stores what stat the sleeve is training at the gym - */ - bbContract = ""; /** * Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs @@ -187,12 +106,6 @@ export class Sleeve extends Person { return false; } - if (this.currentTask !== SleeveTaskType.Idle || this.currentWork === null) { - this.finishTask(p); - } else { - this.resetTaskStatus(p); - } - this.startWork(p, new SleeveCrimeWork(crime.type)); return true; } @@ -259,43 +172,31 @@ export class Sleeve extends Person { if (pHackExp > 0) { this.gainHackingExp(pHackExp); p.gainHackingExp(pHackExp); - this.earningsForPlayer.hack += pHackExp; - this.earningsForTask.hack += pHackExp; } if (pStrExp > 0) { this.gainStrengthExp(pStrExp); p.gainStrengthExp(pStrExp); - this.earningsForPlayer.str += pStrExp; - this.earningsForTask.str += pStrExp; } if (pDefExp > 0) { this.gainDefenseExp(pDefExp); p.gainDefenseExp(pDefExp); - this.earningsForPlayer.def += pDefExp; - this.earningsForTask.def += pDefExp; } if (pDexExp > 0) { this.gainDexterityExp(pDexExp); p.gainDexterityExp(pDexExp); - this.earningsForPlayer.dex += pDexExp; - this.earningsForTask.dex += pDexExp; } if (pAgiExp > 0) { this.gainAgilityExp(pAgiExp); p.gainAgilityExp(pAgiExp); - this.earningsForPlayer.agi += pAgiExp; - this.earningsForTask.agi += pAgiExp; } if (pChaExp > 0) { this.gainCharismaExp(pChaExp); p.gainCharismaExp(pChaExp); - this.earningsForPlayer.cha += pChaExp; - this.earningsForTask.cha += pChaExp; } if (pIntExp > 0) { @@ -303,14 +204,6 @@ export class Sleeve extends Person { p.gainIntelligenceExp(pIntExp); } - // Record earnings for other sleeves - this.earningsForSleeves.hack += pHackExp * (this.sync / 100); - this.earningsForSleeves.str += pStrExp * (this.sync / 100); - this.earningsForSleeves.def += pDefExp * (this.sync / 100); - this.earningsForSleeves.dex += pDexExp * (this.sync / 100); - this.earningsForSleeves.agi += pAgiExp * (this.sync / 100); - this.earningsForSleeves.cha += pChaExp * (this.sync / 100); - // Return the experience to be gained by other sleeves return { hack: pHackExp * (this.sync / 100), @@ -327,12 +220,12 @@ export class Sleeve extends Person { /** * Earn money for player */ - gainMoney(p: IPlayer, task: ITaskTracker, numCycles = 1): void { - const gain: number = task.money * numCycles; - this.earningsForTask.money += gain; - this.earningsForPlayer.money += gain; - p.gainMoney(gain, "sleeves"); - } + // gainMoney(p: IPlayer, task: ITaskTracker, numCycles = 1): void { + // const gain: number = task.money * numCycles; + // this.earningsForTask.money += gain; + // this.earningsForPlayer.money += gain; + // p.gainMoney(gain, "sleeves"); + // } /** * Returns the cost of upgrading this sleeve's memory by a certain amount @@ -363,36 +256,36 @@ export class Sleeve extends Person { * Gets reputation gain for the current task * Only applicable when working for company or faction */ - getRepGain(p: IPlayer): number { - if (this.currentTask === SleeveTaskType.Company) { - const companyName: string = this.currentTaskLocation; - const company: Company | null = Companies[companyName]; - if (company == null) { - console.error(`Invalid company found when trying to calculate rep gain: ${companyName}`); - return 0; - } + // getRepGain(p: IPlayer): number { + // if (this.currentTask === SleeveTaskType.Company) { + // const companyName: string = this.currentTaskLocation; + // const company: Company | null = Companies[companyName]; + // if (company == null) { + // console.error(`Invalid company found when trying to calculate rep gain: ${companyName}`); + // return 0; + // } - const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]]; - if (companyPosition == null) { - console.error(`Invalid company position name found when trying to calculate rep gain: ${p.jobs[companyName]}`); - return 0; - } + // const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]]; + // if (companyPosition == null) { + // console.error(`Invalid company position name found when trying to calculate rep gain: ${p.jobs[companyName]}`); + // return 0; + // } - const jobPerformance: number = companyPosition.calculateJobPerformance( - this.skills.hacking, - this.skills.strength, - this.skills.defense, - this.skills.dexterity, - this.skills.agility, - this.skills.charisma, - ); - const favorMult = 1 + company.favor / 100; + // const jobPerformance: number = companyPosition.calculateJobPerformance( + // this.skills.hacking, + // this.skills.strength, + // this.skills.defense, + // this.skills.dexterity, + // this.skills.agility, + // this.skills.charisma, + // ); + // const favorMult = 1 + company.favor / 100; - return jobPerformance * this.mults.company_rep * favorMult; - } else { - return 0; - } - } + // return jobPerformance * this.mults.company_rep * favorMult; + // } else { + // return 0; + // } + // } installAugmentation(aug: Augmentation): void { this.exp.hacking = 0; @@ -420,8 +313,6 @@ export class Sleeve extends Person { // Reset task-related stuff this.resetTaskStatus(p); - this.earningsForSleeves = createTaskTracker(); - this.earningsForPlayer = createTaskTracker(); this.shockRecovery(p); // Reset augs and multipliers @@ -457,20 +348,6 @@ export class Sleeve extends Person { return; } - let time = cyclesUsed * CONSTANTS.MilliPerCycle; - if (this.currentTaskMaxTime !== 0 && this.currentTaskTime + time > this.currentTaskMaxTime) { - time = this.currentTaskMaxTime - this.currentTaskTime; - cyclesUsed = Math.floor(time / CONSTANTS.MilliPerCycle); - - if (time < 0 || cyclesUsed < 0) { - console.warn(`Sleeve.process() calculated negative cycle usage`); - time = 0; - cyclesUsed = 0; - } - } - - this.currentTaskTime += time; - // Shock gradually goes towards 100 this.shock = Math.min(100, this.shock + 0.0001 * cyclesUsed); @@ -486,39 +363,14 @@ export class Sleeve extends Person { */ resetTaskStatus(p: IPlayer): void { this.stopWork(p); - if (this.bbAction == "Support main sleeve") { - p.bladeburner?.sleeveSupport(false); - } - this.earningsForTask = createTaskTracker(); - this.gainRatesForTask = createTaskTracker(); - this.currentTask = SleeveTaskType.Idle; - this.currentTaskTime = 0; - this.currentTaskMaxTime = 0; - this.factionWorkType = FactionWorkType.HACKING; - this.crimeType = ""; - this.currentTaskLocation = ""; - this.gymStatType = ""; - this.className = ""; - this.bbAction = ""; - this.bbContract = "------"; } shockRecovery(p: IPlayer): boolean { - if (this.currentTask !== SleeveTaskType.Idle || this.currentWork === null) { - this.finishTask(p); - } else { - this.resetTaskStatus(p); - } this.startWork(p, new SleeveRecoveryWork()); return true; } synchronize(p: IPlayer): boolean { - if (this.currentTask !== SleeveTaskType.Idle || this.currentWork !== null) { - this.finishTask(p); - } else { - this.resetTaskStatus(p); - } this.startWork(p, new SleeveSynchroWork()); return true; } @@ -527,12 +379,6 @@ export class Sleeve extends Person { * Take a course at a university */ takeUniversityCourse(p: IPlayer, universityName: string, className: string): boolean { - if (this.currentTask !== SleeveTaskType.Idle || this.currentWork) { - this.finishTask(p); - } else { - this.resetTaskStatus(p); - } - // Set exp/money multipliers based on which university. // Also check that the sleeve is in the right city let loc: LocationName | undefined; @@ -632,12 +478,6 @@ export class Sleeve extends Person { return false; } - if (this.currentTask !== SleeveTaskType.Idle || this.currentWork !== null) { - this.finishTask(p); - } else { - this.resetTaskStatus(p); - } - const company: Company | null = Companies[companyName]; const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]]; if (company == null) return false; @@ -658,12 +498,6 @@ export class Sleeve extends Person { return false; } - if (this.currentTask !== SleeveTaskType.Idle || this.currentWork === null) { - this.finishTask(p); - } else { - this.resetTaskStatus(p); - } - const factionInfo = faction.getInfo(); // Set type of work (hacking/field/security), and the experience gains @@ -697,12 +531,6 @@ export class Sleeve extends Person { * Begin a gym workout task */ workoutAtGym(p: IPlayer, gymName: string, stat: string): boolean { - if (this.currentTask !== SleeveTaskType.Idle || this.currentWork) { - this.finishTask(p); - } else { - this.resetTaskStatus(p); - } - // Set exp/money multipliers based on which university. // Also check that the sleeve is in the right city let loc: LocationName | undefined; @@ -770,42 +598,14 @@ export class Sleeve extends Person { * Begin a bladeburner task */ bladeburner(p: IPlayer, action: string, contract: string): boolean { - if (this.currentTask !== SleeveTaskType.Idle || this.currentWork === null) { - this.finishTask(p); - } else { - this.resetTaskStatus(p); - } - - this.gainRatesForTask.hack = 0; - this.gainRatesForTask.str = 0; - this.gainRatesForTask.def = 0; - this.gainRatesForTask.dex = 0; - this.gainRatesForTask.agi = 0; - this.gainRatesForTask.cha = 0; - this.gainRatesForTask.money = 0; - this.currentTaskLocation = ""; - - const time = 0; - - 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; this.startWork(p, new SleeveBladeburnerWork({ type: "General", name: "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), - // )})`; this.startWork(p, new SleeveBladeburnerWork({ type: "General", name: "Recruitment" })); return true; case "Diplomacy": - // time = this.getBladeburnerActionTime(p, "General", action); this.startWork(p, new SleeveBladeburnerWork({ type: "General", name: "Diplomacy" })); return true; case "Infiltrate synthoids": @@ -817,16 +617,8 @@ export class Sleeve extends Person { case "Take on contracts": this.startWork(p, new SleeveBladeburnerWork({ type: "Contracts", name: contract })); return true; - // time = this.getBladeburnerActionTime(p, "Contracts", contract); - // this.contractGainRates(p, "Contracts", contract); - // this.currentTaskLocation = this.contractSuccessChance(p, "Contracts", contract); - // this.bbContract = capitalizeEachWord(contract.toLowerCase()); - // break; } - this.bbAction = capitalizeFirstLetter(action.toLowerCase()); - this.currentTaskMaxTime = time; - this.currentTask = SleeveTaskType.Bladeburner; return true; } @@ -853,38 +645,38 @@ export class Sleeve extends Person { } } - contractGainRates(p: IPlayer, type: string, name: string): void { - const bb = p.bladeburner; - if (bb === null) { - const errorLogText = `bladeburner is null`; - console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`); - return; - } - const actionIdent = bb.getActionIdFromTypeAndName(type, name); - if (actionIdent === null) { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`); - this.resetTaskStatus(p); - return; - } - const action = bb.getActionObject(actionIdent); - if (action === null) { - const errorLogText = `Invalid action: type='${type}' name='${name}'`; - console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`); - this.resetTaskStatus(p); - return; - } - const retValue = bb.getActionStats(action, true); - this.gainRatesForTask.hack = retValue.hack; - this.gainRatesForTask.str = retValue.str; - this.gainRatesForTask.def = retValue.def; - this.gainRatesForTask.dex = retValue.dex; - this.gainRatesForTask.agi = retValue.agi; - this.gainRatesForTask.cha = retValue.cha; - const rewardMultiplier = Math.pow(action.rewardFac, action.level - 1); - this.gainRatesForTask.money = - BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * bb.skillMultipliers.money; - } + // contractGainRates(p: IPlayer, type: string, name: string): void { + // const bb = p.bladeburner; + // if (bb === null) { + // const errorLogText = `bladeburner is null`; + // console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`); + // return; + // } + // const actionIdent = bb.getActionIdFromTypeAndName(type, name); + // if (actionIdent === null) { + // const errorLogText = `Invalid action: type='${type}' name='${name}'`; + // console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`); + // this.resetTaskStatus(p); + // return; + // } + // const action = bb.getActionObject(actionIdent); + // if (action === null) { + // const errorLogText = `Invalid action: type='${type}' name='${name}'`; + // console.error(`Function: sleeves.contractGainRates; Message: '${errorLogText}'`); + // this.resetTaskStatus(p); + // return; + // } + // const retValue = bb.getActionStats(action, true); + // this.gainRatesForTask.hack = retValue.hack; + // this.gainRatesForTask.str = retValue.str; + // this.gainRatesForTask.def = retValue.def; + // this.gainRatesForTask.dex = retValue.dex; + // this.gainRatesForTask.agi = retValue.agi; + // this.gainRatesForTask.cha = retValue.cha; + // const rewardMultiplier = Math.pow(action.rewardFac, action.level - 1); + // this.gainRatesForTask.money = + // BladeburnerConstants.ContractBaseMoneyGain * rewardMultiplier * bb.skillMultipliers.money; + // } getBladeburnerActionTime(p: IPlayer, type: string, name: string): number { //Maybe find workerscript and use original diff --git a/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts index 62b74d908..16a6f98cd 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts @@ -5,7 +5,6 @@ import { Work, WorkType } from "./Work"; import { CONSTANTS } from "../../../Constants"; import { GeneralActions } from "../../../Bladeburner/data/GeneralActions"; import { applyWorkStatsExp, WorkStats } from "../../../Work/WorkStats"; -import { Contracts } from "src/Bladeburner/data/Contracts"; interface SleeveBladeburnerWorkParams { type: string; diff --git a/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts b/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts index e86e7ab5d..f89a6f5fe 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts @@ -38,12 +38,16 @@ export class SleeveCrimeWork extends Work { }); } + cyclesNeeded(): number { + return this.getCrime().time / CONSTANTS._idleSpeed; + } + process(player: IPlayer, sleeve: Sleeve, cycles: number): number { this.cyclesWorked += cycles; const crime = this.getCrime(); const gains = this.getExp(); - if (this.cyclesWorked >= crime.time / CONSTANTS._idleSpeed) { + if (this.cyclesWorked >= this.cyclesNeeded()) { if (Math.random() < crime.successRate(sleeve)) { applyWorkStats(player, sleeve, gains, 1, "sleeves"); @@ -51,7 +55,7 @@ export class SleeveCrimeWork extends Work { } else { applyWorkStats(player, sleeve, scaleWorkStats(gains, 0.25), 1, "sleeves"); } - this.cyclesWorked -= crime.time / CONSTANTS._idleSpeed; + this.cyclesWorked -= this.cyclesNeeded(); } return 0; } diff --git a/src/PersonObjects/Sleeve/ui/MoreEarningsModal.tsx b/src/PersonObjects/Sleeve/ui/MoreEarningsModal.tsx deleted file mode 100644 index 7b0c76654..000000000 --- a/src/PersonObjects/Sleeve/ui/MoreEarningsModal.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Sleeve } from "../Sleeve"; -import { numeralWrapper } from "../../../ui/numeralFormat"; -import { Money } from "../../../ui/React/Money"; -import * as React from "react"; -import { StatsTable } from "../../../ui/React/StatsTable"; -import { Modal } from "../../../ui/React/Modal"; - -interface IProps { - open: boolean; - onClose: () => void; - sleeve: Sleeve; -} - -export function MoreEarningsModal(props: IProps): React.ReactElement { - return ( - - ], - ["Hacking Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.hack)], - ["Strength Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.str)], - ["Defense Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.def)], - ["Dexterity Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.dex)], - ["Agility Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.agi)], - ["Charisma Exp ", numeralWrapper.formatExp(props.sleeve.earningsForTask.cha)], - ]} - title="Earnings for Current Task:" - /> -
- ], - ["Hacking Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.hack)], - ["Strength Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.str)], - ["Defense Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.def)], - ["Dexterity Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.dex)], - ["Agility Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.agi)], - ["Charisma Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForPlayer.cha)], - ]} - title="Total Earnings for Host Consciousness:" - /> -
- ], - ["Hacking Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.hack)], - ["Strength Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.str)], - ["Defense Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.def)], - ["Dexterity Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.dex)], - ["Agility Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.agi)], - ["Charisma Exp: ", numeralWrapper.formatExp(props.sleeve.earningsForSleeves.cha)], - ]} - title="Total Earnings for Other Sleeves:" - /> -
-
- ); -} diff --git a/src/PersonObjects/Sleeve/ui/SleeveElem.tsx b/src/PersonObjects/Sleeve/ui/SleeveElem.tsx index de0bfd4d7..8edf74759 100644 --- a/src/PersonObjects/Sleeve/ui/SleeveElem.tsx +++ b/src/PersonObjects/Sleeve/ui/SleeveElem.tsx @@ -2,13 +2,10 @@ import { Box, Button, Paper, Tooltip, Typography } from "@mui/material"; import React, { useState } from "react"; import { FactionWorkType } from "../../../Work/data/FactionWorkType"; import { CONSTANTS } from "../../../Constants"; -import { Crimes } from "../../../Crime/Crimes"; import { use } from "../../../ui/Context"; import { numeralWrapper } from "../../../ui/numeralFormat"; import { ProgressBar } from "../../../ui/React/Progress"; import { Sleeve } from "../Sleeve"; -import { SleeveTaskType } from "../SleeveTaskTypesEnum"; -import { MoreEarningsModal } from "./MoreEarningsModal"; import { MoreStatsModal } from "./MoreStatsModal"; import { SleeveAugmentationsModal } from "./SleeveAugmentationsModal"; import { EarningsElement, StatsElement } from "./StatsElement"; @@ -32,7 +29,6 @@ interface IProps { export function SleeveElem(props: IProps): React.ReactElement { const player = use.Player(); const [statsOpen, setStatsOpen] = useState(false); - const [earningsOpen, setEarningsOpen] = useState(false); const [travelOpen, setTravelOpen] = useState(false); const [augmentationsOpen, setAugmentationsOpen] = useState(false); @@ -161,7 +157,6 @@ export function SleeveElem(props: IProps): React.ReactElement { - Insufficient funds : ""}>