COMPANY: Much better job location interface (#927)

This commit is contained in:
Jesse Clark 2023-12-18 04:23:47 -08:00 committed by GitHub
parent 97d679bdac
commit 28ef5df880
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 443 additions and 798 deletions

@ -56,42 +56,6 @@ export class Company {
return this.companyPositions.has(typeof pos === "string" ? pos : pos.name); return this.companyPositions.has(typeof pos === "string" ? pos : pos.name);
} }
hasAgentPositions(): boolean {
return this.companyPositions.has(JobName.agent0);
}
hasBusinessConsultantPositions(): boolean {
return this.companyPositions.has(JobName.businessConsult0);
}
hasBusinessPositions(): boolean {
return this.companyPositions.has(JobName.business0);
}
hasEmployeePositions(): boolean {
return this.companyPositions.has(JobName.employee);
}
hasITPositions(): boolean {
return this.companyPositions.has(JobName.IT0);
}
hasSecurityPositions(): boolean {
return this.companyPositions.has(JobName.security0);
}
hasSoftwareConsultantPositions(): boolean {
return this.companyPositions.has(JobName.softwareConsult0);
}
hasSoftwarePositions(): boolean {
return this.companyPositions.has(JobName.software0);
}
hasWaiterPositions(): boolean {
return this.companyPositions.has(JobName.waiter);
}
prestigeAugmentation(): void { prestigeAugmentation(): void {
if (this.favor == null) this.favor = 0; if (this.favor == null) this.favor = 0;
this.favor += this.getFavorGain(); this.favor += this.getFavorGain();
@ -121,7 +85,7 @@ export class Company {
return Generic_fromJSON(Company, value.data, Company.includedKeys); return Generic_fromJSON(Company, value.data, Company.includedKeys);
} }
// Only these 3 keys are relevant to the save file // Only these 2 keys are relevant to the save file
static includedKeys = ["favor", "playerReputation"] as const; static includedKeys = ["favor", "playerReputation"] as const;
} }

@ -1,22 +1,16 @@
import { Person as IPerson } from "@nsdefs"; import { Person as IPerson } from "@nsdefs";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { JobName, JobField } from "@enums"; import { JobName, JobField } from "@enums";
import { import type { Skills } from "../PersonObjects/Skills";
agentJobs,
businessConsultJobs,
businessJobs,
itJobs,
netEngJobs,
securityJobs,
softwareConsultJobs,
softwareJobs,
} from "./data/JobTracks";
export interface CompanyPositionCtorParams { export interface CompanyPositionCtorParams {
nextPosition: JobName | null; nextPosition: JobName | null;
field: JobField; field: JobField;
baseSalary: number; baseSalary: number;
repMultiplier: number; repMultiplier: number;
applyText?: string;
hiredText?: string;
isPartTime?: boolean;
reqdHacking?: number; reqdHacking?: number;
reqdStrength?: number; reqdStrength?: number;
@ -60,6 +54,15 @@ export class CompanyPosition {
/** Reputation multiplier */ /** Reputation multiplier */
repMultiplier: number; repMultiplier: number;
/** Text to display when applying for this job */
applyText: string;
/** Text to display when receiving this job */
hiredText: string;
/** Whether this position is part-time */
isPartTime: boolean;
/** Required stats to earn this position */ /** Required stats to earn this position */
requiredAgility: number; requiredAgility: number;
requiredCharisma: number; requiredCharisma: number;
@ -93,6 +96,9 @@ export class CompanyPosition {
this.nextPosition = p.nextPosition; this.nextPosition = p.nextPosition;
this.baseSalary = p.baseSalary; this.baseSalary = p.baseSalary;
this.repMultiplier = p.repMultiplier; this.repMultiplier = p.repMultiplier;
this.isPartTime = p.isPartTime ?? false;
this.applyText = p.applyText ?? `Apply for ${this.name} Job`;
this.hiredText = p.hiredText ?? `Congratulations, you are now employed as a ${this.name}`;
this.requiredHacking = p.reqdHacking != null ? p.reqdHacking : 0; this.requiredHacking = p.reqdHacking != null ? p.reqdHacking : 0;
this.requiredStrength = p.reqdStrength != null ? p.reqdStrength : 0; this.requiredStrength = p.reqdStrength != null ? p.reqdStrength : 0;
@ -130,6 +136,18 @@ export class CompanyPosition {
this.charismaExpGain = p.charismaExpGain != null ? p.charismaExpGain : 0; this.charismaExpGain = p.charismaExpGain != null ? p.charismaExpGain : 0;
} }
requiredSkills(jobStatReqOffset: number): Skills {
return {
hacking: this.requiredHacking > 0 ? this.requiredHacking + jobStatReqOffset : 0,
strength: this.requiredStrength > 0 ? this.requiredStrength + jobStatReqOffset : 0,
defense: this.requiredDefense > 0 ? this.requiredDefense + jobStatReqOffset : 0,
dexterity: this.requiredDexterity > 0 ? this.requiredDexterity + jobStatReqOffset : 0,
agility: this.requiredAgility > 0 ? this.requiredAgility + jobStatReqOffset : 0,
charisma: this.requiredCharisma > 0 ? this.requiredCharisma + jobStatReqOffset : 0,
intelligence: 0,
};
}
calculateJobPerformance(worker: IPerson): number { calculateJobPerformance(worker: IPerson): number {
const hackRatio: number = (this.hackingEffectiveness * worker.skills.hacking) / CONSTANTS.MaxSkillLevel; const hackRatio: number = (this.hackingEffectiveness * worker.skills.hacking) / CONSTANTS.MaxSkillLevel;
const strRatio: number = (this.strengthEffectiveness * worker.skills.strength) / CONSTANTS.MaxSkillLevel; const strRatio: number = (this.strengthEffectiveness * worker.skills.strength) / CONSTANTS.MaxSkillLevel;
@ -147,44 +165,4 @@ export class CompanyPosition {
reputationGain += worker.skills.intelligence / CONSTANTS.MaxSkillLevel; reputationGain += worker.skills.intelligence / CONSTANTS.MaxSkillLevel;
return reputationGain; return reputationGain;
} }
isSoftwareJob(): boolean {
return softwareJobs.includes(this.name);
}
isITJob(): boolean {
return itJobs.includes(this.name);
}
isSecurityEngineerJob(): boolean {
return this.name === JobName.securityEng;
}
isNetworkEngineerJob(): boolean {
return netEngJobs.includes(this.name);
}
isBusinessJob(): boolean {
return businessJobs.includes(this.name);
}
isSecurityJob(): boolean {
return securityJobs.includes(this.name);
}
isAgentJob(): boolean {
return agentJobs.includes(this.name);
}
isSoftwareConsultantJob(): boolean {
return softwareConsultJobs.includes(this.name);
}
isBusinessConsultantJob(): boolean {
return businessConsultJobs.includes(this.name);
}
isPartTimeJob(): boolean {
return [JobName.employeePT, JobName.waiterPT].includes(this.name);
}
} }

@ -1,51 +0,0 @@
import { Company } from "./Company";
import { CompanyPosition } from "./CompanyPosition";
/** Returns a string with the given CompanyPosition's stat requirements */
export function getJobRequirementText(company: Company, pos: CompanyPosition, tooltiptext = false): string {
let reqText = "";
const offset: number = company.jobStatReqOffset;
const reqHacking: number = pos.requiredHacking > 0 ? pos.requiredHacking + offset : 0;
const reqStrength: number = pos.requiredStrength > 0 ? pos.requiredStrength + offset : 0;
const reqDefense: number = pos.requiredDefense > 0 ? pos.requiredDefense + offset : 0;
const reqDexterity: number = pos.requiredDexterity > 0 ? pos.requiredDexterity + offset : 0;
const reqAgility: number = pos.requiredDexterity > 0 ? pos.requiredDexterity + offset : 0;
const reqCharisma: number = pos.requiredCharisma > 0 ? pos.requiredCharisma + offset : 0;
const reqRep: number = pos.requiredReputation;
if (tooltiptext) {
reqText = "Requires:<br>";
reqText += reqHacking.toString() + " hacking<br>";
reqText += reqStrength.toString() + " strength<br>";
reqText += reqDefense.toString() + " defense<br>";
reqText += reqDexterity.toString() + " dexterity<br>";
reqText += reqAgility.toString() + " agility<br>";
reqText += reqCharisma.toString() + " charisma<br>";
reqText += reqRep.toString() + " reputation";
} else {
reqText = "(Requires ";
if (reqHacking > 0) {
reqText += reqHacking + " hacking, ";
}
if (reqStrength > 0) {
reqText += reqStrength + " strength, ";
}
if (reqDefense > 0) {
reqText += reqDefense + " defense, ";
}
if (reqDexterity > 0) {
reqText += reqDexterity + " dexterity, ";
}
if (reqAgility > 0) {
reqText += reqAgility + " agility, ";
}
if (reqCharisma > 0) {
reqText += reqCharisma + " charisma, ";
}
if (reqRep > 1) {
reqText += reqRep + " reputation, ";
}
reqText = reqText.substring(0, reqText.length - 2);
reqText += ")";
}
return reqText;
}

@ -0,0 +1,23 @@
import { Company } from "./Company";
import { CompanyPosition } from "./CompanyPosition";
import { PlayerCondition, haveSkill, haveCompanyRep } from "../Faction/FactionJoinCondition";
import type { Skills } from "../PersonObjects/Skills";
export function getJobRequirements(company: Company, pos: CompanyPosition): PlayerCondition[] {
const reqSkills = pos.requiredSkills(company.jobStatReqOffset);
const reqs = [];
for (const [skillName, value] of Object.entries(reqSkills)) {
if (value > 0) reqs.push(haveSkill(skillName as keyof Skills, value));
}
if (pos.requiredReputation > 0) {
reqs.push(haveCompanyRep(company.name, pos.requiredReputation));
}
return reqs;
}
/** Returns a string with the given CompanyPosition's stat requirements */
export function getJobRequirementText(company: Company, pos: CompanyPosition): string {
const reqs = getJobRequirements(company, pos);
return `(${pos.name} requires: ${reqs.map((s) => s.toString()).join(", ")})`;
}

@ -1,14 +1,15 @@
import { CompanyCtorParams } from "../Company"; import { CompanyCtorParams } from "../Company";
import { CompanyName, JobName, FactionName } from "@enums"; import { CompanyName, FactionName, JobName } from "@enums";
import { import {
agentJobs, softwareJobs,
businessJobs, businessJobs,
itJobs,
netEngJobs,
securityJobs, securityJobs,
softwareConsultJobs, softwareConsultJobs,
softwareJobs, netEngJobs,
itJobs,
agentJobs,
businessConsultJobs,
} from "./JobTracks"; } from "./JobTracks";
export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> { export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
@ -107,14 +108,14 @@ export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
}, },
[CompanyName.DefComm]: { [CompanyName.DefComm]: {
name: CompanyName.DefComm, name: CompanyName.DefComm,
companyPositions: [JobName.business5, ...allTechJobs, ...softwareConsultJobs], companyPositions: [JobName.business5, ...allTechJobs, ...softwareConsultJobs, ...businessConsultJobs],
expMultiplier: 1.75, expMultiplier: 1.75,
salaryMultiplier: 1.75, salaryMultiplier: 1.75,
jobStatReqOffset: 199, jobStatReqOffset: 199,
}, },
[CompanyName.HeliosLabs]: { [CompanyName.HeliosLabs]: {
name: CompanyName.HeliosLabs, name: CompanyName.HeliosLabs,
companyPositions: [JobName.business5, ...allTechJobs, ...softwareConsultJobs], companyPositions: [JobName.business5, ...allTechJobs, ...softwareConsultJobs, ...businessConsultJobs],
expMultiplier: 1.8, expMultiplier: 1.8,
salaryMultiplier: 1.8, salaryMultiplier: 1.8,
jobStatReqOffset: 199, jobStatReqOffset: 199,
@ -149,28 +150,28 @@ export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
}, },
[CompanyName.AeroCorp]: { [CompanyName.AeroCorp]: {
name: CompanyName.AeroCorp, name: CompanyName.AeroCorp,
companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs], companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs, ...businessConsultJobs],
expMultiplier: 1.7, expMultiplier: 1.7,
salaryMultiplier: 1.7, salaryMultiplier: 1.7,
jobStatReqOffset: 199, jobStatReqOffset: 199,
}, },
[CompanyName.OmniaCybersystems]: { [CompanyName.OmniaCybersystems]: {
name: CompanyName.OmniaCybersystems, name: CompanyName.OmniaCybersystems,
companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs], companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs, ...businessConsultJobs],
expMultiplier: 1.7, expMultiplier: 1.7,
salaryMultiplier: 1.7, salaryMultiplier: 1.7,
jobStatReqOffset: 199, jobStatReqOffset: 199,
}, },
[CompanyName.SolarisSpaceSystems]: { [CompanyName.SolarisSpaceSystems]: {
name: CompanyName.SolarisSpaceSystems, name: CompanyName.SolarisSpaceSystems,
companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs], companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs, ...businessConsultJobs],
expMultiplier: 1.7, expMultiplier: 1.7,
salaryMultiplier: 1.7, salaryMultiplier: 1.7,
jobStatReqOffset: 199, jobStatReqOffset: 199,
}, },
[CompanyName.DeltaOne]: { [CompanyName.DeltaOne]: {
name: CompanyName.DeltaOne, name: CompanyName.DeltaOne,
companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs], companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs, ...businessConsultJobs],
expMultiplier: 1.6, expMultiplier: 1.6,
salaryMultiplier: 1.6, salaryMultiplier: 1.6,
jobStatReqOffset: 199, jobStatReqOffset: 199,

@ -65,6 +65,7 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
reqdHacking: 501, reqdHacking: 501,
reqdReputation: 400e3, reqdReputation: 400e3,
repMultiplier: 1.6, repMultiplier: 1.6,
hiredText: `Congratulations, you are now ${JobName.software4}`,
}, },
[JobName.software5]: { [JobName.software5]: {
nextPosition: JobName.software6, // Vice President of Technology nextPosition: JobName.software6, // Vice President of Technology
@ -78,6 +79,7 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
reqdHacking: 501, reqdHacking: 501,
reqdReputation: 800e3, reqdReputation: 800e3,
repMultiplier: 1.6, repMultiplier: 1.6,
hiredText: `Congratulations, you are now ${JobName.software5}`,
}, },
[JobName.software6]: { [JobName.software6]: {
nextPosition: JobName.software7, // Chief Technology Officer nextPosition: JobName.software7, // Chief Technology Officer
@ -91,6 +93,7 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
reqdHacking: 601, reqdHacking: 601,
reqdReputation: 1.6e6, reqdReputation: 1.6e6,
repMultiplier: 1.75, repMultiplier: 1.75,
hiredText: `Congratulations, you are now ${JobName.software6}`,
}, },
[JobName.software7]: { [JobName.software7]: {
nextPosition: null, nextPosition: null,
@ -104,6 +107,7 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
reqdHacking: 751, reqdHacking: 751,
reqdReputation: 3.2e6, reqdReputation: 3.2e6,
repMultiplier: 2, repMultiplier: 2,
hiredText: `Congratulations, you are now ${JobName.software7}`,
}, },
[JobName.IT0]: { [JobName.IT0]: {
nextPosition: JobName.IT1, // IT Analyst nextPosition: JobName.IT1, // IT Analyst
@ -256,6 +260,7 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
reqdHacking: 76, reqdHacking: 76,
reqdReputation: 800e3, reqdReputation: 800e3,
repMultiplier: 1.6, repMultiplier: 1.6,
hiredText: `Congratulations, you are now ${JobName.business4}`,
}, },
[JobName.business5]: { [JobName.business5]: {
nextPosition: null, nextPosition: null,
@ -269,6 +274,7 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
reqdHacking: 101, reqdHacking: 101,
reqdReputation: 3.2e6, reqdReputation: 3.2e6,
repMultiplier: 1.75, repMultiplier: 1.75,
hiredText: `Congratulations, you are now ${JobName.business5}`,
}, },
[JobName.security0]: { [JobName.security0]: {
nextPosition: JobName.security1, // Security Officer nextPosition: JobName.security1, // Security Officer
@ -457,6 +463,8 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
agilityExpGain: 0.02, agilityExpGain: 0.02,
charismaExpGain: 0.05, charismaExpGain: 0.05,
repMultiplier: 1, repMultiplier: 1,
applyText: `Apply to be a ${JobName.waiter}`,
hiredText: `Congratulations, you are now employed as a ${JobName.waiter}`,
}, },
[JobName.employee]: { [JobName.employee]: {
nextPosition: null, nextPosition: null,
@ -472,6 +480,8 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
agilityExpGain: 0.02, agilityExpGain: 0.02,
charismaExpGain: 0.04, charismaExpGain: 0.04,
repMultiplier: 1, repMultiplier: 1,
applyText: `Apply to be an ${JobName.employee}`,
hiredText: "Congratulations, you are now employed",
}, },
[JobName.softwareConsult0]: { [JobName.softwareConsult0]: {
nextPosition: JobName.softwareConsult1, // Senior Software Consultant nextPosition: JobName.softwareConsult1, // Senior Software Consultant
@ -483,6 +493,8 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
charismaExpGain: 0.03, charismaExpGain: 0.03,
reqdHacking: 51, reqdHacking: 51,
repMultiplier: 1, repMultiplier: 1,
applyText: `Pitch a Software Consulting contract`,
hiredText: `Congratulations, you got a contract as a ${JobName.softwareConsult0}`,
}, },
[JobName.softwareConsult1]: { [JobName.softwareConsult1]: {
nextPosition: null, nextPosition: null,
@ -495,6 +507,8 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
reqdHacking: 251, reqdHacking: 251,
reqdCharisma: 51, reqdCharisma: 51,
repMultiplier: 1.2, repMultiplier: 1.2,
applyText: `Pitch a Software Consulting contract`,
hiredText: `Congratulations, you got a contract as a ${JobName.softwareConsult1}`,
}, },
[JobName.businessConsult0]: { [JobName.businessConsult0]: {
nextPosition: JobName.businessConsult1, // Senior Business Consultant nextPosition: JobName.businessConsult1, // Senior Business Consultant
@ -507,6 +521,8 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
reqdHacking: 6, reqdHacking: 6,
reqdCharisma: 51, reqdCharisma: 51,
repMultiplier: 1, repMultiplier: 1,
applyText: `Pitch a Business Consulting contract`,
hiredText: `Congratulations, you got a contract as a ${JobName.businessConsult0}`,
}, },
[JobName.businessConsult1]: { [JobName.businessConsult1]: {
nextPosition: null, nextPosition: null,
@ -519,10 +535,12 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
reqdHacking: 51, reqdHacking: 51,
reqdCharisma: 226, reqdCharisma: 226,
repMultiplier: 1.2, repMultiplier: 1.2,
applyText: `Pitch a Business Consulting contract`,
hiredText: `Congratulations, you got a contract as a ${JobName.businessConsult1}`,
}, },
[JobName.waiterPT]: { [JobName.waiterPT]: {
nextPosition: null, nextPosition: null,
field: JobField.partTimeWaiter, field: JobField.waiter,
baseSalary: 20, baseSalary: 20,
strengthEffectiveness: 10, strengthEffectiveness: 10,
dexterityEffectiveness: 10, dexterityEffectiveness: 10,
@ -534,10 +552,13 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
agilityExpGain: 0.0075, agilityExpGain: 0.0075,
charismaExpGain: 0.04, charismaExpGain: 0.04,
repMultiplier: 1, repMultiplier: 1,
isPartTime: true,
applyText: `Apply to be a ${JobName.waiterPT}`,
hiredText: `Congratulations, you are now employed as a ${JobName.waiterPT}`,
}, },
[JobName.employeePT]: { [JobName.employeePT]: {
nextPosition: null, nextPosition: null,
field: JobField.partTimeEmployee, field: JobField.employee,
baseSalary: 20, baseSalary: 20,
strengthEffectiveness: 10, strengthEffectiveness: 10,
dexterityEffectiveness: 10, dexterityEffectiveness: 10,
@ -549,6 +570,9 @@ export function getCompanyPositionMetadata(): Record<JobName, CompanyPositionCto
agilityExpGain: 0.0075, agilityExpGain: 0.0075,
charismaExpGain: 0.03, charismaExpGain: 0.03,
repMultiplier: 1, repMultiplier: 1,
isPartTime: true,
applyText: `Apply to be a ${JobName.employeePT}`,
hiredText: "Congratulations, you are now employed part-time",
}, },
}; };
} }

@ -1,5 +1,7 @@
import { JobName } from "@enums"; import { JobName, JobField } from "@enums";
export const softwareJobs = [
export const JobTracks: Record<JobField, readonly JobName[]> = {
[JobField.software]: [
JobName.software0, JobName.software0,
JobName.software1, JobName.software1,
JobName.software2, JobName.software2,
@ -8,18 +10,33 @@ export const softwareJobs = [
JobName.software5, JobName.software5,
JobName.software6, JobName.software6,
JobName.software7, JobName.software7,
]; ],
export const itJobs = [JobName.IT0, JobName.IT1, JobName.IT2, JobName.IT3]; [JobField.softwareConsultant]: [JobName.softwareConsult0, JobName.softwareConsult1],
export const netEngJobs = [JobName.networkEng0, JobName.networkEng1]; [JobField.it]: [JobName.IT0, JobName.IT1, JobName.IT2, JobName.IT3],
export const businessJobs = [ [JobField.securityEngineer]: [JobName.securityEng],
[JobField.networkEngineer]: [JobName.networkEng0, JobName.networkEng1],
[JobField.business]: [
JobName.business0, JobName.business0,
JobName.business1, JobName.business1,
JobName.business2, JobName.business2,
JobName.business3, JobName.business3,
JobName.business4, JobName.business4,
JobName.business5, JobName.business5,
]; ],
export const securityJobs = [JobName.security0, JobName.security1, JobName.security2, JobName.security3]; [JobField.businessConsultant]: [JobName.businessConsult0, JobName.businessConsult1],
export const agentJobs = [JobName.agent0, JobName.agent1, JobName.agent2]; [JobField.security]: [JobName.security0, JobName.security1, JobName.security2, JobName.security3],
export const softwareConsultJobs = [JobName.softwareConsult0, JobName.softwareConsult1]; [JobField.agent]: [JobName.agent0, JobName.agent1, JobName.agent2],
export const businessConsultJobs = [JobName.businessConsult0, JobName.businessConsult1]; [JobField.employee]: [JobName.employee],
[JobField.partTimeEmployee]: [JobName.employeePT],
[JobField.waiter]: [JobName.waiter],
[JobField.partTimeWaiter]: [JobName.waiterPT],
} as const;
export const softwareJobs = JobTracks[JobField.software];
export const itJobs = JobTracks[JobField.it];
export const netEngJobs = JobTracks[JobField.networkEngineer];
export const businessJobs = JobTracks[JobField.business];
export const securityJobs = JobTracks[JobField.security];
export const agentJobs = JobTracks[JobField.agent];
export const softwareConsultJobs = JobTracks[JobField.softwareConsultant];
export const businessConsultJobs = JobTracks[JobField.businessConsultant];

@ -0,0 +1,74 @@
import * as React from "react";
import { Company } from "../Company";
import { CompanyPosition } from "../CompanyPosition";
import { Player } from "@player";
import { Typography } from "@mui/material";
import { ButtonWithTooltip } from "../../ui/Components/ButtonWithTooltip";
import { CompanyPositions } from "../CompanyPositions";
import { JobSummary } from "./JobSummary";
import { Requirement } from "../../ui/Components/Requirement";
import { getJobRequirements } from "../GetJobRequirements";
interface IProps {
company: Company;
position: CompanyPosition;
currentPosition: CompanyPosition | null;
}
/** React Component for a button that's used to apply for a job */
export function ApplyToJobButton(props: IProps): React.ReactElement {
const underqualified = !Player.isQualified(props.company, props.position);
const nextPos = props.position.nextPosition && CompanyPositions[props.position.nextPosition];
const overqualified = nextPos != null && Player.isQualified(props.company, nextPos);
const reqs = getJobRequirements(props.company, props.position);
const positionRequirements =
reqs.length == 0 ? (
<Typography>Accepting all applicants</Typography>
) : (
<>
<Typography>Requirements:</Typography>
{reqs.map((req, i) => (
<Requirement key={i} fulfilled={req.isSatisfied(Player)} value={req.toString()} />
))}
</>
);
const positionDetails = (
<>
<JobSummary company={props.company} position={props.position} overqualified={overqualified} />
{props.position.isPartTime && (
<Typography>
<br />
Part-time jobs have no penalty for
<br /> doing something else simultaneously.
</Typography>
)}
<br />
{positionRequirements}
{overqualified && (
<Typography>
<br />
You are overqualified for this position.
</Typography>
)}
</>
);
function applyForJob(): void {
Player.applyForJob(props.company, props.position);
}
return (
<ButtonWithTooltip
disabledTooltip={underqualified && positionDetails}
normalTooltip={positionDetails}
onClick={applyForJob}
tooltipProps={{ style: { display: "grid" } }}
>
{props.position.applyText}
</ButtonWithTooltip>
);
}

@ -0,0 +1,40 @@
import type { Company } from "../Company";
import type { CompanyPosition } from "../CompanyPosition";
import React from "react";
import { CompanyPositions } from "../CompanyPositions";
import { ApplyToJobButton } from "./ApplyToJobButton";
import { Player } from "@player";
interface IJobListingsProps {
company: Company;
currentPosition: CompanyPosition | null;
}
export function JobListings(props: IJobListingsProps): React.ReactElement {
const { company, currentPosition } = props;
const jobsToShow = [];
for (const jobName of company.companyPositions) {
const offeredPos = CompanyPositions[jobName];
const underqualified = !Player.isQualified(props.company, offeredPos);
const isCurrentPosition = jobName == props.currentPosition?.name;
const nextPos = offeredPos.nextPosition && CompanyPositions[offeredPos.nextPosition];
const overqualified = nextPos != null && Player.isQualified(props.company, nextPos);
const shouldShowApplyButton =
!isCurrentPosition && !overqualified && (!underqualified || offeredPos.requiredReputation == 0);
if (shouldShowApplyButton) {
jobsToShow.push(offeredPos);
}
}
return (
<>
{jobsToShow.map((position) => (
<ApplyToJobButton key={position.name} company={company} position={position} currentPosition={currentPosition} />
))}
</>
);
}

@ -0,0 +1,35 @@
import { Typography } from "@mui/material";
import { Player } from "@player";
import * as React from "react";
import { CONSTANTS } from "../../Constants";
import { calculateCompanyWorkStats } from "../../Work/Formulas";
import { MoneyRate } from "../../ui/React/MoneyRate";
import { ReputationRate } from "../../ui/React/ReputationRate";
import { StatsTable } from "../../ui/React/StatsTable";
import type { Company } from "../Company";
import type { CompanyPosition } from "../CompanyPosition";
const CYCLES_PER_SEC = 1000 / CONSTANTS.MilliPerCycle;
interface IJobSummaryProps {
company: Company;
position: CompanyPosition;
overqualified?: boolean;
}
export function JobSummary(props: IJobSummaryProps): React.ReactElement {
const workStats = calculateCompanyWorkStats(Player, props.company, props.position, props.company.favor);
return (
<>
<Typography>
<u>{props.position.name}</u>
</Typography>
<StatsTable
wide
rows={[
["Wages:", <MoneyRate key="money" money={workStats.money * CYCLES_PER_SEC} />],
["Reputation:", <ReputationRate key="rep" reputation={workStats.reputation * CYCLES_PER_SEC} />],
]}
/>
</>
);
}

@ -43,6 +43,20 @@ export function ServersDev(): React.ReactElement {
} }
} }
function backdoorServer(): void {
const s = GetServer(server);
if (s === null) return;
if (!(s instanceof Server)) return;
s.backdoorInstalled = true;
}
function backdoorAllServers(): void {
for (const s of GetAllServers()) {
if (!(s instanceof Server)) return;
s.backdoorInstalled = true;
}
}
function minSecurity(): void { function minSecurity(): void {
const s = GetServer(server); const s = GetServer(server);
if (s === null) return; if (s === null) return;
@ -118,6 +132,17 @@ export function ServersDev(): React.ReactElement {
<Button onClick={rootAllServers}>Root all</Button> <Button onClick={rootAllServers}>Root all</Button>
</td> </td>
</tr> </tr>
<tr>
<td>
<Typography>Backdoor:</Typography>
</td>
<td>
<Button onClick={backdoorServer}>Backdoor one</Button>
</td>
<td>
<Button onClick={backdoorAllServers}>Backdoor all</Button>
</td>
</tr>
<tr> <tr>
<td> <td>
<Typography>Security:</Typography> <Typography>Security:</Typography>

@ -1,40 +0,0 @@
import * as React from "react";
import { Company } from "../../Company/Company";
import { CompanyPosition } from "../../Company/CompanyPosition";
import { getJobRequirementText } from "../../Company/GetJobRequirementText";
import { Player } from "@player";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
interface IProps {
company: Company;
entryPosType: CompanyPosition;
onClick: (e: React.MouseEvent<HTMLElement>) => void;
text: string;
}
/** React Component for a button that's used to apply for a job */
export function ApplyToJobButton(props: IProps): React.ReactElement {
function getJobRequirementTooltip(): string {
const pos = Player.getNextCompanyPosition(props.company, props.entryPosType);
if (pos == null) {
return "";
}
if (!props.company.hasPosition(pos)) {
return "";
}
return getJobRequirementText(props.company, pos, true);
}
return (
<>
<Tooltip title={<span dangerouslySetInnerHTML={{ __html: getJobRequirementTooltip() }}></span>}>
<Button onClick={props.onClick}>{props.text}</Button>
</Tooltip>
</>
);
}

@ -4,15 +4,10 @@
* This subcomponent renders all of the buttons for applying to jobs at a company * This subcomponent renders all of the buttons for applying to jobs at a company
*/ */
import React, { useState } from "react"; import React, { useState } from "react";
import Typography from "@mui/material/Typography"; import { Paper, Box, Tooltip, Button, Typography } from "@mui/material";
import Button from "@mui/material/Button";
import Tooltip from "@mui/material/Tooltip";
import Box from "@mui/material/Box";
import { ApplyToJobButton } from "./ApplyToJobButton";
import { Locations } from "../Locations"; import { Locations } from "../Locations";
import { CompanyName, JobName, JobField } from "@enums"; import { CompanyName } from "@enums";
import { Companies } from "../../Company/Companies"; import { Companies } from "../../Company/Companies";
import { CompanyPositions } from "../../Company/CompanyPositions"; import { CompanyPositions } from "../../Company/CompanyPositions";
@ -26,6 +21,9 @@ import { QuitJobModal } from "../../Company/ui/QuitJobModal";
import { CompanyWork } from "../../Work/CompanyWork"; import { CompanyWork } from "../../Work/CompanyWork";
import { useRerender } from "../../ui/React/hooks"; import { useRerender } from "../../ui/React/hooks";
import { companyNameAsLocationName } from "../../Company/utils"; import { companyNameAsLocationName } from "../../Company/utils";
import { JobSummary } from "../../Company/ui/JobSummary";
import { StatsTable } from "../../ui/React/StatsTable";
import { JobListings } from "../../Company/ui/JobListings";
interface IProps { interface IProps {
companyName: CompanyName; companyName: CompanyName;
@ -54,101 +52,12 @@ export function CompanyLocation(props: IProps): React.ReactElement {
const hasMoreJobs = Object.keys(Player.jobs).length > 1; const hasMoreJobs = Object.keys(Player.jobs).length > 1;
/** /**
* CompanyPosition object for the job that the player holds at this company * CompanyPosition object for the job that the player holds at this company, if applicable
* (if he has one)
*/ */
const companyPosition = jobTitle ? CompanyPositions[jobTitle] : null; const currentPosition = jobTitle ? CompanyPositions[jobTitle] : null;
Player.location = companyNameAsLocationName(props.companyName); Player.location = companyNameAsLocationName(props.companyName);
function applyForAgentJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Player.applyForAgentJob();
rerender();
}
function applyForBusinessConsultantJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Player.applyForBusinessConsultantJob();
rerender();
}
function applyForBusinessJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Player.applyForBusinessJob();
rerender();
}
function applyForEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Player.applyForEmployeeJob();
rerender();
}
function applyForItJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Player.applyForItJob();
rerender();
}
function applyForPartTimeEmployeeJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Player.applyForPartTimeEmployeeJob();
rerender();
}
function applyForPartTimeWaiterJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Player.applyForPartTimeWaiterJob();
rerender();
}
function applyForSecurityJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Player.applyForSecurityJob();
rerender();
}
function applyForSoftwareConsultantJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Player.applyForSoftwareConsultantJob();
rerender();
}
function applyForSoftwareJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Player.applyForSoftwareJob();
rerender();
}
function applyForWaiterJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
return;
}
Player.applyForWaiterJob();
rerender();
}
function startInfiltration(e: React.MouseEvent<HTMLElement>): void { function startInfiltration(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
return; return;
@ -164,8 +73,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
return; return;
} }
const pos = companyPosition; if (currentPosition) {
if (pos) {
Player.startWork( Player.startWork(
new CompanyWork({ new CompanyWork({
singularity: false, singularity: false,
@ -189,26 +97,27 @@ export function CompanyLocation(props: IProps): React.ReactElement {
Router.toPage(Page.Job, { location: Locations[Object.keys(Player.jobs)[targetNum]] }); Router.toPage(Page.Job, { location: Locations[Object.keys(Player.jobs)[targetNum]] });
} }
const isEmployedHere = jobTitle != null; const isEmployedHere = currentPosition != null;
const favorGain = company.getFavorGain(); const favorGain = company.getFavorGain();
return ( return (
<> <>
<Box sx={{ display: "grid", width: "fit-content", minWidth: "25em" }}>
{isEmployedHere && hasMoreJobs && ( {isEmployedHere && hasMoreJobs && (
<> <Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Box> <Button onClick={() => switchLoc(-1)}>Previous Job</Button>
<Button onClick={() => switchLoc(-1)}>Previous</Button> <Button onClick={() => switchLoc(1)}>Next Job</Button>
<Button onClick={() => switchLoc(1)}>Next</Button>
</Box> </Box>
<br />
</>
)} )}
{isEmployedHere && ( {isEmployedHere && (
<> <Paper sx={{ p: "0.5em 1em", mt: 2, mb: 2 }}>
<Typography>Job Title: {jobTitle}</Typography> <JobSummary company={company} position={currentPosition} />
<Typography>-------------------------</Typography> <StatsTable
<Box display="flex"> wide
rows={[
[
<Tooltip <Tooltip
key="repLabel"
title={ title={
<> <>
You will have <Favor favor={company.favor + favorGain} /> company favor upon resetting after You will have <Favor favor={company.favor + favorGain} /> company favor upon resetting after
@ -216,32 +125,30 @@ export function CompanyLocation(props: IProps): React.ReactElement {
</> </>
} }
> >
<Typography> <Typography>Total reputation:</Typography>
Company reputation: <Reputation reputation={company.playerReputation} /> </Tooltip>,
</Typography> <Reputation key="rep" reputation={company.playerReputation} />,
</Tooltip> ],
</Box> [
<Typography>-------------------------</Typography>
<Box display="flex">
<Tooltip <Tooltip
key="favorLabel"
title={ title={
<> <>
Company favor increases the rate at which you earn reputation for this company by 1% per favor. Company favor increases the rate at which you earn reputation for this company by 1% per favor.
Company favor is gained whenever you reset after installing Augmentations. The amount of favor you Company favor is gained whenever you reset after installing Augmentations. The amount of favor
gain depends on how much reputation you have with the company. you gain depends on how much reputation you have with the company.
</> </>
} }
> >
<Typography> <Typography>Company favor:</Typography>
Company Favor: <Favor favor={company.favor} /> </Tooltip>,
</Typography> <Favor key="favor" favor={company.favor} />,
</Tooltip> ],
</Box> ]}
<Typography>-------------------------</Typography> />
<br /> </Paper>
</>
)} )}
<Box sx={{ display: "grid", width: "fit-content" }}>
{isEmployedHere && ( {isEmployedHere && (
<Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}> <Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<Button onClick={work}>Work</Button> <Button onClick={work}>Work</Button>
@ -255,94 +162,9 @@ export function CompanyLocation(props: IProps): React.ReactElement {
/> />
</Box> </Box>
)} )}
{company.hasAgentPositions() && (
<ApplyToJobButton {company.companyPositions.size > 0 && <JobListings company={company} currentPosition={currentPosition} />}
company={company}
entryPosType={CompanyPositions[JobName.agent0]}
onClick={applyForAgentJob}
text={"Apply for " + JobField.agent + " Job"}
/>
)}
{company.hasBusinessConsultantPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[JobName.businessConsult0]}
onClick={applyForBusinessConsultantJob}
text={"Apply for " + JobField.businessConsultant + " Job"}
/>
)}
{company.hasBusinessPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[JobName.business0]}
onClick={applyForBusinessJob}
text={"Apply for " + JobField.business + " Job"}
/>
)}
{company.hasEmployeePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[JobName.employee]}
onClick={applyForEmployeeJob}
text={"Apply to be an " + JobField.employee}
/>
)}
{company.hasEmployeePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[JobName.employeePT]}
onClick={applyForPartTimeEmployeeJob}
text={"Apply to be a " + JobField.partTimeEmployee}
/>
)}
{company.hasITPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[JobName.IT0]}
onClick={applyForItJob}
text={"Apply for " + JobField.it + " Job"}
/>
)}
{company.hasSecurityPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[JobName.security0]}
onClick={applyForSecurityJob}
text={"Apply for " + JobField.security + " Job"}
/>
)}
{company.hasSoftwareConsultantPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[JobName.softwareConsult0]}
onClick={applyForSoftwareConsultantJob}
text={"Apply for " + JobField.softwareConsultant + " Job"}
/>
)}
{company.hasSoftwarePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[JobName.software0]}
onClick={applyForSoftwareJob}
text={"Apply for " + JobField.software + " Job"}
/>
)}
{company.hasWaiterPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[JobName.waiter]}
onClick={applyForWaiterJob}
text={"Apply to be a " + JobField.waiter}
/>
)}
{company.hasWaiterPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[JobName.waiterPT]}
onClick={applyForPartTimeWaiterJob}
text={"Apply to be a " + JobField.partTimeWaiter}
/>
)}
{location.infiltrationData != null && <Button onClick={startInfiltration}>Infiltrate Company</Button>} {location.infiltrationData != null && <Button onClick={startInfiltration}>Infiltrate Company</Button>}
</Box> </Box>
</> </>

@ -8,7 +8,6 @@ import {
FactionName, FactionName,
FactionWorkType, FactionWorkType,
GymType, GymType,
JobField,
LocationName, LocationName,
UniversityClassType, UniversityClassType,
} from "@enums"; } from "@enums";
@ -55,8 +54,8 @@ import { Engine } from "../engine";
import { getEnumHelper } from "../utils/EnumHelper"; import { getEnumHelper } from "../utils/EnumHelper";
import { ScriptFilePath, resolveScriptFilePath } from "../Paths/ScriptFilePath"; import { ScriptFilePath, resolveScriptFilePath } from "../Paths/ScriptFilePath";
import { root } from "../Paths/Directory"; import { root } from "../Paths/Directory";
import { companyNameAsLocationName } from "../Company/utils";
import { getRecordEntries } from "../Types/Record"; import { getRecordEntries } from "../Types/Record";
import { JobTracks } from "../Company/data/JobTracks";
export function NetscriptSingularity(): InternalAPI<ISingularity> { export function NetscriptSingularity(): InternalAPI<ISingularity> {
const runAfterReset = function (cbScript: ScriptFilePath) { const runAfterReset = function (cbScript: ScriptFilePath) {
@ -687,20 +686,12 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
const job = CompanyPositions[positionName]; const job = CompanyPositions[positionName];
const res = { const res = {
name: CompanyPositions[positionName].name, name: job.name,
field: CompanyPositions[positionName].field, field: job.field,
nextPosition: CompanyPositions[positionName].nextPosition, nextPosition: job.nextPosition,
salary: CompanyPositions[positionName].baseSalary * company.salaryMultiplier, salary: job.baseSalary * company.salaryMultiplier,
requiredReputation: CompanyPositions[positionName].requiredReputation, requiredReputation: job.requiredReputation,
requiredSkills: { requiredSkills: job.requiredSkills(company.jobStatReqOffset),
hacking: job.requiredHacking > 0 ? job.requiredHacking + company.jobStatReqOffset : 0,
strength: job.requiredStrength > 0 ? job.requiredStrength + company.jobStatReqOffset : 0,
defense: job.requiredDefense > 0 ? job.requiredDefense + company.jobStatReqOffset : 0,
dexterity: job.requiredDexterity > 0 ? job.requiredDexterity + company.jobStatReqOffset : 0,
agility: job.requiredAgility > 0 ? job.requiredAgility + company.jobStatReqOffset : 0,
charisma: job.requiredCharisma > 0 ? job.requiredCharisma + company.jobStatReqOffset : 0,
intelligence: 0,
},
}; };
return res; return res;
}, },
@ -739,62 +730,16 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);
const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
const field = getEnumHelper("JobField").nsGetMember(ctx, _field, "field", { fuzzy: true }); const field = getEnumHelper("JobField").nsGetMember(ctx, _field, "field", { fuzzy: true });
const company = Companies[companyName];
const entryPos = CompanyPositions[JobTracks[field][0]];
Player.location = companyNameAsLocationName(companyName); const jobName = Player.applyForJob(company, entryPos, true);
let res; if (jobName) {
switch (field) { helpers.log(ctx, () => `You were offered a new job at '${companyName}' with position '${jobName}'`);
case JobField.software:
res = Player.applyForSoftwareJob(true);
break;
case JobField.softwareConsultant:
res = Player.applyForSoftwareConsultantJob(true);
break;
case JobField.it:
res = Player.applyForItJob(true);
break;
case JobField.securityEngineer:
res = Player.applyForSecurityEngineerJob(true);
break;
case JobField.networkEngineer:
res = Player.applyForNetworkEngineerJob(true);
break;
case JobField.business:
res = Player.applyForBusinessJob(true);
break;
case JobField.businessConsultant:
res = Player.applyForBusinessConsultantJob(true);
break;
case JobField.security:
res = Player.applyForSecurityJob(true);
break;
case JobField.agent:
res = Player.applyForAgentJob(true);
break;
case JobField.employee:
res = Player.applyForEmployeeJob(true);
break;
case JobField.partTimeEmployee:
res = Player.applyForPartTimeEmployeeJob(true);
break;
case JobField.waiter:
res = Player.applyForWaiterJob(true);
break;
case JobField.partTimeWaiter:
res = Player.applyForPartTimeWaiterJob(true);
break;
default:
helpers.log(ctx, () => `Invalid job: '${field}'.`);
return false;
}
if (res) {
helpers.log(
ctx,
() => `You were offered a new job at '${companyName}' with position '${Player.jobs[companyName]}'`,
);
} else { } else {
helpers.log(ctx, () => `You failed to get a new job/promotion at '${companyName}' in the '${field}' field.`); helpers.log(ctx, () => `You failed to get a new job/promotion at '${companyName}' in the '${field}' field.`);
} }
return res; return jobName;
}, },
quitJob: (ctx) => (_companyName) => { quitJob: (ctx) => (_companyName) => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);

@ -79,19 +79,6 @@ export class PlayerObject extends Person implements IPlayer {
startWork = workMethods.startWork; startWork = workMethods.startWork;
processWork = workMethods.processWork; processWork = workMethods.processWork;
finishWork = workMethods.finishWork; finishWork = workMethods.finishWork;
applyForSoftwareJob = generalMethods.applyForSoftwareJob;
applyForSoftwareConsultantJob = generalMethods.applyForSoftwareConsultantJob;
applyForItJob = generalMethods.applyForItJob;
applyForSecurityEngineerJob = generalMethods.applyForSecurityEngineerJob;
applyForNetworkEngineerJob = generalMethods.applyForNetworkEngineerJob;
applyForBusinessJob = generalMethods.applyForBusinessJob;
applyForBusinessConsultantJob = generalMethods.applyForBusinessConsultantJob;
applyForSecurityJob = generalMethods.applyForSecurityJob;
applyForAgentJob = generalMethods.applyForAgentJob;
applyForEmployeeJob = generalMethods.applyForEmployeeJob;
applyForPartTimeEmployeeJob = generalMethods.applyForPartTimeEmployeeJob;
applyForWaiterJob = generalMethods.applyForWaiterJob;
applyForPartTimeWaiterJob = generalMethods.applyForPartTimeWaiterJob;
applyForJob = generalMethods.applyForJob; applyForJob = generalMethods.applyForJob;
canAccessBladeburner = bladeburnerMethods.canAccessBladeburner; canAccessBladeburner = bladeburnerMethods.canAccessBladeburner;
canAccessCorporation = corporationMethods.canAccessCorporation; canAccessCorporation = corporationMethods.canAccessCorporation;

@ -20,8 +20,7 @@ import { CodingContractRewardType, ICodingContractReward } from "../../CodingCon
import { Company } from "../../Company/Company"; import { Company } from "../../Company/Company";
import { Companies } from "../../Company/Companies"; import { Companies } from "../../Company/Companies";
import { getNextCompanyPositionHelper } from "../../Company/GetNextCompanyPosition"; import { getNextCompanyPositionHelper } from "../../Company/GetNextCompanyPosition";
import { getJobRequirementText } from "../../Company/GetJobRequirementText"; import { getJobRequirements, getJobRequirementText } from "../../Company/GetJobRequirements";
import { CompanyPositions } from "../../Company/CompanyPositions";
import { CompanyPosition } from "../../Company/CompanyPosition"; import { CompanyPosition } from "../../Company/CompanyPosition";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { Exploit } from "../../Exploits/Exploit"; import { Exploit } from "../../Exploits/Exploit";
@ -51,7 +50,7 @@ import { SnackbarEvents } from "../../ui/React/Snackbar";
import { achievements } from "../../Achievements/Achievements"; import { achievements } from "../../Achievements/Achievements";
import { isCompanyWork } from "../../Work/CompanyWork"; import { isCompanyWork } from "../../Work/CompanyWork";
import { getEnumHelper, isMember } from "../../utils/EnumHelper"; import { isMember } from "../../utils/EnumHelper";
export function init(this: PlayerObject): void { export function init(this: PlayerObject): void {
/* Initialize Player's home computer */ /* Initialize Player's home computer */
@ -273,26 +272,34 @@ export function hospitalize(this: PlayerObject): number {
return cost; return cost;
} }
/********* Company job application **********/ /**
//Determines the job that the Player should get (if any) at the current company * Company job application. Determines the job that the Player should get (if any) at the given company.
//The 'sing' argument designates whether or not this is being called from * @param this The player instance
//the applyToCompany() Netscript Singularity function * @param company The company being applied to
export function applyForJob(this: PlayerObject, entryPosType: CompanyPosition, sing = false): boolean { * @param position A specific position
const companyName = getEnumHelper("CompanyName").getMember(this.location); * @param sing Whether this is being called from the applyToCompany() Netscript Singularity function
if (!companyName) return false; * @returns The name of the Job received (if any). May be higher or lower than the job applied to.
const company = Companies[companyName]; //Company being applied to */
let pos = entryPosType; export function applyForJob(
this: PlayerObject,
company: Company,
position: CompanyPosition,
sing = false,
): JobName | null {
if (!company) return null;
// Start searching the job track from the provided point (which may not be the entry position)
let pos = position;
if (!this.isQualified(company, pos)) { if (!this.isQualified(company, pos)) {
if (!sing) { if (!sing) {
dialogBoxCreate("Unfortunately, you do not qualify for this position\n" + getJobRequirementText(company, pos)); dialogBoxCreate(`Unfortunately, you do not qualify for this position.\n${getJobRequirementText(company, pos)}`);
} }
return false; return null;
} }
if (!company.hasPosition(pos)) { if (!company.hasPosition(pos)) {
console.error(`Company ${company.name} does not have position ${pos}. Player.applyToCompany() failed`); console.error(`Company ${company.name} does not have position ${pos}. Player.applyToCompany() failed.`);
return false; return null;
} }
let nextPos = getNextCompanyPositionHelper(pos); let nextPos = getNextCompanyPositionHelper(pos);
@ -305,59 +312,53 @@ export function applyForJob(this: PlayerObject, entryPosType: CompanyPosition, s
if (this.jobs[company.name] === pos.name) { if (this.jobs[company.name] === pos.name) {
if (!sing) { if (!sing) {
const nextPos = getNextCompanyPositionHelper(pos); const nextPos = getNextCompanyPositionHelper(pos);
if (nextPos == null || !company.hasPosition(nextPos)) { if (nextPos == null) {
dialogBoxCreate("You are already at the highest position for your field! No promotion available"); dialogBoxCreate(`You are already ${pos.name}! No promotion available`);
} else if (!company.hasPosition(nextPos)) {
dialogBoxCreate(
`You already have the highest ${pos.field} position available at ${company.name}! No promotion available`,
);
} else { } else {
const reqText = getJobRequirementText(company, nextPos); dialogBoxCreate(
dialogBoxCreate("Unfortunately, you do not qualify for a promotion\n" + reqText); `Unfortunately, you do not qualify for a promotion.\n${getJobRequirementText(company, nextPos)}`,
);
} }
} }
return false; return null;
} }
this.jobs[company.name] = pos.name; this.jobs[company.name] = pos.name;
if (!sing) { if (!sing) {
dialogBoxCreate(`Congratulations! You were offered a new job at ${company.name} for position ${pos.name}!`); dialogBoxCreate(`${pos.hiredText} at ${company.name}!`);
} }
return true; return pos.name;
} }
//Returns your next position at a company given the field (software, business, etc.) /**
* Get a job position that the player can apply for.
* @param this The player instance
* @param company The Company being applied to
* @param entryPosType Job field (Software, Business, etc)
* @returns The highest job the player can apply for at this company, if any
*/
export function getNextCompanyPosition( export function getNextCompanyPosition(
this: PlayerObject, this: PlayerObject,
company: Company, company: Company,
entryPosType: CompanyPosition, entryPosType: CompanyPosition,
): CompanyPosition | null { ): CompanyPosition | null {
const currCompany = Companies[company.name]; let pos: CompanyPosition | null = entryPosType;
let nextPos = getNextCompanyPositionHelper(pos);
//Not employed at this company, so return the entry position // Find the highest-level job in this category that the player is currently able to apply for.
if (currCompany == null || currCompany.name != company.name) { while (nextPos && company.hasPosition(nextPos) && this.isQualified(company, nextPos)) {
return entryPosType; pos = nextPos;
nextPos = getNextCompanyPositionHelper(pos);
} }
// If the player already has this position, return the one after that (if any).
//If the entry pos type and the player's current position have the same type, if (this.jobs[company.name] == pos.name) {
//return the player's "nextCompanyPosition". Otherwise return the entryposType pos = nextPos;
//Employed at this company, so just return the next position if it exists.
const currentPositionName = this.jobs[company.name];
if (!currentPositionName) return entryPosType;
const currentPosition = CompanyPositions[currentPositionName];
if (
(currentPosition.isSoftwareJob() && entryPosType.isSoftwareJob()) ||
(currentPosition.isITJob() && entryPosType.isITJob()) ||
(currentPosition.isBusinessJob() && entryPosType.isBusinessJob()) ||
(currentPosition.isSecurityEngineerJob() && entryPosType.isSecurityEngineerJob()) ||
(currentPosition.isNetworkEngineerJob() && entryPosType.isNetworkEngineerJob()) ||
(currentPosition.isSecurityJob() && entryPosType.isSecurityJob()) ||
(currentPosition.isAgentJob() && entryPosType.isAgentJob()) ||
(currentPosition.isSoftwareConsultantJob() && entryPosType.isSoftwareConsultantJob()) ||
(currentPosition.isBusinessConsultantJob() && entryPosType.isBusinessConsultantJob()) ||
(currentPosition.isPartTimeJob() && entryPosType.isPartTimeJob())
) {
return getNextCompanyPositionHelper(currentPosition);
} }
return pos;
return entryPosType;
} }
export function quitJob(this: PlayerObject, company: CompanyName): void { export function quitJob(this: PlayerObject, company: CompanyName): void {
@ -382,192 +383,10 @@ export function hasJob(this: PlayerObject): boolean {
return Boolean(Object.keys(this.jobs).length); return Boolean(Object.keys(this.jobs).length);
} }
export function applyForSoftwareJob(this: PlayerObject, sing = false): boolean {
return this.applyForJob(CompanyPositions[JobName.software0], sing);
}
export function applyForSoftwareConsultantJob(this: PlayerObject, sing = false): boolean {
return this.applyForJob(CompanyPositions[JobName.softwareConsult0], sing);
}
export function applyForItJob(this: PlayerObject, sing = false): boolean {
return this.applyForJob(CompanyPositions[JobName.IT0], sing);
}
export function applyForSecurityEngineerJob(this: PlayerObject, sing = false): boolean {
const companyName = getEnumHelper("CompanyName").getMember(this.location);
if (!companyName) return false;
const company = Companies[companyName];
if (this.isQualified(company, CompanyPositions[JobName.securityEng])) {
return this.applyForJob(CompanyPositions[JobName.securityEng], sing);
} else {
if (!sing) {
dialogBoxCreate("Unfortunately, you do not qualify for this position");
}
return false;
}
}
export function applyForNetworkEngineerJob(this: PlayerObject, sing = false): boolean {
const companyName = getEnumHelper("CompanyName").getMember(this.location);
if (!companyName) return false;
const company = Companies[companyName];
if (this.isQualified(company, CompanyPositions[JobName.networkEng0])) {
const pos = CompanyPositions[JobName.networkEng0];
return this.applyForJob(pos, sing);
} else {
if (!sing) {
dialogBoxCreate("Unfortunately, you do not qualify for this position");
}
return false;
}
}
export function applyForBusinessJob(this: PlayerObject, sing = false): boolean {
return this.applyForJob(CompanyPositions[JobName.business0], sing);
}
export function applyForBusinessConsultantJob(this: PlayerObject, sing = false): boolean {
return this.applyForJob(CompanyPositions[JobName.businessConsult0], sing);
}
export function applyForSecurityJob(this: PlayerObject, sing = false): boolean {
// TODO Police Jobs
// Indexing starts at 2 because 0 is for police officer
return this.applyForJob(CompanyPositions[JobName.security0], sing);
}
export function applyForAgentJob(this: PlayerObject, sing = false): boolean {
const companyName = getEnumHelper("CompanyName").getMember(this.location);
if (!companyName) return false;
const company = Companies[companyName];
if (this.isQualified(company, CompanyPositions[JobName.agent0])) {
const pos = CompanyPositions[JobName.agent0];
return this.applyForJob(pos, sing);
} else {
if (!sing) {
dialogBoxCreate("Unfortunately, you do not qualify for this position");
}
return false;
}
}
export function applyForEmployeeJob(this: PlayerObject, sing = false): boolean {
const companyName = getEnumHelper("CompanyName").getMember(this.location);
if (!companyName) return false;
const company = Companies[companyName];
const position = JobName.employee;
// Check if this company has the position
if (!company.hasPosition(position)) {
return false;
}
if (this.isQualified(company, CompanyPositions[position])) {
this.jobs[company.name] = position;
if (!sing) {
dialogBoxCreate("Congratulations, you are now employed at " + this.location);
}
return true;
} else {
if (!sing) {
dialogBoxCreate("Unfortunately, you do not qualify for this position");
}
return false;
}
}
export function applyForPartTimeEmployeeJob(this: PlayerObject, sing = false): boolean {
const companyName = getEnumHelper("CompanyName").getMember(this.location);
if (!companyName) return false;
const company = Companies[companyName];
const position = JobName.employeePT;
// Check if this company has the position
if (!company.hasPosition(position)) {
return false;
}
if (this.isQualified(company, CompanyPositions[position])) {
this.jobs[company.name] = position;
if (!sing) {
dialogBoxCreate("Congratulations, you are now employed part-time at " + this.location);
}
return true;
} else {
if (!sing) {
dialogBoxCreate("Unfortunately, you do not qualify for this position");
}
return false;
}
}
export function applyForWaiterJob(this: PlayerObject, sing = false): boolean {
const companyName = getEnumHelper("CompanyName").getMember(this.location);
if (!companyName) return false;
const company = Companies[companyName];
const position = JobName.waiter;
// Check if this company has the position
if (!company.hasPosition(position)) {
return false;
}
if (this.isQualified(company, CompanyPositions[position])) {
this.jobs[company.name] = position;
if (!sing) {
dialogBoxCreate("Congratulations, you are now employed as a waiter at " + this.location);
}
return true;
} else {
if (!sing) {
dialogBoxCreate("Unfortunately, you do not qualify for this position");
}
return false;
}
}
export function applyForPartTimeWaiterJob(this: PlayerObject, sing = false): boolean {
const companyName = getEnumHelper("CompanyName").getMember(this.location);
if (!companyName) return false;
const company = Companies[companyName];
const position = JobName.waiterPT;
// Check if this company has the position
if (!company.hasPosition(position)) {
return false;
}
if (this.isQualified(company, CompanyPositions[position])) {
this.jobs[company.name] = position;
if (!sing) {
dialogBoxCreate("Congratulations, you are now employed as a part-time waiter at " + this.location);
}
return true;
} else {
if (!sing) {
dialogBoxCreate("Unfortunately, you do not qualify for this position");
}
return false;
}
}
//Checks if the Player is qualified for a certain position //Checks if the Player is qualified for a certain position
export function isQualified(this: PlayerObject, company: Company, position: CompanyPosition): boolean { export function isQualified(this: PlayerObject, company: Company, position: CompanyPosition): boolean {
const offset = company.jobStatReqOffset; const reqs = getJobRequirements(company, position);
const reqHacking = position.requiredHacking > 0 ? position.requiredHacking + offset : 0; return reqs.every((req) => req.isSatisfied(this));
const reqStrength = position.requiredStrength > 0 ? position.requiredStrength + offset : 0;
const reqDefense = position.requiredDefense > 0 ? position.requiredDefense + offset : 0;
const reqDexterity = position.requiredDexterity > 0 ? position.requiredDexterity + offset : 0;
const reqAgility = position.requiredDexterity > 0 ? position.requiredDexterity + offset : 0;
const reqCharisma = position.requiredCharisma > 0 ? position.requiredCharisma + offset : 0;
return (
this.skills.hacking >= reqHacking &&
this.skills.strength >= reqStrength &&
this.skills.defense >= reqDefense &&
this.skills.dexterity >= reqDexterity &&
this.skills.agility >= reqAgility &&
this.skills.charisma >= reqCharisma &&
company.playerReputation >= position.requiredReputation
);
} }
/********** Reapplying Augmentations and Source File ***********/ /********** Reapplying Augmentations and Source File ***********/

@ -1893,7 +1893,7 @@ export interface Singularity {
* @param field - Field to which you want to apply. * @param field - Field to which you want to apply.
* @returns True if the player successfully get a job/promotion, and false otherwise. * @returns True if the player successfully get a job/promotion, and false otherwise.
*/ */
applyToCompany(companyName: CompanyName | `${CompanyName}`, field: JobField | `${JobField}`): boolean; applyToCompany(companyName: CompanyName | `${CompanyName}`, field: JobField | `${JobField}`): JobName | null;
/** /**
* Get company reputation. * Get company reputation.

@ -3,14 +3,13 @@ import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue
import { Player } from "@player"; import { Player } from "@player";
import { Work, WorkType } from "./Work"; import { Work, WorkType } from "./Work";
import { influenceStockThroughCompanyWork } from "../StockMarket/PlayerInfluencing"; import { influenceStockThroughCompanyWork } from "../StockMarket/PlayerInfluencing";
import { AugmentationName, CompanyName, JobName } from "@enums"; import { CompanyName, JobName } from "@enums";
import { calculateCompanyWorkStats } from "./Formulas"; import { calculateCompanyWorkStats } from "./Formulas";
import { Companies } from "../Company/Companies"; import { Companies } from "../Company/Companies";
import { applyWorkStats, scaleWorkStats, WorkStats } from "./WorkStats"; import { applyWorkStats, scaleWorkStats, WorkStats } from "./WorkStats";
import { Company } from "../Company/Company"; import { Company } from "../Company/Company";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reputation } from "../ui/React/Reputation"; import { Reputation } from "../ui/React/Reputation";
import { CONSTANTS } from "../Constants";
import { CompanyPositions } from "../Company/CompanyPositions"; import { CompanyPositions } from "../Company/CompanyPositions";
import { isMember } from "../utils/EnumHelper"; import { isMember } from "../utils/EnumHelper";
import { invalidWork } from "./InvalidWork"; import { invalidWork } from "./InvalidWork";
@ -34,10 +33,7 @@ export class CompanyWork extends Work {
} }
getGainRates(job: JobName): WorkStats { getGainRates(job: JobName): WorkStats {
let focusBonus = 1; const focusBonus = CompanyPositions[job].isPartTime ? 1 : Player.focusPenalty();
if (!Player.hasAugmentation(AugmentationName.NeuroreceptorManager, true)) {
focusBonus = Player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
const company = this.getCompany(); const company = this.getCompany();
return scaleWorkStats(calculateCompanyWorkStats(Player, company, CompanyPositions[job], company.favor), focusBonus); return scaleWorkStats(calculateCompanyWorkStats(Player, company, CompanyPositions[job], company.favor), focusBonus);
} }

@ -1,6 +1,6 @@
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { AugmentationName, CompletedProgramName } from "@enums"; import { CompletedProgramName } from "@enums";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { Player } from "@player"; import { Player } from "@player";
import { Programs } from "../Programs/Programs"; import { Programs } from "../Programs/Programs";
@ -55,10 +55,7 @@ export class CreateProgramWork extends Work {
} }
process(cycles: number): boolean { process(cycles: number): boolean {
let focusBonus = 1; const focusBonus = Player.focusPenalty();
if (!Player.hasAugmentation(AugmentationName.NeuroreceptorManager, true)) {
focusBonus = Player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
//Higher hacking skill will allow you to create programs faster //Higher hacking skill will allow you to create programs faster
const reqLvl = this.getProgram().create?.level ?? 0; const reqLvl = this.getProgram().create?.level ?? 0;
let skillMult = (Player.skills.hacking / reqLvl) * calculateIntelligenceBonus(Player.skills.intelligence, 3); //This should always be greater than 1; let skillMult = (Player.skills.hacking / reqLvl) * calculateIntelligenceBonus(Player.skills.intelligence, 3); //This should always be greater than 1;

@ -55,10 +55,10 @@ export class CrimeWork extends Work {
); );
return; return;
} }
const focusPenalty = Player.focusPenalty(); const focusBonus = Player.focusPenalty();
// exp times 2 because were trying to maintain the same numbers as before the conversion // exp times 2 because were trying to maintain the same numbers as before the conversion
// Technically the definition of Crimes should have the success numbers and failure should divide by 4 // Technically the definition of Crimes should have the success numbers and failure should divide by 4
let gains = scaleWorkStats(this.earnings(), focusPenalty, false); let gains = scaleWorkStats(this.earnings(), focusBonus, false);
let karma = crime.karma; let karma = crime.karma;
const success = determineCrimeSuccess(crime.type); const success = determineCrimeSuccess(crime.type);
if (success) { if (success) {
@ -75,7 +75,7 @@ export class CrimeWork extends Work {
Player.gainDexterityExp(gains.dexExp); Player.gainDexterityExp(gains.dexExp);
Player.gainAgilityExp(gains.agiExp); Player.gainAgilityExp(gains.agiExp);
Player.gainCharismaExp(gains.chaExp); Player.gainCharismaExp(gains.chaExp);
Player.karma -= karma * focusPenalty; Player.karma -= karma * focusBonus;
} }
finish(): void { finish(): void {

@ -4,12 +4,11 @@ import React from "react";
import { Work, WorkType } from "./Work"; import { Work, WorkType } from "./Work";
import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../utils/JSONReviver";
import { Player } from "@player"; import { Player } from "@player";
import { AugmentationName, FactionName, FactionWorkType } from "@enums"; import { FactionName, FactionWorkType } from "@enums";
import { Factions } from "../Faction/Factions"; import { Factions } from "../Faction/Factions";
import { applyWorkStats, scaleWorkStats, WorkStats } from "./WorkStats"; import { applyWorkStats, scaleWorkStats, WorkStats } from "./WorkStats";
import { dialogBoxCreate } from "../ui/React/DialogBox"; import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reputation } from "../ui/React/Reputation"; import { Reputation } from "../ui/React/Reputation";
import { CONSTANTS } from "../Constants";
import { calculateFactionExp, calculateFactionRep } from "./Formulas"; import { calculateFactionExp, calculateFactionRep } from "./Formulas";
import { getEnumHelper } from "../utils/EnumHelper"; import { getEnumHelper } from "../utils/EnumHelper";
@ -36,18 +35,12 @@ export class FactionWork extends Work {
} }
getReputationRate(): number { getReputationRate(): number {
let focusBonus = 1; const focusBonus = Player.focusPenalty();
if (!Player.hasAugmentation(AugmentationName.NeuroreceptorManager, true)) {
focusBonus = Player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
return calculateFactionRep(Player, this.factionWorkType, this.getFaction().favor) * focusBonus; return calculateFactionRep(Player, this.factionWorkType, this.getFaction().favor) * focusBonus;
} }
getExpRates(): WorkStats { getExpRates(): WorkStats {
let focusBonus = 1; const focusBonus = Player.focusPenalty();
if (!Player.hasAugmentation(AugmentationName.NeuroreceptorManager, true)) {
focusBonus = Player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
const rate = calculateFactionExp(Player, this.factionWorkType); const rate = calculateFactionExp(Player, this.factionWorkType);
return scaleWorkStats(rate, focusBonus, false); return scaleWorkStats(rate, focusBonus, false);
} }

@ -35,11 +35,7 @@ export class GraftingWork extends Work {
} }
process(cycles: number): boolean { process(cycles: number): boolean {
let focusBonus = 1; const focusBonus = Player.focusPenalty();
if (!Player.hasAugmentation(AugmentationName.NeuroreceptorManager, true)) {
focusBonus = Player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
this.cyclesWorked += cycles; this.cyclesWorked += cycles;
this.unitCompleted += CONSTANTS.MilliPerCycle * cycles * graftingIntBonus() * focusBonus; this.unitCompleted += CONSTANTS.MilliPerCycle * cycles * graftingIntBonus() * focusBonus;