create program and grafting done

This commit is contained in:
Olivier Gagnon 2022-07-10 01:37:36 -04:00
parent 647392626e
commit 606f1bf6c4
25 changed files with 375 additions and 324 deletions

@ -7,6 +7,7 @@ import { SpecialServers } from "../Server/data/SpecialServers";
import { numeralWrapper } from "../ui/numeralFormat";
import { Money } from "../ui/React/Money";
import { DarkWebItem } from "./DarkWebItem";
import { isCreateProgramWork } from "../Work/CreateProgramWork";
//Posts a "help" message if connected to DarkWeb
export function checkIfConnectedToDarkweb(): void {
@ -74,7 +75,7 @@ export function buyDarkwebItem(itemName: string): void {
Player.getHomeComputer().pushProgram(item.program);
// Cancel if the program is in progress of writing
if (Player.createProgramName === item.program) {
if (isCreateProgramWork(Player.currentWork) && Player.currentWork.programName === item.program) {
Player.isWorking = false;
Player.resetWorkStatus();
}

@ -38,7 +38,6 @@ export function SaveFile(): React.ReactElement {
function doRestore(): void {
const save = JSON.parse(saveFile);
console.log(Object.keys(save));
// TODO: Continue here.
}

@ -150,7 +150,7 @@ function initSaveFunctions(): void {
try {
saveObject.exportGame();
} catch (error) {
console.log(error);
console.error(error);
SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000);
}
},
@ -199,7 +199,7 @@ function initElectronBridge(): void {
bridge.send("save-completed");
})
.catch((error: unknown) => {
console.log(error);
console.error(error);
SnackbarEvents.emit("Could not save game.", ToastVariant.ERROR, 2000);
});
});
@ -207,7 +207,7 @@ function initElectronBridge(): void {
try {
window.appSaveFns.triggerGameExport();
} catch (error) {
console.log(error);
console.error(error);
SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000);
}
});
@ -215,7 +215,7 @@ function initElectronBridge(): void {
try {
window.appSaveFns.triggerScriptsExport();
} catch (error) {
console.log(error);
console.error(error);
SnackbarEvents.emit("Could not export scripts.", ToastVariant.ERROR, 2000);
}
});

@ -252,7 +252,6 @@ export class Gang implements IGang {
const total = Object.values(AllGangs)
.map((g) => g.territory)
.reduce((p, c) => p + c, 0);
console.log(total);
Object.values(AllGangs).forEach((g) => (g.territory /= total));
}
}

@ -2418,8 +2418,6 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
workChaExpGained: Player.workChaExpGained,
workRepGained: Player.workRepGained,
workMoneyGained: Player.workMoneyGained,
createProgramName: Player.createProgramName,
createProgramReqLvl: Player.createProgramReqLvl,
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,

@ -7,6 +7,7 @@ import { getGraftingAvailableAugs, calculateGraftingTimeWithBonus } from "../Per
import { IPlayer } from "../PersonObjects/IPlayer";
import { Grafting as IGrafting } from "../ScriptEditor/NetscriptDefinitions";
import { Router } from "../ui/GameRoot";
import { GraftingWork } from "../Work/GraftingWork";
export function NetscriptGrafting(player: IPlayer): InternalAPI<IGrafting> {
const checkGraftingAPIAccess = (ctx: NetscriptContext): void => {
@ -79,8 +80,13 @@ export function NetscriptGrafting(player: IPlayer): InternalAPI<IGrafting> {
return false;
}
player.loseMoney(craftableAug.cost, "augmentations");
player.startGraftAugmentationWork(augName, craftableAug.time);
player.startNEWWork(
new GraftingWork({
singularity: true,
augmentation: augName,
player: player,
}),
);
if (focus) {
player.startFocusing();

@ -51,6 +51,7 @@ import { enterBitNode } from "../RedPill";
import { FactionNames } from "../Faction/data/FactionNames";
import { WorkType } from "../utils/WorkType";
import { ClassWork, ClassType } from "../Work/ClassWork";
import { CreateProgramWork, isCreateProgramWork } from "../Work/CreateProgramWork";
export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript): InternalAPI<ISingularity> {
const getAugmentation = function (_ctx: NetscriptContext, name: string): Augmentation {
@ -528,7 +529,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
player.getHomeComputer().pushProgram(item.program);
// Cancel if the program is in progress of writing
if (player.createProgramName === item.program) {
if (isCreateProgramWork(player.currentWork) && player.currentWork.programName === item.program) {
player.isWorking = false;
player.resetWorkStatus();
}
@ -1182,7 +1183,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript
return false;
}
player.startCreateProgramWork(p.name, create.time, create.level);
player.startNEWWork(
new CreateProgramWork({
programName: p.name,
singularity: true,
player: player,
}),
);
if (focus) {
player.startFocusing();
Router.toWork();

@ -1,6 +1,7 @@
import { CheckBox, CheckBoxOutlineBlank, Construction } from "@mui/icons-material";
import { Box, Button, Container, List, ListItemButton, Paper, Typography } from "@mui/material";
import React, { useEffect, useState } from "react";
import { GraftingWork } from "../../../Work/GraftingWork";
import { Augmentation } from "../../../Augmentation/Augmentation";
import { AugmentationNames } from "../../../Augmentation/data/AugmentationNames";
import { StaticAugmentations } from "../../../Augmentation/StaticAugmentations";
@ -19,7 +20,7 @@ import { IPlayer } from "../../IPlayer";
import { GraftableAugmentation } from "../GraftableAugmentation";
import { calculateGraftingTimeWithBonus, getGraftingAvailableAugs } from "../GraftingHelpers";
const GraftableAugmentations: IMap<GraftableAugmentation> = {};
export const GraftableAugmentations: IMap<GraftableAugmentation> = {};
const canGraft = (player: IPlayer, aug: GraftableAugmentation): boolean => {
if (player.money < aug.cost) {
@ -154,9 +155,13 @@ export const GraftingRoot = (): React.ReactElement => {
open={graftOpen}
onClose={() => setGraftOpen(false)}
onConfirm={() => {
const graftableAug = GraftableAugmentations[selectedAug];
player.loseMoney(graftableAug.cost, "augmentations");
player.startGraftAugmentationWork(selectedAug, graftableAug.time);
player.startNEWWork(
new GraftingWork({
augmentation: selectedAug,
singularity: false,
player: player,
}),
);
player.startFocusing();
router.toWork();
}}

@ -126,12 +126,7 @@ export interface IPlayer extends IPerson {
bladeburner_success_chance_mult: number;
currentWork: Work | null;
createProgramReqLvl: number;
factionWorkType: string;
createProgramName: string;
timeWorkedCreateProgram: number;
graftAugmentationName: string;
timeWorkedGraftAugmentation: number;
timeNeededToCompleteWork: number;
focus: boolean;
currentWorkFactionName: string;
@ -227,7 +222,6 @@ export interface IPlayer extends IPerson {
quitJob(company: string, sing?: boolean): void;
hasJob(): boolean;
createHacknetServer(): HacknetServer;
startCreateProgramWork(programName: string, time: number, reqLevel: number): void;
queueAugmentation(augmentationName: string): void;
receiveInvite(factionName: string): void;
updateSkillLevels(): void;
@ -237,7 +231,6 @@ export interface IPlayer extends IPerson {
finishWork(cancelled: boolean, sing?: boolean): string;
cancelationPenalty(): number;
finishWorkPartTime(sing?: boolean): string;
finishCreateProgramWork(cancelled: boolean): string;
resetMultipliers(): void;
prestigeAugmentation(): void;
prestigeSourceFile(): void;
@ -253,15 +246,11 @@ export interface IPlayer extends IPerson {
getWorkMoneyGain(): number;
processWorkEarnings(cycles: number): void;
hospitalize(): void;
createProgramWork(numCycles: number): boolean;
checkForFactionInvitations(): Faction[];
setBitNodeNumber(n: number): void;
getMult(name: string): number;
setMult(name: string, mult: number): void;
canAccessCotMG(): boolean;
sourceFileLvl(n: number): number;
startGraftAugmentationWork(augmentationName: string, time: number): void;
graftAugmentationWork(numCycles: number): boolean;
finishGraftAugmentationWork(cancelled: boolean, singularity?: boolean): string;
applyEntropy(stacks?: number): void;
}

@ -138,12 +138,7 @@ export class PlayerObject implements IPlayer {
bladeburner_success_chance_mult: number;
currentWork: Work | null;
createProgramReqLvl: number;
factionWorkType: PlayerFactionWorkType;
createProgramName: string;
timeWorkedCreateProgram: number;
graftAugmentationName: string;
timeWorkedGraftAugmentation: number;
timeNeededToCompleteWork: number;
focus: boolean;
currentWorkFactionName: string;
@ -252,7 +247,6 @@ export class PlayerObject implements IPlayer {
hasJob: () => boolean;
process: (router: IRouter, numCycles?: number) => void;
createHacknetServer: () => HacknetServer;
startCreateProgramWork: (programName: string, time: number, reqLevel: number) => void;
queueAugmentation: (augmentationName: string) => void;
receiveInvite: (factionName: string) => void;
updateSkillLevels: () => void;
@ -262,7 +256,6 @@ export class PlayerObject implements IPlayer {
finishWork: (cancelled: boolean, sing?: boolean) => string;
cancelationPenalty: () => number;
finishWorkPartTime: (sing?: boolean) => string;
finishCreateProgramWork: (cancelled: boolean) => string;
resetMultipliers: () => void;
prestigeAugmentation: () => void;
prestigeSourceFile: () => void;
@ -279,16 +272,12 @@ export class PlayerObject implements IPlayer {
getWorkMoneyGain: () => number;
processWorkEarnings: (cycles: number) => void;
hospitalize: () => void;
createProgramWork: (numCycles: number) => boolean;
checkForFactionInvitations: () => Faction[];
setBitNodeNumber: (n: number) => void;
getMult: (name: string) => number;
setMult: (name: string, mult: number) => void;
canAccessCotMG: () => boolean;
sourceFileLvl: (n: number) => number;
startGraftAugmentationWork: (augmentationName: string, time: number) => void;
graftAugmentationWork: (numCycles: number) => boolean;
finishGraftAugmentationWork: (cancelled: boolean, singularity?: boolean) => string;
applyEntropy: (stacks?: number) => void;
constructor() {
@ -410,14 +399,7 @@ export class PlayerObject implements IPlayer {
this.workRepGained = 0;
this.workMoneyGained = 0;
this.createProgramName = "";
this.createProgramReqLvl = 0;
this.graftAugmentationName = "";
this.timeWorkedGraftAugmentation = 0;
this.timeWorked = 0; //in m;
this.timeWorkedCreateProgram = 0;
this.timeNeededToCompleteWork = 0;
this.work_money_mult = 1;
@ -538,12 +520,6 @@ export class PlayerObject implements IPlayer {
this.getWorkChaExpGain = generalMethods.getWorkChaExpGain;
this.getWorkRepGain = generalMethods.getWorkRepGain;
this.process = generalMethods.process;
this.startCreateProgramWork = generalMethods.startCreateProgramWork;
this.createProgramWork = generalMethods.createProgramWork;
this.finishCreateProgramWork = generalMethods.finishCreateProgramWork;
this.startGraftAugmentationWork = generalMethods.startGraftAugmentationWork;
this.graftAugmentationWork = generalMethods.craftAugmentationWork;
this.finishGraftAugmentationWork = generalMethods.finishGraftAugmentationWork;
this.singularityStopWork = generalMethods.singularityStopWork;
this.takeDamage = generalMethods.takeDamage;
this.regenerateHp = generalMethods.regenerateHp;

@ -66,9 +66,8 @@ import { FactionNames } from "../../Faction/data/FactionNames";
import { ITaskTracker } from "../ITaskTracker";
import { IPerson } from "../IPerson";
import { Player } from "../../Player";
import { graftingIntBonus } from "../Grafting/GraftingHelpers";
import { WorkType, PlayerFactionWorkType, ClassType, CrimeType } from "../../utils/WorkType";
import { WorkType, PlayerFactionWorkType } from "../../utils/WorkType";
export function init(this: IPlayer): void {
/* Initialize Player's home computer */
@ -143,7 +142,6 @@ export function prestigeAugmentation(this: PlayerObject): void {
this.isWorking = false;
this.currentWorkFactionName = "";
this.currentWorkFactionDescription = "";
this.createProgramName = "";
this.workHackExpGainRate = 0;
this.workStrExpGainRate = 0;
@ -537,13 +535,9 @@ export function resetWorkStatus(this: IPlayer, generalType?: WorkType, group?: s
this.workMoneyGained = 0;
this.timeWorked = 0;
this.timeWorkedCreateProgram = 0;
this.timeWorkedGraftAugmentation = 0;
this.currentWorkFactionName = "";
this.currentWorkFactionDescription = "";
this.createProgramName = "";
this.graftAugmentationName = "";
this.workType = WorkType.None;
}
@ -603,18 +597,10 @@ export function process(this: IPlayer, router: IRouter, numCycles = 1): void {
if (this.workForFaction(numCycles)) {
router.toFaction(Factions[this.currentWorkFactionName]);
}
} else if (this.workType === WorkType.CreateProgram) {
if (this.createProgramWork(numCycles)) {
router.toTerminal();
}
} else if (this.workType === WorkType.CompanyPartTime) {
if (this.workPartTime(numCycles)) {
router.toCity();
}
} else if (this.workType === WorkType.GraftAugmentation) {
if (this.graftAugmentationWork(numCycles)) {
router.toGrafting();
}
} else if (this.work(numCycles)) {
router.toCity();
}
@ -1257,143 +1243,6 @@ export function getWorkRepGain(this: IPlayer): number {
// return t * this.faction_rep_mult;
// }
/* Creating a Program */
export function startCreateProgramWork(this: IPlayer, programName: string, time: number, reqLevel: number): void {
this.resetWorkStatus();
this.isWorking = true;
this.workType = WorkType.CreateProgram;
//Time needed to complete work affected by hacking skill (linearly based on
//ratio of (your skill - required level) to MAX skill)
//var timeMultiplier = (CONSTANTS.MaxSkillLevel - (this.hacking - reqLevel)) / CONSTANTS.MaxSkillLevel;
//if (timeMultiplier > 1) {timeMultiplier = 1;}
//if (timeMultiplier < 0.01) {timeMultiplier = 0.01;}
this.createProgramReqLvl = reqLevel;
this.timeNeededToCompleteWork = time;
//Check for incomplete program
for (let i = 0; i < this.getHomeComputer().programs.length; ++i) {
const programFile = this.getHomeComputer().programs[i];
if (programFile.startsWith(programName) && programFile.endsWith("%-INC")) {
const res = programFile.split("-");
if (res.length != 3) {
break;
}
const percComplete = Number(res[1].slice(0, -1));
if (isNaN(percComplete) || percComplete < 0 || percComplete >= 100) {
break;
}
this.timeWorkedCreateProgram = (percComplete / 100) * this.timeNeededToCompleteWork;
this.getHomeComputer().programs.splice(i, 1);
}
}
this.createProgramName = programName;
}
export function createProgramWork(this: IPlayer, numCycles: number): boolean {
let focusBonus = 1;
if (!this.hasAugmentation(AugmentationNames["NeuroreceptorManager"])) {
focusBonus = this.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
//Higher hacking skill will allow you to create programs faster
const reqLvl = this.createProgramReqLvl;
let skillMult = (this.hacking / reqLvl) * this.getIntelligenceBonus(3); //This should always be greater than 1;
skillMult = 1 + (skillMult - 1) / 5; //The divider constant can be adjusted as necessary
skillMult *= focusBonus;
//Skill multiplier directly applied to "time worked"
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.timeWorkedCreateProgram += CONSTANTS._idleSpeed * numCycles * skillMult;
if (this.timeWorkedCreateProgram >= this.timeNeededToCompleteWork) {
this.finishCreateProgramWork(false);
return true;
}
return false;
}
export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): string {
const programName = this.createProgramName;
let message = "";
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."];
dialogBoxCreate(lines.join("<br>"));
message = lines.join(" ");
if (!this.getHomeComputer().programs.includes(programName)) {
this.getHomeComputer().programs.push(programName);
}
} else if (!this.getHomeComputer().programs.includes(programName)) {
//Incomplete case
const perc = (Math.floor((this.timeWorkedCreateProgram / this.timeNeededToCompleteWork) * 10000) / 100).toString();
const incompleteName = programName + "-" + perc + "%-INC";
this.getHomeComputer().programs.push(incompleteName);
message = `Cancelled creating program: ${programName} (${perc}% complete)`;
}
this.isWorking = false;
this.resetWorkStatus();
return message;
}
export function startGraftAugmentationWork(this: IPlayer, augmentationName: string, time: number): void {
this.resetWorkStatus();
this.isWorking = true;
this.workType = WorkType.GraftAugmentation;
this.timeNeededToCompleteWork = time;
this.graftAugmentationName = augmentationName;
}
export function craftAugmentationWork(this: IPlayer, numCycles: number): boolean {
let focusBonus = 1;
if (!this.hasAugmentation(AugmentationNames.NeuroreceptorManager)) {
focusBonus = this.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
let skillMult = graftingIntBonus(this);
skillMult *= focusBonus;
this.timeWorked += CONSTANTS._idleSpeed * numCycles;
this.timeWorkedGraftAugmentation += CONSTANTS._idleSpeed * numCycles * skillMult;
if (this.timeWorkedGraftAugmentation >= this.timeNeededToCompleteWork) {
this.finishGraftAugmentationWork(false);
return true;
}
return false;
}
export function finishGraftAugmentationWork(this: IPlayer, cancelled: boolean, singularity = false): string {
const augName = this.graftAugmentationName;
if (cancelled === false) {
applyAugmentation({ name: augName, level: 1 });
if (!this.hasAugmentation(AugmentationNames.CongruityImplant)) {
this.entropy += 1;
this.applyEntropy(this.entropy);
}
dialogBoxCreate(
`You've finished grafting ${augName}.<br>The augmentation has been applied to your body` +
(this.hasAugmentation(AugmentationNames.CongruityImplant) ? "." : ", but you feel a bit off."),
);
} else if (cancelled && singularity === false) {
dialogBoxCreate(`You cancelled the grafting of ${augName}.<br>Your money was not returned to you.`);
}
// Intelligence gain
if (!cancelled) {
this.gainIntelligenceExp((CONSTANTS.IntelligenceGraftBaseExpGain * this.timeWorked) / 10000);
}
this.isWorking = false;
this.resetWorkStatus();
return `Grafting of ${augName} has ended.`;
}
//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 {
@ -1414,12 +1263,6 @@ export function singularityStopWork(this: IPlayer): string {
case WorkType.Faction:
res = this.finishFactionWork(true, true);
break;
case WorkType.CreateProgram:
res = this.finishCreateProgramWork(true);
break;
case WorkType.GraftAugmentation:
res = this.finishGraftAugmentationWork(true, true);
break;
default:
console.error(`Unrecognized work type (${this.workType})`);
return "";

@ -8,6 +8,7 @@ import { use } from "../../ui/Context";
import { Settings } from "../../Settings/Settings";
import { Programs } from "../Programs";
import { CreateProgramWork } from "../../Work/CreateProgramWork";
export const ProgramsSeen: string[] = [];
@ -96,7 +97,9 @@ export function ProgramsRoot(): React.ReactElement {
sx={{ my: 1, width: "100%" }}
onClick={(event) => {
if (!event.isTrusted) return;
player.startCreateProgramWork(program.name, create.time, create.level);
player.startNEWWork(
new CreateProgramWork({ player: player, singularity: false, programName: program.name }),
);
player.startFocusing();
router.toWork();
}}

@ -199,7 +199,7 @@ class BitburnerSaveObject {
try {
parsedSave = JSON.parse(newSave);
} catch (error) {
console.log(error); // We'll handle below
console.error(error); // We'll handle below
}
if (!parsedSave || parsedSave.ctor !== "BitburnerSaveObject" || !parsedSave.data) {

@ -68,8 +68,6 @@ interface Player {
workChaExpGained: number;
workRepGained: number;
workMoneyGained: number;
createProgramName: string;
createProgramReqLvl: number;
work_money_mult: number;
hacknet_node_money_mult: number;
hacknet_node_purchase_cost_mult: number;

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

@ -126,8 +126,6 @@ interface ClassWorkParams {
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;
@ -135,7 +133,7 @@ export class ClassWork extends Work {
earnings = newWorkStats();
constructor(params?: ClassWorkParams) {
super();
super(WorkType.CLASS);
this.classType = params?.classType ?? ClassType.StudyComputerScience;
this.location = params?.location ?? LocationName.Sector12RothmanUniversity;
this.singularity = params?.singularity ?? false;

@ -0,0 +1,125 @@
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { CONSTANTS } from "../Constants";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Programs } from "../Programs/Programs";
import { Work, WorkType } from "./Work";
import { Program } from "../Programs/Program";
export const isCreateProgramWork = (w: Work | null): w is CreateProgramWork =>
w !== null && w.type === WorkType.CREATE_PROGRAM;
interface CreateProgramWorkParams {
programName: string;
singularity: boolean;
player: IPlayer;
}
export class CreateProgramWork extends Work {
programName: string;
// amount of cycles spent doing this task
cyclesWorked: number;
singularity: boolean;
// amount of effective work completed on the program (time boosted by skills).
unitCompleted: number;
constructor(params?: CreateProgramWorkParams) {
super(WorkType.CREATE_PROGRAM);
this.cyclesWorked = 0;
this.unitCompleted = 0;
this.programName = params?.programName ?? "";
this.singularity = params?.singularity ?? false;
if (params?.player) {
const player = params.player;
for (let i = 0; i < player.getHomeComputer().programs.length; ++i) {
const programFile = player.getHomeComputer().programs[i];
if (programFile.startsWith(this.programName) && programFile.endsWith("%-INC")) {
const res = programFile.split("-");
if (res.length != 3) {
break;
}
const percComplete = Number(res[1].slice(0, -1));
if (isNaN(percComplete) || percComplete < 0 || percComplete >= 100) {
break;
}
this.unitCompleted = (percComplete / 100) * this.unitNeeded();
player.getHomeComputer().programs.splice(i, 1);
}
}
}
}
unitNeeded(): number {
return this.getProgram().create?.time ?? 0;
}
getProgram(): Program {
const p = Object.values(Programs).find((p) => p.name.toLowerCase() === this.programName.toLowerCase());
if (!p) throw new Error("Create program work started with invalid program " + this.programName);
return p;
}
process(player: IPlayer, cycles: number): boolean {
let focusBonus = 1;
if (!player.hasAugmentation(AugmentationNames["NeuroreceptorManager"])) {
focusBonus = player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
//Higher hacking skill will allow you to create programs faster
const reqLvl = this.getProgram().create?.level ?? 0;
let skillMult = (player.hacking / reqLvl) * player.getIntelligenceBonus(3); //This should always be greater than 1;
skillMult = 1 + (skillMult - 1) / 5; //The divider constant can be adjusted as necessary
skillMult *= focusBonus;
//Skill multiplier directly applied to "time worked"
this.cyclesWorked += cycles;
this.unitCompleted += CONSTANTS._idleSpeed * cycles * skillMult;
if (this.unitCompleted >= this.unitNeeded()) {
return true;
}
return false;
}
finish(player: IPlayer, cancelled: boolean): void {
const programName = this.programName;
if (!cancelled) {
//Complete case
player.gainIntelligenceExp(
(CONSTANTS.IntelligenceProgramBaseExpGain * this.cyclesWorked * CONSTANTS._idleSpeed) / 1000,
);
if (!this.singularity) {
const lines = [
`You've finished creating ${programName}!`,
"The new program can be found on your home computer.",
];
dialogBoxCreate(lines.join("<br>"));
}
if (!player.getHomeComputer().programs.includes(programName)) {
player.getHomeComputer().programs.push(programName);
}
} else if (!player.getHomeComputer().programs.includes(programName)) {
//Incomplete case
const perc = ((100 * this.unitCompleted) / this.unitNeeded()).toFixed(2);
const incompleteName = programName + "-" + perc + "%-INC";
player.getHomeComputer().programs.push(incompleteName);
}
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): any {
return Generic_toJSON("CreateProgramWork", this);
}
/**
* Initiatizes a CreateProgramWork object from a JSON save state.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromJSON(value: any): CreateProgramWork {
return Generic_fromJSON(CreateProgramWork, value.data);
}
}
Reviver.constructors.CreateProgramWork = CreateProgramWork;

@ -19,14 +19,12 @@ interface CrimeWorkParams {
export const isCrimeWork = (w: Work | null): w is CrimeWork => w !== null && w.type === WorkType.CRIME;
export class CrimeWork extends Work {
type = WorkType.CRIME;
crimeType: CrimeType;
cyclesWorked: number;
singularity: boolean;
constructor(params?: CrimeWorkParams) {
super();
super(WorkType.CRIME);
this.crimeType = params?.crimeType ?? CrimeType.Shoplift;
this.singularity = params?.singularity ?? false;
this.cyclesWorked = 0;

106
src/Work/GraftingWork.tsx Normal file

@ -0,0 +1,106 @@
import React from "react";
import { CONSTANTS } from "../Constants";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
import { GraftableAugmentations } from "../PersonObjects/Grafting/ui/GraftingRoot";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Work, WorkType } from "./Work";
import { graftingIntBonus } from "../PersonObjects/Grafting/GraftingHelpers";
import { applyAugmentation } from "../Augmentation/AugmentationHelpers";
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver";
import { GraftableAugmentation } from "../PersonObjects/Grafting/GraftableAugmentation";
import { StaticAugmentations } from "../Augmentation/StaticAugmentations";
export const isGraftingWork = (w: Work | null): w is GraftingWork => w !== null && w.type === WorkType.GRAFTING;
interface GraftingWorkParams {
augmentation: string;
singularity: boolean;
player: IPlayer;
}
export class GraftingWork extends Work {
augmentation: string;
singularity: boolean;
cyclesWorked: number;
unitCompleted: number;
constructor(params?: GraftingWorkParams) {
super(WorkType.GRAFTING);
this.cyclesWorked = 0;
this.unitCompleted = 0;
this.augmentation = params?.augmentation ?? AugmentationNames.Targeting1;
this.singularity = params?.singularity ?? false;
if (params?.player) params.player.loseMoney(GraftableAugmentations[this.augmentation].cost, "augmentations");
}
unitNeeded(): number {
return new GraftableAugmentation(StaticAugmentations[this.augmentation]).time;
}
process(player: IPlayer, cycles: number): boolean {
let focusBonus = 1;
if (!player.hasAugmentation(AugmentationNames.NeuroreceptorManager)) {
focusBonus = player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
this.cyclesWorked += cycles;
this.unitCompleted += CONSTANTS._idleSpeed * cycles * graftingIntBonus(player) * focusBonus;
return this.unitCompleted >= this.unitNeeded();
}
finish(player: IPlayer, cancelled: boolean): void {
const augName = this.augmentation;
if (!cancelled) {
applyAugmentation({ name: augName, level: 1 });
if (!player.hasAugmentation(AugmentationNames.CongruityImplant)) {
player.entropy += 1;
player.applyEntropy(player.entropy);
}
if (!this.singularity) {
dialogBoxCreate(
<>
You've finished grafting {augName}.<br />
The augmentation has been applied to your body{" "}
{player.hasAugmentation(AugmentationNames.CongruityImplant) ? "." : ", but you feel a bit off."}
</>,
);
}
} else if (cancelled && !this.singularity) {
dialogBoxCreate(
<>
You cancelled the grafting of {augName}.
<br />
Your money was not returned to you.
</>,
);
}
// Intelligence gain
if (!cancelled) {
player.gainIntelligenceExp(
(CONSTANTS.IntelligenceGraftBaseExpGain * this.cyclesWorked * CONSTANTS._idleSpeed) / 10000,
);
}
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): any {
return Generic_toJSON("GraftingWork", this);
}
/**
* Initiatizes a GraftingWork object from a JSON save state.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromJSON(value: any): GraftingWork {
return Generic_fromJSON(GraftingWork, value.data);
}
}
Reviver.constructors.GraftingWork = GraftingWork;

@ -1,13 +1,20 @@
import { IPlayer } from "src/PersonObjects/IPlayer";
export abstract class Work {
abstract type: WorkType;
type: WorkType;
constructor(type: WorkType) {
this.type = type;
}
abstract process(player: IPlayer, cycles: number): boolean;
abstract finish(player: IPlayer, cancelled: boolean): void;
abstract toJSON(): any;
}
export enum WorkType {
CRIME = "CRIME",
CLASS = "CLASS",
CREATE_PROGRAM = "CREATE_PROGRAM",
GRAFTING = "GRAFTING",
}

@ -306,15 +306,9 @@ const Engine: {
case WorkType.Faction:
Player.workForFaction(numCyclesOffline);
break;
case WorkType.CreateProgram:
Player.createProgramWork(numCyclesOffline);
break;
case WorkType.CompanyPartTime:
Player.workPartTime(numCyclesOffline);
break;
case WorkType.GraftAugmentation:
Player.graftAugmentationWork(numCyclesOffline);
break;
default:
Player.work(numCyclesOffline);
}

@ -294,7 +294,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
Router[fnName] = _wrap(Router[fnName], (func, ...args) => {
if (!allowRoutingCalls) {
// Let's just log to console.
console.log(`Routing is currently disabled - Attempted router.${fnName}()`);
console.error(`Routing is currently disabled - Attempted router.${fnName}()`);
return;
}

@ -24,7 +24,6 @@ export function AlertManager(): React.ReactElement {
setAlerts((old) => {
const hash = getMessageHash(text);
if (old.some((a) => a.hash === hash)) {
console.log("Duplicate message");
return old;
}
return [

@ -29,6 +29,8 @@ 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";
interface IProps {
save: () => void;
@ -150,6 +152,26 @@ function Work(): React.ReactElement {
header = <>You are {player.currentWork.getClass().youAreCurrently}</>;
innerText = <>{convertTimeMsToTimeElapsedString(player.currentWork.cyclesWorked * CONSTANTS._idleSpeed)}</>;
}
if (isCreateProgramWork(player.currentWork)) {
const create = player.currentWork;
details = <>Coding {create.programName}</>;
header = <>Creating a program</>;
innerText = (
<>
{create.programName} {((create.unitCompleted / create.unitNeeded()) * 100).toFixed(2)}%
</>
);
}
if (isGraftingWork(player.currentWork)) {
const graft = player.currentWork;
details = <>Grafting {graft.augmentation}</>;
header = <>Grafting an Augmentation</>;
innerText = (
<>
<strong>{((graft.unitCompleted / graft.unitNeeded()) * 100).toFixed(2)}%</strong> done
</>
);
}
switch (player.workType) {
case WorkType.CompanyPartTime:
case WorkType.Company:
@ -186,25 +208,6 @@ function Work(): React.ReactElement {
</>
);
break;
case WorkType.CreateProgram:
details = <>Coding {player.createProgramName}</>;
header = <>Creating a program</>;
innerText = (
<>
{player.createProgramName}{" "}
{((player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100).toFixed(2)}%
</>
);
break;
case WorkType.GraftAugmentation:
details = <>Grafting {player.graftAugmentationName}</>;
header = <>Grafting an Augmentation</>;
innerText = (
<>
<strong>{((player.timeWorkedGraftAugmentation / player.timeNeededToCompleteWork) * 100).toFixed(2)}%</strong>{" "}
done
</>
);
}
return (

@ -22,7 +22,9 @@ 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";
import { WorkStats } from "../Work/WorkStats";
import { isCreateProgramWork } from "../Work/CreateProgramWork";
import { isGraftingWork } from "../Work/GraftingWork";
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
@ -301,6 +303,76 @@ export function WorkInProgressRoot(): React.ReactElement {
stopText: stopText,
};
}
if (isCreateProgramWork(player.currentWork)) {
const create = player.currentWork;
function cancel(): void {
player.finishNEWWork(true);
router.toTerminal();
}
function unfocus(): void {
router.toTerminal();
player.stopFocusing();
}
const completion = (create.unitCompleted / create.unitNeeded()) * 100;
workInfo = {
buttons: {
cancel: cancel,
unfocus: unfocus,
},
title: (
<>
You are currently working on coding <b>{create.programName}</b>
</>
),
progress: {
elapsed: create.cyclesWorked * CONSTANTS._idleSpeed,
percentage: completion,
},
stopText: "Stop creating program",
stopTooltip: "Your work will be saved and you can return to complete the program later.",
};
}
if (isGraftingWork(player.currentWork)) {
const graft = player.currentWork;
function cancel(): void {
player.finishNEWWork(true);
router.toTerminal();
}
function unfocus(): void {
router.toTerminal();
player.stopFocusing();
}
workInfo = {
buttons: {
cancel: cancel,
unfocus: unfocus,
},
title: (
<>
You are currently working on grafting <b>{graft.augmentation}</b>
</>
),
progress: {
elapsed: graft.cyclesWorked * CONSTANTS._idleSpeed,
percentage: (graft.unitCompleted / graft.unitNeeded()) * 100,
},
stopText: "Stop grafting",
stopTooltip: (
<>
If you cancel, your work will <b>not</b> be saved, and the money you spent will <b>not</b> be returned
</>
),
};
}
}
switch (player.workType) {
@ -509,80 +581,6 @@ export function WorkInProgressRoot(): React.ReactElement {
break;
}
case WorkType.CreateProgram: {
function cancel(): void {
player.finishCreateProgramWork(true);
router.toTerminal();
}
function unfocus(): void {
router.toTerminal();
player.stopFocusing();
}
const completion = (player.timeWorkedCreateProgram / player.timeNeededToCompleteWork) * 100;
workInfo = {
buttons: {
cancel: cancel,
unfocus: unfocus,
},
title: (
<>
You are currently working on coding <b>{player.createProgramName}</b>
</>
),
progress: {
elapsed: player.timeWorked,
percentage: completion,
},
stopText: "Stop creating program",
stopTooltip: "Your work will be saved and you can return to complete the program later.",
};
break;
}
case WorkType.GraftAugmentation: {
function cancel(): void {
player.finishGraftAugmentationWork(true);
router.toTerminal();
}
function unfocus(): void {
router.toTerminal();
player.stopFocusing();
}
const completion = (player.timeWorkedGraftAugmentation / player.timeNeededToCompleteWork) * 100;
workInfo = {
buttons: {
cancel: cancel,
unfocus: unfocus,
},
title: (
<>
You are currently working on grafting <b>{player.graftAugmentationName}</b>
</>
),
progress: {
elapsed: player.timeWorked,
percentage: completion,
},
stopText: "Stop grafting",
stopTooltip: (
<>
If you cancel, your work will <b>not</b> be saved, and the money you spent will <b>not</b> be returned
</>
),
};
break;
}
default:
if (player.currentWork === null) {
router.toTerminal();