diff --git a/src/Crime/Crime.ts b/src/Crime/Crime.ts
index c7584ff05..21158376f 100644
--- a/src/Crime/Crime.ts
+++ b/src/Crime/Crime.ts
@@ -4,6 +4,7 @@ import { IPerson } from "../PersonObjects/IPerson";
import { IRouter } from "../ui/Router";
import { WorkerScript } from "../Netscript/WorkerScript";
import { CrimeType } from "../utils/WorkType";
+import { CrimeWork } from "../Work/CrimeWork";
interface IConstructorParams {
hacking_success_weight?: number;
@@ -100,19 +101,13 @@ export class Crime {
if (div <= 0) {
div = 1;
}
- p.startCrime(
- router,
- this.type,
- this.hacking_exp / div,
- this.strength_exp / div,
- this.defense_exp / div,
- this.dexterity_exp / div,
- this.agility_exp / div,
- this.charisma_exp / div,
- this.money / div,
- this.time,
- workerScript,
+ p.startNEWWork(
+ new CrimeWork({
+ crimeType: this.type,
+ singularity: workerScript !== null,
+ }),
);
+ router.toWork();
return this.time;
}
diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts
index 02d3a74ba..c7c04e5dd 100644
--- a/src/NetscriptFunctions.ts
+++ b/src/NetscriptFunctions.ts
@@ -2421,7 +2421,6 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
createProgramName: Player.createProgramName,
createProgramReqLvl: Player.createProgramReqLvl,
className: Player.className,
- crimeType: Player.crimeType,
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,
diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts
index be62e8283..4382ceb26 100644
--- a/src/PersonObjects/IPlayer.ts
+++ b/src/PersonObjects/IPlayer.ts
@@ -32,6 +32,7 @@ import { ISkillProgress } from "./formulas/skill";
import { PlayerAchievement } from "../Achievements/Achievements";
import { IPerson } from "./IPerson";
import { WorkType, ClassType, CrimeType } from "../utils/WorkType";
+import { Work } from "src/Work/Work";
export interface IPlayer extends IPerson {
bitNodeN: number;
@@ -124,15 +125,13 @@ export interface IPlayer extends IPerson {
bladeburner_analysis_mult: number;
bladeburner_success_chance_mult: number;
+ currentWork: Work | null;
createProgramReqLvl: number;
factionWorkType: string;
createProgramName: string;
timeWorkedCreateProgram: number;
graftAugmentationName: string;
timeWorkedGraftAugmentation: number;
- crimeType: CrimeType;
- committingCrimeThruSingFn: boolean;
- singFnCrimeWorkerScript: WorkerScript | null;
timeNeededToCompleteWork: number;
focus: boolean;
className: ClassType;
@@ -163,6 +162,9 @@ export interface IPlayer extends IPerson {
entropy: number;
// Methods
+ startNEWWork(w: Work): void;
+ processNEWWork(cycles: number): void;
+ finishNEWWork(cancelled: boolean): void;
work(numCycles: number): boolean;
workPartTime(numCycles: number): boolean;
workForFaction(numCycles: number): boolean;
@@ -213,19 +215,6 @@ export interface IPlayer extends IPerson {
startFactionWork(faction: Faction): void;
startClass(costMult: number, expMult: number, className: ClassType): void;
startCorporation(corpName: string, additionalShares?: number): void;
- startCrime(
- router: IRouter,
- crimeType: CrimeType,
- hackExp: number,
- strExp: number,
- defExp: number,
- dexExp: number,
- agiExp: number,
- chaExp: number,
- money: number,
- time: number,
- singParams: any,
- ): void;
startFactionFieldWork(faction: Faction): void;
startFactionHackWork(faction: Faction): void;
startFactionSecurityWork(faction: Faction): void;
@@ -251,7 +240,6 @@ export interface IPlayer extends IPerson {
finishWork(cancelled: boolean, sing?: boolean): string;
cancelationPenalty(): number;
finishWorkPartTime(sing?: boolean): string;
- finishCrime(cancelled: boolean): string;
finishCreateProgramWork(cancelled: boolean): string;
resetMultipliers(): void;
prestigeAugmentation(): void;
@@ -270,7 +258,6 @@ export interface IPlayer extends IPerson {
hospitalize(): void;
createProgramWork(numCycles: number): boolean;
takeClass(numCycles: number): boolean;
- commitCrime(numCycles: number): boolean;
checkForFactionInvitations(): Faction[];
setBitNodeNumber(n: number): void;
getMult(name: string): number;
diff --git a/src/PersonObjects/Player/PlayerObject.ts b/src/PersonObjects/Player/PlayerObject.ts
index aae9bdd61..ce138db20 100644
--- a/src/PersonObjects/Player/PlayerObject.ts
+++ b/src/PersonObjects/Player/PlayerObject.ts
@@ -4,6 +4,7 @@ import * as corporationMethods from "./PlayerObjectCorporationMethods";
import * as gangMethods from "./PlayerObjectGangMethods";
import * as generalMethods from "./PlayerObjectGeneralMethods";
import * as serverMethods from "./PlayerObjectServerMethods";
+import * as workMethods from "./PlayerObjectWorkMethods";
import { IMap } from "../../types";
import { Sleeve } from "../Sleeve/Sleeve";
@@ -40,6 +41,7 @@ import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { ITaskTracker } from "../ITaskTracker";
import { CONSTANTS } from "../../Constants";
import { WorkType, ClassType, CrimeType, PlayerFactionWorkType } from "../../utils/WorkType";
+import { Work } from "src/Work/Work";
export class PlayerObject implements IPlayer {
// Class members
@@ -136,15 +138,13 @@ export class PlayerObject implements IPlayer {
bladeburner_analysis_mult: number;
bladeburner_success_chance_mult: number;
+ currentWork: Work | null;
createProgramReqLvl: number;
factionWorkType: PlayerFactionWorkType;
createProgramName: string;
timeWorkedCreateProgram: number;
graftAugmentationName: string;
timeWorkedGraftAugmentation: number;
- crimeType: CrimeType;
- committingCrimeThruSingFn: boolean;
- singFnCrimeWorkerScript: WorkerScript | null;
timeNeededToCompleteWork: number;
focus: boolean;
className: ClassType;
@@ -175,6 +175,9 @@ export class PlayerObject implements IPlayer {
entropy: number;
// Methods
+ startNEWWork: (w: Work) => void;
+ processNEWWork: (cycles: number) => void;
+ finishNEWWork: (cancelled: boolean) => void;
work: (numCycles: number) => boolean;
workPartTime: (numCycles: number) => boolean;
workForFaction: (numCycles: number) => boolean;
@@ -234,19 +237,6 @@ export class PlayerObject implements IPlayer {
startFactionWork: (faction: Faction) => void;
startClass: (costMult: number, expMult: number, className: ClassType) => void;
startCorporation: (corpName: string, additionalShares?: number) => void;
- startCrime: (
- router: IRouter,
- crimeType: CrimeType,
- hackExp: number,
- strExp: number,
- defExp: number,
- dexExp: number,
- agiExp: number,
- chaExp: number,
- money: number,
- time: number,
- singParams: any,
- ) => void;
startFactionFieldWork: (faction: Faction) => void;
startFactionHackWork: (faction: Faction) => void;
startFactionSecurityWork: (faction: Faction) => void;
@@ -276,7 +266,6 @@ export class PlayerObject implements IPlayer {
finishWork: (cancelled: boolean, sing?: boolean) => string;
cancelationPenalty: () => number;
finishWorkPartTime: (sing?: boolean) => string;
- finishCrime: (cancelled: boolean) => string;
finishCreateProgramWork: (cancelled: boolean) => string;
resetMultipliers: () => void;
prestigeAugmentation: () => void;
@@ -296,7 +285,6 @@ export class PlayerObject implements IPlayer {
hospitalize: () => void;
createProgramWork: (numCycles: number) => boolean;
takeClass: (numCycles: number) => boolean;
- commitCrime: (numCycles: number) => boolean;
checkForFactionInvitations: () => Faction[];
setBitNodeNumber: (n: number) => void;
getMult: (name: string) => number;
@@ -435,8 +423,6 @@ export class PlayerObject implements IPlayer {
this.className = ClassType.None;
- this.crimeType = CrimeType.None;
-
this.timeWorked = 0; //in m;
this.timeWorkedCreateProgram = 0;
this.timeNeededToCompleteWork = 0;
@@ -495,6 +481,8 @@ export class PlayerObject implements IPlayer {
this.achievements = [];
this.terminalCommandHistory = [];
+ this.currentWork = null;
+
// Let's get a hash of some semi-random stuff so we have something unique.
this.identifier = cyrb53(
"I-" +
@@ -532,6 +520,9 @@ export class PlayerObject implements IPlayer {
this.processWorkEarnings = generalMethods.processWorkEarnings;
this.startWork = generalMethods.startWork;
this.cancelationPenalty = generalMethods.cancelationPenalty;
+ this.startNEWWork = workMethods.start;
+ this.processNEWWork = workMethods.process;
+ this.finishNEWWork = workMethods.finish;
this.work = generalMethods.work;
this.finishWork = generalMethods.finishWork;
this.startWorkPartTime = generalMethods.startWorkPartTime;
@@ -563,9 +554,6 @@ export class PlayerObject implements IPlayer {
this.startClass = generalMethods.startClass;
this.takeClass = generalMethods.takeClass;
this.finishClass = generalMethods.finishClass;
- this.startCrime = generalMethods.startCrime;
- this.commitCrime = generalMethods.commitCrime;
- this.finishCrime = generalMethods.finishCrime;
this.singularityStopWork = generalMethods.singularityStopWork;
this.takeDamage = generalMethods.takeDamage;
this.regenerateHp = generalMethods.regenerateHp;
@@ -623,8 +611,6 @@ export class PlayerObject implements IPlayer {
this.getUpgradeHomeCoresCost = serverMethods.getUpgradeHomeCoresCost;
this.createHacknetServer = serverMethods.createHacknetServer;
this.factionWorkType = PlayerFactionWorkType.None;
- this.committingCrimeThruSingFn = false;
- this.singFnCrimeWorkerScript = null;
this.getMult = generalMethods.getMult;
this.setMult = generalMethods.setMult;
diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx
index 4ae471894..dc18de70f 100644
--- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx
+++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx
@@ -146,7 +146,6 @@ export function prestigeAugmentation(this: PlayerObject): void {
this.currentWorkFactionDescription = "";
this.createProgramName = "";
this.className = ClassType.None;
- this.crimeType = CrimeType.None;
this.workHackExpGainRate = 0;
this.workStrExpGainRate = 0;
@@ -615,10 +614,6 @@ export function process(this: IPlayer, router: IRouter, numCycles = 1): void {
if (this.takeClass(numCycles)) {
router.toCity();
}
- } else if (this.workType === WorkType.Crime) {
- if (this.commitCrime(numCycles)) {
- router.toLocation(Locations[LocationName.Slums]);
- }
} else if (this.workType === WorkType.CompanyPartTime) {
if (this.workPartTime(numCycles)) {
router.toCity();
@@ -1330,10 +1325,7 @@ export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): stri
if (!cancelled) {
//Complete case
this.gainIntelligenceExp((CONSTANTS.IntelligenceProgramBaseExpGain * this.timeWorked) / 1000);
- const lines = [
- `You've finished creating ${programName}!`,
- "The new program can be found on your home computer.",
- ];
+ const lines = [`You've finished creating ${programName}!`, "The new program can be found on your home computer."];
dialogBoxCreate(lines.join("
"));
message = lines.join(" ");
@@ -1501,196 +1493,6 @@ export function finishClass(this: IPlayer, sing = false): string {
return "";
}
-//The EXP and $ gains are hardcoded. Time is in ms
-export function startCrime(
- this: IPlayer,
- router: IRouter,
- crimeType: CrimeType,
- hackExp: number,
- strExp: number,
- defExp: number,
- dexExp: number,
- agiExp: number,
- chaExp: number,
- money: number,
- time: number,
- workerscript: WorkerScript | null = null,
-): void {
- this.crimeType = crimeType;
-
- this.resetWorkStatus();
- this.isWorking = true;
- this.focus = true;
- this.workType = WorkType.Crime;
-
- if (workerscript !== null) {
- this.committingCrimeThruSingFn = true;
- this.singFnCrimeWorkerScript = workerscript;
- }
-
- this.workHackExpGained = hackExp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain;
- this.workStrExpGained = strExp * this.strength_exp_mult * BitNodeMultipliers.CrimeExpGain;
- this.workDefExpGained = defExp * this.defense_exp_mult * BitNodeMultipliers.CrimeExpGain;
- this.workDexExpGained = dexExp * this.dexterity_exp_mult * BitNodeMultipliers.CrimeExpGain;
- this.workAgiExpGained = agiExp * this.agility_exp_mult * BitNodeMultipliers.CrimeExpGain;
- this.workChaExpGained = chaExp * this.charisma_exp_mult * BitNodeMultipliers.CrimeExpGain;
- this.workMoneyGained = money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney;
-
- this.timeNeededToCompleteWork = time;
- router.toWork();
-}
-
-export function commitCrime(this: IPlayer, numCycles: number): boolean {
- this.timeWorked += CONSTANTS._idleSpeed * numCycles;
-
- if (this.timeWorked >= this.timeNeededToCompleteWork) {
- this.finishCrime(false);
- return true;
- }
- return false;
-}
-
-export function finishCrime(this: IPlayer, cancelled: boolean): string {
- //Determine crime success/failure
- if (!cancelled) {
- if (determineCrimeSuccess(this, this.crimeType)) {
- //Handle Karma and crime statistics
- let crime = null;
- for (const i of Object.keys(Crimes)) {
- if (Crimes[i].type == this.crimeType) {
- crime = Crimes[i];
- break;
- }
- }
- if (crime == null) {
- dialogBoxCreate(
- `ERR: Unrecognized crime type (${this.crimeType}). This is probably a bug please contact the developer`,
- );
- return "";
- }
- this.gainMoney(this.workMoneyGained, "crime");
- this.karma -= crime.karma;
- this.numPeopleKilled += crime.kills;
- if (crime.intelligence_exp > 0) {
- this.gainIntelligenceExp(crime.intelligence_exp);
- }
-
- //On a crime success, gain 2x exp
- this.workHackExpGained *= 2;
- this.workStrExpGained *= 2;
- this.workDefExpGained *= 2;
- this.workDexExpGained *= 2;
- this.workAgiExpGained *= 2;
- this.workChaExpGained *= 2;
- const ws = this.singFnCrimeWorkerScript;
- if (this.committingCrimeThruSingFn && ws !== null) {
- if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
- ws.scriptRef.log(
- "SUCCESS: Crime successful! Gained " +
- numeralWrapper.formatMoney(this.workMoneyGained) +
- ", " +
- numeralWrapper.formatExp(this.workHackExpGained) +
- " hack exp, " +
- numeralWrapper.formatExp(this.workStrExpGained) +
- " str exp, " +
- numeralWrapper.formatExp(this.workDefExpGained) +
- " def exp, " +
- numeralWrapper.formatExp(this.workDexExpGained) +
- " dex exp, " +
- numeralWrapper.formatExp(this.workAgiExpGained) +
- " agi exp, " +
- numeralWrapper.formatExp(this.workChaExpGained) +
- " cha exp.",
- );
- }
- } else {
- dialogBoxCreate(
- <>
- Crime successful!
-
-
- You gained:
-
-
-
- {numeralWrapper.formatExp(this.workHackExpGained)} hacking experience
- {numeralWrapper.formatExp(this.workStrExpGained)} strength experience
-
- {numeralWrapper.formatExp(this.workDefExpGained)} defense experience
-
- {numeralWrapper.formatExp(this.workDexExpGained)} dexterity experience
-
- {numeralWrapper.formatExp(this.workAgiExpGained)} agility experience
-
- {numeralWrapper.formatExp(this.workChaExpGained)} charisma experience
- >,
- );
- }
- } else {
- //Exp halved on failure
- this.workHackExpGained /= 2;
- this.workStrExpGained /= 2;
- this.workDefExpGained /= 2;
- this.workDexExpGained /= 2;
- this.workAgiExpGained /= 2;
- this.workChaExpGained /= 2;
- const ws = this.singFnCrimeWorkerScript;
- if (this.committingCrimeThruSingFn && ws !== null) {
- if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
- ws.scriptRef.log(
- "FAIL: Crime failed! Gained " +
- numeralWrapper.formatExp(this.workHackExpGained) +
- " hack exp, " +
- numeralWrapper.formatExp(this.workStrExpGained) +
- " str exp, " +
- numeralWrapper.formatExp(this.workDefExpGained) +
- " def exp, " +
- numeralWrapper.formatExp(this.workDexExpGained) +
- " dex exp, " +
- numeralWrapper.formatExp(this.workAgiExpGained) +
- " agi exp, " +
- numeralWrapper.formatExp(this.workChaExpGained) +
- " cha exp.",
- );
- }
- } else {
- dialogBoxCreate(
- <>
- Crime failed!
-
-
- You gained:
-
- {numeralWrapper.formatExp(this.workHackExpGained)} hacking experience
- {numeralWrapper.formatExp(this.workStrExpGained)} strength experience
-
- {numeralWrapper.formatExp(this.workDefExpGained)} defense experience
-
- {numeralWrapper.formatExp(this.workDexExpGained)} dexterity experience
-
- {numeralWrapper.formatExp(this.workAgiExpGained)} agility experience
-
- {numeralWrapper.formatExp(this.workChaExpGained)} charisma experience
- >,
- );
- }
- }
-
- this.gainHackingExp(this.workHackExpGained);
- this.gainStrengthExp(this.workStrExpGained);
- this.gainDefenseExp(this.workDefExpGained);
- this.gainDexterityExp(this.workDexExpGained);
- this.gainAgilityExp(this.workAgiExpGained);
- this.gainCharismaExp(this.workChaExpGained);
- }
- this.committingCrimeThruSingFn = false;
- this.singFnCrimeWorkerScript = null;
- this.isWorking = false;
- this.crimeType = CrimeType.None;
- 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 {
@@ -1714,9 +1516,6 @@ export function singularityStopWork(this: IPlayer): string {
case WorkType.CreateProgram:
res = this.finishCreateProgramWork(true);
break;
- case WorkType.Crime:
- res = this.finishCrime(true);
- break;
case WorkType.GraftAugmentation:
res = this.finishGraftAugmentationWork(true, true);
break;
diff --git a/src/PersonObjects/Player/PlayerObjectWorkMethods.ts b/src/PersonObjects/Player/PlayerObjectWorkMethods.ts
new file mode 100644
index 000000000..6ef8557b2
--- /dev/null
+++ b/src/PersonObjects/Player/PlayerObjectWorkMethods.ts
@@ -0,0 +1,21 @@
+import { Work } from "../../Work/Work";
+import { IPlayer } from "../IPlayer";
+
+export function start(this: IPlayer, w: Work): void {
+ if (this.currentWork !== null) {
+ this.currentWork.finish(this, true);
+ }
+ this.currentWork = w;
+}
+export function process(this: IPlayer, cycles = 1): void {
+ if (this.currentWork === null) return;
+ const finished = this.currentWork.process(this, cycles);
+ if (finished) {
+ this.finishNEWWork(false);
+ }
+}
+export function finish(this: IPlayer, cancelled: boolean): void {
+ if (this.currentWork === null) return;
+ this.currentWork.finish(this, cancelled);
+ this.currentWork = null;
+}
diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts
index 0177aee7e..d099e6170 100644
--- a/src/ScriptEditor/NetscriptDefinitions.d.ts
+++ b/src/ScriptEditor/NetscriptDefinitions.d.ts
@@ -71,7 +71,6 @@ interface Player {
createProgramName: string;
createProgramReqLvl: number;
className: string;
- crimeType: string;
work_money_mult: number;
hacknet_node_money_mult: number;
hacknet_node_purchase_cost_mult: number;
diff --git a/src/Work/CrimeWork.tsx b/src/Work/CrimeWork.tsx
new file mode 100644
index 000000000..9bd2bba11
--- /dev/null
+++ b/src/Work/CrimeWork.tsx
@@ -0,0 +1,139 @@
+import React from "react";
+import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver";
+import { Crime } from "../Crime/Crime";
+import { CONSTANTS } from "../Constants";
+import { determineCrimeSuccess } from "../Crime/CrimeHelpers";
+import { Crimes } from "../Crime/Crimes";
+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";
+
+interface CrimeWorkParams {
+ crimeType: CrimeType;
+ singularity: boolean;
+}
+
+export const isCrimeWork = (w: Work): w is CrimeWork => w.type === WorkType.Crime;
+
+export class CrimeWork extends Work {
+ type = WorkType.Crime;
+
+ crimeType: CrimeType;
+ cyclesWorked: number;
+ singularity: boolean;
+
+ constructor(params?: CrimeWorkParams) {
+ super();
+ this.crimeType = params?.crimeType ?? CrimeType.Shoplift;
+ this.singularity = params?.singularity ?? false;
+ this.cyclesWorked = 0;
+ }
+
+ getCrime(): Crime {
+ const crime = Object.values(Crimes).find((c) => c.type === this.crimeType);
+ if (!crime) throw new Error("CrimeWork object constructed with invalid crime type");
+ return crime;
+ }
+
+ process(player: IPlayer, cycles = 1): boolean {
+ this.cyclesWorked += cycles;
+ const time = Object.values(Crimes).find((c) => c.type === this.crimeType)?.time ?? 0;
+ return this.cyclesWorked * CONSTANTS._idleSpeed >= time;
+ }
+
+ finish(player: IPlayer, cancelled: boolean): void {
+ if (cancelled) return;
+ let crime = null;
+ for (const i of Object.keys(Crimes)) {
+ if (Crimes[i].type == this.crimeType) {
+ crime = Crimes[i];
+ break;
+ }
+ }
+ if (crime == null) {
+ dialogBoxCreate(
+ `ERR: Unrecognized crime type (${this.crimeType}). This is probably a bug please contact the developer`,
+ );
+ return;
+ }
+ // exp times 2 because were trying to maintain the same numbers as before the conversion
+ // Technically the definition of Crimes should have the success numbers and failure should divide by 4
+ let hackExp = crime.hacking_exp * 2;
+ let StrExp = crime.strength_exp * 2;
+ let DefExp = crime.defense_exp * 2;
+ let DexExp = crime.dexterity_exp * 2;
+ let AgiExp = crime.agility_exp * 2;
+ let ChaExp = crime.charisma_exp * 2;
+ let karma = crime.karma;
+ const success = determineCrimeSuccess(player, crime.type);
+ if (success) {
+ player.gainMoney(crime.money, "crime");
+ player.numPeopleKilled += crime.kills;
+ player.gainIntelligenceExp(crime.intelligence_exp);
+ } else {
+ hackExp /= 4;
+ StrExp /= 4;
+ DefExp /= 4;
+ DexExp /= 4;
+ AgiExp /= 4;
+ ChaExp /= 4;
+ karma /= 4;
+ }
+
+ player.gainHackingExp(hackExp);
+ player.gainStrengthExp(StrExp);
+ player.gainDefenseExp(DefExp);
+ player.gainDexterityExp(DexExp);
+ player.gainAgilityExp(AgiExp);
+ player.gainCharismaExp(ChaExp);
+ player.karma -= karma;
+
+ if (!this.singularity) {
+ dialogBoxCreate(
+ <>
+ Crime {success ? "successful" : "failed"}!
+
+
+ You gained:
+ {success && (
+ <>
+
+
+ >
+ )}
+
+ {numeralWrapper.formatExp(hackExp)} hacking experience
+ {numeralWrapper.formatExp(StrExp)} strength experience
+
+ {numeralWrapper.formatExp(DefExp)} defense experience
+
+ {numeralWrapper.formatExp(DexExp)} dexterity experience
+
+ {numeralWrapper.formatExp(AgiExp)} agility experience
+
+ {numeralWrapper.formatExp(ChaExp)} charisma experience
+ >,
+ );
+ }
+ }
+
+ /**
+ * Serialize the current object to a JSON save state.
+ */
+ toJSON(): any {
+ return Generic_toJSON("CrimeWork", this);
+ }
+
+ /**
+ * Initiatizes a CrimeWork object from a JSON save state.
+ */
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+ static fromJSON(value: any): CrimeWork {
+ return Generic_fromJSON(CrimeWork, value.data);
+ }
+}
+
+Reviver.constructors.CrimeWork = CrimeWork;
diff --git a/src/Work/Work.ts b/src/Work/Work.ts
new file mode 100644
index 000000000..0b9d6f63c
--- /dev/null
+++ b/src/Work/Work.ts
@@ -0,0 +1,9 @@
+import { IPlayer } from "src/PersonObjects/IPlayer";
+import { WorkType } from "src/utils/WorkType";
+
+export abstract class Work {
+ abstract type: WorkType;
+
+ abstract process(player: IPlayer, cycles: number): boolean;
+ abstract finish(player: IPlayer, cancelled: boolean): void;
+}
diff --git a/src/engine.tsx b/src/engine.tsx
index 5a16e7f54..c29aa7769 100644
--- a/src/engine.tsx
+++ b/src/engine.tsx
@@ -96,7 +96,11 @@ const Engine: {
Terminal.process(Router, Player, numCycles);
- Player.process(Router, numCycles);
+ if (Player.currentWork !== null) {
+ Player.processNEWWork(numCycles);
+ } else {
+ Player.process(Router, numCycles);
+ }
// Update stock prices
if (Player.hasWseAccount) {
@@ -293,7 +297,9 @@ const Engine: {
Player.gainMoney(offlineHackingIncome, "hacking");
// Process offline progress
loadAllRunningScripts(Player); // This also takes care of offline production for those scripts
- if (Player.isWorking) {
+ if (Player.currentWork !== null) {
+ Player.processNEWWork(numCyclesOffline);
+ } else if (Player.isWorking) {
Player.focus = true;
switch (Player.workType) {
case WorkType.Faction:
@@ -305,9 +311,6 @@ const Engine: {
case WorkType.StudyClass:
Player.takeClass(numCyclesOffline);
break;
- case WorkType.Crime:
- Player.commitCrime(numCyclesOffline);
- break;
case WorkType.CompanyPartTime:
Player.workPartTime(numCyclesOffline);
break;
diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx
index ad6d73580..edb6a8b19 100644
--- a/src/ui/GameRoot.tsx
+++ b/src/ui/GameRoot.tsx
@@ -154,7 +154,7 @@ export let Router: IRouter = {
function determineStartPage(player: IPlayer): Page {
if (RecoveryMode) return Page.Recovery;
- if (player.isWorking) return Page.Work;
+ if (player.isWorking || player.currentWork !== null) return Page.Work;
return Page.Terminal;
}
diff --git a/src/ui/WorkInProgressRoot.tsx b/src/ui/WorkInProgressRoot.tsx
index a94d6776a..811ce7b97 100644
--- a/src/ui/WorkInProgressRoot.tsx
+++ b/src/ui/WorkInProgressRoot.tsx
@@ -20,6 +20,7 @@ import { Reputation } from "./React/Reputation";
import { ReputationRate } from "./React/ReputationRate";
import { StatsRow } from "./React/StatsRow";
import { WorkType, ClassType } from "../utils/WorkType";
+import { isCrimeWork } from "../Work/CrimeWork";
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
@@ -137,7 +138,37 @@ export function WorkInProgressRoot(): React.ReactElement {
),
];
- let workInfo: IWorkInfo | null;
+ let workInfo: IWorkInfo = {
+ buttons: {
+ cancel: () => undefined,
+ },
+ title: "",
+ stopText: "",
+ };
+
+ if (player.currentWork !== null) {
+ if (isCrimeWork(player.currentWork)) {
+ const crime = player.currentWork.getCrime();
+ const completion = Math.round(((player.currentWork.cyclesWorked * CONSTANTS._idleSpeed) / crime.time) * 100);
+
+ workInfo = {
+ buttons: {
+ cancel: () => {
+ router.toLocation(Locations[LocationName.Slums]);
+ player.finishNEWWork(true);
+ },
+ },
+ title: `You are attempting to ${crime.type}`,
+
+ progress: {
+ remaining: crime.time - player.currentWork.cyclesWorked * CONSTANTS._idleSpeed,
+ percentage: completion,
+ },
+
+ stopText: "Cancel crime",
+ };
+ }
+ }
switch (player.workType) {
case WorkType.Faction: {
@@ -399,29 +430,6 @@ export function WorkInProgressRoot(): React.ReactElement {
break;
}
- case WorkType.Crime: {
- const completion = Math.round((player.timeWorked / player.timeNeededToCompleteWork) * 100);
-
- workInfo = {
- buttons: {
- cancel: () => {
- router.toLocation(Locations[LocationName.Slums]);
- player.finishCrime(true);
- },
- },
- title: `You are attempting to ${player.crimeType}`,
-
- progress: {
- remaining: player.timeNeededToCompleteWork - player.timeWorked,
- percentage: completion,
- },
-
- stopText: "Cancel crime",
- };
-
- break;
- }
-
case WorkType.CreateProgram: {
function cancel(): void {
player.finishCreateProgramWork(true);
@@ -497,11 +505,12 @@ export function WorkInProgressRoot(): React.ReactElement {
}
default:
- router.toTerminal();
- workInfo = null;
+ if (player.currentWork === null) {
+ router.toTerminal();
+ }
}
- if (workInfo === null) {
+ if (workInfo.title === "") {
return <>>;
}