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..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);
@@ -1452,7 +1454,7 @@ export class Bladeburner implements IBladeburner {
}
}
} catch (e: unknown) {
- exceptionAlert(e);
+ exceptionAlert(String(e));
}
break;
}
@@ -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/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..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.`,
);
@@ -180,20 +181,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) =>
@@ -229,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:
@@ -349,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/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 4f6f5cf30..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,88 +30,22 @@ 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, 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 { SleeveInfiltrateWork } from "./Work/SleeveInfiltrateWork";
+import { SleeveSupportWork } from "./Work/SleeveSupportWork";
+import { SleeveBladeburnerWork } from "./Work/SleeveBladeburnerWork";
+import { SleeveCrimeWork } from "./Work/SleeveCrimeWork";
export class Sleeve extends Person {
- /**
- * 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 = "";
+ currentWork: Work | null = null;
/**
* Clone retains 'memory' synchronization (and maybe exp?) upon prestige/installing Augs
@@ -149,6 +80,23 @@ export class Sleeve extends Person {
}
}
+ shockBonus(): number {
+ return this.shock / 100;
+ }
+ syncBonus(): number {
+ 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
*/
@@ -158,121 +106,19 @@ export class Sleeve extends Person {
return false;
}
- if (this.currentTask !== SleeveTaskType.Idle) {
- 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;
}
/**
* Called to stop the current task
*/
- finishTask(p: IPlayer): ITaskTracker {
- let retValue: ITaskTracker = createTaskTracker(); // Amount of exp to be gained by other sleeves
-
- 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 retValue;
- }
- 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;
- }
- retValue = this.gainExperience(p, successGainRates);
- this.gainMoney(p, this.gainRatesForTask);
-
- p.karma -= crime.karma * (this.sync / 100);
- } else {
- retValue = this.gainExperience(p, this.gainRatesForTask);
- }
-
- // Do not reset task to IDLE
- this.currentTaskTime = 0;
- return retValue;
- }
- } else if (this.currentTask === SleeveTaskType.Bladeburner) {
- if (this.currentTaskMaxTime === 0) {
- this.currentTaskTime = 0;
- return retValue;
- }
- // 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 retValue;
- }
-
- if (this.currentTaskTime >= this.currentTaskMaxTime) {
- if (this.bbAction === "Infiltrate synthoids") {
- bb.infiltrateSynthoidCommunities(p);
- this.currentTaskTime = 0;
- return retValue;
- }
- 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 retValue;
- }
-
- 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.gainMoney(p, bbRetValue);
-
- // Do not reset task to IDLE
- this.currentTaskTime = 0;
- return retValue;
- }
- }
- }
- }
+ finishTask(p: IPlayer): void {
+ this.stopWork(p);
this.resetTaskStatus(p);
- return retValue;
+ return;
}
/**
@@ -326,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) {
@@ -370,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),
@@ -394,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
@@ -430,54 +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.Faction) {
- let favorMult = 1;
- const fac: Faction | null = Factions[this.currentTaskLocation];
- if (fac != null) {
- favorMult = 1 + fac.favor / 100;
- }
+ // 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;
+ // }
- 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) {
- 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;
@@ -505,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
@@ -532,93 +338,19 @@ 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);
- 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;
- }
+ if (this.currentWork) {
+ this.currentWork.process(p, this, cyclesUsed);
+ this.storedCycles -= cyclesUsed;
+ return;
}
- this.currentTaskTime += time;
-
// Shock gradually goes towards 100
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);
-
- 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;
- }
- 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) {
- if (this.currentTask === SleeveTaskType.Crime || this.currentTask === SleeveTaskType.Bladeburner) {
- this.finishTask(p);
- } else {
- this.finishTask(p);
- }
- }
-
this.updateStatLevels();
this.storedCycles -= cyclesUsed;
@@ -630,48 +362,16 @@ export class Sleeve extends Person {
* Resets all parameters used to keep information about the current task
*/
resetTaskStatus(p: IPlayer): void {
- 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;
- this.currentTaskTime = 0;
- this.currentTaskMaxTime = 0;
- this.factionWorkType = FactionWorkType.HACKING;
- this.crimeType = "";
- this.currentTaskLocation = "";
- this.gymStatType = "";
- this.className = "";
- this.bbAction = "";
- this.bbContract = "------";
+ this.stopWork(p);
}
shockRecovery(p: IPlayer): boolean {
- if (this.currentTask !== SleeveTaskType.Idle) {
- this.finishTask(p);
- } else {
- this.resetTaskStatus(p);
- }
-
- this.currentTask = SleeveTaskType.Recovery;
+ this.startWork(p, new SleeveRecoveryWork());
return true;
}
synchronize(p: IPlayer): boolean {
- if (this.currentTask !== SleeveTaskType.Idle) {
- this.finishTask(p);
- } else {
- this.resetTaskStatus(p);
- }
-
- this.currentTask = SleeveTaskType.Synchro;
+ this.startWork(p, new SleeveSynchroWork());
return true;
}
@@ -679,66 +379,59 @@ 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.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 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.startWork(
+ p,
+ new SleeveClassWork({
+ classType: classType,
+ location: loc,
+ }),
+ );
return true;
}
@@ -767,99 +460,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,58 +478,12 @@ export class Sleeve extends Person {
return false;
}
- if (this.currentTask !== SleeveTaskType.Idle) {
- 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;
- }
- 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.startWork(p, new SleeveCompanyWork({ companyName: companyName }));
return true;
}
@@ -944,49 +498,31 @@ export class Sleeve extends Person {
return false;
}
- if (this.currentTask !== SleeveTaskType.Idle) {
- this.finishTask(p);
- } else {
- this.resetTaskStatus(p);
- }
-
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.startWork(
+ p,
+ new SleeveFactionWork({
+ factionWorkType: factionWorkType,
+ factionName: faction.name,
+ }),
+ );
return true;
}
@@ -995,81 +531,65 @@ export class Sleeve extends Person {
* Begin a gym workout task
*/
workoutAtGym(p: IPlayer, gymName: string, stat: string): boolean {
- if (this.currentTask !== SleeveTaskType.Idle) {
- 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 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.startWork(
+ p,
+ new SleeveClassWork({
+ classType: classType,
+ location: loc,
+ }),
+ );
return true;
}
@@ -1078,60 +598,27 @@ export class Sleeve extends Person {
* Begin a bladeburner task
*/
bladeburner(p: IPlayer, action: string, contract: string): boolean {
- if (this.currentTask !== SleeveTaskType.Idle) {
- 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 = "";
-
- let 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;
- break;
+ 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),
- )})`;
- break;
+ this.startWork(p, new SleeveBladeburnerWork({ type: "General", name: "Recruitment" }));
+ return true;
case "Diplomacy":
- time = this.getBladeburnerActionTime(p, "General", action);
- break;
+ this.startWork(p, new SleeveBladeburnerWork({ type: "General", name: "Diplomacy" }));
+ return true;
case "Infiltrate synthoids":
- time = 60000;
- this.currentTaskLocation = "This will generate additional contracts and operations";
- 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;
}
- this.bbAction = capitalizeFirstLetter(action.toLowerCase());
- this.currentTaskMaxTime = time;
- this.currentTask = SleeveTaskType.Bladeburner;
return true;
}
@@ -1158,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
new file mode 100644
index 000000000..16a6f98cd
--- /dev/null
+++ b/src/PersonObjects/Sleeve/Work/SleeveBladeburnerWork.ts
@@ -0,0 +1,74 @@
+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";
+
+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/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/SleeveCrimeWork.ts b/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts
new file mode 100644
index 000000000..f89a6f5fe
--- /dev/null
+++ b/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts
@@ -0,0 +1,84 @@
+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,
+ });
+ }
+
+ 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 >= this.cyclesNeeded()) {
+ 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 -= this.cyclesNeeded();
+ }
+ 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
new file mode 100644
index 000000000..ec732cdb5
--- /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.stopWork(player);
+ 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..5f0de0a5d
--- /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.stopWork(player);
+ 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/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
new file mode 100644
index 000000000..09b5584d3
--- /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.stopWork(player);
+ 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..37703df1a
--- /dev/null
+++ b/src/PersonObjects/Sleeve/Work/Work.ts
@@ -0,0 +1,30 @@
+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;
+ finish(_player: IPlayer): void {
+ /* left for children to implement */
+ }
+}
+
+export enum WorkType {
+ COMPANY = "COMPANY",
+ FACTION = "FACTION",
+ CRIME = "CRIME",
+ CLASS = "CLASS",
+ RECOVERY = "RECOVERY",
+ SYNCHRO = "SYNCHRO",
+ BLADEBURNER = "BLADEBURNER",
+ INFILTRATE = "INFILTRATE",
+ SUPPORT = "SUPPORT",
+}
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 64853d7f9..8edf74759 100644
--- a/src/PersonObjects/Sleeve/ui/SleeveElem.tsx
+++ b/src/PersonObjects/Sleeve/ui/SleeveElem.tsx
@@ -2,18 +2,24 @@ 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";
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 { isSleeveInfiltrateWork } from "../Work/SleeveInfiltrateWork";
+import { isSleeveSupportWork } from "../Work/SleeveSupportWork";
+import { isSleeveBladeburnerWork } from "../Work/SleeveBladeburnerWork";
+import { isSleeveCrimeWork } from "../Work/SleeveCrimeWork";
interface IProps {
sleeve: Sleeve;
@@ -23,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);
@@ -64,83 +69,85 @@ 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.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;
+ let desc = <>This sleeve is currently idle>;
+
+ 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)) {
+ 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;
}
- 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.Gym:
- desc = <>This sleeve is currently working out 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;
- }
- 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]}`);
+ 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 (isSleeveBladeburnerWork(props.sleeve.currentWork)) {
+ const w = props.sleeve.currentWork;
+ desc = (
+ <>
+ This sleeve is currently attempting to perform {w.actionName}. (
+ {((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)}%)
+ >
+ );
+ }
+
+ if (isSleeveSupportWork(props.sleeve.currentWork)) {
+ desc = <>This sleeve is currently supporting you in your bladeburner activities.>;
}
return (
@@ -150,7 +157,6 @@ export function SleeveElem(props: IProps): React.ReactElement {
-
Insufficient funds : ""}>
setStatsOpen(false)} sleeve={props.sleeve} />
- setEarningsOpen(false)} sleeve={props.sleeve} />
setTravelOpen(false)}
diff --git a/src/PersonObjects/Sleeve/ui/StatsElement.tsx b/src/PersonObjects/Sleeve/ui/StatsElement.tsx
index 5851319b0..4000a8348 100644
--- a/src/PersonObjects/Sleeve/ui/StatsElement.tsx
+++ b/src/PersonObjects/Sleeve/ui/StatsElement.tsx
@@ -13,6 +13,10 @@ 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";
+import { isSleeveCrimeWork } from "../Work/SleeveCrimeWork";
interface IProps {
sleeve: Sleeve;
@@ -94,35 +98,56 @@ 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 {
+ }
+ if (isSleeveClassWork(props.sleeve.currentWork)) {
+ const rates = props.sleeve.currentWork.calculateRates(player, props.sleeve);
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`],
+ [`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:`, ],
];
- if (props.sleeve.currentTask === SleeveTaskType.Company || props.sleeve.currentTask === SleeveTaskType.Faction) {
- const repGain: number = props.sleeve.getRepGain(player);
- data.push([`Reputation:`, ]);
- }
}
return (
@@ -130,7 +155,7 @@ export function EarningsElement(props: IProps): React.ReactElement {
- Earnings
+ Earnings {props.sleeve.storedCycles > 50 ? "(overclock)" : ""}
{data.map(([a, b]) => (
diff --git a/src/PersonObjects/Sleeve/ui/TaskSelector.tsx b/src/PersonObjects/Sleeve/ui/TaskSelector.tsx
index 362b3c6b4..eb68ffd72 100644
--- a/src/PersonObjects/Sleeve/ui/TaskSelector.tsx
+++ b/src/PersonObjects/Sleeve/ui/TaskSelector.tsx
@@ -1,7 +1,6 @@
import React, { useState } from "react";
import { Sleeve } from "../Sleeve";
import { IPlayer } from "../../IPlayer";
-import { SleeveTaskType } from "../SleeveTaskTypesEnum";
import { Crimes } from "../../../Crime/Crimes";
import { LocationName } from "../../../Locations/data/LocationNames";
import { CityName } from "../../../Locations/data/CityNames";
@@ -9,7 +8,9 @@ import { Factions } from "../../../Faction/Factions";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import { FactionNames } from "../../../Faction/data/FactionNames";
-import { FactionWorkType } from "../../../Work/data/FactionWorkType";
+import { isSleeveFactionWork } from "../Work/SleeveFactionWork";
+import { isSleeveCompanyWork } from "../Work/SleeveCompanyWork";
+import { isSleeveBladeburnerWork } from "../Work/SleeveBladeburnerWork";
const universitySelectorOptions: string[] = [
"Study Computer Science",
@@ -49,8 +50,8 @@ function possibleJobs(player: IPlayer, sleeve: Sleeve): string[] {
if (sleeve === otherSleeve) {
continue;
}
- if (otherSleeve.currentTask === SleeveTaskType.Company) {
- forbiddenCompanies.push(otherSleeve.currentTaskLocation);
+ if (isSleeveCompanyWork(otherSleeve.currentWork)) {
+ forbiddenCompanies.push(otherSleeve.currentWork.companyName);
}
}
const allJobs: string[] = Object.keys(player.jobs);
@@ -68,8 +69,8 @@ function possibleFactions(player: IPlayer, sleeve: Sleeve): string[] {
if (sleeve === otherSleeve) {
continue;
}
- if (otherSleeve.currentTask === SleeveTaskType.Faction) {
- forbiddenFactions.push(otherSleeve.currentTaskLocation);
+ if (isSleeveFactionWork(otherSleeve.currentWork)) {
+ forbiddenFactions.push(otherSleeve.currentWork.factionName);
}
}
@@ -98,8 +99,9 @@ function possibleContracts(player: IPlayer, sleeve: Sleeve): string[] {
if (sleeve === otherSleeve) {
continue;
}
- if (otherSleeve.currentTask === SleeveTaskType.Bladeburner && otherSleeve.bbAction == "Take on contracts") {
- contracts = contracts.filter((x) => x != otherSleeve.bbContract);
+ if (isSleeveBladeburnerWork(otherSleeve.currentWork) && otherSleeve.currentWork.actionType === "Contracts") {
+ const w = otherSleeve.currentWork;
+ contracts = contracts.filter((x) => x != w.actionName);
}
}
if (contracts.length === 0) {
@@ -241,51 +243,37 @@ const canDo: {
};
function getABC(sleeve: Sleeve): [string, string, string] {
- switch (sleeve.currentTask) {
- case SleeveTaskType.Idle:
- return ["------", "------", "------"];
- case SleeveTaskType.Company:
- return ["Work for Company", sleeve.currentTaskLocation, "------"];
- case SleeveTaskType.Faction: {
- let workType = "";
- switch (sleeve.factionWorkType) {
- case FactionWorkType.HACKING:
- workType = "Hacking Contracts";
- break;
- case FactionWorkType.FIELD:
- workType = "Field Work";
- break;
- case FactionWorkType.SECURITY:
- workType = "Security Work";
- break;
- }
- return ["Work for Faction", sleeve.currentTaskLocation, workType];
- }
- case SleeveTaskType.Crime:
- return ["Commit Crime", sleeve.crimeType, "------"];
- case SleeveTaskType.Class:
- return ["Take University Course", sleeve.className, sleeve.currentTaskLocation];
- case SleeveTaskType.Gym: {
- switch (sleeve.gymStatType) {
- case "none":
- return ["Idle", "------", "------"];
- case "str":
- return ["Workout at Gym", "Train Strength", sleeve.currentTaskLocation];
- case "def":
- return ["Workout at Gym", "Train Defense", sleeve.currentTaskLocation];
- case "dex":
- return ["Workout at Gym", "Train Dexterity", sleeve.currentTaskLocation];
- case "agi":
- return ["Workout at Gym", "Train Agility", sleeve.currentTaskLocation];
- }
- }
- case SleeveTaskType.Bladeburner:
- return ["Perform Bladeburner Actions", sleeve.bbAction, sleeve.bbContract];
- case SleeveTaskType.Recovery:
- return ["Shock Recovery", "------", "------"];
- case SleeveTaskType.Synchro:
- return ["Synchronize", "------", "------"];
- }
+ return ["------", "------", "------"];
+
+ // switch (sleeve.currentTask) {
+ // case SleeveTaskType.Idle:
+ // case SleeveTaskType.Company:
+ // case SleeveTaskType.Faction: {
+ // }
+ // case SleeveTaskType.Crime:
+ // return ["Commit Crime", sleeve.crimeType, "------"];
+ // case SleeveTaskType.Class:
+ // case SleeveTaskType.Gym: {
+ // switch (sleeve.gymStatType) {
+ // case "none":
+ // return ["Idle", "------", "------"];
+ // case "str":
+ // return ["Workout at Gym", "Train Strength", sleeve.currentTaskLocation];
+ // case "def":
+ // return ["Workout at Gym", "Train Defense", sleeve.currentTaskLocation];
+ // case "dex":
+ // return ["Workout at Gym", "Train Dexterity", sleeve.currentTaskLocation];
+ // case "agi":
+ // return ["Workout at Gym", "Train Agility", sleeve.currentTaskLocation];
+ // }
+ // }
+ // case SleeveTaskType.Bladeburner:
+ // return ["Perform Bladeburner Actions", sleeve.bbAction, sleeve.bbContract];
+ // case SleeveTaskType.Recovery:
+ // return ["Shock Recovery", "------", "------"];
+ // case SleeveTaskType.Synchro:
+ // return ["Synchronize", "------", "------"];
+ // }
}
export function TaskSelector(props: IProps): React.ReactElement {
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/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts
index 65cc2b00d..ca79ae60c 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 type 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/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/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/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,
};
}
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,