TYPESAFETY: CompanyName (#650)

This commit is contained in:
Snarling 2023-07-11 09:23:17 -04:00 committed by GitHub
parent e4d3a9020e
commit e2655793f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1548 additions and 1516 deletions

@ -1,46 +1,30 @@
// Constructs all CompanyPosition objects using the metadata in data/companypositions.ts // Constructs all CompanyPosition objects using the metadata in data/companypositions.ts
import { companiesMetadata } from "./data/CompaniesMetadata"; import { getCompaniesMetadata } from "./data/CompaniesMetadata";
import { Company, IConstructorParams } from "./Company"; import { Company } from "./Company";
import { Reviver } from "../utils/JSONReviver"; import { Reviver, assertLoadingType } from "../utils/JSONReviver";
import { CompanyName } from "./Enums";
import { createEnumKeyedRecord } from "../Types/Record";
import { getEnumHelper } from "../utils/EnumHelper";
export let Companies: Record<string, Company> = {}; export const Companies: Record<CompanyName, Company> = (() => {
const metadata = getCompaniesMetadata();
function addCompany(params: IConstructorParams): void { return createEnumKeyedRecord(CompanyName, (name) => new Company(metadata[name]));
if (Companies[params.name] != null) { })();
console.warn(`Duplicate Company Position being defined: ${params.name}`);
}
Companies[params.name] = new Company(params);
}
// Used to initialize new Company objects for the Companies map
// Called when creating new game or after a prestige/reset
export function initCompanies(): void {
// Save Old Company data for 'favor'
const oldCompanies = Companies;
// Re-construct all Companies
Companies = {};
companiesMetadata.forEach((e) => {
addCompany(e);
});
// Reset data
for (const companyName of Object.keys(Companies)) {
const company = Companies[companyName];
const oldCompany = oldCompanies[companyName];
if (!oldCompany) {
// New game, so no OldCompanies data
company.favor = 0;
} else {
company.favor = oldCompanies[companyName].favor;
if (isNaN(company.favor)) {
company.favor = 0;
}
}
}
}
// Used to load Companies map from a save // Used to load Companies map from a save
export function loadCompanies(saveString: string): void { export function loadCompanies(saveString: string): void {
Companies = JSON.parse(saveString, Reviver); const loadedCompanies = JSON.parse(saveString, Reviver) as unknown;
// This loading method allows invalid data in player save, but just ignores anything invalid
if (!loadedCompanies) return;
if (typeof loadedCompanies !== "object") return;
for (const [loadedCompanyName, loadedCompany] of Object.entries(loadedCompanies) as [string, unknown][]) {
if (!getEnumHelper("CompanyName").isMember(loadedCompanyName)) continue;
if (!loadedCompany) continue;
if (typeof loadedCompany !== "object") continue;
const company = Companies[loadedCompanyName];
assertLoadingType<Company>(loadedCompany);
const { playerReputation: loadedRep, favor: loadedFavor } = loadedCompany;
if (typeof loadedRep === "number" && loadedRep > 0) company.playerReputation = loadedRep;
if (typeof loadedFavor === "number" && loadedFavor > 0) company.favor = loadedFavor;
}
} }

@ -1,50 +1,32 @@
import { CompanyPosition } from "./CompanyPosition"; import type { CompanyPosition } from "./CompanyPosition";
import * as posNames from "./data/JobTracks";
import { CompanyName, JobName } from "@enums";
import { favorToRep, repToFavor } from "../Faction/formulas/favor"; import { favorToRep, repToFavor } from "../Faction/formulas/favor";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export interface IConstructorParams { export interface CompanyCtorParams {
name: string; name: CompanyName;
info: string; info?: string;
companyPositions: Record<string, boolean>; companyPositions: JobName[];
expMultiplier: number; expMultiplier: number;
salaryMultiplier: number; salaryMultiplier: number;
jobStatReqOffset: number; jobStatReqOffset: number;
isMegacorp?: boolean; hasFaction?: boolean;
} }
const DefaultConstructorParams: IConstructorParams = {
name: "",
info: "",
companyPositions: {},
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
};
export class Company { export class Company {
/** Company name */ // Static info, initialized once at game load.
name: string;
/** Description and general information about company */ name = CompanyName.NoodleBar;
info: string; info = "";
hasFaction = false;
/** Has faction associated. */ companyPositions = new Set<JobName>();
isMegacorp: boolean;
/**
* Object that holds all available positions in this Company.
* Position names are held in keys.
* The values for the keys don't matter, but we'll make them booleans
*
* Must match names of Company Positions, defined in data/companypositionnames.ts
*/
companyPositions: Record<string, boolean>;
/** Company-specific multiplier for earnings */ /** Company-specific multiplier for earnings */
expMultiplier: number; expMultiplier = 1;
salaryMultiplier: number; salaryMultiplier = 1;
/** /**
* The additional levels of stats you need to quality for a job * The additional levels of stats you need to quality for a job
@ -53,79 +35,76 @@ export class Company {
* For example, the base stat requirement for an intern position is 1. * For example, the base stat requirement for an intern position is 1.
* But if a company has a offset of 200, then you would need stat(s) of 201 * But if a company has a offset of 200, then you would need stat(s) of 201
*/ */
jobStatReqOffset: number; jobStatReqOffset = 0;
/** Properties to track the player's progress in this company */ // Dynamic info, loaded from save and updated during game.
isPlayerEmployed: boolean; playerReputation = 0;
playerReputation: number; favor = 0;
favor: number;
constructor(p: IConstructorParams = DefaultConstructorParams) { constructor(p?: CompanyCtorParams) {
if (!p) return;
this.name = p.name; this.name = p.name;
this.info = p.info; if (p.info) this.info = p.info;
this.companyPositions = p.companyPositions; p.companyPositions.forEach((jobName) => this.companyPositions.add(jobName));
this.expMultiplier = p.expMultiplier; this.expMultiplier = p.expMultiplier;
this.salaryMultiplier = p.salaryMultiplier; this.salaryMultiplier = p.salaryMultiplier;
this.jobStatReqOffset = p.jobStatReqOffset; this.jobStatReqOffset = p.jobStatReqOffset;
if (p.hasFaction) this.hasFaction = true;
this.isPlayerEmployed = false;
this.playerReputation = 1;
this.favor = 0;
this.isMegacorp = false;
if (p.isMegacorp) this.isMegacorp = true;
} }
hasPosition(pos: CompanyPosition | string): boolean { hasPosition(pos: CompanyPosition | JobName): boolean {
return this.companyPositions[typeof pos === "string" ? pos : pos.name] != null; return this.companyPositions.has(typeof pos === "string" ? pos : pos.name);
} }
hasAgentPositions(): boolean { hasAgentPositions(): boolean {
return this.companyPositions[posNames.AgentCompanyPositions[0]] != null; return this.companyPositions.has(JobName.agent0);
} }
hasBusinessConsultantPositions(): boolean { hasBusinessConsultantPositions(): boolean {
return this.companyPositions[posNames.BusinessConsultantCompanyPositions[0]] != null; return this.companyPositions.has(JobName.businessConsult0);
} }
hasBusinessPositions(): boolean { hasBusinessPositions(): boolean {
return this.companyPositions[posNames.BusinessCompanyPositions[0]] != null; return this.companyPositions.has(JobName.business0);
} }
hasEmployeePositions(): boolean { hasEmployeePositions(): boolean {
return this.companyPositions[posNames.MiscCompanyPositions[1]] != null; return this.companyPositions.has(JobName.employee);
} }
hasITPositions(): boolean { hasITPositions(): boolean {
return this.companyPositions[posNames.ITCompanyPositions[0]] != null; return this.companyPositions.has(JobName.IT0);
} }
hasSecurityPositions(): boolean { hasSecurityPositions(): boolean {
return this.companyPositions[posNames.SecurityCompanyPositions[2]] != null; return this.companyPositions.has(JobName.security0);
} }
hasSoftwareConsultantPositions(): boolean { hasSoftwareConsultantPositions(): boolean {
return this.companyPositions[posNames.SoftwareConsultantCompanyPositions[0]] != null; return this.companyPositions.has(JobName.softwareConsult0);
} }
hasSoftwarePositions(): boolean { hasSoftwarePositions(): boolean {
return this.companyPositions[posNames.SoftwareCompanyPositions[0]] != null; return this.companyPositions.has(JobName.software0);
} }
hasWaiterPositions(): boolean { hasWaiterPositions(): boolean {
return this.companyPositions[posNames.MiscCompanyPositions[0]] != null; return this.companyPositions.has(JobName.waiter);
} }
gainFavor(): void { prestigeAugmentation(): void {
if (this.favor == null) { if (this.favor == null) this.favor = 0;
this.favor = 0;
}
this.favor += this.getFavorGain(); this.favor += this.getFavorGain();
this.playerReputation = 0;
}
prestigeSourceFile() {
this.favor = 0;
this.playerReputation = 0;
} }
getFavorGain(): number { getFavorGain(): number {
if (this.favor == null) { if (this.favor == null) this.favor = 0;
this.favor = 0;
}
const storedRep = Math.max(0, favorToRep(this.favor)); const storedRep = Math.max(0, favorToRep(this.favor));
const totalRep = storedRep + this.playerReputation; const totalRep = storedRep + this.playerReputation;
const newFavor = repToFavor(totalRep); const newFavor = repToFavor(totalRep);
@ -134,13 +113,16 @@ export class Company {
/** Serialize the current object to a JSON save state. */ /** Serialize the current object to a JSON save state. */
toJSON(): IReviverValue { toJSON(): IReviverValue {
return Generic_toJSON("Company", this); return Generic_toJSON("Company", this, Company.includedKeys);
} }
/** Initializes a Company from a JSON save state. */ /** Initializes a Company from a JSON save state. */
static fromJSON(value: IReviverValue): Company { static fromJSON(value: IReviverValue): Company {
return Generic_fromJSON(Company, value.data); return Generic_fromJSON(Company, value.data, Company.includedKeys);
} }
// Only these 3 keys are relevant to the save file
static includedKeys = ["favor", "playerReputation"] as const;
} }
constructorsForReviver.Company = Company; constructorsForReviver.Company = Company;

@ -1,10 +1,18 @@
import { Person as IPerson } from "@nsdefs"; import { Person as IPerson } from "@nsdefs";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import * as names from "./data/JobTracks";
import { JobName } from "@enums"; import { JobName } from "@enums";
import {
agentJobs,
businessConsultJobs,
businessJobs,
itJobs,
netEngJobs,
securityJobs,
softwareConsultJobs,
softwareJobs,
} from "./data/JobTracks";
export interface IConstructorParams { export interface CompanyPositionCtorParams {
name: JobName;
nextPosition: JobName | null; nextPosition: JobName | null;
baseSalary: number; baseSalary: number;
repMultiplier: number; repMultiplier: number;
@ -75,8 +83,8 @@ export class CompanyPosition {
agilityExpGain: number; agilityExpGain: number;
charismaExpGain: number; charismaExpGain: number;
constructor(p: IConstructorParams) { constructor(name: JobName, p: CompanyPositionCtorParams) {
this.name = p.name; this.name = name;
this.nextPosition = p.nextPosition; this.nextPosition = p.nextPosition;
this.baseSalary = p.baseSalary; this.baseSalary = p.baseSalary;
this.repMultiplier = p.repMultiplier; this.repMultiplier = p.repMultiplier;
@ -136,42 +144,42 @@ export class CompanyPosition {
} }
isSoftwareJob(): boolean { isSoftwareJob(): boolean {
return names.SoftwareCompanyPositions.includes(this.name); return softwareJobs.includes(this.name);
} }
isITJob(): boolean { isITJob(): boolean {
return names.ITCompanyPositions.includes(this.name); return itJobs.includes(this.name);
} }
isSecurityEngineerJob(): boolean { isSecurityEngineerJob(): boolean {
return names.SecurityEngineerCompanyPositions.includes(this.name); return this.name === JobName.securityEng;
} }
isNetworkEngineerJob(): boolean { isNetworkEngineerJob(): boolean {
return names.NetworkEngineerCompanyPositions.includes(this.name); return netEngJobs.includes(this.name);
} }
isBusinessJob(): boolean { isBusinessJob(): boolean {
return names.BusinessCompanyPositions.includes(this.name); return businessJobs.includes(this.name);
} }
isSecurityJob(): boolean { isSecurityJob(): boolean {
return names.SecurityCompanyPositions.includes(this.name); return securityJobs.includes(this.name);
} }
isAgentJob(): boolean { isAgentJob(): boolean {
return names.AgentCompanyPositions.includes(this.name); return agentJobs.includes(this.name);
} }
isSoftwareConsultantJob(): boolean { isSoftwareConsultantJob(): boolean {
return names.SoftwareConsultantCompanyPositions.includes(this.name); return softwareConsultJobs.includes(this.name);
} }
isBusinessConsultantJob(): boolean { isBusinessConsultantJob(): boolean {
return names.BusinessConsultantCompanyPositions.includes(this.name); return businessConsultJobs.includes(this.name);
} }
isPartTimeJob(): boolean { isPartTimeJob(): boolean {
return names.PartTimeCompanyPositions.includes(this.name); return [JobName.employeePT, JobName.waiterPT].includes(this.name);
} }
} }

@ -1,16 +1,10 @@
// Constructs all CompanyPosition objects using the metadata in data/companypositions.ts import { JobName } from "@enums";
import { companyPositionMetadata } from "./data/CompanyPositionsMetadata";
import { CompanyPosition, IConstructorParams } from "./CompanyPosition";
export const CompanyPositions: Record<string, CompanyPosition> = {}; import { getCompanyPositionMetadata } from "./data/CompanyPositionsMetadata";
import { CompanyPosition } from "./CompanyPosition";
import { createEnumKeyedRecord } from "../Types/Record";
function addCompanyPosition(params: IConstructorParams): void { export const CompanyPositions: Record<JobName, CompanyPosition> = (() => {
if (CompanyPositions[params.name] != null) { const metadata = getCompanyPositionMetadata();
console.warn(`Duplicate Company Position being defined: ${params.name}`); return createEnumKeyedRecord(JobName, (name) => new CompanyPosition(name, metadata[name]));
} })();
CompanyPositions[params.name] = new CompanyPosition(params);
}
companyPositionMetadata.forEach((e) => {
addCompanyPosition(e);
});

40
src/Company/Enums.ts Normal file

@ -0,0 +1,40 @@
export enum CompanyName {
ECorp = "ECorp",
MegaCorp = "MegaCorp",
BachmanAndAssociates = "Bachman & Associates",
BladeIndustries = "Blade Industries",
NWO = "NWO",
ClarkeIncorporated = "Clarke Incorporated",
OmniTekIncorporated = "OmniTek Incorporated",
FourSigma = "Four Sigma",
KuaiGongInternational = "KuaiGong International",
FulcrumTechnologies = "Fulcrum Technologies",
StormTechnologies = "Storm Technologies",
DefComm = "DefComm",
HeliosLabs = "Helios Labs",
VitaLife = "VitaLife",
IcarusMicrosystems = "Icarus Microsystems",
UniversalEnergy = "Universal Energy",
GalacticCybersystems = "Galactic Cybersystems",
AeroCorp = "AeroCorp",
OmniaCybersystems = "Omnia Cybersystems",
SolarisSpaceSystems = "Solaris Space Systems",
DeltaOne = "DeltaOne",
GlobalPharmaceuticals = "Global Pharmaceuticals",
NovaMedical = "Nova Medical",
CIA = "Central Intelligence Agency",
NSA = "National Security Agency",
WatchdogSecurity = "Watchdog Security",
LexoCorp = "LexoCorp",
RhoConstruction = "Rho Construction",
AlphaEnterprises = "Alpha Enterprises",
Police = "Aevum Police Headquarters",
SysCoreSecurities = "SysCore Securities",
CompuTek = "CompuTek",
NetLinkTechnologies = "NetLink Technologies",
CarmichaelSecurity = "Carmichael Security",
FoodNStuff = "FoodNStuff",
JoesGuns = "Joe's Guns",
OmegaSoftware = "Omega Software",
NoodleBar = "Noodle Bar",
}

@ -1,17 +1,13 @@
// Function that returns the next Company Position in the "ladder" // Function that returns the next Company Position in the "ladder"
// i.e. the next position to get promoted to // i.e. the next position to get promoted to
import { CompanyPosition } from "./CompanyPosition"; import type { CompanyPosition } from "./CompanyPosition";
import { CompanyPositions } from "./CompanyPositions"; import { CompanyPositions } from "./CompanyPositions";
export function getNextCompanyPositionHelper(currPos: CompanyPosition | null): CompanyPosition | null { export function getNextCompanyPositionHelper(currPos: CompanyPosition | null): CompanyPosition | null {
if (currPos == null) { if (!currPos) return null;
return null;
}
const nextPosName: string | null = currPos.nextPosition; const nextPosName = currPos.nextPosition;
if (nextPosName == null) { if (!nextPosName) return null;
return null;
}
return CompanyPositions[nextPosName]; return CompanyPositions[nextPosName];
} }

@ -1,449 +1,309 @@
import * as posNames from "./JobTracks"; import { CompanyCtorParams } from "../Company";
import { IConstructorParams } from "../Company";
import { LocationName } from "@enums"; import { CompanyName, JobName } from "@enums";
import {
agentJobs,
businessJobs,
itJobs,
netEngJobs,
securityJobs,
softwareConsultJobs,
softwareJobs,
} from "./JobTracks";
// These are grossly typed, need to address export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
// Create Objects containing Company Positions by category const allTechJobs: JobName[] = [...softwareJobs, ...itJobs, ...netEngJobs, JobName.securityEng];
// Will help in metadata construction later const softwareJobsToHeadOfEng: JobName[] = softwareJobs.slice(0, 6);
const AllSoftwarePositions: Record<string, boolean> = {}; const softwareJobsToLeadDev: JobName[] = softwareJobs.slice(0, 4);
const AllITPositions: Record<string, boolean> = {}; const businessJobToOpsManager: JobName[] = businessJobs.slice(0, 4);
const AllNetworkEngineerPositions: Record<string, boolean> = {};
const SecurityEngineerPositions: Record<string, boolean> = {};
const AllTechnologyPositions: Record<string, boolean> = {};
const AllBusinessPositions: Record<string, boolean> = {};
const AllAgentPositions: Record<string, boolean> = {};
const AllSecurityPositions: Record<string, boolean> = {};
const AllSoftwareConsultantPositions: Record<string, boolean> = {};
const AllBusinessConsultantPositions: Record<string, boolean> = {};
const SoftwarePositionsUpToHeadOfEngineering: Record<string, boolean> = {};
const SoftwarePositionsUpToLeadDeveloper: Record<string, boolean> = {};
const BusinessPositionsUpToOperationsManager: Record<string, boolean> = {};
const WaiterOnly: Record<string, boolean> = {};
const EmployeeOnly: Record<string, boolean> = {};
const PartTimeWaiterOnly: Record<string, boolean> = {};
const PartTimeEmployeeOnly: Record<string, boolean> = {};
const OperationsManagerOnly: Record<string, boolean> = {};
const CEOOnly: Record<string, boolean> = {};
posNames.SoftwareCompanyPositions.forEach((e) => { return {
AllSoftwarePositions[e] = true; [CompanyName.ECorp]: {
AllTechnologyPositions[e] = true; name: CompanyName.ECorp,
}); companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
expMultiplier: 3,
posNames.ITCompanyPositions.forEach((e) => { salaryMultiplier: 3,
AllITPositions[e] = true; jobStatReqOffset: 249,
AllTechnologyPositions[e] = true; },
}); [CompanyName.MegaCorp]: {
name: CompanyName.MegaCorp,
posNames.NetworkEngineerCompanyPositions.forEach((e) => { companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
AllNetworkEngineerPositions[e] = true; expMultiplier: 3,
AllTechnologyPositions[e] = true; salaryMultiplier: 3,
}); jobStatReqOffset: 249,
},
AllTechnologyPositions[posNames.SecurityEngineerCompanyPositions[0]] = true; [CompanyName.BachmanAndAssociates]: {
SecurityEngineerPositions[posNames.SecurityEngineerCompanyPositions[0]] = true; name: CompanyName.BachmanAndAssociates,
companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
posNames.BusinessCompanyPositions.forEach((e) => { expMultiplier: 2.6,
AllBusinessPositions[e] = true; salaryMultiplier: 2.6,
}); jobStatReqOffset: 224,
},
posNames.SecurityCompanyPositions.forEach((e) => { [CompanyName.BladeIndustries]: {
AllSecurityPositions[e] = true; name: CompanyName.BladeIndustries,
}); companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
expMultiplier: 2.75,
posNames.AgentCompanyPositions.forEach((e) => { salaryMultiplier: 2.75,
AllAgentPositions[e] = true; jobStatReqOffset: 224,
}); },
[CompanyName.NWO]: {
posNames.SoftwareConsultantCompanyPositions.forEach((e) => { name: CompanyName.NWO,
AllSoftwareConsultantPositions[e] = true; companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
}); expMultiplier: 2.75,
salaryMultiplier: 2.75,
posNames.BusinessConsultantCompanyPositions.forEach((e) => { jobStatReqOffset: 249,
AllBusinessConsultantPositions[e] = true; },
}); [CompanyName.ClarkeIncorporated]: {
name: CompanyName.ClarkeIncorporated,
for (let i = 0; i < posNames.SoftwareCompanyPositions.length; ++i) { companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
const e = posNames.SoftwareCompanyPositions[i]; expMultiplier: 2.25,
if (i <= 5) { salaryMultiplier: 2.25,
SoftwarePositionsUpToHeadOfEngineering[e] = true; jobStatReqOffset: 224,
} },
if (i <= 3) { [CompanyName.OmniTekIncorporated]: {
SoftwarePositionsUpToLeadDeveloper[e] = true; name: CompanyName.OmniTekIncorporated,
} companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
expMultiplier: 2.25,
salaryMultiplier: 2.25,
jobStatReqOffset: 224,
},
[CompanyName.FourSigma]: {
name: CompanyName.FourSigma,
companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
expMultiplier: 2.5,
salaryMultiplier: 2.5,
jobStatReqOffset: 224,
},
[CompanyName.KuaiGongInternational]: {
name: CompanyName.KuaiGongInternational,
companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
expMultiplier: 2.2,
salaryMultiplier: 2.2,
jobStatReqOffset: 224,
},
[CompanyName.FulcrumTechnologies]: {
name: CompanyName.FulcrumTechnologies,
companyPositions: [...allTechJobs, ...businessJobs],
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 224,
},
[CompanyName.StormTechnologies]: {
name: CompanyName.StormTechnologies,
companyPositions: [...allTechJobs, ...softwareConsultJobs, ...businessJobs],
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 199,
},
[CompanyName.DefComm]: {
name: CompanyName.DefComm,
companyPositions: [JobName.business5, ...allTechJobs, ...softwareConsultJobs],
expMultiplier: 1.75,
salaryMultiplier: 1.75,
jobStatReqOffset: 199,
},
[CompanyName.HeliosLabs]: {
name: CompanyName.HeliosLabs,
companyPositions: [JobName.business5, ...allTechJobs, ...softwareConsultJobs],
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 199,
},
[CompanyName.VitaLife]: {
name: CompanyName.VitaLife,
companyPositions: [...allTechJobs, ...businessJobs, ...softwareConsultJobs],
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 199,
},
[CompanyName.IcarusMicrosystems]: {
name: CompanyName.IcarusMicrosystems,
companyPositions: [...allTechJobs, ...businessJobs, ...softwareConsultJobs],
expMultiplier: 1.9,
salaryMultiplier: 1.9,
jobStatReqOffset: 199,
},
[CompanyName.UniversalEnergy]: {
name: CompanyName.UniversalEnergy,
companyPositions: [...allTechJobs, ...businessJobs, ...softwareConsultJobs],
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 199,
},
[CompanyName.GalacticCybersystems]: {
name: CompanyName.GalacticCybersystems,
companyPositions: [...allTechJobs, ...businessJobs, ...softwareConsultJobs],
expMultiplier: 1.9,
salaryMultiplier: 1.9,
jobStatReqOffset: 199,
},
[CompanyName.AeroCorp]: {
name: CompanyName.AeroCorp,
companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs],
expMultiplier: 1.7,
salaryMultiplier: 1.7,
jobStatReqOffset: 199,
},
[CompanyName.OmniaCybersystems]: {
name: CompanyName.OmniaCybersystems,
companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs],
expMultiplier: 1.7,
salaryMultiplier: 1.7,
jobStatReqOffset: 199,
},
[CompanyName.SolarisSpaceSystems]: {
name: CompanyName.SolarisSpaceSystems,
companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs],
expMultiplier: 1.7,
salaryMultiplier: 1.7,
jobStatReqOffset: 199,
},
[CompanyName.DeltaOne]: {
name: CompanyName.DeltaOne,
companyPositions: [JobName.business3, JobName.business5, ...allTechJobs, ...securityJobs],
expMultiplier: 1.6,
salaryMultiplier: 1.6,
jobStatReqOffset: 199,
},
[CompanyName.GlobalPharmaceuticals]: {
name: CompanyName.GlobalPharmaceuticals,
companyPositions: [...allTechJobs, ...businessJobs, ...softwareConsultJobs, ...securityJobs],
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 224,
},
[CompanyName.NovaMedical]: {
name: CompanyName.NovaMedical,
companyPositions: [...allTechJobs, ...businessJobs, ...softwareConsultJobs, ...securityJobs],
expMultiplier: 1.75,
salaryMultiplier: 1.75,
jobStatReqOffset: 199,
},
[CompanyName.CIA]: {
name: CompanyName.CIA,
companyPositions: [
...softwareJobsToHeadOfEng,
...netEngJobs,
JobName.securityEng,
...itJobs,
...securityJobs,
...agentJobs,
],
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 149,
},
[CompanyName.NSA]: {
name: CompanyName.NSA,
companyPositions: [
...softwareJobsToHeadOfEng,
...netEngJobs,
JobName.securityEng,
...itJobs,
...securityJobs,
...agentJobs,
],
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 149,
},
[CompanyName.WatchdogSecurity]: {
name: CompanyName.WatchdogSecurity,
companyPositions: [
...softwareJobsToHeadOfEng,
...netEngJobs,
...itJobs,
...securityJobs,
...agentJobs,
...softwareConsultJobs,
],
expMultiplier: 1.5,
salaryMultiplier: 1.5,
jobStatReqOffset: 124,
},
[CompanyName.LexoCorp]: {
name: CompanyName.LexoCorp,
companyPositions: [...allTechJobs, ...softwareConsultJobs, ...businessJobs, ...securityJobs],
expMultiplier: 1.4,
salaryMultiplier: 1.4,
jobStatReqOffset: 99,
},
[CompanyName.RhoConstruction]: {
name: CompanyName.RhoConstruction,
companyPositions: [...softwareJobsToLeadDev, ...businessJobToOpsManager],
expMultiplier: 1.3,
salaryMultiplier: 1.3,
jobStatReqOffset: 49,
},
[CompanyName.AlphaEnterprises]: {
name: CompanyName.AlphaEnterprises,
companyPositions: [...softwareJobsToLeadDev, ...businessJobToOpsManager, ...softwareConsultJobs],
expMultiplier: 1.5,
salaryMultiplier: 1.5,
jobStatReqOffset: 99,
},
[CompanyName.Police]: {
name: CompanyName.Police,
companyPositions: [...securityJobs, ...softwareJobsToLeadDev],
expMultiplier: 1.3,
salaryMultiplier: 1.3,
jobStatReqOffset: 99,
},
[CompanyName.SysCoreSecurities]: {
name: CompanyName.SysCoreSecurities,
companyPositions: [...allTechJobs],
expMultiplier: 1.3,
salaryMultiplier: 1.3,
jobStatReqOffset: 124,
},
[CompanyName.CompuTek]: {
name: CompanyName.CompuTek,
companyPositions: [...allTechJobs],
expMultiplier: 1.2,
salaryMultiplier: 1.2,
jobStatReqOffset: 74,
},
[CompanyName.NetLinkTechnologies]: {
name: CompanyName.NetLinkTechnologies,
companyPositions: [...allTechJobs],
expMultiplier: 1.2,
salaryMultiplier: 1.2,
jobStatReqOffset: 99,
},
[CompanyName.CarmichaelSecurity]: {
name: CompanyName.CarmichaelSecurity,
companyPositions: [...allTechJobs, ...softwareConsultJobs, ...agentJobs, ...securityJobs],
expMultiplier: 1.2,
salaryMultiplier: 1.2,
jobStatReqOffset: 74,
},
[CompanyName.FoodNStuff]: {
name: CompanyName.FoodNStuff,
companyPositions: [JobName.employee, JobName.employeePT],
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
},
[CompanyName.JoesGuns]: {
name: CompanyName.JoesGuns,
companyPositions: [JobName.employee, JobName.employeePT],
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
},
[CompanyName.OmegaSoftware]: {
name: CompanyName.OmegaSoftware,
companyPositions: [...softwareJobs, ...softwareConsultJobs, ...itJobs],
expMultiplier: 1.1,
salaryMultiplier: 1.1,
jobStatReqOffset: 49,
},
[CompanyName.NoodleBar]: {
name: CompanyName.NoodleBar,
companyPositions: [JobName.waiter, JobName.waiterPT],
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
},
};
} }
for (let i = 0; i < posNames.BusinessCompanyPositions.length; ++i) {
const e = posNames.BusinessCompanyPositions[i];
if (i <= 3) {
BusinessPositionsUpToOperationsManager[e] = true;
}
}
WaiterOnly[posNames.MiscCompanyPositions[0]] = true;
EmployeeOnly[posNames.MiscCompanyPositions[1]] = true;
PartTimeWaiterOnly[posNames.PartTimeCompanyPositions[0]] = true;
PartTimeEmployeeOnly[posNames.PartTimeCompanyPositions[1]] = true;
OperationsManagerOnly[posNames.BusinessCompanyPositions[3]] = true;
CEOOnly[posNames.BusinessCompanyPositions[5]] = true;
// Metadata
export const companiesMetadata: IConstructorParams[] = [
{
name: LocationName.AevumECorp,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),
expMultiplier: 3,
salaryMultiplier: 3,
jobStatReqOffset: 249,
},
{
name: LocationName.Sector12MegaCorp,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),
expMultiplier: 3,
salaryMultiplier: 3,
jobStatReqOffset: 249,
},
{
name: LocationName.AevumBachmanAndAssociates,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),
expMultiplier: 2.6,
salaryMultiplier: 2.6,
jobStatReqOffset: 224,
},
{
name: LocationName.Sector12BladeIndustries,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),
expMultiplier: 2.75,
salaryMultiplier: 2.75,
jobStatReqOffset: 224,
},
{
name: LocationName.VolhavenNWO,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),
expMultiplier: 2.75,
salaryMultiplier: 2.75,
jobStatReqOffset: 249,
},
{
name: LocationName.AevumClarkeIncorporated,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),
expMultiplier: 2.25,
salaryMultiplier: 2.25,
jobStatReqOffset: 224,
},
{
name: LocationName.VolhavenOmniTekIncorporated,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),
expMultiplier: 2.25,
salaryMultiplier: 2.25,
jobStatReqOffset: 224,
},
{
name: LocationName.Sector12FourSigma,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),
expMultiplier: 2.5,
salaryMultiplier: 2.5,
jobStatReqOffset: 224,
},
{
name: LocationName.ChongqingKuaiGongInternational,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSecurityPositions),
expMultiplier: 2.2,
salaryMultiplier: 2.2,
jobStatReqOffset: 224,
},
{
name: LocationName.AevumFulcrumTechnologies,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions),
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 224,
},
{
name: LocationName.IshimaStormTechnologies,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllSoftwareConsultantPositions, AllBusinessPositions),
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 199,
},
{
name: LocationName.NewTokyoDefComm,
info: "",
companyPositions: Object.assign({}, CEOOnly, AllTechnologyPositions, AllSoftwareConsultantPositions),
expMultiplier: 1.75,
salaryMultiplier: 1.75,
jobStatReqOffset: 199,
},
{
name: LocationName.VolhavenHeliosLabs,
info: "",
companyPositions: Object.assign({}, CEOOnly, AllTechnologyPositions, AllSoftwareConsultantPositions),
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 199,
},
{
name: LocationName.NewTokyoVitaLife,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSoftwareConsultantPositions),
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 199,
},
{
name: LocationName.Sector12IcarusMicrosystems,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSoftwareConsultantPositions),
expMultiplier: 1.9,
salaryMultiplier: 1.9,
jobStatReqOffset: 199,
},
{
name: LocationName.Sector12UniversalEnergy,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSoftwareConsultantPositions),
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 199,
},
{
name: LocationName.AevumGalacticCybersystems,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions, AllBusinessPositions, AllSoftwareConsultantPositions),
expMultiplier: 1.9,
salaryMultiplier: 1.9,
jobStatReqOffset: 199,
},
{
name: LocationName.AevumAeroCorp,
info: "",
companyPositions: Object.assign({}, CEOOnly, OperationsManagerOnly, AllTechnologyPositions, AllSecurityPositions),
expMultiplier: 1.7,
salaryMultiplier: 1.7,
jobStatReqOffset: 199,
},
{
name: LocationName.VolhavenOmniaCybersystems,
info: "",
companyPositions: Object.assign({}, CEOOnly, OperationsManagerOnly, AllTechnologyPositions, AllSecurityPositions),
expMultiplier: 1.7,
salaryMultiplier: 1.7,
jobStatReqOffset: 199,
},
{
name: LocationName.ChongqingSolarisSpaceSystems,
info: "",
companyPositions: Object.assign({}, CEOOnly, OperationsManagerOnly, AllTechnologyPositions, AllSecurityPositions),
expMultiplier: 1.7,
salaryMultiplier: 1.7,
jobStatReqOffset: 199,
},
{
name: LocationName.Sector12DeltaOne,
info: "",
companyPositions: Object.assign({}, CEOOnly, OperationsManagerOnly, AllTechnologyPositions, AllSecurityPositions),
expMultiplier: 1.6,
salaryMultiplier: 1.6,
jobStatReqOffset: 199,
},
{
name: LocationName.NewTokyoGlobalPharmaceuticals,
info: "",
companyPositions: Object.assign(
{},
AllTechnologyPositions,
AllBusinessPositions,
AllSoftwareConsultantPositions,
AllSecurityPositions,
),
expMultiplier: 1.8,
salaryMultiplier: 1.8,
jobStatReqOffset: 224,
},
{
name: LocationName.IshimaNovaMedical,
info: "",
companyPositions: Object.assign(
{},
AllTechnologyPositions,
AllBusinessPositions,
AllSoftwareConsultantPositions,
AllSecurityPositions,
),
expMultiplier: 1.75,
salaryMultiplier: 1.75,
jobStatReqOffset: 199,
},
{
name: LocationName.Sector12CIA,
info: "",
companyPositions: Object.assign(
{},
SoftwarePositionsUpToHeadOfEngineering,
AllNetworkEngineerPositions,
SecurityEngineerPositions,
AllITPositions,
AllSecurityPositions,
AllAgentPositions,
),
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 149,
},
{
name: LocationName.Sector12NSA,
info: "",
companyPositions: Object.assign(
{},
SoftwarePositionsUpToHeadOfEngineering,
AllNetworkEngineerPositions,
SecurityEngineerPositions,
AllITPositions,
AllSecurityPositions,
AllAgentPositions,
),
expMultiplier: 2,
salaryMultiplier: 2,
jobStatReqOffset: 149,
},
{
name: LocationName.AevumWatchdogSecurity,
info: "",
companyPositions: Object.assign(
{},
SoftwarePositionsUpToHeadOfEngineering,
AllNetworkEngineerPositions,
AllITPositions,
AllSecurityPositions,
AllAgentPositions,
AllSoftwareConsultantPositions,
),
expMultiplier: 1.5,
salaryMultiplier: 1.5,
jobStatReqOffset: 124,
},
{
name: LocationName.VolhavenLexoCorp,
info: "",
companyPositions: Object.assign(
{},
AllTechnologyPositions,
AllSoftwareConsultantPositions,
AllBusinessPositions,
AllSecurityPositions,
),
expMultiplier: 1.4,
salaryMultiplier: 1.4,
jobStatReqOffset: 99,
},
{
name: LocationName.AevumRhoConstruction,
info: "",
companyPositions: Object.assign({}, SoftwarePositionsUpToLeadDeveloper, BusinessPositionsUpToOperationsManager),
expMultiplier: 1.3,
salaryMultiplier: 1.3,
jobStatReqOffset: 49,
},
{
name: LocationName.Sector12AlphaEnterprises,
info: "",
companyPositions: Object.assign(
{},
SoftwarePositionsUpToLeadDeveloper,
BusinessPositionsUpToOperationsManager,
AllSoftwareConsultantPositions,
),
expMultiplier: 1.5,
salaryMultiplier: 1.5,
jobStatReqOffset: 99,
},
{
name: LocationName.AevumPolice,
info: "",
companyPositions: Object.assign({}, AllSecurityPositions, SoftwarePositionsUpToLeadDeveloper),
expMultiplier: 1.3,
salaryMultiplier: 1.3,
jobStatReqOffset: 99,
},
{
name: LocationName.VolhavenSysCoreSecurities,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions),
expMultiplier: 1.3,
salaryMultiplier: 1.3,
jobStatReqOffset: 124,
},
{
name: LocationName.VolhavenCompuTek,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions),
expMultiplier: 1.2,
salaryMultiplier: 1.2,
jobStatReqOffset: 74,
},
{
name: LocationName.AevumNetLinkTechnologies,
info: "",
companyPositions: Object.assign({}, AllTechnologyPositions),
expMultiplier: 1.2,
salaryMultiplier: 1.2,
jobStatReqOffset: 99,
},
{
name: LocationName.Sector12CarmichaelSecurity,
info: "",
companyPositions: Object.assign(
{},
AllTechnologyPositions,
AllSoftwareConsultantPositions,
AllAgentPositions,
AllSecurityPositions,
),
expMultiplier: 1.2,
salaryMultiplier: 1.2,
jobStatReqOffset: 74,
},
{
name: LocationName.Sector12FoodNStuff,
info: "",
companyPositions: Object.assign({}, EmployeeOnly, PartTimeEmployeeOnly),
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
},
{
name: LocationName.Sector12JoesGuns,
info: "",
companyPositions: Object.assign({}, EmployeeOnly, PartTimeEmployeeOnly),
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
},
{
name: LocationName.IshimaOmegaSoftware,
info: "",
companyPositions: Object.assign({}, AllSoftwarePositions, AllSoftwareConsultantPositions, AllITPositions),
expMultiplier: 1.1,
salaryMultiplier: 1.1,
jobStatReqOffset: 49,
},
{
name: LocationName.NewTokyoNoodleBar,
info: "",
companyPositions: Object.assign({}, WaiterOnly, PartTimeWaiterOnly),
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
},
];

File diff suppressed because it is too large Load Diff

@ -1,7 +1,5 @@
import { JobName } from "@enums"; import { JobName } from "@enums";
export const softwareJobs = [
// This entire file can be reworked to
export const SoftwareCompanyPositions: JobName[] = [
JobName.software0, JobName.software0,
JobName.software1, JobName.software1,
JobName.software2, JobName.software2,
@ -11,14 +9,9 @@ export const SoftwareCompanyPositions: JobName[] = [
JobName.software6, JobName.software6,
JobName.software7, JobName.software7,
]; ];
export const itJobs = [JobName.IT0, JobName.IT1, JobName.IT2, JobName.IT3];
export const ITCompanyPositions: JobName[] = [JobName.IT0, JobName.IT1, JobName.IT2, JobName.IT3]; export const netEngJobs = [JobName.networkEng0, JobName.networkEng1];
export const businessJobs = [
export const SecurityEngineerCompanyPositions: JobName[] = [JobName.securityEng];
export const NetworkEngineerCompanyPositions: JobName[] = [JobName.networkEng0, JobName.networkEng1];
export const BusinessCompanyPositions: JobName[] = [
JobName.business0, JobName.business0,
JobName.business1, JobName.business1,
JobName.business2, JobName.business2,
@ -26,22 +19,7 @@ export const BusinessCompanyPositions: JobName[] = [
JobName.business4, JobName.business4,
JobName.business5, JobName.business5,
]; ];
export const securityJobs = [JobName.security0, JobName.security1, JobName.security2, JobName.security3];
export const SecurityCompanyPositions: JobName[] = [ export const agentJobs = [JobName.agent0, JobName.agent1, JobName.agent2];
JobName.security0, export const softwareConsultJobs = [JobName.softwareConsult0, JobName.softwareConsult1];
JobName.security1, export const businessConsultJobs = [JobName.businessConsult0, JobName.businessConsult1];
JobName.security2,
JobName.security3,
JobName.security4,
JobName.security5,
];
export const AgentCompanyPositions: JobName[] = [JobName.agent0, JobName.agent1, JobName.agent2];
export const MiscCompanyPositions: JobName[] = [JobName.waiter, JobName.employee];
export const SoftwareConsultantCompanyPositions: JobName[] = [JobName.softwareConsult0, JobName.softwareConsult1];
export const BusinessConsultantCompanyPositions: JobName[] = [JobName.businessConsult0, JobName.businessConsult1];
export const PartTimeCompanyPositions: JobName[] = [JobName.waiterPT, JobName.employeePT];

@ -4,18 +4,19 @@ import { Player } from "@player";
import { Modal } from "../../ui/React/Modal"; import { Modal } from "../../ui/React/Modal";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { CompanyName } from "../Enums";
interface IProps { interface IProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
locName: string; companyName: CompanyName;
company: Company; company: Company;
onQuit: () => void; onQuit: () => void;
} }
export function QuitJobModal(props: IProps): React.ReactElement { export function QuitJobModal(props: IProps): React.ReactElement {
function quit(): void { function quit(): void {
Player.quitJob(props.locName); Player.quitJob(props.companyName);
props.onQuit(); props.onQuit();
props.onClose(); props.onClose();
} }

11
src/Company/utils.ts Normal file

@ -0,0 +1,11 @@
import type { CompanyName, LocationName } from "@enums";
type LocationNameString = `${LocationName}`;
type CompanyNameString = `${CompanyName}`;
type CompanyNamesAreAllLocationNames = CompanyNameString extends LocationNameString ? true : false;
const __companyNameCheck: CompanyNamesAreAllLocationNames = true;
export function companyNameAsLocationName(companyName: CompanyName): LocationName {
// Due to the check above, we know that all company names are valid location names.
return companyName as unknown as LocationName;
}

@ -9,64 +9,67 @@ import Button from "@mui/material/Button";
import Select, { SelectChangeEvent } from "@mui/material/Select"; import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import { FactionName } from "@enums"; import { CompanyName } from "@enums";
import { Companies as AllCompanies } from "../../Company/Companies"; import { Companies } from "../../Company/Companies";
import { Adjuster } from "./Adjuster"; import { Adjuster } from "./Adjuster";
import { isMember } from "../../utils/EnumHelper";
import { getRecordValues } from "../../Types/Record";
const bigNumber = 1e12; const bigNumber = 1e12;
export function CompaniesDev(): React.ReactElement { export function CompaniesDev(): React.ReactElement {
const [company, setCompany] = useState(FactionName.ECorp as string); const [companyName, setCompanyName] = useState(CompanyName.ECorp);
function setCompanyDropdown(event: SelectChangeEvent): void { function setCompanyDropdown(event: SelectChangeEvent): void {
setCompany(event.target.value); if (!isMember("CompanyName", event.target.value)) return;
setCompanyName(event.target.value);
} }
function resetCompanyRep(): void { function resetCompanyRep(): void {
AllCompanies[company].playerReputation = 0; Companies[companyName].playerReputation = 0;
} }
function modifyCompanyRep(modifier: number): (x: number) => void { function modifyCompanyRep(modifier: number): (x: number) => void {
return function (reputation: number): void { return function (reputation: number): void {
const c = AllCompanies[company]; const company = Companies[companyName];
if (c != null && !isNaN(reputation)) { if (!isNaN(reputation)) {
c.playerReputation += reputation * modifier; company.playerReputation += reputation * modifier;
} }
}; };
} }
function modifyCompanyFavor(modifier: number): (x: number) => void { function modifyCompanyFavor(modifier: number): (x: number) => void {
return function (favor: number): void { return function (favor: number): void {
const c = AllCompanies[company]; const company = Companies[companyName];
if (c != null && !isNaN(favor)) { if (!isNaN(favor)) {
c.favor += favor * modifier; company.favor += favor * modifier;
} }
}; };
} }
function resetCompanyFavor(): void { function resetCompanyFavor(): void {
AllCompanies[company].favor = 0; Companies[companyName].favor = 0;
} }
function tonsOfRepCompanies(): void { function tonsOfRepCompanies(): void {
for (const c of Object.keys(AllCompanies)) { for (const company of getRecordValues(Companies)) {
AllCompanies[c].playerReputation = bigNumber; company.playerReputation = bigNumber;
} }
} }
function resetAllRepCompanies(): void { function resetAllRepCompanies(): void {
for (const c of Object.keys(AllCompanies)) { for (const company of getRecordValues(Companies)) {
AllCompanies[c].playerReputation = 0; company.playerReputation = 0;
} }
} }
function tonsOfFavorCompanies(): void { function tonsOfFavorCompanies(): void {
for (const c of Object.keys(AllCompanies)) { for (const company of getRecordValues(Companies)) {
AllCompanies[c].favor = bigNumber; company.favor = bigNumber;
} }
} }
function resetAllFavorCompanies(): void { function resetAllFavorCompanies(): void {
for (const c of Object.keys(AllCompanies)) { for (const company of getRecordValues(Companies)) {
AllCompanies[c].favor = 0; company.favor = 0;
} }
} }
@ -83,8 +86,8 @@ export function CompaniesDev(): React.ReactElement {
<Typography>Company:</Typography> <Typography>Company:</Typography>
</td> </td>
<td colSpan={3}> <td colSpan={3}>
<Select id="dev-companies-dropdown" onChange={setCompanyDropdown} value={company}> <Select id="dev-companies-dropdown" onChange={setCompanyDropdown} value={companyName}>
{Object.values(AllCompanies).map((company) => ( {Object.values(Companies).map((company) => (
<MenuItem key={company.name} value={company.name}> <MenuItem key={company.name} value={company.name}>
{company.name} {company.name}
</MenuItem> </MenuItem>

@ -2,6 +2,7 @@
export * from "./Augmentation/Enums"; export * from "./Augmentation/Enums";
export * from "./Bladeburner/Enums"; export * from "./Bladeburner/Enums";
export * from "./Company/Enums";
export * from "./Corporation/Enums"; export * from "./Corporation/Enums";
export * from "./Crime/Enums"; export * from "./Crime/Enums";
export * from "./Faction/Enums"; export * from "./Faction/Enums";

@ -21,7 +21,6 @@ for (const aug of getRecordValues(Augmentations)) {
} }
export function loadFactions(saveString: string): void { export function loadFactions(saveString: string): void {
// The only information that should be loaded from player save is
const loadedFactions = JSON.parse(saveString, Reviver) as unknown; const loadedFactions = JSON.parse(saveString, Reviver) as unknown;
// This loading method allows invalid data in player save, but just ignores anything invalid // This loading method allows invalid data in player save, but just ignores anything invalid
if (!loadedFactions) return; if (!loadedFactions) return;

@ -22,6 +22,7 @@ import { Player } from "@player";
import { GetServer } from "../Server/AllServers"; import { GetServer } from "../Server/AllServers";
import { Server } from "../Server/Server"; import { Server } from "../Server/Server";
import { Companies } from "../Company/Companies"; import { Companies } from "../Company/Companies";
import { isMember } from "../utils/EnumHelper";
// Returns a boolean indicating whether the player has Hacknet Servers // Returns a boolean indicating whether the player has Hacknet Servers
// (the upgraded form of Hacknet Nodes) // (the upgraded form of Hacknet Nodes)
@ -564,7 +565,7 @@ export function purchaseHashUpgrade(upgName: string, upgTarget: string, count =
break; break;
} }
case "Company Favor": { case "Company Favor": {
if (!(upgTarget in Companies)) { if (!isMember("CompanyName", upgTarget)) {
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`); console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
throw new Error(`'${upgTarget}' is not a company.`); throw new Error(`'${upgTarget}' is not a company.`);
} }

@ -15,8 +15,9 @@ import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { SelectChangeEvent } from "@mui/material/Select"; import { SelectChangeEvent } from "@mui/material/Select";
import { FactionName } from "@enums"; import { CompanyName, FactionName } from "@enums";
import { companiesMetadata } from "../../Company/data/CompaniesMetadata"; import { PartialRecord } from "../../Types/Record";
import { isMember } from "../../utils/EnumHelper";
interface IProps { interface IProps {
hashManager: HashManager; hashManager: HashManager;
@ -24,8 +25,9 @@ interface IProps {
rerender: () => void; rerender: () => void;
} }
// Key is the hash upgrade name
const serversMap: Record<string, string> = {}; const serversMap: Record<string, string> = {};
const companiesMap: Record<string, string> = {}; const companiesMap: PartialRecord<string, CompanyName> = {};
export function HacknetUpgradeElem(props: IProps): React.ReactElement { export function HacknetUpgradeElem(props: IProps): React.ReactElement {
const [selectedServer, setSelectedServer] = useState( const [selectedServer, setSelectedServer] = useState(
@ -35,10 +37,9 @@ export function HacknetUpgradeElem(props: IProps): React.ReactElement {
setSelectedServer(event.target.value); setSelectedServer(event.target.value);
serversMap[props.upg.name] = event.target.value; serversMap[props.upg.name] = event.target.value;
} }
const [selectedCompany, setSelectedCompany] = useState( const [selectedCompany, setSelectedCompany] = useState(companiesMap[props.upg.name] ?? CompanyName.NoodleBar);
companiesMap[props.upg.name] ? companiesMap[props.upg.name] : companiesMetadata[0].name, function changeTargetCompany(event: SelectChangeEvent<CompanyName>): void {
); if (!isMember("CompanyName", event.target.value)) return;
function changeTargetCompany(event: SelectChangeEvent): void {
setSelectedCompany(event.target.value); setSelectedCompany(event.target.value);
companiesMap[props.upg.name] = event.target.value; companiesMap[props.upg.name] = event.target.value;
} }

@ -12,11 +12,10 @@ import Box from "@mui/material/Box";
import { ApplyToJobButton } from "./ApplyToJobButton"; import { ApplyToJobButton } from "./ApplyToJobButton";
import { Locations } from "../Locations"; import { Locations } from "../Locations";
import { LocationName } from "@enums"; import { CompanyName, JobName } from "@enums";
import { Companies } from "../../Company/Companies"; import { Companies } from "../../Company/Companies";
import { CompanyPositions } from "../../Company/CompanyPositions"; import { CompanyPositions } from "../../Company/CompanyPositions";
import * as posNames from "../../Company/data/JobTracks";
import { Reputation } from "../../ui/React/Reputation"; import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor"; import { Favor } from "../../ui/React/Favor";
@ -26,9 +25,10 @@ import { Player } from "@player";
import { QuitJobModal } from "../../Company/ui/QuitJobModal"; 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";
interface IProps { interface IProps {
locName: LocationName; companyName: CompanyName;
} }
export function CompanyLocation(props: IProps): React.ReactElement { export function CompanyLocation(props: IProps): React.ReactElement {
@ -39,17 +39,18 @@ export function CompanyLocation(props: IProps): React.ReactElement {
* We'll keep a reference to the Company that this component is being rendered for, * We'll keep a reference to the Company that this component is being rendered for,
* so we don't have to look it up every time * so we don't have to look it up every time
*/ */
const company = Companies[props.locName]; const company = Companies[props.companyName];
if (company == null) throw new Error(`CompanyLocation component constructed with invalid company: ${props.locName}`); if (company == null)
throw new Error(`CompanyLocation component constructed with invalid company: ${props.companyName}`);
/** Reference to the Location that this component is being rendered for */ /** Reference to the Location that this component is being rendered for */
const location = Locations[props.locName]; const location = Locations[props.companyName];
if (location == null) { if (location == null) {
throw new Error(`CompanyLocation component constructed with invalid location: ${props.locName}`); throw new Error(`CompanyLocation component constructed with invalid location: ${props.companyName}`);
} }
/** Name of company position that player holds, if applicable */ /** Name of company position that player holds, if applicable */
const jobTitle = Player.jobs[props.locName] ? Player.jobs[props.locName] : null; const jobTitle = Player.jobs[props.companyName] ? Player.jobs[props.companyName] : null;
/** /**
* CompanyPosition object for the job that the player holds at this company * CompanyPosition object for the job that the player holds at this company
@ -57,7 +58,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
*/ */
const companyPosition = jobTitle ? CompanyPositions[jobTitle] : null; const companyPosition = jobTitle ? CompanyPositions[jobTitle] : null;
Player.location = props.locName; Player.location = companyNameAsLocationName(props.companyName);
function applyForAgentJob(e: React.MouseEvent<HTMLElement>): void { function applyForAgentJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) { if (!e.isTrusted) {
@ -152,7 +153,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
return; return;
} }
if (!location.infiltrationData) if (!location.infiltrationData)
throw new Error(`trying to start infiltration at ${props.locName} but the infiltrationData is null`); throw new Error(`trying to start infiltration at ${props.companyName} but the infiltrationData is null`);
Router.toPage(Page.Infiltration, { location }); Router.toPage(Page.Infiltration, { location });
} }
@ -167,7 +168,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
Player.startWork( Player.startWork(
new CompanyWork({ new CompanyWork({
singularity: false, singularity: false,
companyName: props.locName, companyName: props.companyName,
}), }),
); );
Player.startFocusing(); Player.startFocusing();
@ -224,7 +225,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
<Button onClick={work}>Work</Button> <Button onClick={work}>Work</Button>
<Button onClick={() => setQuitOpen(true)}>Quit</Button> <Button onClick={() => setQuitOpen(true)}>Quit</Button>
<QuitJobModal <QuitJobModal
locName={props.locName} companyName={props.companyName}
company={company} company={company}
onQuit={rerender} onQuit={rerender}
open={quitOpen} open={quitOpen}
@ -235,7 +236,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasAgentPositions() && ( {company.hasAgentPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={company} company={company}
entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]} entryPosType={CompanyPositions[JobName.agent0]}
onClick={applyForAgentJob} onClick={applyForAgentJob}
text={"Apply for Agent Job"} text={"Apply for Agent Job"}
/> />
@ -243,7 +244,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasBusinessConsultantPositions() && ( {company.hasBusinessConsultantPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={company} company={company}
entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]} entryPosType={CompanyPositions[JobName.businessConsult0]}
onClick={applyForBusinessConsultantJob} onClick={applyForBusinessConsultantJob}
text={"Apply for Business Consultant Job"} text={"Apply for Business Consultant Job"}
/> />
@ -251,7 +252,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasBusinessPositions() && ( {company.hasBusinessPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={company} company={company}
entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]} entryPosType={CompanyPositions[JobName.business0]}
onClick={applyForBusinessJob} onClick={applyForBusinessJob}
text={"Apply for Business Job"} text={"Apply for Business Job"}
/> />
@ -259,7 +260,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasEmployeePositions() && ( {company.hasEmployeePositions() && (
<ApplyToJobButton <ApplyToJobButton
company={company} company={company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]} entryPosType={CompanyPositions[JobName.employee]}
onClick={applyForEmployeeJob} onClick={applyForEmployeeJob}
text={"Apply to be an Employee"} text={"Apply to be an Employee"}
/> />
@ -267,7 +268,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasEmployeePositions() && ( {company.hasEmployeePositions() && (
<ApplyToJobButton <ApplyToJobButton
company={company} company={company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]} entryPosType={CompanyPositions[JobName.employeePT]}
onClick={applyForPartTimeEmployeeJob} onClick={applyForPartTimeEmployeeJob}
text={"Apply to be a part-time Employee"} text={"Apply to be a part-time Employee"}
/> />
@ -275,7 +276,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasITPositions() && ( {company.hasITPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={company} company={company}
entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]} entryPosType={CompanyPositions[JobName.IT0]}
onClick={applyForItJob} onClick={applyForItJob}
text={"Apply for IT Job"} text={"Apply for IT Job"}
/> />
@ -283,7 +284,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasSecurityPositions() && ( {company.hasSecurityPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={company} company={company}
entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]} entryPosType={CompanyPositions[JobName.security0]}
onClick={applyForSecurityJob} onClick={applyForSecurityJob}
text={"Apply for Security Job"} text={"Apply for Security Job"}
/> />
@ -291,7 +292,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasSoftwareConsultantPositions() && ( {company.hasSoftwareConsultantPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={company} company={company}
entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]} entryPosType={CompanyPositions[JobName.softwareConsult0]}
onClick={applyForSoftwareConsultantJob} onClick={applyForSoftwareConsultantJob}
text={"Apply for Software Consultant Job"} text={"Apply for Software Consultant Job"}
/> />
@ -299,7 +300,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasSoftwarePositions() && ( {company.hasSoftwarePositions() && (
<ApplyToJobButton <ApplyToJobButton
company={company} company={company}
entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]} entryPosType={CompanyPositions[JobName.software0]}
onClick={applyForSoftwareJob} onClick={applyForSoftwareJob}
text={"Apply for Software Job"} text={"Apply for Software Job"}
/> />
@ -307,7 +308,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasWaiterPositions() && ( {company.hasWaiterPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={company} company={company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]} entryPosType={CompanyPositions[JobName.waiter]}
onClick={applyForWaiterJob} onClick={applyForWaiterJob}
text={"Apply to be a Waiter"} text={"Apply to be a Waiter"}
/> />
@ -315,7 +316,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasWaiterPositions() && ( {company.hasWaiterPositions() && (
<ApplyToJobButton <ApplyToJobButton
company={company} company={company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]} entryPosType={CompanyPositions[JobName.waiterPT]}
onClick={applyForPartTimeWaiterJob} onClick={applyForPartTimeWaiterJob}
text={"Apply to be a part-time Waiter"} text={"Apply to be a part-time Waiter"}
/> />

@ -31,6 +31,7 @@ import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router"; import { Page } from "../../ui/Router";
import { serverMetadata } from "../../Server/data/servers"; import { serverMetadata } from "../../Server/data/servers";
import { Tooltip } from "@mui/material"; import { Tooltip } from "@mui/material";
import { getEnumHelper } from "../../utils/EnumHelper";
interface IProps { interface IProps {
loc: Location; loc: Location;
@ -45,7 +46,10 @@ export function GenericLocation({ loc }: IProps): React.ReactElement {
const content: React.ReactNode[] = []; const content: React.ReactNode[] = [];
if (loc.types.includes(LocationType.Company)) { if (loc.types.includes(LocationType.Company)) {
content.push(<CompanyLocation key="CompanyLocation" locName={loc.name} />); if (!getEnumHelper("CompanyName").isMember(loc.name)) {
throw new Error(`Location name ${loc.name} is for a company but is not a company name.`);
}
content.push(<CompanyLocation key="CompanyLocation" companyName={loc.name} />);
} }
if (loc.types.includes(LocationType.Gym)) { if (loc.types.includes(LocationType.Gym)) {

@ -1,5 +1,4 @@
import type { Singularity as ISingularity } from "@nsdefs"; import type { Singularity as ISingularity } from "@nsdefs";
import type { Company } from "../Company/Company";
import { Player } from "@player"; import { Player } from "@player";
import { import {
@ -31,7 +30,6 @@ import { Programs } from "../Programs/Programs";
import { formatMoney, formatRam, formatReputation } from "../ui/formatNumber"; import { formatMoney, formatRam, formatReputation } from "../ui/formatNumber";
import { currentNodeMults } from "../BitNode/BitNodeMultipliers"; import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
import { Companies } from "../Company/Companies"; import { Companies } from "../Company/Companies";
import { companiesMetadata } from "../Company/data/CompaniesMetadata";
import { Factions } from "../Faction/Factions"; import { Factions } from "../Faction/Factions";
import { helpers } from "../Netscript/NetscriptHelpers"; import { helpers } from "../Netscript/NetscriptHelpers";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
@ -42,7 +40,7 @@ import { Server } from "../Server/Server";
import { netscriptCanHack } from "../Hacking/netscriptCanHack"; import { netscriptCanHack } from "../Hacking/netscriptCanHack";
import { FactionInfos } from "../Faction/FactionInfo"; import { FactionInfos } from "../Faction/FactionInfo";
import { donate, repNeededToDonate } from "../Faction/formulas/donation"; import { donate, repNeededToDonate } from "../Faction/formulas/donation";
import { InternalAPI, NetscriptContext, removedFunction } from "../Netscript/APIWrapper"; import { InternalAPI, removedFunction } from "../Netscript/APIWrapper";
import { enterBitNode } from "../RedPill"; import { enterBitNode } from "../RedPill";
import { ClassWork } from "../Work/ClassWork"; import { ClassWork } from "../Work/ClassWork";
import { CreateProgramWork, isCreateProgramWork } from "../Work/CreateProgramWork"; import { CreateProgramWork, isCreateProgramWork } from "../Work/CreateProgramWork";
@ -56,14 +54,10 @@ 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";
export function NetscriptSingularity(): InternalAPI<ISingularity> { export function NetscriptSingularity(): InternalAPI<ISingularity> {
const getCompany = function (ctx: NetscriptContext, name: string): Company {
const company = Companies[name];
if (!company) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid company name: '${name}'`);
return company;
};
const runAfterReset = function (cbScript: ScriptFilePath) { const runAfterReset = function (cbScript: ScriptFilePath) {
//Run a script after reset //Run a script after reset
if (!cbScript) return; if (!cbScript) return;
@ -670,45 +664,35 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
}, },
getCompanyPositions: (ctx) => (_companyName) => { getCompanyPositions: (ctx) => (_companyName) => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);
const companyName = helpers.string(ctx, "companyName", _companyName); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
// Make sure its a valid company return getRecordEntries(CompanyPositions)
if (companyName == null || companyName === "" || !Companies[companyName]) {
throw helpers.makeRuntimeErrorMsg(ctx, `Invalid company: '${companyName}'`);
}
return Object.entries(CompanyPositions)
.filter((_position) => Companies[companyName].hasPosition(_position[0])) .filter((_position) => Companies[companyName].hasPosition(_position[0]))
.map((_position) => _position[1].name); .map((_position) => _position[1].name);
}, },
getCompanyPositionInfo: (ctx) => (_companyName, _positionName) => { getCompanyPositionInfo: (ctx) => (_companyName, _positionName) => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);
const companyName = helpers.string(ctx, "companyName", _companyName); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
const positionName = getEnumHelper("JobName").nsGetMember(ctx, _positionName, "positionName"); const positionName = getEnumHelper("JobName").nsGetMember(ctx, _positionName, "positionName");
const company = Companies[companyName];
// Make sure its a valid company if (!company.hasPosition(positionName)) {
if (!(companyName in Companies)) {
throw helpers.makeRuntimeErrorMsg(ctx, `Invalid company: '${companyName}'`);
}
if (!Companies[companyName].hasPosition(positionName)) {
throw helpers.makeRuntimeErrorMsg(ctx, `Company '${companyName}' does not have position '${positionName}'`); throw helpers.makeRuntimeErrorMsg(ctx, `Company '${companyName}' does not have position '${positionName}'`);
} }
const c = CompanyPositions[positionName]; const job = CompanyPositions[positionName];
const n = companiesMetadata.filter((company) => company.name === companyName)[0];
const res = { const res = {
name: CompanyPositions[positionName].name, name: CompanyPositions[positionName].name,
nextPosition: CompanyPositions[positionName].nextPosition, nextPosition: CompanyPositions[positionName].nextPosition,
salary: CompanyPositions[positionName].baseSalary * n.salaryMultiplier, salary: CompanyPositions[positionName].baseSalary * company.salaryMultiplier,
requiredReputation: CompanyPositions[positionName].requiredReputation, requiredReputation: CompanyPositions[positionName].requiredReputation,
requiredSkills: { requiredSkills: {
hacking: c.requiredHacking > 0 ? c.requiredHacking + n.jobStatReqOffset : 0, hacking: job.requiredHacking > 0 ? job.requiredHacking + company.jobStatReqOffset : 0,
strength: c.requiredStrength > 0 ? c.requiredStrength + n.jobStatReqOffset : 0, strength: job.requiredStrength > 0 ? job.requiredStrength + company.jobStatReqOffset : 0,
defense: c.requiredDefense > 0 ? c.requiredDefense + n.jobStatReqOffset : 0, defense: job.requiredDefense > 0 ? job.requiredDefense + company.jobStatReqOffset : 0,
dexterity: c.requiredDexterity > 0 ? c.requiredDexterity + n.jobStatReqOffset : 0, dexterity: job.requiredDexterity > 0 ? job.requiredDexterity + company.jobStatReqOffset : 0,
agility: c.requiredAgility > 0 ? c.requiredAgility + n.jobStatReqOffset : 0, agility: job.requiredAgility > 0 ? job.requiredAgility + company.jobStatReqOffset : 0,
charisma: c.requiredCharisma > 0 ? c.requiredCharisma + n.jobStatReqOffset : 0, charisma: job.requiredCharisma > 0 ? job.requiredCharisma + company.jobStatReqOffset : 0,
intelligence: 0, intelligence: 0,
}, },
}; };
@ -718,26 +702,15 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
(ctx) => (ctx) =>
(_companyName, _focus = true) => { (_companyName, _focus = true) => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);
const companyName = helpers.string(ctx, "companyName", _companyName); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
const focus = !!_focus; const focus = !!_focus;
// Make sure its a valid company const jobName = Player.jobs[companyName];
if (companyName == null || companyName === "" || !Companies[companyName]) {
throw helpers.makeRuntimeErrorMsg(ctx, `Invalid company: '${companyName}'`);
}
// Make sure player is actually employed at the company // Make sure player is actually employed at the company
if (!Object.keys(Player.jobs).includes(companyName)) { if (!jobName) {
throw helpers.makeRuntimeErrorMsg(ctx, `You do not have a job at: '${companyName}'`); throw helpers.makeRuntimeErrorMsg(ctx, `You do not have a job at: '${companyName}'`);
} }
// Check to make sure company position data is valid
const companyPositionName = Player.jobs[companyName];
const companyPosition = CompanyPositions[companyPositionName];
if (companyPositionName === "" || !companyPosition) {
throw helpers.makeRuntimeErrorMsg(ctx, `You do not have a job`);
}
const wasFocused = Player.focus; const wasFocused = Player.focus;
Player.startWork( Player.startWork(
@ -753,16 +726,15 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
Player.stopFocusing(); Player.stopFocusing();
Router.toPage(Page.Terminal); Router.toPage(Page.Terminal);
} }
helpers.log(ctx, () => `Began working at '${companyName}' with position '${companyPositionName}'`); helpers.log(ctx, () => `Began working at '${companyName}' with position '${jobName}'`);
return true; return true;
}, },
applyToCompany: (ctx) => (_companyName, _field) => { applyToCompany: (ctx) => (_companyName, _field) => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);
const companyName = helpers.string(ctx, "companyName", _companyName); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
const field = helpers.string(ctx, "field", _field); const field = helpers.string(ctx, "field", _field);
getCompany(ctx, companyName);
Player.location = companyName as LocationName; Player.location = companyNameAsLocationName(companyName);
let res; let res;
switch (field.toLowerCase()) { switch (field.toLowerCase()) {
case "software": case "software":
@ -826,26 +798,23 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
}, },
quitJob: (ctx) => (_companyName) => { quitJob: (ctx) => (_companyName) => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);
const companyName = helpers.string(ctx, "companyName", _companyName); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
Player.quitJob(companyName); Player.quitJob(companyName);
}, },
getCompanyRep: (ctx) => (_companyName) => { getCompanyRep: (ctx) => (_companyName) => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);
const companyName = helpers.string(ctx, "companyName", _companyName); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
const company = getCompany(ctx, companyName); return Companies[companyName].playerReputation;
return company.playerReputation;
}, },
getCompanyFavor: (ctx) => (_companyName) => { getCompanyFavor: (ctx) => (_companyName) => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);
const companyName = helpers.string(ctx, "companyName", _companyName); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
const company = getCompany(ctx, companyName); return Companies[companyName].favor;
return company.favor;
}, },
getCompanyFavorGain: (ctx) => (_companyName) => { getCompanyFavorGain: (ctx) => (_companyName) => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);
const companyName = helpers.string(ctx, "companyName", _companyName); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
const company = getCompany(ctx, companyName); return Companies[companyName].getFavorGain();
return company.getFavorGain();
}, },
checkFactionInvitations: (ctx) => () => { checkFactionInvitations: (ctx) => () => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);

@ -78,9 +78,9 @@ export function NetscriptSleeve(): InternalAPI<NetscriptSleeve> {
checkSleeveNumber(ctx, sleeveNumber); checkSleeveNumber(ctx, sleeveNumber);
return Player.sleeves[sleeveNumber].travel(cityName); return Player.sleeves[sleeveNumber].travel(cityName);
}, },
setToCompanyWork: (ctx) => (_sleeveNumber, acompanyName) => { setToCompanyWork: (ctx) => (_sleeveNumber, _companyName) => {
const sleeveNumber = helpers.number(ctx, "sleeveNumber", _sleeveNumber); const sleeveNumber = helpers.number(ctx, "sleeveNumber", _sleeveNumber);
const companyName = helpers.string(ctx, "companyName", acompanyName); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
checkSleeveAPIAccess(ctx); checkSleeveAPIAccess(ctx);
checkSleeveNumber(ctx, sleeveNumber); checkSleeveNumber(ctx, sleeveNumber);

@ -17,7 +17,7 @@ import * as serverMethods from "./PlayerObjectServerMethods";
import * as workMethods from "./PlayerObjectWorkMethods"; import * as workMethods from "./PlayerObjectWorkMethods";
import { setPlayer } from "../../Player"; import { setPlayer } from "../../Player";
import { FactionName, LocationName } from "@enums"; import { CompanyName, FactionName, JobName, LocationName } from "@enums";
import { HashManager } from "../../Hacknet/HashManager"; import { HashManager } from "../../Hacknet/HashManager";
import { MoneySourceTracker } from "../../utils/MoneySourceTracker"; import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../../utils/JSONReviver"; import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue } from "../../utils/JSONReviver";
@ -26,7 +26,8 @@ import { cyrb53 } from "../../utils/StringHelperFunctions";
import { getRandomInt } from "../../utils/helpers/getRandomInt"; import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { Person } from "../Person"; import { Person } from "../Person";
import { getEnumHelper } from "../../utils/EnumHelper"; import { isMember } from "../../utils/EnumHelper";
import { PartialRecord } from "../../Types/Record";
export class PlayerObject extends Person implements IPlayer { export class PlayerObject extends Person implements IPlayer {
// Player-specific properties // Player-specific properties
@ -43,7 +44,7 @@ export class PlayerObject extends Person implements IPlayer {
hashManager = new HashManager(); hashManager = new HashManager();
hasTixApiAccess = false; hasTixApiAccess = false;
hasWseAccount = false; hasWseAccount = false;
jobs: Record<string, string> = {}; jobs: PartialRecord<CompanyName, JobName> = {};
karma = 0; karma = 0;
numPeopleKilled = 0; numPeopleKilled = 0;
location = LocationName.TravelAgency; location = LocationName.TravelAgency;
@ -172,11 +173,9 @@ export class PlayerObject extends Person implements IPlayer {
player.hp = { current: player.hp?.current ?? 10, max: player.hp?.max ?? 10 }; player.hp = { current: player.hp?.current ?? 10, max: player.hp?.max ?? 10 };
player.money ??= 0; player.money ??= 0;
// Just remove from the save file any augs that have invalid name // Just remove from the save file any augs that have invalid name
player.augmentations = player.augmentations.filter((ownedAug) => player.augmentations = player.augmentations.filter((ownedAug) => isMember("AugmentationName", ownedAug.name));
getEnumHelper("AugmentationName").isMember(ownedAug.name),
);
player.queuedAugmentations = player.queuedAugmentations.filter((ownedAug) => player.queuedAugmentations = player.queuedAugmentations.filter((ownedAug) =>
getEnumHelper("AugmentationName").isMember(ownedAug.name), isMember("AugmentationName", ownedAug.name),
); );
player.updateSkillLevels(); player.updateSkillLevels();
// Converstion code for Player.sourceFiles is here instead of normal save conversion area because it needs // Converstion code for Player.sourceFiles is here instead of normal save conversion area because it needs
@ -186,6 +185,12 @@ export class PlayerObject extends Person implements IPlayer {
type OldSourceFiles = { n: number; lvl: number }[]; type OldSourceFiles = { n: number; lvl: number }[];
player.sourceFiles = new JSONMap((player.sourceFiles as OldSourceFiles).map(({ n, lvl }) => [n, lvl])); player.sourceFiles = new JSONMap((player.sourceFiles as OldSourceFiles).map(({ n, lvl }) => [n, lvl]));
} }
// Remove any invalid jobs
for (const [loadedCompanyName, loadedJobName] of Object.entries(player.jobs)) {
if (!isMember("CompanyName", loadedCompanyName) || !isMember("JobName", loadedJobName)) {
delete player.jobs[loadedCompanyName as CompanyName];
}
}
return player; return player;
} }
} }

@ -1,4 +1,13 @@
import { AugmentationName, CityName, CompletedProgramName, FactionName, LocationName, ToastVariant } from "@enums"; import {
AugmentationName,
CityName,
CompanyName,
CompletedProgramName,
FactionName,
JobName,
LocationName,
ToastVariant,
} from "@enums";
import type { PlayerObject } from "./PlayerObject"; import type { PlayerObject } from "./PlayerObject";
import type { ProgramFilePath } from "../../Paths/ProgramFilePath"; import type { ProgramFilePath } from "../../Paths/ProgramFilePath";
@ -13,7 +22,6 @@ import { getNextCompanyPositionHelper } from "../../Company/GetNextCompanyPositi
import { getJobRequirementText } from "../../Company/GetJobRequirementText"; import { getJobRequirementText } from "../../Company/GetJobRequirementText";
import { CompanyPositions } from "../../Company/CompanyPositions"; import { CompanyPositions } from "../../Company/CompanyPositions";
import { CompanyPosition } from "../../Company/CompanyPosition"; import { CompanyPosition } from "../../Company/CompanyPosition";
import * as posNames from "../../Company/data/JobTracks";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { Exploit } from "../../Exploits/Exploit"; import { Exploit } from "../../Exploits/Exploit";
import { Faction } from "../../Faction/Faction"; import { Faction } from "../../Faction/Faction";
@ -44,6 +52,7 @@ import { achievements } from "../../Achievements/Achievements";
import { isCompanyWork } from "../../Work/CompanyWork"; import { isCompanyWork } from "../../Work/CompanyWork";
import { serverMetadata } from "../../Server/data/servers"; import { serverMetadata } from "../../Server/data/servers";
import { getEnumHelper, 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 */
@ -262,12 +271,9 @@ export function hospitalize(this: PlayerObject): number {
//The 'sing' argument designates whether or not this is being called from //The 'sing' argument designates whether or not this is being called from
//the applyToCompany() Netscript Singularity function //the applyToCompany() Netscript Singularity function
export function applyForJob(this: PlayerObject, entryPosType: CompanyPosition, sing = false): boolean { export function applyForJob(this: PlayerObject, entryPosType: CompanyPosition, sing = false): boolean {
const company = Companies[this.location]; //Company being applied to const companyName = getEnumHelper("CompanyName").getMember(this.location);
if (!company) { if (!companyName) return false;
console.error(`Could not find company that matches the location: ${this.location}. Player.applyToCompany() failed`); const company = Companies[companyName]; //Company being applied to
return false;
}
let pos = entryPosType; let pos = entryPosType;
if (!this.isQualified(company, pos)) { if (!this.isQualified(company, pos)) {
@ -347,7 +353,7 @@ export function getNextCompanyPosition(
return entryPosType; return entryPosType;
} }
export function quitJob(this: PlayerObject, company: string): void { export function quitJob(this: PlayerObject, company: CompanyName): void {
if (isCompanyWork(this.currentWork) && this.currentWork.companyName === company) { if (isCompanyWork(this.currentWork) && this.currentWork.companyName === company) {
this.finishWork(true); this.finishWork(true);
} }
@ -370,21 +376,23 @@ export function hasJob(this: PlayerObject): boolean {
} }
export function applyForSoftwareJob(this: PlayerObject, sing = false): boolean { export function applyForSoftwareJob(this: PlayerObject, sing = false): boolean {
return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing); return this.applyForJob(CompanyPositions[JobName.software0], sing);
} }
export function applyForSoftwareConsultantJob(this: PlayerObject, sing = false): boolean { export function applyForSoftwareConsultantJob(this: PlayerObject, sing = false): boolean {
return this.applyForJob(CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]], sing); return this.applyForJob(CompanyPositions[JobName.softwareConsult0], sing);
} }
export function applyForItJob(this: PlayerObject, sing = false): boolean { export function applyForItJob(this: PlayerObject, sing = false): boolean {
return this.applyForJob(CompanyPositions[posNames.ITCompanyPositions[0]], sing); return this.applyForJob(CompanyPositions[JobName.IT0], sing);
} }
export function applyForSecurityEngineerJob(this: PlayerObject, sing = false): boolean { export function applyForSecurityEngineerJob(this: PlayerObject, sing = false): boolean {
const company = Companies[this.location]; //Company being applied to const companyName = getEnumHelper("CompanyName").getMember(this.location);
if (this.isQualified(company, CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]])) { if (!companyName) return false;
return this.applyForJob(CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]], sing); const company = Companies[companyName];
if (this.isQualified(company, CompanyPositions[JobName.securityEng])) {
return this.applyForJob(CompanyPositions[JobName.securityEng], sing);
} else { } else {
if (!sing) { if (!sing) {
dialogBoxCreate("Unfortunately, you do not qualify for this position"); dialogBoxCreate("Unfortunately, you do not qualify for this position");
@ -394,9 +402,11 @@ export function applyForSecurityEngineerJob(this: PlayerObject, sing = false): b
} }
export function applyForNetworkEngineerJob(this: PlayerObject, sing = false): boolean { export function applyForNetworkEngineerJob(this: PlayerObject, sing = false): boolean {
const company = Companies[this.location]; //Company being applied to const companyName = getEnumHelper("CompanyName").getMember(this.location);
if (this.isQualified(company, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]])) { if (!companyName) return false;
const pos = CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]]; const company = Companies[companyName];
if (this.isQualified(company, CompanyPositions[JobName.networkEng0])) {
const pos = CompanyPositions[JobName.networkEng0];
return this.applyForJob(pos, sing); return this.applyForJob(pos, sing);
} else { } else {
if (!sing) { if (!sing) {
@ -407,23 +417,25 @@ export function applyForNetworkEngineerJob(this: PlayerObject, sing = false): bo
} }
export function applyForBusinessJob(this: PlayerObject, sing = false): boolean { export function applyForBusinessJob(this: PlayerObject, sing = false): boolean {
return this.applyForJob(CompanyPositions[posNames.BusinessCompanyPositions[0]], sing); return this.applyForJob(CompanyPositions[JobName.business0], sing);
} }
export function applyForBusinessConsultantJob(this: PlayerObject, sing = false): boolean { export function applyForBusinessConsultantJob(this: PlayerObject, sing = false): boolean {
return this.applyForJob(CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], sing); return this.applyForJob(CompanyPositions[JobName.businessConsult0], sing);
} }
export function applyForSecurityJob(this: PlayerObject, sing = false): boolean { export function applyForSecurityJob(this: PlayerObject, sing = false): boolean {
// TODO Police Jobs // TODO Police Jobs
// Indexing starts at 2 because 0 is for police officer // Indexing starts at 2 because 0 is for police officer
return this.applyForJob(CompanyPositions[posNames.SecurityCompanyPositions[2]], sing); return this.applyForJob(CompanyPositions[JobName.security0], sing);
} }
export function applyForAgentJob(this: PlayerObject, sing = false): boolean { export function applyForAgentJob(this: PlayerObject, sing = false): boolean {
const company = Companies[this.location]; //Company being applied to const companyName = getEnumHelper("CompanyName").getMember(this.location);
if (this.isQualified(company, CompanyPositions[posNames.AgentCompanyPositions[0]])) { if (!companyName) return false;
const pos = CompanyPositions[posNames.AgentCompanyPositions[0]]; const company = Companies[companyName];
if (this.isQualified(company, CompanyPositions[JobName.agent0])) {
const pos = CompanyPositions[JobName.agent0];
return this.applyForJob(pos, sing); return this.applyForJob(pos, sing);
} else { } else {
if (!sing) { if (!sing) {
@ -434,8 +446,10 @@ export function applyForAgentJob(this: PlayerObject, sing = false): boolean {
} }
export function applyForEmployeeJob(this: PlayerObject, sing = false): boolean { export function applyForEmployeeJob(this: PlayerObject, sing = false): boolean {
const company = Companies[this.location]; //Company being applied to const companyName = getEnumHelper("CompanyName").getMember(this.location);
const position = posNames.MiscCompanyPositions[1]; if (!companyName) return false;
const company = Companies[companyName];
const position = JobName.employee;
// Check if this company has the position // Check if this company has the position
if (!company.hasPosition(position)) { if (!company.hasPosition(position)) {
return false; return false;
@ -458,8 +472,10 @@ export function applyForEmployeeJob(this: PlayerObject, sing = false): boolean {
} }
export function applyForPartTimeEmployeeJob(this: PlayerObject, sing = false): boolean { export function applyForPartTimeEmployeeJob(this: PlayerObject, sing = false): boolean {
const company = Companies[this.location]; //Company being applied to const companyName = getEnumHelper("CompanyName").getMember(this.location);
const position = posNames.PartTimeCompanyPositions[1]; if (!companyName) return false;
const company = Companies[companyName];
const position = JobName.employeePT;
// Check if this company has the position // Check if this company has the position
if (!company.hasPosition(position)) { if (!company.hasPosition(position)) {
return false; return false;
@ -481,8 +497,10 @@ export function applyForPartTimeEmployeeJob(this: PlayerObject, sing = false): b
} }
export function applyForWaiterJob(this: PlayerObject, sing = false): boolean { export function applyForWaiterJob(this: PlayerObject, sing = false): boolean {
const company = Companies[this.location]; //Company being applied to const companyName = getEnumHelper("CompanyName").getMember(this.location);
const position = posNames.MiscCompanyPositions[0]; if (!companyName) return false;
const company = Companies[companyName];
const position = JobName.waiter;
// Check if this company has the position // Check if this company has the position
if (!company.hasPosition(position)) { if (!company.hasPosition(position)) {
return false; return false;
@ -502,8 +520,10 @@ export function applyForWaiterJob(this: PlayerObject, sing = false): boolean {
} }
export function applyForPartTimeWaiterJob(this: PlayerObject, sing = false): boolean { export function applyForPartTimeWaiterJob(this: PlayerObject, sing = false): boolean {
const company = Companies[this.location]; //Company being applied to const companyName = getEnumHelper("CompanyName").getMember(this.location);
const position = posNames.PartTimeCompanyPositions[0]; if (!companyName) return false;
const company = Companies[companyName];
const position = JobName.waiterPT;
// Check if this company has the position // Check if this company has the position
if (!company.hasPosition(position)) { if (!company.hasPosition(position)) {
return false; return false;
@ -594,20 +614,16 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
const allPositions = Object.values(this.jobs); const allPositions = Object.values(this.jobs);
// Given a company name, safely returns the reputation (returns 0 if invalid company is specified) // Given a company name, safely returns the reputation (returns 0 if invalid company is specified)
function getCompanyRep(companyName: string): number { function getCompanyRep(companyName: CompanyName): number {
const company = Companies[companyName]; const company = Companies[companyName];
if (company == null) { return company.playerReputation;
return 0;
} else {
return company.playerReputation;
}
} }
// Helper function that returns a boolean indicating whether the Player meets // Helper function that returns a boolean indicating whether the Player meets
// the requirements for the specified company. There are two requirements: // the requirements for the specified company. There are two requirements:
// 1. High enough reputation // 1. High enough reputation
// 2. Player is employed at the company // 2. Player is employed at the company
function checkMegacorpRequirements(companyName: string): boolean { function checkMegacorpRequirements(companyName: CompanyName): boolean {
const serverMeta = serverMetadata.find((s) => s.specialName === companyName); const serverMeta = serverMetadata.find((s) => s.specialName === companyName);
const server = GetServer(serverMeta ? serverMeta.hostname : ""); const server = GetServer(serverMeta ? serverMeta.hostname : "");
const bonus = (server as Server).backdoorInstalled ? -100e3 : 0; const bonus = (server as Server).backdoorInstalled ? -100e3 : 0;
@ -673,7 +689,7 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
!ecorpFac.isBanned && !ecorpFac.isBanned &&
!ecorpFac.isMember && !ecorpFac.isMember &&
!ecorpFac.alreadyInvited && !ecorpFac.alreadyInvited &&
checkMegacorpRequirements(LocationName.AevumECorp) checkMegacorpRequirements(CompanyName.ECorp)
) { ) {
invitedFactions.push(ecorpFac); invitedFactions.push(ecorpFac);
} }
@ -684,7 +700,7 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
!megacorpFac.isBanned && !megacorpFac.isBanned &&
!megacorpFac.isMember && !megacorpFac.isMember &&
!megacorpFac.alreadyInvited && !megacorpFac.alreadyInvited &&
checkMegacorpRequirements(LocationName.Sector12MegaCorp) checkMegacorpRequirements(CompanyName.MegaCorp)
) { ) {
invitedFactions.push(megacorpFac); invitedFactions.push(megacorpFac);
} }
@ -695,7 +711,7 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
!bachmanandassociatesFac.isBanned && !bachmanandassociatesFac.isBanned &&
!bachmanandassociatesFac.isMember && !bachmanandassociatesFac.isMember &&
!bachmanandassociatesFac.alreadyInvited && !bachmanandassociatesFac.alreadyInvited &&
checkMegacorpRequirements(LocationName.AevumBachmanAndAssociates) checkMegacorpRequirements(CompanyName.BachmanAndAssociates)
) { ) {
invitedFactions.push(bachmanandassociatesFac); invitedFactions.push(bachmanandassociatesFac);
} }
@ -706,19 +722,14 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
!bladeindustriesFac.isBanned && !bladeindustriesFac.isBanned &&
!bladeindustriesFac.isMember && !bladeindustriesFac.isMember &&
!bladeindustriesFac.alreadyInvited && !bladeindustriesFac.alreadyInvited &&
checkMegacorpRequirements(LocationName.Sector12BladeIndustries) checkMegacorpRequirements(CompanyName.BladeIndustries)
) { ) {
invitedFactions.push(bladeindustriesFac); invitedFactions.push(bladeindustriesFac);
} }
//NWO //NWO
const nwoFac = Factions[FactionName.NWO]; const nwoFac = Factions[FactionName.NWO];
if ( if (!nwoFac.isBanned && !nwoFac.isMember && !nwoFac.alreadyInvited && checkMegacorpRequirements(CompanyName.NWO)) {
!nwoFac.isBanned &&
!nwoFac.isMember &&
!nwoFac.alreadyInvited &&
checkMegacorpRequirements(LocationName.VolhavenNWO)
) {
invitedFactions.push(nwoFac); invitedFactions.push(nwoFac);
} }
@ -728,7 +739,7 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
!clarkeincorporatedFac.isBanned && !clarkeincorporatedFac.isBanned &&
!clarkeincorporatedFac.isMember && !clarkeincorporatedFac.isMember &&
!clarkeincorporatedFac.alreadyInvited && !clarkeincorporatedFac.alreadyInvited &&
checkMegacorpRequirements(LocationName.AevumClarkeIncorporated) checkMegacorpRequirements(CompanyName.ClarkeIncorporated)
) { ) {
invitedFactions.push(clarkeincorporatedFac); invitedFactions.push(clarkeincorporatedFac);
} }
@ -739,7 +750,7 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
!omnitekincorporatedFac.isBanned && !omnitekincorporatedFac.isBanned &&
!omnitekincorporatedFac.isMember && !omnitekincorporatedFac.isMember &&
!omnitekincorporatedFac.alreadyInvited && !omnitekincorporatedFac.alreadyInvited &&
checkMegacorpRequirements(LocationName.VolhavenOmniTekIncorporated) checkMegacorpRequirements(CompanyName.OmniTekIncorporated)
) { ) {
invitedFactions.push(omnitekincorporatedFac); invitedFactions.push(omnitekincorporatedFac);
} }
@ -750,7 +761,7 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
!foursigmaFac.isBanned && !foursigmaFac.isBanned &&
!foursigmaFac.isMember && !foursigmaFac.isMember &&
!foursigmaFac.alreadyInvited && !foursigmaFac.alreadyInvited &&
checkMegacorpRequirements(LocationName.Sector12FourSigma) checkMegacorpRequirements(CompanyName.FourSigma)
) { ) {
invitedFactions.push(foursigmaFac); invitedFactions.push(foursigmaFac);
} }
@ -761,7 +772,7 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
!kuaigonginternationalFac.isBanned && !kuaigonginternationalFac.isBanned &&
!kuaigonginternationalFac.isMember && !kuaigonginternationalFac.isMember &&
!kuaigonginternationalFac.alreadyInvited && !kuaigonginternationalFac.alreadyInvited &&
checkMegacorpRequirements(LocationName.ChongqingKuaiGongInternational) checkMegacorpRequirements(CompanyName.KuaiGongInternational)
) { ) {
invitedFactions.push(kuaigonginternationalFac); invitedFactions.push(kuaigonginternationalFac);
} }
@ -778,7 +789,7 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
!fulcrumsecrettechonologiesFac.isMember && !fulcrumsecrettechonologiesFac.isMember &&
!fulcrumsecrettechonologiesFac.alreadyInvited && !fulcrumsecrettechonologiesFac.alreadyInvited &&
fulcrumSecretServer.backdoorInstalled && fulcrumSecretServer.backdoorInstalled &&
checkMegacorpRequirements(LocationName.AevumFulcrumTechnologies) checkMegacorpRequirements(CompanyName.FulcrumTechnologies)
) { ) {
invitedFactions.push(fulcrumsecrettechonologiesFac); invitedFactions.push(fulcrumsecrettechonologiesFac);
} }
@ -966,9 +977,9 @@ export function checkForFactionInvitations(this: PlayerObject): Faction[] {
!silhouetteFac.isBanned && !silhouetteFac.isBanned &&
!silhouetteFac.isMember && !silhouetteFac.isMember &&
!silhouetteFac.alreadyInvited && !silhouetteFac.alreadyInvited &&
(allPositions.includes("Chief Technology Officer") || (allPositions.includes(JobName.software7) || // CTO
allPositions.includes("Chief Financial Officer") || allPositions.includes(JobName.business4) || // CFO
allPositions.includes("Chief Executive Officer")) && allPositions.includes(JobName.business5)) && // CEO
this.money >= 15000000 && this.money >= 15000000 &&
this.karma <= -22 this.karma <= -22
) { ) {
@ -1136,8 +1147,7 @@ export function gainCodingContractReward(
return `Gained ${gainPerFaction} reputation for each of the following factions: ${factions.join(", ")}`; return `Gained ${gainPerFaction} reputation for each of the following factions: ${factions.join(", ")}`;
} }
case CodingContractRewardType.CompanyReputation: { case CodingContractRewardType.CompanyReputation: {
if (!Companies[reward.name]) { if (!isMember("CompanyName", reward.name)) {
//If no/invalid company was designated, just give rewards to all factions
return this.gainCodingContractReward({ type: CodingContractRewardType.FactionReputationAll }); return this.gainCodingContractReward({ type: CodingContractRewardType.FactionReputationAll });
} }
const repGain = CONSTANTS.CodingContractBaseCompanyRepGain * difficulty; const repGain = CONSTANTS.CodingContractBaseCompanyRepGain * difficulty;

@ -9,18 +9,23 @@
import type { SleevePerson } from "@nsdefs"; import type { SleevePerson } from "@nsdefs";
import type { Augmentation } from "../../Augmentation/Augmentation"; import type { Augmentation } from "../../Augmentation/Augmentation";
import type { Company } from "../../Company/Company";
import type { CompanyPosition } from "../../Company/CompanyPosition";
import type { SleeveWork } from "./Work/Work"; import type { SleeveWork } from "./Work/Work";
import { Player } from "@player"; import { Player } from "@player";
import { Person } from "../Person"; import { Person } from "../Person";
import { Companies } from "../../Company/Companies";
import { CompanyPositions } from "../../Company/CompanyPositions";
import { Contracts } from "../../Bladeburner/data/Contracts"; import { Contracts } from "../../Bladeburner/data/Contracts";
import { CONSTANTS } from "../../Constants"; import { CONSTANTS } from "../../Constants";
import { ClassType, CityName, CrimeType, FactionWorkType, GymType, LocationName, UniversityClassType } from "@enums"; import {
ClassType,
CityName,
CrimeType,
FactionWorkType,
GymType,
LocationName,
UniversityClassType,
CompanyName,
} from "@enums";
import { Factions } from "../../Faction/Factions"; import { Factions } from "../../Faction/Factions";
@ -277,18 +282,11 @@ export class Sleeve extends Person implements SleevePerson {
* Start work for one of the player's companies * Start work for one of the player's companies
* Returns boolean indicating success * Returns boolean indicating success
*/ */
workForCompany(companyName: string): boolean { workForCompany(companyName: CompanyName): boolean {
if (!Companies[companyName] || Player.jobs[companyName] == null) { const companyPositionName = Player.jobs[companyName];
return false; if (!companyPositionName) return false;
}
const company: Company | null = Companies[companyName];
const companyPosition: CompanyPosition | null = CompanyPositions[Player.jobs[companyName]];
if (company == null) return false;
if (companyPosition == null) return false;
this.startWork(new SleeveCompanyWork(companyName)); this.startWork(new SleeveCompanyWork(companyName));
return true; return true;
} }

@ -1,5 +1,5 @@
import { Player } from "@player"; import { Player } from "@player";
import { LocationName } from "@enums"; import { CompanyName, JobName } from "@enums";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve"; import { Sleeve } from "../Sleeve";
import { applySleeveGains, SleeveWorkClass, SleeveWorkType } from "./Work"; import { applySleeveGains, SleeveWorkClass, SleeveWorkType } from "./Work";
@ -9,29 +9,29 @@ import { calculateCompanyWorkStats } from "../../../Work/Formulas";
import { scaleWorkStats, WorkStats } from "../../../Work/WorkStats"; import { scaleWorkStats, WorkStats } from "../../../Work/WorkStats";
import { influenceStockThroughCompanyWork } from "../../../StockMarket/PlayerInfluencing"; import { influenceStockThroughCompanyWork } from "../../../StockMarket/PlayerInfluencing";
import { CompanyPositions } from "../../../Company/CompanyPositions"; import { CompanyPositions } from "../../../Company/CompanyPositions";
import { isMember } from "../../../utils/EnumHelper";
import { invalidWork } from "../../../Work/InvalidWork";
export const isSleeveCompanyWork = (w: SleeveWorkClass | null): w is SleeveCompanyWork => export const isSleeveCompanyWork = (w: SleeveWorkClass | null): w is SleeveCompanyWork =>
w !== null && w.type === SleeveWorkType.COMPANY; w !== null && w.type === SleeveWorkType.COMPANY;
export class SleeveCompanyWork extends SleeveWorkClass { export class SleeveCompanyWork extends SleeveWorkClass {
type: SleeveWorkType.COMPANY = SleeveWorkType.COMPANY; type: SleeveWorkType.COMPANY = SleeveWorkType.COMPANY;
companyName: string; companyName: CompanyName;
constructor(companyName?: string) { constructor(companyName = CompanyName.NoodleBar) {
super(); super();
this.companyName = companyName ?? LocationName.NewTokyoNoodleBar; this.companyName = companyName;
} }
getCompany(): Company { getCompany(): Company {
const c = Companies[this.companyName]; return Companies[this.companyName];
if (!c) throw new Error(`Company not found: '${this.companyName}'`);
return c;
} }
getGainRates(sleeve: Sleeve): WorkStats { getGainRates(sleeve: Sleeve, job: JobName): WorkStats {
const company = this.getCompany(); const company = this.getCompany();
return scaleWorkStats( return scaleWorkStats(
calculateCompanyWorkStats(sleeve, company, CompanyPositions[Player.jobs[company.name]], company.favor), calculateCompanyWorkStats(sleeve, company, CompanyPositions[job], company.favor),
sleeve.shockBonus(), sleeve.shockBonus(),
false, false,
); );
@ -39,7 +39,9 @@ export class SleeveCompanyWork extends SleeveWorkClass {
process(sleeve: Sleeve, cycles: number) { process(sleeve: Sleeve, cycles: number) {
const company = this.getCompany(); const company = this.getCompany();
const gains = this.getGainRates(sleeve); const job = Player.jobs[this.companyName];
if (!job) return sleeve.stopWork();
const gains = this.getGainRates(sleeve, job);
applySleeveGains(sleeve, gains, cycles); applySleeveGains(sleeve, gains, cycles);
company.playerReputation += gains.reputation * cycles; company.playerReputation += gains.reputation * cycles;
influenceStockThroughCompanyWork(company, gains.reputation, cycles); influenceStockThroughCompanyWork(company, gains.reputation, cycles);
@ -59,7 +61,9 @@ export class SleeveCompanyWork extends SleeveWorkClass {
/** Initializes a CompanyWork object from a JSON save state. */ /** Initializes a CompanyWork object from a JSON save state. */
static fromJSON(value: IReviverValue): SleeveCompanyWork { static fromJSON(value: IReviverValue): SleeveCompanyWork {
return Generic_fromJSON(SleeveCompanyWork, value.data); const work = Generic_fromJSON(SleeveCompanyWork, value.data);
if (!isMember("CompanyName", work.companyName)) return invalidWork();
return work;
} }
} }

@ -13,6 +13,7 @@ import { TaskSelector } from "./TaskSelector";
import { TravelModal } from "./TravelModal"; import { TravelModal } from "./TravelModal";
import { findCrime } from "../../../Crime/CrimeHelpers"; import { findCrime } from "../../../Crime/CrimeHelpers";
import { SleeveWorkType } from "../Work/Work"; import { SleeveWorkType } from "../Work/Work";
import { getEnumHelper } from "../../../utils/EnumHelper";
function getWorkDescription(sleeve: Sleeve, progress: number): string { function getWorkDescription(sleeve: Sleeve, progress: number): string {
const work = sleeve.currentWork; const work = sleeve.currentWork;
@ -75,7 +76,8 @@ export function SleeveElem(props: SleeveElemProps): React.ReactElement {
case "------": case "------":
break; break;
case "Work for Company": case "Work for Company":
props.sleeve.workForCompany(abc[1]); if (getEnumHelper("CompanyName").isMember(abc[1])) props.sleeve.workForCompany(abc[1]);
else console.error(`Invalid company name in setSleeveTask: ${abc[1]}`);
break; break;
case "Work for Faction": case "Work for Faction":
props.sleeve.workForFaction(abc[1], abc[2]); props.sleeve.workForFaction(abc[1], abc[2]);

@ -2,6 +2,8 @@ import React from "react";
import { Typography, Table, TableBody, TableCell, TableRow } from "@mui/material"; import { Typography, Table, TableBody, TableCell, TableRow } from "@mui/material";
import { Player } from "@player";
import { CONSTANTS } from "../../../Constants"; import { CONSTANTS } from "../../../Constants";
import { import {
@ -141,8 +143,10 @@ export function EarningsElement(props: IProps): React.ReactElement {
]; ];
} }
if (isSleeveCompanyWork(props.sleeve.currentWork)) { companyWork: if (isSleeveCompanyWork(props.sleeve.currentWork)) {
const rates = props.sleeve.currentWork.getGainRates(props.sleeve); const job = Player.jobs[props.sleeve.currentWork.companyName];
if (!job) break companyWork;
const rates = props.sleeve.currentWork.getGainRates(props.sleeve, job);
data = [ data = [
[`Money:`, <MoneyRate key="money-rate" money={CYCLES_PER_SEC * rates.money} />], [`Money:`, <MoneyRate key="money-rate" money={CYCLES_PER_SEC * rates.money} />],
[`Hacking Exp:`, `${formatExp(CYCLES_PER_SEC * rates.hackExp)} / sec`], [`Hacking Exp:`, `${formatExp(CYCLES_PER_SEC * rates.hackExp)} / sec`],

@ -1,6 +1,6 @@
import { AugmentationName, CityName, CompletedProgramName, FactionName, LiteratureName } from "@enums"; import { AugmentationName, CityName, CompletedProgramName, FactionName, LiteratureName } from "@enums";
import { initBitNodeMultipliers } from "./BitNode/BitNode"; import { initBitNodeMultipliers } from "./BitNode/BitNode";
import { Companies, initCompanies } from "./Company/Companies"; import { Companies } from "./Company/Companies";
import { resetIndustryResearchTrees } from "./Corporation/data/IndustryData"; import { resetIndustryResearchTrees } from "./Corporation/data/IndustryData";
import { Factions } from "./Faction/Factions"; import { Factions } from "./Faction/Factions";
import { joinFaction } from "./Faction/FactionHelpers"; import { joinFaction } from "./Faction/FactionHelpers";
@ -71,7 +71,7 @@ export function prestigeAugmentation(): void {
initForeignServers(Player.getHomeComputer()); initForeignServers(Player.getHomeComputer());
// Gain favor for Companies and Factions // Gain favor for Companies and Factions
for (const company of Object.values(Companies)) company.gainFavor(); for (const company of Object.values(Companies)) company.prestigeAugmentation();
for (const faction of Object.values(Factions)) faction.prestigeAugmentation(); for (const faction of Object.values(Factions)) faction.prestigeAugmentation();
// Stop a Terminal action if there is one. // Stop a Terminal action if there is one.
@ -89,7 +89,6 @@ export function prestigeAugmentation(): void {
Player.reapplyAllAugmentations(); Player.reapplyAllAugmentations();
Player.reapplyAllSourceFiles(); Player.reapplyAllSourceFiles();
Player.hp.current = Player.hp.max; Player.hp.current = Player.hp.max;
initCompanies();
// Apply entropy from grafting // Apply entropy from grafting
Player.applyEntropy(Player.entropy); Player.applyEntropy(Player.entropy);
@ -194,7 +193,7 @@ export function prestigeSourceFile(isFlume: boolean): void {
homeComp.cpuCores = 1; homeComp.cpuCores = 1;
// Reset favor for Companies and Factions // Reset favor for Companies and Factions
for (const company of Object.values(Companies)) company.favor = 0; for (const company of Object.values(Companies)) company.prestigeSourceFile();
for (const faction of Object.values(Factions)) faction.prestigeSourceFile(); for (const faction of Object.values(Factions)) faction.prestigeSourceFile();
// Stop a Terminal action if there is one // Stop a Terminal action if there is one
@ -214,7 +213,6 @@ export function prestigeSourceFile(isFlume: boolean): void {
Player.reapplyAllAugmentations(); Player.reapplyAllAugmentations();
Player.reapplyAllSourceFiles(); Player.reapplyAllSourceFiles();
initCompanies();
if (Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) { if (Player.sourceFileLvl(5) > 0 || Player.bitNodeN === 5) {
homeComp.programs.push(CompletedProgramName.formulas); homeComp.programs.push(CompletedProgramName.formulas);

@ -270,28 +270,6 @@ function evaluateVersionCompatibility(ver: string | number): void {
anyPlayer.companyPosition = ""; anyPlayer.companyPosition = "";
} }
} }
// The "companyName" property of all Companies is renamed to "name"
interface Company0_41_2 {
name: string | number;
companyName: string;
companyPositions: Record<number, boolean>;
}
for (const companyName of Object.keys(Companies)) {
const company = Companies[companyName] as unknown as Company0_41_2;
if (company.name == 0 && company.companyName != null) {
company.name = company.companyName;
}
if (company.companyPositions instanceof Array) {
const pos: Record<number, boolean> = {};
for (let i = 0; i < company.companyPositions.length; ++i) {
pos[company.companyPositions[i]] = true;
}
company.companyPositions = pos;
}
}
} }
// This version allowed players to hold multiple jobs // This version allowed players to hold multiple jobs

@ -6728,12 +6728,10 @@ declare enum JobName {
business3 = "Operations Manager", business3 = "Operations Manager",
business4 = "Chief Financial Officer", business4 = "Chief Financial Officer",
business5 = "Chief Executive Officer", business5 = "Chief Executive Officer",
security0 = "Police Officer", security0 = "Security Guard",
security1 = "Police Chief", security1 = "Security Officer",
security2 = "Security Guard", security2 = "Security Supervisor",
security3 = "Security Officer", security3 = "Head of Security",
security4 = "Security Supervisor",
security5 = "Head of Security",
agent0 = "Field Agent", agent0 = "Field Agent",
agent1 = "Secret Agent", agent1 = "Secret Agent",
agent2 = "Special Operative", agent2 = "Special Operative",

@ -74,9 +74,7 @@ export function influenceStockThroughCompanyWork(
): void { ): void {
const compName = company.name; const compName = company.name;
let stock: Stock | null = null; let stock: Stock | null = null;
if (typeof compName === "string" && compName !== "") { stock = StockMarket[compName];
stock = StockMarket[compName];
}
if (!(stock instanceof Stock)) { if (!(stock instanceof Stock)) {
return; return;
} }

@ -3,7 +3,7 @@ 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, LocationName } from "@enums"; import { AugmentationName, 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";
@ -12,43 +12,42 @@ import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reputation } from "../ui/React/Reputation"; import { Reputation } from "../ui/React/Reputation";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { CompanyPositions } from "../Company/CompanyPositions"; import { CompanyPositions } from "../Company/CompanyPositions";
import { isMember } from "../utils/EnumHelper";
import { invalidWork } from "./InvalidWork";
interface CompanyWorkParams { interface CompanyWorkParams {
companyName: string; companyName: CompanyName;
singularity: boolean; singularity: boolean;
} }
export const isCompanyWork = (w: Work | null): w is CompanyWork => w !== null && w.type === WorkType.COMPANY; export const isCompanyWork = (w: Work | null): w is CompanyWork => w !== null && w.type === WorkType.COMPANY;
export class CompanyWork extends Work { export class CompanyWork extends Work {
companyName: string; companyName: CompanyName;
constructor(params?: CompanyWorkParams) { constructor(params?: CompanyWorkParams) {
super(WorkType.COMPANY, params?.singularity ?? false); super(WorkType.COMPANY, params?.singularity ?? false);
this.companyName = params?.companyName ?? LocationName.NewTokyoNoodleBar; this.companyName = params?.companyName ?? CompanyName.NoodleBar;
} }
getCompany(): Company { getCompany(): Company {
const c = Companies[this.companyName]; return Companies[this.companyName];
if (!c) throw new Error(`Company not found: '${this.companyName}'`);
return c;
} }
getGainRates(): WorkStats { getGainRates(job: JobName): WorkStats {
let focusBonus = 1; let focusBonus = 1;
if (!Player.hasAugmentation(AugmentationName.NeuroreceptorManager, true)) { if (!Player.hasAugmentation(AugmentationName.NeuroreceptorManager, true)) {
focusBonus = Player.focus ? 1 : CONSTANTS.BaseFocusBonus; focusBonus = Player.focus ? 1 : CONSTANTS.BaseFocusBonus;
} }
const company = this.getCompany(); const company = this.getCompany();
return scaleWorkStats( return scaleWorkStats(calculateCompanyWorkStats(Player, company, CompanyPositions[job], company.favor), focusBonus);
calculateCompanyWorkStats(Player, company, CompanyPositions[Player.jobs[company.name]], company.favor),
focusBonus,
);
} }
process(cycles: number): boolean { process(cycles: number): boolean {
this.cyclesWorked += cycles; this.cyclesWorked += cycles;
const company = this.getCompany(); const company = this.getCompany();
const gains = this.getGainRates(); const job = Player.jobs[this.companyName];
if (!job) return true;
const gains = this.getGainRates(job);
applyWorkStats(Player, gains, cycles, "work"); applyWorkStats(Player, gains, cycles, "work");
company.playerReputation += gains.reputation * cycles; company.playerReputation += gains.reputation * cycles;
influenceStockThroughCompanyWork(company, gains.reputation, cycles); influenceStockThroughCompanyWork(company, gains.reputation, cycles);
@ -81,7 +80,9 @@ export class CompanyWork extends Work {
/** Initializes a CompanyWork object from a JSON save state. */ /** Initializes a CompanyWork object from a JSON save state. */
static fromJSON(value: IReviverValue): CompanyWork { static fromJSON(value: IReviverValue): CompanyWork {
return Generic_fromJSON(CompanyWork, value.data); const work = Generic_fromJSON(CompanyWork, value.data);
if (!isMember("CompanyName", work.companyName)) return invalidWork();
return work;
} }
} }

@ -47,12 +47,10 @@ export enum JobName {
business3 = "Operations Manager", business3 = "Operations Manager",
business4 = "Chief Financial Officer", business4 = "Chief Financial Officer",
business5 = "Chief Executive Officer", business5 = "Chief Executive Officer",
security0 = "Police Officer", security0 = "Security Guard",
security1 = "Police Chief", security1 = "Security Officer",
security2 = "Security Guard", security2 = "Security Supervisor",
security3 = "Security Officer", security3 = "Head of Security",
security4 = "Security Supervisor",
security5 = "Head of Security",
agent0 = "Field Agent", agent0 = "Field Agent",
agent1 = "Secret Agent", agent1 = "Secret Agent",
agent2 = "Special Operative", agent2 = "Special Operative",

13
src/Work/InvalidWork.ts Normal file

@ -0,0 +1,13 @@
// This file is just for providing the ability to not load an invalid work.
import type { PlayerObject } from "../PersonObjects/Player/PlayerObject";
import type { Sleeve } from "../PersonObjects/Sleeve/Sleeve";
import type { SleeveWork } from "../PersonObjects/Sleeve/Work/Work";
import type { Work } from "./Work";
// Type verifications to validate that Player.currentWork and sleeve.currentWork are allowed to be null.
const __canPlayerWorkBeNull: null extends PlayerObject["currentWork"] ? true : false = true;
const __canSleeveWorkBeNull: null extends Sleeve["currentWork"] ? true : false = true;
export function invalidWork<W extends Work | SleeveWork>(): W {
return null as unknown as W;
}

@ -3,7 +3,6 @@ import { AugmentationName, ToastVariant } from "@enums";
import { initBitNodeMultipliers } from "./BitNode/BitNode"; import { initBitNodeMultipliers } from "./BitNode/BitNode";
import { initSourceFiles } from "./SourceFile/SourceFiles"; import { initSourceFiles } from "./SourceFile/SourceFiles";
import { generateRandomContract } from "./CodingContractGenerator"; import { generateRandomContract } from "./CodingContractGenerator";
import { initCompanies } from "./Company/Companies";
import { CONSTANTS } from "./Constants"; import { CONSTANTS } from "./Constants";
import { Factions } from "./Faction/Factions"; import { Factions } from "./Faction/Factions";
import { staneksGift } from "./CotMG/Helper"; import { staneksGift } from "./CotMG/Helper";
@ -374,7 +373,6 @@ const Engine: {
Engine.start(); // Run main game loop and Scripts loop Engine.start(); // Run main game loop and Scripts loop
Player.init(); Player.init();
initForeignServers(Player.getHomeComputer()); initForeignServers(Player.getHomeComputer());
initCompanies();
Player.reapplyAllAugmentations(); Player.reapplyAllAugmentations();
// Start interactive tutorial // Start interactive tutorial

@ -1,5 +1,5 @@
// Root React Component for the Corporation UI // Root React Component for the Corporation UI
import React, { useMemo, useState, useEffect } from "react"; import React, { useMemo, useState, useEffect, ReactNode } from "react";
import { Theme, useTheme } from "@mui/material/styles"; import { Theme, useTheme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles"; import makeStyles from "@mui/styles/makeStyles";
@ -337,9 +337,9 @@ function Work(): React.ReactElement {
if (Player.currentWork === null || Player.focus) return <></>; if (Player.currentWork === null || Player.focus) return <></>;
let details = <></>; let details: ReactNode = "";
let header = <></>; let header: ReactNode = "";
let innerText = <></>; let innerText: ReactNode = "";
if (isCrimeWork(Player.currentWork)) { if (isCrimeWork(Player.currentWork)) {
const crime = Player.currentWork.getCrime(); const crime = Player.currentWork.getCrime();
const perc = (Player.currentWork.unitCompleted / crime.time) * 100; const perc = (Player.currentWork.unitCompleted / crime.time) * 100;
@ -391,11 +391,14 @@ function Work(): React.ReactElement {
} }
if (isCompanyWork(Player.currentWork)) { if (isCompanyWork(Player.currentWork)) {
const companyWork = Player.currentWork; const companyWork = Player.currentWork;
const job = Player.jobs[companyWork.companyName];
if (!job) return <></>;
details = ( details = (
<> <>
{Player.jobs[companyWork.companyName]} at <strong>{companyWork.companyName}</strong> {job} at <strong>{companyWork.companyName}</strong>
</> </>
); );
header = ( header = (
<> <>
Working at <strong>{companyWork.companyName}</strong> Working at <strong>{companyWork.companyName}</strong>
@ -405,7 +408,7 @@ function Work(): React.ReactElement {
<> <>
<Reputation reputation={companyWork.getCompany().playerReputation} /> rep <Reputation reputation={companyWork.getCompany().playerReputation} /> rep
<br />( <br />(
<ReputationRate reputation={companyWork.getGainRates().reputation * (1000 / CONSTANTS.MilliPerCycle)} />) <ReputationRate reputation={companyWork.getGainRates(job).reputation * (1000 / CONSTANTS.MilliPerCycle)} />)
</> </>
); );
} }

@ -1,28 +1,28 @@
/** import type { CompanyName } from "../../Enums";
* Creates a dropdown (select HTML element) with company names as options
*/
import React from "react"; import React from "react";
import { companiesMetadata } from "../../Company/data/CompaniesMetadata";
import Select, { SelectChangeEvent } from "@mui/material/Select"; import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { Companies } from "../../Company/Companies";
import { getRecordKeys } from "../../Types/Record";
interface IProps { interface IProps {
purchase: () => void; purchase: () => void;
canPurchase: boolean; canPurchase: boolean;
onChange: (event: SelectChangeEvent) => void; onChange: (event: SelectChangeEvent<CompanyName>) => void;
value: string; value: CompanyName;
} }
const sortedCompanies = companiesMetadata.sort((a, b) => a.name.localeCompare(b.name)); const sortedCompanies = getRecordKeys(Companies).sort((a, b) => a.localeCompare(b));
export function CompanyDropdown(props: IProps): React.ReactElement { export function CompanyDropdown(props: IProps): React.ReactElement {
const companies = []; const companies = [];
for (const company of sortedCompanies) { for (const company of sortedCompanies) {
companies.push( companies.push(
<MenuItem key={company.name} value={company.name}> <MenuItem key={company} value={company}>
{company.name} {company}
</MenuItem>, </MenuItem>,
); );
} }

@ -411,7 +411,7 @@ export function WorkInProgressRoot(): React.ReactElement {
cancel: () => Router.toPage(Page.Terminal), cancel: () => Router.toPage(Page.Terminal),
}, },
title: title:
`You cannot work for ${Player.currentWork.companyName || "(Company not found)"} at this time,` + `You cannot work for ${Player.currentWork.companyName} at this time,` +
" please try again if you think this should have worked", " please try again if you think this should have worked",
stopText: "Back to Terminal", stopText: "Back to Terminal",
@ -421,7 +421,8 @@ export function WorkInProgressRoot(): React.ReactElement {
const companyRep = comp.playerReputation; const companyRep = comp.playerReputation;
const position = Player.jobs[Player.currentWork.companyName]; const position = Player.jobs[Player.currentWork.companyName];
const gains = Player.currentWork.getGainRates(); if (!position) return <></>;
const gains = Player.currentWork.getGainRates(position);
workInfo = { workInfo = {
buttons: { buttons: {
cancel: () => { cancel: () => {

@ -3,16 +3,17 @@ import { Factions } from "../../src/Faction/Factions";
import { Player, setPlayer } from "../../src/Player"; import { Player, setPlayer } from "../../src/Player";
import { PlayerObject } from "../../src/PersonObjects/Player/PlayerObject"; import { PlayerObject } from "../../src/PersonObjects/Player/PlayerObject";
import { joinFaction } from "../../src/Faction/FactionHelpers"; import { joinFaction } from "../../src/Faction/FactionHelpers";
import { AugmentationName, CrimeType, FactionName } from "../../src/Enums"; import { AugmentationName, CompanyName, CrimeType, FactionName } from "../../src/Enums";
import { Augmentations } from "../../src/Augmentation/Augmentations"; import { Augmentations } from "../../src/Augmentation/Augmentations";
import { SleeveCrimeWork } from "../../src/PersonObjects/Sleeve/Work/SleeveCrimeWork"; import { SleeveCrimeWork } from "../../src/PersonObjects/Sleeve/Work/SleeveCrimeWork";
import { Companies } from "../../src/Company/Companies";
describe("Check Save File Continuity", () => { describe("Check Save File Continuity", () => {
establishInitialConditions(); establishInitialConditions();
// Calling getSaveString forces save info to update // Calling getSaveString forces save info to update
saveObject.getSaveString(); saveObject.getSaveString();
const savesToTest = ["FactionsSave", "PlayerSave"] as const; const savesToTest = ["FactionsSave", "PlayerSave", "CompaniesSave"] as const;
for (const saveToTest of savesToTest) { for (const saveToTest of savesToTest) {
test(`${saveToTest} continuity`, () => { test(`${saveToTest} continuity`, () => {
const parsed = JSON.parse(saveObject[saveToTest]); const parsed = JSON.parse(saveObject[saveToTest]);
@ -44,9 +45,6 @@ function establishInitialConditions() {
initForeignServers(Player.getHomeComputer()); initForeignServers(Player.getHomeComputer());
*/ */
// not comparing companies yet
// initCompanies()
// Sleeves (already added in game initializers section) // Sleeves (already added in game initializers section)
Player.sleeves[0].installAugmentation(Augmentations[AugmentationName.BionicArms]); Player.sleeves[0].installAugmentation(Augmentations[AugmentationName.BionicArms]);
Player.sleeves[0].startWork(new SleeveCrimeWork(CrimeType.homicide)); Player.sleeves[0].startWork(new SleeveCrimeWork(CrimeType.homicide));
@ -61,6 +59,11 @@ function establishInitialConditions() {
csec.playerReputation = 1e6; csec.playerReputation = 1e6;
csec.favor = 20; csec.favor = 20;
// Companies
const noodleBar = Companies[CompanyName.NoodleBar];
noodleBar.favor = 100;
noodleBar.playerReputation = 100000;
// Bladeburner. Adding rank will also add bladeburner faction rep. // Bladeburner. Adding rank will also add bladeburner faction rep.
Player.startBladeburner(); Player.startBladeburner();
Player.bladeburner?.changeRank(Player, 2000); Player.bladeburner?.changeRank(Player, 2000);

@ -31,7 +31,7 @@ import {
getSellTransactionGain, getSellTransactionGain,
processTransactionForecastMovement, processTransactionForecastMovement,
} from "../../src/StockMarket/StockMarketHelpers"; } from "../../src/StockMarket/StockMarketHelpers";
import { OrderType, PositionType } from "../../src/Enums"; import { CompanyName, OrderType, PositionType } from "../../src/Enums";
jest.mock(`!!raw-loader!../NetscriptDefinitions.d.ts`, () => "", { jest.mock(`!!raw-loader!../NetscriptDefinitions.d.ts`, () => "", {
virtual: true, virtual: true,
@ -1255,9 +1255,9 @@ describe("Stock Market Tests", function () {
}); });
const company = new Company({ const company = new Company({
name: "MockStock", name: "MockStock" as CompanyName,
info: "", info: "",
companyPositions: {}, companyPositions: [],
expMultiplier: 1, expMultiplier: 1,
salaryMultiplier: 1, salaryMultiplier: 1,
jobStatReqOffset: 1, jobStatReqOffset: 1,

@ -1,5 +1,276 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Check Save File Continuity CompaniesSave continuity 1`] = `
{
"AeroCorp": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Aevum Police Headquarters": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Alpha Enterprises": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Bachman & Associates": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Blade Industries": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Carmichael Security": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Central Intelligence Agency": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Clarke Incorporated": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"CompuTek": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"DefComm": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"DeltaOne": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"ECorp": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"FoodNStuff": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Four Sigma": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Fulcrum Technologies": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Galactic Cybersystems": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Global Pharmaceuticals": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Helios Labs": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Icarus Microsystems": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Joe's Guns": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"KuaiGong International": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"LexoCorp": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"MegaCorp": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"NWO": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"National Security Agency": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"NetLink Technologies": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Noodle Bar": {
"ctor": "Company",
"data": {
"favor": 100,
"playerReputation": 100000,
},
},
"Nova Medical": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Omega Software": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"OmniTek Incorporated": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Omnia Cybersystems": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Rho Construction": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Solaris Space Systems": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Storm Technologies": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"SysCore Securities": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Universal Energy": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"VitaLife": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
"Watchdog Security": {
"ctor": "Company",
"data": {
"favor": 0,
"playerReputation": 0,
},
},
}
`;
exports[`Check Save File Continuity FactionsSave continuity 1`] = ` exports[`Check Save File Continuity FactionsSave continuity 1`] = `
{ {
"Aevum": { "Aevum": {