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'
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

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

@ -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: {
<br />
</>
),
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,
}),
},
};

@ -6,7 +6,7 @@ import { IMap } from "../types";
import { CrimeType } from "../utils/WorkType";
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,
agility_success_weight: 1,
@ -14,7 +14,7 @@ export const Crimes: IMap<Crime> = {
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<Crime> = {
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<Crime> = {
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<Crime> = {
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<Crime> = {
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<Crime> = {
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<Crime> = {
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<Crime> = {
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<Crime> = {
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<Crime> = {
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<Crime> = {
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,

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

@ -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<ISleeve> {
const checkSleeveAPIAccess = function (ctx: NetscriptContext): void {
@ -120,7 +121,7 @@ export function NetscriptSleeve(player: IPlayer): InternalAPI<ISleeve> {
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<ISleeve> {
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<ISleeve> {
},
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<ISleeve> {
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<ISleeve> {
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.`,
);

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

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 { 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,17 +69,43 @@ 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 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.factionWorkType) {
switch (props.sleeve.currentWork.factionWorkType) {
case FactionWorkType.FIELD:
doing = "Field work";
break;
@ -87,60 +118,36 @@ export function SleeveElem(props: IProps): React.ReactElement {
}
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: {
const crime = Object.values(Crimes).find((crime) => crime.name === props.sleeve.crimeType);
if (!crime) throw new Error("crime should not be undefined");
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 {crime.type} (Success Rate:{" "}
{numeralWrapper.formatPercentage(crime.successRate(props.sleeve))}).
This sleeve is currently attempting to perform {w.actionName}. (
{((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 = (
<>
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 = (
<>
This sleeve is currently set to focus on shock recovery. This causes the Sleeve's shock to decrease at a
faster rate.
</>
);
break;
case SleeveTaskType.Synchro:
desc = (
<>
This sleeve is currently set to synchronize with the original consciousness. This causes the Sleeve's
synchronization to increase.
</>
);
break;
default:
console.error(`Invalid/Unrecognized taskValue in updateSleeveTaskDescription(): ${abc[0]}`);
if (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 {
<StatsElement sleeve={props.sleeve} />
<Box display="grid" sx={{ gridTemplateColumns: "1fr 1fr", width: "100%" }}>
<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> : ""}>
<span>
<Button
@ -185,12 +191,21 @@ export function SleeveElem(props: IProps): React.ReactElement {
</Button>
<Typography>{desc}</Typography>
<Typography>
{(props.sleeve.currentTask === SleeveTaskType.Crime ||
props.sleeve.currentTask === SleeveTaskType.Bladeburner) &&
props.sleeve.currentTaskMaxTime > 0 && (
{isSleeveCrimeWork(props.sleeve.currentWork) && (
<ProgressBar
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"
/>
)}
@ -198,7 +213,6 @@ export function SleeveElem(props: IProps): React.ReactElement {
</span>
</Paper>
<MoreStatsModal open={statsOpen} onClose={() => setStatsOpen(false)} sleeve={props.sleeve} />
<MoreEarningsModal open={earningsOpen} onClose={() => setEarningsOpen(false)} sleeve={props.sleeve} />
<TravelModal
open={travelOpen}
onClose={() => setTravelOpen(false)}

@ -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`,
<>
<Money money={parseFloat(props.sleeve.currentTaskLocation)} /> (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:`, <Money money={5 * gains.money} />],
[`Hacking Exp:`, `${numeralWrapper.formatExp(5 * gains.hackExp)}`],
[`Strength Exp:`, `${numeralWrapper.formatExp(5 * gains.strExp)}`],
[`Defense Exp:`, `${numeralWrapper.formatExp(5 * gains.defExp)}`],
[`Dexterity Exp:`, `${numeralWrapper.formatExp(5 * gains.dexExp)}`],
[`Agility Exp:`, `${numeralWrapper.formatExp(5 * gains.agiExp)}`],
[`Charisma Exp:`, `${numeralWrapper.formatExp(5 * gains.chaExp)}`],
];
} else {
data = [
[`Money:`, <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 (
@ -130,7 +155,7 @@ export function EarningsElement(props: IProps): React.ReactElement {
<TableBody>
<TableRow>
<TableCell classes={{ root: classes.cellNone }}>
<Typography variant="h6">Earnings</Typography>
<Typography variant="h6">Earnings {props.sleeve.storedCycles > 50 ? "(overclock)" : ""}</Typography>
</TableCell>
</TableRow>
{data.map(([a, b]) => (

@ -1,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", "------", "------"];
}
// 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 {

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

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

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

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

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

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

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

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

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

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

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

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