mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-17 23:08:36 +02:00
537 lines
17 KiB
TypeScript
537 lines
17 KiB
TypeScript
// React Component for displaying an Industry's OfficeSpace information
|
|
// (bottom-left panel in the Industry UI)
|
|
import React, { useState } from "react";
|
|
|
|
import { OfficeSpace } from "../OfficeSpace";
|
|
import { IIndustry } from "../IIndustry";
|
|
import { Employee } from "../Employee";
|
|
import { EmployeePositions } from "../EmployeePositions";
|
|
|
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
|
|
|
import { getSelectText } from "../../ui/uiHelpers/getSelectData";
|
|
import { createPopup } from "../../ui/React/createPopup";
|
|
import { UpgradeOfficeSizePopup } from "./UpgradeOfficeSizePopup";
|
|
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;
|
|
division: IIndustry;
|
|
office: OfficeSpace;
|
|
player: IPlayer;
|
|
rerender: () => void;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
interface ISwitchProps {
|
|
manualMode: boolean;
|
|
switchMode: (f: (b: boolean) => boolean) => void;
|
|
}
|
|
|
|
function SwitchButton(props: ISwitchProps): React.ReactElement {
|
|
if (props.manualMode) {
|
|
return (
|
|
<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>
|
|
);
|
|
}
|
|
}
|
|
|
|
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",
|
|
margin: "4px",
|
|
padding: "4px",
|
|
};
|
|
|
|
// Employee Selector
|
|
const employees = [];
|
|
for (let i = 0; i < props.office.employees.length; ++i) {
|
|
employees.push(<option key={props.office.employees[i].name}>{props.office.employees[i].name}</option>);
|
|
}
|
|
|
|
function employeeSelectorOnChange(e: React.ChangeEvent<HTMLSelectElement>): void {
|
|
const name = getSelectText(e.target);
|
|
for (let i = 0; i < props.office.employees.length; ++i) {
|
|
if (name === props.office.employees[i].name) {
|
|
setEmployee(props.office.employees[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
props.rerender();
|
|
}
|
|
|
|
// Employee Positions Selector
|
|
const emp = employee;
|
|
let employeePositionSelectorInitialValue = "";
|
|
const employeePositions = [];
|
|
const positionNames = Object.values(EmployeePositions);
|
|
for (let i = 0; i < positionNames.length; ++i) {
|
|
employeePositions.push(
|
|
<option key={positionNames[i]} value={positionNames[i]}>
|
|
{positionNames[i]}
|
|
</option>,
|
|
);
|
|
if (emp != null && emp.pos === positionNames[i]) {
|
|
employeePositionSelectorInitialValue = positionNames[i];
|
|
}
|
|
}
|
|
|
|
function employeePositionSelectorOnChange(e: React.ChangeEvent<HTMLSelectElement>): void {
|
|
if (employee === null) return;
|
|
const pos = getSelectText(e.target);
|
|
employee.pos = pos;
|
|
props.rerender();
|
|
}
|
|
|
|
// Numeraljs formatter
|
|
const nf = "0.000";
|
|
|
|
// Employee stats (after applying multipliers)
|
|
const effCre = emp ? emp.cre * props.corp.getEmployeeCreMultiplier() * props.division.getEmployeeCreMultiplier() : 0;
|
|
const effCha = emp ? emp.cha * props.corp.getEmployeeChaMultiplier() * props.division.getEmployeeChaMultiplier() : 0;
|
|
const effInt = emp ? emp.int * props.corp.getEmployeeIntMultiplier() * props.division.getEmployeeIntMultiplier() : 0;
|
|
const effEff = emp ? emp.eff * props.corp.getEmployeeEffMultiplier() * props.division.getEmployeeEffMultiplier() : 0;
|
|
|
|
return (
|
|
<div style={employeeInfoDivStyle}>
|
|
<select className="dropdown" onChange={employeeSelectorOnChange}>
|
|
{employees}
|
|
</select>
|
|
{employee != null && (
|
|
<p>
|
|
Morale: {numeralWrapper.format(employee.mor, nf)}
|
|
<br />
|
|
Happiness: {numeralWrapper.format(employee.hap, nf)}
|
|
<br />
|
|
Energy: {numeralWrapper.format(employee.ene, nf)}
|
|
<br />
|
|
Intelligence: {numeralWrapper.format(effInt, nf)}
|
|
<br />
|
|
Charisma: {numeralWrapper.format(effCha, nf)}
|
|
<br />
|
|
Experience: {numeralWrapper.format(employee.exp, nf)}
|
|
<br />
|
|
Creativity: {numeralWrapper.format(effCre, nf)}
|
|
<br />
|
|
Efficiency: {numeralWrapper.format(effEff, nf)}
|
|
<br />
|
|
Salary: <Money money={employee.sal} />
|
|
</p>
|
|
)}
|
|
{employee != null && (
|
|
<select
|
|
className="dropdown"
|
|
onChange={employeePositionSelectorOnChange}
|
|
value={employeePositionSelectorInitialValue}
|
|
>
|
|
{employeePositions}
|
|
</select>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface IAutoAssignProps {
|
|
office: OfficeSpace;
|
|
corp: ICorporation;
|
|
division: IIndustry;
|
|
player: IPlayer;
|
|
job: string;
|
|
desc: string;
|
|
rerender: () => void;
|
|
}
|
|
|
|
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.rerender();
|
|
}
|
|
|
|
function unassignEmployee(): void {
|
|
props.office.unassignEmployeeFromJob(props.job);
|
|
props.office.calculateEmployeeProductivity(props.corp, props.division);
|
|
props.rerender();
|
|
}
|
|
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 />
|
|
<table>
|
|
<tbody>
|
|
<tr>
|
|
<td>
|
|
<p>Avg Employee Morale:</p>
|
|
</td>
|
|
<td>
|
|
<p>{numeralWrapper.format(avgMorale, "0.000")}</p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<p>Avg Employee Happiness:</p>
|
|
</td>
|
|
<td>
|
|
<p>{numeralWrapper.format(avgHappiness, "0.000")}</p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<p>Avg Employee Energy:</p>
|
|
</td>
|
|
<td>
|
|
<p>{numeralWrapper.format(avgEnergy, "0.000")}</p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<p>Total Employee Salary:</p>
|
|
</td>
|
|
<td>
|
|
<p>
|
|
<Money money={totalSalary} />
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
{vechain && (
|
|
<>
|
|
<tr>
|
|
<td>
|
|
<p className={"tooltip"} style={{ display: "inline-block" }}>
|
|
Material Production:
|
|
<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>
|
|
</td>
|
|
<td>
|
|
<p>{numeralWrapper.format(props.division.getOfficeProductivity(props.office), "0.000")}</p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<p className={"tooltip"} style={{ display: "inline-block" }}>
|
|
Product Production:
|
|
<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>
|
|
</td>
|
|
<td>
|
|
<p>
|
|
{numeralWrapper.format(
|
|
props.division.getOfficeProductivity(props.office, {
|
|
forProduct: true,
|
|
}),
|
|
"0.000",
|
|
)}
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<p className={"tooltip"} style={{ display: "inline-block" }}>
|
|
Business Multiplier:
|
|
<span className={"tooltiptext"}>
|
|
The effect this office's 'Business' employees has on boosting sales
|
|
</span>
|
|
</p>
|
|
</td>
|
|
<td>
|
|
<p>x{numeralWrapper.format(props.division.getBusinessFactor(props.office), "0.000")}</p>
|
|
</td>
|
|
</tr>
|
|
</>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
<AutoAssignJob
|
|
rerender={props.rerender}
|
|
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
|
|
rerender={props.rerender}
|
|
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
|
|
rerender={props.rerender}
|
|
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
|
|
rerender={props.rerender}
|
|
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
|
|
rerender={props.rerender}
|
|
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
|
|
rerender={props.rerender}
|
|
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",
|
|
};
|
|
|
|
// Hire Employee button
|
|
let hireEmployeeButtonClass = "tooltip";
|
|
if (props.office.atCapacity()) {
|
|
hireEmployeeButtonClass += " a-link-button-inactive";
|
|
} else {
|
|
hireEmployeeButtonClass += " std-button";
|
|
if (props.office.employees.length === 0) {
|
|
hireEmployeeButtonClass += " flashing-button";
|
|
}
|
|
}
|
|
|
|
function openHireEmployeePopup(): void {
|
|
const popupId = "cmpy-mgmt-hire-employee-popup";
|
|
createPopup(popupId, HireEmployeePopup, {
|
|
rerender: props.rerender,
|
|
office: props.office,
|
|
corp: props.corp,
|
|
popupId: popupId,
|
|
player: props.player,
|
|
});
|
|
}
|
|
|
|
// Autohire employee button
|
|
let autohireEmployeeButtonClass = "tooltip";
|
|
if (props.office.atCapacity()) {
|
|
autohireEmployeeButtonClass += " a-link-button-inactive";
|
|
} else {
|
|
autohireEmployeeButtonClass += " std-button";
|
|
}
|
|
function autohireEmployeeButtonOnClick(): void {
|
|
if (props.office.atCapacity()) return;
|
|
props.office.hireRandomEmployee();
|
|
props.rerender();
|
|
}
|
|
|
|
function openUpgradeOfficeSizePopup(): void {
|
|
const popupId = "cmpy-mgmt-upgrade-office-size-popup";
|
|
createPopup(popupId, UpgradeOfficeSizePopup, {
|
|
rerender: props.rerender,
|
|
office: props.office,
|
|
corp: props.corp,
|
|
popupId: popupId,
|
|
player: props.player,
|
|
});
|
|
}
|
|
|
|
function openThrowPartyPopup(): void {
|
|
const popupId = "cmpy-mgmt-throw-office-party-popup";
|
|
createPopup(popupId, ThrowPartyPopup, {
|
|
office: props.office,
|
|
corp: props.corp,
|
|
popupId: popupId,
|
|
});
|
|
}
|
|
|
|
return (
|
|
<div className={"cmpy-mgmt-employee-panel"}>
|
|
<h1 style={{ margin: "4px 0px 5px 0px" }}>Office Space</h1>
|
|
<p>
|
|
Size: {props.office.employees.length} / {props.office.size} employees
|
|
</p>
|
|
<button className={hireEmployeeButtonClass} onClick={openHireEmployeePopup} style={buttonStyle}>
|
|
Hire Employee
|
|
{props.office.employees.length === 0 && (
|
|
<span className={"tooltiptext"}>
|
|
You'll need to hire some employees to get your operations started! It's recommended to have at least one
|
|
employee in every position
|
|
</span>
|
|
)}
|
|
</button>
|
|
<button className={autohireEmployeeButtonClass} onClick={autohireEmployeeButtonOnClick} style={buttonStyle}>
|
|
Autohire Employee
|
|
<span className={"tooltiptext"}>Automatically hires an employee and gives him/her a random name</span>
|
|
</button>
|
|
<br />
|
|
<button
|
|
className={"std-button tooltip"}
|
|
onClick={openUpgradeOfficeSizePopup}
|
|
style={buttonStyle}
|
|
disabled={props.corp.funds.lt(0)}
|
|
>
|
|
Upgrade size
|
|
<span className={"tooltiptext"}>Upgrade the office's size so that it can hold more employees!</span>
|
|
</button>
|
|
{!props.division.hasResearch("AutoPartyManager") && (
|
|
<button
|
|
className={"std-button tooltip"}
|
|
onClick={openThrowPartyPopup}
|
|
style={buttonStyle}
|
|
disabled={props.corp.funds.lt(0)}
|
|
>
|
|
Throw Party
|
|
<span className={"tooltiptext"}>
|
|
"Throw an office party to increase your employee's morale and happiness"
|
|
</span>
|
|
</button>
|
|
)}
|
|
<br />
|
|
|
|
<div>
|
|
<SwitchButton manualMode={employeeManualAssignMode} switchMode={setEmployeeManualAssignMode} />
|
|
</div>
|
|
{employeeManualAssignMode ? (
|
|
<ManualManagement
|
|
rerender={props.rerender}
|
|
corp={props.corp}
|
|
division={props.division}
|
|
office={props.office}
|
|
player={props.player}
|
|
/>
|
|
) : (
|
|
<AutoManagement
|
|
rerender={props.rerender}
|
|
corp={props.corp}
|
|
division={props.division}
|
|
office={props.office}
|
|
player={props.player}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|