diff --git a/src/Locations/ui/CompanyLocation.tsx b/src/Locations/ui/CompanyLocation.tsx
index 9adc3af47..d570f4027 100644
--- a/src/Locations/ui/CompanyLocation.tsx
+++ b/src/Locations/ui/CompanyLocation.tsx
@@ -23,6 +23,7 @@ import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor";
import { use } from "../../ui/Context";
import { QuitJobModal } from "../../Company/ui/QuitJobModal";
+import { CompanyWork } from "../../Work/CompanyWork";
type IProps = {
locName: LocationName;
@@ -175,11 +176,12 @@ export function CompanyLocation(props: IProps): React.ReactElement {
const pos = companyPosition;
if (pos instanceof CompanyPosition) {
- if (pos.isPartTimeJob() || pos.isSoftwareConsultantJob() || pos.isBusinessConsultantJob()) {
- p.startWorkPartTime(props.locName);
- } else {
- p.startWork(props.locName);
- }
+ p.startNEWWork(
+ new CompanyWork({
+ singularity: false,
+ companyName: props.locName,
+ }),
+ );
p.startFocusing();
router.toWork();
}
diff --git a/src/Work/ClassWork.tsx b/src/Work/ClassWork.tsx
index ab444bed8..a33faec74 100644
--- a/src/Work/ClassWork.tsx
+++ b/src/Work/ClassWork.tsx
@@ -128,14 +128,12 @@ export const isClassWork = (w: Work | null): w is ClassWork => w !== null && w.t
export class ClassWork extends Work {
classType: ClassType;
location: LocationName;
- cyclesWorked: number;
earnings = newWorkStats();
constructor(params?: ClassWorkParams) {
super(WorkType.CLASS, params?.singularity ?? true);
this.classType = params?.classType ?? ClassType.StudyComputerScience;
this.location = params?.location ?? LocationName.Sector12RothmanUniversity;
- this.cyclesWorked = 0;
}
isGym(): boolean {
diff --git a/src/Work/CompanyWork.tsx b/src/Work/CompanyWork.tsx
new file mode 100644
index 000000000..6b1695fc2
--- /dev/null
+++ b/src/Work/CompanyWork.tsx
@@ -0,0 +1,73 @@
+import React from "react";
+import { Reviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
+import { IPlayer } from "src/PersonObjects/IPlayer";
+import { Work, WorkType } from "./Work";
+import { influenceStockThroughCompanyWork } from "../StockMarket/PlayerInfluencing";
+import { LocationName } from "../Locations/data/LocationNames";
+import { calculateCompanyWorkStats } from "./formulas/Company";
+import { Companies } from "../Company/Companies";
+import { applyWorkStats, WorkStats } from "./WorkStats";
+import { Company } from "../Company/Company";
+import { dialogBoxCreate } from "../ui/React/DialogBox";
+import { Reputation } from "../ui/React/Reputation";
+
+interface CompanyWorkParams {
+ companyName: string;
+ singularity: boolean;
+}
+
+export const isCompanyWork = (w: Work | null): w is CompanyWork => w !== null && w.type === WorkType.COMPANY;
+
+export class CompanyWork extends Work {
+ companyName: string;
+ constructor(params?: CompanyWorkParams) {
+ super(WorkType.COMPANY, params?.singularity ?? false);
+ this.companyName = params?.companyName ?? LocationName.NewTokyoNoodleBar;
+ console.log(Companies);
+ }
+
+ getCompany(): Company {
+ const c = Companies[this.companyName];
+ if (!c) throw new Error(`Company not found: '${this.companyName}'`);
+ return c;
+ }
+
+ getGainRates(player: IPlayer): WorkStats {
+ return calculateCompanyWorkStats(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");
+ company.playerReputation += gains.reputation * cycles;
+ influenceStockThroughCompanyWork(company, gains.reputation, cycles);
+ return false;
+ }
+ finish(): void {
+ dialogBoxCreate(
+ <>
+ You finished working for {this.companyName}
+
+ You have reputation with them.
+ >,
+ );
+ }
+
+ /**
+ * Serialize the current object to a JSON save state.
+ */
+ toJSON(): IReviverValue {
+ return Generic_toJSON("CompanyWork", this);
+ }
+
+ /**
+ * Initiatizes a CompanyWork object from a JSON save state.
+ */
+ static fromJSON(value: IReviverValue): CompanyWork {
+ return Generic_fromJSON(CompanyWork, value.data);
+ }
+}
+
+Reviver.constructors.CompanyWork = CompanyWork;
diff --git a/src/Work/CreateProgramWork.ts b/src/Work/CreateProgramWork.ts
index a3f4ef2a9..fe85a96e0 100644
--- a/src/Work/CreateProgramWork.ts
+++ b/src/Work/CreateProgramWork.ts
@@ -18,14 +18,11 @@ interface CreateProgramWorkParams {
export class CreateProgramWork extends Work {
programName: string;
- // amount of cycles spent doing this task
- cyclesWorked: number;
// amount of effective work completed on the program (time boosted by skills).
unitCompleted: number;
constructor(params?: CreateProgramWorkParams) {
super(WorkType.CREATE_PROGRAM, params?.singularity ?? true);
- this.cyclesWorked = 0;
this.unitCompleted = 0;
this.programName = params?.programName ?? "";
diff --git a/src/Work/CrimeWork.tsx b/src/Work/CrimeWork.tsx
index 761377bec..4e59afbc1 100644
--- a/src/Work/CrimeWork.tsx
+++ b/src/Work/CrimeWork.tsx
@@ -20,12 +20,10 @@ export const isCrimeWork = (w: Work | null): w is CrimeWork => w !== null && w.t
export class CrimeWork extends Work {
crimeType: CrimeType;
- cyclesWorked: number;
constructor(params?: CrimeWorkParams) {
super(WorkType.CRIME, params?.singularity ?? true);
this.crimeType = params?.crimeType ?? CrimeType.Shoplift;
- this.cyclesWorked = 0;
}
getCrime(): Crime {
diff --git a/src/Work/FactionWork.tsx b/src/Work/FactionWork.tsx
index d1671ba09..8daea2a59 100644
--- a/src/Work/FactionWork.tsx
+++ b/src/Work/FactionWork.tsx
@@ -29,13 +29,11 @@ export const isFactionWork = (w: Work | null): w is FactionWork => w !== null &&
export class FactionWork extends Work {
factionWorkType: FactionWorkType;
factionName: string;
- cyclesWorked: number;
constructor(params?: FactionWorkParams) {
super(WorkType.FACTION, params?.singularity ?? true);
this.factionWorkType = params?.factionWorkType ?? FactionWorkType.HACKING;
this.factionName = params?.faction ?? FactionNames.Sector12;
- this.cyclesWorked = 0;
}
getFaction(): Faction {
diff --git a/src/Work/GraftingWork.tsx b/src/Work/GraftingWork.tsx
index d149e9ad5..0e7d29292 100644
--- a/src/Work/GraftingWork.tsx
+++ b/src/Work/GraftingWork.tsx
@@ -21,12 +21,10 @@ interface GraftingWorkParams {
export class GraftingWork extends Work {
augmentation: string;
- cyclesWorked: number;
unitCompleted: number;
constructor(params?: GraftingWorkParams) {
super(WorkType.GRAFTING, params?.singularity ?? true);
- this.cyclesWorked = 0;
this.unitCompleted = 0;
this.augmentation = params?.augmentation ?? AugmentationNames.Targeting1;
diff --git a/src/Work/Work.ts b/src/Work/Work.ts
index e3c93e0ed..73b2bb1b4 100644
--- a/src/Work/Work.ts
+++ b/src/Work/Work.ts
@@ -4,10 +4,12 @@ import { IReviverValue } from "../utils/JSONReviver";
export abstract class Work {
type: WorkType;
singularity: boolean;
+ cyclesWorked: number;
constructor(type: WorkType, singularity: boolean) {
this.type = type;
this.singularity = singularity;
+ this.cyclesWorked = 0;
}
abstract process(player: IPlayer, cycles: number): boolean;
@@ -21,4 +23,5 @@ export enum WorkType {
CREATE_PROGRAM = "CREATE_PROGRAM",
GRAFTING = "GRAFTING",
FACTION = "FACTION",
+ COMPANY = "COMPANY",
}
diff --git a/src/Work/WorkStats.ts b/src/Work/WorkStats.ts
index 7881b5abf..1388560e9 100644
--- a/src/Work/WorkStats.ts
+++ b/src/Work/WorkStats.ts
@@ -4,6 +4,7 @@ import { IPlayer } from "../PersonObjects/IPlayer";
export interface WorkStats {
money: number;
+ reputation: number;
hackExp: number;
strExp: number;
defExp: number;
@@ -15,6 +16,7 @@ export interface WorkStats {
interface newWorkStatsParams {
money?: number;
+ reputation?: number;
hackExp?: number;
strExp?: number;
defExp?: number;
@@ -27,6 +29,7 @@ interface newWorkStatsParams {
export const newWorkStats = (params?: newWorkStatsParams): WorkStats => {
return {
money: params?.money ?? 0,
+ reputation: params?.reputation ?? 0,
hackExp: params?.hackExp ?? 0,
strExp: params?.strExp ?? 0,
defExp: params?.defExp ?? 0,
@@ -40,6 +43,7 @@ export const newWorkStats = (params?: newWorkStatsParams): WorkStats => {
export const sumWorkStats = (w0: WorkStats, w1: WorkStats): WorkStats => {
return {
money: w0.money + w1.money,
+ reputation: w0.reputation + w1.reputation,
hackExp: w0.hackExp + w1.hackExp,
strExp: w0.strExp + w1.strExp,
defExp: w0.defExp + w1.defExp,
@@ -53,6 +57,7 @@ export const sumWorkStats = (w0: WorkStats, w1: WorkStats): WorkStats => {
export const scaleWorkStats = (w: WorkStats, n: number): WorkStats => {
return {
money: w.money * n,
+ reputation: w.reputation * n,
hackExp: w.hackExp * n,
strExp: w.strExp * n,
defExp: w.defExp * n,
@@ -64,19 +69,16 @@ export const scaleWorkStats = (w: WorkStats, n: number): WorkStats => {
};
export const applyWorkStats = (player: IPlayer, workStats: WorkStats, cycles: number, source: string): WorkStats => {
- let focusBonus = 1;
- if (!player.hasAugmentation(AugmentationNames.NeuroreceptorManager)) {
- focusBonus = player.focus ? 1 : CONSTANTS.BaseFocusBonus;
- }
const gains = {
money: workStats.money * cycles,
- hackExp: focusBonus * workStats.hackExp * cycles,
- strExp: focusBonus * workStats.strExp * cycles,
- defExp: focusBonus * workStats.defExp * cycles,
- dexExp: focusBonus * workStats.dexExp * cycles,
- agiExp: focusBonus * workStats.agiExp * cycles,
- chaExp: focusBonus * workStats.chaExp * cycles,
- intExp: focusBonus * workStats.intExp * cycles,
+ reputation: 0,
+ hackExp: workStats.hackExp * cycles,
+ strExp: workStats.strExp * cycles,
+ defExp: workStats.defExp * cycles,
+ dexExp: workStats.dexExp * cycles,
+ agiExp: workStats.agiExp * cycles,
+ chaExp: workStats.chaExp * cycles,
+ intExp: workStats.intExp * cycles,
};
player.gainHackingExp(gains.hackExp);
player.gainStrengthExp(gains.strExp);
diff --git a/src/Work/formulas/Class.ts b/src/Work/formulas/Class.ts
index 4351c2679..6f5e29565 100644
--- a/src/Work/formulas/Class.ts
+++ b/src/Work/formulas/Class.ts
@@ -35,6 +35,7 @@ export function calculateClassEarnings(player: IPlayer, work: ClassWork): WorkSt
const chaExp = ((classs.earnings.chaExp * location.expMult) / gameCPS) * hashMult;
return {
money: cost,
+ reputation: 0,
hackExp: hackExp * player.hacking_exp_mult * BitNodeMultipliers.ClassGymExpGain,
strExp: strExp * player.strength_exp_mult * BitNodeMultipliers.ClassGymExpGain,
defExp: defExp * player.defense_exp_mult * BitNodeMultipliers.ClassGymExpGain,
diff --git a/src/Work/formulas/Company.ts b/src/Work/formulas/Company.ts
new file mode 100644
index 000000000..fbdced8b2
--- /dev/null
+++ b/src/Work/formulas/Company.ts
@@ -0,0 +1,87 @@
+import { CompanyPositions } from "../../Company/CompanyPositions";
+import { Company } from "../../Company/Company";
+import { IPlayer } from "../../PersonObjects/IPlayer";
+import { WorkStats } from "../WorkStats";
+import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
+import { CONSTANTS } from "../../Constants";
+import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
+
+export const calculateCompanyWorkStats = (player: IPlayer, 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)) {
+ favorMult = 1;
+ }
+
+ let bn11Mult = 1;
+ if (player.sourceFileLvl(11) > 0) {
+ bn11Mult = favorMult;
+ }
+
+ let jobPerformance = companyPosition.calculateJobPerformance(
+ player.hacking,
+ player.strength,
+ player.defense,
+ player.dexterity,
+ player.agility,
+ player.charisma,
+ );
+
+ jobPerformance += player.intelligence / CONSTANTS.MaxSkillLevel;
+
+ return {
+ money:
+ focusBonus *
+ companyPosition.baseSalary *
+ company.salaryMultiplier *
+ player.work_money_mult *
+ BitNodeMultipliers.CompanyWorkMoney *
+ bn11Mult,
+ reputation: focusBonus * jobPerformance * player.company_rep_mult * favorMult,
+ hackExp:
+ focusBonus *
+ companyPosition.hackingExpGain *
+ company.expMultiplier *
+ player.hacking_exp_mult *
+ BitNodeMultipliers.CompanyWorkExpGain,
+ strExp:
+ focusBonus *
+ companyPosition.strengthExpGain *
+ company.expMultiplier *
+ player.strength_exp_mult *
+ BitNodeMultipliers.CompanyWorkExpGain,
+ defExp:
+ focusBonus *
+ companyPosition.defenseExpGain *
+ company.expMultiplier *
+ player.defense_exp_mult *
+ BitNodeMultipliers.CompanyWorkExpGain,
+ dexExp:
+ focusBonus *
+ companyPosition.dexterityExpGain *
+ company.expMultiplier *
+ player.dexterity_exp_mult *
+ BitNodeMultipliers.CompanyWorkExpGain,
+ agiExp:
+ focusBonus *
+ companyPosition.agilityExpGain *
+ company.expMultiplier *
+ player.agility_exp_mult *
+ BitNodeMultipliers.CompanyWorkExpGain,
+ chaExp:
+ focusBonus *
+ companyPosition.charismaExpGain *
+ company.expMultiplier *
+ player.charisma_exp_mult *
+ BitNodeMultipliers.CompanyWorkExpGain,
+ intExp: 0,
+ };
+};
diff --git a/src/Work/formulas/Faction.ts b/src/Work/formulas/Faction.ts
index 6104e540e..8bcfd3a44 100644
--- a/src/Work/formulas/Faction.ts
+++ b/src/Work/formulas/Faction.ts
@@ -1,3 +1,4 @@
+import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
@@ -26,15 +27,26 @@ export const FactionWorkStats: Record = {
};
export function calculateFactionExp(player: IPlayer, tpe: FactionWorkType): WorkStats {
+ let focusBonus = 1;
+ if (!player.hasAugmentation(AugmentationNames.NeuroreceptorManager)) {
+ focusBonus = player.focus ? 1 : CONSTANTS.BaseFocusBonus;
+ }
const baseStats = FactionWorkStats[tpe];
return {
money: 0,
- hackExp: (baseStats.hackExp * player.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
- strExp: (baseStats.strExp * player.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
- defExp: (baseStats.defExp * player.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
- dexExp: (baseStats.dexExp * player.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
- agiExp: (baseStats.agiExp * player.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
- chaExp: (baseStats.chaExp * player.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain) / gameCPS,
+ reputation: 0,
+ hackExp:
+ (focusBonus * (baseStats.hackExp * player.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
+ strExp:
+ (focusBonus * (baseStats.strExp * player.strength_exp_mult * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
+ defExp:
+ (focusBonus * (baseStats.defExp * player.defense_exp_mult * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
+ dexExp:
+ (focusBonus * (baseStats.dexExp * player.dexterity_exp_mult * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
+ agiExp:
+ (focusBonus * (baseStats.agiExp * player.agility_exp_mult * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
+ chaExp:
+ (focusBonus * (baseStats.chaExp * player.charisma_exp_mult * BitNodeMultipliers.FactionWorkExpGain)) / gameCPS,
intExp: 0,
};
}
diff --git a/src/ui/React/CharacterOverview.tsx b/src/ui/React/CharacterOverview.tsx
index ced8be855..40030226e 100644
--- a/src/ui/React/CharacterOverview.tsx
+++ b/src/ui/React/CharacterOverview.tsx
@@ -26,13 +26,13 @@ import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { Box, Tooltip } from "@mui/material";
-import { WorkType } from "../../utils/WorkType";
import { isClassWork } from "../../Work/ClassWork";
import { CONSTANTS } from "../../Constants";
import { isCreateProgramWork } from "../../Work/CreateProgramWork";
import { isGraftingWork } from "../../Work/GraftingWork";
import { isFactionWork } from "../../Work/FactionWork";
import { ReputationRate } from "./ReputationRate";
+import { isCompanyWork } from "../../Work/CompanyWork";
interface IProps {
save: () => void;
@@ -190,25 +190,25 @@ function Work(): React.ReactElement {
>
);
}
- switch (player.workType) {
- case WorkType.CompanyPartTime:
- case WorkType.Company:
- details = (
- <>
- {player.jobs[player.companyName]} at {player.companyName}
- >
- );
- header = (
- <>
- Working at {player.companyName}
- >
- );
- innerText = (
- <>
- + rep
- >
- );
- break;
+ if (isCompanyWork(player.currentWork)) {
+ const companyWork = player.currentWork;
+ details = (
+ <>
+ {player.jobs[companyWork.companyName]} at {companyWork.companyName}
+ >
+ );
+ header = (
+ <>
+ Working at {companyWork.companyName}
+ >
+ );
+ innerText = (
+ <>
+ rep
+
(
+ )
+ >
+ );
}
return (
diff --git a/src/ui/WorkInProgressRoot.tsx b/src/ui/WorkInProgressRoot.tsx
index 4a84b4985..ff5a1b0b5 100644
--- a/src/ui/WorkInProgressRoot.tsx
+++ b/src/ui/WorkInProgressRoot.tsx
@@ -26,6 +26,7 @@ import { isCreateProgramWork } from "../Work/CreateProgramWork";
import { isGraftingWork } from "../Work/GraftingWork";
import { isFactionWork } from "../Work/FactionWork";
import { FactionWorkType } from "../Work/data/FactionWorkType";
+import { isCompanyWork } from "../Work/CompanyWork";
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
@@ -420,18 +421,16 @@ export function WorkInProgressRoot(): React.ReactElement {
stopText: "Stop Faction work",
};
}
- }
- switch (player.workType) {
- case WorkType.Company: {
- const comp = Companies[player.companyName];
+ if (isCompanyWork(player.currentWork)) {
+ const comp = Companies[player.currentWork.companyName];
if (comp == null || !(comp instanceof Company)) {
workInfo = {
buttons: {
cancel: () => router.toTerminal(),
},
title:
- `You cannot work for ${player.companyName || "(Company not found)"} at this time,` +
+ `You cannot work for ${player.currentWork.companyName || "(Company not found)"} at this time,` +
" please try again if you think this should have worked",
stopText: "Back to Terminal",
@@ -441,7 +440,7 @@ export function WorkInProgressRoot(): React.ReactElement {
const companyRep = comp.playerReputation;
function cancel(): void {
- player.finishWork(true);
+ player.finishNEWWork(true);
router.toJob();
}
function unfocus(): void {
@@ -450,10 +449,7 @@ export function WorkInProgressRoot(): React.ReactElement {
}
const position = player.jobs[player.companyName];
-
- const penalty = player.cancelationPenalty();
-
- const penaltyString = penalty === 0.5 ? "half" : "three-quarters";
+ const gains = player.currentWork.getGainRates(player);
workInfo = {
buttons: {
@@ -462,7 +458,7 @@ export function WorkInProgressRoot(): React.ReactElement {
},
title: (
<>
- You are currently working as a {position} at {player.companyName}
+ You are currently working as a {position} at {player.currentWork.companyName}
>
),
@@ -474,95 +470,23 @@ export function WorkInProgressRoot(): React.ReactElement {
gains: [
- ()
+
,
- (
- )
+
,
- ...expGains,
+ ...ExpRows(gains),
],
progress: {
- elapsed: player.timeWorked,
+ elapsed: player.currentWork.cyclesWorked * CYCLES_PER_SEC,
},
stopText: "Stop working",
- stopTooltip:
- "You will automatically finish after working for 8 hours. You can cancel earlier if you wish" +
- ` but you will only gain ${penaltyString} of the reputation you've earned so far.`,
};
-
- break;
}
-
- case WorkType.CompanyPartTime: {
- function cancel(): void {
- player.finishWorkPartTime(true);
- router.toJob();
- }
- function unfocus(): void {
- player.stopFocusing();
- router.toJob();
- }
- const comp = Companies[player.companyName];
- let companyRep = 0;
- if (comp == null || !(comp instanceof Company)) {
- throw new Error(`Could not find Company: ${player.companyName}`);
- }
- companyRep = comp.playerReputation;
-
- const position = player.jobs[player.companyName];
-
- workInfo = {
- buttons: {
- cancel: cancel,
- unfocus: unfocus,
- },
- title: (
- <>
- You are currently working as a {position} at {player.companyName}
- >
- ),
-
- description: (
- <>
- Current Company Reputation:
- >
- ),
- gains: [
-
-
- ()
-
- ,
-
-
- (
- )
-
- ,
- ...expGains,
- ],
- progress: {
- elapsed: player.timeWorked,
- },
-
- stopText: "Stop working",
- stopTooltip:
- "You will automatically finish after working for 8 hours. You can cancel earlier if you wish" +
- " and there will be no penalty because this is a part-time job.",
- };
-
- break;
- }
-
- default:
- if (player.currentWork === null) {
- router.toTerminal();
- }
}
if (workInfo.title === "") {