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
import { companiesMetadata } from "./data/CompaniesMetadata";
import { Company, IConstructorParams } from "./Company";
import { Reviver } from "../utils/JSONReviver";
import { getCompaniesMetadata } from "./data/CompaniesMetadata";
import { Company } from "./Company";
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> = {};
function addCompany(params: IConstructorParams): void {
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;
}
}
}
}
export const Companies: Record<CompanyName, Company> = (() => {
const metadata = getCompaniesMetadata();
return createEnumKeyedRecord(CompanyName, (name) => new Company(metadata[name]));
})();
// Used to load Companies map from a save
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 * as posNames from "./data/JobTracks";
import type { CompanyPosition } from "./CompanyPosition";
import { CompanyName, JobName } from "@enums";
import { favorToRep, repToFavor } from "../Faction/formulas/favor";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../utils/JSONReviver";
export interface IConstructorParams {
name: string;
info: string;
companyPositions: Record<string, boolean>;
export interface CompanyCtorParams {
name: CompanyName;
info?: string;
companyPositions: JobName[];
expMultiplier: number;
salaryMultiplier: number;
jobStatReqOffset: number;
isMegacorp?: boolean;
hasFaction?: boolean;
}
const DefaultConstructorParams: IConstructorParams = {
name: "",
info: "",
companyPositions: {},
expMultiplier: 1,
salaryMultiplier: 1,
jobStatReqOffset: 0,
};
export class Company {
/** Company name */
name: string;
// Static info, initialized once at game load.
/** Description and general information about company */
info: string;
name = CompanyName.NoodleBar;
info = "";
hasFaction = false;
/** Has faction associated. */
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>;
companyPositions = new Set<JobName>();
/** Company-specific multiplier for earnings */
expMultiplier: number;
salaryMultiplier: number;
expMultiplier = 1;
salaryMultiplier = 1;
/**
* 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.
* 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 */
isPlayerEmployed: boolean;
playerReputation: number;
favor: number;
// Dynamic info, loaded from save and updated during game.
playerReputation = 0;
favor = 0;
constructor(p: IConstructorParams = DefaultConstructorParams) {
constructor(p?: CompanyCtorParams) {
if (!p) return;
this.name = p.name;
this.info = p.info;
this.companyPositions = p.companyPositions;
if (p.info) this.info = p.info;
p.companyPositions.forEach((jobName) => this.companyPositions.add(jobName));
this.expMultiplier = p.expMultiplier;
this.salaryMultiplier = p.salaryMultiplier;
this.jobStatReqOffset = p.jobStatReqOffset;
this.isPlayerEmployed = false;
this.playerReputation = 1;
this.favor = 0;
this.isMegacorp = false;
if (p.isMegacorp) this.isMegacorp = true;
if (p.hasFaction) this.hasFaction = true;
}
hasPosition(pos: CompanyPosition | string): boolean {
return this.companyPositions[typeof pos === "string" ? pos : pos.name] != null;
hasPosition(pos: CompanyPosition | JobName): boolean {
return this.companyPositions.has(typeof pos === "string" ? pos : pos.name);
}
hasAgentPositions(): boolean {
return this.companyPositions[posNames.AgentCompanyPositions[0]] != null;
return this.companyPositions.has(JobName.agent0);
}
hasBusinessConsultantPositions(): boolean {
return this.companyPositions[posNames.BusinessConsultantCompanyPositions[0]] != null;
return this.companyPositions.has(JobName.businessConsult0);
}
hasBusinessPositions(): boolean {
return this.companyPositions[posNames.BusinessCompanyPositions[0]] != null;
return this.companyPositions.has(JobName.business0);
}
hasEmployeePositions(): boolean {
return this.companyPositions[posNames.MiscCompanyPositions[1]] != null;
return this.companyPositions.has(JobName.employee);
}
hasITPositions(): boolean {
return this.companyPositions[posNames.ITCompanyPositions[0]] != null;
return this.companyPositions.has(JobName.IT0);
}
hasSecurityPositions(): boolean {
return this.companyPositions[posNames.SecurityCompanyPositions[2]] != null;
return this.companyPositions.has(JobName.security0);
}
hasSoftwareConsultantPositions(): boolean {
return this.companyPositions[posNames.SoftwareConsultantCompanyPositions[0]] != null;
return this.companyPositions.has(JobName.softwareConsult0);
}
hasSoftwarePositions(): boolean {
return this.companyPositions[posNames.SoftwareCompanyPositions[0]] != null;
return this.companyPositions.has(JobName.software0);
}
hasWaiterPositions(): boolean {
return this.companyPositions[posNames.MiscCompanyPositions[0]] != null;
return this.companyPositions.has(JobName.waiter);
}
gainFavor(): void {
if (this.favor == null) {
this.favor = 0;
}
prestigeAugmentation(): void {
if (this.favor == null) this.favor = 0;
this.favor += this.getFavorGain();
this.playerReputation = 0;
}
prestigeSourceFile() {
this.favor = 0;
this.playerReputation = 0;
}
getFavorGain(): number {
if (this.favor == null) {
this.favor = 0;
}
if (this.favor == null) this.favor = 0;
const storedRep = Math.max(0, favorToRep(this.favor));
const totalRep = storedRep + this.playerReputation;
const newFavor = repToFavor(totalRep);
@ -134,13 +113,16 @@ export class Company {
/** Serialize the current object to a JSON save state. */
toJSON(): IReviverValue {
return Generic_toJSON("Company", this);
return Generic_toJSON("Company", this, Company.includedKeys);
}
/** Initializes a Company from a JSON save state. */
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;

@ -1,10 +1,18 @@
import { Person as IPerson } from "@nsdefs";
import { CONSTANTS } from "../Constants";
import * as names from "./data/JobTracks";
import { JobName } from "@enums";
import {
agentJobs,
businessConsultJobs,
businessJobs,
itJobs,
netEngJobs,
securityJobs,
softwareConsultJobs,
softwareJobs,
} from "./data/JobTracks";
export interface IConstructorParams {
name: JobName;
export interface CompanyPositionCtorParams {
nextPosition: JobName | null;
baseSalary: number;
repMultiplier: number;
@ -75,8 +83,8 @@ export class CompanyPosition {
agilityExpGain: number;
charismaExpGain: number;
constructor(p: IConstructorParams) {
this.name = p.name;
constructor(name: JobName, p: CompanyPositionCtorParams) {
this.name = name;
this.nextPosition = p.nextPosition;
this.baseSalary = p.baseSalary;
this.repMultiplier = p.repMultiplier;
@ -136,42 +144,42 @@ export class CompanyPosition {
}
isSoftwareJob(): boolean {
return names.SoftwareCompanyPositions.includes(this.name);
return softwareJobs.includes(this.name);
}
isITJob(): boolean {
return names.ITCompanyPositions.includes(this.name);
return itJobs.includes(this.name);
}
isSecurityEngineerJob(): boolean {
return names.SecurityEngineerCompanyPositions.includes(this.name);
return this.name === JobName.securityEng;
}
isNetworkEngineerJob(): boolean {
return names.NetworkEngineerCompanyPositions.includes(this.name);
return netEngJobs.includes(this.name);
}
isBusinessJob(): boolean {
return names.BusinessCompanyPositions.includes(this.name);
return businessJobs.includes(this.name);
}
isSecurityJob(): boolean {
return names.SecurityCompanyPositions.includes(this.name);
return securityJobs.includes(this.name);
}
isAgentJob(): boolean {
return names.AgentCompanyPositions.includes(this.name);
return agentJobs.includes(this.name);
}
isSoftwareConsultantJob(): boolean {
return names.SoftwareConsultantCompanyPositions.includes(this.name);
return softwareConsultJobs.includes(this.name);
}
isBusinessConsultantJob(): boolean {
return names.BusinessConsultantCompanyPositions.includes(this.name);
return businessConsultJobs.includes(this.name);
}
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 { companyPositionMetadata } from "./data/CompanyPositionsMetadata";
import { CompanyPosition, IConstructorParams } from "./CompanyPosition";
import { JobName } from "@enums";
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 {
if (CompanyPositions[params.name] != null) {
console.warn(`Duplicate Company Position being defined: ${params.name}`);
}
CompanyPositions[params.name] = new CompanyPosition(params);
}
companyPositionMetadata.forEach((e) => {
addCompanyPosition(e);
});
export const CompanyPositions: Record<JobName, CompanyPosition> = (() => {
const metadata = getCompanyPositionMetadata();
return createEnumKeyedRecord(JobName, (name) => new CompanyPosition(name, metadata[name]));
})();

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"
// i.e. the next position to get promoted to
import { CompanyPosition } from "./CompanyPosition";
import type { CompanyPosition } from "./CompanyPosition";
import { CompanyPositions } from "./CompanyPositions";
export function getNextCompanyPositionHelper(currPos: CompanyPosition | null): CompanyPosition | null {
if (currPos == null) {
return null;
}
if (!currPos) return null;
const nextPosName: string | null = currPos.nextPosition;
if (nextPosName == null) {
return null;
}
const nextPosName = currPos.nextPosition;
if (!nextPosName) return null;
return CompanyPositions[nextPosName];
}

@ -1,449 +1,309 @@
import * as posNames from "./JobTracks";
import { IConstructorParams } from "../Company";
import { CompanyCtorParams } 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
// Create Objects containing Company Positions by category
// Will help in metadata construction later
const AllSoftwarePositions: Record<string, boolean> = {};
const AllITPositions: Record<string, boolean> = {};
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> = {};
export function getCompaniesMetadata(): Record<CompanyName, CompanyCtorParams> {
const allTechJobs: JobName[] = [...softwareJobs, ...itJobs, ...netEngJobs, JobName.securityEng];
const softwareJobsToHeadOfEng: JobName[] = softwareJobs.slice(0, 6);
const softwareJobsToLeadDev: JobName[] = softwareJobs.slice(0, 4);
const businessJobToOpsManager: JobName[] = businessJobs.slice(0, 4);
posNames.SoftwareCompanyPositions.forEach((e) => {
AllSoftwarePositions[e] = true;
AllTechnologyPositions[e] = true;
});
posNames.ITCompanyPositions.forEach((e) => {
AllITPositions[e] = true;
AllTechnologyPositions[e] = true;
});
posNames.NetworkEngineerCompanyPositions.forEach((e) => {
AllNetworkEngineerPositions[e] = true;
AllTechnologyPositions[e] = true;
});
AllTechnologyPositions[posNames.SecurityEngineerCompanyPositions[0]] = true;
SecurityEngineerPositions[posNames.SecurityEngineerCompanyPositions[0]] = true;
posNames.BusinessCompanyPositions.forEach((e) => {
AllBusinessPositions[e] = true;
});
posNames.SecurityCompanyPositions.forEach((e) => {
AllSecurityPositions[e] = true;
});
posNames.AgentCompanyPositions.forEach((e) => {
AllAgentPositions[e] = true;
});
posNames.SoftwareConsultantCompanyPositions.forEach((e) => {
AllSoftwareConsultantPositions[e] = true;
});
posNames.BusinessConsultantCompanyPositions.forEach((e) => {
AllBusinessConsultantPositions[e] = true;
});
for (let i = 0; i < posNames.SoftwareCompanyPositions.length; ++i) {
const e = posNames.SoftwareCompanyPositions[i];
if (i <= 5) {
SoftwarePositionsUpToHeadOfEngineering[e] = true;
}
if (i <= 3) {
SoftwarePositionsUpToLeadDeveloper[e] = true;
}
return {
[CompanyName.ECorp]: {
name: CompanyName.ECorp,
companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
expMultiplier: 3,
salaryMultiplier: 3,
jobStatReqOffset: 249,
},
[CompanyName.MegaCorp]: {
name: CompanyName.MegaCorp,
companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
expMultiplier: 3,
salaryMultiplier: 3,
jobStatReqOffset: 249,
},
[CompanyName.BachmanAndAssociates]: {
name: CompanyName.BachmanAndAssociates,
companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
expMultiplier: 2.6,
salaryMultiplier: 2.6,
jobStatReqOffset: 224,
},
[CompanyName.BladeIndustries]: {
name: CompanyName.BladeIndustries,
companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
expMultiplier: 2.75,
salaryMultiplier: 2.75,
jobStatReqOffset: 224,
},
[CompanyName.NWO]: {
name: CompanyName.NWO,
companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
expMultiplier: 2.75,
salaryMultiplier: 2.75,
jobStatReqOffset: 249,
},
[CompanyName.ClarkeIncorporated]: {
name: CompanyName.ClarkeIncorporated,
companyPositions: [...allTechJobs, ...businessJobs, ...securityJobs],
expMultiplier: 2.25,
salaryMultiplier: 2.25,
jobStatReqOffset: 224,
},
[CompanyName.OmniTekIncorporated]: {
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";
// This entire file can be reworked to
export const SoftwareCompanyPositions: JobName[] = [
export const softwareJobs = [
JobName.software0,
JobName.software1,
JobName.software2,
@ -11,14 +9,9 @@ export const SoftwareCompanyPositions: JobName[] = [
JobName.software6,
JobName.software7,
];
export const ITCompanyPositions: JobName[] = [JobName.IT0, JobName.IT1, JobName.IT2, JobName.IT3];
export const SecurityEngineerCompanyPositions: JobName[] = [JobName.securityEng];
export const NetworkEngineerCompanyPositions: JobName[] = [JobName.networkEng0, JobName.networkEng1];
export const BusinessCompanyPositions: JobName[] = [
export const itJobs = [JobName.IT0, JobName.IT1, JobName.IT2, JobName.IT3];
export const netEngJobs = [JobName.networkEng0, JobName.networkEng1];
export const businessJobs = [
JobName.business0,
JobName.business1,
JobName.business2,
@ -26,22 +19,7 @@ export const BusinessCompanyPositions: JobName[] = [
JobName.business4,
JobName.business5,
];
export const SecurityCompanyPositions: JobName[] = [
JobName.security0,
JobName.security1,
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];
export const securityJobs = [JobName.security0, JobName.security1, JobName.security2, JobName.security3];
export const agentJobs = [JobName.agent0, JobName.agent1, JobName.agent2];
export const softwareConsultJobs = [JobName.softwareConsult0, JobName.softwareConsult1];
export const businessConsultJobs = [JobName.businessConsult0, JobName.businessConsult1];

@ -4,18 +4,19 @@ import { Player } from "@player";
import { Modal } from "../../ui/React/Modal";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import { CompanyName } from "../Enums";
interface IProps {
open: boolean;
onClose: () => void;
locName: string;
companyName: CompanyName;
company: Company;
onQuit: () => void;
}
export function QuitJobModal(props: IProps): React.ReactElement {
function quit(): void {
Player.quitJob(props.locName);
Player.quitJob(props.companyName);
props.onQuit();
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 MenuItem from "@mui/material/MenuItem";
import { FactionName } from "@enums";
import { Companies as AllCompanies } from "../../Company/Companies";
import { CompanyName } from "@enums";
import { Companies } from "../../Company/Companies";
import { Adjuster } from "./Adjuster";
import { isMember } from "../../utils/EnumHelper";
import { getRecordValues } from "../../Types/Record";
const bigNumber = 1e12;
export function CompaniesDev(): React.ReactElement {
const [company, setCompany] = useState(FactionName.ECorp as string);
const [companyName, setCompanyName] = useState(CompanyName.ECorp);
function setCompanyDropdown(event: SelectChangeEvent): void {
setCompany(event.target.value);
if (!isMember("CompanyName", event.target.value)) return;
setCompanyName(event.target.value);
}
function resetCompanyRep(): void {
AllCompanies[company].playerReputation = 0;
Companies[companyName].playerReputation = 0;
}
function modifyCompanyRep(modifier: number): (x: number) => void {
return function (reputation: number): void {
const c = AllCompanies[company];
if (c != null && !isNaN(reputation)) {
c.playerReputation += reputation * modifier;
const company = Companies[companyName];
if (!isNaN(reputation)) {
company.playerReputation += reputation * modifier;
}
};
}
function modifyCompanyFavor(modifier: number): (x: number) => void {
return function (favor: number): void {
const c = AllCompanies[company];
if (c != null && !isNaN(favor)) {
c.favor += favor * modifier;
const company = Companies[companyName];
if (!isNaN(favor)) {
company.favor += favor * modifier;
}
};
}
function resetCompanyFavor(): void {
AllCompanies[company].favor = 0;
Companies[companyName].favor = 0;
}
function tonsOfRepCompanies(): void {
for (const c of Object.keys(AllCompanies)) {
AllCompanies[c].playerReputation = bigNumber;
for (const company of getRecordValues(Companies)) {
company.playerReputation = bigNumber;
}
}
function resetAllRepCompanies(): void {
for (const c of Object.keys(AllCompanies)) {
AllCompanies[c].playerReputation = 0;
for (const company of getRecordValues(Companies)) {
company.playerReputation = 0;
}
}
function tonsOfFavorCompanies(): void {
for (const c of Object.keys(AllCompanies)) {
AllCompanies[c].favor = bigNumber;
for (const company of getRecordValues(Companies)) {
company.favor = bigNumber;
}
}
function resetAllFavorCompanies(): void {
for (const c of Object.keys(AllCompanies)) {
AllCompanies[c].favor = 0;
for (const company of getRecordValues(Companies)) {
company.favor = 0;
}
}
@ -83,8 +86,8 @@ export function CompaniesDev(): React.ReactElement {
<Typography>Company:</Typography>
</td>
<td colSpan={3}>
<Select id="dev-companies-dropdown" onChange={setCompanyDropdown} value={company}>
{Object.values(AllCompanies).map((company) => (
<Select id="dev-companies-dropdown" onChange={setCompanyDropdown} value={companyName}>
{Object.values(Companies).map((company) => (
<MenuItem key={company.name} value={company.name}>
{company.name}
</MenuItem>

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

@ -21,7 +21,6 @@ for (const aug of getRecordValues(Augmentations)) {
}
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;
// This loading method allows invalid data in player save, but just ignores anything invalid
if (!loadedFactions) return;

@ -22,6 +22,7 @@ import { Player } from "@player";
import { GetServer } from "../Server/AllServers";
import { Server } from "../Server/Server";
import { Companies } from "../Company/Companies";
import { isMember } from "../utils/EnumHelper";
// Returns a boolean indicating whether the player has Hacknet Servers
// (the upgraded form of Hacknet Nodes)
@ -564,7 +565,7 @@ export function purchaseHashUpgrade(upgName: string, upgTarget: string, count =
break;
}
case "Company Favor": {
if (!(upgTarget in Companies)) {
if (!isMember("CompanyName", upgTarget)) {
console.error(`Invalid target specified in purchaseHashUpgrade(): ${upgTarget}`);
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 Button from "@mui/material/Button";
import { SelectChangeEvent } from "@mui/material/Select";
import { FactionName } from "@enums";
import { companiesMetadata } from "../../Company/data/CompaniesMetadata";
import { CompanyName, FactionName } from "@enums";
import { PartialRecord } from "../../Types/Record";
import { isMember } from "../../utils/EnumHelper";
interface IProps {
hashManager: HashManager;
@ -24,8 +25,9 @@ interface IProps {
rerender: () => void;
}
// Key is the hash upgrade name
const serversMap: Record<string, string> = {};
const companiesMap: Record<string, string> = {};
const companiesMap: PartialRecord<string, CompanyName> = {};
export function HacknetUpgradeElem(props: IProps): React.ReactElement {
const [selectedServer, setSelectedServer] = useState(
@ -35,10 +37,9 @@ export function HacknetUpgradeElem(props: IProps): React.ReactElement {
setSelectedServer(event.target.value);
serversMap[props.upg.name] = event.target.value;
}
const [selectedCompany, setSelectedCompany] = useState(
companiesMap[props.upg.name] ? companiesMap[props.upg.name] : companiesMetadata[0].name,
);
function changeTargetCompany(event: SelectChangeEvent): void {
const [selectedCompany, setSelectedCompany] = useState(companiesMap[props.upg.name] ?? CompanyName.NoodleBar);
function changeTargetCompany(event: SelectChangeEvent<CompanyName>): void {
if (!isMember("CompanyName", event.target.value)) return;
setSelectedCompany(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 { Locations } from "../Locations";
import { LocationName } from "@enums";
import { CompanyName, JobName } from "@enums";
import { Companies } from "../../Company/Companies";
import { CompanyPositions } from "../../Company/CompanyPositions";
import * as posNames from "../../Company/data/JobTracks";
import { Reputation } from "../../ui/React/Reputation";
import { Favor } from "../../ui/React/Favor";
@ -26,9 +25,10 @@ import { Player } from "@player";
import { QuitJobModal } from "../../Company/ui/QuitJobModal";
import { CompanyWork } from "../../Work/CompanyWork";
import { useRerender } from "../../ui/React/hooks";
import { companyNameAsLocationName } from "../../Company/utils";
interface IProps {
locName: LocationName;
companyName: CompanyName;
}
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,
* so we don't have to look it up every time
*/
const company = Companies[props.locName];
if (company == null) throw new Error(`CompanyLocation component constructed with invalid company: ${props.locName}`);
const company = Companies[props.companyName];
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 */
const location = Locations[props.locName];
const location = Locations[props.companyName];
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 */
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
@ -57,7 +58,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
*/
const companyPosition = jobTitle ? CompanyPositions[jobTitle] : null;
Player.location = props.locName;
Player.location = companyNameAsLocationName(props.companyName);
function applyForAgentJob(e: React.MouseEvent<HTMLElement>): void {
if (!e.isTrusted) {
@ -152,7 +153,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
return;
}
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 });
}
@ -167,7 +168,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
Player.startWork(
new CompanyWork({
singularity: false,
companyName: props.locName,
companyName: props.companyName,
}),
);
Player.startFocusing();
@ -224,7 +225,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
<Button onClick={work}>Work</Button>
<Button onClick={() => setQuitOpen(true)}>Quit</Button>
<QuitJobModal
locName={props.locName}
companyName={props.companyName}
company={company}
onQuit={rerender}
open={quitOpen}
@ -235,7 +236,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasAgentPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.AgentCompanyPositions[0]]}
entryPosType={CompanyPositions[JobName.agent0]}
onClick={applyForAgentJob}
text={"Apply for Agent Job"}
/>
@ -243,7 +244,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasBusinessConsultantPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]]}
entryPosType={CompanyPositions[JobName.businessConsult0]}
onClick={applyForBusinessConsultantJob}
text={"Apply for Business Consultant Job"}
/>
@ -251,7 +252,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasBusinessPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.BusinessCompanyPositions[0]]}
entryPosType={CompanyPositions[JobName.business0]}
onClick={applyForBusinessJob}
text={"Apply for Business Job"}
/>
@ -259,7 +260,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasEmployeePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[1]]}
entryPosType={CompanyPositions[JobName.employee]}
onClick={applyForEmployeeJob}
text={"Apply to be an Employee"}
/>
@ -267,7 +268,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasEmployeePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[1]]}
entryPosType={CompanyPositions[JobName.employeePT]}
onClick={applyForPartTimeEmployeeJob}
text={"Apply to be a part-time Employee"}
/>
@ -275,7 +276,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasITPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.ITCompanyPositions[0]]}
entryPosType={CompanyPositions[JobName.IT0]}
onClick={applyForItJob}
text={"Apply for IT Job"}
/>
@ -283,7 +284,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasSecurityPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.SecurityCompanyPositions[2]]}
entryPosType={CompanyPositions[JobName.security0]}
onClick={applyForSecurityJob}
text={"Apply for Security Job"}
/>
@ -291,7 +292,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasSoftwareConsultantPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]]}
entryPosType={CompanyPositions[JobName.softwareConsult0]}
onClick={applyForSoftwareConsultantJob}
text={"Apply for Software Consultant Job"}
/>
@ -299,7 +300,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasSoftwarePositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.SoftwareCompanyPositions[0]]}
entryPosType={CompanyPositions[JobName.software0]}
onClick={applyForSoftwareJob}
text={"Apply for Software Job"}
/>
@ -307,7 +308,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasWaiterPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.MiscCompanyPositions[0]]}
entryPosType={CompanyPositions[JobName.waiter]}
onClick={applyForWaiterJob}
text={"Apply to be a Waiter"}
/>
@ -315,7 +316,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
{company.hasWaiterPositions() && (
<ApplyToJobButton
company={company}
entryPosType={CompanyPositions[posNames.PartTimeCompanyPositions[0]]}
entryPosType={CompanyPositions[JobName.waiterPT]}
onClick={applyForPartTimeWaiterJob}
text={"Apply to be a part-time Waiter"}
/>

@ -31,6 +31,7 @@ import { Router } from "../../ui/GameRoot";
import { Page } from "../../ui/Router";
import { serverMetadata } from "../../Server/data/servers";
import { Tooltip } from "@mui/material";
import { getEnumHelper } from "../../utils/EnumHelper";
interface IProps {
loc: Location;
@ -45,7 +46,10 @@ export function GenericLocation({ loc }: IProps): React.ReactElement {
const content: React.ReactNode[] = [];
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)) {

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

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

@ -17,7 +17,7 @@ import * as serverMethods from "./PlayerObjectServerMethods";
import * as workMethods from "./PlayerObjectWorkMethods";
import { setPlayer } from "../../Player";
import { FactionName, LocationName } from "@enums";
import { CompanyName, FactionName, JobName, LocationName } from "@enums";
import { HashManager } from "../../Hacknet/HashManager";
import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
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 { CONSTANTS } from "../../Constants";
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 {
// Player-specific properties
@ -43,7 +44,7 @@ export class PlayerObject extends Person implements IPlayer {
hashManager = new HashManager();
hasTixApiAccess = false;
hasWseAccount = false;
jobs: Record<string, string> = {};
jobs: PartialRecord<CompanyName, JobName> = {};
karma = 0;
numPeopleKilled = 0;
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.money ??= 0;
// Just remove from the save file any augs that have invalid name
player.augmentations = player.augmentations.filter((ownedAug) =>
getEnumHelper("AugmentationName").isMember(ownedAug.name),
);
player.augmentations = player.augmentations.filter((ownedAug) => isMember("AugmentationName", ownedAug.name));
player.queuedAugmentations = player.queuedAugmentations.filter((ownedAug) =>
getEnumHelper("AugmentationName").isMember(ownedAug.name),
isMember("AugmentationName", ownedAug.name),
);
player.updateSkillLevels();
// 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 }[];
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;
}
}

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

@ -9,18 +9,23 @@
import type { SleevePerson } from "@nsdefs";
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 { Player } from "@player";
import { Person } from "../Person";
import { Companies } from "../../Company/Companies";
import { CompanyPositions } from "../../Company/CompanyPositions";
import { Contracts } from "../../Bladeburner/data/Contracts";
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";
@ -277,18 +282,11 @@ export class Sleeve extends Person implements SleevePerson {
* Start work for one of the player's companies
* Returns boolean indicating success
*/
workForCompany(companyName: string): boolean {
if (!Companies[companyName] || Player.jobs[companyName] == null) {
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;
workForCompany(companyName: CompanyName): boolean {
const companyPositionName = Player.jobs[companyName];
if (!companyPositionName) return false;
this.startWork(new SleeveCompanyWork(companyName));
return true;
}

@ -1,5 +1,5 @@
import { Player } from "@player";
import { LocationName } from "@enums";
import { CompanyName, JobName } from "@enums";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "../../../utils/JSONReviver";
import { Sleeve } from "../Sleeve";
import { applySleeveGains, SleeveWorkClass, SleeveWorkType } from "./Work";
@ -9,29 +9,29 @@ import { calculateCompanyWorkStats } from "../../../Work/Formulas";
import { scaleWorkStats, WorkStats } from "../../../Work/WorkStats";
import { influenceStockThroughCompanyWork } from "../../../StockMarket/PlayerInfluencing";
import { CompanyPositions } from "../../../Company/CompanyPositions";
import { isMember } from "../../../utils/EnumHelper";
import { invalidWork } from "../../../Work/InvalidWork";
export const isSleeveCompanyWork = (w: SleeveWorkClass | null): w is SleeveCompanyWork =>
w !== null && w.type === SleeveWorkType.COMPANY;
export class SleeveCompanyWork extends SleeveWorkClass {
type: SleeveWorkType.COMPANY = SleeveWorkType.COMPANY;
companyName: string;
companyName: CompanyName;
constructor(companyName?: string) {
constructor(companyName = CompanyName.NoodleBar) {
super();
this.companyName = companyName ?? LocationName.NewTokyoNoodleBar;
this.companyName = companyName;
}
getCompany(): Company {
const c = Companies[this.companyName];
if (!c) throw new Error(`Company not found: '${this.companyName}'`);
return c;
return Companies[this.companyName];
}
getGainRates(sleeve: Sleeve): WorkStats {
getGainRates(sleeve: Sleeve, job: JobName): WorkStats {
const company = this.getCompany();
return scaleWorkStats(
calculateCompanyWorkStats(sleeve, company, CompanyPositions[Player.jobs[company.name]], company.favor),
calculateCompanyWorkStats(sleeve, company, CompanyPositions[job], company.favor),
sleeve.shockBonus(),
false,
);
@ -39,7 +39,9 @@ export class SleeveCompanyWork extends SleeveWorkClass {
process(sleeve: Sleeve, cycles: number) {
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);
company.playerReputation += 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. */
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 { findCrime } from "../../../Crime/CrimeHelpers";
import { SleeveWorkType } from "../Work/Work";
import { getEnumHelper } from "../../../utils/EnumHelper";
function getWorkDescription(sleeve: Sleeve, progress: number): string {
const work = sleeve.currentWork;
@ -75,7 +76,8 @@ export function SleeveElem(props: SleeveElemProps): React.ReactElement {
case "------":
break;
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;
case "Work for Faction":
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 { Player } from "@player";
import { CONSTANTS } from "../../../Constants";
import {
@ -141,8 +143,10 @@ export function EarningsElement(props: IProps): React.ReactElement {
];
}
if (isSleeveCompanyWork(props.sleeve.currentWork)) {
const rates = props.sleeve.currentWork.getGainRates(props.sleeve);
companyWork: if (isSleeveCompanyWork(props.sleeve.currentWork)) {
const job = Player.jobs[props.sleeve.currentWork.companyName];
if (!job) break companyWork;
const rates = props.sleeve.currentWork.getGainRates(props.sleeve, job);
data = [
[`Money:`, <MoneyRate key="money-rate" money={CYCLES_PER_SEC * rates.money} />],
[`Hacking Exp:`, `${formatExp(CYCLES_PER_SEC * rates.hackExp)} / sec`],

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

@ -270,28 +270,6 @@ function evaluateVersionCompatibility(ver: string | number): void {
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

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

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

@ -3,7 +3,7 @@ import { constructorsForReviver, Generic_toJSON, Generic_fromJSON, IReviverValue
import { Player } from "@player";
import { Work, WorkType } from "./Work";
import { influenceStockThroughCompanyWork } from "../StockMarket/PlayerInfluencing";
import { AugmentationName, LocationName } from "@enums";
import { AugmentationName, CompanyName, JobName } from "@enums";
import { calculateCompanyWorkStats } from "./Formulas";
import { Companies } from "../Company/Companies";
import { applyWorkStats, scaleWorkStats, WorkStats } from "./WorkStats";
@ -12,43 +12,42 @@ import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reputation } from "../ui/React/Reputation";
import { CONSTANTS } from "../Constants";
import { CompanyPositions } from "../Company/CompanyPositions";
import { isMember } from "../utils/EnumHelper";
import { invalidWork } from "./InvalidWork";
interface CompanyWorkParams {
companyName: string;
companyName: CompanyName;
singularity: boolean;
}
export const isCompanyWork = (w: Work | null): w is CompanyWork => w !== null && w.type === WorkType.COMPANY;
export class CompanyWork extends Work {
companyName: string;
companyName: CompanyName;
constructor(params?: CompanyWorkParams) {
super(WorkType.COMPANY, params?.singularity ?? false);
this.companyName = params?.companyName ?? LocationName.NewTokyoNoodleBar;
this.companyName = params?.companyName ?? CompanyName.NoodleBar;
}
getCompany(): Company {
const c = Companies[this.companyName];
if (!c) throw new Error(`Company not found: '${this.companyName}'`);
return c;
return Companies[this.companyName];
}
getGainRates(): WorkStats {
getGainRates(job: JobName): WorkStats {
let focusBonus = 1;
if (!Player.hasAugmentation(AugmentationName.NeuroreceptorManager, true)) {
focusBonus = Player.focus ? 1 : CONSTANTS.BaseFocusBonus;
}
const company = this.getCompany();
return scaleWorkStats(
calculateCompanyWorkStats(Player, company, CompanyPositions[Player.jobs[company.name]], company.favor),
focusBonus,
);
return scaleWorkStats(calculateCompanyWorkStats(Player, company, CompanyPositions[job], company.favor), focusBonus);
}
process(cycles: number): boolean {
this.cyclesWorked += cycles;
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");
company.playerReputation += 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. */
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",
business4 = "Chief Financial Officer",
business5 = "Chief Executive Officer",
security0 = "Police Officer",
security1 = "Police Chief",
security2 = "Security Guard",
security3 = "Security Officer",
security4 = "Security Supervisor",
security5 = "Head of Security",
security0 = "Security Guard",
security1 = "Security Officer",
security2 = "Security Supervisor",
security3 = "Head of Security",
agent0 = "Field Agent",
agent1 = "Secret Agent",
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 { initSourceFiles } from "./SourceFile/SourceFiles";
import { generateRandomContract } from "./CodingContractGenerator";
import { initCompanies } from "./Company/Companies";
import { CONSTANTS } from "./Constants";
import { Factions } from "./Faction/Factions";
import { staneksGift } from "./CotMG/Helper";
@ -374,7 +373,6 @@ const Engine: {
Engine.start(); // Run main game loop and Scripts loop
Player.init();
initForeignServers(Player.getHomeComputer());
initCompanies();
Player.reapplyAllAugmentations();
// Start interactive tutorial

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

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

@ -411,7 +411,7 @@ export function WorkInProgressRoot(): React.ReactElement {
cancel: () => Router.toPage(Page.Terminal),
},
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",
stopText: "Back to Terminal",
@ -421,7 +421,8 @@ export function WorkInProgressRoot(): React.ReactElement {
const companyRep = comp.playerReputation;
const position = Player.jobs[Player.currentWork.companyName];
const gains = Player.currentWork.getGainRates();
if (!position) return <></>;
const gains = Player.currentWork.getGainRates(position);
workInfo = {
buttons: {
cancel: () => {

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

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

@ -1,5 +1,276 @@
// 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`] = `
{
"Aevum": {