convert taking class ot new work system

This commit is contained in:
Olivier Gagnon 2022-07-07 17:28:23 -04:00
parent 24b6eb4d56
commit 647392626e
19 changed files with 563 additions and 388 deletions

@ -24,7 +24,7 @@ import { IMap } from "../types";
import * as data from "./AchievementData.json";
import { FactionNames } from "../Faction/data/FactionNames";
import { BlackOperationNames } from "../Bladeburner/data/BlackOperationNames";
import { ClassType } from "../utils/WorkType";
import { isClassWork } from "../Work/ClassWork";
// Unable to correctly cast the JSON data into AchievementDataJson type otherwise...
const achievementData = (<AchievementDataJson>(<unknown>data)).achievements;
@ -391,10 +391,7 @@ export const achievements: IMap<Achievement> = {
WORKOUT: {
...achievementData["WORKOUT"],
Icon: "WORKOUT",
Condition: () =>
[ClassType.GymStrength, ClassType.GymDefense, ClassType.GymDexterity, ClassType.GymAgility].includes(
Player.className,
),
Condition: () => isClassWork(Player.currentWork),
},
TOR: {
...achievementData["TOR"],

@ -8,16 +8,13 @@ import Button from "@mui/material/Button";
import { Location } from "../Location";
import { CONSTANTS } from "../../Constants";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { GetServer } from "../../Server/AllServers";
import { Server } from "../../Server/Server";
import { Money } from "../../ui/React/Money";
import { IRouter } from "../../ui/Router";
import { serverMetadata } from "../../Server/data/servers";
import { Box } from "@mui/material";
import { ClassType } from "../../utils/WorkType";
import { ClassWork, ClassType, Classes } from "../../Work/ClassWork";
import { calculateCost } from "../../Work/formulas/Class";
type IProps = {
loc: Location;
@ -26,51 +23,32 @@ type IProps = {
};
export function GymLocation(props: IProps): React.ReactElement {
function calculateCost(): number {
const serverMeta = serverMetadata.find((s) => s.specialName === props.loc.name);
const server = GetServer(serverMeta ? serverMeta.hostname : "");
if (server == null || !server.hasOwnProperty("backdoorInstalled")) return props.loc.costMult;
const discount = (server as Server).backdoorInstalled ? 0.9 : 1;
return props.loc.costMult * discount;
}
function train(stat: ClassType): void {
const loc = props.loc;
props.p.startClass(calculateCost(), loc.expMult, stat);
props.p.startNEWWork(
new ClassWork({
classType: stat,
location: props.loc.name,
singularity: false,
}),
);
props.p.startFocusing();
props.router.toWork();
}
function trainStrength(): void {
train(ClassType.GymStrength);
}
function trainDefense(): void {
train(ClassType.GymDefense);
}
function trainDexterity(): void {
train(ClassType.GymDexterity);
}
function trainAgility(): void {
train(ClassType.GymAgility);
}
const cost = CONSTANTS.ClassGymBaseCost * calculateCost();
const cost = calculateCost(Classes[ClassType.GymStrength], props.loc);
return (
<Box sx={{ display: "grid", width: "fit-content" }}>
<Button onClick={trainStrength}>
<Button onClick={() => train(ClassType.GymStrength)}>
Train Strength (<Money money={cost} player={props.p} /> / sec)
</Button>
<Button onClick={trainDefense}>
<Button onClick={() => train(ClassType.GymDefense)}>
Train Defense (<Money money={cost} player={props.p} /> / sec)
</Button>
<Button onClick={trainDexterity}>
<Button onClick={() => train(ClassType.GymDexterity)}>
Train Dexterity (<Money money={cost} player={props.p} /> / sec)
</Button>
<Button onClick={trainAgility}>
<Button onClick={() => train(ClassType.GymAgility)}>
Train Agility (<Money money={cost} player={props.p} /> / sec)
</Button>
</Box>

@ -9,15 +9,12 @@ import Button from "@mui/material/Button";
import { Location } from "../Location";
import { CONSTANTS } from "../../Constants";
import { GetServer } from "../../Server/AllServers";
import { Server } from "../../Server/Server";
import { Money } from "../../ui/React/Money";
import { use } from "../../ui/Context";
import { Box } from "@mui/material";
import { ClassType } from "../../utils/WorkType";
import { ClassWork, ClassType, Classes } from "../../Work/ClassWork";
import { calculateCost } from "../../Work/formulas/Class";
type IProps = {
loc: Location;
@ -27,51 +24,23 @@ export function UniversityLocation(props: IProps): React.ReactElement {
const player = use.Player();
const router = use.Router();
function calculateCost(): number {
const server = GetServer(props.loc.name);
if (server == null || !server.hasOwnProperty("backdoorInstalled")) return props.loc.costMult;
const discount = (server as Server).backdoorInstalled ? 0.9 : 1;
return props.loc.costMult * discount;
}
function take(stat: ClassType): void {
const loc = props.loc;
player.startClass(calculateCost(), loc.expMult, stat);
function take(classType: ClassType): void {
player.startNEWWork(
new ClassWork({
classType: classType,
location: props.loc.name,
singularity: false,
}),
);
player.startFocusing();
router.toWork();
}
function study(): void {
take(ClassType.StudyComputerScience);
}
function dataStructures(): void {
take(ClassType.DataStructures);
}
function networks(): void {
take(ClassType.Networks);
}
function algorithms(): void {
take(ClassType.Algorithms);
}
function management(): void {
take(ClassType.Management);
}
function leadership(): void {
take(ClassType.Leadership);
}
const costMult: number = calculateCost();
const dataStructuresCost = CONSTANTS.ClassDataStructuresBaseCost * costMult;
const networksCost = CONSTANTS.ClassNetworksBaseCost * costMult;
const algorithmsCost = CONSTANTS.ClassAlgorithmsBaseCost * costMult;
const managementCost = CONSTANTS.ClassManagementBaseCost * costMult;
const leadershipCost = CONSTANTS.ClassLeadershipBaseCost * costMult;
const dataStructuresCost = calculateCost(Classes[ClassType.DataStructures], props.loc);
const networksCost = calculateCost(Classes[ClassType.Networks], props.loc);
const algorithmsCost = calculateCost(Classes[ClassType.Algorithms], props.loc);
const managementCost = calculateCost(Classes[ClassType.Management], props.loc);
const leadershipCost = calculateCost(Classes[ClassType.Leadership], props.loc);
const earnHackingExpTooltip = `Gain hacking experience!`;
const earnCharismaExpTooltip = `Gain charisma experience!`;
@ -79,34 +48,34 @@ export function UniversityLocation(props: IProps): React.ReactElement {
return (
<Box sx={{ display: "grid", width: "fit-content" }}>
<Tooltip title={earnHackingExpTooltip}>
<Button onClick={study}>Study Computer Science (free)</Button>
<Button onClick={() => take(ClassType.StudyComputerScience)}>Study Computer Science (free)</Button>
</Tooltip>
<Tooltip title={earnHackingExpTooltip}>
<Button onClick={dataStructures}>
<Button onClick={() => take(ClassType.DataStructures)}>
Take Data Structures course (
<Money money={dataStructuresCost} player={player} /> / sec)
</Button>
</Tooltip>
<Tooltip title={earnHackingExpTooltip}>
<Button onClick={networks}>
<Button onClick={() => take(ClassType.Networks)}>
Take Networks course (
<Money money={networksCost} player={player} /> / sec)
</Button>
</Tooltip>
<Tooltip title={earnHackingExpTooltip}>
<Button onClick={algorithms}>
<Button onClick={() => take(ClassType.Algorithms)}>
Take Algorithms course (
<Money money={algorithmsCost} player={player} /> / sec)
</Button>
</Tooltip>
<Tooltip title={earnCharismaExpTooltip}>
<Button onClick={management}>
<Button onClick={() => take(ClassType.Management)}>
Take Management course (
<Money money={managementCost} player={player} /> / sec)
</Button>
</Tooltip>
<Tooltip title={earnCharismaExpTooltip}>
<Button onClick={leadership}>
<Button onClick={() => take(ClassType.Leadership)}>
Take Leadership course (
<Money money={leadershipCost} player={player} /> / sec)
</Button>

@ -2420,7 +2420,6 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
workMoneyGained: Player.workMoneyGained,
createProgramName: Player.createProgramName,
createProgramReqLvl: Player.createProgramReqLvl,
className: Player.className,
work_money_mult: Player.work_money_mult,
hacknet_node_money_mult: Player.hacknet_node_money_mult,
hacknet_node_purchase_cost_mult: Player.hacknet_node_purchase_cost_mult,

@ -49,7 +49,8 @@ import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper";
import { BlackOperationNames } from "../Bladeburner/data/BlackOperationNames";
import { enterBitNode } from "../RedPill";
import { FactionNames } from "../Faction/data/FactionNames";
import { ClassType, WorkType } from "../utils/WorkType";
import { WorkType } from "../utils/WorkType";
import { ClassWork, ClassType } from "../Work/ClassWork";
export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript): InternalAPI<ISingularity> {
const getAugmentation = function (_ctx: NetscriptContext, name: string): Augmentation {
@ -264,12 +265,11 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const className = _ctx.helper.string("className", _className);
const focus = _ctx.helper.boolean(_focus);
const wasFocusing = player.focus;
if (player.isWorking) {
if (player.currentWork) {
const txt = player.singularityStopWork();
_ctx.log(() => txt);
}
let costMult, expMult;
switch (universityName.toLowerCase()) {
case LocationName.AevumSummitUniversity.toLowerCase():
if (player.city != CityName.Aevum) {
@ -277,8 +277,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return false;
}
player.gotoLocation(LocationName.AevumSummitUniversity);
costMult = 4;
expMult = 3;
break;
case LocationName.Sector12RothmanUniversity.toLowerCase():
if (player.city != CityName.Sector12) {
@ -286,8 +284,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return false;
}
player.location = LocationName.Sector12RothmanUniversity;
costMult = 3;
expMult = 2;
break;
case LocationName.VolhavenZBInstituteOfTechnology.toLowerCase():
if (player.city != CityName.Volhaven) {
@ -297,8 +293,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return false;
}
player.location = LocationName.VolhavenZBInstituteOfTechnology;
costMult = 5;
expMult = 4;
break;
default:
_ctx.log(() => `Invalid university name: '${universityName}'.`);
@ -329,7 +323,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
_ctx.log(() => `Invalid class name: ${className}.`);
return false;
}
player.startClass(costMult, expMult, task);
player.startNEWWork(
new ClassWork({
classType: task,
location: player.location,
singularity: true,
}),
);
if (focus) {
player.startFocusing();
Router.toWork();
@ -352,7 +352,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
const txt = player.singularityStopWork();
_ctx.log(() => txt);
}
let costMult, expMult;
switch (gymName.toLowerCase()) {
case LocationName.AevumCrushFitnessGym.toLowerCase():
if (player.city != CityName.Aevum) {
@ -363,8 +362,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return false;
}
player.location = LocationName.AevumCrushFitnessGym;
costMult = 3;
expMult = 2;
break;
case LocationName.AevumSnapFitnessGym.toLowerCase():
if (player.city != CityName.Aevum) {
@ -375,8 +372,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return false;
}
player.location = LocationName.AevumSnapFitnessGym;
costMult = 10;
expMult = 5;
break;
case LocationName.Sector12IronGym.toLowerCase():
if (player.city != CityName.Sector12) {
@ -387,8 +382,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return false;
}
player.location = LocationName.Sector12IronGym;
costMult = 1;
expMult = 1;
break;
case LocationName.Sector12PowerhouseGym.toLowerCase():
if (player.city != CityName.Sector12) {
@ -399,8 +392,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return false;
}
player.location = LocationName.Sector12PowerhouseGym;
costMult = 20;
expMult = 10;
break;
case LocationName.VolhavenMilleniumFitnessGym.toLowerCase():
if (player.city != CityName.Volhaven) {
@ -411,8 +402,6 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return false;
}
player.location = LocationName.VolhavenMilleniumFitnessGym;
costMult = 7;
expMult = 4;
break;
default:
_ctx.log(() => `Invalid gym name: ${gymName}. gymWorkout() failed`);
@ -422,19 +411,27 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
switch (stat.toLowerCase()) {
case "strength".toLowerCase():
case "str".toLowerCase():
player.startClass(costMult, expMult, ClassType.GymStrength);
player.startNEWWork(
new ClassWork({ classType: ClassType.GymStrength, location: player.location, singularity: true }),
);
break;
case "defense".toLowerCase():
case "def".toLowerCase():
player.startClass(costMult, expMult, ClassType.GymDefense);
player.startNEWWork(
new ClassWork({ classType: ClassType.GymDefense, location: player.location, singularity: true }),
);
break;
case "dexterity".toLowerCase():
case "dex".toLowerCase():
player.startClass(costMult, expMult, ClassType.GymDexterity);
player.startNEWWork(
new ClassWork({ classType: ClassType.GymDexterity, location: player.location, singularity: true }),
);
break;
case "agility".toLowerCase():
case "agi".toLowerCase():
player.startClass(costMult, expMult, ClassType.GymAgility);
player.startNEWWork(
new ClassWork({ classType: ClassType.GymAgility, location: player.location, singularity: true }),
);
break;
default:
_ctx.log(() => `Invalid stat: ${stat}.`);

@ -134,7 +134,6 @@ export interface IPlayer extends IPerson {
timeWorkedGraftAugmentation: number;
timeNeededToCompleteWork: number;
focus: boolean;
className: ClassType;
currentWorkFactionName: string;
workType: WorkType;
workCostMult: number;
@ -213,7 +212,6 @@ export interface IPlayer extends IPerson {
singularityStopWork(): string;
startBladeburner(p: any): void;
startFactionWork(faction: Faction): void;
startClass(costMult: number, expMult: number, className: ClassType): void;
startCorporation(corpName: string, additionalShares?: number): void;
startFactionFieldWork(faction: Faction): void;
startFactionHackWork(faction: Faction): void;
@ -236,7 +234,6 @@ export interface IPlayer extends IPerson {
gainCodingContractReward(reward: ICodingContractReward, difficulty?: number): string;
stopFocusing(): void;
finishFactionWork(cancelled: boolean, sing?: boolean): string;
finishClass(sing?: boolean): string;
finishWork(cancelled: boolean, sing?: boolean): string;
cancelationPenalty(): number;
finishWorkPartTime(sing?: boolean): string;
@ -257,7 +254,6 @@ export interface IPlayer extends IPerson {
processWorkEarnings(cycles: number): void;
hospitalize(): void;
createProgramWork(numCycles: number): boolean;
takeClass(numCycles: number): boolean;
checkForFactionInvitations(): Faction[];
setBitNodeNumber(n: number): void;
getMult(name: string): number;

@ -10,7 +10,6 @@ import { IMap } from "../../types";
import { Sleeve } from "../Sleeve/Sleeve";
import { IPlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile";
import { Exploit } from "../../Exploits/Exploit";
import { WorkerScript } from "../../Netscript/WorkerScript";
import { CompanyPosition } from "../../Company/CompanyPosition";
import { Server } from "../../Server/Server";
import { BaseServer } from "../../Server/BaseServer";
@ -147,7 +146,6 @@ export class PlayerObject implements IPlayer {
timeWorkedGraftAugmentation: number;
timeNeededToCompleteWork: number;
focus: boolean;
className: ClassType;
currentWorkFactionName: string;
workType: WorkType;
workCostMult: number;
@ -235,7 +233,6 @@ export class PlayerObject implements IPlayer {
singularityStopWork: () => string;
startBladeburner: (p: any) => void;
startFactionWork: (faction: Faction) => void;
startClass: (costMult: number, expMult: number, className: ClassType) => void;
startCorporation: (corpName: string, additionalShares?: number) => void;
startFactionFieldWork: (faction: Faction) => void;
startFactionHackWork: (faction: Faction) => void;
@ -262,7 +259,6 @@ export class PlayerObject implements IPlayer {
gainCodingContractReward: (reward: ICodingContractReward, difficulty?: number) => string;
stopFocusing: () => void;
finishFactionWork: (cancelled: boolean, sing?: boolean) => string;
finishClass: (sing?: boolean) => string;
finishWork: (cancelled: boolean, sing?: boolean) => string;
cancelationPenalty: () => number;
finishWorkPartTime: (sing?: boolean) => string;
@ -284,7 +280,6 @@ export class PlayerObject implements IPlayer {
processWorkEarnings: (cycles: number) => void;
hospitalize: () => void;
createProgramWork: (numCycles: number) => boolean;
takeClass: (numCycles: number) => boolean;
checkForFactionInvitations: () => Faction[];
setBitNodeNumber: (n: number) => void;
getMult: (name: string) => number;
@ -421,8 +416,6 @@ export class PlayerObject implements IPlayer {
this.graftAugmentationName = "";
this.timeWorkedGraftAugmentation = 0;
this.className = ClassType.None;
this.timeWorked = 0; //in m;
this.timeWorkedCreateProgram = 0;
this.timeNeededToCompleteWork = 0;
@ -551,9 +544,6 @@ export class PlayerObject implements IPlayer {
this.startGraftAugmentationWork = generalMethods.startGraftAugmentationWork;
this.graftAugmentationWork = generalMethods.craftAugmentationWork;
this.finishGraftAugmentationWork = generalMethods.finishGraftAugmentationWork;
this.startClass = generalMethods.startClass;
this.takeClass = generalMethods.takeClass;
this.finishClass = generalMethods.finishClass;
this.singularityStopWork = generalMethods.singularityStopWork;
this.takeDamage = generalMethods.takeDamage;
this.regenerateHp = generalMethods.regenerateHp;

@ -61,7 +61,6 @@ import { Money } from "../../ui/React/Money";
import React from "react";
import { serverMetadata } from "../../Server/data/servers";
import { SnackbarEvents, ToastVariant } from "../../ui/React/Snackbar";
import { calculateClassEarnings } from "../formulas/work";
import { achievements } from "../../Achievements/Achievements";
import { FactionNames } from "../../Faction/data/FactionNames";
import { ITaskTracker } from "../ITaskTracker";
@ -145,7 +144,6 @@ export function prestigeAugmentation(this: PlayerObject): void {
this.currentWorkFactionName = "";
this.currentWorkFactionDescription = "";
this.createProgramName = "";
this.className = ClassType.None;
this.workHackExpGainRate = 0;
this.workStrExpGainRate = 0;
@ -546,7 +544,6 @@ export function resetWorkStatus(this: IPlayer, generalType?: WorkType, group?: s
this.currentWorkFactionDescription = "";
this.createProgramName = "";
this.graftAugmentationName = "";
this.className = ClassType.None;
this.workType = WorkType.None;
}
@ -568,7 +565,7 @@ export function processWorkEarnings(this: IPlayer, numCycles = 1): void {
this.gainDexterityExp(dexExpGain);
this.gainAgilityExp(agiExpGain);
this.gainCharismaExp(chaExpGain);
this.gainMoney(moneyGain, this.className ? "class" : "work");
this.gainMoney(moneyGain, "work");
this.workHackExpGained += hackExpGain;
this.workStrExpGained += strExpGain;
this.workDefExpGained += defExpGain;
@ -610,10 +607,6 @@ export function process(this: IPlayer, router: IRouter, numCycles = 1): void {
if (this.createProgramWork(numCycles)) {
router.toTerminal();
}
} else if (this.workType === WorkType.StudyClass) {
if (this.takeClass(numCycles)) {
router.toCity();
}
} else if (this.workType === WorkType.CompanyPartTime) {
if (this.workPartTime(numCycles)) {
router.toCity();
@ -1401,109 +1394,17 @@ export function finishGraftAugmentationWork(this: IPlayer, cancelled: boolean, s
return `Grafting of ${augName} has ended.`;
}
/* Studying/Taking Classes */
export function startClass(this: IPlayer, costMult: number, expMult: number, className: ClassType): void {
this.resetWorkStatus();
this.isWorking = true;
this.workType = WorkType.StudyClass;
this.workCostMult = costMult;
this.workExpMult = expMult;
this.className = className;
const earnings = calculateClassEarnings(this);
this.workMoneyLossRate = earnings.workMoneyLossRate;
this.workHackExpGainRate = earnings.workHackExpGainRate;
this.workStrExpGainRate = earnings.workStrExpGainRate;
this.workDefExpGainRate = earnings.workDefExpGainRate;
this.workDexExpGainRate = earnings.workDexExpGainRate;
this.workAgiExpGainRate = earnings.workAgiExpGainRate;
this.workChaExpGainRate = earnings.workChaExpGainRate;
}
export function takeClass(this: IPlayer, numCycles: number): boolean {
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
const earnings = calculateClassEarnings(this);
this.workMoneyLossRate = earnings.workMoneyLossRate;
this.workHackExpGainRate = earnings.workHackExpGainRate;
this.workStrExpGainRate = earnings.workStrExpGainRate;
this.workDefExpGainRate = earnings.workDefExpGainRate;
this.workDexExpGainRate = earnings.workDexExpGainRate;
this.workAgiExpGainRate = earnings.workAgiExpGainRate;
this.workChaExpGainRate = earnings.workChaExpGainRate;
this.processWorkEarnings(numCycles);
return false;
}
//The 'sing' argument defines whether or not this function was called
//through a Singularity Netscript function
export function finishClass(this: IPlayer, sing = false): string {
this.gainIntelligenceExp(CONSTANTS.IntelligenceClassBaseExpGain * Math.round(this.timeWorked / 1000));
if (this.workMoneyGained > 0) {
throw new Error("ERR: Somehow gained money while taking class");
}
this.updateSkillLevels();
if (!sing) {
dialogBoxCreate(
<>
After {this.className} for {convertTimeMsToTimeElapsedString(this.timeWorked)}, <br />
you spent a total of <Money money={-this.workMoneyGained} />. <br />
<br />
You earned a total of: <br />
{numeralWrapper.formatExp(this.workHackExpGained)} hacking exp <br />
{numeralWrapper.formatExp(this.workStrExpGained)} strength exp <br />
{numeralWrapper.formatExp(this.workDefExpGained)} defense exp <br />
{numeralWrapper.formatExp(this.workDexExpGained)} dexterity exp <br />
{numeralWrapper.formatExp(this.workAgiExpGained)} agility exp <br />
{numeralWrapper.formatExp(this.workChaExpGained)} charisma exp
<br />
</>,
);
}
this.isWorking = false;
if (sing) {
const res =
"After " +
this.className +
" for " +
convertTimeMsToTimeElapsedString(this.timeWorked) +
", " +
"you spent a total of " +
numeralWrapper.formatMoney(this.workMoneyGained * -1) +
". " +
"You earned a total of: " +
numeralWrapper.formatExp(this.workHackExpGained) +
" hacking exp, " +
numeralWrapper.formatExp(this.workStrExpGained) +
" strength exp, " +
numeralWrapper.formatExp(this.workDefExpGained) +
" defense exp, " +
numeralWrapper.formatExp(this.workDexExpGained) +
" dexterity exp, " +
numeralWrapper.formatExp(this.workAgiExpGained) +
" agility exp, and " +
numeralWrapper.formatExp(this.workChaExpGained) +
" charisma exp";
this.resetWorkStatus();
return res;
}
this.resetWorkStatus();
return "";
}
//Cancels the player's current "work" assignment and gives the proper rewards
//Used only for Singularity functions, so no popups are created
export function singularityStopWork(this: IPlayer): string {
if (this.currentWork !== null) {
this.finishNEWWork(true);
}
if (!this.isWorking) {
return "";
}
let res = ""; //Earnings text for work
switch (this.workType) {
case WorkType.StudyClass:
res = this.finishClass(true);
break;
case WorkType.Company:
res = this.finishWork(true, true);
break;

@ -1,81 +0,0 @@
import { CONSTANTS } from "../../Constants";
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
import { IPlayer } from "../IPlayer";
import { ClassType } from "../../utils/WorkType";
export interface WorkEarnings {
workMoneyLossRate: number;
workHackExpGainRate: number;
workStrExpGainRate: number;
workDefExpGainRate: number;
workDexExpGainRate: number;
workAgiExpGainRate: number;
workChaExpGainRate: number;
}
export function calculateClassEarnings(player: IPlayer): WorkEarnings {
const gameCPS = 1000 / CONSTANTS._idleSpeed;
//Find cost and exp gain per game cycle
let cost = 0;
let hackExp = 0,
strExp = 0,
defExp = 0,
dexExp = 0,
agiExp = 0,
chaExp = 0;
const hashManager = player.hashManager;
switch (player.className) {
case ClassType.StudyComputerScience:
hackExp =
((CONSTANTS.ClassStudyComputerScienceBaseExp * player.workExpMult) / gameCPS) * hashManager.getStudyMult();
break;
case ClassType.DataStructures:
cost = (CONSTANTS.ClassDataStructuresBaseCost * player.workCostMult) / gameCPS;
hackExp = ((CONSTANTS.ClassDataStructuresBaseExp * player.workExpMult) / gameCPS) * hashManager.getStudyMult();
break;
case ClassType.Networks:
cost = (CONSTANTS.ClassNetworksBaseCost * player.workCostMult) / gameCPS;
hackExp = ((CONSTANTS.ClassNetworksBaseExp * player.workExpMult) / gameCPS) * hashManager.getStudyMult();
break;
case ClassType.Algorithms:
cost = (CONSTANTS.ClassAlgorithmsBaseCost * player.workCostMult) / gameCPS;
hackExp = ((CONSTANTS.ClassAlgorithmsBaseExp * player.workExpMult) / gameCPS) * hashManager.getStudyMult();
break;
case ClassType.Management:
cost = (CONSTANTS.ClassManagementBaseCost * player.workCostMult) / gameCPS;
chaExp = ((CONSTANTS.ClassManagementBaseExp * player.workExpMult) / gameCPS) * hashManager.getStudyMult();
break;
case ClassType.Leadership:
cost = (CONSTANTS.ClassLeadershipBaseCost * player.workCostMult) / gameCPS;
chaExp = ((CONSTANTS.ClassLeadershipBaseExp * player.workExpMult) / gameCPS) * hashManager.getStudyMult();
break;
case ClassType.GymStrength:
cost = (CONSTANTS.ClassGymBaseCost * player.workCostMult) / gameCPS;
strExp = (player.workExpMult / gameCPS) * hashManager.getTrainingMult();
break;
case ClassType.GymDefense:
cost = (CONSTANTS.ClassGymBaseCost * player.workCostMult) / gameCPS;
defExp = (player.workExpMult / gameCPS) * hashManager.getTrainingMult();
break;
case ClassType.GymDexterity:
cost = (CONSTANTS.ClassGymBaseCost * player.workCostMult) / gameCPS;
dexExp = (player.workExpMult / gameCPS) * hashManager.getTrainingMult();
break;
case ClassType.GymAgility:
cost = (CONSTANTS.ClassGymBaseCost * player.workCostMult) / gameCPS;
agiExp = (player.workExpMult / gameCPS) * hashManager.getTrainingMult();
break;
default:
throw new Error("ERR: Invalid/unrecognized class name");
}
return {
workMoneyLossRate: cost,
workHackExpGainRate: hackExp * player.hacking_exp_mult * BitNodeMultipliers.ClassGymExpGain,
workStrExpGainRate: strExp * player.strength_exp_mult * BitNodeMultipliers.ClassGymExpGain,
workDefExpGainRate: defExp * player.defense_exp_mult * BitNodeMultipliers.ClassGymExpGain,
workDexExpGainRate: dexExp * player.dexterity_exp_mult * BitNodeMultipliers.ClassGymExpGain,
workAgiExpGainRate: agiExp * player.agility_exp_mult * BitNodeMultipliers.ClassGymExpGain,
workChaExpGainRate: chaExp * player.charisma_exp_mult * BitNodeMultipliers.ClassGymExpGain,
};
}

@ -70,7 +70,6 @@ interface Player {
workMoneyGained: number;
createProgramName: string;
createProgramReqLvl: number;
className: string;
work_money_mult: number;
hacknet_node_money_mult: number;
hacknet_node_purchase_cost_mult: number;

@ -46,6 +46,7 @@ export function GetServer(s: string): BaseServer | null {
const server = AllServers[s];
if (server) return server;
}
console.log(AllServers);
if (!isValidIPAddress(s)) {
return GetServerByHostname(s);

204
src/Work/ClassWork.tsx Normal file

@ -0,0 +1,204 @@
import React from "react";
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver";
import { CONSTANTS } from "../Constants";
import { LocationName } from "../Locations/data/LocationNames";
import { numeralWrapper } from "../ui/numeralFormat";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Money } from "../ui/React/Money";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { IPlayer } from "../PersonObjects/IPlayer";
import { calculateClassEarnings as calculateClassEarningsRate } from "./formulas/Class";
import { Work, WorkType } from "./Work";
import { applyWorkStats, newWorkStats, sumWorkStats, WorkStats } from "./WorkStats";
export enum ClassType {
StudyComputerScience = "StudyComputerScience",
DataStructures = "DataStructures",
Networks = "Networks",
Algorithms = "Algorithms",
Management = "Management",
Leadership = "Leadership",
GymStrength = "GymStrength",
GymDefense = "GymDefense",
GymDexterity = "GymDexterity",
GymAgility = "GymAgility",
}
export interface Class {
youAreCurrently: string;
type: ClassType;
earnings: WorkStats;
}
export const Classes: Record<ClassType, Class> = {
[ClassType.StudyComputerScience]: {
youAreCurrently: "studying Computer Science",
type: ClassType.StudyComputerScience,
earnings: newWorkStats({ hackExp: 0.5, intExp: 0.01 }),
},
[ClassType.DataStructures]: {
youAreCurrently: "taking a Data Structures course",
type: ClassType.DataStructures,
earnings: newWorkStats({
money: -40,
hackExp: 1,
intExp: 0.01,
}),
},
[ClassType.Networks]: {
youAreCurrently: "taking a Networks course",
type: ClassType.Networks,
earnings: newWorkStats({
money: -80,
hackExp: 2,
intExp: 0.01,
}),
},
[ClassType.Algorithms]: {
youAreCurrently: "taking an Algorithms course",
type: ClassType.Algorithms,
earnings: newWorkStats({
money: -320,
hackExp: 4,
intExp: 0.01,
}),
},
[ClassType.Management]: {
youAreCurrently: "taking a Management course",
type: ClassType.Management,
earnings: newWorkStats({
money: -160,
chaExp: 2,
intExp: 0.01,
}),
},
[ClassType.Leadership]: {
youAreCurrently: "taking a Leadership course",
type: ClassType.Leadership,
earnings: newWorkStats({
money: -320,
chaExp: 4,
intExp: 0.01,
}),
},
[ClassType.GymStrength]: {
youAreCurrently: "training your strength at a gym",
type: ClassType.GymStrength,
earnings: newWorkStats({
money: -120,
strExp: 1,
}),
},
[ClassType.GymDefense]: {
youAreCurrently: "training your defense at a gym",
type: ClassType.GymDefense,
earnings: newWorkStats({
money: -120,
defExp: 1,
}),
},
[ClassType.GymDexterity]: {
youAreCurrently: "training your dexterity at a gym",
type: ClassType.GymDexterity,
earnings: newWorkStats({
money: -120,
dexExp: 1,
}),
},
[ClassType.GymAgility]: {
youAreCurrently: "training your agility at a gym",
type: ClassType.GymAgility,
earnings: newWorkStats({
money: -120,
agiExp: 1,
}),
},
};
interface ClassWorkParams {
classType: ClassType;
location: LocationName;
singularity: boolean;
}
export const isClassWork = (w: Work | null): w is ClassWork => w !== null && w.type === WorkType.CLASS;
export class ClassWork extends Work {
type = WorkType.CLASS;
classType: ClassType;
location: LocationName;
cyclesWorked: number;
singularity: boolean;
earnings = newWorkStats();
constructor(params?: ClassWorkParams) {
super();
this.classType = params?.classType ?? ClassType.StudyComputerScience;
this.location = params?.location ?? LocationName.Sector12RothmanUniversity;
this.singularity = params?.singularity ?? false;
this.cyclesWorked = 0;
}
isGym(): boolean {
return [ClassType.GymAgility, ClassType.GymDefense, ClassType.GymDexterity, ClassType.GymStrength].includes(
this.classType,
);
}
getClass(): Class {
return Classes[this.classType];
}
calculateRates(player: IPlayer): WorkStats {
return calculateClassEarningsRate(player, this);
}
process(player: IPlayer, cycles: number): boolean {
this.cyclesWorked += cycles;
const rate = this.calculateRates(player);
const earnings = applyWorkStats(player, rate, cycles, "class");
this.earnings = sumWorkStats(this.earnings, earnings);
return false;
}
finish(): void {
if (!this.singularity) {
dialogBoxCreate(
<>
After {this.getClass().youAreCurrently} for{" "}
{convertTimeMsToTimeElapsedString(this.cyclesWorked * CONSTANTS._idleSpeed)}, <br />
you spent a total of <Money money={-this.earnings.money} />. <br />
<br />
You earned a total of: <br />
{numeralWrapper.formatExp(this.earnings.hackExp)} hacking exp <br />
{numeralWrapper.formatExp(this.earnings.strExp)} strength exp <br />
{numeralWrapper.formatExp(this.earnings.defExp)} defense exp <br />
{numeralWrapper.formatExp(this.earnings.dexExp)} dexterity exp <br />
{numeralWrapper.formatExp(this.earnings.agiExp)} agility exp <br />
{numeralWrapper.formatExp(this.earnings.chaExp)} charisma exp
<br />
</>,
);
}
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): any {
return Generic_toJSON("ClassWork", this);
}
/**
* Initiatizes a ClassWork object from a JSON save state.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromJSON(value: any): ClassWork {
return Generic_fromJSON(ClassWork, value.data);
}
}
Reviver.constructors.ClassWork = ClassWork;

@ -8,18 +8,18 @@ import { IPlayer } from "../PersonObjects/IPlayer";
import { numeralWrapper } from "../ui/numeralFormat";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Money } from "../ui/React/Money";
import { CrimeType, WorkType } from "../utils/WorkType";
import { Work } from "./Work";
import { CrimeType } from "../utils/WorkType";
import { Work, WorkType } from "./Work";
interface CrimeWorkParams {
crimeType: CrimeType;
singularity: boolean;
}
export const isCrimeWork = (w: Work): w is CrimeWork => w.type === WorkType.Crime;
export const isCrimeWork = (w: Work | null): w is CrimeWork => w !== null && w.type === WorkType.CRIME;
export class CrimeWork extends Work {
type = WorkType.Crime;
type = WorkType.CRIME;
crimeType: CrimeType;
cyclesWorked: number;

@ -1,5 +1,4 @@
import { IPlayer } from "src/PersonObjects/IPlayer";
import { WorkType } from "src/utils/WorkType";
export abstract class Work {
abstract type: WorkType;
@ -7,3 +6,8 @@ export abstract class Work {
abstract process(player: IPlayer, cycles: number): boolean;
abstract finish(player: IPlayer, cancelled: boolean): void;
}
export enum WorkType {
CRIME = "CRIME",
CLASS = "CLASS",
}

96
src/Work/WorkStats.ts Normal file

@ -0,0 +1,96 @@
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
export interface WorkStats {
money: number;
reputation: number;
hackExp: number;
strExp: number;
defExp: number;
dexExp: number;
agiExp: number;
chaExp: number;
intExp: number;
}
interface newWorkStatsParams {
money?: number;
reputation?: number;
hackExp?: number;
strExp?: number;
defExp?: number;
dexExp?: number;
agiExp?: number;
chaExp?: number;
intExp?: number;
}
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,
dexExp: params?.dexExp ?? 0,
agiExp: params?.agiExp ?? 0,
chaExp: params?.chaExp ?? 0,
intExp: params?.intExp ?? 0,
};
};
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,
dexExp: w0.dexExp + w1.dexExp,
agiExp: w0.agiExp + w1.agiExp,
chaExp: w0.chaExp + w1.chaExp,
intExp: w0.intExp + w1.intExp,
};
};
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,
dexExp: w.dexExp * n,
agiExp: w.agiExp * n,
chaExp: w.chaExp * n,
intExp: w.intExp * n,
};
};
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,
reputation: focusBonus * workStats.reputation * 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,
};
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);
return gains;
};

@ -0,0 +1,47 @@
import { Locations } from "../../Locations/Locations";
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 { WorkStats } from "../WorkStats";
import { Server } from "../../Server/Server";
import { GetServer } from "../../Server/AllServers";
import { serverMetadata } from "../../Server/data/servers";
const gameCPS = 1000 / CONSTANTS._idleSpeed; // 5 cycles per second
export function calculateCost(classs: Class, location: Location): number {
const serverMeta = serverMetadata.find((s) => s.specialName === location.name);
const server = GetServer(serverMeta ? serverMeta.hostname : "");
const discount = (server as Server).backdoorInstalled ? 0.9 : 1;
return classs.earnings.money * location.costMult * discount;
}
export function calculateClassEarnings(player: IPlayer, work: ClassWork): WorkStats {
//Find cost and exp gain per game cycle
const hashManager = player.hashManager;
const classs = Classes[work.classType];
const location = Locations[work.location];
const hashMult = work.isGym() ? hashManager.getTrainingMult() : hashManager.getStudyMult();
const cost = calculateCost(classs, location) / gameCPS;
const hackExp = ((classs.earnings.hackExp * location.expMult) / gameCPS) * hashMult;
const strExp = ((classs.earnings.strExp * location.expMult) / gameCPS) * hashMult;
const defExp = ((classs.earnings.defExp * location.expMult) / gameCPS) * hashMult;
const dexExp = ((classs.earnings.dexExp * location.expMult) / gameCPS) * hashMult;
const agiExp = ((classs.earnings.agiExp * location.expMult) / gameCPS) * hashMult;
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,
dexExp: dexExp * player.dexterity_exp_mult * BitNodeMultipliers.ClassGymExpGain,
agiExp: agiExp * player.agility_exp_mult * BitNodeMultipliers.ClassGymExpGain,
chaExp: chaExp * player.charisma_exp_mult * BitNodeMultipliers.ClassGymExpGain,
intExp: 0,
};
}

@ -298,6 +298,7 @@ const Engine: {
// Process offline progress
loadAllRunningScripts(Player); // This also takes care of offline production for those scripts
if (Player.currentWork !== null) {
Player.focus = true;
Player.processNEWWork(numCyclesOffline);
} else if (Player.isWorking) {
Player.focus = true;
@ -308,9 +309,6 @@ const Engine: {
case WorkType.CreateProgram:
Player.createProgramWork(numCyclesOffline);
break;
case WorkType.StudyClass:
Player.takeClass(numCyclesOffline);
break;
case WorkType.CompanyPartTime:
Player.workPartTime(numCyclesOffline);
break;

@ -27,6 +27,8 @@ 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";
interface IProps {
save: () => void;
@ -138,12 +140,16 @@ function Work(): React.ReactElement {
player.startFocusing();
router.toWork();
};
if (!player.isWorking || player.focus) return <></>;
if ((!player.isWorking && player.currentWork === null) || player.focus) return <></>;
let details = <></>;
let header = <></>;
let innerText = <></>;
if (isClassWork(player.currentWork)) {
details = <>{player.currentWork.getClass().youAreCurrently}</>;
header = <>You are {player.currentWork.getClass().youAreCurrently}</>;
innerText = <>{convertTimeMsToTimeElapsedString(player.currentWork.cyclesWorked * CONSTANTS._idleSpeed)}</>;
}
switch (player.workType) {
case WorkType.CompanyPartTime:
case WorkType.Company:
@ -180,11 +186,6 @@ function Work(): React.ReactElement {
</>
);
break;
case WorkType.StudyClass:
details = <>{player.workType}</>;
header = <>You are {player.className}</>;
innerText = <>{convertTimeMsToTimeElapsedString(player.timeWorked)}</>;
break;
case WorkType.CreateProgram:
details = <>Coding {player.createProgramName}</>;
header = <>Creating a program</>;

@ -21,6 +21,8 @@ import { ReputationRate } from "./React/ReputationRate";
import { StatsRow } from "./React/StatsRow";
import { WorkType, ClassType } from "../utils/WorkType";
import { isCrimeWork } from "../Work/CrimeWork";
import { isClassWork } from "../Work/ClassWork";
import { WorkStats } from "src/Work/WorkStats";
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
@ -43,6 +45,89 @@ interface IWorkInfo {
stopTooltip?: string | React.ReactElement;
}
export function ExpRows(total: WorkStats, rate: WorkStats): React.ReactElement[] {
return [
total.hackExp > 0 ? (
<StatsRow
name="Hacking Exp"
color={Settings.theme.hack}
data={{
content: `${numeralWrapper.formatExp(total.hackExp)} (${numeralWrapper.formatExp(
rate.hackExp * CYCLES_PER_SEC,
)} / sec)`,
}}
/>
) : (
<></>
),
total.strExp > 0 ? (
<StatsRow
name="Strength Exp"
color={Settings.theme.combat}
data={{
content: `${numeralWrapper.formatExp(total.strExp)} (${numeralWrapper.formatExp(
rate.strExp * CYCLES_PER_SEC,
)} / sec)`,
}}
/>
) : (
<></>
),
total.defExp > 0 ? (
<StatsRow
name="Defense Exp"
color={Settings.theme.combat}
data={{
content: `${numeralWrapper.formatExp(total.defExp)} (${numeralWrapper.formatExp(
rate.defExp * CYCLES_PER_SEC,
)} / sec)`,
}}
/>
) : (
<></>
),
total.dexExp > 0 ? (
<StatsRow
name="Dexterity Exp"
color={Settings.theme.combat}
data={{
content: `${numeralWrapper.formatExp(total.dexExp)} (${numeralWrapper.formatExp(
rate.dexExp * CYCLES_PER_SEC,
)} / sec)`,
}}
/>
) : (
<></>
),
total.agiExp > 0 ? (
<StatsRow
name="Agility Exp"
color={Settings.theme.combat}
data={{
content: `${numeralWrapper.formatExp(total.agiExp)} (${numeralWrapper.formatExp(
rate.agiExp * CYCLES_PER_SEC,
)} / sec)`,
}}
/>
) : (
<></>
),
total.chaExp > 0 ? (
<StatsRow
name="Charisma Exp"
color={Settings.theme.cha}
data={{
content: `${numeralWrapper.formatExp(total.chaExp)} (${numeralWrapper.formatExp(
rate.chaExp * CYCLES_PER_SEC,
)} / sec)`,
}}
/>
) : (
<></>
),
];
}
export function WorkInProgressRoot(): React.ReactElement {
const setRerender = useState(false)[1];
function rerender(): void {
@ -57,7 +142,7 @@ export function WorkInProgressRoot(): React.ReactElement {
const player = use.Player();
const router = use.Router();
const expGains = [
let expGains = [
player.workHackExpGained > 0 ? (
<StatsRow
name="Hacking Exp"
@ -168,6 +253,54 @@ export function WorkInProgressRoot(): React.ReactElement {
stopText: "Cancel crime",
};
}
if (isClassWork(player.currentWork)) {
const classWork = player.currentWork;
function cancel(): void {
player.finishNEWWork(true);
router.toCity();
}
function unfocus(): void {
router.toCity();
player.stopFocusing();
}
let stopText = "";
if (classWork.isGym()) {
stopText = "Stop training at gym";
} else {
stopText = "Stop taking course";
}
const rates = classWork.calculateRates(player);
expGains = ExpRows(classWork.earnings, rates);
workInfo = {
buttons: {
cancel: cancel,
unfocus: unfocus,
},
title: (
<>
You are currently <b>{classWork.getClass().youAreCurrently}</b>
</>
),
gains: [
<StatsRow name="Total Cost" color={Settings.theme.money}>
<Typography>
<Money money={classWork.earnings.money} /> (<MoneyRate money={rates.money * CYCLES_PER_SEC} />)
</Typography>
</StatsRow>,
...expGains,
],
progress: {
elapsed: classWork.cyclesWorked * CONSTANTS._idleSpeed,
},
stopText: stopText,
};
}
}
switch (player.workType) {
@ -240,60 +373,6 @@ export function WorkInProgressRoot(): React.ReactElement {
break;
}
case WorkType.StudyClass: {
const className = player.className;
function cancel(): void {
player.finishClass(true);
router.toCity();
}
function unfocus(): void {
router.toCity();
player.stopFocusing();
}
let stopText = "";
if (
className === ClassType.GymStrength ||
className === ClassType.GymDefense ||
className === ClassType.GymDexterity ||
className === ClassType.GymAgility
) {
stopText = "Stop training at gym";
} else {
stopText = "Stop taking course";
}
workInfo = {
buttons: {
cancel: cancel,
unfocus: unfocus,
},
title: (
<>
You are currently <b>{className}</b>
</>
),
gains: [
<StatsRow name="Total Cost" color={Settings.theme.money}>
<Typography>
<Money money={-player.workMoneyGained} /> (<MoneyRate money={player.workMoneyLossRate * CYCLES_PER_SEC} />
)
</Typography>
</StatsRow>,
...expGains,
],
progress: {
elapsed: player.timeWorked,
},
stopText: stopText,
};
break;
}
case WorkType.Company: {
const comp = Companies[player.companyName];
if (comp == null || !(comp instanceof Company)) {