diff --git a/src/Crime/Crime.ts b/src/Crime/Crime.ts index 08fdb8837..ceee8d4f6 100644 --- a/src/Crime/Crime.ts +++ b/src/Crime/Crime.ts @@ -25,22 +25,25 @@ interface IConstructorParams { export class Crime { // Number representing the difficulty of the crime. Used for success chance calculations - difficulty = 0; + difficulty: number; // Amount of karma lost for SUCCESSFULLY committing this crime - karma = 0; + karma: number; // How many people die as a result of this crime - kills = 0; + kills: number; // How much money is given by the - money = 0; + money: number; // Name of crime - name = ""; + name: string; // Name of crime as it appears on work screen: "You are attempting..." - workName = ""; + workName: string; + + // Tooltip text in slums ui + tooltipText: string; // Milliseconds it takes to attempt the crime time = 0; @@ -66,17 +69,19 @@ export class Crime { intelligence_exp = 0; constructor( - name = "", - workName = "", + name: string, + workName: string, + tooltipText: string, type: CrimeType, - time = 0, - money = 0, - difficulty = 0, - karma = 0, - params: IConstructorParams = {}, + time: number, + money: number, + difficulty: number, + karma: number, + params: IConstructorParams, ) { this.name = name; this.workName = workName; + this.tooltipText = tooltipText; this.type = type; this.time = time; this.money = money; diff --git a/src/Crime/CrimeHelpers.ts b/src/Crime/CrimeHelpers.ts index 4f50ffa1b..be65ddb51 100644 --- a/src/Crime/CrimeHelpers.ts +++ b/src/Crime/CrimeHelpers.ts @@ -3,53 +3,46 @@ import { Crime } from "./Crime"; import { Player } from "@player"; import { dialogBoxCreate } from "../ui/React/DialogBox"; +import { checkEnum } from "../utils/helpers/checkEnum"; +import { CrimeType } from "../utils/WorkType"; //This is only used for the player export function determineCrimeSuccess(type: string): boolean { - let chance = 0; - let found = false; - for (const i of Object.keys(Crimes)) { - const crime = Crimes[i]; - if (crime.type === type) { - chance = crime.successRate(Player); - found = true; - break; - } - } - - if (!found) { + if (!checkEnum(CrimeType, type)) { dialogBoxCreate(`ERR: Unrecognized crime type: ${type} This is probably a bug please contact the developer`); return false; } + const crime = Crimes[type]; + const chance = crime.successRate(Player); return Math.random() <= chance; } export function findCrime(roughName: string): Crime | null { roughName = roughName.toLowerCase(); if (roughName.includes("shoplift")) { - return Crimes.Shoplift; + return Crimes[CrimeType.SHOPLIFT]; } else if (roughName.includes("rob") && roughName.includes("store")) { - return Crimes.RobStore; + return Crimes[CrimeType.ROB_STORE]; } else if (roughName.includes("mug")) { - return Crimes.Mug; + return Crimes[CrimeType.MUG]; } else if (roughName.includes("larceny")) { - return Crimes.Larceny; + return Crimes[CrimeType.LARCENY]; } else if (roughName.includes("drugs")) { - return Crimes.DealDrugs; + return Crimes[CrimeType.DRUGS]; } else if (roughName.includes("bond") && roughName.includes("forge")) { - return Crimes.BondForgery; + return Crimes[CrimeType.BOND_FORGERY]; } else if ((roughName.includes("traffic") || roughName.includes("illegal")) && roughName.includes("arms")) { - return Crimes.TraffickArms; + return Crimes[CrimeType.TRAFFIC_ARMS]; } else if (roughName.includes("homicide")) { - return Crimes.Homicide; + return Crimes[CrimeType.HOMICIDE]; } else if (roughName.includes("grand") && roughName.includes("auto")) { - return Crimes.GrandTheftAuto; + return Crimes[CrimeType.GRAND_THEFT_AUTO]; } else if (roughName.includes("kidnap")) { - return Crimes.Kidnap; + return Crimes[CrimeType.KIDNAP]; } else if (roughName.includes("assassin")) { - return Crimes.Assassination; + return Crimes[CrimeType.ASSASSINATION]; } else if (roughName.includes("heist")) { - return Crimes.Heist; + return Crimes[CrimeType.HEIST]; } return null; diff --git a/src/Crime/Crimes.ts b/src/Crime/Crimes.ts index b3cf29060..f41b161b8 100644 --- a/src/Crime/Crimes.ts +++ b/src/Crime/Crimes.ts @@ -3,162 +3,282 @@ import { Crime } from "./Crime"; import { CONSTANTS } from "../Constants"; import { CrimeType } from "../utils/WorkType"; +// TODO: What is the point of CrimeType using totally different strings than +export const Crimes: Record = { + [CrimeType.SHOPLIFT]: new Crime( + "Shoplift", + "to shoplift", + "Attempt to shoplift from a low-end retailer", + CrimeType.SHOPLIFT, + 2e3, + 15e3, + 1 / 20, + 0.1, + { + dexterity_success_weight: 1, + agility_success_weight: 1, -export const Crimes: Record = { - Shoplift: new Crime("Shoplift", "to shoplift", CrimeType.SHOPLIFT, 2e3, 15e3, 1 / 20, 0.1, { - dexterity_success_weight: 1, - agility_success_weight: 1, + dexterity_exp: 2, + agility_exp: 2, + }, + ), - dexterity_exp: 2, - agility_exp: 2, - }), + [CrimeType.ROB_STORE]: new Crime( + "Rob Store", + "to rob a store", + "Attempt to commit armed robbery on a high-end store", + CrimeType.ROB_STORE, + 60e3, + 400e3, + 1 / 5, + 0.5, + { + hacking_exp: 30, + dexterity_exp: 45, + agility_exp: 45, - RobStore: new Crime("Rob Store", "to rob a store", CrimeType.ROB_STORE, 60e3, 400e3, 1 / 5, 0.5, { - hacking_exp: 30, - dexterity_exp: 45, - agility_exp: 45, + hacking_success_weight: 0.5, + dexterity_success_weight: 2, + agility_success_weight: 1, - hacking_success_weight: 0.5, - dexterity_success_weight: 2, - agility_success_weight: 1, + intelligence_exp: 7.5 * CONSTANTS.IntelligenceCrimeBaseExpGain, + }, + ), - intelligence_exp: 7.5 * CONSTANTS.IntelligenceCrimeBaseExpGain, - }), + [CrimeType.MUG]: new Crime( + "Mug", + "to mug", + "Attempt to mug a random person on the street", + CrimeType.MUG, + 4e3, + 36e3, + 1 / 5, + 0.25, + { + strength_exp: 3, + defense_exp: 3, + dexterity_exp: 3, + agility_exp: 3, - Mug: new Crime("Mug", "to mug", CrimeType.MUG, 4e3, 36e3, 1 / 5, 0.25, { - strength_exp: 3, - defense_exp: 3, - dexterity_exp: 3, - agility_exp: 3, + strength_success_weight: 1.5, + defense_success_weight: 0.5, + dexterity_success_weight: 1.5, + agility_success_weight: 0.5, + }, + ), - strength_success_weight: 1.5, - defense_success_weight: 0.5, - dexterity_success_weight: 1.5, - agility_success_weight: 0.5, - }), + [CrimeType.LARCENY]: new Crime( + "Larceny", + "larceny", + "Attempt to rob property from someone's house", + CrimeType.LARCENY, + 90e3, + 800e3, + 1 / 3, + 1.5, + { + hacking_exp: 45, + dexterity_exp: 60, + agility_exp: 60, - Larceny: new Crime("Larceny", "larceny", CrimeType.LARCENY, 90e3, 800e3, 1 / 3, 1.5, { - hacking_exp: 45, - dexterity_exp: 60, - agility_exp: 60, + hacking_success_weight: 0.5, + dexterity_success_weight: 1, + agility_success_weight: 1, - hacking_success_weight: 0.5, - dexterity_success_weight: 1, - agility_success_weight: 1, + intelligence_exp: 15 * CONSTANTS.IntelligenceCrimeBaseExpGain, + }, + ), - intelligence_exp: 15 * CONSTANTS.IntelligenceCrimeBaseExpGain, - }), + [CrimeType.DRUGS]: new Crime( + "Deal Drugs", + "to deal drugs", + "Attempt to deal drugs", + CrimeType.DRUGS, + 10e3, + 120e3, + 1, + 0.5, + { + dexterity_exp: 5, + agility_exp: 5, + charisma_exp: 10, - DealDrugs: new Crime("Deal Drugs", "to deal drugs", CrimeType.DRUGS, 10e3, 120e3, 1, 0.5, { - dexterity_exp: 5, - agility_exp: 5, - charisma_exp: 10, + charisma_success_weight: 3, + dexterity_success_weight: 2, + agility_success_weight: 1, + }, + ), - charisma_success_weight: 3, - dexterity_success_weight: 2, - agility_success_weight: 1, - }), + [CrimeType.BOND_FORGERY]: new Crime( + "Bond Forgery", + "to forge bonds", + "Attempt to forge corporate bonds", + CrimeType.BOND_FORGERY, + 300e3, + 4.5e6, + 1 / 2, + 0.1, + { + hacking_exp: 100, + dexterity_exp: 150, + charisma_exp: 15, - BondForgery: new Crime("Bond Forgery", "to forge bonds", CrimeType.BOND_FORGERY, 300e3, 4.5e6, 1 / 2, 0.1, { - hacking_exp: 100, - dexterity_exp: 150, - charisma_exp: 15, + hacking_success_weight: 0.05, + dexterity_success_weight: 1.25, - hacking_success_weight: 0.05, - dexterity_success_weight: 1.25, + intelligence_exp: 60 * CONSTANTS.IntelligenceCrimeBaseExpGain, + }, + ), - intelligence_exp: 60 * CONSTANTS.IntelligenceCrimeBaseExpGain, - }), + [CrimeType.TRAFFIC_ARMS]: new Crime( + "Traffick Arms", + "to traffic arms", + "Attempt to smuggle illegal arms into the city", + CrimeType.TRAFFIC_ARMS, + 40e3, + 600e3, + 2, + 1, + { + strength_exp: 20, + defense_exp: 20, + dexterity_exp: 20, + agility_exp: 20, + charisma_exp: 40, - TraffickArms: new Crime("Traffick Arms", "to traffic arms", CrimeType.TRAFFIC_ARMS, 40e3, 600e3, 2, 1, { - strength_exp: 20, - defense_exp: 20, - dexterity_exp: 20, - agility_exp: 20, - charisma_exp: 40, + charisma_success_weight: 1, + strength_success_weight: 1, + defense_success_weight: 1, + dexterity_success_weight: 1, + agility_success_weight: 1, + }, + ), - charisma_success_weight: 1, - strength_success_weight: 1, - defense_success_weight: 1, - dexterity_success_weight: 1, - agility_success_weight: 1, - }), + [CrimeType.HOMICIDE]: new Crime( + "Homicide", + "homicide", + "Attempt to murder a random person on the street", + CrimeType.HOMICIDE, + 3e3, + 45e3, + 1, + 3, + { + strength_exp: 2, + defense_exp: 2, + dexterity_exp: 2, + agility_exp: 2, - Homicide: new Crime("Homicide", "homicide", CrimeType.HOMICIDE, 3e3, 45e3, 1, 3, { - strength_exp: 2, - defense_exp: 2, - dexterity_exp: 2, - agility_exp: 2, + strength_success_weight: 2, + defense_success_weight: 2, + dexterity_success_weight: 0.5, + agility_success_weight: 0.5, - strength_success_weight: 2, - defense_success_weight: 2, - dexterity_success_weight: 0.5, - agility_success_weight: 0.5, + kills: 1, + }, + ), - kills: 1, - }), + [CrimeType.GRAND_THEFT_AUTO]: new Crime( + "Grand Theft Auto", + "grand theft auto", + "Attempt to commit grand theft auto", + CrimeType.GRAND_THEFT_AUTO, + 80e3, + 1.6e6, + 8, + 5, + { + strength_exp: 20, + defense_exp: 20, + dexterity_exp: 20, + agility_exp: 80, + charisma_exp: 40, - GrandTheftAuto: new Crime("Grand Theft Auto", "grand theft auto", CrimeType.GRAND_THEFT_AUTO, 80e3, 1.6e6, 8, 5, { - strength_exp: 20, - defense_exp: 20, - dexterity_exp: 20, - agility_exp: 80, - charisma_exp: 40, + hacking_success_weight: 1, + strength_success_weight: 1, + dexterity_success_weight: 4, + agility_success_weight: 2, + charisma_success_weight: 2, - hacking_success_weight: 1, - strength_success_weight: 1, - dexterity_success_weight: 4, - agility_success_weight: 2, - charisma_success_weight: 2, + intelligence_exp: 16 * CONSTANTS.IntelligenceCrimeBaseExpGain, + }, + ), - intelligence_exp: 16 * CONSTANTS.IntelligenceCrimeBaseExpGain, - }), + [CrimeType.KIDNAP]: new Crime( + "Kidnap", + "to kidnap", + "Attempt to kidnap and ransom a high-profile-target", + CrimeType.KIDNAP, + 120e3, + 3.6e6, + 5, + 6, + { + strength_exp: 80, + defense_exp: 80, + dexterity_exp: 80, + agility_exp: 80, + charisma_exp: 80, - Kidnap: new Crime("Kidnap", "to kidnap", CrimeType.KIDNAP, 120e3, 3.6e6, 5, 6, { - strength_exp: 80, - defense_exp: 80, - dexterity_exp: 80, - agility_exp: 80, - charisma_exp: 80, + charisma_success_weight: 1, + strength_success_weight: 1, + dexterity_success_weight: 1, + agility_success_weight: 1, - charisma_success_weight: 1, - strength_success_weight: 1, - dexterity_success_weight: 1, - agility_success_weight: 1, + intelligence_exp: 26 * CONSTANTS.IntelligenceCrimeBaseExpGain, + }, + ), - intelligence_exp: 26 * CONSTANTS.IntelligenceCrimeBaseExpGain, - }), + [CrimeType.ASSASSINATION]: new Crime( + "Assassination", + "to assassinate", + "Attempt to assassinate a high-profile target", + CrimeType.ASSASSINATION, + 300e3, + 12e6, + 8, + 10, + { + strength_exp: 300, + defense_exp: 300, + dexterity_exp: 300, + agility_exp: 300, - Assassination: new Crime("Assassination", "to assassinate", CrimeType.ASSASSINATION, 300e3, 12e6, 8, 10, { - strength_exp: 300, - defense_exp: 300, - dexterity_exp: 300, - agility_exp: 300, + strength_success_weight: 1, + dexterity_success_weight: 2, + agility_success_weight: 1, - strength_success_weight: 1, - dexterity_success_weight: 2, - agility_success_weight: 1, + intelligence_exp: 65 * CONSTANTS.IntelligenceCrimeBaseExpGain, - intelligence_exp: 65 * CONSTANTS.IntelligenceCrimeBaseExpGain, + kills: 1, + }, + ), - kills: 1, - }), + [CrimeType.HEIST]: new Crime( + "Heist", + "a heist", + "Attempt to pull off the ultimate heist", + CrimeType.HEIST, + 600e3, + 120e6, + 18, + 15, + { + hacking_exp: 450, + strength_exp: 450, + defense_exp: 450, + dexterity_exp: 450, + agility_exp: 450, + charisma_exp: 450, - Heist: new Crime("Heist", "a heist", CrimeType.HEIST, 600e3, 120e6, 18, 15, { - hacking_exp: 450, - strength_exp: 450, - defense_exp: 450, - dexterity_exp: 450, - agility_exp: 450, - charisma_exp: 450, + hacking_success_weight: 1, + strength_success_weight: 1, + defense_success_weight: 1, + dexterity_success_weight: 1, + agility_success_weight: 1, + charisma_success_weight: 1, - hacking_success_weight: 1, - strength_success_weight: 1, - defense_success_weight: 1, - dexterity_success_weight: 1, - agility_success_weight: 1, - charisma_success_weight: 1, - - intelligence_exp: 130 * CONSTANTS.IntelligenceCrimeBaseExpGain, - }), + intelligence_exp: 130 * CONSTANTS.IntelligenceCrimeBaseExpGain, + }, + ), }; diff --git a/src/Locations/ui/SlumsLocation.tsx b/src/Locations/ui/SlumsLocation.tsx index 52aa9ea2f..fad038f5a 100644 --- a/src/Locations/ui/SlumsLocation.tsx +++ b/src/Locations/ui/SlumsLocation.tsx @@ -3,7 +3,7 @@ * * This subcomponent renders all of the buttons for committing crimes */ -import * as React from "react"; +import React, { useState, useEffect } from "react"; import Button from "@mui/material/Button"; import Tooltip from "@mui/material/Tooltip"; @@ -13,185 +13,33 @@ import { numeralWrapper } from "../../ui/numeralFormat"; import { Router } from "../../ui/GameRoot"; import { Player } from "@player"; import { Box } from "@mui/material"; +import { Crime } from "../../Crime/Crime"; export function SlumsLocation(): React.ReactElement { - function shoplift(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.Shoplift.commit(); + const setRerender = useState(false)[1]; + const rerender = () => setRerender((o) => !o); + const crimes = Object.values(Crimes); + useEffect(() => { + const timerId = setInterval(() => rerender(), 1000); + return () => clearInterval(timerId); + }); + + function doCrime(e: React.MouseEvent, crime: Crime) { + if (!e.isTrusted) return; + crime.commit(); Router.toWork(); Player.focus = true; } - function robStore(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.RobStore.commit(); - Router.toWork(); - Player.focus = true; - } - - function mug(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.Mug.commit(); - Router.toWork(); - Player.focus = true; - } - - function larceny(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.Larceny.commit(); - Router.toWork(); - Player.focus = true; - } - - function dealDrugs(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.DealDrugs.commit(); - Router.toWork(); - Player.focus = true; - } - - function bondForgery(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.BondForgery.commit(); - Router.toWork(); - Player.focus = true; - } - - function traffickArms(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.TraffickArms.commit(); - Router.toWork(); - Player.focus = true; - } - - function homicide(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.Homicide.commit(); - Router.toWork(); - Player.focus = true; - } - - function grandTheftAuto(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.GrandTheftAuto.commit(); - Router.toWork(); - Player.focus = true; - } - - function kidnap(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.Kidnap.commit(); - Router.toWork(); - Player.focus = true; - } - - function assassinate(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.Assassination.commit(); - Router.toWork(); - Player.focus = true; - } - - function heist(e: React.MouseEvent): void { - if (!e.isTrusted) { - return; - } - Crimes.Heist.commit(); - Router.toWork(); - Player.focus = true; - } - - const shopliftChance = Crimes.Shoplift.successRate(Player); - const robStoreChance = Crimes.RobStore.successRate(Player); - const mugChance = Crimes.Mug.successRate(Player); - const larcenyChance = Crimes.Larceny.successRate(Player); - const drugsChance = Crimes.DealDrugs.successRate(Player); - const bondChance = Crimes.BondForgery.successRate(Player); - const armsChance = Crimes.TraffickArms.successRate(Player); - const homicideChance = Crimes.Homicide.successRate(Player); - const gtaChance = Crimes.GrandTheftAuto.successRate(Player); - const kidnapChance = Crimes.Kidnap.successRate(Player); - const assassinateChance = Crimes.Assassination.successRate(Player); - const heistChance = Crimes.Heist.successRate(Player); - return ( - Attempt to shoplift from a low-end retailer}> - - - Attempt to commit armed robbery on a high-end store}> - - - Attempt to mug a random person on the street}> - - - Attempt to rob property from someone's house}> - - - Attempt to deal drugs}> - - - Attempt to forge corporate bonds}> - - - Attempt to smuggle illegal arms into the city}> - - - Attempt to murder a random person on the street}> - - - Attempt to commit grand theft auto}> - - - Attempt to kidnap and ransom a high-profile-target}> - - - Attempt to assassinate a high-profile target}> - - - Attempt to pull off the ultimate heist}> - - + {crimes.map((crime) => ( + + + + ))} ); } diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 7d0b175fd..a4a4ca028 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -77,11 +77,12 @@ import { INetscriptExtra } from "./NetscriptFunctions/Extra"; import { ScriptDeath } from "./Netscript/ScriptDeath"; import { getBitNodeMultipliers } from "./BitNode/BitNode"; import { assert, arrayAssert, stringAssert, objectAssert } from "./utils/helpers/typeAssertion"; +import { CrimeType } from "./utils/WorkType"; -// "Enums" as object export const enums = { toast: ToastVariant, -} as const; + crimes: CrimeType, +}; export type NSFull = Readonly; export function NetscriptFunctions(workerScript: WorkerScript): NSFull { @@ -125,6 +126,7 @@ const base: InternalAPI = { (ctx) => (_hostname, opts = {}) => { const hostname = helpers.string(ctx, "hostname", _hostname); + // Todo: better type safety rework for functions using assertObjectType, then remove function. const optsValidator: BasicHGWOptions = {}; assertObjectType(ctx, "opts", opts, optsValidator); return helpers.hack(ctx, hostname, false, { threads: opts.threads, stock: opts.stock }); diff --git a/src/NetscriptFunctions/Formulas.ts b/src/NetscriptFunctions/Formulas.ts index aa4f621f9..0ebfd14a9 100644 --- a/src/NetscriptFunctions/Formulas.ts +++ b/src/NetscriptFunctions/Formulas.ts @@ -48,6 +48,8 @@ import { calculateFactionExp, calculateFactionRep } from "../Work/formulas/Facti import { FactionWorkType } from "../Work/data/FactionWorkType"; import { defaultMultipliers } from "../PersonObjects/Multipliers"; +import { checkEnum } from "../utils/helpers/checkEnum"; +import { CrimeType } from "../utils/WorkType"; export function NetscriptFormulas(): InternalAPI { const checkFormulasAccess = function (ctx: NetscriptContext): void { @@ -362,9 +364,8 @@ export function NetscriptFormulas(): InternalAPI { work: { crimeGains: (ctx) => (_crimeType) => { const crimeType = helpers.string(ctx, "crimeType", _crimeType); - const crime = Object.values(Crimes).find((c) => String(c.type) === crimeType); - if (!crime) throw new Error(`Invalid crime type: ${crimeType}`); - return calculateCrimeWorkStats(crime); + if (!checkEnum(CrimeType, crimeType)) throw new Error(`Invalid crime type: ${crimeType}`); + return calculateCrimeWorkStats(Crimes[crimeType]); }, classGains: (ctx) => (_person, _classType, _locationName) => { const person = helpers.player(ctx, _person); diff --git a/src/NetscriptFunctions/Singularity.ts b/src/NetscriptFunctions/Singularity.ts index bec8caee8..c707e192b 100644 --- a/src/NetscriptFunctions/Singularity.ts +++ b/src/NetscriptFunctions/Singularity.ts @@ -50,6 +50,9 @@ import { CompanyWork } from "../Work/CompanyWork"; import { canGetBonus, onExport } from "../ExportBonus"; import { saveObject } from "../SaveObject"; import { calculateCrimeWorkStats } from "../Work/formulas/Crime"; +import { checkEnum } from "../utils/helpers/checkEnum"; +import { Crimes } from "../Crime/Crimes"; +import { CrimeType } from "../utils/WorkType"; export function NetscriptSingularity(): InternalAPI { const getAugmentation = function (ctx: NetscriptContext, name: string): Augmentation { @@ -1115,56 +1118,47 @@ export function NetscriptSingularity(): InternalAPI { helpers.log(ctx, () => `Began creating program: '${programName}'`); return true; }, - commitCrime: - (ctx) => - (_crimeRoughName, _focus = true) => { - helpers.checkSingularityAccess(ctx); - const crimeRoughName = helpers.string(ctx, "crimeRoughName", _crimeRoughName); - const focus = !!_focus; - const wasFocusing = Player.focus; - - if (Player.currentWork !== null) { - Player.finishWork(true); - } - - // Set Location to slums - Player.gotoLocation(LocationName.Slums); - - const crime = findCrime(crimeRoughName.toLowerCase()); - if (crime == null) { - // couldn't find crime - throw helpers.makeRuntimeErrorMsg(ctx, `Invalid crime: '${crimeRoughName}'`); - } - helpers.log(ctx, () => `Attempting to commit ${crime.name}...`); - const crimeTime = crime.commit(1, ctx.workerScript); - if (focus) { - Player.startFocusing(); - Router.toWork(); - } else if (wasFocusing) { - Player.stopFocusing(); - Router.toTerminal(); - } - return crimeTime; - }, - getCrimeChance: (ctx) => (_crimeRoughName) => { + commitCrime: (ctx) => (_crimeType, _focus) => { helpers.checkSingularityAccess(ctx); - const crimeRoughName = helpers.string(ctx, "crimeRoughName", _crimeRoughName); + const crimeType = helpers.string(ctx, "crimeType", _crimeType); + const focus = _focus === undefined ? true : !!_focus; + const wasFocusing = Player.focus; - const crime = findCrime(crimeRoughName.toLowerCase()); - if (crime == null) { - throw helpers.makeRuntimeErrorMsg(ctx, `Invalid crime: ${crimeRoughName}`); + if (Player.currentWork !== null) Player.finishWork(true); + Player.gotoLocation(LocationName.Slums); + + // If input isn't a crimeType, use search using roughname. + const crime = checkEnum(CrimeType, crimeType) ? Crimes[crimeType] : findCrime(crimeType); + if (crime == null) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid crime: '${crimeType}'`); + + helpers.log(ctx, () => `Attempting to commit ${crime.name}...`); + const crimeTime = crime.commit(1, ctx.workerScript); + if (focus) { + Player.startFocusing(); + Router.toWork(); + } else if (wasFocusing) { + Player.stopFocusing(); + Router.toTerminal(); } + return crimeTime; + }, + getCrimeChance: (ctx) => (_crimeType) => { + helpers.checkSingularityAccess(ctx); + const crimeType = helpers.string(ctx, "crimeType", _crimeType); + + // If input isn't a crimeType, use search using roughname. + const crime = checkEnum(CrimeType, crimeType) ? Crimes[crimeType] : findCrime(crimeType); + if (crime == null) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid crime: '${crimeType}'`); return crime.successRate(Player); }, - getCrimeStats: (ctx) => (_crimeRoughName) => { + getCrimeStats: (ctx) => (_crimeType) => { helpers.checkSingularityAccess(ctx); - const crimeRoughName = helpers.string(ctx, "crimeRoughName", _crimeRoughName); + const crimeType = helpers.string(ctx, "crimeType", _crimeType); - const crime = findCrime(crimeRoughName.toLowerCase()); - if (crime == null) { - throw helpers.makeRuntimeErrorMsg(ctx, `Invalid crime: ${crimeRoughName}`); - } + // If input isn't a crimeType, use search using roughname. + const crime = checkEnum(CrimeType, crimeType) ? Crimes[crimeType] : findCrime(crimeType); + if (crime == null) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid crime: '${crimeType}'`); const crimeStatsWithMultipliers = calculateCrimeWorkStats(crime); diff --git a/src/NetscriptFunctions/Sleeve.ts b/src/NetscriptFunctions/Sleeve.ts index f482ff586..f0300183f 100644 --- a/src/NetscriptFunctions/Sleeve.ts +++ b/src/NetscriptFunctions/Sleeve.ts @@ -11,6 +11,8 @@ import { isSleeveBladeburnerWork } from "../PersonObjects/Sleeve/Work/SleeveBlad import { isSleeveFactionWork } from "../PersonObjects/Sleeve/Work/SleeveFactionWork"; import { isSleeveCompanyWork } from "../PersonObjects/Sleeve/Work/SleeveCompanyWork"; import { helpers } from "../Netscript/NetscriptHelpers"; +import { Crimes } from "../Crime/Crimes"; +import { CrimeType } from "../utils/WorkType"; export function NetscriptSleeve(): InternalAPI { const checkSleeveAPIAccess = function (ctx: NetscriptContext) { @@ -62,15 +64,13 @@ export function NetscriptSleeve(): InternalAPI { checkSleeveNumber(ctx, sleeveNumber); return Player.sleeves[sleeveNumber].synchronize(); }, - setToCommitCrime: (ctx) => (_sleeveNumber, _crimeRoughName) => { + setToCommitCrime: (ctx) => (_sleeveNumber, _crimeType) => { const sleeveNumber = helpers.number(ctx, "sleeveNumber", _sleeveNumber); - const crimeRoughName = helpers.string(ctx, "crimeName", _crimeRoughName); + const crimeType = helpers.string(ctx, "crimeType", _crimeType); checkSleeveAPIAccess(ctx); checkSleeveNumber(ctx, sleeveNumber); - const crime = findCrime(crimeRoughName); - if (crime === null) { - return false; - } + const crime = checkEnum(CrimeType, crimeType) ? Crimes[crimeType] : findCrime(crimeType); + if (crime == null) return false; return Player.sleeves[sleeveNumber].commitCrime(crime.name); }, setToUniversityCourse: (ctx) => (_sleeveNumber, _universityName, _className) => { diff --git a/src/PersonObjects/Sleeve/Sleeve.ts b/src/PersonObjects/Sleeve/Sleeve.ts index fd6fd37c7..48fdc683c 100644 --- a/src/PersonObjects/Sleeve/Sleeve.ts +++ b/src/PersonObjects/Sleeve/Sleeve.ts @@ -12,21 +12,18 @@ import { Person } from "../Person"; import { Augmentation } from "../../Augmentation/Augmentation"; -import { Crime } from "../../Crime/Crime"; -import { Crimes } from "../../Crime/Crimes"; - import { Companies } from "../../Company/Companies"; import { Company } from "../../Company/Company"; import { CompanyPosition } from "../../Company/CompanyPosition"; import { CompanyPositions } from "../../Company/CompanyPositions"; - import { Contracts } from "../../Bladeburner/data/Contracts"; - import { CONSTANTS } from "../../Constants"; +import { checkEnum } from "../../utils/helpers/checkEnum"; +import { CrimeType } from "../../utils/WorkType"; +import { CityName } from "../../Locations/data/CityNames"; import { Factions } from "../../Faction/Factions"; -import { CityName } from "../../Locations/data/CityNames"; import { LocationName } from "../../Locations/data/LocationNames"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../../utils/JSONReviver"; @@ -97,13 +94,9 @@ export class Sleeve extends Person { } /** Commit crimes */ - commitCrime(crimeKey: string): boolean { - const crime: Crime | null = Crimes[crimeKey] || Object.values(Crimes).find((crime) => crime.name === crimeKey); - if (!crime) { - return false; - } - - this.startWork(new SleeveCrimeWork(crime.type)); + commitCrime(type: string): boolean { + if (!checkEnum(CrimeType, type)) return false; + this.startWork(new SleeveCrimeWork(type)); return true; } diff --git a/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts b/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts index e0a62f497..6f254399c 100644 --- a/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts +++ b/src/PersonObjects/Sleeve/Work/SleeveCrimeWork.ts @@ -8,6 +8,7 @@ import { Crime } from "../../../Crime/Crime"; import { newWorkStats, scaleWorkStats, WorkStats } from "../../../Work/WorkStats"; import { CONSTANTS } from "../../../Constants"; import { BitNodeMultipliers } from "../../../BitNode/BitNodeMultipliers"; +import { checkEnum } from "../../../utils/helpers/checkEnum"; export const isSleeveCrimeWork = (w: Work | null): w is SleeveCrimeWork => w !== null && w.type === WorkType.CRIME; @@ -20,9 +21,8 @@ export class SleeveCrimeWork extends Work { } getCrime(): Crime { - const crime = Object.values(Crimes).find((crime) => crime.type === this.crimeType); - if (!crime) throw new Error("crime should not be undefined"); - return crime; + if (!checkEnum(CrimeType, this.crimeType)) throw new Error("crime should not be undefined"); + return Crimes[this.crimeType]; } getExp(sleeve: Sleeve): WorkStats { diff --git a/src/PersonObjects/Sleeve/ui/SleeveElem.tsx b/src/PersonObjects/Sleeve/ui/SleeveElem.tsx index 58c443cb4..68bb7d4fc 100644 --- a/src/PersonObjects/Sleeve/ui/SleeveElem.tsx +++ b/src/PersonObjects/Sleeve/ui/SleeveElem.tsx @@ -74,7 +74,7 @@ export function SleeveElem(props: IProps): React.ReactElement { const crime = w.getCrime(); desc = ( <> - This sleeve is currently attempting to {crime.type} (Success Rate:{" "} + This sleeve is currently attempting {crime.workName} (Success Rate:{" "} {numeralWrapper.formatPercentage(crime.successRate(props.sleeve))}). ); diff --git a/src/PersonObjects/Sleeve/ui/TaskSelector.tsx b/src/PersonObjects/Sleeve/ui/TaskSelector.tsx index 73f03fb2c..a0001a421 100644 --- a/src/PersonObjects/Sleeve/ui/TaskSelector.tsx +++ b/src/PersonObjects/Sleeve/ui/TaskSelector.tsx @@ -19,6 +19,8 @@ import { isSleeveSupportWork } from "../Work/SleeveSupportWork"; import { ClassType } from "../../../Work/ClassWork"; import { isSleeveCrimeWork } from "../Work/SleeveCrimeWork"; import { FactionWorkType } from "../../../Work/data/FactionWorkType"; +import { checkEnum } from "../../../utils/helpers/checkEnum"; +import { CrimeType } from "../../../utils/WorkType"; const universitySelectorOptions: string[] = [ "Study Computer Science", @@ -314,11 +316,7 @@ function getABC(sleeve: Sleeve): [string, string, string] { } } if (isSleeveCrimeWork(w)) { - return [ - "Commit Crime", - Object.values(Crimes).find((crime) => crime.type === w.crimeType)?.name ?? "Shoplift", - "------", - ]; + return ["Commit Crime", checkEnum(CrimeType, w.crimeType) ? Crimes[w.crimeType].name : "Shoplift", "------"]; } if (isSleeveSupportWork(w)) { return ["Perform Bladeburner Actions", "Support main sleeve", "------"]; diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 99a7d9a36..7ef65fb3b 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -1,6 +1,3 @@ -/** @public */ -export type ValuesFrom = T[keyof T]; - /** @public */ export interface HP { current: number; @@ -1979,19 +1976,13 @@ export interface Singularity { * * This function returns the number of milliseconds it takes to attempt the * specified crime (e.g It takes 60 seconds to attempt the ‘Rob Store’ crime, - * so running `commitCrime('rob store')` will return 60,000). - * - * Warning: I do not recommend using the time returned from this function to try - * and schedule your crime attempts. Instead, I would use the isBusy Singularity - * function to check whether you have finished attempting a crime. This is because - * although the game sets a certain crime to be X amount of seconds, there is no - * guarantee that your browser will follow that time limit. + * so running `commitCrime('ROBSTORE')` will return 60,000). * * @param crime - Name of crime to attempt. * @param focus - Acquire player focus on this crime. Optional. Defaults to true. * @returns The number of milliseconds it takes to attempt the specified crime. */ - commitCrime(crime: string, focus?: boolean): number; + commitCrime(crime: CrimeType | CrimeNames, focus?: boolean): number; /** * Get chance to successfully commit a crime. @@ -2004,7 +1995,7 @@ export interface Singularity { * @param crime - Name of crime. * @returns Chance of success at committing the specified crime. */ - getCrimeChance(crime: string): number; + getCrimeChance(crime: CrimeType | CrimeNames): number; /** * Get stats related to a crime. @@ -2014,10 +2005,10 @@ export interface Singularity { * * Returns the stats of the crime. * - * @param crime - Name of crime. Not case-sensitive + * @param crime - Name of crime. * @returns The stats of the crime. */ - getCrimeStats(crime: string): CrimeStats; + getCrimeStats(crime: CrimeType | CrimeNames): CrimeStats; /** * Get a list of owned augmentation. @@ -2037,6 +2028,7 @@ export interface Singularity { * @remarks * RAM cost: 5 GB * + * * Returns an array of source files * * @returns Array containing an object with number and level of the source file. @@ -3651,32 +3643,13 @@ export interface Sleeve { * @remarks * RAM cost: 4 GB * - * Return a boolean indicating whether or not this action was set successfully. - * - * Returns false if an invalid action is specified. - * - * You can set a sleeve to commit one of the following crimes. The crime names are not - * case sensitive. For example, you can pass in the crime name as `"Shoplift"`, - * `"shoplift"`, `"shopLift"`, or even `"SHOPLIFT"`. - * - * - Assassination - * - Bond forgery - * - Deal drugs - * - Grand theft auto - * - Heist - * - Homicide - * - Kidnap - * - Larceny - * - Mug - * - Rob store - * - Shoplift - * - Traffick arms + * Return a boolean indicating whether or not this action was set successfully (false if an invalid action is specified). * * @example * ```ts * // NS1 * // Assign the first 3 sleeves to commit various crimes. - * var crime = ["mug", "rob store", "shoplift"]; + * var crime = ["MUG", "ROBSTORE", "SHOPLIFT"]; * for (var i = 0; i < crime.length; i++) { * tprintf("Sleeve %d commits crime: %s", i, crime[i]); * sleeve.setToCommitCrime(i, crime[i]); @@ -3686,7 +3659,7 @@ export interface Sleeve { * ```ts * // NS2 * // Assign the first 3 sleeves to commit various crimes. - * const crime = ["mug", "rob store", "shoplift"]; + * const crime = ["MUG", "ROBSTORE", "SHOPLIFT"]; * for (let i = 0; i < crime.length; i++) { * ns.tprintf("Sleeve %d commits crime: %s", i, crime[i]); * ns.sleeve.setToCommitCrime(i, crime[i]); @@ -3694,10 +3667,10 @@ export interface Sleeve { * ``` * * @param sleeveNumber - Index of the sleeve to start committing crime. Sleeves are numbered starting from 0. - * @param name - Name of the crime. Must be an exact match. Refer to the list of crimes. + * @param name - Name of the crime. * @returns True if this action was set successfully, false otherwise. */ - setToCommitCrime(sleeveNumber: number, name: string): boolean; + setToCommitCrime(sleeveNumber: number, crimeType: CrimeType | CrimeNames): boolean; /** * Set a sleeve to work for a faction. @@ -3935,7 +3908,7 @@ export interface WorkStats { * @public */ interface WorkFormulas { - crimeGains(crimeType: string): WorkStats; + crimeGains(crimeType: CrimeType | CrimeNames): WorkStats; classGains(player: Player, classType: string, locationName: string): WorkStats; factionGains(player: Player, workType: string, favor: number): WorkStats; } @@ -6700,7 +6673,7 @@ export interface NS { * @param variant - Type of toast, must be one of success, info, warning, error. Defaults to success. * @param duration - Duration of toast in ms. Can also be `null` to create a persistent toast. Defaults to 2000 */ - toast(msg: string, variant?: ToastVariant, duration?: number | null): void; + toast(msg: string, variant?: ToastTypes | ToastVariant, duration?: number | null): void; /** * Download a file from the internet. @@ -6899,21 +6872,38 @@ export interface NS { enums: NSEnums; } +declare enum ToastVariant { + SUCCESS = "success", + WARNING = "warning", + ERROR = "error", + INFO = "info", +} /** @public */ -declare const enums = { - toast: { - SUCCESS: "success", - WARNING: "warning", - ERROR: "error", - INFO: "info", - }, +export type ToastTypes = `${ToastVariant}`; + +declare enum CrimeType { + SHOPLIFT = "SHOPLIFT", + ROB_STORE = "ROBSTORE", + MUG = "MUG", + LARCENY = "LARCENY", + DRUGS = "DRUGS", + BOND_FORGERY = "BONDFORGERY", + TRAFFIC_ARMS = "TRAFFICKARMS", + HOMICIDE = "HOMICIDE", + GRAND_THEFT_AUTO = "GRANDTHEFTAUTO", + KIDNAP = "KIDNAP", + ASSASSINATION = "ASSASSINATION", + HEIST = "HEIST", +} +/** @public */ +type CrimeNames = `${CrimeType}`; + +/** @public */ +export type NSEnums = { + toast: typeof ToastVariant; + crimes: typeof CrimeType; }; -/** @public */ -type ToastVariant = ValuesFrom; - -/** @public */ -export type NSEnums = typeof enums; /** * Corporation Office API * @remarks diff --git a/src/Work/CrimeWork.ts b/src/Work/CrimeWork.ts index 20e68d235..ea1eae137 100644 --- a/src/Work/CrimeWork.ts +++ b/src/Work/CrimeWork.ts @@ -9,51 +9,7 @@ import { CrimeType } from "../utils/WorkType"; import { Work, WorkType } from "./Work"; import { scaleWorkStats, WorkStats } from "./WorkStats"; import { calculateCrimeWorkStats } from "./formulas/Crime"; - -enum newCrimeType { - SHOPLIFT = "SHOPLIFT", - ROBSTORE = "ROBSTORE", - MUG = "MUG", - LARCENY = "LARCENY", - DRUGS = "DRUGS", - BONDFORGERY = "BONDFORGERY", - TRAFFICKARMS = "TRAFFICKARMS", - HOMICIDE = "HOMICIDE", - GRANDTHEFTAUTO = "GRANDTHEFTAUTO", - KIDNAP = "KIDNAP", - ASSASSINATION = "ASSASSINATION", - HEIST = "HEIST", -} - -const convertCrimeType = (crimeType: CrimeType): newCrimeType => { - switch (crimeType) { - case CrimeType.SHOPLIFT: - return newCrimeType.SHOPLIFT; - case CrimeType.ROB_STORE: - return newCrimeType.ROBSTORE; - case CrimeType.MUG: - return newCrimeType.MUG; - case CrimeType.LARCENY: - return newCrimeType.LARCENY; - case CrimeType.DRUGS: - return newCrimeType.DRUGS; - case CrimeType.BOND_FORGERY: - return newCrimeType.BONDFORGERY; - case CrimeType.TRAFFIC_ARMS: - return newCrimeType.TRAFFICKARMS; - case CrimeType.HOMICIDE: - return newCrimeType.HOMICIDE; - case CrimeType.GRAND_THEFT_AUTO: - return newCrimeType.GRANDTHEFTAUTO; - case CrimeType.KIDNAP: - return newCrimeType.KIDNAP; - case CrimeType.ASSASSINATION: - return newCrimeType.ASSASSINATION; - case CrimeType.HEIST: - return newCrimeType.HEIST; - } - return newCrimeType.SHOPLIFT; -}; +import { checkEnum } from "../utils/helpers/checkEnum"; interface CrimeWorkParams { crimeType: CrimeType; @@ -73,9 +29,10 @@ export class CrimeWork extends Work { } 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; + if (!checkEnum(CrimeType, this.crimeType)) { + throw new Error("CrimeWork object constructed with invalid crime type"); + } + return Crimes[this.crimeType]; } process(cycles = 1): boolean { @@ -132,7 +89,7 @@ export class CrimeWork extends Work { return { type: this.type, cyclesWorked: this.cyclesWorked, - crimeType: convertCrimeType(this.crimeType), + crimeType: checkEnum(CrimeType, this.crimeType) ? this.crimeType : CrimeType.SHOPLIFT, }; } diff --git a/src/utils/WorkType.ts b/src/utils/WorkType.ts index f4cd1a285..19620eebe 100644 --- a/src/utils/WorkType.ts +++ b/src/utils/WorkType.ts @@ -1,5 +1,4 @@ export enum CrimeType { - None = "", SHOPLIFT = "SHOPLIFT", //"shoplift", ROB_STORE = "ROBSTORE", //"rob a store", MUG = "MUG", //"mug someone",