Merge pull request #3935 from danielyxie/sleeve-work

API: rework sleeve work
This commit is contained in:
hydroflame 2022-07-28 03:09:12 -04:00 committed by GitHub
commit 72a75fe7b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1199 additions and 1142 deletions

@ -95,10 +95,10 @@ Singularity
This means calls like 'ns.connect' need to be changed to 'ns.singularity.connect' 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. are very common tokens that would trick the ram calculation.
corporation.bribe corporation.bribe

@ -36,6 +36,8 @@ import { joinFaction } from "../Faction/FactionHelpers";
import { WorkerScript } from "../Netscript/WorkerScript"; import { WorkerScript } from "../Netscript/WorkerScript";
import { FactionNames } from "../Faction/data/FactionNames"; import { FactionNames } from "../Faction/data/FactionNames";
import { KEY } from "../utils/helpers/keyCodes"; import { KEY } from "../utils/helpers/keyCodes";
import { isSleeveInfiltrateWork } from "../PersonObjects/Sleeve/Work/SleeveInfiltrateWork";
import { isSleeveSupportWork } from "../PersonObjects/Sleeve/Work/SleeveSupportWork";
interface BlackOpsAttempt { interface BlackOpsAttempt {
error?: string; error?: string;
@ -1124,7 +1126,7 @@ export class Bladeburner implements IBladeburner {
const losses = getRandomInt(0, max); const losses = getRandomInt(0, max);
this.teamSize -= losses; this.teamSize -= losses;
if (this.teamSize < this.sleeveSize) { 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--) { for (let i = 0; i > this.teamSize - this.sleeveSize; i--) {
const r = Math.floor(Math.random() * sup.length); const r = Math.floor(Math.random() * sup.length);
sup[r].takeDamage(sup[r].hp.max); sup[r].takeDamage(sup[r].hp.max);
@ -1438,7 +1440,7 @@ export class Bladeburner implements IBladeburner {
const losses = getRandomInt(1, teamLossMax); const losses = getRandomInt(1, teamLossMax);
this.teamSize -= losses; this.teamSize -= losses;
if (this.teamSize < this.sleeveSize) { 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--) { for (let i = 0; i > this.teamSize - this.sleeveSize; i--) {
const r = Math.floor(Math.random() * sup.length); const r = Math.floor(Math.random() * sup.length);
sup[r].takeDamage(sup[r].hp.max); sup[r].takeDamage(sup[r].hp.max);
@ -1452,7 +1454,7 @@ export class Bladeburner implements IBladeburner {
} }
} }
} catch (e: unknown) { } catch (e: unknown) {
exceptionAlert(e); exceptionAlert(String(e));
} }
break; break;
} }
@ -1602,7 +1604,7 @@ export class Bladeburner implements IBladeburner {
} }
infiltrateSynthoidCommunities(p: IPlayer): void { 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; const amt = Math.pow(infilSleeves, -0.5) / 2;
for (const contract of Object.keys(this.contracts)) { for (const contract of Object.keys(this.contracts)) {
this.contracts[contract].count += amt; this.contracts[contract].count += amt;

@ -1,11 +1,13 @@
import React from "react"; import React from "react";
import { newWorkStats, WorkStats } from "../../Work/WorkStats";
interface IContract { interface IGeneral {
desc: JSX.Element; desc: JSX.Element;
exp: WorkStats;
} }
export const GeneralActions: { export const GeneralActions: {
[key: string]: IContract | undefined; [key: string]: IGeneral | undefined;
} = { } = {
Training: { Training: {
desc: ( desc: (
@ -14,6 +16,12 @@ export const GeneralActions: {
all combat stats and also increases your max stamina. all combat stats and also increases your max stamina.
</> </>
), ),
exp: newWorkStats({
strExp: 30,
defExp: 30,
dexExp: 30,
agiExp: 30,
}),
}, },
"Field Analysis": { "Field Analysis": {
@ -27,6 +35,10 @@ export const GeneralActions: {
Does NOT require stamina. Does NOT require stamina.
</> </>
), ),
exp: newWorkStats({
hackExp: 20,
chaExp: 20,
}),
}, },
Recruitment: { Recruitment: {
@ -38,6 +50,9 @@ export const GeneralActions: {
Does NOT require stamina. Does NOT require stamina.
</> </>
), ),
exp: newWorkStats({
chaExp: 120,
}),
}, },
Diplomacy: { Diplomacy: {
@ -50,6 +65,9 @@ export const GeneralActions: {
Does NOT require stamina. Does NOT require stamina.
</> </>
), ),
exp: newWorkStats({
chaExp: 120,
}),
}, },
"Hyperbolic Regeneration Chamber": { "Hyperbolic Regeneration Chamber": {
@ -61,6 +79,7 @@ export const GeneralActions: {
<br /> <br />
</> </>
), ),
exp: newWorkStats(),
}, },
"Incite Violence": { "Incite Violence": {
desc: ( desc: (
@ -69,5 +88,12 @@ export const GeneralActions: {
additional contracts and operations, at the cost of increased Chaos. additional contracts and operations, at the cost of increased Chaos.
</> </>
), ),
exp: newWorkStats({
strExp: 10,
defExp: 10,
dexExp: 10,
agiExp: 10,
chaExp: 10,
}),
}, },
}; };

@ -6,7 +6,7 @@ import { IMap } from "../types";
import { CrimeType } from "../utils/WorkType"; import { CrimeType } from "../utils/WorkType";
export const Crimes: IMap<Crime> = { export const Crimes: IMap<Crime> = {
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, dexterity_success_weight: 1,
agility_success_weight: 1, agility_success_weight: 1,
@ -14,7 +14,7 @@ export const Crimes: IMap<Crime> = {
agility_exp: 2, 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, hacking_exp: 30,
dexterity_exp: 45, dexterity_exp: 45,
agility_exp: 45, agility_exp: 45,
@ -26,7 +26,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 7.5 * CONSTANTS.IntelligenceCrimeBaseExpGain, 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, strength_exp: 3,
defense_exp: 3, defense_exp: 3,
dexterity_exp: 3, dexterity_exp: 3,
@ -38,7 +38,7 @@ export const Crimes: IMap<Crime> = {
agility_success_weight: 0.5, 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, hacking_exp: 45,
dexterity_exp: 60, dexterity_exp: 60,
agility_exp: 60, agility_exp: 60,
@ -50,7 +50,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 15 * CONSTANTS.IntelligenceCrimeBaseExpGain, 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, dexterity_exp: 5,
agility_exp: 5, agility_exp: 5,
charisma_exp: 10, charisma_exp: 10,
@ -60,7 +60,7 @@ export const Crimes: IMap<Crime> = {
agility_success_weight: 1, 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, hacking_exp: 100,
dexterity_exp: 150, dexterity_exp: 150,
charisma_exp: 15, charisma_exp: 15,
@ -71,7 +71,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 60 * CONSTANTS.IntelligenceCrimeBaseExpGain, 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, strength_exp: 20,
defense_exp: 20, defense_exp: 20,
dexterity_exp: 20, dexterity_exp: 20,
@ -85,7 +85,7 @@ export const Crimes: IMap<Crime> = {
agility_success_weight: 1, 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, strength_exp: 2,
defense_exp: 2, defense_exp: 2,
dexterity_exp: 2, dexterity_exp: 2,
@ -99,7 +99,7 @@ export const Crimes: IMap<Crime> = {
kills: 1, 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, strength_exp: 20,
defense_exp: 20, defense_exp: 20,
dexterity_exp: 20, dexterity_exp: 20,
@ -115,7 +115,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 16 * CONSTANTS.IntelligenceCrimeBaseExpGain, 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, strength_exp: 80,
defense_exp: 80, defense_exp: 80,
dexterity_exp: 80, dexterity_exp: 80,
@ -130,7 +130,7 @@ export const Crimes: IMap<Crime> = {
intelligence_exp: 26 * CONSTANTS.IntelligenceCrimeBaseExpGain, 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, strength_exp: 300,
defense_exp: 300, defense_exp: 300,
dexterity_exp: 300, dexterity_exp: 300,
@ -145,7 +145,7 @@ export const Crimes: IMap<Crime> = {
kills: 1, 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, hacking_exp: 450,
strength_exp: 450, strength_exp: 450,
defense_exp: 450, defense_exp: 450,

@ -136,7 +136,7 @@ const stock = {
getSaleGain: RamCostConstants.ScriptGetStockRamCost, getSaleGain: RamCostConstants.ScriptGetStockRamCost,
buyStock: RamCostConstants.ScriptBuySellStockRamCost, buyStock: RamCostConstants.ScriptBuySellStockRamCost,
sellStock: RamCostConstants.ScriptBuySellStockRamCost, sellStock: RamCostConstants.ScriptBuySellStockRamCost,
short: RamCostConstants.ScriptBuySellStockRamCost, buyShort: RamCostConstants.ScriptBuySellStockRamCost,
sellShort: RamCostConstants.ScriptBuySellStockRamCost, sellShort: RamCostConstants.ScriptBuySellStockRamCost,
placeOrder: RamCostConstants.ScriptBuySellStockRamCost, placeOrder: RamCostConstants.ScriptBuySellStockRamCost,
cancelOrder: RamCostConstants.ScriptBuySellStockRamCost, cancelOrder: RamCostConstants.ScriptBuySellStockRamCost,

@ -1,5 +1,4 @@
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { SleeveTaskType } from "../PersonObjects/Sleeve/SleeveTaskTypesEnum";
import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers"; import { findSleevePurchasableAugs } from "../PersonObjects/Sleeve/SleeveHelpers";
import { StaticAugmentations } from "../Augmentation/StaticAugmentations"; import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
import { CityName } from "../Locations/data/CityNames"; import { CityName } from "../Locations/data/CityNames";
@ -15,7 +14,9 @@ import {
} from "../ScriptEditor/NetscriptDefinitions"; } from "../ScriptEditor/NetscriptDefinitions";
import { checkEnum } from "../utils/helpers/checkEnum"; import { checkEnum } from "../utils/helpers/checkEnum";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper"; 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<ISleeve> { export function NetscriptSleeve(player: IPlayer): InternalAPI<ISleeve> {
const checkSleeveAPIAccess = function (ctx: NetscriptContext): void { const checkSleeveAPIAccess = function (ctx: NetscriptContext): void {
@ -120,7 +121,7 @@ export function NetscriptSleeve(player: IPlayer): InternalAPI<ISleeve> {
continue; continue;
} }
const other = player.sleeves[i]; const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Company && other.currentTaskLocation === companyName) { if (isSleeveCompanyWork(other.currentWork) && other.currentWork.companyName === companyName) {
throw ctx.makeRuntimeErrorMsg( throw ctx.makeRuntimeErrorMsg(
`Sleeve ${sleeveNumber} cannot work for company ${companyName} because Sleeve ${i} is already working for them.`, `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<ISleeve> {
continue; continue;
} }
const other = player.sleeves[i]; const other = player.sleeves[i];
if (other.currentTask === SleeveTaskType.Faction && other.currentTaskLocation === factionName) { if (isSleeveFactionWork(other.currentWork) && other.currentWork.factionName === factionName) {
throw ctx.makeRuntimeErrorMsg( throw ctx.makeRuntimeErrorMsg(
`Sleeve ${sleeveNumber} cannot work for faction ${factionName} because Sleeve ${i} is already working for them.`, `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<ISleeve> {
}, },
getTask: getTask:
(ctx: NetscriptContext) => (ctx: NetscriptContext) =>
(_sleeveNumber: unknown): SleeveTask => { (_sleeveNumber: unknown): SleeveTask | null => {
const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber); const sleeveNumber = ctx.helper.number("sleeveNumber", _sleeveNumber);
checkSleeveAPIAccess(ctx); checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber); checkSleeveNumber(ctx, sleeveNumber);
const sl = player.sleeves[sleeveNumber]; const sl = player.sleeves[sleeveNumber];
return { if (sl.currentWork === null) return null;
task: SleeveTaskType[sl.currentTask], return sl.currentWork.APICopy();
crime: sl.crimeType,
location: sl.currentTaskLocation,
gymStatType: sl.gymStatType,
factionWorkType: FactionWorkType[sl.factionWorkType],
className: sl.className,
};
}, },
getInformation: getInformation:
(ctx: NetscriptContext) => (ctx: NetscriptContext) =>
@ -229,36 +224,6 @@ export function NetscriptSleeve(player: IPlayer): InternalAPI<ISleeve> {
strengthExp: sl.mults.strength_exp, strengthExp: sl.mults.strength_exp,
workMoney: sl.mults.work_money, 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: getSleeveAugmentations:
@ -349,11 +314,7 @@ export function NetscriptSleeve(player: IPlayer): InternalAPI<ISleeve> {
continue; continue;
} }
const other = player.sleeves[i]; const other = player.sleeves[i];
if ( if (isSleeveBladeburnerWork(other.currentWork) && other.currentWork.actionName === contract) {
other.currentTask === SleeveTaskType.Bladeburner &&
other.bbAction === action &&
other.bbContract === contract
) {
throw ctx.helper.makeRuntimeErrorMsg( throw ctx.helper.makeRuntimeErrorMsg(
`Sleeve ${sleeveNumber} cannot take on contracts because Sleeve ${i} is already performing that action.`, `Sleeve ${sleeveNumber} cannot take on contracts because Sleeve ${i} is already performing that action.`,
); );

@ -165,7 +165,7 @@ export function NetscriptStockMarket(player: IPlayer, workerScript: WorkerScript
return res ? stock.getBidPrice() : 0; return res ? stock.getBidPrice() : 0;
}, },
short: buyShort:
(ctx: NetscriptContext) => (ctx: NetscriptContext) =>
(_symbol: unknown, _shares: unknown): number => { (_symbol: unknown, _shares: unknown): number => {
const symbol = ctx.helper.string("symbol", _symbol); const symbol = ctx.helper.string("symbol", _symbol);

File diff suppressed because it is too large Load Diff

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

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

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

@ -0,0 +1,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<string, unknown> {
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;

@ -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<string, unknown> {
return {
type: this.type,
factionWorkType: this.factionWorkType,
factionName: this.factionName,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveFactionWork", this);
}
/**
* Initiatizes a FactionWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveFactionWork {
return Generic_fromJSON(SleeveFactionWork, value.data);
}
}
Reviver.constructors.SleeveFactionWork = SleeveFactionWork;

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

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

@ -0,0 +1,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<string, unknown> {
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;

@ -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<string, unknown> {
return {
type: this.type,
};
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): IReviverValue {
return Generic_toJSON("SleeveSynchroWork", this);
}
/**
* Initiatizes a SynchroWork object from a JSON save state.
*/
static fromJSON(value: IReviverValue): SleeveSynchroWork {
return Generic_fromJSON(SleeveSynchroWork, value.data);
}
}
Reviver.constructors.SleeveSynchroWork = SleeveSynchroWork;

@ -0,0 +1,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<string, unknown>;
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",
}

@ -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 (
<Modal open={props.open} onClose={props.onClose}>
<StatsTable
rows={[
["Money ", <Money money={props.sleeve.earningsForTask.money} />],
["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:"
/>
<br />
<StatsTable
rows={[
["Money: ", <Money money={props.sleeve.earningsForPlayer.money} />],
["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:"
/>
<br />
<StatsTable
rows={[
["Money: ", <Money money={props.sleeve.earningsForSleeves.money} />],
["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:"
/>
<br />
</Modal>
);
}

@ -2,18 +2,24 @@ import { Box, Button, Paper, Tooltip, Typography } from "@mui/material";
import React, { useState } from "react"; import React, { useState } from "react";
import { FactionWorkType } from "../../../Work/data/FactionWorkType"; import { FactionWorkType } from "../../../Work/data/FactionWorkType";
import { CONSTANTS } from "../../../Constants"; import { CONSTANTS } from "../../../Constants";
import { Crimes } from "../../../Crime/Crimes";
import { use } from "../../../ui/Context"; import { use } from "../../../ui/Context";
import { numeralWrapper } from "../../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { ProgressBar } from "../../../ui/React/Progress"; import { ProgressBar } from "../../../ui/React/Progress";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { SleeveTaskType } from "../SleeveTaskTypesEnum";
import { MoreEarningsModal } from "./MoreEarningsModal";
import { MoreStatsModal } from "./MoreStatsModal"; import { MoreStatsModal } from "./MoreStatsModal";
import { SleeveAugmentationsModal } from "./SleeveAugmentationsModal"; import { SleeveAugmentationsModal } from "./SleeveAugmentationsModal";
import { EarningsElement, StatsElement } from "./StatsElement"; import { EarningsElement, StatsElement } from "./StatsElement";
import { TaskSelector } from "./TaskSelector"; import { TaskSelector } from "./TaskSelector";
import { TravelModal } from "./TravelModal"; 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 { interface IProps {
sleeve: Sleeve; sleeve: Sleeve;
@ -23,7 +29,6 @@ interface IProps {
export function SleeveElem(props: IProps): React.ReactElement { export function SleeveElem(props: IProps): React.ReactElement {
const player = use.Player(); const player = use.Player();
const [statsOpen, setStatsOpen] = useState(false); const [statsOpen, setStatsOpen] = useState(false);
const [earningsOpen, setEarningsOpen] = useState(false);
const [travelOpen, setTravelOpen] = useState(false); const [travelOpen, setTravelOpen] = useState(false);
const [augmentationsOpen, setAugmentationsOpen] = useState(false); const [augmentationsOpen, setAugmentationsOpen] = useState(false);
@ -64,17 +69,43 @@ export function SleeveElem(props: IProps): React.ReactElement {
props.rerender(); props.rerender();
} }
let desc = <></>; let desc = <>This sleeve is currently idle</>;
switch (props.sleeve.currentTask) {
case SleeveTaskType.Idle: if (isSleeveCrimeWork(props.sleeve.currentWork)) {
desc = <>This sleeve is currently idle</>; const w = props.sleeve.currentWork;
break; const crime = w.getCrime();
case SleeveTaskType.Company: desc = (
desc = <>This sleeve is currently working your job at {props.sleeve.currentTaskLocation}.</>; <>
break; This sleeve is currently attempting to {crime.type} (Success Rate:{" "}
case SleeveTaskType.Faction: { {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"; let doing = "nothing";
switch (props.sleeve.factionWorkType) { switch (props.sleeve.currentWork.factionWorkType) {
case FactionWorkType.FIELD: case FactionWorkType.FIELD:
doing = "Field work"; doing = "Field work";
break; break;
@ -87,60 +118,36 @@ export function SleeveElem(props: IProps): React.ReactElement {
} }
desc = ( desc = (
<> <>
This sleeve is currently doing {doing} for {props.sleeve.currentTaskLocation}. This sleeve is currently doing {doing} for {props.sleeve.currentWork.factionName}.
</> </>
); );
break;
} }
case SleeveTaskType.Crime: { if (isSleeveCompanyWork(props.sleeve.currentWork)) {
const crime = Object.values(Crimes).find((crime) => crime.name === props.sleeve.crimeType); desc = <>This sleeve is currently working your job at {props.sleeve.currentWork.companyName}.</>;
if (!crime) throw new Error("crime should not be undefined"); }
if (isSleeveBladeburnerWork(props.sleeve.currentWork)) {
const w = props.sleeve.currentWork;
desc = ( desc = (
<> <>
This sleeve is currently attempting to {crime.type} (Success Rate:{" "} This sleeve is currently attempting to perform {w.actionName}. (
{numeralWrapper.formatPercentage(crime.successRate(props.sleeve))}). {((100 * w.cyclesWorked) / w.cyclesNeeded(player, props.sleeve)).toFixed(2)}%)
</> </>
); );
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;
} }
if (isSleeveInfiltrateWork(props.sleeve.currentWork)) {
const w = props.sleeve.currentWork;
desc = ( desc = (
<> <>
This sleeve is currently attempting to {props.sleeve.bbAction}. {message} This sleeve is currently attempting to infiltrate synthoids communities. (
{((100 * w.cyclesWorked) / w.cyclesNeeded()).toFixed(2)}%)
</> </>
); );
break;
} }
case SleeveTaskType.Recovery:
desc = ( if (isSleeveSupportWork(props.sleeve.currentWork)) {
<> desc = <>This sleeve is currently supporting you in your bladeburner activities.</>;
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]}`);
} }
return ( return (
@ -150,7 +157,6 @@ export function SleeveElem(props: IProps): React.ReactElement {
<StatsElement sleeve={props.sleeve} /> <StatsElement sleeve={props.sleeve} />
<Box display="grid" sx={{ gridTemplateColumns: "1fr 1fr", width: "100%" }}> <Box display="grid" sx={{ gridTemplateColumns: "1fr 1fr", width: "100%" }}>
<Button onClick={() => setStatsOpen(true)}>More Stats</Button> <Button onClick={() => setStatsOpen(true)}>More Stats</Button>
<Button onClick={() => setEarningsOpen(true)}>More Earnings Info</Button>
<Tooltip title={player.money < CONSTANTS.TravelCost ? <Typography>Insufficient funds</Typography> : ""}> <Tooltip title={player.money < CONSTANTS.TravelCost ? <Typography>Insufficient funds</Typography> : ""}>
<span> <span>
<Button <Button
@ -185,12 +191,21 @@ export function SleeveElem(props: IProps): React.ReactElement {
</Button> </Button>
<Typography>{desc}</Typography> <Typography>{desc}</Typography>
<Typography> <Typography>
{(props.sleeve.currentTask === SleeveTaskType.Crime || {isSleeveCrimeWork(props.sleeve.currentWork) && (
props.sleeve.currentTask === SleeveTaskType.Bladeburner) &&
props.sleeve.currentTaskMaxTime > 0 && (
<ProgressBar <ProgressBar
variant="determinate" variant="determinate"
value={(props.sleeve.currentTaskTime / props.sleeve.currentTaskMaxTime) * 100} value={(props.sleeve.currentWork.cyclesWorked / props.sleeve.currentWork.cyclesNeeded()) * 100}
color="primary"
/>
)}
{isSleeveBladeburnerWork(props.sleeve.currentWork) && (
<ProgressBar
variant="determinate"
value={
(props.sleeve.currentWork.cyclesWorked /
props.sleeve.currentWork.cyclesNeeded(player, props.sleeve)) *
100
}
color="primary" color="primary"
/> />
)} )}
@ -198,7 +213,6 @@ export function SleeveElem(props: IProps): React.ReactElement {
</span> </span>
</Paper> </Paper>
<MoreStatsModal open={statsOpen} onClose={() => setStatsOpen(false)} sleeve={props.sleeve} /> <MoreStatsModal open={statsOpen} onClose={() => setStatsOpen(false)} sleeve={props.sleeve} />
<MoreEarningsModal open={earningsOpen} onClose={() => setEarningsOpen(false)} sleeve={props.sleeve} />
<TravelModal <TravelModal
open={travelOpen} open={travelOpen}
onClose={() => setTravelOpen(false)} onClose={() => setTravelOpen(false)}

@ -13,6 +13,10 @@ import { use } from "../../../ui/Context";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { SleeveTaskType } from "../SleeveTaskTypesEnum"; 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 { interface IProps {
sleeve: Sleeve; sleeve: Sleeve;
@ -94,35 +98,56 @@ export function EarningsElement(props: IProps): React.ReactElement {
const player = use.Player(); const player = use.Player();
let data: (string | JSX.Element)[][] = []; let data: (string | JSX.Element)[][] = [];
if (props.sleeve.currentTask === SleeveTaskType.Crime) { if (isSleeveCrimeWork(props.sleeve.currentWork)) {
const gains = props.sleeve.currentWork.getExp();
data = [ data = [
[ [`Money:`, <Money money={5 * gains.money} />],
`Money`, [`Hacking Exp:`, `${numeralWrapper.formatExp(5 * gains.hackExp)}`],
<> [`Strength Exp:`, `${numeralWrapper.formatExp(5 * gains.strExp)}`],
<Money money={parseFloat(props.sleeve.currentTaskLocation)} /> (on success) [`Defense Exp:`, `${numeralWrapper.formatExp(5 * gains.defExp)}`],
</>, [`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * gains.dexExp)}`],
], [`Agility Exp:`, `${numeralWrapper.formatExp(5 * gains.agiExp)}`],
[`Hacking Exp`, `${numeralWrapper.formatExp(props.sleeve.gainRatesForTask.hack)} (2x on success)`], [`Charisma Exp:`, `${numeralWrapper.formatExp(5 * gains.chaExp)}`],
[`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)`],
]; ];
} else {
data = [
[`Money:`, <MoneyRate money={5 * props.sleeve.gainRatesForTask.money} />],
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.hack)} / sec`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.str)} / sec`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.def)} / sec`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.dex)} / sec`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.agi)} / sec`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * props.sleeve.gainRatesForTask.cha)} / sec`],
];
if (props.sleeve.currentTask === SleeveTaskType.Company || props.sleeve.currentTask === SleeveTaskType.Faction) {
const repGain: number = props.sleeve.getRepGain(player);
data.push([`Reputation:`, <ReputationRate reputation={5 * repGain} />]);
} }
if (isSleeveClassWork(props.sleeve.currentWork)) {
const rates = props.sleeve.currentWork.calculateRates(player, props.sleeve);
data = [
[`Money:`, <MoneyRate money={5 * rates.money} />],
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * rates.hackExp)} / sec`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * rates.strExp)} / sec`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * rates.defExp)} / sec`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * rates.dexExp)} / sec`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * rates.agiExp)} / sec`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * rates.chaExp)} / sec`],
];
}
if (isSleeveFactionWork(props.sleeve.currentWork)) {
const rates = props.sleeve.currentWork.getExpRates(props.sleeve);
const repGain = props.sleeve.currentWork.getReputationRate(props.sleeve);
data = [
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * rates.hackExp)} / sec`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * rates.strExp)} / sec`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * rates.defExp)} / sec`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * rates.dexExp)} / sec`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * rates.agiExp)} / sec`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * rates.chaExp)} / sec`],
[`Reputation:`, <ReputationRate reputation={5 * repGain} />],
];
}
if (isSleeveCompanyWork(props.sleeve.currentWork)) {
const rates = props.sleeve.currentWork.getGainRates(player, props.sleeve);
data = [
[`Money:`, <MoneyRate money={5 * rates.money} />],
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * rates.hackExp)} / sec`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * rates.strExp)} / sec`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * rates.defExp)} / sec`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * rates.dexExp)} / sec`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * rates.agiExp)} / sec`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * rates.chaExp)} / sec`],
[`Reputation:`, <ReputationRate reputation={5 * rates.reputation} />],
];
} }
return ( return (
@ -130,7 +155,7 @@ export function EarningsElement(props: IProps): React.ReactElement {
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableCell classes={{ root: classes.cellNone }}> <TableCell classes={{ root: classes.cellNone }}>
<Typography variant="h6">Earnings</Typography> <Typography variant="h6">Earnings {props.sleeve.storedCycles > 50 ? "(overclock)" : ""}</Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>
{data.map(([a, b]) => ( {data.map(([a, b]) => (

@ -1,7 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { IPlayer } from "../../IPlayer"; import { IPlayer } from "../../IPlayer";
import { SleeveTaskType } from "../SleeveTaskTypesEnum";
import { Crimes } from "../../../Crime/Crimes"; import { Crimes } from "../../../Crime/Crimes";
import { LocationName } from "../../../Locations/data/LocationNames"; import { LocationName } from "../../../Locations/data/LocationNames";
import { CityName } from "../../../Locations/data/CityNames"; import { CityName } from "../../../Locations/data/CityNames";
@ -9,7 +8,9 @@ import { Factions } from "../../../Faction/Factions";
import Select, { SelectChangeEvent } from "@mui/material/Select"; import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import { FactionNames } from "../../../Faction/data/FactionNames"; 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[] = [ const universitySelectorOptions: string[] = [
"Study Computer Science", "Study Computer Science",
@ -49,8 +50,8 @@ function possibleJobs(player: IPlayer, sleeve: Sleeve): string[] {
if (sleeve === otherSleeve) { if (sleeve === otherSleeve) {
continue; continue;
} }
if (otherSleeve.currentTask === SleeveTaskType.Company) { if (isSleeveCompanyWork(otherSleeve.currentWork)) {
forbiddenCompanies.push(otherSleeve.currentTaskLocation); forbiddenCompanies.push(otherSleeve.currentWork.companyName);
} }
} }
const allJobs: string[] = Object.keys(player.jobs); const allJobs: string[] = Object.keys(player.jobs);
@ -68,8 +69,8 @@ function possibleFactions(player: IPlayer, sleeve: Sleeve): string[] {
if (sleeve === otherSleeve) { if (sleeve === otherSleeve) {
continue; continue;
} }
if (otherSleeve.currentTask === SleeveTaskType.Faction) { if (isSleeveFactionWork(otherSleeve.currentWork)) {
forbiddenFactions.push(otherSleeve.currentTaskLocation); forbiddenFactions.push(otherSleeve.currentWork.factionName);
} }
} }
@ -98,8 +99,9 @@ function possibleContracts(player: IPlayer, sleeve: Sleeve): string[] {
if (sleeve === otherSleeve) { if (sleeve === otherSleeve) {
continue; continue;
} }
if (otherSleeve.currentTask === SleeveTaskType.Bladeburner && otherSleeve.bbAction == "Take on contracts") { if (isSleeveBladeburnerWork(otherSleeve.currentWork) && otherSleeve.currentWork.actionType === "Contracts") {
contracts = contracts.filter((x) => x != otherSleeve.bbContract); const w = otherSleeve.currentWork;
contracts = contracts.filter((x) => x != w.actionName);
} }
} }
if (contracts.length === 0) { if (contracts.length === 0) {
@ -241,51 +243,37 @@ const canDo: {
}; };
function getABC(sleeve: Sleeve): [string, string, string] { function getABC(sleeve: Sleeve): [string, string, string] {
switch (sleeve.currentTask) {
case SleeveTaskType.Idle:
return ["------", "------", "------"]; return ["------", "------", "------"];
case SleeveTaskType.Company:
return ["Work for Company", sleeve.currentTaskLocation, "------"]; // switch (sleeve.currentTask) {
case SleeveTaskType.Faction: { // case SleeveTaskType.Idle:
let workType = ""; // case SleeveTaskType.Company:
switch (sleeve.factionWorkType) { // case SleeveTaskType.Faction: {
case FactionWorkType.HACKING: // }
workType = "Hacking Contracts"; // case SleeveTaskType.Crime:
break; // return ["Commit Crime", sleeve.crimeType, "------"];
case FactionWorkType.FIELD: // case SleeveTaskType.Class:
workType = "Field Work"; // case SleeveTaskType.Gym: {
break; // switch (sleeve.gymStatType) {
case FactionWorkType.SECURITY: // case "none":
workType = "Security Work"; // return ["Idle", "------", "------"];
break; // case "str":
} // return ["Workout at Gym", "Train Strength", sleeve.currentTaskLocation];
return ["Work for Faction", sleeve.currentTaskLocation, workType]; // case "def":
} // return ["Workout at Gym", "Train Defense", sleeve.currentTaskLocation];
case SleeveTaskType.Crime: // case "dex":
return ["Commit Crime", sleeve.crimeType, "------"]; // return ["Workout at Gym", "Train Dexterity", sleeve.currentTaskLocation];
case SleeveTaskType.Class: // case "agi":
return ["Take University Course", sleeve.className, sleeve.currentTaskLocation]; // return ["Workout at Gym", "Train Agility", sleeve.currentTaskLocation];
case SleeveTaskType.Gym: { // }
switch (sleeve.gymStatType) { // }
case "none": // case SleeveTaskType.Bladeburner:
return ["Idle", "------", "------"]; // return ["Perform Bladeburner Actions", sleeve.bbAction, sleeve.bbContract];
case "str": // case SleeveTaskType.Recovery:
return ["Workout at Gym", "Train Strength", sleeve.currentTaskLocation]; // return ["Shock Recovery", "------", "------"];
case "def": // case SleeveTaskType.Synchro:
return ["Workout at Gym", "Train Defense", sleeve.currentTaskLocation]; // return ["Synchronize", "------", "------"];
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 { export function TaskSelector(props: IProps): React.ReactElement {

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

@ -1016,36 +1016,13 @@ export interface SleeveInformation {
tor: boolean; tor: boolean;
/** Sleeve multipliers */ /** Sleeve multipliers */
mult: CharacterMult; 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. * Object representing a sleeve current task.
* @public * @public
*/ */
export interface SleeveTask { export type SleeveTask = any;
/** 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;
}
/** /**
* Object representing a port. A port is a serialized queue. * 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. * @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. * @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. * Sell short stock.

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

@ -10,6 +10,8 @@ import { applyWorkStats, WorkStats } from "./WorkStats";
import { Company } from "../Company/Company"; import { Company } from "../Company/Company";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reputation } from "../ui/React/Reputation"; import { Reputation } from "../ui/React/Reputation";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { CONSTANTS } from "../Constants";
interface CompanyWorkParams { interface CompanyWorkParams {
companyName: string; companyName: string;
@ -32,14 +34,18 @@ export class CompanyWork extends Work {
} }
getGainRates(player: IPlayer): WorkStats { 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 { process(player: IPlayer, cycles: number): boolean {
this.cyclesWorked += cycles; this.cyclesWorked += cycles;
const company = this.getCompany(); const company = this.getCompany();
const gains = this.getGainRates(player); const gains = this.getGainRates(player);
applyWorkStats(player, gains, cycles, "work"); applyWorkStats(player, player, gains, cycles, "work");
company.playerReputation += gains.reputation * cycles; company.playerReputation += gains.reputation * cycles;
influenceStockThroughCompanyWork(company, gains.reputation, cycles); influenceStockThroughCompanyWork(company, gains.reputation, cycles);
return false; return false;

@ -26,29 +26,29 @@ enum newCrimeType {
const convertCrimeType = (crimeType: CrimeType): newCrimeType => { const convertCrimeType = (crimeType: CrimeType): newCrimeType => {
switch (crimeType) { switch (crimeType) {
case CrimeType.Shoplift: case CrimeType.SHOPLIFT:
return newCrimeType.SHOPLIFT; return newCrimeType.SHOPLIFT;
case CrimeType.RobStore: case CrimeType.ROB_STORE:
return newCrimeType.ROBSTORE; return newCrimeType.ROBSTORE;
case CrimeType.Mug: case CrimeType.MUG:
return newCrimeType.MUG; return newCrimeType.MUG;
case CrimeType.Larceny: case CrimeType.LARCENY:
return newCrimeType.LARCENY; return newCrimeType.LARCENY;
case CrimeType.Drugs: case CrimeType.DRUGS:
return newCrimeType.DRUGS; return newCrimeType.DRUGS;
case CrimeType.BondForgery: case CrimeType.BOND_FORGERY:
return newCrimeType.BONDFORGERY; return newCrimeType.BONDFORGERY;
case CrimeType.TraffickArms: case CrimeType.TRAFFIC_ARMS:
return newCrimeType.TRAFFICKARMS; return newCrimeType.TRAFFICKARMS;
case CrimeType.Homicide: case CrimeType.HOMICIDE:
return newCrimeType.HOMICIDE; return newCrimeType.HOMICIDE;
case CrimeType.GrandTheftAuto: case CrimeType.GRAND_THEFT_AUTO:
return newCrimeType.GRANDTHEFTAUTO; return newCrimeType.GRANDTHEFTAUTO;
case CrimeType.Kidnap: case CrimeType.KIDNAP:
return newCrimeType.KIDNAP; return newCrimeType.KIDNAP;
case CrimeType.Assassination: case CrimeType.ASSASSINATION:
return newCrimeType.ASSASSINATION; return newCrimeType.ASSASSINATION;
case CrimeType.Heist: case CrimeType.HEIST:
return newCrimeType.HEIST; return newCrimeType.HEIST;
} }
return newCrimeType.SHOPLIFT; return newCrimeType.SHOPLIFT;
@ -67,7 +67,7 @@ export class CrimeWork extends Work {
constructor(params?: CrimeWorkParams) { constructor(params?: CrimeWorkParams) {
super(WorkType.CRIME, params?.singularity ?? true); super(WorkType.CRIME, params?.singularity ?? true);
this.crimeType = params?.crimeType ?? CrimeType.Shoplift; this.crimeType = params?.crimeType ?? CrimeType.SHOPLIFT;
this.unitCompleted = 0; this.unitCompleted = 0;
} }

@ -5,7 +5,7 @@ import { IPlayer } from "../PersonObjects/IPlayer";
import { FactionNames } from "../Faction/data/FactionNames"; import { FactionNames } from "../Faction/data/FactionNames";
import { Factions } from "../Faction/Factions"; import { Factions } from "../Faction/Factions";
import { Faction } from "../Faction/Faction"; import { Faction } from "../Faction/Faction";
import { applyWorkStats, WorkStats } from "./WorkStats"; import { applyWorkStats, scaleWorkStats, WorkStats } from "./WorkStats";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reputation } from "../ui/React/Reputation"; import { Reputation } from "../ui/React/Reputation";
import { import {
@ -58,7 +58,12 @@ export class FactionWork extends Work {
} }
getExpRates(player: IPlayer): WorkStats { 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 { process(player: IPlayer, cycles: number): boolean {
@ -66,7 +71,7 @@ export class FactionWork extends Work {
this.getFaction().playerReputation += this.getReputationRate(player) * cycles; this.getFaction().playerReputation += this.getReputationRate(player) * cycles;
const rate = this.getExpRates(player); const rate = this.getExpRates(player);
applyWorkStats(player, rate, cycles, "class"); applyWorkStats(player, player, rate, cycles, "class");
return false; return false;
} }

@ -1,3 +1,4 @@
import { IPerson } from "src/PersonObjects/IPerson";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
export interface WorkStats { 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 { return {
money: w.money * n, money: w.money * m,
reputation: w.reputation * n, reputation: w.reputation * n,
hackExp: w.hackExp * n, hackExp: w.hackExp * n,
strExp: w.strExp * 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 = { const gains = {
money: workStats.money * cycles, money: workStats.money * cycles,
reputation: 0, 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, hackExp: workStats.hackExp * cycles,
strExp: workStats.strExp * cycles, strExp: workStats.strExp * cycles,
defExp: workStats.defExp * cycles, defExp: workStats.defExp * cycles,
@ -78,13 +104,12 @@ export const applyWorkStats = (player: IPlayer, workStats: WorkStats, cycles: nu
chaExp: workStats.chaExp * cycles, chaExp: workStats.chaExp * cycles,
intExp: workStats.intExp * cycles, intExp: workStats.intExp * cycles,
}; };
player.gainHackingExp(gains.hackExp); target.gainHackingExp(gains.hackExp);
player.gainStrengthExp(gains.strExp); target.gainStrengthExp(gains.strExp);
player.gainDefenseExp(gains.defExp); target.gainDefenseExp(gains.defExp);
player.gainDexterityExp(gains.dexExp); target.gainDexterityExp(gains.dexExp);
player.gainAgilityExp(gains.agiExp); target.gainAgilityExp(gains.agiExp);
player.gainCharismaExp(gains.chaExp); target.gainCharismaExp(gains.chaExp);
player.gainIntelligenceExp(gains.intExp); target.gainIntelligenceExp(gains.intExp);
player.gainMoney(gains.money, source);
return gains; return gains;
}; };

@ -3,11 +3,13 @@ import { Location } from "../../Locations/Location";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { Class, Classes, ClassWork } from "../ClassWork"; import { Class, Classes, ClassType } from "../ClassWork";
import { WorkStats } from "../WorkStats"; import { WorkStats } from "../WorkStats";
import { Server } from "../../Server/Server"; import { Server } from "../../Server/Server";
import { GetServer } from "../../Server/AllServers"; import { GetServer } from "../../Server/AllServers";
import { serverMetadata } from "../../Server/data/servers"; 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 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; 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 //Find cost and exp gain per game cycle
const hashManager = player.hashManager; const hashManager = player.hashManager;
const classs = Classes[work.classType]; const classs = Classes[type];
const location = Locations[work.location]; 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 cost = calculateCost(classs, location) / gameCPS;
const hackExp = ((classs.earnings.hackExp * location.expMult) / gameCPS) * hashMult; const hackExp = ((classs.earnings.hackExp * location.expMult) / gameCPS) * hashMult;
@ -36,12 +47,12 @@ export function calculateClassEarnings(player: IPlayer, work: ClassWork): WorkSt
return { return {
money: cost, money: cost,
reputation: 0, reputation: 0,
hackExp: hackExp * player.mults.hacking_exp * BitNodeMultipliers.ClassGymExpGain, hackExp: hackExp * target.mults.hacking_exp * BitNodeMultipliers.ClassGymExpGain,
strExp: strExp * player.mults.strength_exp * BitNodeMultipliers.ClassGymExpGain, strExp: strExp * target.mults.strength_exp * BitNodeMultipliers.ClassGymExpGain,
defExp: defExp * player.mults.defense_exp * BitNodeMultipliers.ClassGymExpGain, defExp: defExp * target.mults.defense_exp * BitNodeMultipliers.ClassGymExpGain,
dexExp: dexExp * player.mults.dexterity_exp * BitNodeMultipliers.ClassGymExpGain, dexExp: dexExp * target.mults.dexterity_exp * BitNodeMultipliers.ClassGymExpGain,
agiExp: agiExp * player.mults.agility_exp * BitNodeMultipliers.ClassGymExpGain, agiExp: agiExp * target.mults.agility_exp * BitNodeMultipliers.ClassGymExpGain,
chaExp: chaExp * player.mults.charisma_exp * BitNodeMultipliers.ClassGymExpGain, chaExp: chaExp * target.mults.charisma_exp * BitNodeMultipliers.ClassGymExpGain,
intExp: 0, intExp: 0,
}; };
} }

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

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

@ -1,15 +1,15 @@
export enum CrimeType { export enum CrimeType {
None = "", None = "",
Shoplift = "shoplift", SHOPLIFT = "SHOPLIFT", //"shoplift",
RobStore = "rob a store", ROB_STORE = "ROBSTORE", //"rob a store",
Mug = "mug someone", MUG = "MUG", //"mug someone",
Larceny = "commit larceny", LARCENY = "LARCENY", //"commit larceny",
Drugs = "deal drugs", DRUGS = "DRUGS", //"deal drugs",
BondForgery = "forge corporate bonds", BOND_FORGERY = "BONDFORGERY", //"forge corporate bonds",
TraffickArms = "traffick illegal arms", TRAFFIC_ARMS = "TRAFFICKARMS", //"traffick illegal arms",
Homicide = "commit homicide", HOMICIDE = "HOMICIDE", //"commit homicide",
GrandTheftAuto = "commit grand theft auto", GRAND_THEFT_AUTO = "GRANDTHEFTAUTO", //"commit grand theft auto",
Kidnap = "kidnap someone for ransom", KIDNAP = "KIDNAP", //"kidnap someone for ransom",
Assassination = "assassinate a high-profile target", ASSASSINATION = "ASSASSINATION", //"assassinate a high-profile target",
Heist = "pull off the ultimate heist", HEIST = "HEIST", //"pull off the ultimate heist",
} }

@ -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", reason: "sell is a very common word so in order to avoid ram costs it was renamed ns.stock.sellStock",
offenders: [], 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, matchJS: /ns\.corporation\.bribe/g,
matchScript: /corporation\.bribe/g, matchScript: /corporation\.bribe/g,