From 1a052a7daf955d49f07ba7070abd7f1f7d14d3c7 Mon Sep 17 00:00:00 2001 From: alutman Date: Tue, 17 Oct 2023 20:33:16 +1100 Subject: [PATCH] API: Change singularity.applyToCompany() to use an enum for job field (#859) --- .../bitburner.companypositioninfo.field.md | 11 ++ markdown/bitburner.companypositioninfo.md | 1 + markdown/bitburner.jobfield.md | 31 ++++++ markdown/bitburner.md | 1 + markdown/bitburner.nsenums.md | 3 +- .../bitburner.singularity.applytocompany.md | 4 +- src/Company/CompanyPosition.ts | 7 +- src/Company/data/CompanyPositionsMetadata.ts | 38 ++++++- src/Locations/ui/CompanyLocation.tsx | 24 ++--- src/NetscriptFunctions.ts | 2 + src/NetscriptFunctions/Singularity.ts | 49 ++++++--- src/ScriptEditor/NetscriptDefinitions.d.ts | 21 +++- src/Work/Enums.ts | 16 +++ .../NetscriptFunctions/Singularity.test.ts | 101 ++++++++++++++++++ 14 files changed, 275 insertions(+), 34 deletions(-) create mode 100644 markdown/bitburner.companypositioninfo.field.md create mode 100644 markdown/bitburner.jobfield.md create mode 100644 test/jest/NetscriptFunctions/Singularity.test.ts diff --git a/markdown/bitburner.companypositioninfo.field.md b/markdown/bitburner.companypositioninfo.field.md new file mode 100644 index 000000000..cbd11546a --- /dev/null +++ b/markdown/bitburner.companypositioninfo.field.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [CompanyPositionInfo](./bitburner.companypositioninfo.md) > [field](./bitburner.companypositioninfo.field.md) + +## CompanyPositionInfo.field property + +**Signature:** + +```typescript +field: JobField; +``` diff --git a/markdown/bitburner.companypositioninfo.md b/markdown/bitburner.companypositioninfo.md index f76fd5828..538e84634 100644 --- a/markdown/bitburner.companypositioninfo.md +++ b/markdown/bitburner.companypositioninfo.md @@ -16,6 +16,7 @@ export interface CompanyPositionInfo | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [field](./bitburner.companypositioninfo.field.md) | | [JobField](./bitburner.jobfield.md) | | | [name](./bitburner.companypositioninfo.name.md) | | [JobName](./bitburner.jobname.md) | | | [nextPosition](./bitburner.companypositioninfo.nextposition.md) | | [JobName](./bitburner.jobname.md) \| null | | | [requiredReputation](./bitburner.companypositioninfo.requiredreputation.md) | | number | | diff --git a/markdown/bitburner.jobfield.md b/markdown/bitburner.jobfield.md new file mode 100644 index 000000000..9c37371f9 --- /dev/null +++ b/markdown/bitburner.jobfield.md @@ -0,0 +1,31 @@ + + +[Home](./index.md) > [bitburner](./bitburner.md) > [JobField](./bitburner.jobfield.md) + +## JobField enum + + +**Signature:** + +```typescript +declare enum JobField +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| agent | "Agent" | | +| business | "Business" | | +| businessConsultant | "Business Consultant" | | +| employee | "Employee" | | +| it | "IT" | | +| networkEngineer | "Network Engineer" | | +| partTimeEmployee | "part-time Employee" | | +| partTimeWaiter | "part-time Waiter" | | +| security | "Security" | | +| securityEngineer | "Security Engineer" | | +| software | "Software" | | +| softwareConsultant | "Software Consultant" | | +| waiter | "Waiter" | | + diff --git a/markdown/bitburner.md b/markdown/bitburner.md index 8cb319a2c..6ae71ba46 100644 --- a/markdown/bitburner.md +++ b/markdown/bitburner.md @@ -13,6 +13,7 @@ | [CrimeType](./bitburner.crimetype.md) | | | [FactionWorkType](./bitburner.factionworktype.md) | | | [GymType](./bitburner.gymtype.md) | | +| [JobField](./bitburner.jobfield.md) | | | [JobName](./bitburner.jobname.md) | | | [LocationName](./bitburner.locationname.md) | Names of all locations | | [OrderType](./bitburner.ordertype.md) | | diff --git a/markdown/bitburner.nsenums.md b/markdown/bitburner.nsenums.md index f1b1fe8fc..a6d9b58da 100644 --- a/markdown/bitburner.nsenums.md +++ b/markdown/bitburner.nsenums.md @@ -14,11 +14,12 @@ export type NSEnums = { FactionWorkType: typeof FactionWorkType; GymType: typeof GymType; JobName: typeof JobName; + JobField: typeof JobField; LocationName: typeof LocationName; ToastVariant: typeof ToastVariant; UniversityClassType: typeof UniversityClassType; CompanyName: typeof CompanyName; }; ``` -**References:** [CityName](./bitburner.cityname.md), [CrimeType](./bitburner.crimetype.md), [FactionWorkType](./bitburner.factionworktype.md), [GymType](./bitburner.gymtype.md), [JobName](./bitburner.jobname.md), [LocationName](./bitburner.locationname.md), [ToastVariant](./bitburner.toastvariant.md), [UniversityClassType](./bitburner.universityclasstype.md), [CompanyName](./bitburner.companyname.md) +**References:** [CityName](./bitburner.cityname.md), [CrimeType](./bitburner.crimetype.md), [FactionWorkType](./bitburner.factionworktype.md), [GymType](./bitburner.gymtype.md), [JobName](./bitburner.jobname.md), [JobField](./bitburner.jobfield.md), [LocationName](./bitburner.locationname.md), [ToastVariant](./bitburner.toastvariant.md), [UniversityClassType](./bitburner.universityclasstype.md), [CompanyName](./bitburner.companyname.md) diff --git a/markdown/bitburner.singularity.applytocompany.md b/markdown/bitburner.singularity.applytocompany.md index 63eb45fdf..6d22ea046 100644 --- a/markdown/bitburner.singularity.applytocompany.md +++ b/markdown/bitburner.singularity.applytocompany.md @@ -9,7 +9,7 @@ Apply for a job at a company. **Signature:** ```typescript -applyToCompany(companyName: CompanyName | `${CompanyName}`, field: string): boolean; +applyToCompany(companyName: CompanyName | `${CompanyName}`, field: JobField | `${JobField}`): boolean; ``` ## Parameters @@ -17,7 +17,7 @@ applyToCompany(companyName: CompanyName | `${CompanyName}`, field: string): bool | Parameter | Type | Description | | --- | --- | --- | | companyName | [CompanyName](./bitburner.companyname.md) \| \`${[CompanyName](./bitburner.companyname.md)}\` | Name of company to apply to. | -| field | string | Field to which you want to apply. | +| field | [JobField](./bitburner.jobfield.md) \| \`${[JobField](./bitburner.jobfield.md)}\` | Field to which you want to apply. | **Returns:** diff --git a/src/Company/CompanyPosition.ts b/src/Company/CompanyPosition.ts index a752512ec..36ea3438d 100644 --- a/src/Company/CompanyPosition.ts +++ b/src/Company/CompanyPosition.ts @@ -1,6 +1,6 @@ import { Person as IPerson } from "@nsdefs"; import { CONSTANTS } from "../Constants"; -import { JobName } from "@enums"; +import { JobName, JobField } from "@enums"; import { agentJobs, businessConsultJobs, @@ -14,6 +14,7 @@ import { export interface CompanyPositionCtorParams { nextPosition: JobName | null; + field: JobField; baseSalary: number; repMultiplier: number; @@ -44,6 +45,9 @@ export class CompanyPosition { /** Position title */ name: JobName; + /** Field type of the position (software, it, business, etc) */ + field: JobField; + /** Title of next position to be promoted to */ nextPosition: JobName | null; @@ -85,6 +89,7 @@ export class CompanyPosition { constructor(name: JobName, p: CompanyPositionCtorParams) { this.name = name; + this.field = p.field; this.nextPosition = p.nextPosition; this.baseSalary = p.baseSalary; this.repMultiplier = p.repMultiplier; diff --git a/src/Company/data/CompanyPositionsMetadata.ts b/src/Company/data/CompanyPositionsMetadata.ts index 629ac9a9a..e45812cb0 100644 --- a/src/Company/data/CompanyPositionsMetadata.ts +++ b/src/Company/data/CompanyPositionsMetadata.ts @@ -1,11 +1,12 @@ // Metadata used for constructing Company Positions -import { JobName } from "@enums"; +import { JobName, JobField } from "@enums"; import { CompanyPositionCtorParams } from "../CompanyPosition"; export function getCompanyPositionMetadata(): Record { return { [JobName.software0]: { nextPosition: JobName.software1, // Junior Software Engineer + field: JobField.software, baseSalary: 33, charismaEffectiveness: 15, charismaExpGain: 0.02, @@ -16,6 +17,7 @@ export function getCompanyPositionMetadata(): Record )} {company.hasBusinessConsultantPositions() && ( @@ -246,7 +246,7 @@ export function CompanyLocation(props: IProps): React.ReactElement { company={company} entryPosType={CompanyPositions[JobName.businessConsult0]} onClick={applyForBusinessConsultantJob} - text={"Apply for Business Consultant Job"} + text={"Apply for " + JobField.businessConsultant + " Job"} /> )} {company.hasBusinessPositions() && ( @@ -254,7 +254,7 @@ export function CompanyLocation(props: IProps): React.ReactElement { company={company} entryPosType={CompanyPositions[JobName.business0]} onClick={applyForBusinessJob} - text={"Apply for Business Job"} + text={"Apply for " + JobField.business + " Job"} /> )} {company.hasEmployeePositions() && ( @@ -262,7 +262,7 @@ export function CompanyLocation(props: IProps): React.ReactElement { company={company} entryPosType={CompanyPositions[JobName.employee]} onClick={applyForEmployeeJob} - text={"Apply to be an Employee"} + text={"Apply to be an " + JobField.employee} /> )} {company.hasEmployeePositions() && ( @@ -270,7 +270,7 @@ export function CompanyLocation(props: IProps): React.ReactElement { company={company} entryPosType={CompanyPositions[JobName.employeePT]} onClick={applyForPartTimeEmployeeJob} - text={"Apply to be a part-time Employee"} + text={"Apply to be a " + JobField.partTimeEmployee} /> )} {company.hasITPositions() && ( @@ -278,7 +278,7 @@ export function CompanyLocation(props: IProps): React.ReactElement { company={company} entryPosType={CompanyPositions[JobName.IT0]} onClick={applyForItJob} - text={"Apply for IT Job"} + text={"Apply for " + JobField.it + " Job"} /> )} {company.hasSecurityPositions() && ( @@ -286,7 +286,7 @@ export function CompanyLocation(props: IProps): React.ReactElement { company={company} entryPosType={CompanyPositions[JobName.security0]} onClick={applyForSecurityJob} - text={"Apply for Security Job"} + text={"Apply for " + JobField.security + " Job"} /> )} {company.hasSoftwareConsultantPositions() && ( @@ -294,7 +294,7 @@ export function CompanyLocation(props: IProps): React.ReactElement { company={company} entryPosType={CompanyPositions[JobName.softwareConsult0]} onClick={applyForSoftwareConsultantJob} - text={"Apply for Software Consultant Job"} + text={"Apply for " + JobField.softwareConsultant + " Job"} /> )} {company.hasSoftwarePositions() && ( @@ -302,7 +302,7 @@ export function CompanyLocation(props: IProps): React.ReactElement { company={company} entryPosType={CompanyPositions[JobName.software0]} onClick={applyForSoftwareJob} - text={"Apply for Software Job"} + text={"Apply for " + JobField.software + " Job"} /> )} {company.hasWaiterPositions() && ( @@ -310,7 +310,7 @@ export function CompanyLocation(props: IProps): React.ReactElement { company={company} entryPosType={CompanyPositions[JobName.waiter]} onClick={applyForWaiterJob} - text={"Apply to be a Waiter"} + text={"Apply to be a " + JobField.waiter} /> )} {company.hasWaiterPositions() && ( @@ -318,7 +318,7 @@ export function CompanyLocation(props: IProps): React.ReactElement { company={company} entryPosType={CompanyPositions[JobName.waiterPT]} onClick={applyForPartTimeWaiterJob} - text={"Apply to be a part-time Waiter"} + text={"Apply to be a " + JobField.partTimeWaiter} /> )} {location.infiltrationData != null && } diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 7cefda6ca..641b6ddfe 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -20,6 +20,7 @@ import { FactionWorkType, GymType, JobName, + JobField, LiteratureName, LocationName, ToastVariant, @@ -109,6 +110,7 @@ export const enums: NSEnums = { FactionWorkType, GymType, JobName, + JobField, LocationName, ToastVariant, UniversityClassType, diff --git a/src/NetscriptFunctions/Singularity.ts b/src/NetscriptFunctions/Singularity.ts index 1335017f8..dab4d7f77 100644 --- a/src/NetscriptFunctions/Singularity.ts +++ b/src/NetscriptFunctions/Singularity.ts @@ -8,6 +8,7 @@ import { FactionName, FactionWorkType, GymType, + JobField, LocationName, UniversityClassType, } from "@enums"; @@ -31,7 +32,7 @@ import { formatMoney, formatRam, formatReputation } from "../ui/formatNumber"; import { currentNodeMults } from "../BitNode/BitNodeMultipliers"; import { Companies } from "../Company/Companies"; import { Factions } from "../Faction/Factions"; -import { helpers } from "../Netscript/NetscriptHelpers"; +import { helpers, assertString } from "../Netscript/NetscriptHelpers"; import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions"; import { getServerOnNetwork } from "../Server/ServerHelpers"; import { Terminal } from "../Terminal"; @@ -687,6 +688,7 @@ export function NetscriptSingularity(): InternalAPI { const job = CompanyPositions[positionName]; const res = { name: CompanyPositions[positionName].name, + field: CompanyPositions[positionName].field, nextPosition: CompanyPositions[positionName].nextPosition, salary: CompanyPositions[positionName].baseSalary * company.salaryMultiplier, requiredReputation: CompanyPositions[positionName].requiredReputation, @@ -736,48 +738,63 @@ export function NetscriptSingularity(): InternalAPI { applyToCompany: (ctx) => (_companyName, _field) => { helpers.checkSingularityAccess(ctx); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName); - const field = helpers.string(ctx, "field", _field); + assertString(ctx, "field", _field); + + // capitalize each word, except for "part-time" + function capitalizeJobField(field: string) { + return field + .toLowerCase() + .split(" ") + .map((s) => { + if (s.length == 0 || s == "part-time") return s; + if (s.length == 2) return s.toUpperCase(); // Probably an acronym + return s[0].toUpperCase() + s.slice(1); + }) + .join(" "); + } + + const field = getEnumHelper("JobField").nsGetMember(ctx, capitalizeJobField(_field as string), "field"); Player.location = companyNameAsLocationName(companyName); let res; - switch (field.toLowerCase()) { - case "software": + switch (field) { + case JobField.software: res = Player.applyForSoftwareJob(true); break; - case "software consultant": + case JobField.softwareConsultant: res = Player.applyForSoftwareConsultantJob(true); break; - case "it": + case JobField.it: res = Player.applyForItJob(true); break; - case "security engineer": + case JobField.securityEngineer: res = Player.applyForSecurityEngineerJob(true); break; - case "network engineer": + case JobField.networkEngineer: res = Player.applyForNetworkEngineerJob(true); break; - case "business": + case JobField.business: res = Player.applyForBusinessJob(true); break; - case "business consultant": + case JobField.businessConsultant: res = Player.applyForBusinessConsultantJob(true); break; - case "security": + case JobField.security: res = Player.applyForSecurityJob(true); break; - case "agent": + case JobField.agent: res = Player.applyForAgentJob(true); break; - case "employee": + case JobField.employee: res = Player.applyForEmployeeJob(true); break; - case "part-time employee": + case JobField.partTimeEmployee: res = Player.applyForPartTimeEmployeeJob(true); break; - case "waiter": + case JobField.waiter: res = Player.applyForWaiterJob(true); break; - case "part-time waiter": + case JobField.partTimeWaiter: res = Player.applyForPartTimeWaiterJob(true); break; default: diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index de481fbf3..9401221cf 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -1856,7 +1856,7 @@ export interface Singularity { * @param field - Field to which you want to apply. * @returns True if the player successfully get a job/promotion, and false otherwise. */ - applyToCompany(companyName: CompanyName | `${CompanyName}`, field: string): boolean; + applyToCompany(companyName: CompanyName | `${CompanyName}`, field: JobField | `${JobField}`): boolean; /** * Get company reputation. @@ -2423,6 +2423,7 @@ export interface Singularity { */ export interface CompanyPositionInfo { name: JobName; + field: JobField; nextPosition: JobName | null; salary: number; requiredReputation: number; @@ -6860,6 +6861,23 @@ declare enum JobName { employeePT = "Part-time Employee", } +/** @public */ +declare enum JobField { + software = "Software", + softwareConsultant = "Software Consultant", + it = "IT", + securityEngineer = "Security Engineer", + networkEngineer = "Network Engineer", + business = "Business", + businessConsultant = "Business Consultant", + security = "Security", + agent = "Agent", + employee = "Employee", + partTimeEmployee = "part-time Employee", + waiter = "Waiter", + partTimeWaiter = "part-time Waiter", +} + // CORP ENUMS - Changed to types /** @public */ type CorpEmployeePosition = @@ -7021,6 +7039,7 @@ export type NSEnums = { FactionWorkType: typeof FactionWorkType; GymType: typeof GymType; JobName: typeof JobName; + JobField: typeof JobField; LocationName: typeof LocationName; ToastVariant: typeof ToastVariant; UniversityClassType: typeof UniversityClassType; diff --git a/src/Work/Enums.ts b/src/Work/Enums.ts index 1c31d3f6b..4cceec84a 100644 --- a/src/Work/Enums.ts +++ b/src/Work/Enums.ts @@ -63,3 +63,19 @@ export enum JobName { businessConsult0 = "Business Consultant", businessConsult1 = "Senior Business Consultant", } + +export enum JobField { + software = "Software", + softwareConsultant = "Software Consultant", + it = "IT", + securityEngineer = "Security Engineer", + networkEngineer = "Network Engineer", + business = "Business", + businessConsultant = "Business Consultant", + security = "Security", + agent = "Agent", + employee = "Employee", + partTimeEmployee = "part-time Employee", + waiter = "Waiter", + partTimeWaiter = "part-time Waiter", +} diff --git a/test/jest/NetscriptFunctions/Singularity.test.ts b/test/jest/NetscriptFunctions/Singularity.test.ts new file mode 100644 index 000000000..cd65e5402 --- /dev/null +++ b/test/jest/NetscriptFunctions/Singularity.test.ts @@ -0,0 +1,101 @@ +import { Player, setPlayer } from "../../../src/Player"; +import { getCompaniesMetadata } from "../../../src/Company/data/CompaniesMetadata"; +import { getCompanyPositionMetadata } from "../../../src/Company/data/CompanyPositionsMetadata"; +import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject"; +import { NetscriptSingularity } from "../../../src/NetscriptFunctions/Singularity"; +import { CompanyName, JobField, JobName } from "@enums"; + +import { WorkerScript } from "../../../src/Netscript/WorkerScript"; +import { Script } from "../../../src/Script/Script"; +import { RunningScript } from "../../../src/Script/RunningScript"; +import { ScriptFilePath } from "../../../src/Paths/ScriptFilePath"; +import { GetServer } from "../../../src/Server/AllServers"; + +describe("Singularity", () => { + let ctx; + let singularity; + let positionMetadata; + let companyMetadata; + + beforeAll(() => { + singularity = NetscriptSingularity(); + positionMetadata = getCompanyPositionMetadata(); + companyMetadata = getCompaniesMetadata(); + }); + + beforeEach(() => { + setPlayer(new PlayerObject()); + Player.init(); + Player.sourceFiles.set(4, 3); + + GetServer("home").writeToScriptFile("function.js", ""); + let script = new Script("function.js", "", "home"); + let runningScript = new RunningScript(script, 1, []); + let workerScript = new WorkerScript(runningScript, 1); + ctx = { workerScript: workerScript, function: "singularityTest", functionPath: "test.singularityTest" }; + }); + + afterEach(() => { + Object.values(CompanyName).forEach((k) => { + companyMetadata[k].playerReputation = 0; + }); + }); + + describe("getCompanyPositionInfo", () => { + it("returns an enum for field", () => { + let companyWithPositions = Object.values(CompanyName).find( + (cn) => companyMetadata[cn].companyPositions.length > 0, + ); + let company = companyMetadata[companyWithPositions]; + let positionName = company.companyPositions[0]; + let position = positionMetadata[positionName]; + + let companyPosition = singularity.getCompanyPositionInfo(ctx)(company.name, positionName); + expect(companyPosition.field).toEqual(position.field); + }); + }); + + describe("applyToCompany", () => { + it("throws an error if input doesn't match an enum", () => { + let anyValidCompany = Object.values(CompanyName)[0]; + expect(() => singularity.applyToCompany(ctx)(anyValidCompany, "sockware")).toThrow("should be a JobField"); + expect(() => singularity.applyToCompany(ctx)(anyValidCompany, "invalid-job-field")).toThrow( + "should be a JobField", + ); + }); + + it("accepts the JobField specified by getCompanyPositionInfo", () => { + Object.values(CompanyName).forEach((cn) => { + companyMetadata[cn].companyPositions.forEach((pn) => { + let pos = positionMetadata[pn]; + expect(() => singularity.applyToCompany(ctx)(cn, pos.field)).not.toThrow(); + Player.quitJob(cn); + }); + }); + }); + + it("is case-insensitive to string inputs to the field parameter", () => { + Object.values(CompanyName).forEach((cn) => { + companyMetadata[cn].companyPositions.forEach((pn) => { + let pos = positionMetadata[pn]; + let field = pos.field; + + let upperCase = field.toUpperCase(); + expect(() => singularity.applyToCompany(ctx)(cn, upperCase)).not.toThrow(); + Player.quitJob(cn); + + let lowerCase = field.toLowerCase(); + expect(() => singularity.applyToCompany(ctx)(cn, lowerCase)).not.toThrow(); + Player.quitJob(cn); + + let brokenCasing = field + .split("") + .map((c, i) => (i % 2 == 0 ? c.toUpperCase() : c.toLowerCase())) + .join(""); + expect(() => singularity.applyToCompany(ctx)(cn, brokenCasing)).not.toThrow(); + Player.quitJob(cn); + }); + }); + }); + }); +});