convert company work to new work system

This commit is contained in:
Olivier Gagnon 2022-07-12 22:43:03 -04:00
parent 2d73f546b0
commit 598e47766e
14 changed files with 233 additions and 140 deletions

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

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

73
src/Work/CompanyWork.tsx Normal file

@ -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}
<br />
You have <Reputation reputation={this.getCompany().playerReputation} /> 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;

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

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

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

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

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

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

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

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

@ -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<FactionWorkType, WorkStats> = {
};
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,
};
}

@ -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 <strong>{player.companyName}</strong>
</>
);
header = (
<>
Working at <strong>{player.companyName}</strong>
</>
);
innerText = (
<>
+<Reputation reputation={player.workRepGained} /> rep
</>
);
break;
if (isCompanyWork(player.currentWork)) {
const companyWork = player.currentWork;
details = (
<>
{player.jobs[companyWork.companyName]} at <strong>{companyWork.companyName}</strong>
</>
);
header = (
<>
Working at <strong>{companyWork.companyName}</strong>
</>
);
innerText = (
<>
<Reputation reputation={companyWork.getCompany().playerReputation} /> rep
<br />(
<ReputationRate reputation={companyWork.getGainRates(player).reputation * (1000 / CONSTANTS._idleSpeed)} />)
</>
);
}
return (

@ -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 <b>{position}</b> at <b>{player.companyName}</b>
You are currently working as a <b>{position}</b> at <b>{player.currentWork.companyName}</b>
</>
),
@ -474,95 +470,23 @@ export function WorkInProgressRoot(): React.ReactElement {
gains: [
<StatsRow name="Money" color={Settings.theme.money}>
<Typography>
<Money money={player.workMoneyGained} /> (<MoneyRate money={player.workMoneyGainRate * CYCLES_PER_SEC} />)
<MoneyRate money={gains.money * CYCLES_PER_SEC} />
</Typography>
</StatsRow>,
<StatsRow name="Company Reputation" color={Settings.theme.rep}>
<Typography>
<Reputation reputation={player.workRepGained} /> (
<ReputationRate reputation={player.workRepGainRate * CYCLES_PER_SEC} />)
<ReputationRate reputation={gains.reputation * CYCLES_PER_SEC} />
</Typography>
</StatsRow>,
...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 <b>{position}</b> at <b>{player.companyName}</b>
</>
),
description: (
<>
Current Company Reputation: <Reputation reputation={companyRep} />
</>
),
gains: [
<StatsRow name="Money" color={Settings.theme.money}>
<Typography>
<Money money={player.workMoneyGained} /> (<MoneyRate money={player.workMoneyGainRate * CYCLES_PER_SEC} />)
</Typography>
</StatsRow>,
<StatsRow name="Company Reputation" color={Settings.theme.rep}>
<Typography>
<Reputation reputation={player.workRepGained} /> (
<ReputationRate reputation={player.workRepGainRate * CYCLES_PER_SEC} />)
</Typography>
</StatsRow>,
...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 === "") {