2022-12-30 02:28:53 +01:00
|
|
|
import { EmployeePositions } from "./data/Enums";
|
|
|
|
import * as corpConstants from "./data/Constants";
|
2022-07-15 01:00:10 +02:00
|
|
|
import { Generic_fromJSON, Generic_toJSON, IReviverValue, Reviver } from "../utils/JSONReviver";
|
2022-09-20 12:47:54 +02:00
|
|
|
import { Industry } from "./Industry";
|
|
|
|
import { Corporation } from "./Corporation";
|
2022-10-24 22:33:25 +02:00
|
|
|
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
2022-12-30 02:28:53 +01:00
|
|
|
import { CityName } from "../Enums";
|
2021-08-28 08:50:06 +02:00
|
|
|
|
|
|
|
interface IParams {
|
2022-12-30 02:28:53 +01:00
|
|
|
loc?: CityName;
|
2021-09-05 01:09:30 +02:00
|
|
|
size?: number;
|
2021-08-28 08:50:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class OfficeSpace {
|
2022-12-30 02:28:53 +01:00
|
|
|
loc: CityName;
|
2021-09-05 01:09:30 +02:00
|
|
|
size: number;
|
2022-06-02 00:26:32 +02:00
|
|
|
|
|
|
|
maxEne = 100;
|
2021-09-05 01:09:30 +02:00
|
|
|
maxHap = 100;
|
|
|
|
maxMor = 100;
|
2022-06-02 00:26:32 +02:00
|
|
|
|
2022-10-24 14:44:01 +02:00
|
|
|
avgEne = 75;
|
|
|
|
avgHap = 75;
|
|
|
|
avgMor = 75;
|
|
|
|
|
|
|
|
avgInt = 75;
|
|
|
|
avgCha = 75;
|
|
|
|
totalExp = 0;
|
|
|
|
avgCre = 75;
|
|
|
|
avgEff = 75;
|
|
|
|
|
|
|
|
totalEmployees = 0;
|
|
|
|
totalSalary = 0;
|
|
|
|
|
2022-06-02 00:26:32 +02:00
|
|
|
autoCoffee = false;
|
2022-06-02 23:51:36 +02:00
|
|
|
autoParty = false;
|
2022-11-07 15:09:53 +01:00
|
|
|
coffeePending = false;
|
2022-11-20 04:42:34 +01:00
|
|
|
partyMult = 1;
|
2022-06-02 00:26:32 +02:00
|
|
|
|
2022-10-24 14:44:01 +02:00
|
|
|
employeeProd: Record<EmployeePositions | "total", number> = {
|
2021-09-05 01:09:30 +02:00
|
|
|
[EmployeePositions.Operations]: 0,
|
|
|
|
[EmployeePositions.Engineer]: 0,
|
|
|
|
[EmployeePositions.Business]: 0,
|
|
|
|
[EmployeePositions.Management]: 0,
|
|
|
|
[EmployeePositions.RandD]: 0,
|
2022-10-24 14:44:01 +02:00
|
|
|
[EmployeePositions.Training]: 0,
|
|
|
|
[EmployeePositions.Unassigned]: 0,
|
2021-09-05 01:09:30 +02:00
|
|
|
total: 0,
|
|
|
|
};
|
2022-10-24 14:44:01 +02:00
|
|
|
employeeJobs: Record<EmployeePositions, number> = {
|
2022-04-01 07:09:09 +02:00
|
|
|
[EmployeePositions.Operations]: 0,
|
|
|
|
[EmployeePositions.Engineer]: 0,
|
|
|
|
[EmployeePositions.Business]: 0,
|
|
|
|
[EmployeePositions.Management]: 0,
|
|
|
|
[EmployeePositions.RandD]: 0,
|
2022-04-01 07:44:53 +02:00
|
|
|
[EmployeePositions.Training]: 0,
|
|
|
|
[EmployeePositions.Unassigned]: 0,
|
2022-06-01 19:11:33 +02:00
|
|
|
};
|
2022-10-24 14:44:01 +02:00
|
|
|
employeeNextJobs: Record<EmployeePositions, number> = {
|
2022-06-01 19:11:33 +02:00
|
|
|
[EmployeePositions.Operations]: 0,
|
|
|
|
[EmployeePositions.Engineer]: 0,
|
|
|
|
[EmployeePositions.Business]: 0,
|
|
|
|
[EmployeePositions.Management]: 0,
|
|
|
|
[EmployeePositions.RandD]: 0,
|
|
|
|
[EmployeePositions.Training]: 0,
|
|
|
|
[EmployeePositions.Unassigned]: 0,
|
2022-04-01 07:09:09 +02:00
|
|
|
};
|
2021-09-05 01:09:30 +02:00
|
|
|
|
|
|
|
constructor(params: IParams = {}) {
|
2022-12-30 02:28:53 +01:00
|
|
|
this.loc = params.loc ? params.loc : CityName.Sector12;
|
2021-09-05 01:09:30 +02:00
|
|
|
this.size = params.size ? params.size : 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
atCapacity(): boolean {
|
2022-10-24 14:44:01 +02:00
|
|
|
return this.totalEmployees >= this.size;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2022-09-20 12:47:54 +02:00
|
|
|
process(marketCycles = 1, corporation: Corporation, industry: Industry): number {
|
2021-09-05 01:09:30 +02:00
|
|
|
// HRBuddy AutoRecruitment and training
|
|
|
|
if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) {
|
2022-10-24 14:44:01 +02:00
|
|
|
this.hireRandomEmployee(
|
|
|
|
industry.hasResearch("HRBuddy-Training") ? EmployeePositions.Training : EmployeePositions.Unassigned,
|
|
|
|
);
|
2021-08-28 08:50:06 +02:00
|
|
|
}
|
|
|
|
|
2022-06-02 00:26:32 +02:00
|
|
|
// Update employee jobs and job counts
|
2022-10-24 14:44:01 +02:00
|
|
|
for (const [pos, jobCount] of Object.entries(this.employeeNextJobs) as [EmployeePositions, number][]) {
|
|
|
|
this.employeeJobs[pos] = jobCount;
|
2022-06-02 23:51:36 +02:00
|
|
|
}
|
2022-04-01 07:09:09 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
// Process Office properties
|
|
|
|
this.maxEne = 100;
|
|
|
|
this.maxHap = 100;
|
|
|
|
this.maxMor = 100;
|
2022-06-02 00:26:32 +02:00
|
|
|
|
2023-01-29 14:14:12 +01:00
|
|
|
if (industry.hasResearch("Go-Juice")) this.maxEne += 10;
|
|
|
|
if (industry.hasResearch("JoyWire")) this.maxHap += 10;
|
|
|
|
if (industry.hasResearch("Sti.mu")) this.maxMor += 10;
|
|
|
|
if (industry.hasResearch("AutoBrew")) this.autoCoffee = true;
|
|
|
|
if (industry.hasResearch("AutoPartyManager")) this.autoParty = true;
|
2021-08-28 08:50:06 +02:00
|
|
|
|
2022-10-24 14:44:01 +02:00
|
|
|
if (this.totalEmployees > 0) {
|
2022-11-07 15:09:53 +01:00
|
|
|
/** Multiplier for employee morale/happiness/energy based on company performance */
|
|
|
|
const perfMult = Math.pow(
|
2023-01-27 14:15:54 +01:00
|
|
|
1.002 -
|
|
|
|
(corporation.funds < 0 ? 0.002 : 0) -
|
|
|
|
(industry.lastCycleRevenue < industry.lastCycleExpenses ? 0.002 : 0),
|
2022-11-07 15:09:53 +01:00
|
|
|
marketCycles,
|
|
|
|
);
|
2023-01-27 14:15:54 +01:00
|
|
|
// Flat reduction per cycle.
|
|
|
|
// This does not cause a noticable decrease (it's only -.001% per cycle), it only serves
|
|
|
|
// to make the numbers slightly different between Happiness and Morale.
|
2022-11-07 15:09:53 +01:00
|
|
|
const reduction = 0.001 * marketCycles;
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2022-06-02 00:26:32 +02:00
|
|
|
if (this.autoCoffee) {
|
2022-10-24 14:44:01 +02:00
|
|
|
this.avgEne = this.maxEne;
|
2021-09-05 01:09:30 +02:00
|
|
|
} else {
|
2022-11-07 15:09:53 +01:00
|
|
|
// Coffee gives a flat +3 to energy
|
|
|
|
this.avgEne = (this.avgEne - reduction) * perfMult + (this.coffeePending ? 3 : 0);
|
|
|
|
// Coffee also halves the difference between current and max energy
|
|
|
|
if (this.coffeePending) this.avgEne = this.maxEne - (this.maxEne - this.avgEne) / 2;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2022-06-02 00:26:32 +02:00
|
|
|
if (this.autoParty) {
|
2022-10-24 14:44:01 +02:00
|
|
|
this.avgMor = this.maxMor;
|
|
|
|
this.avgHap = this.maxHap;
|
2021-09-05 01:09:30 +02:00
|
|
|
} else {
|
2022-11-07 15:09:53 +01:00
|
|
|
// Each 5% multiplier gives an extra flat +1 to morale and happiness to make recovering from low morale easier.
|
2022-11-20 13:56:58 +01:00
|
|
|
const increase = this.partyMult > 1 ? (this.partyMult - 1) * 20 : 0;
|
2022-11-07 15:09:53 +01:00
|
|
|
this.avgHap = ((this.avgHap - reduction) * perfMult + increase) * this.partyMult;
|
|
|
|
this.avgMor = (this.avgMor * perfMult + increase) * this.partyMult;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2023-01-03 17:55:50 +01:00
|
|
|
this.avgEne = Math.max(Math.min(this.avgEne, this.maxEne), corpConstants.minEmployeeDecay);
|
|
|
|
this.avgMor = Math.max(Math.min(this.avgMor, this.maxMor), corpConstants.minEmployeeDecay);
|
|
|
|
this.avgHap = Math.max(Math.min(this.avgHap, this.maxHap), corpConstants.minEmployeeDecay);
|
2022-06-01 19:11:33 +02:00
|
|
|
|
2022-11-07 15:09:53 +01:00
|
|
|
this.coffeePending = false;
|
2022-11-20 04:42:34 +01:00
|
|
|
this.partyMult = 1;
|
2022-06-01 19:11:33 +02:00
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2022-10-24 14:44:01 +02:00
|
|
|
// Get experience increase; unassigned employees do not contribute, employees in training contribute 5x
|
|
|
|
this.totalExp +=
|
|
|
|
0.0015 *
|
|
|
|
marketCycles *
|
|
|
|
(this.totalEmployees -
|
|
|
|
this.employeeJobs[EmployeePositions.Unassigned] +
|
|
|
|
this.employeeJobs[EmployeePositions.Training] * 4);
|
2022-04-01 07:09:09 +02:00
|
|
|
|
2022-10-24 14:44:01 +02:00
|
|
|
this.calculateEmployeeProductivity(corporation, industry);
|
|
|
|
if (this.totalEmployees === 0) {
|
|
|
|
this.totalSalary = 0;
|
|
|
|
} else {
|
|
|
|
this.totalSalary =
|
2022-12-30 02:28:53 +01:00
|
|
|
corpConstants.employeeSalaryMultiplier *
|
2022-10-24 14:44:01 +02:00
|
|
|
marketCycles *
|
|
|
|
this.totalEmployees *
|
|
|
|
(this.avgInt + this.avgCha + this.totalExp / this.totalEmployees + this.avgCre + this.avgEff);
|
2022-04-01 07:09:09 +02:00
|
|
|
}
|
2022-10-24 14:44:01 +02:00
|
|
|
return this.totalSalary;
|
2022-04-01 07:09:09 +02:00
|
|
|
}
|
|
|
|
|
2022-09-20 12:47:54 +02:00
|
|
|
calculateEmployeeProductivity(corporation: Corporation, industry: Industry): void {
|
2022-10-24 14:44:01 +02:00
|
|
|
const effCre = this.avgCre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),
|
|
|
|
effCha = this.avgCha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),
|
|
|
|
effInt = this.avgInt * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(),
|
|
|
|
effEff = this.avgEff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier();
|
|
|
|
const prodBase = this.avgMor * this.avgHap * this.avgEne * 1e-6;
|
2021-08-28 08:50:06 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
let total = 0;
|
2022-10-24 14:44:01 +02:00
|
|
|
const exp = this.totalExp / this.totalEmployees || 0;
|
|
|
|
for (const name of Object.keys(this.employeeProd) as (EmployeePositions | "total")[]) {
|
|
|
|
let prodMult = 0;
|
|
|
|
switch (name) {
|
|
|
|
case EmployeePositions.Operations:
|
|
|
|
prodMult = 0.6 * effInt + 0.1 * effCha + exp + 0.5 * effCre + effEff;
|
|
|
|
break;
|
|
|
|
case EmployeePositions.Engineer:
|
|
|
|
prodMult = effInt + 0.1 * effCha + 1.5 * exp + effEff;
|
|
|
|
break;
|
|
|
|
case EmployeePositions.Business:
|
|
|
|
prodMult = 0.4 * effInt + effCha + 0.5 * exp;
|
|
|
|
break;
|
|
|
|
case EmployeePositions.Management:
|
|
|
|
prodMult = 2 * effCha + exp + 0.2 * effCre + 0.7 * effEff;
|
|
|
|
break;
|
|
|
|
case EmployeePositions.RandD:
|
|
|
|
prodMult = 1.5 * effInt + 0.8 * exp + effCre + 0.5 * effEff;
|
|
|
|
break;
|
|
|
|
case EmployeePositions.Unassigned:
|
|
|
|
case EmployeePositions.Training:
|
|
|
|
case "total":
|
|
|
|
continue;
|
|
|
|
default:
|
|
|
|
console.error(`Invalid employee position: ${name}`);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
this.employeeProd[name] = this.employeeJobs[name] * prodMult * prodBase;
|
|
|
|
total += this.employeeProd[name];
|
2021-08-28 08:50:06 +02:00
|
|
|
}
|
2022-10-24 14:44:01 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
this.employeeProd.total = total;
|
|
|
|
}
|
|
|
|
|
2022-10-24 14:44:01 +02:00
|
|
|
hireRandomEmployee(position: EmployeePositions): boolean {
|
|
|
|
if (this.atCapacity()) return false;
|
|
|
|
if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) return false;
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2022-10-24 14:44:01 +02:00
|
|
|
++this.totalEmployees;
|
|
|
|
++this.employeeJobs[position];
|
|
|
|
++this.employeeNextJobs[position];
|
2021-09-05 01:09:30 +02:00
|
|
|
|
2022-10-24 22:33:25 +02:00
|
|
|
this.totalExp += getRandomInt(50, 100);
|
|
|
|
|
|
|
|
this.avgMor = (this.avgMor * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1);
|
|
|
|
this.avgHap = (this.avgHap * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1);
|
|
|
|
this.avgEne = (this.avgEne * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1);
|
|
|
|
|
|
|
|
this.avgInt = (this.avgInt * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1);
|
|
|
|
this.avgCha = (this.avgCha * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1);
|
|
|
|
this.avgCre = (this.avgCre * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1);
|
|
|
|
this.avgEff = (this.avgEff * this.totalEmployees + getRandomInt(50, 100)) / (this.totalEmployees + 1);
|
2022-10-24 14:44:01 +02:00
|
|
|
return true;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2022-10-24 14:44:01 +02:00
|
|
|
autoAssignJob(job: EmployeePositions, target: number): boolean {
|
2023-01-29 14:14:12 +01:00
|
|
|
if (job === EmployeePositions.Unassigned) {
|
|
|
|
throw new Error("internal autoAssignJob function called with EmployeePositions.Unassigned");
|
|
|
|
}
|
2022-10-24 14:44:01 +02:00
|
|
|
const diff = target - this.employeeNextJobs[job];
|
2022-06-01 19:11:33 +02:00
|
|
|
|
2023-01-30 20:42:46 +01:00
|
|
|
if (diff === 0) return true;
|
|
|
|
// We are already at the desired number
|
2023-01-29 14:14:12 +01:00
|
|
|
else if (diff <= this.employeeNextJobs[EmployeePositions.Unassigned]) {
|
2022-10-24 14:44:01 +02:00
|
|
|
// This covers both a negative diff (reducing the amount of employees in position) and a positive (increasing and using up unassigned employees)
|
|
|
|
this.employeeNextJobs[EmployeePositions.Unassigned] -= diff;
|
|
|
|
this.employeeNextJobs[job] = target;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
2022-06-02 03:43:22 +02:00
|
|
|
getCoffeeCost(): number {
|
2022-12-30 02:28:53 +01:00
|
|
|
return corpConstants.coffeeCostPerEmployee * this.totalEmployees;
|
2022-06-02 03:43:22 +02:00
|
|
|
}
|
|
|
|
|
2022-11-07 15:09:53 +01:00
|
|
|
setCoffee(): boolean {
|
|
|
|
if (!this.coffeePending && !this.autoCoffee && this.totalEmployees > 0) {
|
|
|
|
this.coffeePending = true;
|
2022-06-02 00:26:32 +02:00
|
|
|
return true;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-06-02 00:26:32 +02:00
|
|
|
setParty(mult: number): boolean {
|
2022-11-20 04:42:34 +01:00
|
|
|
if (mult > 1 && this.partyMult === 1 && !this.autoParty && this.totalEmployees > 0) {
|
2022-06-02 00:26:32 +02:00
|
|
|
this.partyMult = mult;
|
|
|
|
return true;
|
2022-01-13 19:47:54 +01:00
|
|
|
}
|
2022-06-02 00:26:32 +02:00
|
|
|
return false;
|
2022-01-13 19:47:54 +01:00
|
|
|
}
|
|
|
|
|
2022-07-15 01:00:10 +02:00
|
|
|
toJSON(): IReviverValue {
|
2021-09-05 01:09:30 +02:00
|
|
|
return Generic_toJSON("OfficeSpace", this);
|
|
|
|
}
|
|
|
|
|
2022-07-15 01:00:10 +02:00
|
|
|
static fromJSON(value: IReviverValue): OfficeSpace {
|
2022-10-24 14:44:01 +02:00
|
|
|
// Convert employees from the old version
|
|
|
|
if (value.data.hasOwnProperty("employees")) {
|
|
|
|
const empCopy: [{ data: { hap: number; mor: number; ene: number; exp: number } }] = value.data.employees;
|
|
|
|
delete value.data.employees;
|
|
|
|
const ret = Generic_fromJSON(OfficeSpace, value.data);
|
|
|
|
ret.totalEmployees = empCopy.length;
|
|
|
|
ret.avgHap = empCopy.reduce((a, b) => a + b.data.hap, 0) / ret.totalEmployees || 75;
|
|
|
|
ret.avgMor = empCopy.reduce((a, b) => a + b.data.mor, 0) / ret.totalEmployees || 75;
|
|
|
|
ret.avgEne = empCopy.reduce((a, b) => a + b.data.ene, 0) / ret.totalEmployees || 75;
|
|
|
|
ret.totalExp = empCopy.reduce((a, b) => a + b.data.exp, 0);
|
|
|
|
return ret;
|
|
|
|
}
|
2021-09-05 01:09:30 +02:00
|
|
|
return Generic_fromJSON(OfficeSpace, value.data);
|
|
|
|
}
|
2021-08-28 08:50:06 +02:00
|
|
|
}
|
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
Reviver.constructors.OfficeSpace = OfficeSpace;
|