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 === "") {