Fix manual management issues

This commit is contained in:
Olivier Gagnon 2021-09-06 19:10:40 -04:00
parent 66a593e06b
commit 6e670e88e2
3 changed files with 360 additions and 577 deletions

@ -16,7 +16,7 @@ interface IExpandButtonProps {
setCity: (name: string) => void;
}
function ExpandButton(props: IExpandButtonProps) {
function ExpandButton(props: IExpandButtonProps): React.ReactElement {
function openExpandNewCityModal(): void {
const popupId = "cmpy-mgmt-expand-city-popup";
createPopup(popupId, ExpandNewCityPopup, {
@ -61,8 +61,7 @@ export function CityTabs(props: IProps): React.ReactElement {
return (
<>
{Object.values(props.division.offices).map(
(office: OfficeSpace | 0) =>
office !== 0 && (
(office: OfficeSpace | 0) => office !== 0 && (
<CityTab
current={city === office.loc}
key={office.loc}

@ -20,8 +20,7 @@ function ExpandButton(props: IExpandButtonProps): React.ReactElement {
const allIndustries = Object.keys(Industries).sort();
const possibleIndustries = allIndustries
.filter(
(industryType: string) =>
props.corp.divisions.find(
(industryType: string) => props.corp.divisions.find(
(division: IIndustry) => division.type === industryType,
) === undefined,
)

@ -16,6 +16,7 @@ import { HireEmployeePopup } from "./HireEmployeePopup";
import { ThrowPartyPopup } from "./ThrowPartyPopup";
import { ICorporation } from "../ICorporation";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Money } from "../../ui/React/Money";
interface IProps {
corp: ICorporation;
@ -24,503 +25,54 @@ interface IProps {
player: IPlayer;
}
export function IndustryOffice(props: IProps): React.ReactElement {
const [employeeManualAssignMode, setEmployeeManualAssignMode] =
useState(false);
const [employee, setEmployee] = useState<Employee | null>(null);
const [numEmployees, setNumEmployees] = useState(0);
const [numOperations, setNumOperations] = useState(0);
const [numEngineers, setNumEngineers] = useState(0);
const [numBusiness, setNumBusiness] = useState(0);
const [numManagement, setNumManagement] = useState(0);
const [numResearch, setNumResearch] = useState(0);
const [numUnassigned, setNumUnassigned] = useState(0);
const [numTraining, setNumTraining] = useState(0);
function countEmployee(employees: Employee[], job: string): number {
let n = 0;
for (let i = 0; i < employees.length; ++i) {
if (employees[i].pos === job) n++;
}
return n;
}
function resetEmployeeCount(): void {
setNumEmployees(0);
setNumOperations(0);
setNumEngineers(0);
setNumBusiness(0);
setNumManagement(0);
setNumResearch(0);
setNumUnassigned(0);
setNumTraining(0);
}
function updateEmployeeCount(): void {
// Calculate how many NEW employees we need to account for
const currentNumEmployees = props.office.employees.length;
let newOperations = numOperations;
let newEngineers = numEngineers;
let newBusiness = numBusiness;
let newManagement = numManagement;
let newResearch = numResearch;
let newUnassigned = numUnassigned;
let newTraining = numTraining;
// Record the number of employees in each position, for NEW employees only
for (let i = numEmployees; i < props.office.employees.length; ++i) {
switch (props.office.employees[i].pos) {
case EmployeePositions.Operations:
newOperations++;
break;
case EmployeePositions.Engineer:
newEngineers++;
break;
case EmployeePositions.Business:
newBusiness++;
break;
case EmployeePositions.Management:
newManagement++;
break;
case EmployeePositions.RandD:
newResearch++;
break;
case EmployeePositions.Unassigned:
newUnassigned++;
break;
case EmployeePositions.Training:
newTraining++;
break;
default:
console.error(
"Unrecognized employee position: " + props.office.employees[i].pos,
);
break;
}
}
if (newOperations !== numOperations) setNumOperations(newOperations);
if (newEngineers !== numEngineers) setNumEngineers(newEngineers);
if (newBusiness !== numBusiness) setNumBusiness(newBusiness);
if (newManagement !== numManagement) setNumManagement(newManagement);
if (newResearch !== numResearch) setNumResearch(newResearch);
if (newUnassigned !== numUnassigned) setNumUnassigned(newUnassigned);
if (newTraining !== numTraining) setNumTraining(newTraining);
if (currentNumEmployees !== numEmployees)
setNumEmployees(currentNumEmployees);
}
updateEmployeeCount();
// Renders the "Employee Management" section of the Office UI
function renderEmployeeManagement(): React.ReactElement {
updateEmployeeCount();
if (employeeManualAssignMode) {
return renderManualEmployeeManagement();
} else {
return renderAutomaticEmployeeManagement();
}
}
function renderAutomaticEmployeeManagement(): React.ReactElement {
const vechain = props.corp.unlockUpgrades[4] === 1; // Has Vechain upgrade
function switchModeOnClick(): void {
setEmployeeManualAssignMode(true);
props.corp.rerender(props.player);
}
// Calculate average morale, happiness, and energy. Also salary
// TODO is this efficient?
let totalMorale = 0,
totalHappiness = 0,
totalEnergy = 0,
totalSalary = 0;
for (let i = 0; i < props.office.employees.length; ++i) {
totalMorale += props.office.employees[i].mor;
totalHappiness += props.office.employees[i].hap;
totalEnergy += props.office.employees[i].ene;
totalSalary += props.office.employees[i].sal;
}
let avgMorale = 0,
avgHappiness = 0,
avgEnergy = 0;
if (props.office.employees.length > 0) {
avgMorale = totalMorale / props.office.employees.length;
avgHappiness = totalHappiness / props.office.employees.length;
avgEnergy = totalEnergy / props.office.employees.length;
}
// Helper functions for (re-)assigning employees to different positions
function assignEmployee(to: string): void {
if (numUnassigned <= 0) {
console.warn(
"Cannot assign employee. No unassigned employees available",
);
return;
}
switch (to) {
case EmployeePositions.Operations:
setNumOperations((n) => n + 1);
break;
case EmployeePositions.Engineer:
setNumEngineers((n) => n + 1);
break;
case EmployeePositions.Business:
setNumBusiness((n) => n + 1);
break;
case EmployeePositions.Management:
setNumManagement((n) => n + 1);
break;
case EmployeePositions.RandD:
setNumResearch((n) => n + 1);
break;
case EmployeePositions.Unassigned:
setNumUnassigned((n) => n + 1);
break;
case EmployeePositions.Training:
setNumTraining((n) => n + 1);
break;
default:
console.error("Unrecognized employee position: " + to);
break;
}
setNumUnassigned((n) => n - 1);
props.office.assignEmployeeToJob(to);
props.office.calculateEmployeeProductivity(props.corp, props.division);
props.corp.rerender(props.player);
}
function unassignEmployee(from: string): void {
function logWarning(pos: string): void {
console.warn(
`Cannot unassign from ${pos} because there is nobody assigned to that position`,
);
}
switch (from) {
case EmployeePositions.Operations:
if (numOperations <= 0) {
return logWarning(EmployeePositions.Operations);
}
setNumOperations((n) => n - 1);
break;
case EmployeePositions.Engineer:
if (numEngineers <= 0) {
return logWarning(EmployeePositions.Operations);
}
setNumEngineers((n) => n - 1);
break;
case EmployeePositions.Business:
if (numBusiness <= 0) {
return logWarning(EmployeePositions.Operations);
}
setNumBusiness((n) => n - 1);
break;
case EmployeePositions.Management:
if (numManagement <= 0) {
return logWarning(EmployeePositions.Operations);
}
setNumManagement((n) => n - 1);
break;
case EmployeePositions.RandD:
if (numResearch <= 0) {
return logWarning(EmployeePositions.Operations);
}
setNumResearch((n) => n - 1);
break;
case EmployeePositions.Unassigned:
console.warn(`Tried to unassign from the Unassigned position`);
break;
case EmployeePositions.Training:
if (numTraining <= 0) {
return logWarning(EmployeePositions.Operations);
}
setNumTraining((n) => n - 1);
break;
default:
console.error("Unrecognized employee position: " + from);
break;
}
setNumUnassigned((n) => n + 1);
props.office.unassignEmployeeFromJob(from);
props.office.calculateEmployeeProductivity(props.corp, props.division);
props.corp.rerender(props.player);
}
const positionHeaderStyle = {
fontSize: "15px",
margin: "5px 0px 5px 0px",
width: "50%",
};
const assignButtonClass =
numUnassigned > 0 ? "std-button" : "a-link-button-inactive";
function operationAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Operations);
props.corp.rerender(props.player);
}
function operationUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Operations);
props.corp.rerender(props.player);
}
const operationUnassignButtonClass =
numOperations > 0 ? "std-button" : "a-link-button-inactive";
function engineerAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Engineer);
props.corp.rerender(props.player);
}
function engineerUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Engineer);
props.corp.rerender(props.player);
}
const engineerUnassignButtonClass =
numEngineers > 0 ? "std-button" : "a-link-button-inactive";
function businessAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Business);
props.corp.rerender(props.player);
}
function businessUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Business);
props.corp.rerender(props.player);
}
const businessUnassignButtonClass =
numBusiness > 0 ? "std-button" : "a-link-button-inactive";
function managementAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Management);
props.corp.rerender(props.player);
}
function managementUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Management);
props.corp.rerender(props.player);
}
const managementUnassignButtonClass =
numManagement > 0 ? "std-button" : "a-link-button-inactive";
function rndAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.RandD);
props.corp.rerender(props.player);
}
function rndUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.RandD);
props.corp.rerender(props.player);
}
const rndUnassignButtonClass =
numResearch > 0 ? "std-button" : "a-link-button-inactive";
function trainingAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Training);
props.corp.rerender(props.player);
}
function trainingUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Training);
props.corp.rerender(props.player);
}
const trainingUnassignButtonClass =
numTraining > 0 ? "std-button" : "a-link-button-inactive";
interface ISwitchProps {
manualMode: boolean;
switchMode: (f: (b: boolean) => boolean) => void;
}
function SwitchButton(props: ISwitchProps): React.ReactElement {
if (props.manualMode) {
return (
<div>
<button className={"std-button tooltip"} onClick={switchModeOnClick}>
<button
className={"std-button tooltip"}
onClick={() => props.switchMode((old) => !old)}
>
Switch to Auto Mode
<span className={"tooltiptext"}>
Switch to Automatic Assignment Mode, which will automatically assign
employees to your selected jobs. You simply have to select the number
of assignments for each job
</span>
</button>
);
} else {
return (
<button
className={"std-button tooltip"}
onClick={() => props.switchMode((old) => !old)}
>
Switch to Manual Mode
<span className={"tooltiptext"}>
Switch to Manual Assignment Mode, which allows you to specify which
employees should get which jobs
</span>
</button>
<p>
<strong>Unassigned Employees: {numUnassigned}</strong>
</p>
<br />
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
<p>
Avg Employee Happiness: {numeralWrapper.format(avgHappiness, "0.000")}
</p>
<p>Avg Employee Energy: {numeralWrapper.format(avgEnergy, "0.000")}</p>
<p>Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}</p>
{vechain && (
<p className={"tooltip"} style={{ display: "inline-block" }}>
Material Production:{" "}
{numeralWrapper.format(
props.division.getOfficeProductivity(props.office),
"0.000",
)}
<span className={"tooltiptext"}>
The base amount of material this office can produce. Does not
include production multipliers from upgrades and materials. This
value is based off the productivity of your Operations,
Engineering, and Management employees
</span>
</p>
)}
{vechain && <br />}
{vechain && (
<p className={"tooltip"} style={{ display: "inline-block" }}>
Product Production:{" "}
{numeralWrapper.format(
props.division.getOfficeProductivity(props.office, {
forProduct: true,
}),
"0.000",
)}
<span className={"tooltiptext"}>
The base amount of any given Product this office can produce. Does
not include production multipliers from upgrades and materials.
This value is based off the productivity of your Operations,
Engineering, and Management employees
</span>
</p>
)}
{vechain && <br />}
{vechain && (
<p className={"tooltip"} style={{ display: "inline-block" }}>
Business Multiplier: x
{numeralWrapper.format(
props.division.getBusinessFactor(props.office),
"0.000",
)}
<span className={"tooltiptext"}>
The effect this office's 'Business' employees has on boosting
sales
</span>
</p>
)}
{vechain && <br />}
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Operations} ({numOperations})
<span className={"tooltiptext"}>
Manages supply chain operations. Improves the amount of Materials
and Products you produce.
</span>
</h2>
<button
className={assignButtonClass}
onClick={operationAssignButtonOnClick}
>
+
</button>
<button
className={operationUnassignButtonClass}
onClick={operationUnassignButtonOnClick}
>
-
</button>
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Engineer} ({numEngineers})
<span className={"tooltiptext"}>
Develops and maintains products and production systems. Increases
the quality of everything you produce. Also increases the amount you
produce (not as much as Operations, however)
</span>
</h2>
<button
className={assignButtonClass}
onClick={engineerAssignButtonOnClick}
>
+
</button>
<button
className={engineerUnassignButtonClass}
onClick={engineerUnassignButtonOnClick}
>
-
</button>
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Business} ({numBusiness})
<span className={"tooltiptext"}>
Handles sales and finances. Improves the amount of Materials and
Products you can sell.
</span>
</h2>
<button
className={assignButtonClass}
onClick={businessAssignButtonOnClick}
>
+
</button>
<button
className={businessUnassignButtonClass}
onClick={businessUnassignButtonOnClick}
>
-
</button>
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Management} ({numManagement})
<span className={"tooltiptext"}>
Leads and oversees employees and office operations. Improves the
effectiveness of Engineer and Operations employees
</span>
</h2>
<button
className={assignButtonClass}
onClick={managementAssignButtonOnClick}
>
+
</button>
<button
className={managementUnassignButtonClass}
onClick={managementUnassignButtonOnClick}
>
-
</button>
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.RandD} ({numResearch})
<span className={"tooltiptext"}>
Research new innovative ways to improve the company. Generates
Scientific Research
</span>
</h2>
<button className={assignButtonClass} onClick={rndAssignButtonOnClick}>
+
</button>
<button
className={rndUnassignButtonClass}
onClick={rndUnassignButtonOnClick}
>
-
</button>
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Training} ({numTraining})
<span className={"tooltiptext"}>
Set employee to training, which will increase some of their stats.
Employees in training do not affect any company operations.
</span>
</h2>
<button
className={assignButtonClass}
onClick={trainingAssignButtonOnClick}
>
+
</button>
<button
className={trainingUnassignButtonClass}
onClick={trainingUnassignButtonOnClick}
>
-
</button>
</div>
);
}
}
function renderManualEmployeeManagement(): React.ReactElement {
function switchModeOnClick(): void {
setEmployeeManualAssignMode(false);
props.corp.rerender(props.player);
}
function ManualManagement(props: IProps): React.ReactElement {
const [employee, setEmployee] = useState<Employee | null>(
props.office.employees.length > 0 ? props.office.employees[0] : null,
);
const employeeInfoDivStyle = {
color: "white",
@ -574,7 +126,6 @@ export function IndustryOffice(props: IProps): React.ReactElement {
if (employee === null) return;
const pos = getSelectText(e.target);
employee.pos = pos;
resetEmployeeCount();
props.corp.rerender(props.player);
}
@ -604,18 +155,10 @@ export function IndustryOffice(props: IProps): React.ReactElement {
: 0;
return (
<div>
<button className={"std-button tooltip"} onClick={switchModeOnClick}>
Switch to Auto Mode
<span className={"tooltiptext"}>
Switch to Automatic Assignment Mode, which will automatically assign
employees to your selected jobs. You simply have to select the
number of assignments for each job
</span>
</button>
<div style={employeeInfoDivStyle}>
<select onChange={employeeSelectorOnChange}>{employees}</select>
<select className="dropdown" onChange={employeeSelectorOnChange}>
{employees}
</select>
{employee != null && (
<p>
Morale: {numeralWrapper.format(employee.mor, nf)}
@ -634,11 +177,12 @@ export function IndustryOffice(props: IProps): React.ReactElement {
<br />
Efficiency: {numeralWrapper.format(effEff, nf)}
<br />
Salary: {numeralWrapper.formatMoney(employee.sal)}
Salary: <Money money={employee.sal} />
</p>
)}
{employee != null && (
<select
className="dropdown"
onChange={employeePositionSelectorOnChange}
value={employeePositionSelectorInitialValue}
>
@ -646,10 +190,231 @@ export function IndustryOffice(props: IProps): React.ReactElement {
</select>
)}
</div>
</div>
);
}
interface IAutoAssignProps {
office: OfficeSpace;
corp: ICorporation;
division: IIndustry;
player: IPlayer;
job: string;
desc: string;
}
function AutoAssignJob(props: IAutoAssignProps): React.ReactElement {
const numJob = countEmployee(props.office.employees, props.job);
const numUnassigned = countEmployee(
props.office.employees,
EmployeePositions.Unassigned,
);
function assignEmployee(): void {
if (numUnassigned <= 0) {
console.warn("Cannot assign employee. No unassigned employees available");
return;
}
props.office.assignEmployeeToJob(props.job);
props.office.calculateEmployeeProductivity(props.corp, props.division);
props.corp.rerender(props.player);
}
function unassignEmployee(): void {
props.office.unassignEmployeeFromJob(props.job);
props.office.calculateEmployeeProductivity(props.corp, props.division);
props.corp.rerender(props.player);
}
const positionHeaderStyle = {
fontSize: "15px",
margin: "5px 0px 5px 0px",
width: "50%",
};
return (
<>
<h2 className={"tooltip"} style={positionHeaderStyle}>
{props.job} ({numJob})
<span className={"tooltiptext"}>{props.desc}</span>
</h2>
<button
className={numUnassigned > 0 ? "std-button" : "a-link-button-inactive"}
onClick={assignEmployee}
>
+
</button>
<button
className={numJob > 0 ? "std-button" : "a-link-button-inactive"}
onClick={unassignEmployee}
>
-
</button>
<br />
</>
);
}
function AutoManagement(props: IProps): React.ReactElement {
const numUnassigned = countEmployee(
props.office.employees,
EmployeePositions.Unassigned,
);
const vechain = props.corp.unlockUpgrades[4] === 1; // Has Vechain upgrade
// Calculate average morale, happiness, and energy. Also salary
// TODO is this efficient?
let totalMorale = 0,
totalHappiness = 0,
totalEnergy = 0,
totalSalary = 0;
for (let i = 0; i < props.office.employees.length; ++i) {
totalMorale += props.office.employees[i].mor;
totalHappiness += props.office.employees[i].hap;
totalEnergy += props.office.employees[i].ene;
totalSalary += props.office.employees[i].sal;
}
let avgMorale = 0,
avgHappiness = 0,
avgEnergy = 0;
if (props.office.employees.length > 0) {
avgMorale = totalMorale / props.office.employees.length;
avgHappiness = totalHappiness / props.office.employees.length;
avgEnergy = totalEnergy / props.office.employees.length;
}
return (
<>
<p>
<strong>Unassigned Employees: {numUnassigned}</strong>
</p>
<br />
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
<p>
Avg Employee Happiness: {numeralWrapper.format(avgHappiness, "0.000")}
</p>
<p>Avg Employee Energy: {numeralWrapper.format(avgEnergy, "0.000")}</p>
<p>
Total Employee Salary: <Money money={totalSalary} />
</p>
{vechain && (
<>
<p className={"tooltip"} style={{ display: "inline-block" }}>
Material Production:{" "}
{numeralWrapper.format(
props.division.getOfficeProductivity(props.office),
"0.000",
)}
<span className={"tooltiptext"}>
The base amount of material this office can produce. Does not
include production multipliers from upgrades and materials. This
value is based off the productivity of your Operations,
Engineering, and Management employees
</span>
</p>
<br />
<p className={"tooltip"} style={{ display: "inline-block" }}>
Product Production:{" "}
{numeralWrapper.format(
props.division.getOfficeProductivity(props.office, {
forProduct: true,
}),
"0.000",
)}
<span className={"tooltiptext"}>
The base amount of any given Product this office can produce. Does
not include production multipliers from upgrades and materials.
This value is based off the productivity of your Operations,
Engineering, and Management employees
</span>
</p>
<br />
<p className={"tooltip"} style={{ display: "inline-block" }}>
Business Multiplier: x
{numeralWrapper.format(
props.division.getBusinessFactor(props.office),
"0.000",
)}
<span className={"tooltiptext"}>
The effect this office's 'Business' employees has on boosting
sales
</span>
</p>
<br />
</>
)}
<AutoAssignJob
office={props.office}
corp={props.corp}
division={props.division}
player={props.player}
job={EmployeePositions.Operations}
desc={
"Manages supply chain operations. Improves the amount of Materials and Products you produce."
}
/>
<AutoAssignJob
office={props.office}
corp={props.corp}
division={props.division}
player={props.player}
job={EmployeePositions.Engineer}
desc={
"Develops and maintains products and production systems. Increases the quality of everything you produce. Also increases the amount you produce (not as much as Operations, however)"
}
/>
<AutoAssignJob
office={props.office}
corp={props.corp}
division={props.division}
player={props.player}
job={EmployeePositions.Business}
desc={
"Handles sales and finances. Improves the amount of Materials and Products you can sell."
}
/>
<AutoAssignJob
office={props.office}
corp={props.corp}
division={props.division}
player={props.player}
job={EmployeePositions.Management}
desc={
"Leads and oversees employees and office operations. Improves the effectiveness of Engineer and Operations employees."
}
/>
<AutoAssignJob
office={props.office}
corp={props.corp}
division={props.division}
player={props.player}
job={EmployeePositions.RandD}
desc={
"Research new innovative ways to improve the company. Generates Scientific Research."
}
/>
<AutoAssignJob
office={props.office}
corp={props.corp}
division={props.division}
player={props.player}
job={EmployeePositions.Training}
desc={
"Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations."
}
/>
</>
);
}
export function IndustryOffice(props: IProps): React.ReactElement {
const [employeeManualAssignMode, setEmployeeManualAssignMode] =
useState(false);
const buttonStyle = {
fontSize: "13px",
};
@ -762,7 +527,27 @@ export function IndustryOffice(props: IProps): React.ReactElement {
)}
<br />
{renderEmployeeManagement()}
<div>
<SwitchButton
manualMode={employeeManualAssignMode}
switchMode={setEmployeeManualAssignMode}
/>
</div>
{employeeManualAssignMode ? (
<ManualManagement
corp={props.corp}
division={props.division}
office={props.office}
player={props.player}
/>
) : (
<AutoManagement
corp={props.corp}
division={props.division}
office={props.office}
player={props.player}
/>
)}
</div>
);
}