From a8254e7144955bd4b5cd73bf5b4786363bc30ec1 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Fri, 27 Aug 2021 17:39:15 -0400
Subject: [PATCH 01/30] one component
---
src/Corporation/ui/HeaderTab.tsx | 20 ++++++++++++++++++++
src/Corporation/ui/HeaderTabs.jsx | 14 +-------------
2 files changed, 21 insertions(+), 13 deletions(-)
create mode 100644 src/Corporation/ui/HeaderTab.tsx
diff --git a/src/Corporation/ui/HeaderTab.tsx b/src/Corporation/ui/HeaderTab.tsx
new file mode 100644
index 000000000..c7317e2f0
--- /dev/null
+++ b/src/Corporation/ui/HeaderTab.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+
+interface IProps {
+ current: boolean;
+ text: string;
+ onClick: () => void;
+}
+
+export function HeaderTab(props: IProps) {
+ let className = "cmpy-mgmt-header-tab";
+ if (props.current) {
+ className += " current";
+ }
+
+ return (
+
+ {props.text}
+
+ )
+}
\ No newline at end of file
diff --git a/src/Corporation/ui/HeaderTabs.jsx b/src/Corporation/ui/HeaderTabs.jsx
index c2d895d7d..aefcf8de1 100644
--- a/src/Corporation/ui/HeaderTabs.jsx
+++ b/src/Corporation/ui/HeaderTabs.jsx
@@ -3,19 +3,7 @@
// divisions, see an overview of your corporation, or create a new industry
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
-
-function HeaderTab(props) {
- let className = "cmpy-mgmt-header-tab";
- if (props.current) {
- className += " current";
- }
-
- return (
-
- {props.text}
-
- )
-}
+import { HeaderTab } from "./HeaderTab";
export class HeaderTabs extends BaseReactComponent {
renderTab(props) {
From 07c0b708d7ea6403257d88d063933a2f928b3c9a Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 00:11:42 -0400
Subject: [PATCH 02/30] more convertion
---
src/Corporation/IDivision.ts | 5 +++
src/Corporation/IOfficeSpace.ts | 15 +++++++
src/Corporation/ui/HeaderTab.tsx | 2 +-
src/Corporation/ui/HeaderTabs.jsx | 66 -------------------------------
src/Corporation/ui/HeaderTabs.tsx | 49 +++++++++++++++++++++++
src/Corporation/ui/Root.jsx | 2 +-
6 files changed, 71 insertions(+), 68 deletions(-)
create mode 100644 src/Corporation/IDivision.ts
create mode 100644 src/Corporation/IOfficeSpace.ts
delete mode 100644 src/Corporation/ui/HeaderTabs.jsx
create mode 100644 src/Corporation/ui/HeaderTabs.tsx
diff --git a/src/Corporation/IDivision.ts b/src/Corporation/IDivision.ts
new file mode 100644
index 000000000..603d9b538
--- /dev/null
+++ b/src/Corporation/IDivision.ts
@@ -0,0 +1,5 @@
+
+export interface IDivision {
+ name: string;
+ offices: IMap;
+}
diff --git a/src/Corporation/IOfficeSpace.ts b/src/Corporation/IOfficeSpace.ts
new file mode 100644
index 000000000..2138e5e19
--- /dev/null
+++ b/src/Corporation/IOfficeSpace.ts
@@ -0,0 +1,15 @@
+export interface IOfficeSpace {
+ loc: string;
+ cost: number;
+ size: number;
+ comf: number;
+ beau: number;
+ tier: any;
+ minEne: number;
+ maxEne: number;
+ minHap: number;
+ maxHap: number;
+ maxMor: number;
+ employees: any;
+ employeeProd: any;
+}
diff --git a/src/Corporation/ui/HeaderTab.tsx b/src/Corporation/ui/HeaderTab.tsx
index c7317e2f0..d31f34271 100644
--- a/src/Corporation/ui/HeaderTab.tsx
+++ b/src/Corporation/ui/HeaderTab.tsx
@@ -6,7 +6,7 @@ interface IProps {
onClick: () => void;
}
-export function HeaderTab(props: IProps) {
+export function HeaderTab(props: IProps): React.ReactElement {
let className = "cmpy-mgmt-header-tab";
if (props.current) {
className += " current";
diff --git a/src/Corporation/ui/HeaderTabs.jsx b/src/Corporation/ui/HeaderTabs.jsx
deleted file mode 100644
index aefcf8de1..000000000
--- a/src/Corporation/ui/HeaderTabs.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-// React Components for the Corporation UI's navigation tabs
-// These are the tabs at the top of the UI that let you switch to different
-// divisions, see an overview of your corporation, or create a new industry
-import React from "react";
-import { BaseReactComponent } from "./BaseReactComponent";
-import { HeaderTab } from "./HeaderTab";
-
-export class HeaderTabs extends BaseReactComponent {
- renderTab(props) {
- return (
-
- )
- }
-
- render() {
- const overviewOnClick = () => {
- this.routing().routeToOverviewPage();
- this.corp().rerender();
- }
-
- const divisionOnClicks = {};
- for (const division of this.corp().divisions) {
- const name = division.name;
- const onClick = () => {
- this.routing().routeTo(name);
- this.corp().rerender();
- }
-
- divisionOnClicks[name] = onClick;
- }
-
- return (
-
- {
- this.renderTab({
- current: this.routing().isOnOverviewPage(),
- key: "overview",
- onClick: overviewOnClick,
- text: this.corp().name,
- })
- }
- {
- this.corp().divisions.map((division) => {
- return this.renderTab({
- current: this.routing().isOn(division.name),
- key: division.name,
- onClick: divisionOnClicks[division.name],
- text: division.name,
- });
- })
- }
- {
- this.renderTab({
- onClick: this.eventHandler().createNewIndustryPopup.bind(this.eventHandler()),
- text: "Expand into new Industry",
- })
- }
-
- )
- }
-}
diff --git a/src/Corporation/ui/HeaderTabs.tsx b/src/Corporation/ui/HeaderTabs.tsx
new file mode 100644
index 000000000..75f41b4ef
--- /dev/null
+++ b/src/Corporation/ui/HeaderTabs.tsx
@@ -0,0 +1,49 @@
+// React Components for the Corporation UI's navigation tabs
+// These are the tabs at the top of the UI that let you switch to different
+// divisions, see an overview of your corporation, or create a new industry
+import React from "react";
+import { HeaderTab } from "./HeaderTab";
+import { IDivision } from "../IDivision";
+
+interface IProps {
+ corp: any;
+ eventHandler: any;
+ routing: any;
+}
+
+export function HeaderTabs(props: IProps): React.ReactElement {
+ console.log(props);
+ function overviewOnClick() {
+ props.routing.routeToOverviewPage();
+ props.corp.rerender();
+ }
+
+ return (
+
+
+ {
+ props.corp.divisions.map((division: IDivision) =>
+ {
+ props.routing.routeTo(division.name);
+ props.corp.rerender();
+ }}
+ text={division.name}
+ />)
+ }
+
+
+ )
+
+}
diff --git a/src/Corporation/ui/Root.jsx b/src/Corporation/ui/Root.jsx
index f393de20e..b9071646a 100644
--- a/src/Corporation/ui/Root.jsx
+++ b/src/Corporation/ui/Root.jsx
@@ -9,7 +9,7 @@ export class CorporationRoot extends BaseReactComponent {
render() {
return (
-
+
)
From 3d2aeb63a0fdab641442aa98fd2a01befec3be35 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 00:29:19 -0400
Subject: [PATCH 03/30] more convert
---
src/Corporation/IDivision.ts | 2 +
src/Corporation/ui/CityTab.tsx | 20 ++++++++++
src/Corporation/ui/CityTabs.jsx | 62 -------------------------------
src/Corporation/ui/CityTabs.tsx | 36 ++++++++++++++++++
src/Corporation/ui/HeaderTabs.tsx | 1 -
5 files changed, 58 insertions(+), 63 deletions(-)
create mode 100644 src/Corporation/ui/CityTab.tsx
delete mode 100644 src/Corporation/ui/CityTabs.jsx
create mode 100644 src/Corporation/ui/CityTabs.tsx
diff --git a/src/Corporation/IDivision.ts b/src/Corporation/IDivision.ts
index 603d9b538..1ed13a8e8 100644
--- a/src/Corporation/IDivision.ts
+++ b/src/Corporation/IDivision.ts
@@ -1,3 +1,5 @@
+import { IOfficeSpace } from "./IOfficeSpace";
+import { IMap } from "../types";
export interface IDivision {
name: string;
diff --git a/src/Corporation/ui/CityTab.tsx b/src/Corporation/ui/CityTab.tsx
new file mode 100644
index 000000000..8e6759417
--- /dev/null
+++ b/src/Corporation/ui/CityTab.tsx
@@ -0,0 +1,20 @@
+import React from "react";
+
+interface IProps {
+ onClick: () => void;
+ name: string;
+ current: boolean;
+}
+
+export function CityTab(props: IProps): React.ReactElement {
+ let className = "cmpy-mgmt-city-tab";
+ if (props.current) {
+ className += " current";
+ }
+
+ return (
+
+ {props.name}
+
+ )
+}
\ No newline at end of file
diff --git a/src/Corporation/ui/CityTabs.jsx b/src/Corporation/ui/CityTabs.jsx
deleted file mode 100644
index 56dfbf27b..000000000
--- a/src/Corporation/ui/CityTabs.jsx
+++ /dev/null
@@ -1,62 +0,0 @@
-// React Components for the Corporation UI's City navigation tabs
-// These allow player to navigate between different cities for each industry
-import React from "react";
-import { BaseReactComponent } from "./BaseReactComponent";
-
-export class CityTabs extends BaseReactComponent {
- constructor(props) {
- // An object with [key = city name] and [value = click handler]
- // needs to be passed into the constructor as the "onClicks" property.
- // We'll make sure that that happens here
- if (props.onClicks == null) {
- throw new Error(`CityTabs component constructed without onClick handlers`);
- }
- if (props.city == null) {
- throw new Error(`CityTabs component constructed without 'city' property`)
- }
- if (props.cityStateSetter == null) {
- throw new Error(`CityTabs component constructed without 'cityStateSetter' property`)
- }
-
- super(props);
- }
-
- renderTab(props) {
- let className = "cmpy-mgmt-city-tab";
- if (props.current) {
- className += " current";
- }
-
- return (
-
- {props.key}
-
- )
- }
-
- render() {
- const division = this.routing().currentDivision;
-
- const tabs = [];
-
- // Tabs for each city
- for (const cityName in this.props.onClicks) {
- tabs.push(this.renderTab({
- current: this.props.city === cityName,
- key: cityName,
- onClick: this.props.onClicks[cityName],
- }));
- }
-
- // Tab to "Expand into new City"
- const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler(), division, this.props.cityStateSetter);
-
- tabs.push(this.renderTab({
- current: false,
- key: "Expand into new City",
- onClick: newCityOnClick,
- }));
-
- return tabs;
- }
-}
diff --git a/src/Corporation/ui/CityTabs.tsx b/src/Corporation/ui/CityTabs.tsx
new file mode 100644
index 000000000..e0f222a49
--- /dev/null
+++ b/src/Corporation/ui/CityTabs.tsx
@@ -0,0 +1,36 @@
+// React Components for the Corporation UI's City navigation tabs
+// These allow player to navigate between different cities for each industry
+import React from "react";
+import { CityTab } from "./CityTab";
+
+interface IProps {
+ eventHandler: any;
+ routing: any;
+ onClicks: {[key: string]: () => void};
+ city: string; // currentCity
+ cityStateSetter: any;
+}
+
+export function CityTabs(props: IProps): React.ReactElement {
+ const division = props.routing.currentDivision;
+
+ const tabs = [];
+
+ // Tabs for each city
+ for (const cityName in props.onClicks) {
+ tabs.push(
+
+ );
+ }
+
+ tabs.push(
+ props.eventHandler.createNewCityPopup(division, props.cityStateSetter)}
+ />
+ );
+
+ return <>{tabs}>;
+}
\ No newline at end of file
diff --git a/src/Corporation/ui/HeaderTabs.tsx b/src/Corporation/ui/HeaderTabs.tsx
index 75f41b4ef..1888ba7c6 100644
--- a/src/Corporation/ui/HeaderTabs.tsx
+++ b/src/Corporation/ui/HeaderTabs.tsx
@@ -12,7 +12,6 @@ interface IProps {
}
export function HeaderTabs(props: IProps): React.ReactElement {
- console.log(props);
function overviewOnClick() {
props.routing.routeToOverviewPage();
props.corp.rerender();
From 4b53d6ecf7c89d2f611c65ae14c5114cb98763e1 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 02:50:06 -0400
Subject: [PATCH 04/30] more convertion
---
src/Corporation/Corporation.jsx | 457 +-----------------
src/Corporation/Employee.ts | 194 ++++++++
src/Corporation/IDivision.ts | 2 +-
src/Corporation/OfficeSpace.ts | 323 +++++++++++++
src/Corporation/data/Constants.ts | 60 +++
src/Corporation/ui/CityTabs.tsx | 39 +-
.../ui/CorporationUIEventHandler.js | 2 +-
src/Corporation/ui/ExpandNewCityPopup.tsx | 51 ++
src/Corporation/ui/HeaderTabs.tsx | 3 +-
src/Corporation/ui/Industry.jsx | 7 +-
src/Corporation/ui/IndustryOffice.jsx | 2 +-
src/Corporation/ui/IndustryOverview.jsx | 2 +-
...tryWarehouse.jsx => IndustryWarehouse.tsx} | 101 ++--
src/Corporation/ui/LevelableUpgrade.jsx | 36 --
src/Corporation/ui/LevelableUpgrade.tsx | 39 ++
src/Corporation/ui/MainPanel.jsx | 3 +-
.../ui/{Overview.jsx => Overview.tsx} | 197 ++++----
src/Corporation/ui/UnlockUpgrade.jsx | 30 --
src/Corporation/ui/UnlockUpgrade.tsx | 32 ++
src/InteractiveTutorial.d.ts | 2 +-
20 files changed, 889 insertions(+), 693 deletions(-)
create mode 100644 src/Corporation/Employee.ts
create mode 100644 src/Corporation/OfficeSpace.ts
create mode 100644 src/Corporation/data/Constants.ts
create mode 100644 src/Corporation/ui/ExpandNewCityPopup.tsx
rename src/Corporation/ui/{IndustryWarehouse.jsx => IndustryWarehouse.tsx} (89%)
delete mode 100644 src/Corporation/ui/LevelableUpgrade.jsx
create mode 100644 src/Corporation/ui/LevelableUpgrade.tsx
rename src/Corporation/ui/{Overview.jsx => Overview.tsx} (63%)
delete mode 100644 src/Corporation/ui/UnlockUpgrade.jsx
create mode 100644 src/Corporation/ui/UnlockUpgrade.tsx
diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx
index 02f4c9a76..681960e18 100644
--- a/src/Corporation/Corporation.jsx
+++ b/src/Corporation/Corporation.jsx
@@ -12,6 +12,8 @@ import { MaterialSizes } from "./MaterialSizes";
import { Product } from "./Product";
import { ResearchMap } from "./ResearchMap";
import { Warehouse } from "./Warehouse";
+import { Employee } from "./Employee";
+import { OfficeSpace } from "./OfficeSpace";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { showLiterature } from "../Literature/LiteratureHelpers";
@@ -1453,167 +1455,6 @@ Industry.fromJSON = function(value) {
Reviver.constructors.Industry = Industry;
-function Employee(params={}) {
- if (!(this instanceof Employee)) {
- return new Employee(params);
- }
- this.name = params.name ? params.name : "Bobby";
-
- //Morale, happiness, and energy are 0-100
- this.mor = params.morale ? params.morale : getRandomInt(50, 100);
- this.hap = params.happiness ? params.happiness : getRandomInt(50, 100);
- this.ene = params.energy ? params.energy : getRandomInt(50, 100);
-
- this.int = params.intelligence ? params.intelligence : getRandomInt(10, 50);
- this.cha = params.charisma ? params.charisma : getRandomInt(10, 50);
- this.exp = params.experience ? params.experience : getRandomInt(10, 50);
- this.cre = params.creativity ? params.creativity : getRandomInt(10, 50);
- this.eff = params.efficiency ? params.efficiency : getRandomInt(10, 50);
- this.sal = params.salary ? params.salary : getRandomInt(0.1, 5);
- this.pro = 0; //Productivity, This is calculated
-
- this.cyclesUntilRaise = CyclesPerEmployeeRaise;
-
- this.loc = params.loc ? params.loc : "";
- this.pos = EmployeePositions.Unassigned;
-}
-
-//Returns the amount the employee needs to be paid
-Employee.prototype.process = function(marketCycles=1, office) {
- var gain = 0.003 * marketCycles,
- det = gain * Math.random();
- this.exp += gain;
-
- // Employee salaries slowly go up over time
- this.cyclesUntilRaise -= marketCycles;
- if (this.cyclesUntilRaise <= 0) {
- this.salary += EmployeeRaiseAmount;
- this.cyclesUntilRaise += CyclesPerEmployeeRaise;
- }
-
- //Training
- var trainingEff = gain * Math.random();
- if (this.pos === EmployeePositions.Training) {
- //To increase creativity and intelligence special upgrades are needed
- this.cha += trainingEff;
- this.exp += trainingEff;
- this.eff += trainingEff;
- }
-
- this.ene -= det;
- this.hap -= det;
-
- if (this.ene < office.minEne) {this.ene = office.minEne;}
- if (this.hap < office.minHap) {this.hap = office.minHap;}
- var salary = this.sal * marketCycles * SecsPerMarketCycle;
- return salary;
-}
-
-Employee.prototype.calculateProductivity = function(corporation, industry) {
- var effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),
- effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),
- effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(),
- effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier();
- const prodBase = this.mor * this.hap * this.ene * 1e-6;
- let prodMult;
- switch(this.pos) {
- //Calculate productivity based on position. This is multipled by prodBase
- //to get final value
- case EmployeePositions.Operations:
- prodMult = (0.6 * effInt) + (0.1 * effCha) + (this.exp) +
- (0.5 * effCre) + (effEff);
- break;
- case EmployeePositions.Engineer:
- prodMult = (effInt) + (0.1 * effCha) + (1.5 * this.exp) +
- (effEff);
- break;
- case EmployeePositions.Business:
- prodMult = (0.4 * effInt) + (effCha) + (0.5 * this.exp);
- break;
- case EmployeePositions.Management:
- prodMult = (2 * effCha) + (this.exp) + (0.2 * effCre) +
- (0.7 * effEff);
- break;
- case EmployeePositions.RandD:
- prodMult = (1.5 * effInt) + (0.8 * this.exp) + (effCre) +
- (0.5 * effEff);
- break;
- case EmployeePositions.Unassigned:
- case EmployeePositions.Training:
- prodMult = 0;
- break;
- default:
- console.error(`Invalid employee position: ${this.pos}`);
- break;
- }
- return prodBase * prodMult;
-}
-
-//Process benefits from having an office party thrown
-Employee.prototype.throwParty = function(money) {
- var mult = 1 + (money / 10e6);
- this.mor *= mult;
- this.mor = Math.min(100, this.mor);
- this.hap *= mult;
- this.hap = Math.min(100, this.hap);
- return mult;
-}
-
-//'panel' is the DOM element on which to create the UI
-Employee.prototype.createUI = function(panel, corporation, industry) {
- var effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),
- effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),
- effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(),
- effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier();
- panel.style.color = "white";
- panel.appendChild(createElement("p", {
- id:"cmpy-mgmt-employee-" + this.name + "-panel-text",
- innerHTML:"Morale: " + formatNumber(this.mor, 3) + " " +
- "Happiness: " + formatNumber(this.hap, 3) + " " +
- "Energy: " + formatNumber(this.ene, 3) + " " +
- "Intelligence: " + formatNumber(effInt, 3) + " " +
- "Charisma: " + formatNumber(effCha, 3) + " " +
- "Experience: " + formatNumber(this.exp, 3) + " " +
- "Creativity: " + formatNumber(effCre, 3) + " " +
- "Efficiency: " + formatNumber(effEff, 3) + " " +
- "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s ",
- }));
-
- //Selector for employee position
- var selector = createElement("select", {});
- for (var key in EmployeePositions) {
- if (EmployeePositions.hasOwnProperty(key)) {
- selector.add(createElement("option", {
- text: EmployeePositions[key],
- value: EmployeePositions[key],
- }));
- }
- }
-
- selector.addEventListener("change", () => {
- this.pos = selector.options[selector.selectedIndex].value;
- });
-
- //Set initial value of selector
- for (var i = 0; i < selector.length; ++i) {
- if (selector.options[i].value === this.pos) {
- selector.selectedIndex = i;
- break;
- }
- }
- panel.appendChild(selector);
-}
-
-Employee.prototype.toJSON = function() {
- return Generic_toJSON("Employee", this);
-}
-
-Employee.fromJSON = function(value) {
- return Generic_fromJSON(Employee, value.data);
-}
-
-Reviver.constructors.Employee = Employee;
-
var OfficeSpaceTiers = {
Basic: "Basic",
Enhanced: "Enhanced",
@@ -1621,298 +1462,6 @@ var OfficeSpaceTiers = {
Extravagant: "Extravagant",
}
-function OfficeSpace(params={}) {
- this.loc = params.loc ? params.loc : "";
- this.cost = params.cost ? params.cost : 1;
- this.size = params.size ? params.size : 1;
- this.comf = params.comfort ? params.comfort : 1;
- this.beau = params.beauty ? params.beauty : 1;
- this.tier = OfficeSpaceTiers.Basic;
-
- // Min/max energy of employees
- this.minEne = 0;
- this.maxEne = 100;
-
- // Min/max Happiness of office
- this.minHap = 0;
- this.maxHap = 100;
-
- // Maximum Morale of office
- this.maxMor = 100;
-
- this.employees = [];
- this.employeeProd = {
- [EmployeePositions.Operations]: 0,
- [EmployeePositions.Engineer]: 0,
- [EmployeePositions.Business]: 0,
- [EmployeePositions.Management]: 0,
- [EmployeePositions.RandD]: 0,
- total: 0,
- };
-}
-
-OfficeSpace.prototype.atCapacity = function() {
- return (this.employees.length) >= this.size;
-}
-
-OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) {
- var industry = parentRefs.industry;
-
- // HRBuddy AutoRecruitment and training
- if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) {
- const emp = this.hireRandomEmployee();
- if (industry.hasResearch("HRBuddy-Training")) {
- emp.pos = EmployeePositions.Training;
- }
- }
-
- // Process Office properties
- this.maxEne = 100;
- this.maxHap = 100;
- this.maxMor = 100;
- if (industry.hasResearch("Go-Juice")) {
- this.maxEne += 10;
- }
- if (industry.hasResearch("JoyWire")) {
- this.maxHap += 10;
- }
- if (industry.hasResearch("Sti.mu")) {
- this.maxMor += 10;
- }
-
- // Calculate changes in Morale/Happiness/Energy for Employees
- var perfMult=1; //Multiplier for employee morale/happiness/energy based on company performance
- if (industry.funds < 0 && industry.lastCycleRevenue < 0) {
- perfMult = Math.pow(0.99, marketCycles);
- } else if (industry.funds > 0 && industry.lastCycleRevenue > 0) {
- perfMult = Math.pow(1.01, marketCycles);
- }
-
- const hasAutobrew = industry.hasResearch("AutoBrew");
- const hasAutoparty = industry.hasResearch("AutoPartyManager");
-
- var salaryPaid = 0;
- for (let i = 0; i < this.employees.length; ++i) {
- const emp = this.employees[i];
- if (hasAutoparty) {
- emp.mor = this.maxMor;
- emp.hap = this.maxHap;
- } else {
- emp.mor *= perfMult;
- emp.hap *= perfMult;
- emp.mor = Math.min(emp.mor, this.maxMor);
- emp.hap = Math.min(emp.hap, this.maxHap);
- }
-
- if (hasAutobrew) {
- emp.ene = this.maxEne;
- } else {
- emp.ene *= perfMult;
- emp.ene = Math.min(emp.ene, this.maxEne);
- }
-
- const salary = emp.process(marketCycles, this);
- salaryPaid += salary;
- }
-
- this.calculateEmployeeProductivity(parentRefs);
- return salaryPaid;
-}
-
-OfficeSpace.prototype.calculateEmployeeProductivity = function(parentRefs) {
- var company = parentRefs.corporation, industry = parentRefs.industry;
-
- //Reset
- for (const name in this.employeeProd) {
- this.employeeProd[name] = 0;
- }
-
- var total = 0;
- for (let i = 0; i < this.employees.length; ++i) {
- const employee = this.employees[i];
- const prod = employee.calculateProductivity(company, industry);
- this.employeeProd[employee.pos] += prod;
- total += prod;
- }
- this.employeeProd["total"] = total;
-}
-
-//Takes care of UI as well
-OfficeSpace.prototype.findEmployees = function(parentRefs) {
- if (this.atCapacity()) { return; }
- if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;}
-
- //Generate three random employees (meh, decent, amazing)
- var mult1 = getRandomInt(25, 50)/100,
- mult2 = getRandomInt(51, 75)/100,
- mult3 = getRandomInt(76, 100)/100;
- var int = getRandomInt(50, 100),
- cha = getRandomInt(50, 100),
- exp = getRandomInt(50, 100),
- cre = getRandomInt(50, 100),
- eff = getRandomInt(50, 100),
- sal = EmployeeSalaryMultiplier * (int + cha + exp + cre + eff);
-
- var emp1 = new Employee({
- intelligence: int * mult1,
- charisma: cha * mult1,
- experience: exp * mult1,
- creativity: cre * mult1,
- efficiency: eff * mult1,
- salary: sal * mult1,
- });
-
- var emp2 = new Employee({
- intelligence: int * mult2,
- charisma: cha * mult2,
- experience: exp * mult2,
- creativity: cre * mult2,
- efficiency: eff * mult2,
- salary: sal * mult2,
- });
-
- var emp3 = new Employee({
- intelligence: int * mult3,
- charisma: cha * mult3,
- experience: exp * mult3,
- creativity: cre * mult3,
- efficiency: eff * mult3,
- salary: sal * mult3,
- });
-
- var text = createElement("h1", {
- innerHTML: "Select one of the following candidates for hire:",
- });
-
- var createEmpDiv = function(employee, office) {
- var div = createElement("div", {
- class:"cmpy-mgmt-find-employee-option",
- innerHTML: "Intelligence: " + formatNumber(employee.int, 1) + " " +
- "Charisma: " + formatNumber(employee.cha, 1) + " " +
- "Experience: " + formatNumber(employee.exp, 1) + " " +
- "Creativity: " + formatNumber(employee.cre, 1) + " " +
- "Efficiency: " + formatNumber(employee.eff, 1) + " " +
- "Salary: " + numeralWrapper.format(employee.sal, '$0.000a') + " \ s ",
- clickListener:() => {
- office.hireEmployee(employee, parentRefs);
- removeElementById("cmpy-mgmt-hire-employee-popup");
- return false;
- },
- });
- return div;
- };
-
- var cancelBtn = createElement("a", {
- class:"a-link-button",
- innerText:"Cancel",
- float:"right",
- clickListener:() => {
- removeElementById("cmpy-mgmt-hire-employee-popup");
- return false;
- },
- });
-
- var elems = [text,
- createEmpDiv(emp1, this),
- createEmpDiv(emp2, this),
- createEmpDiv(emp3, this),
- cancelBtn];
-
- createPopup("cmpy-mgmt-hire-employee-popup", elems);
-}
-
-OfficeSpace.prototype.hireEmployee = function(employee, parentRefs) {
- var company = parentRefs.corporation;
- var yesBtn = yesNoTxtInpBoxGetYesButton(),
- noBtn = yesNoTxtInpBoxGetNoButton();
- yesBtn.innerHTML = "Hire";
- noBtn.innerHTML = "Cancel";
- yesBtn.addEventListener("click", () => {
- var name = yesNoTxtInpBoxGetInput();
- for (var i = 0; i < this.employees.length; ++i) {
- if (this.employees[i].name === name) {
- dialogBoxCreate("You already have an employee with this nickname! Please give every employee a unique nickname.");
- return false;
- }
- }
- employee.name = name;
- this.employees.push(employee);
- company.rerender();
- return yesNoTxtInpBoxClose();
- });
- noBtn.addEventListener("click", () => {
- return yesNoTxtInpBoxClose();
- });
- yesNoTxtInpBoxCreate("Give your employee a nickname!");
-}
-
-OfficeSpace.prototype.hireRandomEmployee = function() {
- if (this.atCapacity()) { return; }
- if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;}
-
- //Generate three random employees (meh, decent, amazing)
- var mult = getRandomInt(76, 100)/100;
- var int = getRandomInt(50, 100),
- cha = getRandomInt(50, 100),
- exp = getRandomInt(50, 100),
- cre = getRandomInt(50, 100),
- eff = getRandomInt(50, 100),
- sal = EmployeeSalaryMultiplier * (int + cha + exp + cre + eff);
-
- var emp = new Employee({
- intelligence: int * mult,
- charisma: cha * mult,
- experience: exp * mult,
- creativity: cre * mult,
- efficiency: eff * mult,
- salary: sal * mult,
- });
-
- var name = generateRandomString(7);
-
- for (let i = 0; i < this.employees.length; ++i) {
- if (this.employees[i].name === name) {
- return this.hireRandomEmployee();
- }
- }
- emp.name = name;
- this.employees.push(emp);
-
- return emp;
-}
-
-//Finds the first unassigned employee and assigns its to the specified job
-OfficeSpace.prototype.assignEmployeeToJob = function(job) {
- for (var i = 0; i < this.employees.length; ++i) {
- if (this.employees[i].pos === EmployeePositions.Unassigned) {
- this.employees[i].pos = job;
- return true;
- }
- }
- return false;
-}
-
-//Finds the first employee with the given job and unassigns it
-OfficeSpace.prototype.unassignEmployeeFromJob = function(job) {
- for (var i = 0; i < this.employees.length; ++i) {
- if (this.employees[i].pos === job) {
- this.employees[i].pos = EmployeePositions.Unassigned;
- return true;
- }
- }
- return false;
-}
-
-OfficeSpace.prototype.toJSON = function() {
- return Generic_toJSON("OfficeSpace", this);
-}
-
-OfficeSpace.fromJSON = function(value) {
- return Generic_fromJSON(OfficeSpace, value.data);
-}
-
-Reviver.constructors.OfficeSpace = OfficeSpace;
-
function Corporation(params={}) {
this.name = params.name ? params.name : "The Corporation";
@@ -2398,4 +1947,4 @@ Corporation.fromJSON = function(value) {
Reviver.constructors.Corporation = Corporation;
-export {Corporation, Industry, OfficeSpace, Warehouse};
+export {Corporation, Industry, Warehouse};
diff --git a/src/Corporation/Employee.ts b/src/Corporation/Employee.ts
new file mode 100644
index 000000000..c42d2cf58
--- /dev/null
+++ b/src/Corporation/Employee.ts
@@ -0,0 +1,194 @@
+import { CorporationConstants } from "./data/Constants";
+import { getRandomInt } from "../../utils/helpers/getRandomInt";
+import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
+import { createElement } from "../../utils/uiHelpers/createElement";
+import { EmployeePositions } from "./EmployeePositions";
+import { numeralWrapper } from "../ui/numeralFormat";
+import { formatNumber } from "../../utils/StringHelperFunctions";
+
+interface IParams {
+ name?: string;
+ morale?: number;
+ happiness?: number;
+ energy?: number;
+ intelligence?: number;
+ charisma?: number;
+ experience?: number;
+ creativity?: number;
+ efficiency?: number;
+ salary?: number;
+ loc?: string;
+}
+
+export class Employee {
+ name: string;
+ mor: number;
+ hap: number;
+ ene: number;
+ int: number;
+ cha: number;
+ exp: number;
+ cre: number;
+ eff: number;
+ sal: number;
+ pro = 0;
+ cyclesUntilRaise = CorporationConstants.CyclesPerEmployeeRaise;
+ loc: string;
+ pos: string;
+
+ constructor(params: IParams = {}) {
+ this.name = params.name ? params.name : "Bobby";
+
+ //Morale, happiness, and energy are 0-100
+ this.mor = params.morale ? params.morale : getRandomInt(50, 100);
+ this.hap = params.happiness ? params.happiness : getRandomInt(50, 100);
+ this.ene = params.energy ? params.energy : getRandomInt(50, 100);
+
+ this.int = params.intelligence ? params.intelligence : getRandomInt(10, 50);
+ this.cha = params.charisma ? params.charisma : getRandomInt(10, 50);
+ this.exp = params.experience ? params.experience : getRandomInt(10, 50);
+ this.cre = params.creativity ? params.creativity : getRandomInt(10, 50);
+ this.eff = params.efficiency ? params.efficiency : getRandomInt(10, 50);
+ this.sal = params.salary ? params.salary : getRandomInt(0.1, 5);
+
+ this.loc = params.loc ? params.loc : "";
+ this.pos = EmployeePositions.Unassigned;
+ }
+
+ //Returns the amount the employee needs to be paid
+ process(marketCycles = 1, office: any) {
+ const gain = 0.003 * marketCycles,
+ det = gain * Math.random();
+ this.exp += gain;
+
+ // Employee salaries slowly go up over time
+ this.cyclesUntilRaise -= marketCycles;
+ if (this.cyclesUntilRaise <= 0) {
+ this.sal += CorporationConstants.EmployeeRaiseAmount;
+ this.cyclesUntilRaise += CorporationConstants.CyclesPerEmployeeRaise;
+ }
+
+ //Training
+ const trainingEff = gain * Math.random();
+ if (this.pos === EmployeePositions.Training) {
+ //To increase creativity and intelligence special upgrades are needed
+ this.cha += trainingEff;
+ this.exp += trainingEff;
+ this.eff += trainingEff;
+ }
+
+ this.ene -= det;
+ this.hap -= det;
+
+ if (this.ene < office.minEne) {this.ene = office.minEne;}
+ if (this.hap < office.minHap) {this.hap = office.minHap;}
+ const salary = this.sal * marketCycles * CorporationConstants.SecsPerMarketCycle;
+ return salary;
+ }
+
+ calculateProductivity(corporation: any, industry: any): number {
+ const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),
+ effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),
+ effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(),
+ effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier();
+ const prodBase = this.mor * this.hap * this.ene * 1e-6;
+ let prodMult = 0;
+ switch(this.pos) {
+ //Calculate productivity based on position. This is multipled by prodBase
+ //to get final value
+ case EmployeePositions.Operations:
+ prodMult = (0.6 * effInt) + (0.1 * effCha) + (this.exp) +
+ (0.5 * effCre) + (effEff);
+ break;
+ case EmployeePositions.Engineer:
+ prodMult = (effInt) + (0.1 * effCha) + (1.5 * this.exp) +
+ (effEff);
+ break;
+ case EmployeePositions.Business:
+ prodMult = (0.4 * effInt) + (effCha) + (0.5 * this.exp);
+ break;
+ case EmployeePositions.Management:
+ prodMult = (2 * effCha) + (this.exp) + (0.2 * effCre) +
+ (0.7 * effEff);
+ break;
+ case EmployeePositions.RandD:
+ prodMult = (1.5 * effInt) + (0.8 * this.exp) + (effCre) +
+ (0.5 * effEff);
+ break;
+ case EmployeePositions.Unassigned:
+ case EmployeePositions.Training:
+ prodMult = 0;
+ break;
+ default:
+ console.error(`Invalid employee position: ${this.pos}`);
+ break;
+ }
+ return prodBase * prodMult;
+ }
+
+ //Process benefits from having an office party thrown
+ throwParty(money: number) {
+ const mult = 1 + (money / 10e6);
+ this.mor *= mult;
+ this.mor = Math.min(100, this.mor);
+ this.hap *= mult;
+ this.hap = Math.min(100, this.hap);
+ return mult;
+ }
+
+ //'panel' is the DOM element on which to create the UI
+ createUI(panel: any, corporation: any, industry: any) {
+ const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),
+ effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),
+ effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(),
+ effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier();
+ panel.style.color = "white";
+ panel.appendChild(createElement("p", {
+ id:"cmpy-mgmt-employee-" + this.name + "-panel-text",
+ innerHTML:"Morale: " + formatNumber(this.mor, 3) + " " +
+ "Happiness: " + formatNumber(this.hap, 3) + " " +
+ "Energy: " + formatNumber(this.ene, 3) + " " +
+ "Intelligence: " + formatNumber(effInt, 3) + " " +
+ "Charisma: " + formatNumber(effCha, 3) + " " +
+ "Experience: " + formatNumber(this.exp, 3) + " " +
+ "Creativity: " + formatNumber(effCre, 3) + " " +
+ "Efficiency: " + formatNumber(effEff, 3) + " " +
+ "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s ",
+ }));
+
+ //Selector for employee position
+ const selector = createElement("select", {}) as HTMLSelectElement;
+ for (const key in EmployeePositions) {
+ if (EmployeePositions.hasOwnProperty(key)) {
+ selector.add(createElement("option", {
+ text: EmployeePositions[key],
+ value: EmployeePositions[key],
+ }) as HTMLOptionElement);
+ }
+ }
+
+ selector.addEventListener("change", () => {
+ this.pos = selector.options[selector.selectedIndex].value;
+ });
+
+ //Set initial value of selector
+ for (let i = 0; i < selector.length; ++i) {
+ if (selector.options[i].value === this.pos) {
+ selector.selectedIndex = i;
+ break;
+ }
+ }
+ panel.appendChild(selector);
+ }
+
+ toJSON(): any {
+ return Generic_toJSON("Employee", this);
+ }
+
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+ static fromJSON(value: any): Employee {
+ return Generic_fromJSON(Employee, value.data);
+ }
+}
+
+Reviver.constructors.Employee = Employee;
\ No newline at end of file
diff --git a/src/Corporation/IDivision.ts b/src/Corporation/IDivision.ts
index 1ed13a8e8..53b63e1cf 100644
--- a/src/Corporation/IDivision.ts
+++ b/src/Corporation/IDivision.ts
@@ -3,5 +3,5 @@ import { IMap } from "../types";
export interface IDivision {
name: string;
- offices: IMap;
+ offices: IMap;
}
diff --git a/src/Corporation/OfficeSpace.ts b/src/Corporation/OfficeSpace.ts
new file mode 100644
index 000000000..095c5081e
--- /dev/null
+++ b/src/Corporation/OfficeSpace.ts
@@ -0,0 +1,323 @@
+import { EmployeePositions } from "./EmployeePositions";
+import { CorporationConstants } from "./data/Constants";
+import { getRandomInt } from "../../utils/helpers/getRandomInt";
+import { formatNumber, generateRandomString } from "../../utils/StringHelperFunctions";
+import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
+import { yesNoBoxCreate,
+ yesNoTxtInpBoxCreate,
+ yesNoBoxGetYesButton,
+ yesNoBoxGetNoButton,
+ yesNoTxtInpBoxGetYesButton,
+ yesNoTxtInpBoxGetNoButton,
+ yesNoTxtInpBoxGetInput,
+ yesNoBoxClose,
+ yesNoTxtInpBoxClose } from "../../utils/YesNoBox";
+import { dialogBoxCreate } from "../../utils/DialogBox";
+import { createPopup } from "../../utils/uiHelpers/createPopup";
+import { removeElementById } from "../../utils/uiHelpers/removeElementById";
+import { createElement } from "../../utils/uiHelpers/createElement";
+import { numeralWrapper } from "../ui/numeralFormat";
+import { Employee } from "./Employee";
+
+interface IParams {
+ loc?: string;
+ cost?: number;
+ size?: number;
+ comfort?: number;
+ beauty?: number;
+}
+
+export class OfficeSpace {
+ loc: string;
+ cost: number;
+ size: number;
+ comf: number;
+ beau: number;
+ tier = "Basic";
+ minEne = 0;
+ maxEne = 100;
+ minHap = 0;
+ maxHap = 100;
+ maxMor = 100;
+ employees: any[] = [];
+ employeeProd: {[key: string]: number} = {
+ [EmployeePositions.Operations]: 0,
+ [EmployeePositions.Engineer]: 0,
+ [EmployeePositions.Business]: 0,
+ [EmployeePositions.Management]: 0,
+ [EmployeePositions.RandD]: 0,
+ total: 0,
+ };
+
+ constructor(params: IParams = {}) {
+ this.loc = params.loc ? params.loc : "";
+ this.cost = params.cost ? params.cost : 1;
+ this.size = params.size ? params.size : 1;
+ this.comf = params.comfort ? params.comfort : 1;
+ this.beau = params.beauty ? params.beauty : 1;
+ }
+
+
+ atCapacity(): boolean {
+ return (this.employees.length) >= this.size;
+ }
+
+ process(marketCycles = 1, parentRefs: any): number {
+ const industry = parentRefs.industry;
+
+ // HRBuddy AutoRecruitment and training
+ if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) {
+ const emp = this.hireRandomEmployee();
+ if (industry.hasResearch("HRBuddy-Training") && emp !== undefined) {
+ emp.pos = EmployeePositions.Training;
+ }
+ }
+
+ // Process Office properties
+ this.maxEne = 100;
+ this.maxHap = 100;
+ this.maxMor = 100;
+ if (industry.hasResearch("Go-Juice")) {
+ this.maxEne += 10;
+ }
+ if (industry.hasResearch("JoyWire")) {
+ this.maxHap += 10;
+ }
+ if (industry.hasResearch("Sti.mu")) {
+ this.maxMor += 10;
+ }
+
+ // Calculate changes in Morale/Happiness/Energy for Employees
+ let perfMult=1; //Multiplier for employee morale/happiness/energy based on company performance
+ if (industry.funds < 0 && industry.lastCycleRevenue < 0) {
+ perfMult = Math.pow(0.99, marketCycles);
+ } else if (industry.funds > 0 && industry.lastCycleRevenue > 0) {
+ perfMult = Math.pow(1.01, marketCycles);
+ }
+
+ const hasAutobrew = industry.hasResearch("AutoBrew");
+ const hasAutoparty = industry.hasResearch("AutoPartyManager");
+
+ let salaryPaid = 0;
+ for (let i = 0; i < this.employees.length; ++i) {
+ const emp = this.employees[i];
+ if (hasAutoparty) {
+ emp.mor = this.maxMor;
+ emp.hap = this.maxHap;
+ } else {
+ emp.mor *= perfMult;
+ emp.hap *= perfMult;
+ emp.mor = Math.min(emp.mor, this.maxMor);
+ emp.hap = Math.min(emp.hap, this.maxHap);
+ }
+
+ if (hasAutobrew) {
+ emp.ene = this.maxEne;
+ } else {
+ emp.ene *= perfMult;
+ emp.ene = Math.min(emp.ene, this.maxEne);
+ }
+
+ const salary = emp.process(marketCycles, this);
+ salaryPaid += salary;
+ }
+
+ this.calculateEmployeeProductivity(parentRefs);
+ return salaryPaid;
+ }
+
+ calculateEmployeeProductivity(parentRefs: any) {
+ const company = parentRefs.corporation, industry = parentRefs.industry;
+
+ //Reset
+ for (const name in this.employeeProd) {
+ this.employeeProd[name] = 0;
+ }
+
+ let total = 0;
+ for (let i = 0; i < this.employees.length; ++i) {
+ const employee = this.employees[i];
+ const prod = employee.calculateProductivity(company, industry);
+ this.employeeProd[employee.pos] += prod;
+ total += prod;
+ }
+ this.employeeProd.total = total;
+ }
+
+ //Takes care of UI as well
+ findEmployees(parentRefs: any) {
+ if (this.atCapacity()) { return; }
+ if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;}
+
+ //Generate three random employees (meh, decent, amazing)
+ const mult1 = getRandomInt(25, 50)/100,
+ mult2 = getRandomInt(51, 75)/100,
+ mult3 = getRandomInt(76, 100)/100;
+ const int = getRandomInt(50, 100),
+ cha = getRandomInt(50, 100),
+ exp = getRandomInt(50, 100),
+ cre = getRandomInt(50, 100),
+ eff = getRandomInt(50, 100),
+ sal = CorporationConstants.EmployeeSalaryMultiplier * (int + cha + exp + cre + eff);
+
+ const emp1 = new Employee({
+ intelligence: int * mult1,
+ charisma: cha * mult1,
+ experience: exp * mult1,
+ creativity: cre * mult1,
+ efficiency: eff * mult1,
+ salary: sal * mult1,
+ });
+
+ const emp2 = new Employee({
+ intelligence: int * mult2,
+ charisma: cha * mult2,
+ experience: exp * mult2,
+ creativity: cre * mult2,
+ efficiency: eff * mult2,
+ salary: sal * mult2,
+ });
+
+ const emp3 = new Employee({
+ intelligence: int * mult3,
+ charisma: cha * mult3,
+ experience: exp * mult3,
+ creativity: cre * mult3,
+ efficiency: eff * mult3,
+ salary: sal * mult3,
+ });
+
+ const text = createElement("h1", {
+ innerHTML: "Select one of the following candidates for hire:",
+ });
+
+ const createEmpDiv = function(employee: any, office: any) {
+ const div = createElement("div", {
+ class:"cmpy-mgmt-find-employee-option",
+ innerHTML: "Intelligence: " + formatNumber(employee.int, 1) + " " +
+ "Charisma: " + formatNumber(employee.cha, 1) + " " +
+ "Experience: " + formatNumber(employee.exp, 1) + " " +
+ "Creativity: " + formatNumber(employee.cre, 1) + " " +
+ "Efficiency: " + formatNumber(employee.eff, 1) + " " +
+ "Salary: " + numeralWrapper.format(employee.sal, '$0.000a') + " \ s ",
+ clickListener: () => {
+ office.hireEmployee(employee, parentRefs);
+ removeElementById("cmpy-mgmt-hire-employee-popup");
+ return false;
+ },
+ });
+ return div;
+ };
+
+ const cancelBtn = createElement("a", {
+ class:"a-link-button",
+ innerText:"Cancel",
+ float:"right",
+ clickListener:() => {
+ removeElementById("cmpy-mgmt-hire-employee-popup");
+ return false;
+ },
+ });
+
+ const elems = [text,
+ createEmpDiv(emp1, this),
+ createEmpDiv(emp2, this),
+ createEmpDiv(emp3, this),
+ cancelBtn];
+
+ createPopup("cmpy-mgmt-hire-employee-popup", elems);
+ }
+
+ hireEmployee(employee: Employee, parentRefs: any) {
+ const company = parentRefs.corporation;
+ const yesBtn = yesNoTxtInpBoxGetYesButton(),
+ noBtn = yesNoTxtInpBoxGetNoButton();
+ yesBtn.innerHTML = "Hire";
+ noBtn.innerHTML = "Cancel";
+ yesBtn.addEventListener("click", () => {
+ const name = yesNoTxtInpBoxGetInput();
+ for (let i = 0; i < this.employees.length; ++i) {
+ if (this.employees[i].name === name) {
+ dialogBoxCreate("You already have an employee with this nickname! Please give every employee a unique nickname.");
+ return false;
+ }
+ }
+ employee.name = name;
+ this.employees.push(employee);
+ company.rerender();
+ return yesNoTxtInpBoxClose();
+ });
+ noBtn.addEventListener("click", () => {
+ return yesNoTxtInpBoxClose();
+ });
+ yesNoTxtInpBoxCreate("Give your employee a nickname!");
+ }
+
+ hireRandomEmployee(): Employee | undefined {
+ if (this.atCapacity()) return;
+ if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) return;
+
+ //Generate three random employees (meh, decent, amazing)
+ const mult = getRandomInt(76, 100)/100;
+ const int = getRandomInt(50, 100),
+ cha = getRandomInt(50, 100),
+ exp = getRandomInt(50, 100),
+ cre = getRandomInt(50, 100),
+ eff = getRandomInt(50, 100),
+ sal = CorporationConstants.EmployeeSalaryMultiplier * (int + cha + exp + cre + eff);
+
+ const emp = new Employee({
+ intelligence: int * mult,
+ charisma: cha * mult,
+ experience: exp * mult,
+ creativity: cre * mult,
+ efficiency: eff * mult,
+ salary: sal * mult,
+ });
+
+ const name = generateRandomString(7);
+
+ for (let i = 0; i < this.employees.length; ++i) {
+ if (this.employees[i].name === name) {
+ return this.hireRandomEmployee();
+ }
+ }
+ emp.name = name;
+ this.employees.push(emp);
+
+ return emp;
+ }
+
+ //Finds the first unassigned employee and assigns its to the specified job
+ assignEmployeeToJob(job: any): boolean {
+ for (let i = 0; i < this.employees.length; ++i) {
+ if (this.employees[i].pos === EmployeePositions.Unassigned) {
+ this.employees[i].pos = job;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ //Finds the first employee with the given job and unassigns it
+ unassignEmployeeFromJob(job: any): boolean {
+ for (let i = 0; i < this.employees.length; ++i) {
+ if (this.employees[i].pos === job) {
+ this.employees[i].pos = EmployeePositions.Unassigned;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ toJSON(): any {
+ return Generic_toJSON("OfficeSpace", this);
+ }
+
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+ static fromJSON(value: any): OfficeSpace {
+ return Generic_fromJSON(OfficeSpace, value.data);
+ }
+}
+
+Reviver.constructors.OfficeSpace = OfficeSpace;
\ No newline at end of file
diff --git a/src/Corporation/data/Constants.ts b/src/Corporation/data/Constants.ts
new file mode 100644
index 000000000..3dce0b1a9
--- /dev/null
+++ b/src/Corporation/data/Constants.ts
@@ -0,0 +1,60 @@
+const CyclesPerMarketCycle = 50;
+const AllCorporationStates = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"];
+export const CorporationConstants: {
+ INITIALSHARES: number;
+ SHARESPERPRICEUPDATE: number;
+ IssueNewSharesCooldown: number;
+ SellSharesCooldown: number;
+ CyclesPerMarketCycle: number;
+ CyclesPerIndustryStateCycle: number;
+ SecsPerMarketCycle: number;
+ Cities: string[];
+ WarehouseInitialCost: number;
+ WarehouseInitialSize: number;
+ WarehouseUpgradeBaseCost: number;
+ OfficeInitialCost: number;
+ OfficeInitialSize: number;
+ OfficeUpgradeBaseCost: number;
+ BribeThreshold: number;
+ BribeToRepRatio: number;
+ ProductProductionCostRatio: number;
+ DividendMaxPercentage: number;
+ EmployeeSalaryMultiplier: number;
+ CyclesPerEmployeeRaise: number;
+ EmployeeRaiseAmount: number;
+ BaseMaxProducts: number;
+ AllCorporationStates: string[];
+} = {
+ INITIALSHARES: 1e9, //Total number of shares you have at your company
+ SHARESPERPRICEUPDATE: 1e6, //When selling large number of shares, price is dynamically updated for every batch of this amount
+ IssueNewSharesCooldown: 216e3, // 12 Hour in terms of game cycles
+ SellSharesCooldown: 18e3, // 1 Hour in terms of game cycles
+
+ CyclesPerMarketCycle: CyclesPerMarketCycle,
+ CyclesPerIndustryStateCycle: CyclesPerMarketCycle / AllCorporationStates.length,
+ SecsPerMarketCycle: CyclesPerMarketCycle / 5,
+
+ Cities: ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"],
+
+ WarehouseInitialCost: 5e9, //Initial purchase cost of warehouse
+ WarehouseInitialSize: 100,
+ WarehouseUpgradeBaseCost: 1e9,
+
+ OfficeInitialCost: 4e9,
+ OfficeInitialSize: 3,
+ OfficeUpgradeBaseCost: 1e9,
+
+ BribeThreshold: 100e12, //Money needed to be able to bribe for faction rep
+ BribeToRepRatio: 1e9, //Bribe Value divided by this = rep gain
+
+ ProductProductionCostRatio: 5, //Ratio of material cost of a product to its production cost
+
+ DividendMaxPercentage: 50,
+
+ EmployeeSalaryMultiplier: 3, // Employee stats multiplied by this to determine initial salary
+ CyclesPerEmployeeRaise: 400, // All employees get a raise every X market cycles
+ EmployeeRaiseAmount: 50, // Employee salary increases by this (additive)
+
+ BaseMaxProducts: 3, // Initial value for maximum number of products allowed
+ AllCorporationStates: AllCorporationStates,
+};
\ No newline at end of file
diff --git a/src/Corporation/ui/CityTabs.tsx b/src/Corporation/ui/CityTabs.tsx
index e0f222a49..c774adc1b 100644
--- a/src/Corporation/ui/CityTabs.tsx
+++ b/src/Corporation/ui/CityTabs.tsx
@@ -2,35 +2,42 @@
// These allow player to navigate between different cities for each industry
import React from "react";
import { CityTab } from "./CityTab";
+import { ExpandNewCityPopup } from "./ExpandNewCityPopup";
+import { createPopup } from "../../ui/React/createPopup";
+import { IDivision } from "../IDivision";
interface IProps {
eventHandler: any;
routing: any;
onClicks: {[key: string]: () => void};
city: string; // currentCity
- cityStateSetter: any;
+ cityStateSetter: (city: string) => void;
+ corp: any;
}
export function CityTabs(props: IProps): React.ReactElement {
const division = props.routing.currentDivision;
- const tabs = [];
-
- // Tabs for each city
- for (const cityName in props.onClicks) {
- tabs.push(
-
- );
+ function openExpandNewCityModal() {
+ const popupId = "cmpy-mgmt-expand-city-popup";
+ createPopup(popupId, ExpandNewCityPopup, {
+ popupId: popupId,
+ corp: props.corp,
+ division: division as IDivision,
+ cityStateSetter: props.cityStateSetter,
+ });
}
- tabs.push(
+ return <>
+ {
+ Object.keys(props.onClicks).map((cityName: string) => ,
+ )
+ }
props.eventHandler.createNewCityPopup(division, props.cityStateSetter)}
+ current={false}
+ key={"Expand into new City"}
+ name={"Expand into new City"}
+ onClick={openExpandNewCityModal}
/>
- );
-
- return <>{tabs}>;
+ >;
}
\ No newline at end of file
diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js
index 687457f55..8b158971c 100644
--- a/src/Corporation/ui/CorporationUIEventHandler.js
+++ b/src/Corporation/ui/CorporationUIEventHandler.js
@@ -2,7 +2,6 @@
import { CorporationRouting } from "./Routing";
import { Corporation,
Industry,
- OfficeSpace,
Warehouse,
DividendMaxPercentage,
IssueNewSharesCooldown,
@@ -12,6 +11,7 @@ import { Corporation,
WarehouseInitialCost,
WarehouseInitialSize,
BribeToRepRatio } from "../Corporation";
+import { OfficeSpace } from "../OfficeSpace";
import { Industries,
IndustryStartingCosts,
diff --git a/src/Corporation/ui/ExpandNewCityPopup.tsx b/src/Corporation/ui/ExpandNewCityPopup.tsx
new file mode 100644
index 000000000..c9d91087f
--- /dev/null
+++ b/src/Corporation/ui/ExpandNewCityPopup.tsx
@@ -0,0 +1,51 @@
+import React, { useRef } from "react";
+import { IDivision } from "../IDivision";
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { CorporationConstants } from "../data/Constants";
+import { removePopup } from "../../ui/React/createPopup";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { OfficeSpace } from "../OfficeSpace";
+
+interface IProps {
+ popupId: string;
+ corp: any;
+ division: IDivision;
+ cityStateSetter: (city: string) => void;
+}
+
+export function ExpandNewCityPopup(props: IProps): React.ReactElement {
+ const dropdown = useRef(null);
+
+ function expand(): void {
+ if(dropdown.current === null) return;
+ const city = dropdown.current.value;
+ if (props.corp.funds.lt(CorporationConstants.OfficeInitialCost)) {
+ dialogBoxCreate("You don't have enough company funds to open a new office!");
+ } else {
+ props.corp.funds = props.corp.funds.minus(CorporationConstants.OfficeInitialCost);
+ dialogBoxCreate(`Opened a new office in ${city}!`);
+ props.division.offices[city] = new OfficeSpace({
+ loc: city,
+ size: CorporationConstants.OfficeInitialSize,
+ });
+ }
+
+ props.cityStateSetter(city);
+ removePopup(props.popupId);
+ }
+ return (<>
+
+ Would you like to expand into a new city by opening an office?
+ This would cost {numeralWrapper.format(CorporationConstants.OfficeInitialCost, '$0.000a')}
+
+
+ {
+ Object.keys(props.division.offices)
+ .filter((cityName: string) => props.division.offices[cityName] === 0)
+ .map((cityName: string) => {cityName} ,
+ )
+ }
+
+ Confirm
+ >);
+}
\ No newline at end of file
diff --git a/src/Corporation/ui/HeaderTabs.tsx b/src/Corporation/ui/HeaderTabs.tsx
index 1888ba7c6..8ee5b6ff8 100644
--- a/src/Corporation/ui/HeaderTabs.tsx
+++ b/src/Corporation/ui/HeaderTabs.tsx
@@ -26,8 +26,7 @@ export function HeaderTabs(props: IProps): React.ReactElement {
text={props.corp.name}
/>
{
- props.corp.divisions.map((division: IDivision) =>
- {
diff --git a/src/Corporation/ui/Industry.jsx b/src/Corporation/ui/Industry.jsx
index c6787e3ed..6d290d467 100644
--- a/src/Corporation/ui/Industry.jsx
+++ b/src/Corporation/ui/Industry.jsx
@@ -25,7 +25,12 @@ export class Industry extends BaseReactComponent {
-
+
)
diff --git a/src/Corporation/ui/IndustryOffice.jsx b/src/Corporation/ui/IndustryOffice.jsx
index d43cadf22..8f1dfa540 100644
--- a/src/Corporation/ui/IndustryOffice.jsx
+++ b/src/Corporation/ui/IndustryOffice.jsx
@@ -3,7 +3,7 @@
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
-import { OfficeSpace } from "../Corporation";
+import { OfficeSpace } from "../OfficeSpace";
import { EmployeePositions } from "../EmployeePositions";
import { numeralWrapper } from "../../ui/numeralFormat";
diff --git a/src/Corporation/ui/IndustryOverview.jsx b/src/Corporation/ui/IndustryOverview.jsx
index 09cdc6a0a..a3e1e2b5b 100644
--- a/src/Corporation/ui/IndustryOverview.jsx
+++ b/src/Corporation/ui/IndustryOverview.jsx
@@ -3,7 +3,7 @@
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
-import { OfficeSpace } from "../Corporation";
+import { OfficeSpace } from "../OfficeSpace";
import { Industries } from "../IndustryData";
import { IndustryUpgrades } from "../IndustryUpgrades";
import { numeralWrapper } from "../../ui/numeralFormat";
diff --git a/src/Corporation/ui/IndustryWarehouse.jsx b/src/Corporation/ui/IndustryWarehouse.tsx
similarity index 89%
rename from src/Corporation/ui/IndustryWarehouse.jsx
rename to src/Corporation/ui/IndustryWarehouse.tsx
index d0f220a56..acb217148 100644
--- a/src/Corporation/ui/IndustryWarehouse.jsx
+++ b/src/Corporation/ui/IndustryWarehouse.tsx
@@ -1,12 +1,9 @@
// React Component for displaying an Industry's warehouse information
// (right-side panel in the Industry UI)
import React from "react";
-import { BaseReactComponent } from "./BaseReactComponent";
-import { OfficeSpace,
- WarehouseInitialCost,
- WarehouseUpgradeBaseCost,
- ProductProductionCostRatio } from "../Corporation";
+import { CorporationConstants } from "../data/Constants";
+import { OfficeSpace } from "../OfficeSpace";
import { Material } from "../Material";
import { Product } from "../Product";
import { Warehouse } from "../Warehouse";
@@ -15,8 +12,16 @@ import { numeralWrapper } from "../../ui/numeralFormat";
import { isString } from "../../../utils/helpers/isString";
+interface IProductProps {
+ corp: any;
+ division: any;
+ city: string;
+ product: any;
+ eventHandler: any;
+}
+
// Creates the UI for a single Product type
-function ProductComponent(props) {
+function ProductComponent(props: IProductProps) {
const corp = props.corp;
const division = props.division;
const city = props.city;
@@ -146,7 +151,7 @@ function ProductComponent(props) {
- Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)}
+ Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / CorporationConstants.ProductProductionCostRatio)}
An estimate of the material cost it takes to create this Product.
@@ -181,8 +186,17 @@ function ProductComponent(props) {
)
}
+interface IMaterialProps {
+ corp: any;
+ division: any;
+ warehouse: any;
+ city: string;
+ mat: any;
+ eventHandler: any;
+}
+
// Creates the UI for a single Material type
-function MaterialComponent(props) {
+function MaterialComponent(props: any) {
const corp = props.corp;
const division = props.division;
const warehouse = props.warehouse;
@@ -230,7 +244,7 @@ function MaterialComponent(props) {
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit);
} else if (mat.sCost) {
if (isString(mat.sCost)) {
- var sCost = mat.sCost.replace(/MP/g, mat.bCost);
+ const sCost = mat.sCost.replace(/MP/g, mat.bCost);
sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost));
} else {
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.sCost);
@@ -320,10 +334,17 @@ function MaterialComponent(props) {
)
}
-export class IndustryWarehouse extends BaseReactComponent {
+interface IProps {
+ corp: any;
+ routing: any;
+ currentCity: string;
+ eventHandler: any;
+}
+
+export function IndustryWarehouse(props: IProps) {
// Returns a boolean indicating whether the given material is relevant for the
// current industry.
- isRelevantMaterial(matName, division) {
+ function isRelevantMaterial(matName: string, division: any): boolean {
// Materials that affect Production multiplier
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"];
@@ -334,10 +355,10 @@ export class IndustryWarehouse extends BaseReactComponent {
return false;
}
- renderWarehouseUI() {
- const corp = this.corp();
- const division = this.routing().currentDivision; // Validated in render()
- const warehouse = division.warehouses[this.props.currentCity]; // Validated in render()
+ function renderWarehouseUI() {
+ const corp = props.corp;
+ const division = props.routing.currentDivision; // Validated in render()
+ const warehouse = division.warehouses[props.currentCity]; // Validated in render()
// General Storage information at the top
const sizeUsageStyle = {
@@ -346,7 +367,7 @@ export class IndustryWarehouse extends BaseReactComponent {
}
// Upgrade Warehouse size button
- const sizeUpgradeCost = WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
+ const sizeUpgradeCost = CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
const canAffordUpgrade = (corp.funds.gt(sizeUpgradeCost));
const upgradeWarehouseClass = canAffordUpgrade ? "std-button" : "a-link-button-inactive";
const upgradeWarehouseOnClick = () => {
@@ -416,7 +437,7 @@ export class IndustryWarehouse extends BaseReactComponent {
// Smart Supply Checkbox
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
- const smartSupplyOnChange = (e) => {
+ const smartSupplyOnChange = (e: React.ChangeEvent) => {
warehouse.smartSupplyEnabled = e.target.checked;
corp.rerender();
}
@@ -426,12 +447,12 @@ export class IndustryWarehouse extends BaseReactComponent {
for (const matName in warehouse.materials) {
if (warehouse.materials[matName] instanceof Material) {
// Only create UI for materials that are relevant for the industry
- if (this.isRelevantMaterial(matName, division)) {
+ if (isRelevantMaterial(matName, division)) {
mats.push( );
@@ -445,13 +466,13 @@ export class IndustryWarehouse extends BaseReactComponent {
for (const productName in division.products) {
if (division.products[productName] instanceof Product) {
products.push( );
+ />);
}
}
}
@@ -500,25 +521,21 @@ export class IndustryWarehouse extends BaseReactComponent {
)
}
- render() {
- const division = this.routing().currentDivision;
- if (division == null) {
- throw new Error(`Routing does not hold reference to the current Industry`);
- }
- const warehouse = division.warehouses[this.props.currentCity];
+ const division = props.routing.currentDivision;
+ if (division == null) {
+ throw new Error(`Routing does not hold reference to the current Industry`);
+ }
+ const warehouse = division.warehouses[props.currentCity];
- const newWarehouseOnClick = this.eventHandler().purchaseWarehouse.bind(this.eventHandler(), division, this.props.currentCity);
-
- if (warehouse instanceof Warehouse) {
- return this.renderWarehouseUI();
- } else {
- return (
-
-
- Purchase Warehouse ({numeralWrapper.formatMoney(WarehouseInitialCost)})
-
-
- )
- }
+ if (warehouse instanceof Warehouse) {
+ return renderWarehouseUI();
+ } else {
+ return (
+
+ props.eventHandler.purchaseWarehouse(division, props.currentCity)}>
+ Purchase Warehouse ({numeralWrapper.formatMoney(CorporationConstants.WarehouseInitialCost)})
+
+
+ )
}
}
diff --git a/src/Corporation/ui/LevelableUpgrade.jsx b/src/Corporation/ui/LevelableUpgrade.jsx
deleted file mode 100644
index 937652d8f..000000000
--- a/src/Corporation/ui/LevelableUpgrade.jsx
+++ /dev/null
@@ -1,36 +0,0 @@
-// React components for the levelable upgrade buttons on the overview panel
-import React from "react";
-import { BaseReactComponent } from "./BaseReactComponent";
-
-import { numeralWrapper } from "../../ui/numeralFormat";
-import { dialogBoxCreate } from "../../../utils/DialogBox";
-
-export class LevelableUpgrade extends BaseReactComponent {
- render() {
- const data = this.props.upgradeData;
- const level = this.props.upgradeLevel;
-
- const baseCost = data[1];
- const priceMult = data[2];
- const cost = baseCost * Math.pow(priceMult, level);
-
- const text = `${data[4]} - ${numeralWrapper.formatMoney(cost)}`
- const tooltip = data[5];
- const onClick = () => {
- const corp = this.corp();
- if (corp.funds.lt(cost)) {
- dialogBoxCreate("Insufficient funds");
- } else {
- corp.upgrade(data);
- corp.rerender();
- }
- }
-
- return (
-
- {text}
- {tooltip}
-
- )
- }
-}
diff --git a/src/Corporation/ui/LevelableUpgrade.tsx b/src/Corporation/ui/LevelableUpgrade.tsx
new file mode 100644
index 000000000..8b8c69d8a
--- /dev/null
+++ b/src/Corporation/ui/LevelableUpgrade.tsx
@@ -0,0 +1,39 @@
+// React components for the levelable upgrade buttons on the overview panel
+import React from "react";
+
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+
+interface IProps {
+ upgradeData: number[];
+ upgradeLevel: number;
+ corp: any;
+}
+
+export function LevelableUpgrade(props: IProps): React.ReactElement {
+ const data = props.upgradeData;
+ const level = props.upgradeLevel;
+
+ const baseCost = data[1];
+ const priceMult = data[2];
+ const cost = baseCost * Math.pow(priceMult, level);
+
+ const text = `${data[4]} - ${numeralWrapper.formatMoney(cost)}`
+ const tooltip = data[5];
+ function onClick() {
+ const corp = props.corp;
+ if (corp.funds.lt(cost)) {
+ dialogBoxCreate("Insufficient funds");
+ } else {
+ corp.upgrade(data);
+ corp.rerender();
+ }
+ }
+
+ return (
+
+ {text}
+ {tooltip}
+
+ )
+}
diff --git a/src/Corporation/ui/MainPanel.jsx b/src/Corporation/ui/MainPanel.jsx
index 3a632639c..5ed4cad31 100644
--- a/src/Corporation/ui/MainPanel.jsx
+++ b/src/Corporation/ui/MainPanel.jsx
@@ -8,7 +8,7 @@ import { CityTabs } from "./CityTabs";
import { Industry } from "./Industry";
import { Overview } from "./Overview";
-import { OfficeSpace } from "../Corporation";
+import { OfficeSpace } from "../OfficeSpace";
import { CityName } from "../../Locations/data/CityNames";
@@ -80,6 +80,7 @@ export class MainPanel extends BaseReactComponent {
const cityTabs = (
+
{props.text}
{
hasTooltip &&
@@ -33,59 +37,58 @@ export class Overview extends BaseReactComponent {
}
)
-
}
// Returns a string with general information about Corporation
- getOverviewText() {
+ function getOverviewText() {
// Formatted text for profit
- var profit = this.corp().revenue.minus(this.corp().expenses).toNumber(),
+ const profit = props.corp.revenue.minus(props.corp.expenses).toNumber(),
profitStr = profit >= 0 ? numeralWrapper.formatMoney(profit) : "-" + numeralWrapper.format(-1 * profit, "$0.000a");
// Formatted text for dividend information, if applicable
let dividendStr = "";
- if (this.corp().dividendPercentage > 0 && profit > 0) {
- const totalDividends = (this.corp().dividendPercentage / 100) * profit;
+ if (props.corp.dividendPercentage > 0 && profit > 0) {
+ const totalDividends = (props.corp.dividendPercentage / 100) * profit;
const retainedEarnings = profit - totalDividends;
- const dividendsPerShare = totalDividends / this.corp().totalShares;
- const playerEarnings = this.corp().numShares * dividendsPerShare;
+ const dividendsPerShare = totalDividends / props.corp.totalShares;
+ const playerEarnings = props.corp.numShares * dividendsPerShare;
dividendStr = `Retained Profits (after dividends): ${numeralWrapper.format(retainedEarnings, "$0.000a")} / s ` +
- `Dividend Percentage: ${numeralWrapper.format(this.corp().dividendPercentage / 100, "0%")} ` +
+ `Dividend Percentage: ${numeralWrapper.format(props.corp.dividendPercentage / 100, "0%")} ` +
`Dividends per share: ${numeralWrapper.format(dividendsPerShare, "$0.000a")} / s ` +
`Your earnings as a shareholder (Pre-Tax): ${numeralWrapper.format(playerEarnings, "$0.000a")} / s ` +
- `Dividend Tax Rate: ${this.corp().dividendTaxPercentage}% ` +
- `Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (this.corp().dividendTaxPercentage / 100)), "$0.000a")} / s `;
+ `Dividend Tax Rate: ${props.corp.dividendTaxPercentage}% ` +
+ `Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (props.corp.dividendTaxPercentage / 100)), "$0.000a")} / s `;
}
- let txt = "Total Funds: " + numeralWrapper.format(this.corp().funds.toNumber(), '$0.000a') + " " +
- "Total Revenue: " + numeralWrapper.format(this.corp().revenue.toNumber(), "$0.000a") + " / s " +
- "Total Expenses: " + numeralWrapper.format(this.corp().expenses.toNumber(), "$0.000a") + " / s " +
+ let txt = "Total Funds: " + numeralWrapper.format(props.corp.funds.toNumber(), '$0.000a') + " " +
+ "Total Revenue: " + numeralWrapper.format(props.corp.revenue.toNumber(), "$0.000a") + " / s " +
+ "Total Expenses: " + numeralWrapper.format(props.corp.expenses.toNumber(), "$0.000a") + " / s " +
"Total Profits: " + profitStr + " / s " +
dividendStr +
- "Publicly Traded: " + (this.corp().public ? "Yes" : "No") + " " +
- "Owned Stock Shares: " + numeralWrapper.format(this.corp().numShares, '0.000a') + " " +
- "Stock Price: " + (this.corp().public ? numeralWrapper.formatMoney(this.corp().sharePrice) : "N/A") + " " +
- "Total Stock Shares: " + numeralWrapper.format(this.corp().totalShares, "0.000a") +
+ "Publicly Traded: " + (props.corp.public ? "Yes" : "No") + " " +
+ "Owned Stock Shares: " + numeralWrapper.format(props.corp.numShares, '0.000a') + " " +
+ "Stock Price: " + (props.corp.public ? numeralWrapper.formatMoney(props.corp.sharePrice) : "N/A") + " " +
+ "
Total Stock Shares: " + numeralWrapper.format(props.corp.totalShares, "0.000a") +
"" +
- `Outstanding Shares: ${numeralWrapper.format(this.corp().issuedShares, "0.000a")} ` +
- `Private Shares: ${numeralWrapper.format(this.corp().totalShares - this.corp().issuedShares - this.corp().numShares, "0.000a")}` +
+ `Outstanding Shares: ${numeralWrapper.format(props.corp.issuedShares, "0.000a")} ` +
+ `Private Shares: ${numeralWrapper.format(props.corp.totalShares - props.corp.issuedShares - props.corp.numShares, "0.000a")}` +
"
";
- const storedTime = this.corp().storedCycles * CONSTANTS.MilliPerCycle;
+ const storedTime = props.corp.storedCycles * CONSTANTS.MilliPerCycle;
if (storedTime > 15000) {
txt += `Bonus time: ${convertTimeMsToTimeElapsedString(storedTime)} `;
}
- let prodMult = this.corp().getProductionMultiplier(),
- storageMult = this.corp().getStorageMultiplier(),
- advMult = this.corp().getAdvertisingMultiplier(),
- empCreMult = this.corp().getEmployeeCreMultiplier(),
- empChaMult = this.corp().getEmployeeChaMultiplier(),
- empIntMult = this.corp().getEmployeeIntMultiplier(),
- empEffMult = this.corp().getEmployeeEffMultiplier(),
- salesMult = this.corp().getSalesMultiplier(),
- sciResMult = this.corp().getScientificResearchMultiplier();
+ const prodMult = props.corp.getProductionMultiplier(),
+ storageMult = props.corp.getStorageMultiplier(),
+ advMult = props.corp.getAdvertisingMultiplier(),
+ empCreMult = props.corp.getEmployeeCreMultiplier(),
+ empChaMult = props.corp.getEmployeeChaMultiplier(),
+ empIntMult = props.corp.getEmployeeIntMultiplier(),
+ empEffMult = props.corp.getEmployeeEffMultiplier(),
+ salesMult = props.corp.getSalesMultiplier(),
+ sciResMult = props.corp.getScientificResearchMultiplier();
if (prodMult > 1) {txt += "Production Multiplier: " + numeralWrapper.format(prodMult, "0.000") + " ";}
if (storageMult > 1) {txt += "Storage Multiplier: " + numeralWrapper.format(storageMult, "0.000") + " ";}
if (advMult > 1) {txt += "Advertising Multiplier: " + numeralWrapper.format(advMult, "0.000") + " ";}
@@ -101,11 +104,11 @@ export class Overview extends BaseReactComponent {
// Render the buttons that lie below the overview text.
// These are mainly for things such as managing finances/stock
- renderButtons() {
+ function renderButtons() {
// Create a "Getting Started Guide" button that lets player view the
// handbook and adds it to the players home computer
- const getStarterGuideOnClick = this.corp().getStarterGuide.bind(this.corp());
- const getStarterGuideBtn = this.createButton({
+ const getStarterGuideOnClick = props.corp.getStarterGuide.bind(props.corp);
+ const getStarterGuideBtn = createButton({
class: "a-link-button",
display: "inline-block",
onClick: getStarterGuideOnClick,
@@ -117,13 +120,12 @@ export class Overview extends BaseReactComponent {
// Create a "Bribe Factions" button if your Corporation is powerful enough.
// This occurs regardless of whether you're public or private
- const canBribe = (this.corp().determineValuation() >= BribeThreshold);
- const bribeFactionsOnClick = this.eventHandler().createBribeFactionsPopup.bind(this.eventHandler());
+ const canBribe = (props.corp.determineValuation() >= CorporationConstants.BribeThreshold);
const bribeFactionsClass = (canBribe ? "a-link-button" : "a-link-button-inactive");
- const bribeFactionsBtn = this.createButton({
+ const bribeFactionsBtn = createButton({
class: bribeFactionsClass,
display: "inline-block",
- onClick: bribeFactionsOnClick,
+ onClick: props.eventHandler.createBribeFactionsPopup,
text: "Bribe Factions",
tooltip: (canBribe
? "Use your Corporations power and influence to bribe Faction leaders in exchange for reputation"
@@ -136,34 +138,36 @@ export class Overview extends BaseReactComponent {
getStarterGuide: getStarterGuideBtn,
};
- if (this.corp().public) {
- return this.renderPublicButtons(generalBtns);
+ if (props.corp.public) {
+ return renderPublicButtons(generalBtns);
} else {
- return this.renderPrivateButtons(generalBtns);
+ return renderPrivateButtons(generalBtns);
}
}
// Render the buttons for when your Corporation is still private
- renderPrivateButtons(generalBtns) {
- const fundingAvailable = (this.corp().fundingRound < 4);
+ function renderPrivateButtons(generalBtns: any) {
+ const fundingAvailable = (props.corp.fundingRound < 4);
const findInvestorsClassName = fundingAvailable ? "std-button" : "a-link-button-inactive";
const findInvestorsTooltip = fundingAvailable ? "Search for private investors who will give you startup funding in exchangefor equity (stock shares) in your company" : null;
- const findInvestorsOnClick = this.corp().getInvestment.bind(this.corp());
- const goPublicOnClick = this.corp().goPublic.bind(this.corp());
+ const findInvestorsOnClick = props.corp.getInvestment.bind(props.corp);
+ const goPublicOnClick = props.corp.goPublic.bind(props.corp);
- const findInvestorsBtn = this.createButton({
+ const findInvestorsBtn = createButton({
class: findInvestorsClassName,
onClick: findInvestorsOnClick,
style: "inline-block",
text: "Find Investors",
tooltip: findInvestorsTooltip,
+ display: "inline-block",
});
- const goPublicBtn = this.createButton({
+ const goPublicBtn = createButton({
class: "std-button",
onClick: goPublicOnClick,
style: "inline-block",
+ display: "inline-block",
text: "Go Public",
tooltip: "Become a publicly traded and owned entity. Going public " +
"involves issuing shares for an IPO. Once you are a public " +
@@ -183,10 +187,9 @@ export class Overview extends BaseReactComponent {
}
// Render the buttons for when your Corporation has gone public
- renderPublicButtons(generalBtns) {
- const corp = this.corp();
+ function renderPublicButtons(generalBtns: any) {
+ const corp = props.corp;
- const sellSharesOnClick = this.eventHandler().createSellSharesPopup.bind(this.eventHandler());
const sellSharesOnCd = (corp.shareSaleCooldown > 0);
const sellSharesClass = sellSharesOnCd ? "a-link-button-inactive" : "std-button";
const sellSharesTooltip = sellSharesOnCd
@@ -194,45 +197,42 @@ export class Overview extends BaseReactComponent {
: "Sell your shares in the company. The money earned from selling your " +
"shares goes into your personal account, not the Corporation's. " +
"This is one of the only ways to profit from your business venture."
- const sellSharesBtn = this.createButton({
+ const sellSharesBtn = createButton({
class: sellSharesClass,
display: "inline-block",
- onClick: function(event) {
+ onClick: function(event: MouseEvent) {
if(!event.isTrusted) return;
- sellSharesOnClick(event);
+ props.eventHandler.createSellSharesPopup(event);
},
text: "Sell Shares",
tooltip: sellSharesTooltip,
});
- const buybackSharesOnClick = this.eventHandler().createBuybackSharesPopup.bind(this.eventHandler());
- const buybackSharesBtn = this.createButton({
+ const buybackSharesBtn = createButton({
class: "std-button",
display: "inline-block",
- onClick: buybackSharesOnClick,
+ onClick: props.eventHandler.createBuybackSharesPopup,
text: "Buyback shares",
tooltip: "Buy back shares you that previously issued or sold at market price.",
});
- const issueNewSharesOnClick = this.eventHandler().createIssueNewSharesPopup.bind(this.eventHandler());
const issueNewSharesOnCd = (corp.issueNewSharesCooldown > 0);
const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button";
const issueNewSharesTooltip = issueNewSharesOnCd
? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown)
: "Issue new equity shares to raise capital.";
- const issueNewSharesBtn = this.createButton({
+ const issueNewSharesBtn = createButton({
class: issueNewSharesClass,
display: "inline-block",
- onClick: issueNewSharesOnClick,
+ onClick: props.eventHandler.createIssueNewSharesPopup,
text: "Issue New Shares",
tooltip: issueNewSharesTooltip,
});
- const issueDividendsOnClick = this.eventHandler().createIssueDividendsPopup.bind(this.eventHandler());
- const issueDividendsBtn = this.createButton({
+ const issueDividendsBtn = createButton({
class: "std-button",
display: "inline-block",
- onClick: issueDividendsOnClick,
+ onClick: props.eventHandler.createIssueDividendsPopup,
text: "Issue Dividends",
tooltip: "Manage the dividends that are paid out to shareholders (including yourself)",
});
@@ -252,23 +252,27 @@ export class Overview extends BaseReactComponent {
}
// Render the UI for Corporation upgrades
- renderUpgrades() {
+ function renderUpgrades() {
// Don't show upgrades
- if (this.corp().divisions.length <= 0) { return; }
+ if (props.corp.divisions.length <= 0) { return; }
// Create an array of all Unlocks
- const unlockUpgrades = [];
+ const unlockUpgrades: React.ReactElement[] = [];
Object.values(CorporationUnlockUpgrades).forEach((unlockData) => {
- if (this.corp().unlockUpgrades[unlockData[0]] === 0) {
- unlockUpgrades.push(this.renderUnlockUpgrade(unlockData));
+ if (props.corp.unlockUpgrades[unlockData[0]] === 0) {
+ unlockUpgrades.push( );
}
});
// Create an array of properties of all unlocks
const levelableUpgradeProps = [];
- for (let i = 0; i < this.corp().upgrades.length; ++i) {
+ for (let i = 0; i < props.corp.upgrades.length; ++i) {
const upgradeData = CorporationUpgrades[i];
- const level = this.corp().upgrades[i];
+ const level = props.corp.upgrades[i];
levelableUpgradeProps.push({
upgradeData: upgradeData,
@@ -284,44 +288,25 @@ export class Overview extends BaseReactComponent {
Upgrades
{
- levelableUpgradeProps.map((data) => {
- return this.renderLevelableUpgrade(data);
- })
+ levelableUpgradeProps.map((data: any) => ,
+ )
}
)
}
- renderUnlockUpgrade(data) {
- return (
-
- )
- }
- renderLevelableUpgrade(data) {
- return (
-
- )
-
- }
-
- render() {
- return (
-
-
- {this.renderButtons()}
-
- {this.renderUpgrades()}
-
- )
- }
+ return (
+
+
+ {renderButtons()}
+
+ {renderUpgrades()}
+
+ )
}
diff --git a/src/Corporation/ui/UnlockUpgrade.jsx b/src/Corporation/ui/UnlockUpgrade.jsx
deleted file mode 100644
index e719c73c7..000000000
--- a/src/Corporation/ui/UnlockUpgrade.jsx
+++ /dev/null
@@ -1,30 +0,0 @@
-// React Components for the Unlock upgrade buttons on the overview page
-import React from "react";
-import { BaseReactComponent } from "./BaseReactComponent";
-
-import { numeralWrapper } from "../../ui/numeralFormat";
-import { dialogBoxCreate } from "../../../utils/DialogBox";
-
-export class UnlockUpgrade extends BaseReactComponent {
- render() {
- const data = this.props.upgradeData;
- const text = `${data[2]} - ${numeralWrapper.formatMoney(data[1])}`;
- const tooltip = data[3];
- const onClick = () => {
- const corp = this.corp();
- if (corp.funds.lt(data[1])) {
- dialogBoxCreate("Insufficient funds");
- } else {
- corp.unlock(data);
- corp.rerender();
- }
- }
-
- return (
-
- {text}
- {tooltip}
-
- )
- }
-}
diff --git a/src/Corporation/ui/UnlockUpgrade.tsx b/src/Corporation/ui/UnlockUpgrade.tsx
new file mode 100644
index 000000000..beb7965bf
--- /dev/null
+++ b/src/Corporation/ui/UnlockUpgrade.tsx
@@ -0,0 +1,32 @@
+// React Components for the Unlock upgrade buttons on the overview page
+import React from "react";
+
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+
+interface IProps {
+ upgradeData: number[];
+ corp: any;
+}
+
+export function UnlockUpgrade(props: IProps): React.ReactElement {
+ const data = props.upgradeData;
+ const text = `${data[2]} - ${numeralWrapper.formatMoney(data[1])}`;
+ const tooltip = data[3];
+ function onClick() {
+ const corp = props.corp;
+ if (corp.funds.lt(data[1])) {
+ dialogBoxCreate("Insufficient funds");
+ } else {
+ corp.unlock(data);
+ corp.rerender();
+ }
+ }
+
+ return (
+
+ {text}
+ {tooltip}
+
+ )
+}
diff --git a/src/InteractiveTutorial.d.ts b/src/InteractiveTutorial.d.ts
index fb971c533..d99d083d8 100644
--- a/src/InteractiveTutorial.d.ts
+++ b/src/InteractiveTutorial.d.ts
@@ -1,3 +1,3 @@
export declare function iTutorialNextStep(): void;
-export declare const ITutorial: {isRunning: boolean, currStep: number};
+export declare const ITutorial: {isRunning: boolean; currStep: number};
export declare const iTutorialSteps: {[key: string]: number};
\ No newline at end of file
From 94ad7ccf4bec15c7bd27a11339273374225ca30c Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 02:57:57 -0400
Subject: [PATCH 05/30] more conversion
---
...ustryOverview.jsx => IndustryOverview.tsx} | 84 ++++++++++---------
src/Corporation/ui/IndustryWarehouse.tsx | 2 +-
2 files changed, 45 insertions(+), 41 deletions(-)
rename src/Corporation/ui/{IndustryOverview.jsx => IndustryOverview.tsx} (85%)
diff --git a/src/Corporation/ui/IndustryOverview.jsx b/src/Corporation/ui/IndustryOverview.tsx
similarity index 85%
rename from src/Corporation/ui/IndustryOverview.jsx
rename to src/Corporation/ui/IndustryOverview.tsx
index a3e1e2b5b..2cc4d2b62 100644
--- a/src/Corporation/ui/IndustryOverview.jsx
+++ b/src/Corporation/ui/IndustryOverview.tsx
@@ -1,7 +1,6 @@
// React Component for displaying an Industry's overview information
// (top-left panel in the Industry UI)
import React from "react";
-import { BaseReactComponent } from "./BaseReactComponent";
import { OfficeSpace } from "../OfficeSpace";
import { Industries } from "../IndustryData";
@@ -10,11 +9,19 @@ import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
-export class IndustryOverview extends BaseReactComponent {
- renderMakeProductButton() {
- const division = this.routing().currentDivision; // Validated inside render()
+interface IProps {
+ routing: any;
+ eventHandler: any;
+ corp: any;
+ currentCity: string;
+}
- var createProductButtonText, createProductPopupText;
+export function IndustryOverview(props: IProps): React.ReactElement {
+ function renderMakeProductButton() {
+ const division = props.routing.currentDivision; // Validated inside render()
+
+ let createProductButtonText = "";
+ let createProductPopupText = "";
switch(division.type) {
case Industries.Food:
createProductButtonText = "Build Restaurant";
@@ -65,14 +72,13 @@ export class IndustryOverview extends BaseReactComponent {
const hasMaxProducts = division.hasMaximumNumberProducts();
const className = hasMaxProducts ? "a-link-button-inactive tooltip" : "std-button";
- const onClick = this.eventHandler().createMakeProductPopup.bind(this.eventHandler(), createProductPopupText, division);
const buttonStyle = {
margin: "6px",
display: "inline-block",
}
return (
-
+ props.eventHandler.createMakeProductPopup(createProductPopupText, division)} style={buttonStyle}>
{createProductButtonText}
{
hasMaxProducts &&
@@ -84,9 +90,9 @@ export class IndustryOverview extends BaseReactComponent {
)
}
- renderText() {
- const corp = this.corp();
- const division = this.routing().currentDivision; // Validated inside render()
+ function renderText() {
+ const corp = props.corp;
+ const division = props.routing.currentDivision; // Validated inside render()
const vechain = (corp.unlockUpgrades[4] === 1);
const profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber();
@@ -111,7 +117,7 @@ export class IndustryOverview extends BaseReactComponent {
// Wrapper for createProgressBarText()
// Converts the industry's "effectiveness factors"
// into a graphic (string) depicting how high that effectiveness is
- function convertEffectFacToGraphic(fac) {
+ function convertEffectFacToGraphic(fac: number) {
return createProgressBarText({
progress: fac,
totalTicks: 20,
@@ -185,12 +191,12 @@ export class IndustryOverview extends BaseReactComponent {
)
}
- renderUpgrades() {
- const corp = this.corp();
- const division = this.routing().currentDivision; // Validated inside render()
- const office = division.offices[this.props.currentCity];
+ function renderUpgrades() {
+ const corp = props.corp;
+ const division = props.routing.currentDivision; // Validated inside render()
+ const office = division.offices[props.currentCity];
if (!(office instanceof OfficeSpace)) {
- throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`);
+ throw new Error(`Current City (${props.currentCity}) for UI does not have an OfficeSpace object`);
}
const upgrades = [];
@@ -227,7 +233,7 @@ export class IndustryOverview extends BaseReactComponent {
}
}
- upgrades.push(this.renderUpgrade({
+ upgrades.push(renderUpgrade({
onClick: onClick,
text: `${upgrade[4]} - ${numeralWrapper.formatMoney(cost)}`,
tooltip: upgrade[5],
@@ -237,7 +243,7 @@ export class IndustryOverview extends BaseReactComponent {
return upgrades;
}
- renderUpgrade(props) {
+ function renderUpgrade(props: any) {
return (
{props.text}
@@ -249,27 +255,25 @@ export class IndustryOverview extends BaseReactComponent {
)
}
- render() {
- const division = this.routing().currentDivision;
- if (division == null) {
- throw new Error(`Routing does not hold reference to the current Industry`);
- }
-
- const makeProductButton = this.renderMakeProductButton();
-
- return (
-
- {this.renderText()}
-
-
- Purchases & Upgrades
- {this.renderUpgrades()}
-
- {
- division.makesProducts &&
- makeProductButton
- }
-
- )
+ const division = props.routing.currentDivision;
+ if (division == null) {
+ throw new Error(`Routing does not hold reference to the current Industry`);
}
+
+ const makeProductButton = renderMakeProductButton();
+
+ return (
+
+ {renderText()}
+
+
+ Purchases & Upgrades
+ {renderUpgrades()}
+
+ {
+ division.makesProducts &&
+ makeProductButton
+ }
+
+ )
}
diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx
index acb217148..1c6181e3a 100644
--- a/src/Corporation/ui/IndustryWarehouse.tsx
+++ b/src/Corporation/ui/IndustryWarehouse.tsx
@@ -196,7 +196,7 @@ interface IMaterialProps {
}
// Creates the UI for a single Material type
-function MaterialComponent(props: any) {
+function MaterialComponent(props: IMaterialProps) {
const corp = props.corp;
const division = props.division;
const warehouse = props.warehouse;
From 0de3deee3f17c5e157090a5b08cee3313c7419f8 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 03:31:47 -0400
Subject: [PATCH 06/30] Even more conversion
---
src/Corporation/ui/Industry.jsx | 7 +-
...{IndustryOffice.jsx => IndustryOffice.tsx} | 453 +++++++++---------
2 files changed, 240 insertions(+), 220 deletions(-)
rename src/Corporation/ui/{IndustryOffice.jsx => IndustryOffice.tsx} (60%)
diff --git a/src/Corporation/ui/Industry.jsx b/src/Corporation/ui/Industry.jsx
index 6d290d467..4b5988c1d 100644
--- a/src/Corporation/ui/Industry.jsx
+++ b/src/Corporation/ui/Industry.jsx
@@ -20,7 +20,12 @@ export class Industry extends BaseReactComponent {
return (
-
+
diff --git a/src/Corporation/ui/IndustryOffice.jsx b/src/Corporation/ui/IndustryOffice.tsx
similarity index 60%
rename from src/Corporation/ui/IndustryOffice.jsx
rename to src/Corporation/ui/IndustryOffice.tsx
index 8f1dfa540..93758e70f 100644
--- a/src/Corporation/ui/IndustryOffice.jsx
+++ b/src/Corporation/ui/IndustryOffice.tsx
@@ -1,120 +1,136 @@
// React Component for displaying an Industry's OfficeSpace information
// (bottom-left panel in the Industry UI)
-import React from "react";
-import { BaseReactComponent } from "./BaseReactComponent";
+import React, { useState } from "react";
import { OfficeSpace } from "../OfficeSpace";
+import { Employee } from "../Employee";
import { EmployeePositions } from "../EmployeePositions";
import { numeralWrapper } from "../../ui/numeralFormat";
import { getSelectText } from "../../../utils/uiHelpers/getSelectData";
-export class IndustryOffice extends BaseReactComponent {
- constructor(props) {
- super(props);
+interface IProps {
+ routing: any;
+ eventHandler: any;
+ corp: any;
+ currentCity: string;
+}
- this.state = {
- city: "",
- division: "",
- employeeManualAssignMode: false,
- employee: null, // Reference to employee being referenced if in Manual Mode
- numEmployees: 0,
- numOperations: 0,
- numEngineers: 0,
- numBusiness: 0,
- numManagement: 0,
- numResearch: 0,
- numUnassigned: 0,
- numTraining: 0,
- }
+export function IndustryOffice(props: IProps): React.ReactElement {
+ const [employeeManualAssignMode, setEmployeeManualAssignMode] = useState(false);
+ const [city, setCity] = useState("");
+ const [divisionName, setDivisionName] = useState("");
+ const [employee, setEmployee] = useState
(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);
- this.updateEmployeeCount(); // This function validates division and office refs
+ function resetEmployeeCount() {
+ setNumEmployees(0);
+ setNumOperations(0);
+ setNumEngineers(0);
+ setNumBusiness(0);
+ setNumManagement(0);
+ setNumResearch(0);
+ setNumUnassigned(0);
+ setNumTraining(0);
}
- resetEmployeeCount() {
- this.state.numEmployees = 0;
- this.state.numOperations = 0;
- this.state.numEngineers = 0;
- this.state.numBusiness = 0;
- this.state.numManagement = 0;
- this.state.numResearch = 0;
- this.state.numUnassigned = 0;
- this.state.numTraining = 0;
- }
-
- updateEmployeeCount() {
- const division = this.routing().currentDivision;
+ function updateEmployeeCount() {
+ const division = props.routing.currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
- const office = division.offices[this.props.currentCity];
+ const office = division.offices[props.currentCity];
if (!(office instanceof OfficeSpace)) {
- throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`);
+ throw new Error(`Current City (${props.currentCity}) for UI does not have an OfficeSpace object`);
}
// If we're in a new city, we have to reset the state
- if (division.name !== this.state.division || this.props.currentCity !== this.state.city) {
- this.resetEmployeeCount();
- this.state.division = division.name;
- this.state.city = this.props.currentCity;
+ if (division.name !== divisionName || props.currentCity !== city) {
+ resetEmployeeCount();
+ setDivisionName(division.name);
+ setCity(props.currentCity);
}
- // Calculate how many NEW emplyoees we need to account for
+ // Calculate how many NEW employees we need to account for
const currentNumEmployees = 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 = this.state.numEmployees; i < office.employees.length; ++i) {
+ for (let i = numEmployees; i < office.employees.length; ++i) {
switch (office.employees[i].pos) {
case EmployeePositions.Operations:
- ++this.state.numOperations;
+ newOperations++;
break;
case EmployeePositions.Engineer:
- ++this.state.numEngineers;
+ newEngineers++;
break;
case EmployeePositions.Business:
- ++this.state.numBusiness;
+ newBusiness++;
break;
case EmployeePositions.Management:
- ++this.state.numManagement;
+ newManagement++;
break;
case EmployeePositions.RandD:
- ++this.state.numResearch;
+ newResearch++;
break;
case EmployeePositions.Unassigned:
- ++this.state.numUnassigned;
+ newUnassigned++;
break;
case EmployeePositions.Training:
- ++this.state.numTraining;
+ newTraining++;
break;
default:
console.error("Unrecognized employee position: " + 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);
- this.state.numEmployees = currentNumEmployees;
+ if(currentNumEmployees !== numEmployees) setNumEmployees(currentNumEmployees);
}
- // Renders the "Employee Management" section of the Office UI
- renderEmployeeManagement() {
- this.updateEmployeeCount();
+ updateEmployeeCount();
- if (this.state.employeeManualAssignMode) {
- return this.renderManualEmployeeManagement();
+ // Renders the "Employee Management" section of the Office UI
+ function renderEmployeeManagement() {
+ updateEmployeeCount();
+
+ if (employeeManualAssignMode) {
+ return renderManualEmployeeManagement();
} else {
- return this.renderAutomaticEmployeeManagement();
+ return renderAutomaticEmployeeManagement();
}
}
- renderAutomaticEmployeeManagement() {
- const division = this.routing().currentDivision; // Validated in constructor
- const office = division.offices[this.props.currentCity]; // Validated in constructor
- const vechain = (this.corp().unlockUpgrades[4] === 1); // Has Vechain upgrade
+ function renderAutomaticEmployeeManagement() {
+ const division = props.routing.currentDivision; // Validated in constructor
+ const office = division.offices[props.currentCity]; // Validated in constructor
+ const vechain = (props.corp.unlockUpgrades[4] === 1); // Has Vechain upgrade
const switchModeOnClick = () => {
- this.state.employeeManualAssignMode = true;
- this.corp().rerender();
+ setEmployeeManualAssignMode(true);
+ props.corp.rerender();
}
// Calculate average morale, happiness, and energy. Also salary
@@ -135,87 +151,87 @@ export class IndustryOffice extends BaseReactComponent {
}
// Helper functions for (re-)assigning employees to different positions
- const assignEmployee = (to) => {
- if (this.state.numUnassigned <= 0) {
+ function assignEmployee(to: string) {
+ if (numUnassigned <= 0) {
console.warn("Cannot assign employee. No unassigned employees available");
return;
}
switch (to) {
case EmployeePositions.Operations:
- ++this.state.numOperations;
+ setNumOperations(n => n+1);
break;
case EmployeePositions.Engineer:
- ++this.state.numEngineers;
+ setNumEngineers(n => n+1);
break;
case EmployeePositions.Business:
- ++this.state.numBusiness;
+ setNumBusiness(n => n+1);
break;
case EmployeePositions.Management:
- ++this.state.numManagement;
+ setNumManagement(n => n+1);
break;
case EmployeePositions.RandD:
- ++this.state.numResearch;
+ setNumResearch(n => n+1);
break;
case EmployeePositions.Unassigned:
- ++this.state.numUnassigned;
+ setNumUnassigned(n => n+1);
break;
case EmployeePositions.Training:
- ++this.state.numTraining;
+ setNumTraining(n => n+1);
break;
default:
console.error("Unrecognized employee position: " + to);
break;
}
- --this.state.numUnassigned;
+ setNumUnassigned(n => n-1);
office.assignEmployeeToJob(to);
- office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
- this.corp().rerender();
+ office.calculateEmployeeProductivity({ corporation: props.corp, industry:division });
+ props.corp.rerender();
}
- const unassignEmployee = (from) => {
- function logWarning(pos) {
+ function unassignEmployee(from: string) {
+ function logWarning(pos: string) {
console.warn(`Cannot unassign from ${pos} because there is nobody assigned to that position`);
}
switch (from) {
case EmployeePositions.Operations:
- if (this.state.numOperations <= 0) { return logWarning(EmployeePositions.Operations); }
- --this.state.numOperations;
+ if (numOperations <= 0) { return logWarning(EmployeePositions.Operations); }
+ setNumOperations(n => n-1);
break;
case EmployeePositions.Engineer:
- if (this.state.numEngineers <= 0) { return logWarning(EmployeePositions.Operations); }
- --this.state.numEngineers;
+ if (numEngineers <= 0) { return logWarning(EmployeePositions.Operations); }
+ setNumEngineers(n => n-1);
break;
case EmployeePositions.Business:
- if (this.state.numBusiness <= 0) { return logWarning(EmployeePositions.Operations); }
- --this.state.numBusiness;
+ if (numBusiness <= 0) { return logWarning(EmployeePositions.Operations); }
+ setNumBusiness(n => n-1);
break;
case EmployeePositions.Management:
- if (this.state.numManagement <= 0) { return logWarning(EmployeePositions.Operations); }
- --this.state.numManagement;
+ if (numManagement <= 0) { return logWarning(EmployeePositions.Operations); }
+ setNumManagement(n => n-1);
break;
case EmployeePositions.RandD:
- if (this.state.numResearch <= 0) { return logWarning(EmployeePositions.Operations); }
- --this.state.numResearch;
+ 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 (this.state.numTraining <= 0) { return logWarning(EmployeePositions.Operations); }
- --this.state.numTraining;
+ if (numTraining <= 0) { return logWarning(EmployeePositions.Operations); }
+ setNumTraining(n => n-1);
break;
default:
console.error("Unrecognized employee position: " + from);
break;
}
- ++this.state.numUnassigned;
+ setNumUnassigned(n => n+1);
office.unassignEmployeeFromJob(from);
- office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
- this.corp().rerender();
+ office.calculateEmployeeProductivity({ corporation: props.corp, industry:division });
+ props.corp.rerender();
}
const positionHeaderStyle = {
@@ -223,67 +239,67 @@ export class IndustryOffice extends BaseReactComponent {
margin: "5px 0px 5px 0px",
width: "50%",
}
- const assignButtonClass = this.state.numUnassigned > 0 ? "std-button" : "a-link-button-inactive";
+ const assignButtonClass = numUnassigned > 0 ? "std-button" : "a-link-button-inactive";
const operationAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.Operations);
- this.corp().rerender();
+ props.corp.rerender();
}
const operationUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.Operations);
- this.corp().rerender();
+ props.corp.rerender();
}
- const operationUnassignButtonClass = this.state.numOperations > 0 ? "std-button" : "a-link-button-inactive";
+ const operationUnassignButtonClass = numOperations > 0 ? "std-button" : "a-link-button-inactive";
const engineerAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.Engineer);
- this.corp().rerender();
+ props.corp.rerender();
}
const engineerUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.Engineer);
- this.corp().rerender();
+ props.corp.rerender();
}
- const engineerUnassignButtonClass = this.state.numEngineers > 0 ? "std-button" : "a-link-button-inactive";
+ const engineerUnassignButtonClass = numEngineers > 0 ? "std-button" : "a-link-button-inactive";
const businessAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.Business);
- this.corp().rerender();
+ props.corp.rerender();
}
const businessUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.Business);
- this.corp().rerender();
+ props.corp.rerender();
}
- const businessUnassignButtonClass = this.state.numBusiness > 0 ? "std-button" : "a-link-button-inactive";
+ const businessUnassignButtonClass = numBusiness > 0 ? "std-button" : "a-link-button-inactive";
const managementAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.Management);
- this.corp().rerender();
+ props.corp.rerender();
}
const managementUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.Management);
- this.corp().rerender();
+ props.corp.rerender();
}
- const managementUnassignButtonClass = this.state.numManagement > 0 ? "std-button" : "a-link-button-inactive";
+ const managementUnassignButtonClass = numManagement > 0 ? "std-button" : "a-link-button-inactive";
const rndAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.RandD);
- this.corp().rerender();
+ props.corp.rerender();
}
const rndUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.RandD);
- this.corp().rerender();
+ props.corp.rerender();
}
- const rndUnassignButtonClass = this.state.numResearch > 0 ? "std-button" : "a-link-button-inactive";
+ const rndUnassignButtonClass = numResearch > 0 ? "std-button" : "a-link-button-inactive";
const trainingAssignButtonOnClick = () => {
assignEmployee(EmployeePositions.Training);
- this.corp().rerender();
+ props.corp.rerender();
}
const trainingUnassignButtonOnClick = () => {
unassignEmployee(EmployeePositions.Training);
- this.corp().rerender();
+ props.corp.rerender();
}
- const trainingUnassignButtonClass = this.state.numTraining > 0 ? "std-button" : "a-link-button-inactive";
+ const trainingUnassignButtonClass = numTraining > 0 ? "std-button" : "a-link-button-inactive";
return (
@@ -295,7 +311,7 @@ export class IndustryOffice extends BaseReactComponent {
-
Unassigned Employees: {this.state.numUnassigned}
+
Unassigned Employees: {numUnassigned}
Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}
@@ -344,7 +360,7 @@ export class IndustryOffice extends BaseReactComponent {
}
- {EmployeePositions.Operations} ({this.state.numOperations})
+ {EmployeePositions.Operations} ({numOperations})
Manages supply chain operations. Improves the amount of Materials and Products you produce.
@@ -354,7 +370,7 @@ export class IndustryOffice extends BaseReactComponent {
- {EmployeePositions.Engineer} ({this.state.numEngineers})
+ {EmployeePositions.Engineer} ({numEngineers})
Develops and maintains products and production systems. Increases the quality of
everything you produce. Also increases the amount you produce (not as much
@@ -366,7 +382,7 @@ export class IndustryOffice extends BaseReactComponent {
- {EmployeePositions.Business} ({this.state.numBusiness})
+ {EmployeePositions.Business} ({numBusiness})
Handles sales and finances. Improves the amount of Materials and Products you can sell.
@@ -376,7 +392,7 @@ export class IndustryOffice extends BaseReactComponent {
- {EmployeePositions.Management} ({this.state.numManagement})
+ {EmployeePositions.Management} ({numManagement})
Leads and oversees employees and office operations. Improves the effectiveness of
Engineer and Operations employees
@@ -387,7 +403,7 @@ export class IndustryOffice extends BaseReactComponent {
- {EmployeePositions.RandD} ({this.state.numResearch})
+ {EmployeePositions.RandD} ({numResearch})
Research new innovative ways to improve the company. Generates Scientific Research
@@ -397,7 +413,7 @@ export class IndustryOffice extends BaseReactComponent {
- {EmployeePositions.Training} ({this.state.numTraining})
+ {EmployeePositions.Training} ({numTraining})
Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations.
@@ -408,14 +424,14 @@ export class IndustryOffice extends BaseReactComponent {
)
}
- renderManualEmployeeManagement() {
- const corp = this.corp();
- const division = this.routing().currentDivision; // Validated in constructor
- const office = division.offices[this.props.currentCity]; // Validated in constructor
+ function renderManualEmployeeManagement() {
+ const corp = props.corp;
+ const division = props.routing.currentDivision; // Validated in constructor
+ const office = division.offices[props.currentCity]; // Validated in constructor
const switchModeOnClick = () => {
- this.state.employeeManualAssignMode = false;
- this.corp().rerender();
+ setEmployeeManualAssignMode(false);
+ props.corp.rerender();
}
const employeeInfoDivStyle = {
@@ -430,11 +446,11 @@ export class IndustryOffice extends BaseReactComponent {
employees.push({office.employees[i].name} )
}
- const employeeSelectorOnChange = (e) => {
+ const employeeSelectorOnChange = (e: React.ChangeEvent) => {
const name = getSelectText(e.target);
for (let i = 0; i < office.employees.length; ++i) {
if (name === office.employees[i].name) {
- this.state.employee = office.employees[i];
+ setEmployee(office.employees[i]);
break;
}
}
@@ -443,8 +459,8 @@ export class IndustryOffice extends BaseReactComponent {
}
// Employee Positions Selector
- const emp = this.state.employee;
- let employeePositionSelectorInitialValue = null;
+ const emp = employee;
+ let employeePositionSelectorInitialValue = "";
const employeePositions = [];
const positionNames = Object.values(EmployeePositions);
for (let i = 0; i < positionNames.length; ++i) {
@@ -454,10 +470,11 @@ export class IndustryOffice extends BaseReactComponent {
}
}
- const employeePositionSelectorOnChange = (e) => {
+ function employeePositionSelectorOnChange(e: React.ChangeEvent) {
+ if(employee === null) return;
const pos = getSelectText(e.target);
- this.state.employee.pos = pos;
- this.resetEmployeeCount();
+ employee.pos = pos;
+ resetEmployeeCount();
corp.rerender();
}
@@ -486,29 +503,29 @@ export class IndustryOffice extends BaseReactComponent {
{employees}
{
- this.state.employee != null &&
+ employee != null &&
- Morale: {numeralWrapper.format(this.state.employee.mor, nf)}
+ Morale: {numeralWrapper.format(employee.mor, nf)}
- Happiness: {numeralWrapper.format(this.state.employee.hap, nf)}
+ Happiness: {numeralWrapper.format(employee.hap, nf)}
- Energy: {numeralWrapper.format(this.state.employee.ene, nf)}
+ Energy: {numeralWrapper.format(employee.ene, nf)}
Intelligence: {numeralWrapper.format(effInt, nf)}
Charisma: {numeralWrapper.format(effCha, nf)}
- Experience: {numeralWrapper.format(this.state.employee.exp, nf)}
+ Experience: {numeralWrapper.format(employee.exp, nf)}
Creativity: {numeralWrapper.format(effCre, nf)}
Efficiency: {numeralWrapper.format(effEff, nf)}
- Salary: {numeralWrapper.formatMoney(this.state.employee.sal)}
+ Salary: {numeralWrapper.formatMoney(employee.sal)}
}
{
- this.state.employee != null &&
+ employee != null &&
{employeePositions}
@@ -518,89 +535,87 @@ export class IndustryOffice extends BaseReactComponent {
)
}
- render() {
- const corp = this.corp();
- const division = this.routing().currentDivision; // Validated in constructor
- const office = division.offices[this.props.currentCity]; // Validated in constructor
+ const corp = props.corp;
+ const division = props.routing.currentDivision; // Validated in constructor
+ const office = division.offices[props.currentCity]; // Validated in constructor
- const buttonStyle = {
- fontSize: "13px",
- }
-
- // Hire Employee button
- let hireEmployeeButtonClass = "tooltip";
- if (office.atCapacity()) {
- hireEmployeeButtonClass += " a-link-button-inactive";
- } else {
- hireEmployeeButtonClass += " std-button";
- if (office.employees.length === 0) {
- hireEmployeeButtonClass += " flashing-button";
- }
- }
-
- const hireEmployeeButtonOnClick = () => {
- office.findEmployees({ corporation: corp, industry: division });
- }
-
- // Autohire employee button
- let autohireEmployeeButtonClass = "tooltip";
- if (office.atCapacity()) {
- autohireEmployeeButtonClass += " a-link-button-inactive";
- } else {
- autohireEmployeeButtonClass += " std-button";
- }
- const autohireEmployeeButtonOnClick = () => {
- if (office.atCapacity()) { return; }
- office.hireRandomEmployee();
- this.corp().rerender();
- }
-
- // Upgrade Office Size Button
- const upgradeOfficeSizeOnClick = this.eventHandler().createUpgradeOfficeSizePopup.bind(this.eventHandler(), office);
-
- // Throw Office Party
- const throwOfficePartyOnClick = this.eventHandler().createThrowOfficePartyPopup.bind(this.eventHandler(), office);
-
- return (
-
-
Office Space
-
Size: {office.employees.length} / {office.size} employees
-
- Hire Employee
- {
- office.employees.length === 0 &&
-
- You'll need to hire some employees to get your operations started!
- It's recommended to have at least one employee in every position
-
- }
-
-
- Autohire Employee
-
- Automatically hires an employee and gives him/her a random name
-
-
-
-
- Upgrade size
-
- Upgrade the office's size so that it can hold more employees!
-
-
- {
- !division.hasResearch("AutoPartyManager") &&
-
- Throw Party
-
- "Throw an office party to increase your employee's morale and happiness"
-
-
- }
-
-
- {this.renderEmployeeManagement()}
-
- )
+ const buttonStyle = {
+ fontSize: "13px",
}
+
+ // Hire Employee button
+ let hireEmployeeButtonClass = "tooltip";
+ if (office.atCapacity()) {
+ hireEmployeeButtonClass += " a-link-button-inactive";
+ } else {
+ hireEmployeeButtonClass += " std-button";
+ if (office.employees.length === 0) {
+ hireEmployeeButtonClass += " flashing-button";
+ }
+ }
+
+ const hireEmployeeButtonOnClick = () => {
+ office.findEmployees({ corporation: corp, industry: division });
+ }
+
+ // Autohire employee button
+ let autohireEmployeeButtonClass = "tooltip";
+ if (office.atCapacity()) {
+ autohireEmployeeButtonClass += " a-link-button-inactive";
+ } else {
+ autohireEmployeeButtonClass += " std-button";
+ }
+ const autohireEmployeeButtonOnClick = () => {
+ if (office.atCapacity()) { return; }
+ office.hireRandomEmployee();
+ props.corp.rerender();
+ }
+
+ // Upgrade Office Size Button
+ const upgradeOfficeSizeOnClick = props.eventHandler.createUpgradeOfficeSizePopup.bind(props.eventHandler, office);
+
+ // Throw Office Party
+ const throwOfficePartyOnClick = props.eventHandler.createThrowOfficePartyPopup.bind(props.eventHandler, office);
+
+ return (
+
+
Office Space
+
Size: {office.employees.length} / {office.size} employees
+
+ Hire Employee
+ {
+ office.employees.length === 0 &&
+
+ You'll need to hire some employees to get your operations started!
+ It's recommended to have at least one employee in every position
+
+ }
+
+
+ Autohire Employee
+
+ Automatically hires an employee and gives him/her a random name
+
+
+
+
+ Upgrade size
+
+ Upgrade the office's size so that it can hold more employees!
+
+
+ {
+ !division.hasResearch("AutoPartyManager") &&
+
+ Throw Party
+
+ "Throw an office party to increase your employee's morale and happiness"
+
+
+ }
+
+
+ {renderEmployeeManagement()}
+
+ )
}
From 361ef31fe773f95eb78ec207dfd517b6e448a3a1 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 03:49:15 -0400
Subject: [PATCH 07/30] more conversion
---
src/Corporation/ui/BaseReactComponent.js | 20 -----
src/Corporation/ui/Industry.jsx | 44 ----------
src/Corporation/ui/Industry.tsx | 40 +++++++++
src/Corporation/ui/MainPanel.jsx | 106 -----------------------
src/Corporation/ui/MainPanel.tsx | 93 ++++++++++++++++++++
src/Corporation/ui/Root.jsx | 17 ----
src/Corporation/ui/Root.tsx | 20 +++++
7 files changed, 153 insertions(+), 187 deletions(-)
delete mode 100644 src/Corporation/ui/BaseReactComponent.js
delete mode 100644 src/Corporation/ui/Industry.jsx
create mode 100644 src/Corporation/ui/Industry.tsx
delete mode 100644 src/Corporation/ui/MainPanel.jsx
create mode 100644 src/Corporation/ui/MainPanel.tsx
delete mode 100644 src/Corporation/ui/Root.jsx
create mode 100644 src/Corporation/ui/Root.tsx
diff --git a/src/Corporation/ui/BaseReactComponent.js b/src/Corporation/ui/BaseReactComponent.js
deleted file mode 100644
index b3da9a6fc..000000000
--- a/src/Corporation/ui/BaseReactComponent.js
+++ /dev/null
@@ -1,20 +0,0 @@
-// Base class for React Components for Corporation UI
-// Contains a few helper functions that let derived classes easily
-// access Corporation properties
-import React from "react";
-
-const Component = React.Component;
-
-export class BaseReactComponent extends Component {
- corp() {
- return this.props.corp;
- }
-
- eventHandler() {
- return this.props.eventHandler;
- }
-
- routing() {
- return this.props.routing;
- }
-}
diff --git a/src/Corporation/ui/Industry.jsx b/src/Corporation/ui/Industry.jsx
deleted file mode 100644
index 4b5988c1d..000000000
--- a/src/Corporation/ui/Industry.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-// React Component for managing the Corporation's Industry UI
-// This Industry component does NOT include the city tabs at the top
-import React from "react";
-import { BaseReactComponent } from "./BaseReactComponent";
-
-import { IndustryOffice } from "./IndustryOffice";
-import { IndustryOverview } from "./IndustryOverview";
-import { IndustryWarehouse } from "./IndustryWarehouse";
-
-export class Industry extends BaseReactComponent {
- constructor(props) {
- if (props.currentCity == null) {
- throw new Error(`Industry component constructed without 'city' prop`);
- }
-
- super(props);
- }
-
- render() {
- return (
-
- )
-
- }
-}
diff --git a/src/Corporation/ui/Industry.tsx b/src/Corporation/ui/Industry.tsx
new file mode 100644
index 000000000..ef2dac622
--- /dev/null
+++ b/src/Corporation/ui/Industry.tsx
@@ -0,0 +1,40 @@
+// React Component for managing the Corporation's Industry UI
+// This Industry component does NOT include the city tabs at the top
+import React from "react";
+
+import { IndustryOffice } from "./IndustryOffice";
+import { IndustryOverview } from "./IndustryOverview";
+import { IndustryWarehouse } from "./IndustryWarehouse";
+
+interface IProps {
+ routing: any;
+ eventHandler: any;
+ corp: any;
+ currentCity: string;
+}
+
+export function Industry(props: IProps): React.ReactElement {
+ return (
+
+ )
+}
diff --git a/src/Corporation/ui/MainPanel.jsx b/src/Corporation/ui/MainPanel.jsx
deleted file mode 100644
index 5ed4cad31..000000000
--- a/src/Corporation/ui/MainPanel.jsx
+++ /dev/null
@@ -1,106 +0,0 @@
-// React Component for the element that contains the actual info/data
-// for the Corporation UI. This panel lies below the header tabs and will
-// be filled with whatever is needed based on the routing/navigation
-import React from "react";
-import { BaseReactComponent } from "./BaseReactComponent";
-
-import { CityTabs } from "./CityTabs";
-import { Industry } from "./Industry";
-import { Overview } from "./Overview";
-
-import { OfficeSpace } from "../OfficeSpace";
-
-import { CityName } from "../../Locations/data/CityNames";
-
-export class MainPanel extends BaseReactComponent {
- constructor(props) {
- super(props);
-
- this.state = {
- division: "",
- city: CityName.Sector12,
- }
- }
-
- // We can pass this setter to child components
- changeCityState(newCity) {
- if (Object.values(CityName).includes(newCity)) {
- this.state.city = newCity;
- } else {
- console.error(`Tried to change MainPanel's city state to an invalid city: ${newCity}`);
- }
- }
-
- // Determines what UI content to render based on routing
- renderContent() {
- if (this.routing().isOnOverviewPage()) {
- // Corporation overview Content
- return this.renderOverviewPage();
- } else {
- // Division content
-
- // First, check if we're at a new division. If so, we need to reset the city to Sector-12
- // Otherwise, just switch the 'city' state
- const currentDivision = this.routing().current();
- if (currentDivision !== this.state.division) {
- this.state.division = currentDivision;
- this.state.city = CityName.Sector12;
- }
-
- return this.renderDivisionPage();
- }
- }
-
- renderOverviewPage() {
- return (
-
-
-
- )
- }
-
- renderDivisionPage() {
- // Note: Division is the same thing as Industry...I wasn't consistent with naming
- const division = this.routing().currentDivision;
- if (division == null) {
- throw new Error(`Routing does not hold reference to the current Industry`);
- }
-
- // City tabs
- const onClicks = {};
- for (const cityName in division.offices) {
- if (division.offices[cityName] instanceof OfficeSpace) {
- onClicks[cityName] = () => {
- this.state.city = cityName;
- this.corp().rerender();
- }
- }
- }
-
- const cityTabs = (
-
- )
-
- // Rest of Industry UI
- const industry = (
-
- )
-
- return (
-
- {cityTabs}
- {industry}
-
- )
- }
-
- render() {
- return this.renderContent();
- }
-}
diff --git a/src/Corporation/ui/MainPanel.tsx b/src/Corporation/ui/MainPanel.tsx
new file mode 100644
index 000000000..c46acf65f
--- /dev/null
+++ b/src/Corporation/ui/MainPanel.tsx
@@ -0,0 +1,93 @@
+// React Component for the element that contains the actual info/data
+// for the Corporation UI. This panel lies below the header tabs and will
+// be filled with whatever is needed based on the routing/navigation
+import React, { useState } from "react";
+
+import { CityTabs } from "./CityTabs";
+import { Industry } from "./Industry";
+import { Overview } from "./Overview";
+
+import { OfficeSpace } from "../OfficeSpace";
+
+import { CityName } from "../../Locations/data/CityNames";
+
+interface IProps {
+ corp: any;
+ eventHandler: any;
+ routing: any;
+}
+
+export function MainPanel(props: IProps): React.ReactElement {
+ const [division, setDivision] = useState("");
+ const [city, setCity] = useState(CityName.Sector12);
+
+ // We can pass this setter to child components
+ function changeCityState(newCity: string): void {
+ if (Object.values(CityName).includes(newCity as CityName)) {
+ setCity(newCity);
+ } else {
+ console.error(`Tried to change MainPanel's city state to an invalid city: ${newCity}`);
+ }
+ }
+
+ function renderOverviewPage() {
+ return (
+
+
+
+ )
+ }
+
+ function renderDivisionPage() {
+ // Note: Division is the same thing as Industry...I wasn't consistent with naming
+ const division = props.routing.currentDivision;
+ if (division == null) {
+ throw new Error(`Routing does not hold reference to the current Industry`);
+ }
+
+ // City tabs
+ const onClicks: { [key: string]: () => void } = {};
+ for (const cityName in division.offices) {
+ if (division.offices[cityName] instanceof OfficeSpace) {
+ onClicks[cityName] = () => {
+ setCity(cityName);
+ props.corp.rerender();
+ }
+ }
+ }
+
+ const cityTabs = (
+
+ )
+
+ return (
+
+ {cityTabs}
+
+
+ )
+ }
+
+ if (props.routing.isOnOverviewPage()) {
+ // Corporation overview Content
+ return renderOverviewPage();
+ } else {
+ // Division content
+
+ // First, check if we're at a new division. If so, we need to reset the city to Sector-12
+ // Otherwise, just switch the 'city' state
+ const currentDivision = props.routing.current();
+ if (currentDivision !== division) {
+ setDivision(currentDivision);
+ setCity(CityName.Sector12);
+ }
+
+ return renderDivisionPage();
+ }
+}
diff --git a/src/Corporation/ui/Root.jsx b/src/Corporation/ui/Root.jsx
deleted file mode 100644
index b9071646a..000000000
--- a/src/Corporation/ui/Root.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-// Root React Component for the Corporation UI
-import React from "react";
-import { BaseReactComponent } from "./BaseReactComponent";
-
-import { HeaderTabs } from "./HeaderTabs";
-import { MainPanel } from "./MainPanel";
-
-export class CorporationRoot extends BaseReactComponent {
- render() {
- return (
-
-
-
-
- )
- }
-}
diff --git a/src/Corporation/ui/Root.tsx b/src/Corporation/ui/Root.tsx
new file mode 100644
index 000000000..7c3294390
--- /dev/null
+++ b/src/Corporation/ui/Root.tsx
@@ -0,0 +1,20 @@
+// Root React Component for the Corporation UI
+import React from "react";
+
+import { HeaderTabs } from "./HeaderTabs";
+import { MainPanel } from "./MainPanel";
+
+interface IProps {
+ corp: any;
+ eventHandler: any;
+ routing: any;
+}
+
+export function CorporationRoot(props: IProps): React.ReactElement {
+ return (
+
+
+
+
+ )
+}
From 0d30544a524f6d39e0017cb913b54082de4ecfa1 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 12:00:18 -0400
Subject: [PATCH 08/30] more conversion
---
src/Corporation/Corporation.jsx | 1 +
src/Corporation/ui/BribeFactionPopup.tsx | 83 ++++++++++++++
.../ui/CorporationUIEventHandler.js | 108 ------------------
src/Corporation/ui/HeaderTabs.tsx | 2 +-
src/Corporation/ui/MainPanel.tsx | 2 +
src/Corporation/ui/Overview.tsx | 17 ++-
src/Corporation/ui/Root.tsx | 4 +-
7 files changed, 105 insertions(+), 112 deletions(-)
create mode 100644 src/Corporation/ui/BribeFactionPopup.tsx
diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx
index 681960e18..1cebfec66 100644
--- a/src/Corporation/Corporation.jsx
+++ b/src/Corporation/Corporation.jsx
@@ -1924,6 +1924,7 @@ Corporation.prototype.rerender = function() {
corp={this}
routing={corpRouting}
eventHandler={eventHandler}
+ player={Player}
/>, companyManagementDiv);
}
diff --git a/src/Corporation/ui/BribeFactionPopup.tsx b/src/Corporation/ui/BribeFactionPopup.tsx
new file mode 100644
index 000000000..62196e077
--- /dev/null
+++ b/src/Corporation/ui/BribeFactionPopup.tsx
@@ -0,0 +1,83 @@
+import React, { useState } from 'react';
+import { IPlayer } from "../../PersonObjects/IPlayer";
+import { Factions } from "../../Faction/Factions";
+import { CorporationConstants } from "../data/Constants";
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { removePopup } from "../../ui/React/createPopup";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+
+interface IProps {
+ popupId: string;
+ corp: any;
+ player: IPlayer;
+}
+
+export function BribeFactionPopup(props: IProps): React.ReactElement {
+ const [money, setMoney] = useState(0);
+ const [stock, setStock] = useState(0);
+ const [selectedFaction, setSelectedFaction] = useState(props.player.factions.length > 0 ? props.player.factions[0] : "");
+
+ function onMoneyChange(event: React.ChangeEvent): void {
+ setMoney(parseFloat(event.target.value));
+ }
+
+ function onStockChange(event: React.ChangeEvent): void {
+ setStock(parseFloat(event.target.value));
+ }
+
+ function repGain(money: number, stock: number): number {
+ return (money + (stock * props.corp.sharePrice)) / CorporationConstants.BribeToRepRatio;
+ }
+
+ function getRepText(money: number, stock: number): string {
+ if(money === 0 && stock === 0) return "";
+ if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) {
+ return "ERROR: Invalid value(s) entered";
+ } else if (props.corp.funds.lt(money)) {
+ return "ERROR: You do not have this much money to bribe with";
+ } else if (props.corp.stock > props.corp.numShares) {
+ return "ERROR: You do not have this many shares to bribe with";
+ } else {
+ return "You will gain " + numeralWrapper.formatReputation(repGain(money, stock)) +
+ " reputation with " +
+ selectedFaction +
+ " with this bribe";
+ }
+ }
+
+ function bribe(money: number, stock: number) {
+ const fac = Factions[selectedFaction];
+ if (fac == null) {
+ dialogBoxCreate("ERROR: You must select a faction to bribe");
+ }
+ if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) {
+ } else if (props.corp.funds.lt(money)) {
+ } else if (stock > props.corp.numShares) {
+ } else {
+ const rep = repGain(money, stock);
+ dialogBoxCreate("You gained " + numeralWrapper.formatReputation(rep) +
+ " reputation with " + fac.name + " by bribing them.");
+ fac.playerReputation += rep;
+ props.corp.funds = props.corp.funds.minus(money);
+ props.corp.numShares -= stock;
+ removePopup(props.popupId);
+ }
+ }
+
+ return (<>
+ You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation.
+
+ {
+ props.player.factions.map((name: string) => {
+ const info = Factions[name].getInfo();
+ if(!info.offersWork()) return;
+ return {name}
+ })
+ }
+
+ {getRepText(money ? money : 0, stock ? stock : 0)}
+
+
+ bribe(money ? money : 0, stock ? stock : 0)} style={{display:"inline-block"}}>Bribe
+ >);
+}
diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js
index 8b158971c..a3b6e11d8 100644
--- a/src/Corporation/ui/CorporationUIEventHandler.js
+++ b/src/Corporation/ui/CorporationUIEventHandler.js
@@ -56,114 +56,6 @@ export class CorporationEventHandler {
this.routing = routing;
}
- // Create a popup that lets the player bribe factions
- // This is created when the player clicks the "Bribe Factions" button in the overview panel
- createBribeFactionsPopup() {
- const popupId = "cmpy-mgmt-bribe-factions-popup";
- const txt = createElement("p", {
- innerText:"You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation",
- });
- const factionSelector = createElement("select", { class: "dropdown", margin:"3px" });
- for (let i = 0; i < Player.factions.length; ++i) {
- const facName = Player.factions[i];
-
- const faction = Factions[facName];
- const info = faction.getInfo();
- if(!info.offersWork()) continue;
-
- factionSelector.add(createElement("option", {
- text: facName, value: facName,
- }));
- }
- var repGainText = createElement("p");
- var stockSharesInput;
- var moneyInput = createElement("input", {
- class: "text-input",
- type:"number", placeholder:"Corporation funds", margin:"5px",
- inputListener:()=>{
- var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value);
- var stockPrice = this.corp.sharePrice;
- var stockShares = stockSharesInput.value == null || stockSharesInput.value == "" ? 0 : Math.round(parseFloat(stockSharesInput.value));
- if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) {
- repGainText.innerText = "ERROR: Invalid value(s) entered";
- } else if (this.corp.funds.lt(money)) {
- repGainText.innerText = "ERROR: You do not have this much money to bribe with";
- } else if (this.corp.stockShares > this.corp.numShares) {
- repGainText.innerText = "ERROR: You do not have this many shares to bribe with";
- } else {
-
- var totalAmount = Number(money) + (stockShares * stockPrice);
- var repGain = totalAmount / BribeToRepRatio;
- repGainText.innerText = "You will gain " + numeralWrapper.format(repGain, "0,0") +
- " reputation with " +
- factionSelector.options[factionSelector.selectedIndex].value +
- " with this bribe";
- }
- },
- });
- stockSharesInput = createElement("input", {
- class: "text-input",
- type:"number", placeholder:"Stock Shares", margin: "5px",
- inputListener:()=>{
- var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value);
- var stockPrice = this.corp.sharePrice;
- var stockShares = stockSharesInput.value == null || stockSharesInput.value == "" ? 0 : Math.round(stockSharesInput.value);
- if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) {
- repGainText.innerText = "ERROR: Invalid value(s) entered";
- } else if (this.corp.funds.lt(money)) {
- repGainText.innerText = "ERROR: You do not have this much money to bribe with";
- } else if (this.corp.stockShares > this.corp.numShares) {
- repGainText.innerText = "ERROR: You do not have this many shares to bribe with";
- } else {
- var totalAmount = money + (stockShares * stockPrice);
- var repGain = totalAmount / BribeToRepRatio;
- repGainText.innerText = "You will gain " + numeralWrapper.format(repGain, "0,0") +
- " reputation with " +
- factionSelector.options[factionSelector.selectedIndex].value +
- " with this bribe";
- }
- },
- });
- var confirmButton = createElement("button", {
- class:"a-link-button", innerText:"Bribe", display:"inline-block",
- clickListener:()=>{
- var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value);
- var stockPrice = this.corp.sharePrice;
- var stockShares = stockSharesInput.value == null || stockSharesInput.value == ""? 0 : Math.round(parseFloat(stockSharesInput.value));
- var fac = Factions[factionSelector.options[factionSelector.selectedIndex].value];
- if (fac == null) {
- dialogBoxCreate("ERROR: You must select a faction to bribe");
- return false;
- }
- if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) {
- dialogBoxCreate("ERROR: Invalid value(s) entered");
- } else if (this.corp.funds.lt(money)) {
- dialogBoxCreate("ERROR: You do not have this much money to bribe with");
- } else if (stockShares > this.corp.numShares) {
- dialogBoxCreate("ERROR: You do not have this many shares to bribe with");
- } else {
- var totalAmount = money + (stockShares * stockPrice);
- var repGain = totalAmount / BribeToRepRatio;
- dialogBoxCreate("You gained " + numeralWrapper.format(repGain, "0,0") +
- " reputation with " + fac.name + " by bribing them.");
- fac.playerReputation += repGain;
- this.corp.funds = this.corp.funds.minus(money);
- this.corp.numShares -= stockShares;
- removeElementById(popupId);
- return false;
- }
- },
- });
- const cancelButton = createPopupCloseButton(popupId, {
- class: "std-button",
- display: "inline-block",
- innerText: "Cancel",
- })
-
- createPopup(popupId, [txt, factionSelector, repGainText,
- moneyInput, stockSharesInput, confirmButton, cancelButton]);
- }
-
// Create a popup that lets the player buyback shares
// This is created when the player clicks the "Buyback Shares" button in the overview panel
createBuybackSharesPopup() {
diff --git a/src/Corporation/ui/HeaderTabs.tsx b/src/Corporation/ui/HeaderTabs.tsx
index 8ee5b6ff8..119d19916 100644
--- a/src/Corporation/ui/HeaderTabs.tsx
+++ b/src/Corporation/ui/HeaderTabs.tsx
@@ -38,7 +38,7 @@ export function HeaderTabs(props: IProps): React.ReactElement {
}
props.eventHandler.createNewIndustryPopup()}
text={"Expand into new Industry"}
/>
diff --git a/src/Corporation/ui/MainPanel.tsx b/src/Corporation/ui/MainPanel.tsx
index c46acf65f..434d812d1 100644
--- a/src/Corporation/ui/MainPanel.tsx
+++ b/src/Corporation/ui/MainPanel.tsx
@@ -10,11 +10,13 @@ import { Overview } from "./Overview";
import { OfficeSpace } from "../OfficeSpace";
import { CityName } from "../../Locations/data/CityNames";
+import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
corp: any;
eventHandler: any;
routing: any;
+ player: IPlayer;
}
export function MainPanel(props: IProps): React.ReactElement {
diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx
index 760690293..6d9b2e48d 100644
--- a/src/Corporation/ui/Overview.tsx
+++ b/src/Corporation/ui/Overview.tsx
@@ -2,6 +2,7 @@
import React from "react";
import { LevelableUpgrade } from "./LevelableUpgrade";
import { UnlockUpgrade } from "./UnlockUpgrade";
+import { BribeFactionPopup } from "./BribeFactionPopup";
import { CorporationConstants } from "../data/Constants";
import { CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades";
@@ -10,10 +11,13 @@ import { CorporationUpgrades } from "../data/CorporationUpgrades";
import { CONSTANTS } from "../../Constants";
import { numeralWrapper } from "../../ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
+import { createPopup } from "../../ui/React/createPopup";
+import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
corp: any;
eventHandler: any;
+ player: IPlayer;
}
export function Overview(props: IProps): React.ReactElement {
@@ -118,14 +122,23 @@ export function Overview(props: IProps): React.ReactElement {
"provides some tips/pointers for helping you get started with managing it.",
});
+ function openBribeFactionPopup() {
+ const popupId = "corp-bribe-popup";
+ createPopup(popupId, BribeFactionPopup, {
+ player: props.player,
+ popupId: popupId,
+ corp: props.corp,
+ });
+ }
+
// Create a "Bribe Factions" button if your Corporation is powerful enough.
// This occurs regardless of whether you're public or private
- const canBribe = (props.corp.determineValuation() >= CorporationConstants.BribeThreshold);
+ const canBribe = (props.corp.determineValuation() >= CorporationConstants.BribeThreshold) || true;
const bribeFactionsClass = (canBribe ? "a-link-button" : "a-link-button-inactive");
const bribeFactionsBtn = createButton({
class: bribeFactionsClass,
display: "inline-block",
- onClick: props.eventHandler.createBribeFactionsPopup,
+ onClick: openBribeFactionPopup,
text: "Bribe Factions",
tooltip: (canBribe
? "Use your Corporations power and influence to bribe Faction leaders in exchange for reputation"
diff --git a/src/Corporation/ui/Root.tsx b/src/Corporation/ui/Root.tsx
index 7c3294390..f7c3cb348 100644
--- a/src/Corporation/ui/Root.tsx
+++ b/src/Corporation/ui/Root.tsx
@@ -3,18 +3,20 @@ import React from "react";
import { HeaderTabs } from "./HeaderTabs";
import { MainPanel } from "./MainPanel";
+import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
corp: any;
eventHandler: any;
routing: any;
+ player: IPlayer;
}
export function CorporationRoot(props: IProps): React.ReactElement {
return (
-
+
)
}
From 8bb4e8b7cf7d4a37ce439c28c6ebe83d7e48e728 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 13:07:35 -0400
Subject: [PATCH 09/30] more conversion
---
.../ui/CorporationUIEventHandler.js | 165 ------------------
src/Corporation/ui/IndustryOffice.tsx | 31 +++-
src/Corporation/ui/IndustryWarehouse.tsx | 18 +-
src/Corporation/ui/ThrowPartyPopup.tsx | 57 ++++++
src/Corporation/ui/UpgradeOfficeSizePopup.tsx | 77 ++++++++
5 files changed, 174 insertions(+), 174 deletions(-)
create mode 100644 src/Corporation/ui/ThrowPartyPopup.tsx
create mode 100644 src/Corporation/ui/UpgradeOfficeSizePopup.tsx
diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js
index a3b6e11d8..2a7db33aa 100644
--- a/src/Corporation/ui/CorporationUIEventHandler.js
+++ b/src/Corporation/ui/CorporationUIEventHandler.js
@@ -1474,171 +1474,6 @@ export class CorporationEventHandler {
input.focus();
}
- // Creates a popup that lets the player throw an office party
- createThrowOfficePartyPopup(office) {
- const popupId = "cmpy-mgmt-throw-office-party-popup";
- const txt = createElement("p", {
- innerText:"Enter the amount of money you would like to spend PER EMPLOYEE " +
- "on this office party",
- });
- const totalCostTxt = createElement("p", {
- innerText:"Throwing this party will cost a total of $0",
- });
- let confirmBtn;
- const input = createElement("input", {
- type: "number", margin: "5px", placeholder: "$ / employee",
- inputListener: () => {
- if (isNaN(input.value) || input.value < 0) {
- totalCostTxt.innerText = "Invalid value entered!"
- } else {
- const totalCost = input.value * office.employees.length;
- totalCostTxt.innerText = "Throwing this party will cost a total of " + numeralWrapper.format(totalCost, '$0.000a');
- }
- },
- onkeyup:(e)=>{
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
- },
- });
- confirmBtn = createElement("button", {
- class: "std-button",
- innerText: "Throw Party",
- clickListener:()=>{
- if (isNaN(input.value) || input.value < 0) {
- dialogBoxCreate("Invalid value entered");
- } else {
- var totalCost = input.value * office.employees.length;
- if (this.corp.funds.lt(totalCost)) {
- dialogBoxCreate("You don't have enough company funds to throw this.corp party!");
- } else {
- this.corp.funds = this.corp.funds.minus(totalCost);
- var mult;
- for (let fooit = 0; fooit < office.employees.length; ++fooit) {
- mult = office.employees[fooit].throwParty(input.value);
- }
- dialogBoxCreate("You threw a party for the office! The morale and happiness " +
- "of each employee increased by " + numeralWrapper.formatPercentage((mult-1)));
- removeElementById(popupId);
- }
- }
- return false;
- },
- });
- const cancelBtn = createPopupCloseButton(popupId, { class: "std-button", innerText: "Cancel" });
-
- createPopup(popupId, [txt, totalCostTxt, input, confirmBtn, cancelBtn]);
- input.focus();
- }
-
- // Creates a popup that lets the player upgrade the current OfficeSpace's size
- createUpgradeOfficeSizePopup(office) {
- const popupId = "cmpy-mgmt-upgrade-office-size-popup";
- const initialPriceMult = Math.round(office.size / OfficeInitialSize);
- const costMultiplier = 1.09;
- const upgradeCost = OfficeInitialCost * Math.pow(costMultiplier, initialPriceMult);
-
- // Calculate cost to upgrade size by 15 employees
- let mult = 0;
- for (let i = 0; i < 5; ++i) {
- mult += (Math.pow(costMultiplier, initialPriceMult + i));
- }
- const upgradeCost15 = OfficeInitialCost * mult;
-
- //Calculate max upgrade size and cost
- let maxMult = (this.corp.funds.dividedBy(OfficeInitialCost)).toNumber();
- let maxNum = 1;
- mult = Math.pow(costMultiplier, initialPriceMult);
- while(maxNum < 50) { //Hard cap of 50x (extra 150 employees)
- if (mult >= maxMult) {break;}
- let multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum);
- if (mult + multIncrease > maxMult) {
- break;
- } else {
- mult += multIncrease;
- }
- ++maxNum;
- }
- const upgradeCostMax = OfficeInitialCost * mult;
-
- const text = createElement("p", {
- innerText:"Increase the size of your office space to fit additional employees!",
- });
- const text2 = createElement("p", { innerText: "Upgrade size: " });
-
- const confirmBtn = createElement("button", {
- class: this.corp.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button",
- display:"inline-block", margin:"4px", innerText:"by 3",
- tooltip:numeralWrapper.format(upgradeCost, "$0.000a"),
- clickListener:()=>{
- if (this.corp.funds.lt(upgradeCost)) {
- dialogBoxCreate("You don't have enough company funds to purchase this upgrade!");
- } else {
- office.size += OfficeInitialSize;
- this.corp.funds = this.corp.funds.minus(upgradeCost);
- dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees");
- this.rerender();
- }
- removeElementById(popupId);
- return false;
- },
- });
- const confirmBtn15 = createElement("button", {
- class: this.corp.funds.lt(upgradeCost15) ? "a-link-button-inactive" : "a-link-button",
- display:"inline-block", margin:"4px", innerText:"by 15",
- tooltip:numeralWrapper.format(upgradeCost15, "$0.000a"),
- clickListener:()=>{
- if (this.corp.funds.lt(upgradeCost15)) {
- dialogBoxCreate("You don't have enough company funds to purchase this upgrade!");
- } else {
- office.size += (OfficeInitialSize * 5);
- this.corp.funds = this.corp.funds.minus(upgradeCost15);
- dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees");
- this.rerender();
- }
- removeElementById(popupId);
- return false;
- },
- });
- const confirmBtnMax = createElement("button", {
- class:this.corp.funds.lt(upgradeCostMax) ? "a-link-button-inactive" : "a-link-button",
- display:"inline-block", margin:"4px", innerText:"by MAX (" + maxNum*OfficeInitialSize + ")",
- tooltip:numeralWrapper.format(upgradeCostMax, "$0.000a"),
- clickListener:()=>{
- if (this.corp.funds.lt(upgradeCostMax)) {
- dialogBoxCreate("You don't have enough company funds to purchase this upgrade!");
- } else {
- office.size += (OfficeInitialSize * maxNum);
- this.corp.funds = this.corp.funds.minus(upgradeCostMax);
- dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees");
- this.rerender();
- }
- removeElementById(popupId);
- return false;
- },
- });
- const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
- cancelBtn.style.margin = "4px";
-
- createPopup(popupId, [text, text2, confirmBtn, confirmBtn15, confirmBtnMax, cancelBtn]);
- }
-
- // Purchases a new Warehouse
- purchaseWarehouse(division, city) {
- const corp = this.corp;
- if (corp.funds.lt(WarehouseInitialCost)) {
- dialogBoxCreate("You do not have enough funds to do this!");
- } else {
- division.warehouses[city] = new Warehouse({
- corp: corp,
- industry: division,
- loc: city,
- size: WarehouseInitialSize,
- });
- corp.funds = corp.funds.minus(WarehouseInitialCost);
- this.rerender();
- }
- }
-
rerender() {
this.corp.rerender();
}
diff --git a/src/Corporation/ui/IndustryOffice.tsx b/src/Corporation/ui/IndustryOffice.tsx
index 93758e70f..c2b2438b8 100644
--- a/src/Corporation/ui/IndustryOffice.tsx
+++ b/src/Corporation/ui/IndustryOffice.tsx
@@ -9,6 +9,9 @@ import { EmployeePositions } from "../EmployeePositions";
import { numeralWrapper } from "../../ui/numeralFormat";
import { getSelectText } from "../../../utils/uiHelpers/getSelectData";
+import { createPopup } from "../../ui/React/createPopup";
+import { UpgradeOfficeSizePopup } from "./UpgradeOfficeSizePopup";
+import { ThrowPartyPopup } from "./ThrowPartyPopup";
interface IProps {
routing: any;
@@ -565,17 +568,29 @@ export function IndustryOffice(props: IProps): React.ReactElement {
} else {
autohireEmployeeButtonClass += " std-button";
}
- const autohireEmployeeButtonOnClick = () => {
- if (office.atCapacity()) { return; }
+ function autohireEmployeeButtonOnClick(): void {
+ if (office.atCapacity()) return;
office.hireRandomEmployee();
props.corp.rerender();
}
- // Upgrade Office Size Button
- const upgradeOfficeSizeOnClick = props.eventHandler.createUpgradeOfficeSizePopup.bind(props.eventHandler, office);
+ function openUpgradeOfficeSizePopup(): void {
+ const popupId = "cmpy-mgmt-upgrade-office-size-popup";
+ createPopup(popupId, UpgradeOfficeSizePopup, {
+ office: office,
+ corp: props.corp,
+ popupId: popupId,
+ });
+ }
- // Throw Office Party
- const throwOfficePartyOnClick = props.eventHandler.createThrowOfficePartyPopup.bind(props.eventHandler, office);
+ function openThrowPartyPopup(): void {
+ const popupId = "cmpy-mgmt-throw-office-party-popup";
+ createPopup(popupId, ThrowPartyPopup, {
+ office: office,
+ corp: props.corp,
+ popupId: popupId,
+ });
+ }
return (
@@ -598,7 +613,7 @@ export function IndustryOffice(props: IProps): React.ReactElement {
-
+
Upgrade size
Upgrade the office's size so that it can hold more employees!
@@ -606,7 +621,7 @@ export function IndustryOffice(props: IProps): React.ReactElement {
{
!division.hasResearch("AutoPartyManager") &&
-
+
Throw Party
"Throw an office party to increase your employee's morale and happiness"
diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx
index 1c6181e3a..3a56ddc9c 100644
--- a/src/Corporation/ui/IndustryWarehouse.tsx
+++ b/src/Corporation/ui/IndustryWarehouse.tsx
@@ -9,6 +9,7 @@ import { Product } from "../Product";
import { Warehouse } from "../Warehouse";
import { numeralWrapper } from "../../ui/numeralFormat";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
import { isString } from "../../../utils/helpers/isString";
@@ -527,12 +528,27 @@ export function IndustryWarehouse(props: IProps) {
}
const warehouse = division.warehouses[props.currentCity];
+ function purchaseWarehouse(division: any, city: string) {
+ if (props.corp.funds.lt(CorporationConstants.WarehouseInitialCost)) {
+ dialogBoxCreate("You do not have enough funds to do this!");
+ } else {
+ division.warehouses[city] = new Warehouse({
+ corp: props.corp,
+ industry: division,
+ loc: city,
+ size: CorporationConstants.WarehouseInitialSize,
+ });
+ props.corp.funds = props.corp.funds.minus(CorporationConstants.WarehouseInitialCost);
+ props.corp.rerender();
+ }
+ }
+
if (warehouse instanceof Warehouse) {
return renderWarehouseUI();
} else {
return (
- props.eventHandler.purchaseWarehouse(division, props.currentCity)}>
+ purchaseWarehouse(division, props.currentCity)}>
Purchase Warehouse ({numeralWrapper.formatMoney(CorporationConstants.WarehouseInitialCost)})
diff --git a/src/Corporation/ui/ThrowPartyPopup.tsx b/src/Corporation/ui/ThrowPartyPopup.tsx
new file mode 100644
index 000000000..ff267fa38
--- /dev/null
+++ b/src/Corporation/ui/ThrowPartyPopup.tsx
@@ -0,0 +1,57 @@
+import React, { useState } from 'react';
+import { removePopup } from "../../ui/React/createPopup";
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { IOfficeSpace } from "../IOfficeSpace";
+
+interface IProps {
+ office: IOfficeSpace;
+ corp: any;
+ popupId: string;
+}
+
+export function ThrowPartyPopup(props: IProps): React.ReactElement {
+ const [cost, setCost] = useState(null);
+
+ function changeCost(event: React.ChangeEvent): void {
+ setCost(parseFloat(event.target.value));
+ }
+
+ function throwParty() {
+ if (cost === null || isNaN(cost) || cost < 0) {
+ dialogBoxCreate("Invalid value entered");
+ } else {
+ const totalCost = cost * props.office.employees.length;
+ if (props.corp.funds.lt(totalCost)) {
+ dialogBoxCreate("You don't have enough company funds to throw a party!");
+ } else {
+ props.corp.funds = props.corp.funds.minus(totalCost);
+ let mult;
+ for (let fooit = 0; fooit < props.office.employees.length; ++fooit) {
+ mult = props.office.employees[fooit].throwParty(cost);
+ }
+ dialogBoxCreate("You threw a party for the office! The morale and happiness " +
+ "of each employee increased by " + numeralWrapper.formatPercentage((mult-1)));
+ removePopup(props.popupId);
+ }
+ }
+ }
+
+ function EffectText(props: {cost: number | null, office: IOfficeSpace}): React.ReactElement {
+ let cost = props.cost;
+ if(cost !== null && (isNaN(cost) || cost < 0)) return Invalid value entered!
+ if(cost === null) cost = 0;
+ return Throwing this party will cost a total of {numeralWrapper.formatMoney(cost * props.office.employees.length)}
+ }
+
+ function onKeyDown(event: React.KeyboardEvent) {
+ if (event.keyCode === 13) throwParty();
+ }
+
+ return (<>
+ Enter the amount of money you would like to spend PER EMPLOYEE on this office party
+
+
+ Throw Party
+ >);
+}
\ No newline at end of file
diff --git a/src/Corporation/ui/UpgradeOfficeSizePopup.tsx b/src/Corporation/ui/UpgradeOfficeSizePopup.tsx
new file mode 100644
index 000000000..b40cf75dd
--- /dev/null
+++ b/src/Corporation/ui/UpgradeOfficeSizePopup.tsx
@@ -0,0 +1,77 @@
+import React from "react";
+import { removePopup } from "../../ui/React/createPopup";
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { CorporationConstants } from "../data/Constants";
+import { IOfficeSpace } from "../IOfficeSpace";
+
+interface IProps {
+ office: IOfficeSpace;
+ corp: any;
+ popupId: string;
+}
+
+export function UpgradeOfficeSizePopup(props: IProps): React.ReactElement {
+ const initialPriceMult = Math.round(props.office.size / CorporationConstants.OfficeInitialSize);
+ const costMultiplier = 1.09;
+ const upgradeCost = CorporationConstants.OfficeInitialCost * Math.pow(costMultiplier, initialPriceMult);
+
+ // Calculate cost to upgrade size by 15 employees
+ let mult = 0;
+ for (let i = 0; i < 5; ++i) {
+ mult += (Math.pow(costMultiplier, initialPriceMult + i));
+ }
+ const upgradeCost15 = CorporationConstants.OfficeInitialCost * mult;
+
+ //Calculate max upgrade size and cost
+ let maxMult = (props.corp.funds.dividedBy(CorporationConstants.OfficeInitialCost)).toNumber();
+ let maxNum = 1;
+ mult = Math.pow(costMultiplier, initialPriceMult);
+ while(maxNum < 50) { //Hard cap of 50x (extra 150 employees)
+ if (mult >= maxMult) break;
+ let multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum);
+ if (mult + multIncrease > maxMult) {
+ break;
+ } else {
+ mult += multIncrease;
+ }
+ ++maxNum;
+ }
+ const upgradeCostMax = CorporationConstants.OfficeInitialCost * mult;
+
+ function upgradeSize(cost: number, size: number): void {
+ if (props.corp.funds.lt(cost)) {
+ dialogBoxCreate("You don't have enough company funds to purchase this upgrade!");
+ } else {
+ props.office.size += size;
+ props.corp.funds = props.corp.funds.minus(cost);
+ dialogBoxCreate("Office space increased! It can now hold " + props.office.size + " employees");
+ props.corp.rerender();
+ }
+ removePopup(props.popupId);
+ }
+
+ interface IUpgradeButton {
+ cost: number;
+ size: number;
+ corp: any;
+ }
+
+ function UpgradeSizeButton(props: IUpgradeButton): React.ReactElement {
+ return ( upgradeSize(props.cost, props.size)}
+ >by {props.size}
+ {numeralWrapper.formatMoney(props.cost)}
+ );
+ }
+
+ return (<>
+ Increase the size of your office space to fit additional employees!
+ Upgrade size:
+
+
+
+ >);
+}
\ No newline at end of file
From 4b6d049da253e7c631302969efaf81779f1926bc Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 13:41:25 -0400
Subject: [PATCH 10/30] more conversion
---
.../ui/CorporationUIEventHandler.js | 134 ------------------
src/Corporation/ui/Overview.tsx | 8 +-
src/Corporation/ui/SellSharesPopup.tsx | 97 +++++++++++++
3 files changed, 104 insertions(+), 135 deletions(-)
create mode 100644 src/Corporation/ui/SellSharesPopup.tsx
diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js
index 2a7db33aa..f56b80a34 100644
--- a/src/Corporation/ui/CorporationUIEventHandler.js
+++ b/src/Corporation/ui/CorporationUIEventHandler.js
@@ -695,56 +695,6 @@ export class CorporationEventHandler {
}
}
- // Create a popup that lets the player expand into a new city (for the current industry)
- // The 'cityStateSetter' arg is a function that sets the UI's 'city' state property
- createNewCityPopup(division, cityStateSetter) {
- const popupId = "cmpy-mgmt-expand-city-popup";
- const text = createElement("p", {
- innerText: "Would you like to expand into a new city by opening an office? " +
- "This would cost " + numeralWrapper.format(OfficeInitialCost, '$0.000a'),
- });
- const citySelector = createElement("select", { class: "dropdown", margin:"5px" });
- for (const cityName in division.offices) {
- if (!(division.offices[cityName] instanceof OfficeSpace)) {
- citySelector.add(createElement("option", {
- text: cityName,
- value: cityName,
- }));
- }
- }
-
- const confirmBtn = createElement("button", {
- class:"std-button",
- display:"inline-block",
- innerText: "Confirm",
- clickListener: () => {
- if (citySelector.length <= 0) { return false; }
- let city = citySelector.options[citySelector.selectedIndex].value;
- if (this.corp.funds.lt(OfficeInitialCost)) {
- dialogBoxCreate("You don't have enough company funds to open a new office!");
- } else {
- this.corp.funds = this.corp.funds.minus(OfficeInitialCost);
- dialogBoxCreate("Opened a new office in " + city + "!");
- division.offices[city] = new OfficeSpace({
- loc: city,
- size: OfficeInitialSize,
- });
- }
-
- cityStateSetter(city);
- removeElementById(popupId);
- this.rerender();
- return false;
- },
- });
- const cancelBtn = createPopupCloseButton(popupId, {
- class: "std-button",
- innerText: "Cancel",
- });
-
- createPopup(popupId, [text, citySelector, confirmBtn, cancelBtn]);
- }
-
// Create a popup that lets the player create a new industry.
// This is created when the player clicks the "Expand into new Industry" header tab
createNewIndustryPopup() {
@@ -1390,90 +1340,6 @@ export class CorporationEventHandler {
inputQty.focus();
}
- // Create a popup that lets the player sell Corporation shares
- // This is created when the player clicks the "Sell Shares" button in the overview panel
- createSellSharesPopup() {
- const popupId = "cmpy-mgmt-sell-shares-popup";
- const currentStockPrice = this.corp.sharePrice;
- const txt = createElement("p", {
- innerHTML: "Enter the number of shares you would like to sell. The money from " +
- "selling your shares will go directly to you (NOT your Corporation). " +
- "Selling your shares will cause your corporation's stock price to fall due to " +
- "dilution. Furthermore, selling a large number of shares all at once will have an immediate effect " +
- "in reducing your stock price. " +
- "The current price of your " +
- "company's stock is " + numeralWrapper.format(currentStockPrice, "$0.000a"),
- });
- const profitIndicator = createElement("p");
- const input = createElement("input", {
- class: "text-input",
- type:"number", placeholder:"Shares to sell", margin:"5px",
- inputListener: ()=> {
- var numShares = Math.round(input.value);
- if (isNaN(numShares) || numShares <= 0) {
- profitIndicator.innerText = "ERROR: Invalid value entered for number of shares to sell"
- } else if (numShares > this.corp.numShares) {
- profitIndicator.innerText = "You don't have this many shares to sell!";
- } else {
- const stockSaleResults = this.corp.calculateShareSale(numShares);
- const profit = stockSaleResults[0];
- profitIndicator.innerText = "Sell " + numShares + " shares for a total of " +
- numeralWrapper.format(profit, '$0.000a');
- }
- },
- });
- const confirmBtn = createElement("button", {
- class:"a-link-button", innerText:"Sell shares", display:"inline-block",
- clickListener:()=>{
- var shares = Math.round(input.value);
- if (isNaN(shares) || shares <= 0) {
- dialogBoxCreate("ERROR: Invalid value for number of shares");
- } else if (shares > this.corp.numShares) {
- dialogBoxCreate("ERROR: You don't have this many shares to sell");
- } else {
- const stockSaleResults = this.corp.calculateShareSale(shares);
- const profit = stockSaleResults[0];
- const newSharePrice = stockSaleResults[1];
- const newSharesUntilUpdate = stockSaleResults[2];
-
- this.corp.numShares -= shares;
- if (isNaN(this.corp.issuedShares)) {
- console.error(`Corporation issuedShares is NaN: ${this.corp.issuedShares}`);
- var res = parseInt(this.corp.issuedShares);
- if (isNaN(res)) {
- this.corp.issuedShares = 0;
- } else {
- this.corp.issuedShares = res;
- }
- }
- this.corp.issuedShares += shares;
- this.corp.sharePrice = newSharePrice;
- this.corp.shareSalesUntilPriceUpdate = newSharesUntilUpdate;
- this.corp.shareSaleCooldown = SellSharesCooldown;
- Player.gainMoney(profit);
- Player.recordMoneySource(profit, "corporation");
- removeElementById(popupId);
- dialogBoxCreate(`Sold ${numeralWrapper.formatMoney(shares, "0.000a")} shares for ` +
- `${numeralWrapper.formatMoney(profit, "$0.000a")}. ` +
- `The corporation's stock price fell to ${numeralWrapper.formatMoney(this.corp.sharePrice)} ` +
- `as a result of dilution.`);
-
- this.rerender();
- return false;
- }
-
- },
- });
- const cancelBtn = createPopupCloseButton(popupId, {
- class: "std-button",
- display: "inline-block",
- innerText: "Cancel",
- });
-
- createPopup(popupId, [txt, profitIndicator, input, confirmBtn, cancelBtn]);
- input.focus();
- }
-
rerender() {
this.corp.rerender();
}
diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx
index 6d9b2e48d..58b48bc4e 100644
--- a/src/Corporation/ui/Overview.tsx
+++ b/src/Corporation/ui/Overview.tsx
@@ -3,6 +3,7 @@ import React from "react";
import { LevelableUpgrade } from "./LevelableUpgrade";
import { UnlockUpgrade } from "./UnlockUpgrade";
import { BribeFactionPopup } from "./BribeFactionPopup";
+import { SellSharesPopup } from "./SellSharesPopup";
import { CorporationConstants } from "../data/Constants";
import { CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades";
@@ -215,7 +216,12 @@ export function Overview(props: IProps): React.ReactElement {
display: "inline-block",
onClick: function(event: MouseEvent) {
if(!event.isTrusted) return;
- props.eventHandler.createSellSharesPopup(event);
+ const popupId = "cmpy-mgmt-sell-shares-popup";
+ createPopup(popupId, SellSharesPopup, {
+ corp: props.corp,
+ player: props.player,
+ popupId: popupId,
+ });
},
text: "Sell Shares",
tooltip: sellSharesTooltip,
diff --git a/src/Corporation/ui/SellSharesPopup.tsx b/src/Corporation/ui/SellSharesPopup.tsx
new file mode 100644
index 000000000..385074223
--- /dev/null
+++ b/src/Corporation/ui/SellSharesPopup.tsx
@@ -0,0 +1,97 @@
+import React, { useState } from 'react';
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { IPlayer } from "../../PersonObjects/IPlayer";
+import { removePopup } from "../../ui/React/createPopup";
+import { CorporationConstants } from "../data/Constants";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+
+interface IProps {
+ corp: any;
+ player: IPlayer;
+ popupId: string;
+}
+
+// Create a popup that lets the player sell Corporation shares
+// This is created when the player clicks the "Sell Shares" button in the overview panel
+export function SellSharesPopup(props: IProps): React.ReactElement {
+ const [shares, setShares] = useState(null);
+
+ function changeShares(event: React.ChangeEvent): void {
+ if(event.target.value === "") setShares(null);
+ else setShares(Math.round(parseFloat(event.target.value)));
+ }
+
+ function ProfitIndicator(props: {shares: number | null, corp: any}): React.ReactElement {
+ if(props.shares === null) return (<>>);
+ if (isNaN(props.shares) || props.shares <= 0) {
+ return (<>ERROR: Invalid value entered for number of shares to sell>);
+ } else if (props.shares > props.corp.numShares) {
+ return (<>You don't have this many shares to sell!>);
+ } else {
+ const stockSaleResults = props.corp.calculateShareSale(props.shares);
+ const profit = stockSaleResults[0];
+ return (<>Sell {props.shares} shares for a total of {numeralWrapper.formatMoney(profit)}>);
+ }
+ }
+
+ function sell() {
+ if(shares === null) return;
+ if (isNaN(shares) || shares <= 0) {
+ dialogBoxCreate("ERROR: Invalid value for number of shares");
+ } else if (shares > props.corp.numShares) {
+ dialogBoxCreate("ERROR: You don't have this many shares to sell");
+ } else {
+ const stockSaleResults = props.corp.calculateShareSale(shares);
+ const profit = stockSaleResults[0];
+ const newSharePrice = stockSaleResults[1];
+ const newSharesUntilUpdate = stockSaleResults[2];
+
+ props.corp.numShares -= shares;
+ if (isNaN(props.corp.issuedShares)) {
+ console.error(`Corporation issuedShares is NaN: ${props.corp.issuedShares}`);
+ var res = parseInt(props.corp.issuedShares);
+ if (isNaN(res)) {
+ props.corp.issuedShares = 0;
+ } else {
+ props.corp.issuedShares = res;
+ }
+ }
+ props.corp.issuedShares += shares;
+ props.corp.sharePrice = newSharePrice;
+ props.corp.shareSalesUntilPriceUpdate = newSharesUntilUpdate;
+ props.corp.shareSaleCooldown = CorporationConstants.SellSharesCooldown;
+ props.player.gainMoney(profit);
+ props.player.recordMoneySource(profit, "corporation");
+ removePopup(props.popupId);
+ dialogBoxCreate(`Sold ${numeralWrapper.formatMoney(shares)} shares for ` +
+ `${numeralWrapper.formatMoney(profit)}. ` +
+ `The corporation's stock price fell to ${numeralWrapper.formatMoney(props.corp.sharePrice)} ` +
+ `as a result of dilution.`);
+
+ props.corp.rerender();
+ }
+ }
+
+
+ function onKeyDown(event: React.KeyboardEvent) {
+ if (event.keyCode === 13) sell();
+ }
+
+ return (<>
+
+Enter the number of shares you would like to sell. The money from
+selling your shares will go directly to you (NOT your Corporation).
+Selling your shares will cause your corporation's stock price to fall due to
+dilution. Furthermore, selling a large number of shares all at once will have an immediate effect
+in reducing your stock price.
+The current price of your
+company's stock is {numeralWrapper.formatMoney(props.corp.sharePrice)}
+
+
+
+
+ Sell shares
+ >);
+
+}
\ No newline at end of file
From 717b32b0b4111ffd9b2d9df42a74e40e70fbb13d Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 14:03:07 -0400
Subject: [PATCH 11/30] more conversion
---
src/Corporation/ui/BuybackSharesPopup.tsx | 87 +++++++++++++++++++
.../ui/CorporationUIEventHandler.js | 76 ----------------
src/Corporation/ui/Overview.tsx | 12 ++-
3 files changed, 98 insertions(+), 77 deletions(-)
create mode 100644 src/Corporation/ui/BuybackSharesPopup.tsx
diff --git a/src/Corporation/ui/BuybackSharesPopup.tsx b/src/Corporation/ui/BuybackSharesPopup.tsx
new file mode 100644
index 000000000..45af985b2
--- /dev/null
+++ b/src/Corporation/ui/BuybackSharesPopup.tsx
@@ -0,0 +1,87 @@
+import React, { useState } from 'react';
+import { IPlayer } from "../../PersonObjects/IPlayer";
+import { removePopup } from "../../ui/React/createPopup";
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+
+interface IProps {
+ player: IPlayer,
+ popupId: string,
+ corp: any,
+}
+
+// Create a popup that lets the player buyback shares
+// This is created when the player clicks the "Buyback Shares" button in the overview panel
+export function BuybackSharesPopup(props: IProps): React.ReactElement {
+ const [shares, setShares] = useState(null);
+
+ function changeShares(event: React.ChangeEvent): void {
+ if(event.target.value === "") setShares(null);
+ else setShares(Math.round(parseFloat(event.target.value)));
+ }
+
+ const currentStockPrice = props.corp.sharePrice;
+ const buybackPrice = currentStockPrice * 1.1;
+
+ function buy() {
+ if(shares === null) return;
+ const tempStockPrice = props.corp.sharePrice;
+ const buybackPrice = tempStockPrice * 1.1;
+ if (isNaN(shares) || shares <= 0) {
+ dialogBoxCreate("ERROR: Invalid value for number of shares");
+ } else if (shares > props.corp.issuedShares) {
+ dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back");
+ } else if (shares * buybackPrice > props.player.money) {
+ dialogBoxCreate("ERROR: You do not have enough money to purchase this many shares (you need " +
+ numeralWrapper.format(shares * buybackPrice, "$0.000a") + ")");
+ } else {
+ props.corp.numShares += shares;
+ if (isNaN(props.corp.issuedShares)) {
+ console.warn("Corporation issuedShares is NaN: " + props.corp.issuedShares);
+ console.warn("Converting to number now");
+ const res = parseInt(props.corp.issuedShares);
+ if (isNaN(res)) {
+ props.corp.issuedShares = 0;
+ } else {
+ props.corp.issuedShares = res;
+ }
+ }
+ props.corp.issuedShares -= shares;
+ props.player.loseMoney(shares * buybackPrice);
+ removePopup(props.popupId);
+ props.corp.rerender();
+ }
+ }
+
+ function CostIndicator(): React.ReactElement {
+ if(shares === null) return (<>>);
+ if (isNaN(shares) || shares <= 0) {
+ return (<>ERROR: Invalid value entered for number of shares to buyback>);
+ } else if (shares > props.corp.issuedShares) {
+ return (<>There are not this many shares available to buy back.
+ There are only {numeralWrapper.formatBigNumber(props.corp.issuedShares)} outstanding shares.>);
+ } else {
+ return (<>Purchase {shares} shares for a total of {numeralWrapper.formatMoney(shares * buybackPrice)}>);
+ }
+ }
+
+ function onKeyDown(event: React.KeyboardEvent) {
+ if (event.keyCode === 13) buy();
+ }
+
+ return (<>
+
+Enter the number of outstanding shares you would like to buy back.
+These shares must be bought at a 10% premium. However,
+repurchasing shares from the market tends to lead to an increase in stock price.
+To purchase these shares, you must use your own money (NOT your Corporation's funds).
+The current buyback price of your company's stock is {numeralWrapper.formatMoney(buybackPrice)}.
+Your company currently has {numeralWrapper.formatBigNumber(props.corp.issuedShares)} outstanding stock shares.
+
+
+
+
+ Buy shares
+ >);
+}
\ No newline at end of file
diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js
index f56b80a34..3b3013d09 100644
--- a/src/Corporation/ui/CorporationUIEventHandler.js
+++ b/src/Corporation/ui/CorporationUIEventHandler.js
@@ -56,82 +56,6 @@ export class CorporationEventHandler {
this.routing = routing;
}
- // Create a popup that lets the player buyback shares
- // This is created when the player clicks the "Buyback Shares" button in the overview panel
- createBuybackSharesPopup() {
- const popupId = "cmpy-mgmt-buyback-shares-popup";
- const currentStockPrice = this.corp.sharePrice;
- const buybackPrice = currentStockPrice * 1.1;
- const txt = createElement("p", {
- innerHTML: "Enter the number of outstanding shares you would like to buy back. " +
- "These shares must be bought at a 10% premium. However, " +
- "repurchasing shares from the market tends to lead to an increase in stock price. " +
- "To purchase these shares, you must use your own money (NOT your Corporation's funds). " +
- "The current buyback price of your company's stock is " +
- numeralWrapper.format(buybackPrice, "$0.000a") +
- ". Your company currently has " + numeralWrapper.formatBigNumber(this.corp.issuedShares) + " outstanding stock shares",
- });
- var costIndicator = createElement("p", {});
- var input = createElement("input", {
- type:"number", placeholder:"Shares to buyback", margin:"5px",
- inputListener: ()=> {
- var numShares = Math.round(input.value);
- if (isNaN(numShares) || numShares <= 0) {
- costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback"
- } else if (numShares > this.corp.issuedShares) {
- costIndicator.innerText = "There are not this many shares available to buy back. " +
- "There are only " + numeralWrapper.formatBigNumber(this.corp.issuedShares) +
- " outstanding shares.";
- } else {
- costIndicator.innerText = "Purchase " + numShares + " shares for a total of " +
- numeralWrapper.format(numShares * buybackPrice, '$0.000a');
- }
- },
- });
- var confirmBtn = createElement("button", {
- class:"a-link-button", innerText:"Buy shares", display:"inline-block",
- clickListener: () => {
- var shares = Math.round(input.value);
- const tempStockPrice = this.corp.sharePrice;
- const buybackPrice = tempStockPrice * 1.1;
- if (isNaN(shares) || shares <= 0) {
- dialogBoxCreate("ERROR: Invalid value for number of shares");
- } else if (shares > this.corp.issuedShares) {
- dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back");
- } else if (shares * buybackPrice > Player.money) {
- dialogBoxCreate("ERROR: You do not have enough money to purchase this many shares (you need " +
- numeralWrapper.format(shares * buybackPrice, "$0.000a") + ")");
- } else {
- this.corp.numShares += shares;
- if (isNaN(this.corp.issuedShares)) {
- console.warn("Corporation issuedShares is NaN: " + this.corp.issuedShares);
- console.warn("Converting to number now");
- const res = parseInt(this.corp.issuedShares);
- if (isNaN(res)) {
- this.corp.issuedShares = 0;
- } else {
- this.corp.issuedShares = res;
- }
- }
- this.corp.issuedShares -= shares;
- Player.loseMoney(shares * buybackPrice);
- removeElementById(popupId);
- this.rerender();
- }
- return false;
-
- },
- });
- var cancelBtn = createPopupCloseButton(popupId, {
- class: "std-button",
- display: "inline-block",
- innerText: "Cancel",
- });
-
- createPopup(popupId, [txt, costIndicator, input, confirmBtn, cancelBtn]);
- input.focus();
- }
-
// Create a popup that lets the player discontinue a product
createDiscontinueProductPopup(product, industry) {
const popupId = "cmpy-mgmt-discontinue-product-popup";
diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx
index 58b48bc4e..7305d97e8 100644
--- a/src/Corporation/ui/Overview.tsx
+++ b/src/Corporation/ui/Overview.tsx
@@ -4,6 +4,7 @@ import { LevelableUpgrade } from "./LevelableUpgrade";
import { UnlockUpgrade } from "./UnlockUpgrade";
import { BribeFactionPopup } from "./BribeFactionPopup";
import { SellSharesPopup } from "./SellSharesPopup";
+import { BuybackSharesPopup } from "./BuybackSharesPopup";
import { CorporationConstants } from "../data/Constants";
import { CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades";
@@ -227,10 +228,19 @@ export function Overview(props: IProps): React.ReactElement {
tooltip: sellSharesTooltip,
});
+ function openBuybackSharesPopup() {
+ const popupId = "corp-buyback-shares-popup";
+ createPopup(popupId, BuybackSharesPopup, {
+ player: props.player,
+ popupId: popupId,
+ corp: props.corp,
+ });
+ }
+
const buybackSharesBtn = createButton({
class: "std-button",
display: "inline-block",
- onClick: props.eventHandler.createBuybackSharesPopup,
+ onClick: openBuybackSharesPopup,
text: "Buyback shares",
tooltip: "Buy back shares you that previously issued or sold at market price.",
});
From a760ede1299404f8fcd6bd74e4562b82aabdbfe9 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 14:22:36 -0400
Subject: [PATCH 12/30] more conversion
---
.../ui/CorporationUIEventHandler.js | 23 ----------------
.../ui/DiscontinueProductPopup.tsx | 27 +++++++++++++++++++
src/Corporation/ui/IndustryWarehouse.tsx | 17 +++++++++---
src/DevMenu.jsx | 12 +++++++++
4 files changed, 52 insertions(+), 27 deletions(-)
create mode 100644 src/Corporation/ui/DiscontinueProductPopup.tsx
diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js
index 3b3013d09..e95942560 100644
--- a/src/Corporation/ui/CorporationUIEventHandler.js
+++ b/src/Corporation/ui/CorporationUIEventHandler.js
@@ -56,29 +56,6 @@ export class CorporationEventHandler {
this.routing = routing;
}
- // Create a popup that lets the player discontinue a product
- createDiscontinueProductPopup(product, industry) {
- const popupId = "cmpy-mgmt-discontinue-product-popup";
- const txt = createElement("p", {
- innerText:"Are you sure you want to do this? Discontinuing a product " +
- "removes it completely and permanently. You will no longer " +
- "produce this product and all of its existing stock will be " +
- "removed and left unsold",
- });
- const confirmBtn = createElement("button", {
- class:"popup-box-button",innerText:"Discontinue",
- clickListener: () => {
- industry.discontinueProduct(product);
- removeElementById(popupId);
- this.rerender();
- return false;
- },
- });
- const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
-
- createPopup(popupId, [txt, cancelBtn, confirmBtn]);
- }
-
// Create a popup that lets the player manage exports
createExportMaterialPopup(mat) {
const corp = this.corp;
diff --git a/src/Corporation/ui/DiscontinueProductPopup.tsx b/src/Corporation/ui/DiscontinueProductPopup.tsx
new file mode 100644
index 000000000..8c814c8e5
--- /dev/null
+++ b/src/Corporation/ui/DiscontinueProductPopup.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { removePopup } from "../../ui/React/createPopup";
+
+interface IProps {
+ product: any;
+ industry: any;
+ corp: any;
+ popupId: string;
+}
+
+// Create a popup that lets the player discontinue a product
+export function DiscontinueProductPopup(props: IProps): React.ReactElement {
+ function discontinue() {
+ props.industry.discontinueProduct(props.product);
+ removePopup(props.popupId);
+ props.corp.rerender();
+ }
+
+ return (<>
+
+Are you sure you want to do this? Discontinuing a product
+removes it completely and permanently. You will no longer
+produce this product and all of its existing stock will be
+removed and left unsold
+ Discontinue
+ >);
+}
\ No newline at end of file
diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx
index 3a56ddc9c..86f2867f0 100644
--- a/src/Corporation/ui/IndustryWarehouse.tsx
+++ b/src/Corporation/ui/IndustryWarehouse.tsx
@@ -7,9 +7,11 @@ import { OfficeSpace } from "../OfficeSpace";
import { Material } from "../Material";
import { Product } from "../Product";
import { Warehouse } from "../Warehouse";
+import { DiscontinueProductPopup } from "./DiscontinueProductPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createPopup } from "../../ui/React/createPopup";
import { isString } from "../../../utils/helpers/isString";
@@ -71,8 +73,15 @@ function ProductComponent(props: IProductProps) {
}
const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city);
- // Discontinue Button
- const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product, division);
+ function openDiscontinueProductPopup() {
+ const popupId = "cmpy-mgmt-discontinue-product-popup";
+ createPopup(popupId, DiscontinueProductPopup, {
+ product: product,
+ industry: division,
+ corp: props.corp,
+ popupId: popupId,
+ });
+ }
// Market TA button
const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division);
@@ -93,7 +102,7 @@ function ProductComponent(props: IProductProps) {
{limitProductionButtonText}
-
+
Discontinue
{
@@ -173,7 +182,7 @@ function ProductComponent(props: IProductProps) {
{limitProductionButtonText}
-
+
Discontinue
{
diff --git a/src/DevMenu.jsx b/src/DevMenu.jsx
index c158e9f32..5f0e6caea 100644
--- a/src/DevMenu.jsx
+++ b/src/DevMenu.jsx
@@ -584,6 +584,13 @@ class DevMenuComponent extends Component {
}
}
+ finishCorporationProducts() {
+ if(!Player.corporation) return;
+ Player.corporation.divisions.forEach(div => {
+ Object.keys(div.products).forEach(prod => div.products[prod].prog = 99.9)
+ });
+ }
+
specificContract() {
generateContract({
problemType: this.state.codingcontract,
@@ -1169,6 +1176,11 @@ class DevMenuComponent extends Component {
/>
+
+
+ Finish products
+
+
From 21008ba65a3a6d1bd59fe354540dca4f3505a598 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Sat, 28 Aug 2021 14:45:55 -0400
Subject: [PATCH 13/30] more conversion
---
src/Corporation/Corporation.jsx | 17 +------
src/Corporation/Employee.ts | 6 +--
src/Corporation/OfficeSpace.ts | 16 +++---
src/Corporation/ui/BribeFactionPopup.tsx | 8 ++-
src/Corporation/ui/BuybackSharesPopup.tsx | 11 ++--
src/Corporation/ui/CityTabs.tsx | 2 +-
.../ui/CorporationUIEventHandler.js | 11 +---
.../ui/DiscontinueProductPopup.tsx | 2 +-
src/Corporation/ui/HeaderTabs.tsx | 2 +-
src/Corporation/ui/IndustryOffice.tsx | 50 +++++++++----------
src/Corporation/ui/IndustryOverview.tsx | 16 +++---
src/Corporation/ui/IndustryWarehouse.tsx | 17 +++----
src/Corporation/ui/LevelableUpgrade.tsx | 2 +-
src/Corporation/ui/MainPanel.tsx | 4 +-
src/Corporation/ui/Overview.tsx | 18 +++----
src/Corporation/ui/SellSharesPopup.tsx | 9 ++--
src/Corporation/ui/ThrowPartyPopup.tsx | 6 +--
src/Corporation/ui/UnlockUpgrade.tsx | 2 +-
src/Corporation/ui/UpgradeOfficeSizePopup.tsx | 4 +-
19 files changed, 89 insertions(+), 114 deletions(-)
diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx
index 1cebfec66..764aac225 100644
--- a/src/Corporation/Corporation.jsx
+++ b/src/Corporation/Corporation.jsx
@@ -12,7 +12,6 @@ import { MaterialSizes } from "./MaterialSizes";
import { Product } from "./Product";
import { ResearchMap } from "./ResearchMap";
import { Warehouse } from "./Warehouse";
-import { Employee } from "./Employee";
import { OfficeSpace } from "./OfficeSpace";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
@@ -34,21 +33,16 @@ import { appendLineBreaks } from "../../utils/uiHelp
import { createElement } from "../../utils/uiHelpers/createElement";
import { createPopup } from "../../utils/uiHelpers/createPopup";
import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton";
-import { formatNumber, generateRandomString } from "../../utils/StringHelperFunctions";
+import { formatNumber } from "../../utils/StringHelperFunctions";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { isString } from "../../utils/helpers/isString";
import { KEY } from "../../utils/helpers/keyCodes";
import { removeElement } from "../../utils/uiHelpers/removeElement";
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
import { yesNoBoxCreate,
- yesNoTxtInpBoxCreate,
yesNoBoxGetYesButton,
yesNoBoxGetNoButton,
- yesNoTxtInpBoxGetYesButton,
- yesNoTxtInpBoxGetNoButton,
- yesNoTxtInpBoxGetInput,
- yesNoBoxClose,
- yesNoTxtInpBoxClose } from "../../utils/YesNoBox";
+ yesNoBoxClose } from "../../utils/YesNoBox";
// UI Related Imports
import React from "react";
@@ -1455,13 +1449,6 @@ Industry.fromJSON = function(value) {
Reviver.constructors.Industry = Industry;
-var OfficeSpaceTiers = {
- Basic: "Basic",
- Enhanced: "Enhanced",
- Luxurious: "Luxurious",
- Extravagant: "Extravagant",
-}
-
function Corporation(params={}) {
this.name = params.name ? params.name : "The Corporation";
diff --git a/src/Corporation/Employee.ts b/src/Corporation/Employee.ts
index c42d2cf58..b69aae632 100644
--- a/src/Corporation/Employee.ts
+++ b/src/Corporation/Employee.ts
@@ -56,7 +56,7 @@ export class Employee {
}
//Returns the amount the employee needs to be paid
- process(marketCycles = 1, office: any) {
+ process(marketCycles = 1, office: any): number {
const gain = 0.003 * marketCycles,
det = gain * Math.random();
this.exp += gain;
@@ -127,7 +127,7 @@ export class Employee {
}
//Process benefits from having an office party thrown
- throwParty(money: number) {
+ throwParty(money: number): number {
const mult = 1 + (money / 10e6);
this.mor *= mult;
this.mor = Math.min(100, this.mor);
@@ -137,7 +137,7 @@ export class Employee {
}
//'panel' is the DOM element on which to create the UI
- createUI(panel: any, corporation: any, industry: any) {
+ createUI(panel: any, corporation: any, industry: any): void {
const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),
effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),
effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(),
diff --git a/src/Corporation/OfficeSpace.ts b/src/Corporation/OfficeSpace.ts
index 095c5081e..7262ce348 100644
--- a/src/Corporation/OfficeSpace.ts
+++ b/src/Corporation/OfficeSpace.ts
@@ -3,14 +3,10 @@ import { CorporationConstants } from "./data/Constants";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { formatNumber, generateRandomString } from "../../utils/StringHelperFunctions";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
-import { yesNoBoxCreate,
- yesNoTxtInpBoxCreate,
- yesNoBoxGetYesButton,
- yesNoBoxGetNoButton,
+import { yesNoTxtInpBoxCreate,
yesNoTxtInpBoxGetYesButton,
yesNoTxtInpBoxGetNoButton,
yesNoTxtInpBoxGetInput,
- yesNoBoxClose,
yesNoTxtInpBoxClose } from "../../utils/YesNoBox";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { createPopup } from "../../utils/uiHelpers/createPopup";
@@ -126,7 +122,7 @@ export class OfficeSpace {
return salaryPaid;
}
- calculateEmployeeProductivity(parentRefs: any) {
+ calculateEmployeeProductivity(parentRefs: any): void {
const company = parentRefs.corporation, industry = parentRefs.industry;
//Reset
@@ -145,7 +141,7 @@ export class OfficeSpace {
}
//Takes care of UI as well
- findEmployees(parentRefs: any) {
+ findEmployees(parentRefs: any): void {
if (this.atCapacity()) { return; }
if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;}
@@ -191,7 +187,7 @@ export class OfficeSpace {
innerHTML: "Select one of the following candidates for hire:",
});
- const createEmpDiv = function(employee: any, office: any) {
+ function createEmpDiv(employee: any, office: any): HTMLElement {
const div = createElement("div", {
class:"cmpy-mgmt-find-employee-option",
innerHTML: "Intelligence: " + formatNumber(employee.int, 1) + " " +
@@ -207,7 +203,7 @@ export class OfficeSpace {
},
});
return div;
- };
+ }
const cancelBtn = createElement("a", {
class:"a-link-button",
@@ -228,7 +224,7 @@ export class OfficeSpace {
createPopup("cmpy-mgmt-hire-employee-popup", elems);
}
- hireEmployee(employee: Employee, parentRefs: any) {
+ hireEmployee(employee: Employee, parentRefs: any): void {
const company = parentRefs.corporation;
const yesBtn = yesNoTxtInpBoxGetYesButton(),
noBtn = yesNoTxtInpBoxGetNoButton();
diff --git a/src/Corporation/ui/BribeFactionPopup.tsx b/src/Corporation/ui/BribeFactionPopup.tsx
index 62196e077..e50ebfb11 100644
--- a/src/Corporation/ui/BribeFactionPopup.tsx
+++ b/src/Corporation/ui/BribeFactionPopup.tsx
@@ -25,6 +25,10 @@ export function BribeFactionPopup(props: IProps): React.ReactElement {
setStock(parseFloat(event.target.value));
}
+ function changeFaction(event: React.ChangeEvent): void {
+ setSelectedFaction(event.target.value);
+ }
+
function repGain(money: number, stock: number): number {
return (money + (stock * props.corp.sharePrice)) / CorporationConstants.BribeToRepRatio;
}
@@ -45,7 +49,7 @@ export function BribeFactionPopup(props: IProps): React.ReactElement {
}
}
- function bribe(money: number, stock: number) {
+ function bribe(money: number, stock: number): void {
const fac = Factions[selectedFaction];
if (fac == null) {
dialogBoxCreate("ERROR: You must select a faction to bribe");
@@ -66,7 +70,7 @@ export function BribeFactionPopup(props: IProps): React.ReactElement {
return (<>
You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation.
-
+
{
props.player.factions.map((name: string) => {
const info = Factions[name].getInfo();
diff --git a/src/Corporation/ui/BuybackSharesPopup.tsx b/src/Corporation/ui/BuybackSharesPopup.tsx
index 45af985b2..6b45d87ac 100644
--- a/src/Corporation/ui/BuybackSharesPopup.tsx
+++ b/src/Corporation/ui/BuybackSharesPopup.tsx
@@ -3,12 +3,11 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
import { removePopup } from "../../ui/React/createPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
-import { createElement } from "../../../utils/uiHelpers/createElement";
interface IProps {
- player: IPlayer,
- popupId: string,
- corp: any,
+ player: IPlayer;
+ popupId: string;
+ corp: any;
}
// Create a popup that lets the player buyback shares
@@ -24,7 +23,7 @@ export function BuybackSharesPopup(props: IProps): React.ReactElement {
const currentStockPrice = props.corp.sharePrice;
const buybackPrice = currentStockPrice * 1.1;
- function buy() {
+ function buy(): void {
if(shares === null) return;
const tempStockPrice = props.corp.sharePrice;
const buybackPrice = tempStockPrice * 1.1;
@@ -66,7 +65,7 @@ export function BuybackSharesPopup(props: IProps): React.ReactElement {
}
}
- function onKeyDown(event: React.KeyboardEvent) {
+ function onKeyDown(event: React.KeyboardEvent): void {
if (event.keyCode === 13) buy();
}
diff --git a/src/Corporation/ui/CityTabs.tsx b/src/Corporation/ui/CityTabs.tsx
index c774adc1b..6f4e1205b 100644
--- a/src/Corporation/ui/CityTabs.tsx
+++ b/src/Corporation/ui/CityTabs.tsx
@@ -18,7 +18,7 @@ interface IProps {
export function CityTabs(props: IProps): React.ReactElement {
const division = props.routing.currentDivision;
- function openExpandNewCityModal() {
+ function openExpandNewCityModal(): void {
const popupId = "cmpy-mgmt-expand-city-popup";
createPopup(popupId, ExpandNewCityPopup, {
popupId: popupId,
diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js
index e95942560..bfe6d3e53 100644
--- a/src/Corporation/ui/CorporationUIEventHandler.js
+++ b/src/Corporation/ui/CorporationUIEventHandler.js
@@ -4,13 +4,7 @@ import { Corporation,
Industry,
Warehouse,
DividendMaxPercentage,
- IssueNewSharesCooldown,
- OfficeInitialCost,
- OfficeInitialSize,
- SellSharesCooldown,
- WarehouseInitialCost,
- WarehouseInitialSize,
- BribeToRepRatio } from "../Corporation";
+ IssueNewSharesCooldown } from "../Corporation";
import { OfficeSpace } from "../OfficeSpace";
import { Industries,
@@ -22,9 +16,6 @@ import { MaterialSizes } from "../MaterialSizes";
import { Product } from "../Product";
-import { Player } from "../../Player";
-
-import { Factions } from "../../Faction/Factions";
import { Cities } from "../../Locations/Cities";
import { numeralWrapper } from "../../ui/numeralFormat";
diff --git a/src/Corporation/ui/DiscontinueProductPopup.tsx b/src/Corporation/ui/DiscontinueProductPopup.tsx
index 8c814c8e5..d22b139f3 100644
--- a/src/Corporation/ui/DiscontinueProductPopup.tsx
+++ b/src/Corporation/ui/DiscontinueProductPopup.tsx
@@ -10,7 +10,7 @@ interface IProps {
// Create a popup that lets the player discontinue a product
export function DiscontinueProductPopup(props: IProps): React.ReactElement {
- function discontinue() {
+ function discontinue(): void {
props.industry.discontinueProduct(props.product);
removePopup(props.popupId);
props.corp.rerender();
diff --git a/src/Corporation/ui/HeaderTabs.tsx b/src/Corporation/ui/HeaderTabs.tsx
index 119d19916..ec5dd8ab9 100644
--- a/src/Corporation/ui/HeaderTabs.tsx
+++ b/src/Corporation/ui/HeaderTabs.tsx
@@ -12,7 +12,7 @@ interface IProps {
}
export function HeaderTabs(props: IProps): React.ReactElement {
- function overviewOnClick() {
+ function overviewOnClick(): void {
props.routing.routeToOverviewPage();
props.corp.rerender();
}
diff --git a/src/Corporation/ui/IndustryOffice.tsx b/src/Corporation/ui/IndustryOffice.tsx
index c2b2438b8..a71b12096 100644
--- a/src/Corporation/ui/IndustryOffice.tsx
+++ b/src/Corporation/ui/IndustryOffice.tsx
@@ -34,7 +34,7 @@ export function IndustryOffice(props: IProps): React.ReactElement {
const [numUnassigned, setNumUnassigned] = useState(0);
const [numTraining, setNumTraining] = useState(0);
- function resetEmployeeCount() {
+ function resetEmployeeCount(): void {
setNumEmployees(0);
setNumOperations(0);
setNumEngineers(0);
@@ -45,7 +45,7 @@ export function IndustryOffice(props: IProps): React.ReactElement {
setNumTraining(0);
}
- function updateEmployeeCount() {
+ function updateEmployeeCount(): void {
const division = props.routing.currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
@@ -116,7 +116,7 @@ export function IndustryOffice(props: IProps): React.ReactElement {
updateEmployeeCount();
// Renders the "Employee Management" section of the Office UI
- function renderEmployeeManagement() {
+ function renderEmployeeManagement(): React.ReactElement {
updateEmployeeCount();
if (employeeManualAssignMode) {
@@ -126,12 +126,12 @@ export function IndustryOffice(props: IProps): React.ReactElement {
}
}
- function renderAutomaticEmployeeManagement() {
+ function renderAutomaticEmployeeManagement(): React.ReactElement {
const division = props.routing.currentDivision; // Validated in constructor
const office = division.offices[props.currentCity]; // Validated in constructor
const vechain = (props.corp.unlockUpgrades[4] === 1); // Has Vechain upgrade
- const switchModeOnClick = () => {
+ function switchModeOnClick(): void {
setEmployeeManualAssignMode(true);
props.corp.rerender();
}
@@ -154,7 +154,7 @@ export function IndustryOffice(props: IProps): React.ReactElement {
}
// Helper functions for (re-)assigning employees to different positions
- function assignEmployee(to: string) {
+ function assignEmployee(to: string): void {
if (numUnassigned <= 0) {
console.warn("Cannot assign employee. No unassigned employees available");
return;
@@ -193,8 +193,8 @@ export function IndustryOffice(props: IProps): React.ReactElement {
props.corp.rerender();
}
- function unassignEmployee(from: string) {
- function logWarning(pos: string) {
+ function unassignEmployee(from: string): void {
+ function logWarning(pos: string): void {
console.warn(`Cannot unassign from ${pos} because there is nobody assigned to that position`);
}
@@ -244,61 +244,61 @@ export function IndustryOffice(props: IProps): React.ReactElement {
}
const assignButtonClass = numUnassigned > 0 ? "std-button" : "a-link-button-inactive";
- const operationAssignButtonOnClick = () => {
+ function operationAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Operations);
props.corp.rerender();
}
- const operationUnassignButtonOnClick = () => {
+ function operationUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Operations);
props.corp.rerender();
}
const operationUnassignButtonClass = numOperations > 0 ? "std-button" : "a-link-button-inactive";
- const engineerAssignButtonOnClick = () => {
+ function engineerAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Engineer);
props.corp.rerender();
}
- const engineerUnassignButtonOnClick = () => {
+ function engineerUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Engineer);
props.corp.rerender();
}
const engineerUnassignButtonClass = numEngineers > 0 ? "std-button" : "a-link-button-inactive";
- const businessAssignButtonOnClick = () => {
+ function businessAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Business);
props.corp.rerender();
}
- const businessUnassignButtonOnClick = () => {
+ function businessUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Business);
props.corp.rerender();
}
const businessUnassignButtonClass = numBusiness > 0 ? "std-button" : "a-link-button-inactive";
- const managementAssignButtonOnClick = () => {
+ function managementAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Management);
props.corp.rerender();
}
- const managementUnassignButtonOnClick = () => {
+ function managementUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Management);
props.corp.rerender();
}
const managementUnassignButtonClass = numManagement > 0 ? "std-button" : "a-link-button-inactive";
- const rndAssignButtonOnClick = () => {
+ function rndAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.RandD);
props.corp.rerender();
}
- const rndUnassignButtonOnClick = () => {
+ function rndUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.RandD);
props.corp.rerender();
}
const rndUnassignButtonClass = numResearch > 0 ? "std-button" : "a-link-button-inactive";
- const trainingAssignButtonOnClick = () => {
+ function trainingAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Training);
props.corp.rerender();
}
- const trainingUnassignButtonOnClick = () => {
+ function trainingUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Training);
props.corp.rerender();
}
@@ -427,12 +427,12 @@ export function IndustryOffice(props: IProps): React.ReactElement {
)
}
- function renderManualEmployeeManagement() {
+ function renderManualEmployeeManagement(): React.ReactElement {
const corp = props.corp;
const division = props.routing.currentDivision; // Validated in constructor
const office = division.offices[props.currentCity]; // Validated in constructor
- const switchModeOnClick = () => {
+ function switchModeOnClick(): void {
setEmployeeManualAssignMode(false);
props.corp.rerender();
}
@@ -449,7 +449,7 @@ export function IndustryOffice(props: IProps): React.ReactElement {
employees.push({office.employees[i].name} )
}
- const employeeSelectorOnChange = (e: React.ChangeEvent) => {
+ function employeeSelectorOnChange(e: React.ChangeEvent): void {
const name = getSelectText(e.target);
for (let i = 0; i < office.employees.length; ++i) {
if (name === office.employees[i].name) {
@@ -473,7 +473,7 @@ export function IndustryOffice(props: IProps): React.ReactElement {
}
}
- function employeePositionSelectorOnChange(e: React.ChangeEvent) {
+ function employeePositionSelectorOnChange(e: React.ChangeEvent): void {
if(employee === null) return;
const pos = getSelectText(e.target);
employee.pos = pos;
@@ -557,7 +557,7 @@ export function IndustryOffice(props: IProps): React.ReactElement {
}
}
- const hireEmployeeButtonOnClick = () => {
+ function hireEmployeeButtonOnClick(): void {
office.findEmployees({ corporation: corp, industry: division });
}
diff --git a/src/Corporation/ui/IndustryOverview.tsx b/src/Corporation/ui/IndustryOverview.tsx
index 2cc4d2b62..5e900e652 100644
--- a/src/Corporation/ui/IndustryOverview.tsx
+++ b/src/Corporation/ui/IndustryOverview.tsx
@@ -17,7 +17,7 @@ interface IProps {
}
export function IndustryOverview(props: IProps): React.ReactElement {
- function renderMakeProductButton() {
+ function renderMakeProductButton(): React.ReactElement {
const division = props.routing.currentDivision; // Validated inside render()
let createProductButtonText = "";
@@ -59,7 +59,7 @@ export function IndustryOverview(props: IProps): React.ReactElement {
default:
createProductButtonText = "Create Product";
createProductPopupText = "Create a new product!";
- return "";
+ return (<>>);
}
createProductPopupText += " To begin developing a product, " +
"first choose the city in which it will be designed. The stats of your employees " +
@@ -90,7 +90,7 @@ export function IndustryOverview(props: IProps): React.ReactElement {
)
}
- function renderText() {
+ function renderText(): React.ReactElement {
const corp = props.corp;
const division = props.routing.currentDivision; // Validated inside render()
@@ -113,11 +113,11 @@ export function IndustryOverview(props: IProps): React.ReactElement {
const expenses = `Expenses: ${numeralWrapper.formatMoney(division.lastCycleExpenses.toNumber())} /s`;
const profitStr = `Profit: ${numeralWrapper.formatMoney(profit)} / s`;
- const productionMultHelpTipOnClick = () => {
+ function productionMultHelpTipOnClick(): void {
// Wrapper for createProgressBarText()
// Converts the industry's "effectiveness factors"
// into a graphic (string) depicting how high that effectiveness is
- function convertEffectFacToGraphic(fac: number) {
+ function convertEffectFacToGraphic(fac: number): string {
return createProgressBarText({
progress: fac,
totalTicks: 20,
@@ -191,7 +191,7 @@ export function IndustryOverview(props: IProps): React.ReactElement {
)
}
- function renderUpgrades() {
+ function renderUpgrades(): React.ReactElement[] {
const corp = props.corp;
const division = props.routing.currentDivision; // Validated inside render()
const office = division.offices[props.currentCity];
@@ -219,7 +219,7 @@ export function IndustryOverview(props: IProps): React.ReactElement {
break;
}
- const onClick = () => {
+ function onClick(): void {
if (corp.funds.lt(cost)) {
dialogBoxCreate("Insufficient funds");
} else {
@@ -243,7 +243,7 @@ export function IndustryOverview(props: IProps): React.ReactElement {
return upgrades;
}
- function renderUpgrade(props: any) {
+ function renderUpgrade(props: any): React.ReactElement {
return (
{props.text}
diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx
index 86f2867f0..dfc1ed454 100644
--- a/src/Corporation/ui/IndustryWarehouse.tsx
+++ b/src/Corporation/ui/IndustryWarehouse.tsx
@@ -24,7 +24,7 @@ interface IProductProps {
}
// Creates the UI for a single Product type
-function ProductComponent(props: IProductProps) {
+function ProductComponent(props: IProductProps): React.ReactElement {
const corp = props.corp;
const division = props.division;
const city = props.city;
@@ -73,7 +73,7 @@ function ProductComponent(props: IProductProps) {
}
const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city);
- function openDiscontinueProductPopup() {
+ function openDiscontinueProductPopup(): void {
const popupId = "cmpy-mgmt-discontinue-product-popup";
createPopup(popupId, DiscontinueProductPopup, {
product: product,
@@ -206,7 +206,7 @@ interface IMaterialProps {
}
// Creates the UI for a single Material type
-function MaterialComponent(props: IMaterialProps) {
+function MaterialComponent(props: IMaterialProps): React.ReactElement {
const corp = props.corp;
const division = props.division;
const warehouse = props.warehouse;
@@ -351,7 +351,7 @@ interface IProps {
eventHandler: any;
}
-export function IndustryWarehouse(props: IProps) {
+export function IndustryWarehouse(props: IProps): React.ReactElement {
// Returns a boolean indicating whether the given material is relevant for the
// current industry.
function isRelevantMaterial(matName: string, division: any): boolean {
@@ -365,7 +365,7 @@ export function IndustryWarehouse(props: IProps) {
return false;
}
- function renderWarehouseUI() {
+ function renderWarehouseUI(): React.ReactElement {
const corp = props.corp;
const division = props.routing.currentDivision; // Validated in render()
const warehouse = division.warehouses[props.currentCity]; // Validated in render()
@@ -380,12 +380,11 @@ export function IndustryWarehouse(props: IProps) {
const sizeUpgradeCost = CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
const canAffordUpgrade = (corp.funds.gt(sizeUpgradeCost));
const upgradeWarehouseClass = canAffordUpgrade ? "std-button" : "a-link-button-inactive";
- const upgradeWarehouseOnClick = () => {
+ function upgradeWarehouseOnClick(): void {
++warehouse.level;
warehouse.updateSize(corp, division);
corp.funds = corp.funds.minus(sizeUpgradeCost);
corp.rerender();
- return;
}
// Industry material Requirements
@@ -447,7 +446,7 @@ export function IndustryWarehouse(props: IProps) {
// Smart Supply Checkbox
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
- const smartSupplyOnChange = (e: React.ChangeEvent
) => {
+ function smartSupplyOnChange(e: React.ChangeEvent): void {
warehouse.smartSupplyEnabled = e.target.checked;
corp.rerender();
}
@@ -537,7 +536,7 @@ export function IndustryWarehouse(props: IProps) {
}
const warehouse = division.warehouses[props.currentCity];
- function purchaseWarehouse(division: any, city: string) {
+ function purchaseWarehouse(division: any, city: string): void {
if (props.corp.funds.lt(CorporationConstants.WarehouseInitialCost)) {
dialogBoxCreate("You do not have enough funds to do this!");
} else {
diff --git a/src/Corporation/ui/LevelableUpgrade.tsx b/src/Corporation/ui/LevelableUpgrade.tsx
index 8b8c69d8a..b5b960f80 100644
--- a/src/Corporation/ui/LevelableUpgrade.tsx
+++ b/src/Corporation/ui/LevelableUpgrade.tsx
@@ -20,7 +20,7 @@ export function LevelableUpgrade(props: IProps): React.ReactElement {
const text = `${data[4]} - ${numeralWrapper.formatMoney(cost)}`
const tooltip = data[5];
- function onClick() {
+ function onClick(): void {
const corp = props.corp;
if (corp.funds.lt(cost)) {
dialogBoxCreate("Insufficient funds");
diff --git a/src/Corporation/ui/MainPanel.tsx b/src/Corporation/ui/MainPanel.tsx
index 434d812d1..e01d846d1 100644
--- a/src/Corporation/ui/MainPanel.tsx
+++ b/src/Corporation/ui/MainPanel.tsx
@@ -32,7 +32,7 @@ export function MainPanel(props: IProps): React.ReactElement {
}
}
- function renderOverviewPage() {
+ function renderOverviewPage(): React.ReactElement {
return (
@@ -40,7 +40,7 @@ export function MainPanel(props: IProps): React.ReactElement {
)
}
- function renderDivisionPage() {
+ function renderDivisionPage(): React.ReactElement {
// Note: Division is the same thing as Industry...I wasn't consistent with naming
const division = props.routing.currentDivision;
if (division == null) {
diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx
index 7305d97e8..5972cd6a4 100644
--- a/src/Corporation/ui/Overview.tsx
+++ b/src/Corporation/ui/Overview.tsx
@@ -24,7 +24,7 @@ interface IProps {
export function Overview(props: IProps): React.ReactElement {
// Generic Function for Creating a button
- function createButton(props: any) {
+ function createButton(props: any): React.ReactElement {
let className = props.class ? props.class : "std-button";
const displayStyle = props.display ? props.display : "block";
const hasTooltip = (props.tooltip != null);
@@ -46,7 +46,7 @@ export function Overview(props: IProps): React.ReactElement {
}
// Returns a string with general information about Corporation
- function getOverviewText() {
+ function getOverviewText(): string {
// Formatted text for profit
const profit = props.corp.revenue.minus(props.corp.expenses).toNumber(),
profitStr = profit >= 0 ? numeralWrapper.formatMoney(profit) : "-" + numeralWrapper.format(-1 * profit, "$0.000a");
@@ -110,7 +110,7 @@ export function Overview(props: IProps): React.ReactElement {
// Render the buttons that lie below the overview text.
// These are mainly for things such as managing finances/stock
- function renderButtons() {
+ function renderButtons(): React.ReactElement {
// Create a "Getting Started Guide" button that lets player view the
// handbook and adds it to the players home computer
const getStarterGuideOnClick = props.corp.getStarterGuide.bind(props.corp);
@@ -124,7 +124,7 @@ export function Overview(props: IProps): React.ReactElement {
"provides some tips/pointers for helping you get started with managing it.",
});
- function openBribeFactionPopup() {
+ function openBribeFactionPopup(): void {
const popupId = "corp-bribe-popup";
createPopup(popupId, BribeFactionPopup, {
player: props.player,
@@ -162,7 +162,7 @@ export function Overview(props: IProps): React.ReactElement {
// Render the buttons for when your Corporation is still private
- function renderPrivateButtons(generalBtns: any) {
+ function renderPrivateButtons(generalBtns: any): React.ReactElement {
const fundingAvailable = (props.corp.fundingRound < 4);
const findInvestorsClassName = fundingAvailable ? "std-button" : "a-link-button-inactive";
const findInvestorsTooltip = fundingAvailable ? "Search for private investors who will give you startup funding in exchangefor equity (stock shares) in your company" : null;
@@ -202,7 +202,7 @@ export function Overview(props: IProps): React.ReactElement {
}
// Render the buttons for when your Corporation has gone public
- function renderPublicButtons(generalBtns: any) {
+ function renderPublicButtons(generalBtns: any): React.ReactElement {
const corp = props.corp;
const sellSharesOnCd = (corp.shareSaleCooldown > 0);
@@ -228,7 +228,7 @@ export function Overview(props: IProps): React.ReactElement {
tooltip: sellSharesTooltip,
});
- function openBuybackSharesPopup() {
+ function openBuybackSharesPopup(): void {
const popupId = "corp-buyback-shares-popup";
createPopup(popupId, BuybackSharesPopup, {
player: props.player,
@@ -281,9 +281,9 @@ export function Overview(props: IProps): React.ReactElement {
}
// Render the UI for Corporation upgrades
- function renderUpgrades() {
+ function renderUpgrades(): React.ReactElement {
// Don't show upgrades
- if (props.corp.divisions.length <= 0) { return; }
+ if (props.corp.divisions.length <= 0) { return (<>>); }
// Create an array of all Unlocks
const unlockUpgrades: React.ReactElement[] = [];
diff --git a/src/Corporation/ui/SellSharesPopup.tsx b/src/Corporation/ui/SellSharesPopup.tsx
index 385074223..10d075db1 100644
--- a/src/Corporation/ui/SellSharesPopup.tsx
+++ b/src/Corporation/ui/SellSharesPopup.tsx
@@ -4,7 +4,6 @@ import { dialogBoxCreate } from "../../../utils/DialogBox";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { removePopup } from "../../ui/React/createPopup";
import { CorporationConstants } from "../data/Constants";
-import { createElement } from "../../../utils/uiHelpers/createElement";
interface IProps {
corp: any;
@@ -22,7 +21,7 @@ export function SellSharesPopup(props: IProps): React.ReactElement {
else setShares(Math.round(parseFloat(event.target.value)));
}
- function ProfitIndicator(props: {shares: number | null, corp: any}): React.ReactElement {
+ function ProfitIndicator(props: {shares: number | null; corp: any}): React.ReactElement {
if(props.shares === null) return (<>>);
if (isNaN(props.shares) || props.shares <= 0) {
return (<>ERROR: Invalid value entered for number of shares to sell>);
@@ -35,7 +34,7 @@ export function SellSharesPopup(props: IProps): React.ReactElement {
}
}
- function sell() {
+ function sell(): void {
if(shares === null) return;
if (isNaN(shares) || shares <= 0) {
dialogBoxCreate("ERROR: Invalid value for number of shares");
@@ -50,7 +49,7 @@ export function SellSharesPopup(props: IProps): React.ReactElement {
props.corp.numShares -= shares;
if (isNaN(props.corp.issuedShares)) {
console.error(`Corporation issuedShares is NaN: ${props.corp.issuedShares}`);
- var res = parseInt(props.corp.issuedShares);
+ const res = parseInt(props.corp.issuedShares);
if (isNaN(res)) {
props.corp.issuedShares = 0;
} else {
@@ -74,7 +73,7 @@ export function SellSharesPopup(props: IProps): React.ReactElement {
}
- function onKeyDown(event: React.KeyboardEvent
) {
+ function onKeyDown(event: React.KeyboardEvent): void {
if (event.keyCode === 13) sell();
}
diff --git a/src/Corporation/ui/ThrowPartyPopup.tsx b/src/Corporation/ui/ThrowPartyPopup.tsx
index ff267fa38..ee25fa830 100644
--- a/src/Corporation/ui/ThrowPartyPopup.tsx
+++ b/src/Corporation/ui/ThrowPartyPopup.tsx
@@ -17,7 +17,7 @@ export function ThrowPartyPopup(props: IProps): React.ReactElement {
setCost(parseFloat(event.target.value));
}
- function throwParty() {
+ function throwParty(): void {
if (cost === null || isNaN(cost) || cost < 0) {
dialogBoxCreate("Invalid value entered");
} else {
@@ -37,14 +37,14 @@ export function ThrowPartyPopup(props: IProps): React.ReactElement {
}
}
- function EffectText(props: {cost: number | null, office: IOfficeSpace}): React.ReactElement {
+ function EffectText(props: {cost: number | null; office: IOfficeSpace}): React.ReactElement {
let cost = props.cost;
if(cost !== null && (isNaN(cost) || cost < 0)) return Invalid value entered!
if(cost === null) cost = 0;
return Throwing this party will cost a total of {numeralWrapper.formatMoney(cost * props.office.employees.length)}
}
- function onKeyDown(event: React.KeyboardEvent) {
+ function onKeyDown(event: React.KeyboardEvent): void {
if (event.keyCode === 13) throwParty();
}
diff --git a/src/Corporation/ui/UnlockUpgrade.tsx b/src/Corporation/ui/UnlockUpgrade.tsx
index beb7965bf..77bb92873 100644
--- a/src/Corporation/ui/UnlockUpgrade.tsx
+++ b/src/Corporation/ui/UnlockUpgrade.tsx
@@ -13,7 +13,7 @@ export function UnlockUpgrade(props: IProps): React.ReactElement {
const data = props.upgradeData;
const text = `${data[2]} - ${numeralWrapper.formatMoney(data[1])}`;
const tooltip = data[3];
- function onClick() {
+ function onClick(): void {
const corp = props.corp;
if (corp.funds.lt(data[1])) {
dialogBoxCreate("Insufficient funds");
diff --git a/src/Corporation/ui/UpgradeOfficeSizePopup.tsx b/src/Corporation/ui/UpgradeOfficeSizePopup.tsx
index b40cf75dd..087548a92 100644
--- a/src/Corporation/ui/UpgradeOfficeSizePopup.tsx
+++ b/src/Corporation/ui/UpgradeOfficeSizePopup.tsx
@@ -24,12 +24,12 @@ export function UpgradeOfficeSizePopup(props: IProps): React.ReactElement {
const upgradeCost15 = CorporationConstants.OfficeInitialCost * mult;
//Calculate max upgrade size and cost
- let maxMult = (props.corp.funds.dividedBy(CorporationConstants.OfficeInitialCost)).toNumber();
+ const maxMult = (props.corp.funds.dividedBy(CorporationConstants.OfficeInitialCost)).toNumber();
let maxNum = 1;
mult = Math.pow(costMultiplier, initialPriceMult);
while(maxNum < 50) { //Hard cap of 50x (extra 150 employees)
if (mult >= maxMult) break;
- let multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum);
+ const multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum);
if (mult + multIncrease > maxMult) {
break;
} else {
From a72d1aa99fbf9ff32eee308b4421a28aa8f72a52 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Mon, 30 Aug 2021 03:07:14 -0400
Subject: [PATCH 14/30] more conversion
---
src/Corporation/Corporation.d.ts | 3 +
src/Corporation/IndustryData.ts | 140 ----
src/Corporation/IndustryData.tsx | 143 ++++
.../ui/CorporationUIEventHandler.js | 660 ------------------
src/Corporation/ui/ExportPopup.tsx | 120 ++++
src/Corporation/ui/HeaderTabs.tsx | 13 +-
src/Corporation/ui/IndustryOverview.tsx | 14 +-
src/Corporation/ui/IndustryWarehouse.tsx | 57 +-
src/Corporation/ui/IssueDividendsPopup.tsx | 57 ++
src/Corporation/ui/IssueNewSharesPopup.tsx | 134 ++++
.../ui/LimitProductProductionPopup.tsx | 59 ++
src/Corporation/ui/MakeProductPopup.tsx | 107 +++
src/Corporation/ui/MaterialMarketTaPopup.tsx | 121 ++++
src/Corporation/ui/NewIndustryPopup.tsx | 89 +++
src/Corporation/ui/Overview.tsx | 22 +-
src/DevMenu.jsx | 12 +
16 files changed, 934 insertions(+), 817 deletions(-)
create mode 100644 src/Corporation/Corporation.d.ts
delete mode 100644 src/Corporation/IndustryData.ts
create mode 100644 src/Corporation/IndustryData.tsx
create mode 100644 src/Corporation/ui/ExportPopup.tsx
create mode 100644 src/Corporation/ui/IssueDividendsPopup.tsx
create mode 100644 src/Corporation/ui/IssueNewSharesPopup.tsx
create mode 100644 src/Corporation/ui/LimitProductProductionPopup.tsx
create mode 100644 src/Corporation/ui/MakeProductPopup.tsx
create mode 100644 src/Corporation/ui/MaterialMarketTaPopup.tsx
create mode 100644 src/Corporation/ui/NewIndustryPopup.tsx
diff --git a/src/Corporation/Corporation.d.ts b/src/Corporation/Corporation.d.ts
new file mode 100644
index 000000000..36b7c3f3b
--- /dev/null
+++ b/src/Corporation/Corporation.d.ts
@@ -0,0 +1,3 @@
+export class Industry {
+ constructor(props: any)
+}
\ No newline at end of file
diff --git a/src/Corporation/IndustryData.ts b/src/Corporation/IndustryData.ts
deleted file mode 100644
index a7154026f..000000000
--- a/src/Corporation/IndustryData.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-import { ResearchTree } from "./ResearchTree";
-import { getBaseResearchTreeCopy,
- getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree";
-
-import { numeralWrapper } from "../ui/numeralFormat";
-
-interface IIndustryMap {
- Energy: T;
- Utilities: T;
- Agriculture: T;
- Fishing: T;
- Mining: T;
- Food: T;
- Tobacco: T;
- Chemical: T;
- Pharmaceutical: T;
- Computer: T;
- Robotics: T;
- Software: T;
- Healthcare: T;
- RealEstate: T;
-}
-
-// Map of official names for each Industry
-export const Industries: IIndustryMap = {
- Energy: "Energy",
- Utilities: "Water Utilities",
- Agriculture: "Agriculture",
- Fishing: "Fishing",
- Mining: "Mining",
- Food: "Food",
- Tobacco: "Tobacco",
- Chemical: "Chemical",
- Pharmaceutical: "Pharmaceutical",
- Computer: "Computer Hardware",
- Robotics: "Robotics",
- Software: "Software",
- Healthcare: "Healthcare",
- RealEstate: "RealEstate",
-}
-
-// Map of how much money it takes to start each industry
-export const IndustryStartingCosts: IIndustryMap = {
- Energy: 225e9,
- Utilities: 150e9,
- Agriculture: 40e9,
- Fishing: 80e9,
- Mining: 300e9,
- Food: 10e9,
- Tobacco: 20e9,
- Chemical: 70e9,
- Pharmaceutical: 200e9,
- Computer: 500e9,
- Robotics: 1e12,
- Software: 25e9,
- Healthcare: 750e9,
- RealEstate: 600e9,
-}
-
-// Map of description for each industry
-export const IndustryDescriptions: IIndustryMap = {
- Energy: "Engage in the production and distribution of energy. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Energy, "$0.000a") + " " +
- "Recommended starting Industry: NO",
- Utilities: "Distribute water and provide wastewater services. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Utilities, "$0.000a") + " " +
- "Recommended starting Industry: NO",
- Agriculture: "Cultivate crops and breed livestock to produce food. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Agriculture, "$0.000a") + " " +
- "Recommended starting Industry: YES",
- Fishing: "Produce food through the breeding and processing of fish and fish products. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Fishing, "$0.000a") + " " +
- "Recommended starting Industry: NO",
- Mining: "Extract and process metals from the earth. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Mining, "$0.000a") + " " +
- "Recommended starting Industry: NO",
- Food: "Create your own restaurants all around the world. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Food, "$0.000a") + " " +
- "Recommended starting Industry: YES",
- Tobacco: "Create and distribute tobacco and tobacco-related products. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Tobacco, "$0.000a") + " " +
- "Recommended starting Industry: YES",
- Chemical: "Produce industrial chemicals. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Chemical, "$0.000a") + " " +
- "Recommended starting Industry: NO",
- Pharmaceutical: "Discover, develop, and create new pharmaceutical drugs. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Pharmaceutical, "$0.000a") + " " +
- "Recommended starting Industry: NO",
- Computer: "Develop and manufacture new computer hardware and networking infrastructures. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Computer, "$0.000a") + " " +
- "Recommended starting Industry: NO",
- Robotics: "Develop and create robots. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Robotics, "$0.000a") + " " +
- "Recommended starting Industry: NO",
- Software: "Develop computer software and create AI Cores. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Software, "$0.000a") + " " +
- "Recommended starting Industry: YES",
- Healthcare: "Create and manage hospitals. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Healthcare, "$0.000a") + " " +
- "Recommended starting Industry: NO",
- RealEstate: "Develop and manage real estate properties. " +
- "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.RealEstate, "$0.000a") + " " +
- "Recommended starting Industry: NO",
-}
-
-// Map of available Research for each Industry. This data is held in a
-// ResearchTree object
-export const IndustryResearchTrees: IIndustryMap = {
- Energy: getBaseResearchTreeCopy(),
- Utilities: getBaseResearchTreeCopy(),
- Agriculture: getBaseResearchTreeCopy(),
- Fishing: getBaseResearchTreeCopy(),
- Mining: getBaseResearchTreeCopy(),
- Food: getProductIndustryResearchTreeCopy(),
- Tobacco: getProductIndustryResearchTreeCopy(),
- Chemical: getBaseResearchTreeCopy(),
- Pharmaceutical: getProductIndustryResearchTreeCopy(),
- Computer: getProductIndustryResearchTreeCopy(),
- Robotics: getProductIndustryResearchTreeCopy(),
- Software: getProductIndustryResearchTreeCopy(),
- Healthcare: getProductIndustryResearchTreeCopy(),
- RealEstate: getProductIndustryResearchTreeCopy(),
-}
-
-export function resetIndustryResearchTrees(): void {
- IndustryResearchTrees.Energy = getBaseResearchTreeCopy();
- IndustryResearchTrees.Utilities = getBaseResearchTreeCopy();
- IndustryResearchTrees.Agriculture = getBaseResearchTreeCopy();
- IndustryResearchTrees.Fishing = getBaseResearchTreeCopy();
- IndustryResearchTrees.Mining = getBaseResearchTreeCopy();
- IndustryResearchTrees.Food = getBaseResearchTreeCopy();
- IndustryResearchTrees.Tobacco = getBaseResearchTreeCopy();
- IndustryResearchTrees.Chemical = getBaseResearchTreeCopy();
- IndustryResearchTrees.Pharmaceutical = getBaseResearchTreeCopy();
- IndustryResearchTrees.Computer = getBaseResearchTreeCopy();
- IndustryResearchTrees.Robotics = getBaseResearchTreeCopy();
- IndustryResearchTrees.Software = getBaseResearchTreeCopy();
- IndustryResearchTrees.Healthcare = getBaseResearchTreeCopy();
- IndustryResearchTrees.RealEstate = getBaseResearchTreeCopy();
-}
diff --git a/src/Corporation/IndustryData.tsx b/src/Corporation/IndustryData.tsx
new file mode 100644
index 000000000..250a53be4
--- /dev/null
+++ b/src/Corporation/IndustryData.tsx
@@ -0,0 +1,143 @@
+import React from 'react';
+import { ResearchTree } from "./ResearchTree";
+import { getBaseResearchTreeCopy,
+ getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree";
+
+import { numeralWrapper } from "../ui/numeralFormat";
+import { Money } from "../ui/React/Money";
+
+interface IIndustryMap {
+ [key: string]: T | undefined;
+ Energy: T;
+ Utilities: T;
+ Agriculture: T;
+ Fishing: T;
+ Mining: T;
+ Food: T;
+ Tobacco: T;
+ Chemical: T;
+ Pharmaceutical: T;
+ Computer: T;
+ Robotics: T;
+ Software: T;
+ Healthcare: T;
+ RealEstate: T;
+}
+
+// Map of official names for each Industry
+export const Industries: IIndustryMap = {
+ Energy: "Energy",
+ Utilities: "Water Utilities",
+ Agriculture: "Agriculture",
+ Fishing: "Fishing",
+ Mining: "Mining",
+ Food: "Food",
+ Tobacco: "Tobacco",
+ Chemical: "Chemical",
+ Pharmaceutical: "Pharmaceutical",
+ Computer: "Computer Hardware",
+ Robotics: "Robotics",
+ Software: "Software",
+ Healthcare: "Healthcare",
+ RealEstate: "RealEstate",
+}
+
+// Map of how much money it takes to start each industry
+export const IndustryStartingCosts: IIndustryMap = {
+ Energy: 225e9,
+ Utilities: 150e9,
+ Agriculture: 40e9,
+ Fishing: 80e9,
+ Mining: 300e9,
+ Food: 10e9,
+ Tobacco: 20e9,
+ Chemical: 70e9,
+ Pharmaceutical: 200e9,
+ Computer: 500e9,
+ Robotics: 1e12,
+ Software: 25e9,
+ Healthcare: 750e9,
+ RealEstate: 600e9,
+}
+
+// Map of description for each industry
+export const IndustryDescriptions: IIndustryMap = {
+ Energy: (<>Engage in the production and distribution of energy.
+ Starting cost: {Money(IndustryStartingCosts.Energy)}
+ Recommended starting Industry: NO>),
+ Utilities: (<>Distribute water and provide wastewater services.
+ Starting cost: {Money(IndustryStartingCosts.Utilities)}
+ Recommended starting Industry: NO>),
+ Agriculture: (<>Cultivate crops and breed livestock to produce food.
+ Starting cost: {Money(IndustryStartingCosts.Agriculture)}
+ Recommended starting Industry: YES>),
+ Fishing: (<>Produce food through the breeding and processing of fish and fish products.
+ Starting cost: {Money(IndustryStartingCosts.Fishing)}
+ Recommended starting Industry: NO>),
+ Mining: (<>Extract and process metals from the earth.
+ Starting cost: {Money(IndustryStartingCosts.Mining)}
+ Recommended starting Industry: NO>),
+ Food: (<>Create your own restaurants all around the world.
+ Starting cost: {Money(IndustryStartingCosts.Food)}
+ Recommended starting Industry: YES>),
+ Tobacco: (<>Create and distribute tobacco and tobacco-related products.
+ Starting cost: {Money(IndustryStartingCosts.Tobacco)}
+ Recommended starting Industry: YES>),
+ Chemical: (<>Produce industrial chemicals.
+ Starting cost: {Money(IndustryStartingCosts.Chemical)}
+ Recommended starting Industry: NO>),
+ Pharmaceutical: (<>Discover, develop, and create new pharmaceutical drugs.
+ Starting cost: {Money(IndustryStartingCosts.Pharmaceutical)}
+ Recommended starting Industry: NO>),
+ Computer: (<>Develop and manufacture new computer hardware and networking infrastructures.
+ Starting cost: {Money(IndustryStartingCosts.Computer)}
+ Recommended starting Industry: NO>),
+ Robotics: (<>Develop and create robots.
+ Starting cost: {Money(IndustryStartingCosts.Robotics)}
+ Recommended starting Industry: NO>),
+ Software: (<>Develop computer software and create AI Cores.
+ Starting cost: {Money(IndustryStartingCosts.Software)}
+ Recommended starting Industry: YES>),
+ Healthcare: (<>Create and manage hospitals.
+ Starting cost: {Money(IndustryStartingCosts.Healthcare)}
+ Recommended starting Industry: NO>),
+ RealEstate: (<>Develop and manage real estate properties.
+ Starting cost: {Money(IndustryStartingCosts.RealEstate)}
+ Recommended starting Industry: NO>),
+}
+
+// Map of available Research for each Industry. This data is held in a
+// ResearchTree object
+export const IndustryResearchTrees: IIndustryMap = {
+ Energy: getBaseResearchTreeCopy(),
+ Utilities: getBaseResearchTreeCopy(),
+ Agriculture: getBaseResearchTreeCopy(),
+ Fishing: getBaseResearchTreeCopy(),
+ Mining: getBaseResearchTreeCopy(),
+ Food: getProductIndustryResearchTreeCopy(),
+ Tobacco: getProductIndustryResearchTreeCopy(),
+ Chemical: getBaseResearchTreeCopy(),
+ Pharmaceutical: getProductIndustryResearchTreeCopy(),
+ Computer: getProductIndustryResearchTreeCopy(),
+ Robotics: getProductIndustryResearchTreeCopy(),
+ Software: getProductIndustryResearchTreeCopy(),
+ Healthcare: getProductIndustryResearchTreeCopy(),
+ RealEstate: getProductIndustryResearchTreeCopy(),
+}
+
+export function resetIndustryResearchTrees(): void {
+ IndustryResearchTrees.Energy = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Utilities = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Agriculture = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Fishing = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Mining = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Food = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Tobacco = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Chemical = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Pharmaceutical = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Computer = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Robotics = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Software = getBaseResearchTreeCopy();
+ IndustryResearchTrees.Healthcare = getBaseResearchTreeCopy();
+ IndustryResearchTrees.RealEstate = getBaseResearchTreeCopy();
+}
diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js
index bfe6d3e53..3f586d8cf 100644
--- a/src/Corporation/ui/CorporationUIEventHandler.js
+++ b/src/Corporation/ui/CorporationUIEventHandler.js
@@ -47,666 +47,6 @@ export class CorporationEventHandler {
this.routing = routing;
}
- // Create a popup that lets the player manage exports
- createExportMaterialPopup(mat) {
- const corp = this.corp;
-
- const popupId = "cmpy-mgmt-export-popup";
- const exportTxt = createElement("p", {
- innerText:"Select the industry and city to export this material to, as well as " +
- "how much of this material to export per second. You can set the export " +
- "amount to 'MAX' to export all of the materials in this warehouse.",
- });
-
- //Select industry and city to export to
- const citySelector = createElement("select", {class: "dropdown"});
- const industrySelector = createElement("select", {
- class: "dropdown",
- changeListener: () => {
- const industryName = getSelectValue(industrySelector);
- for (let i = 0; i < corp.divisions.length; ++i) {
- if (corp.divisions[i].name == industryName) {
- clearSelector(citySelector);
- for (const cityName in corp.divisions[i].warehouses) {
- if (corp.divisions[i].warehouses[cityName] instanceof Warehouse) {
- citySelector.add(createElement("option", {
- value:cityName, text:cityName,
- }));
- }
- }
- return;
- }
- }
- },
- });
-
- for (let i = 0; i < corp.divisions.length; ++i) {
- industrySelector.add(createOptionElement(corp.divisions[i].name));
- }
-
- // Force change listener to initialize citySelector
- industrySelector.dispatchEvent(new Event("change"));
-
- //Select amount to export
- const exportAmount = createElement("input", {
- class: "text-input",
- placeholder:"Export amount / s",
- });
-
- const exportBtn = createElement("button", {
- class: "std-button", display:"inline-block", innerText:"Export",
- clickListener: () => {
- const industryName = getSelectText(industrySelector);
- const cityName = citySelector.options[citySelector.selectedIndex].text;
- const amt = exportAmount.value;
-
- // Sanitize amt
- let sanitizedAmt = amt.replace(/\s+/g, '');
- sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, '');
- let temp = sanitizedAmt.replace(/MAX/g, 1);
- try {
- temp = eval(temp);
- } catch(e) {
- dialogBoxCreate("Invalid expression entered for export amount: " + e);
- return false;
- }
-
- if (temp == null || isNaN(temp) || temp < 0) {
- dialogBoxCreate("Invalid amount entered for export");
- return;
- }
- var exportObj = {ind:industryName, city:cityName, amt:sanitizedAmt};
- mat.exp.push(exportObj);
- removeElementById(popupId);
- return false;
- },
- });
-
- const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
-
- const currExportsText = createElement("p", {
- innerText:"Below is a list of all current exports of this material from this warehouse. " +
- "Clicking on one of the exports below will REMOVE that export.",
- });
- const currExports = [];
- for (var i = 0; i < mat.exp.length; ++i) {
- (function(i, mat, currExports){
- currExports.push(createElement("div", {
- class:"cmpy-mgmt-existing-export",
- innerHTML: "Industry: " + mat.exp[i].ind + " " +
- "City: " + mat.exp[i].city + " " +
- "Amount/s: " + mat.exp[i].amt,
- clickListener:()=>{
- mat.exp.splice(i, 1); //Remove export object
- removeElementById(popupId);
- createExportMaterialPopup(mat);
- },
- }));
- })(i, mat, currExports);
- }
- createPopup(popupId, [exportTxt, industrySelector, citySelector, exportAmount,
- exportBtn, cancelBtn, currExportsText].concat(currExports));
- }
-
- // Create a popup that lets the player issue & manage dividends
- // This is created when the player clicks the "Issue Dividends" button in the overview panel
- createIssueDividendsPopup() {
- const popupId = "cmpy-mgmt-issue-dividends-popup";
- const descText = "Dividends are a distribution of a portion of the corporation's " +
- "profits to the shareholders. This includes yourself, as well. " +
- "In order to issue dividends, simply allocate some percentage " +
- "of your corporation's profits to dividends. This percentage must be an " +
- `integer between 0 and ${DividendMaxPercentage}. (A percentage of 0 means no dividends will be ` +
- "issued " +
- "Two important things to note: " +
- " * Issuing dividends will negatively affect your corporation's stock price " +
- " * Dividends are taxed. Taxes start at 50%, but can be decreased " +
- "Example: Assume your corporation makes $100m / sec in profit and you allocate " +
- "40% of that towards dividends. That means your corporation will gain $60m / sec " +
- "in funds and the remaining $40m / sec will be paid as dividends. Since your " +
- "corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share " +
- "per second before taxes.";
- const txt = createElement("p", { innerHTML: descText });
-
- let allocateBtn;
- const dividendPercentInput = createElement("input", {
- margin: "5px",
- placeholder: "Dividend %",
- type: "number",
- onkeyup: (e) => {
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) {allocateBtn.click();}
- },
- });
-
- allocateBtn = createElement("button", {
- class: "std-button",
- display: "inline-block",
- innerText: "Allocate Dividend Percentage",
- clickListener: () => {
- const percentage = Math.round(parseInt(dividendPercentInput.value));
- if (isNaN(percentage) || percentage < 0 || percentage > DividendMaxPercentage) {
- return dialogBoxCreate(`Invalid value. Must be an integer between 0 and ${DividendMaxPercentage}`);
- }
-
- this.corp.dividendPercentage = percentage;
-
- removeElementById(popupId);
-
- this.rerender();
- return false;
- },
- });
-
- const cancelBtn = createPopupCloseButton(popupId, {
- class: "std-button",
- display: "inline-block",
- innerText: "Cancel",
- });
-
- createPopup(popupId, [txt, dividendPercentInput, allocateBtn, cancelBtn]);
- dividendPercentInput.focus();
- }
-
- // Create a popup that lets the player issue new shares
- // This is created when the player clicks the "Issue New Shares" buttons in the overview panel
- createIssueNewSharesPopup() {
- const popupId = "cmpy-mgmt-issue-new-shares-popup";
- const maxNewSharesUnrounded = Math.round(this.corp.totalShares * 0.2);
- const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6);
-
- const descText = createElement("p", {
- innerHTML: "You can issue new equity shares (i.e. stocks) in order to raise " +
- "capital for your corporation. " +
- ` * You can issue at most ${numeralWrapper.format(maxNewShares, "0.000a")} new shares ` +
- ` * New shares are sold at a 10% discount ` +
- ` * You can only issue new shares once every 12 hours ` +
- ` * Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share ` +
- ` * Number of new shares issued must be a multiple of 10 million ` +
- `When you choose to issue new equity, private shareholders have first priority for up to 50% of the new shares. ` +
- `If they choose to exercise this option, these newly issued shares become private, restricted shares, which means ` +
- `you cannot buy them back.`,
- });
-
- let issueBtn, newSharesInput;
- const dynamicText = createElement("p", {
- display: "block",
- });
-
- function updateDynamicText(corp) {
- const newSharePrice = Math.round(corp.sharePrice * 0.9);
- let newShares = parseInt(newSharesInput.value);
- if (isNaN(newShares)) {
- dynamicText.innerText = "Invalid input";
- return;
- }
-
- // Round to nearest ten-millionth
- newShares /= 10e6;
- newShares = Math.round(newShares) * 10e6;
-
- if (newShares < 10e6) {
- dynamicText.innerText = "Must issue at least 10 million new shares";
- return;
- }
-
- if (newShares > maxNewShares) {
- dynamicText.innerText = "You cannot issue that many shares";
- return;
- }
-
- dynamicText.innerText = `Issue ${numeralWrapper.format(newShares, "0.000a")} new shares ` +
- `for ${numeralWrapper.formatMoney(newShares * newSharePrice)}?`
- }
- newSharesInput = createElement("input", {
- margin: "5px",
- placeholder: "# New Shares",
- type: "number",
- onkeyup: (e) => {
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) {
- issueBtn.click();
- } else {
- updateDynamicText(this.corp);
- }
- },
- });
-
- issueBtn = createElement("button", {
- class: "std-button",
- display: "inline-block",
- innerText: "Issue New Shares",
- clickListener: () => {
- const newSharePrice = Math.round(this.corp.sharePrice * 0.9);
- let newShares = parseInt(newSharesInput.value);
- if (isNaN(newShares)) {
- dialogBoxCreate("Invalid input for number of new shares");
- return;
- }
-
- // Round to nearest ten-millionth
- newShares = Math.round(newShares / 10e6) * 10e6;
-
- if (newShares < 10e6 || newShares > maxNewShares) {
- dialogBoxCreate("Invalid input for number of new shares");
- return;
- }
-
- const profit = newShares * newSharePrice;
- this.corp.issueNewSharesCooldown = IssueNewSharesCooldown;
- this.corp.totalShares += newShares;
-
- // Determine how many are bought by private investors
- // Private investors get up to 50% at most
- // Round # of private shares to the nearest millionth
- let privateShares = getRandomInt(0, Math.round(newShares / 2));
- privateShares = Math.round(privateShares / 1e6) * 1e6;
-
- this.corp.issuedShares += (newShares - privateShares);
- this.corp.funds = this.corp.funds.plus(profit);
- this.corp.immediatelyUpdateSharePrice();
-
- removeElementById(popupId);
- dialogBoxCreate(`Issued ${numeralWrapper.format(newShares, "0.000a")} and raised ` +
- `${numeralWrapper.formatMoney(profit)}. ${numeralWrapper.format(privateShares, "0.000a")} ` +
- `of these shares were bought by private investors. ` +
- `Stock price decreased to ${numeralWrapper.formatMoney(this.corp.sharePrice)}`);
-
- this.rerender();
- return false;
- },
- });
-
- const cancelBtn = createPopupCloseButton(popupId, {
- class: "std-button",
- display: "inline-block",
- innerText: "Cancel",
- });
-
- createPopup(popupId, [descText, dynamicText, newSharesInput, issueBtn, cancelBtn]);
- newSharesInput.focus();
- }
-
- // Create a popup that lets the player limit the production of a product
- createLimitProductProdutionPopup(product, city) {
- const popupId = "cmpy-mgmt-limit-product-production-popup";
- const txt = createElement("p", {
- innerText:"Enter a limit to the amount of this product you would " +
- "like to product per second. Leave the box empty to set no limit.",
- });
- let confirmBtn;
- const input = createElement("input", {
- margin: "5px",
- placeholder:"Limit",
- type:"number",
- onkeyup: (e) => {
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) { confirmBtn.click(); }
- },
- });
- confirmBtn = createElement("button", {
- class: "std-button",
- display: "inline-block",
- innerText: "Limit production",
- margin: "5px",
- clickListener: () => {
- if (input.value === "") {
- product.prdman[city][0] = false;
- removeElementById(popupId);
- return false;
- }
- var qty = parseFloat(input.value);
- if (isNaN(qty)) {
- dialogBoxCreate("Invalid value entered");
- return false;
- }
- if (qty < 0) {
- product.prdman[city][0] = false;
- } else {
- product.prdman[city][0] = true;
- product.prdman[city][1] = qty;
- }
- removeElementById(popupId);
- this.rerender();
- return false;
- },
- });
- const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" });
- cancelBtn.style.margin = "6px";
-
- createPopup(popupId, [txt, input, confirmBtn, cancelBtn]);
- input.focus();
- }
-
- // Create a popup that lets the player create a product for their current industry
- createMakeProductPopup(popupText, division) {
- if (division.hasMaximumNumberProducts()) { return; }
-
- const popupId = "cmpy-mgmt-create-product-popup";
- const txt = createElement("p", {
- innerHTML: popupText,
- });
- const designCity = createElement("select", {
- class: "dropdown",
- margin: "5px",
- });
- for (const cityName in division.offices) {
- if (division.offices[cityName] instanceof OfficeSpace) {
- designCity.add(createElement("option", {
- value: cityName,
- text: cityName,
- }));
- }
- }
- let productNamePlaceholder = "Product Name";
- if (division.type === Industries.Food) {
- productNamePlaceholder = "Restaurant Name";
- } else if (division.type === Industries.Healthcare) {
- productNamePlaceholder = "Hospital Name";
- } else if (division.type === Industries.RealEstate) {
- productNamePlaceholder = "Property Name";
- }
- var productNameInput = createElement("input", {
- class: "text-input",
- margin: "5px",
- placeholder: productNamePlaceholder,
- });
- var lineBreak1 = createElement("br");
- var designInvestInput = createElement("input", {
- class: "text-input",
- margin: "5px",
- placeholder: "Design investment",
- type: "number",
- });
- let confirmBtn;
- var marketingInvestInput = createElement("input", {
- class: "text-input",
- margin: "5px",
- placeholder: "Marketing investment",
- type: "number",
- onkeyup: (e) => {
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) { confirmBtn.click(); }
- },
- });
- confirmBtn = createElement("button", {
- class: "std-button",
- innerText: "Develop Product",
- clickListener: () => {
- if (designInvestInput.value == null || designInvestInput.value < 0) { designInvestInput.value = 0; }
- if (marketingInvestInput.value == null || marketingInvestInput.value < 0) { marketingInvestInput.value = 0; }
- var designInvest = parseFloat(designInvestInput.value),
- marketingInvest = parseFloat(marketingInvestInput.value);
- if (productNameInput.value == null || productNameInput.value === "") {
- dialogBoxCreate("You must specify a name for your product!");
- } else if (isNaN(designInvest)) {
- dialogBoxCreate("Invalid value for design investment");
- } else if (isNaN(marketingInvest)) {
- dialogBoxCreate("Invalid value for marketing investment");
- } else if (this.corp.funds.lt(designInvest + marketingInvest)) {
- dialogBoxCreate("You don't have enough company funds to make this large of an investment");
- } else {
- const product = new Product({
- name:productNameInput.value.replace(/[<>]/g, ''), //Sanitize for HTMl elements
- createCity:designCity.options[designCity.selectedIndex].value,
- designCost: designInvest,
- advCost: marketingInvest,
- });
- if (division.products[product.name] instanceof Product) {
- dialogBoxCreate(`You already have a product with this name!`);
- return;
- }
- this.corp.funds = this.corp.funds.minus(designInvest + marketingInvest);
- division.products[product.name] = product;
- removeElementById(popupId);
- }
- this.rerender();
- return false;
- },
- })
- const cancelBtn = createPopupCloseButton(popupId, {
- class: "std-button",
- innerText: "Cancel",
- });
-
- createPopup(popupId, [txt, designCity, productNameInput, lineBreak1,
- designInvestInput, marketingInvestInput, confirmBtn, cancelBtn]);
- productNameInput.focus();
- }
-
- // Create a popup that lets the player use the Market TA research for Materials
- createMaterialMarketTaPopup(mat, industry) {
- const popupId = "cmpy-mgmt-marketta-popup";
- const markupLimit = mat.getMarkupLimit();
- const ta1 = createElement("p", {
- innerHTML: "Market-TA.I " +
- "The maximum sale price you can mark this up to is " +
- numeralWrapper.formatMoney(mat.bCost + markupLimit) +
- ". This means that if you set the sale price higher than this, " +
- "you will begin to experience a loss in number of sales",
- });
-
- // Enable using Market-TA1 for automatically setting sale price
- const useTa1AutoSaleId = "cmpy-mgmt-marketa1-checkbox";
- const useTa1AutoSaleDiv = createElement("div", { display: "block" });
- const useTa1AutoSaleLabel = createElement("label", {
- color: "white",
- for: useTa1AutoSaleId,
- innerText: "Use Market-TA.I for Auto-Sale Price",
- tooltip: "If this is enabled, then this Material will automatically " +
- "be sold at the price identified by Market-TA.I (i.e. the price shown above)",
- })
- const useTa1AutoSaleCheckbox = createElement("input", {
- checked: mat.marketTa1,
- id: useTa1AutoSaleId,
- margin: "3px",
- type: "checkbox",
- changeListener: (e) => {
- mat.marketTa1 = e.target.checked;
- },
- });
- useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel);
- useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
-
- const closeBtn = createPopupCloseButton(popupId, {
- class: "std-button",
- display: "block",
- innerText: "Close",
- });
-
- if (industry.hasResearch("Market-TA.II")) {
- let updateTa2Text;
- const ta2Text = createElement("p");
- const ta2Input = createElement("input", {
- marginTop: "4px",
- onkeyup: (e) => {
- e.preventDefault();
- updateTa2Text();
- },
- type: "number",
- value: mat.bCost,
- });
-
- // Function that updates the text in ta2Text element
- updateTa2Text = function() {
- const sCost = parseFloat(ta2Input.value);
- let markup = 1;
- if (sCost > mat.bCost) {
- //Penalty if difference between sCost and bCost is greater than markup limit
- if ((sCost - mat.bCost) > markupLimit) {
- markup = Math.pow(markupLimit / (sCost - mat.bCost), 2);
- }
- } else if (sCost < mat.bCost) {
- if (sCost <= 0) {
- markup = 1e12; //Sell everything, essentially discard
- } else {
- //Lower prices than market increases sales
- markup = mat.bCost / sCost;
- }
- }
- ta2Text.innerHTML = `Market-TA.II ` +
- `If you sell at ${numeralWrapper.formatMoney(sCost)}, ` +
- `then you will sell ${numeralWrapper.format(markup, "0.00000")}x as much compared ` +
- `to if you sold at market price.`;
- }
- updateTa2Text();
-
- // Enable using Market-TA2 for automatically setting sale price
- const useTa2AutoSaleId = "cmpy-mgmt-marketa2-checkbox";
- const useTa2AutoSaleDiv = createElement("div", { display: "block" });
- const useTa2AutoSaleLabel = createElement("label", {
- color: "white",
- for: useTa2AutoSaleId,
- innerText: "Use Market-TA.II for Auto-Sale Price",
- tooltip: "If this is enabled, then this Material will automatically " +
- "be sold at the optimal price such that the amount sold matches the " +
- "amount produced. (i.e. the highest possible price, while still ensuring " +
- " that all produced materials will be sold)",
- })
- const useTa2AutoSaleCheckbox = createElement("input", {
- checked: mat.marketTa2,
- id: useTa2AutoSaleId,
- margin: "3px",
- type: "checkbox",
- changeListener: (e) => {
- mat.marketTa2 = e.target.checked;
- },
- });
- useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
- useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox);
-
- const ta2OverridesTa1 = createElement("p", {
- innerText: "Note that Market-TA.II overrides Market-TA.I. This means that if " +
- "both are enabled, then Market-TA.II will take effect, not Market-TA.I",
- });
-
- createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, ta2OverridesTa1, closeBtn]);
- } else {
- // Market-TA.I only
- createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
- }
- }
-
- // Create a popup that lets the player create a new industry.
- // This is created when the player clicks the "Expand into new Industry" header tab
- createNewIndustryPopup() {
- const popupId = "cmpy-mgmt-expand-industry-popup";
- if (document.getElementById(popupId) != null) { return; }
-
- var txt = createElement("p", {
- innerHTML: "Create a new division to expand into a new industry:",
- });
- var selector = createElement("select", {
- class:"dropdown",
- });
- var industryDescription = createElement("p", {});
- var yesBtn;
- var nameInput = createElement("input", {
- type:"text",
- id:"cmpy-mgmt-expand-industry-name-input",
- class: "text-input",
- display:"block",
- maxLength: 30,
- pattern:"[a-zA-Z0-9-_]",
- onkeyup:(e)=>{
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) {yesBtn.click();}
- },
- });
- var nameLabel = createElement("label", {
- for:"cmpy-mgmt-expand-industry-name-input",
- innerText:"Division name: ",
- });
- yesBtn = createElement("span", {
- class:"popup-box-button",
- innerText:"Create Division",
- clickListener: ()=>{
- const ind = selector.options[selector.selectedIndex].value;
- const newDivisionName = nameInput.value;
-
- for (let i = 0; i < this.corp.divisions.length; ++i) {
- if (this.corp.divisions[i].name === newDivisionName) {
- dialogBoxCreate("This name is already in use!");
- return false;
- }
- }
- if (this.corp.funds.lt(IndustryStartingCosts[ind])) {
- dialogBoxCreate("Not enough money to create a new division in this industry");
- } else if (newDivisionName === "") {
- dialogBoxCreate("New division must have a name!");
- } else {
- this.corp.funds = this.corp.funds.minus(IndustryStartingCosts[ind]);
- var newInd = new Industry({
- corp: this.corp,
- name: newDivisionName,
- type: ind,
- });
- this.corp.divisions.push(newInd);
-
- // Set routing to the new division so that the UI automatically switches to it
- this.routing.routeTo(newDivisionName);
-
- removeElementById("cmpy-mgmt-expand-industry-popup");
- this.rerender();
- }
- return false;
- },
- });
-
- const noBtn = createPopupCloseButton(popupId, {
- display: "inline-block",
- innerText: "Cancel",
- });
-
- // Make an object to keep track of what industries you're already in
- const ownedIndustries = {};
- for (let i = 0; i < this.corp.divisions.length; ++i) {
- ownedIndustries[this.corp.divisions[i].type] = true;
- }
-
- // Add industry types to selector
- // Have Agriculture be first as recommended option
- if (!ownedIndustries["Agriculture"]) {
- selector.add(createElement("option", {
- text:Industries["Agriculture"], value:"Agriculture",
- }));
- }
-
- for (var key in Industries) {
- if (key !== "Agriculture" && Industries.hasOwnProperty(key) && !ownedIndustries[key]) {
- var ind = Industries[key];
- selector.add(createElement("option", {
- text: ind,value:key,
- }));
- }
- }
-
- //Initial Industry Description
- var ind = selector.options[selector.selectedIndex].value;
- industryDescription.innerHTML = (IndustryDescriptions[ind] + " ");
-
- //Change the industry description text based on selected option
- selector.addEventListener("change", function() {
- var ind = selector.options[selector.selectedIndex].value;
- industryDescription.innerHTML = IndustryDescriptions[ind] + " ";
- });
-
- //Add to DOM
- const elems = [];
- elems.push(txt);
- elems.push(selector);
- elems.push(industryDescription);
- elems.push(nameLabel);
- elems.push(nameInput);
- elems.push(noBtn);
- elems.push(yesBtn);
-
- createPopup(popupId, elems);
- nameInput.focus();
-
- return false;
- }
-
// Create a popup that lets the player use the Market TA research for Products
createProductMarketTaPopup(product, industry) {
const popupId = "cmpy-mgmt-marketta-popup";
diff --git a/src/Corporation/ui/ExportPopup.tsx b/src/Corporation/ui/ExportPopup.tsx
new file mode 100644
index 000000000..9431ad116
--- /dev/null
+++ b/src/Corporation/ui/ExportPopup.tsx
@@ -0,0 +1,120 @@
+import React, { useState } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+
+interface IProps {
+ mat: any;
+ corp: any;
+ popupId: string;
+}
+
+// Create a popup that lets the player manage exports
+export function ExportPopup(props: IProps): React.ReactElement {
+ if(props.corp.divisions.length === 0)
+ throw new Error('Export popup created with no divisions.');
+ if(Object.keys(props.corp.divisions[0].warehouses).length === 0)
+ throw new Error('Export popup created in a division with no warehouses.');
+ const [industry, setIndustry] = useState(props.corp.divisions[0].name);
+ const [city, setCity] = useState(Object.keys(props.corp.divisions[0].warehouses)[0]);
+ const [amt, setAmt] = useState('');
+ const setRerender = useState(false)[1];
+
+ function rerender(): void {
+ setRerender(old => !old);
+ }
+
+ function onCityChange(event: React.ChangeEvent): void {
+ setCity(event.target.value);
+ }
+
+ function onIndustryChange(event: React.ChangeEvent): void {
+ setIndustry(event.target.value);
+ }
+
+ function onAmtChange(event: React.ChangeEvent): void {
+ setAmt(event.target.value);
+ }
+
+ function exportMaterial(): void {
+ const industryName = industry;
+ const cityName = city;
+ console.log(`${industryName}, ${cityName}, ${amt}`)
+
+ // Sanitize amt
+ let sanitizedAmt = amt.replace(/\s+/g, '');
+ sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, '');
+ let temp = sanitizedAmt.replace(/MAX/g, '1');
+ try {
+ temp = eval(temp);
+ } catch(e) {
+ dialogBoxCreate("Invalid expression entered for export amount: " + e);
+ return;
+ }
+
+ const n = parseFloat(temp);
+
+ if (n == null || isNaN(n) || n < 0) {
+ dialogBoxCreate("Invalid amount entered for export");
+ return;
+ }
+ const exportObj = {ind:industryName, city:cityName, amt:sanitizedAmt};
+ console.log(exportObj);
+ props.mat.exp.push(exportObj);
+ removePopup(props.popupId);
+ }
+
+ function removeExport(exp: any): void {
+ for (let i = 0; i < props.mat.exp.length; ++i) {
+ if(props.mat.exp[i].ind !== exp.ind ||
+ props.mat.exp[i].city !== exp.city ||
+ props.mat.exp[i].amt !== exp.amt) continue;
+ props.mat.exp.splice(i, 1);
+ break
+ }
+ rerender();
+ }
+
+ const currentDivision = props.corp.divisions.find((division: any) => division.name === industry);
+
+ return (<>
+
+Select the industry and city to export this material to, as well as
+how much of this material to export per second. You can set the export
+amount to 'MAX' to export all of the materials in this warehouse.
+
+
+ {
+ props.corp.divisions.map((division: any) =>
+ {division.name} )
+ }
+
+
+ {
+ currentDivision && Object.keys(currentDivision.warehouses).map((cityName: any) => {
+ if(currentDivision.warehouses[cityName] === 0) return;
+ return ({cityName} );
+ })
+ }
+
+
+ Export
+
+Below is a list of all current exports of this material from this warehouse.
+Clicking on one of the exports below will REMOVE that export.
+
+ {
+ props.mat.exp.map((exp: any, index: number) =>
+ removeExport(exp)}>
+ Industry: {exp.ind}
+ City: {exp.city}
+ Amount/s: {exp.amt}
+
)
+ }
+ >);
+}
diff --git a/src/Corporation/ui/HeaderTabs.tsx b/src/Corporation/ui/HeaderTabs.tsx
index ec5dd8ab9..b22bc0277 100644
--- a/src/Corporation/ui/HeaderTabs.tsx
+++ b/src/Corporation/ui/HeaderTabs.tsx
@@ -4,6 +4,8 @@
import React from "react";
import { HeaderTab } from "./HeaderTab";
import { IDivision } from "../IDivision";
+import { NewIndustryPopup } from "./NewIndustryPopup";
+import { createPopup } from "../../ui/React/createPopup";
interface IProps {
corp: any;
@@ -17,6 +19,15 @@ export function HeaderTabs(props: IProps): React.ReactElement {
props.corp.rerender();
}
+ function openNewIndustryPopup(): void {
+ const popupId = "cmpy-mgmt-expand-industry-popup";
+ createPopup(popupId, NewIndustryPopup, {
+ corp: props.corp,
+ routing: props.routing,
+ popupId: popupId,
+ });
+ }
+
return (
props.eventHandler.createNewIndustryPopup()}
+ onClick={openNewIndustryPopup}
text={"Expand into new Industry"}
/>
diff --git a/src/Corporation/ui/IndustryOverview.tsx b/src/Corporation/ui/IndustryOverview.tsx
index 5e900e652..06d4d2f44 100644
--- a/src/Corporation/ui/IndustryOverview.tsx
+++ b/src/Corporation/ui/IndustryOverview.tsx
@@ -8,6 +8,8 @@ import { IndustryUpgrades } from "../IndustryUpgrades";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
+import { MakeProductPopup } from "./MakeProductPopup";
+import { createPopup } from "../../ui/React/createPopup";
interface IProps {
routing: any;
@@ -77,8 +79,18 @@ export function IndustryOverview(props: IProps): React.ReactElement {
display: "inline-block",
}
+ function openMakeProductPopup() {
+ const popupId = "cmpy-mgmt-create-product-popup";
+ createPopup(popupId, MakeProductPopup, {
+ popupText: createProductPopupText,
+ division: division,
+ corp: props.corp,
+ popupId: popupId,
+ });
+ }
+
return (
- props.eventHandler.createMakeProductPopup(createProductPopupText, division)} style={buttonStyle}>
+
{createProductButtonText}
{
hasMaxProducts &&
diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx
index dfc1ed454..ac5c0d2d0 100644
--- a/src/Corporation/ui/IndustryWarehouse.tsx
+++ b/src/Corporation/ui/IndustryWarehouse.tsx
@@ -8,6 +8,9 @@ import { Material } from "../Material";
import { Product } from "../Product";
import { Warehouse } from "../Warehouse";
import { DiscontinueProductPopup } from "./DiscontinueProductPopup";
+import { ExportPopup } from "./ExportPopup";
+import { LimitProductProductionPopup } from "./LimitProductProductionPopup";
+import { MaterialMarketTaPopup } from "./MaterialMarketTaPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
@@ -71,7 +74,15 @@ function ProductComponent(props: IProductProps): React.ReactElement {
if (product.prdman[city][0]) {
limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")";
}
- const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city);
+
+ function openLimitProductProdutionPopup(): void {
+ const popupId = "cmpy-mgmt-limit-product-production-popup";
+ createPopup(popupId, LimitProductProductionPopup, {
+ product: product,
+ city: city,
+ popupId: popupId,
+ });
+ }
function openDiscontinueProductPopup(): void {
const popupId = "cmpy-mgmt-discontinue-product-popup";
@@ -83,8 +94,15 @@ function ProductComponent(props: IProductProps): React.ReactElement {
});
}
- // Market TA button
- const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division);
+ function openMaterialMarketTaPopup(): void {
+ const popupId = "cmpy-mgmt-export-popup";
+ createPopup(popupId, MaterialMarketTaPopup, {
+ mat: product,
+ industry: division,
+ corp: props.corp,
+ popupId: popupId,
+ });
+ }
// Unfinished Product
if (!product.fin) {
@@ -99,7 +117,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
{sellButtonText}
-
+
{limitProductionButtonText}
@@ -107,7 +125,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
{
division.hasResearch("Market-TA.I") &&
-
+
Market-TA
}
@@ -179,7 +197,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
{sellButtonText}
-
+
{limitProductionButtonText}
@@ -187,7 +205,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
{
division.hasResearch("Market-TA.I") &&
-
+
Market-TA
}
@@ -236,8 +254,14 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
- // Export material button
- const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat);
+ function openExportPopup() {
+ const popupId = "cmpy-mgmt-export-popup";
+ createPopup(popupId, ExportPopup, {
+ mat: mat,
+ corp: props.corp,
+ popupId: popupId,
+ });
+ }
// Sell material button
let sellButtonText;
@@ -265,8 +289,15 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
}
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
- // Market TA button
- const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division);
+ function openMaterialMarketTaPopup(): void {
+ const popupId = "cmpy-mgmt-export-popup";
+ createPopup(popupId, MaterialMarketTaPopup, {
+ mat: mat,
+ industry: division,
+ corp: props.corp,
+ popupId: popupId,
+ });
+ }
return (
@@ -322,7 +353,7 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
{
corp.unlockUpgrades[0] === 1 &&
-
+
Export
}
@@ -334,7 +365,7 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
{
division.hasResearch("Market-TA.I") &&
-
+
Market-TA
}
diff --git a/src/Corporation/ui/IssueDividendsPopup.tsx b/src/Corporation/ui/IssueDividendsPopup.tsx
new file mode 100644
index 000000000..f04a06647
--- /dev/null
+++ b/src/Corporation/ui/IssueDividendsPopup.tsx
@@ -0,0 +1,57 @@
+import React, { useState } from 'react';
+import { removePopup } from "../../ui/React/createPopup";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { CorporationConstants } from "../data/Constants";
+
+interface IProps {
+ popupId: string;
+ corp: any;
+}
+
+// Create a popup that lets the player issue & manage dividends
+// This is created when the player clicks the "Issue Dividends" button in the overview panel
+export function IssueDividendsPopup(props: IProps): React.ReactElement {
+ const [percent, setPercent] = useState(null);
+
+ function issueDividends(): void {
+ if(percent === null) return;
+ if (isNaN(percent) || percent < 0 || percent > CorporationConstants.DividendMaxPercentage) {
+ dialogBoxCreate(`Invalid value. Must be an integer between 0 and ${CorporationConstants.DividendMaxPercentage}`);
+ return;
+ }
+
+ props.corp.dividendPercentage = percent;
+
+ removePopup(props.popupId);
+ }
+
+ function onKeyDown(event: React.KeyboardEvent): void {
+ if (event.keyCode === 13) issueDividends();
+ }
+
+ function onChange(event: React.ChangeEvent): void {
+ if(event.target.value === "") setPercent(null);
+ else setPercent(parseFloat(event.target.value));
+ }
+
+ return (<>
+
+"Dividends are a distribution of a portion of the corporation's
+profits to the shareholders. This includes yourself, as well.
+In order to issue dividends, simply allocate some percentage
+of your corporation's profits to dividends. This percentage must be an
+integer between 0 and {CorporationConstants.DividendMaxPercentage}. (A percentage of 0 means no dividends will be
+issued
+Two important things to note:
+ * Issuing dividends will negatively affect your corporation's stock price
+ * Dividends are taxed. Taxes start at 50%, but can be decreased
+Example: Assume your corporation makes $100m / sec in profit and you allocate
+40% of that towards dividends. That means your corporation will gain $60m / sec
+in funds and the remaining $40m / sec will be paid as dividends. Since your
+corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share
+per second before taxes.
+
+
+ Allocate Dividend Percentage
+ >);
+}
diff --git a/src/Corporation/ui/IssueNewSharesPopup.tsx b/src/Corporation/ui/IssueNewSharesPopup.tsx
new file mode 100644
index 000000000..f1c8d82f7
--- /dev/null
+++ b/src/Corporation/ui/IssueNewSharesPopup.tsx
@@ -0,0 +1,134 @@
+import React, { useState } from 'react';
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { removePopup } from "../../ui/React/createPopup";
+import { getRandomInt } from "../../../utils/helpers/getRandomInt";
+import { CorporationConstants } from "../data/Constants";
+
+interface IEffectTextProps {
+ corp: any;
+ shares: number | null;
+}
+
+function EffectText(props: IEffectTextProps): React.ReactElement {
+ if(props.shares === null) return (<>>);
+ const newSharePrice = Math.round(props.corp.sharePrice * 0.9);
+ const maxNewSharesUnrounded = Math.round(props.corp.totalShares * 0.2);
+ const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6);
+ let newShares = props.shares;
+ if (isNaN(newShares)) {
+ return (Invalid input
);
+ }
+
+ // Round to nearest ten-millionth
+ newShares /= 10e6;
+ newShares = Math.round(newShares) * 10e6;
+
+ if (newShares < 10e6) {
+ return (Must issue at least 10 million new shares
);
+ }
+
+ if (newShares > maxNewShares) {
+ return (You cannot issue that many shares
);
+ }
+
+ return (
+ Issue ${numeralWrapper.format(newShares, "0.000a")} new
+ shares for {numeralWrapper.formatMoney(newShares * newSharePrice)}?
+
);
+}
+
+interface IProps {
+ corp: any;
+ popupId: string;
+}
+
+// Create a popup that lets the player issue new shares
+// This is created when the player clicks the "Issue New Shares" buttons in the overview panel
+export function IssueNewSharesPopup(props: IProps): React.ReactElement {
+ const [shares, setShares] = useState(null);
+ const maxNewSharesUnrounded = Math.round(props.corp.totalShares * 0.2);
+ const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6);
+
+ function issueNewShares(): void {
+ if(shares === null) return;
+ const newSharePrice = Math.round(props.corp.sharePrice * 0.9);
+ let newShares = shares;
+ if (isNaN(newShares)) {
+ dialogBoxCreate("Invalid input for number of new shares");
+ return;
+ }
+
+ // Round to nearest ten-millionth
+ newShares = Math.round(newShares / 10e6) * 10e6;
+
+ if (newShares < 10e6 || newShares > maxNewShares) {
+ dialogBoxCreate("Invalid input for number of new shares");
+ return;
+ }
+
+ const profit = newShares * newSharePrice;
+ props.corp.issueNewSharesCooldown = CorporationConstants.IssueNewSharesCooldown;
+ props.corp.totalShares += newShares;
+
+ // Determine how many are bought by private investors
+ // Private investors get up to 50% at most
+ // Round # of private shares to the nearest millionth
+ let privateShares = getRandomInt(0, Math.round(newShares / 2));
+ privateShares = Math.round(privateShares / 1e6) * 1e6;
+
+ props.corp.issuedShares += (newShares - privateShares);
+ props.corp.funds = props.corp.funds.plus(profit);
+ props.corp.immediatelyUpdateSharePrice();
+
+ removePopup(props.popupId);
+ dialogBoxCreate(`Issued ${numeralWrapper.format(newShares, "0.000a")} and raised ` +
+ `${numeralWrapper.formatMoney(profit)}. ${numeralWrapper.format(privateShares, "0.000a")} ` +
+ `of these shares were bought by private investors. ` +
+ `Stock price decreased to ${numeralWrapper.formatMoney(props.corp.sharePrice)}`);
+ }
+
+ function onKeyDown(event: React.KeyboardEvent): void {
+ if (event.keyCode === 13) issueNewShares();
+ }
+
+ function onChange(event: React.ChangeEvent): void {
+ if(event.target.value === "") setShares(null);
+ else setShares(parseFloat(event.target.value));
+ }
+
+ return (<>
+
+You can issue new equity shares (i.e. stocks) in order to raise
+capital for your corporation.
+ * You can issue at most {numeralWrapper.formatMoney(maxNewShares)} new shares
+ * New shares are sold at a 10% discount
+ * You can only issue new shares once every 12 hours
+ * Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share
+ * Number of new shares issued must be a multiple of 10 million
+When you choose to issue new equity, private shareholders have first priority for up to 50% of the new shares.
+If they choose to exercise this option, these newly issued shares become private, restricted shares, which means
+you cannot buy them back.
+
+
+
+ Issue New Shares
+ >);
+
+
+ // let issueBtn, newSharesInput;
+ // const dynamicText = createElement("p", {
+ // display: "block",
+ // });
+
+ // function updateDynamicText(corp) {
+
+ // }
+
+
+
+
+ // createPopup(popupId, [descText, dynamicText, newSharesInput, issueBtn, cancelBtn]);
+ // newSharesInput.focus();
+}
diff --git a/src/Corporation/ui/LimitProductProductionPopup.tsx b/src/Corporation/ui/LimitProductProductionPopup.tsx
new file mode 100644
index 000000000..e734daff3
--- /dev/null
+++ b/src/Corporation/ui/LimitProductProductionPopup.tsx
@@ -0,0 +1,59 @@
+import React, { useState } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+
+
+interface IProps {
+ product: any;
+ city: any;
+ popupId: string;
+}
+
+// Create a popup that lets the player limit the production of a product
+export function LimitProductProductionPopup(props: IProps): React.ReactElement {
+ const [limit, setLimit] = useState(null);
+
+ function limitProductProduction(): void {
+ if (limit === null) {
+ props.product.prdman[props.city][0] = false;
+ removePopup(props.popupId);
+ return;
+ }
+ var qty = limit;
+ if (isNaN(qty)) {
+ dialogBoxCreate("Invalid value entered");
+ return;
+ }
+ if (qty < 0) {
+ props.product.prdman[props.city][0] = false;
+ } else {
+ props.product.prdman[props.city][0] = true;
+ props.product.prdman[props.city][1] = qty;
+ }
+ removePopup(props.popupId);
+ }
+
+ function onKeyDown(event: React.KeyboardEvent): void {
+ if (event.keyCode === 13) limitProductProduction();
+ }
+
+ function onChange(event: React.ChangeEvent): void {
+ if(event.target.value === "") setLimit(null);
+ else setLimit(parseFloat(event.target.value));
+ }
+
+ return (<>
+
+Enter a limit to the amount of this product you would
+like to product per second. Leave the box empty to set no limit.
+
+
+ Limit production
+ >);
+}
\ No newline at end of file
diff --git a/src/Corporation/ui/MakeProductPopup.tsx b/src/Corporation/ui/MakeProductPopup.tsx
new file mode 100644
index 000000000..451dc06e9
--- /dev/null
+++ b/src/Corporation/ui/MakeProductPopup.tsx
@@ -0,0 +1,107 @@
+import React, { useState } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+import { Industries } from "../IndustryData";
+import { Product } from "../Product";
+
+interface IProps {
+ popupText: string;
+ division: any;
+ corp: any;
+ popupId: string;
+}
+
+function productPlaceholder(tpe: string): string {
+ if (tpe === Industries.Food) {
+ return "Restaurant Name";
+ } else if (tpe === Industries.Healthcare) {
+ return "Hospital Name";
+ } else if (tpe === Industries.RealEstate) {
+ return "Property Name";
+ }
+ return "Product Name";
+}
+
+// Create a popup that lets the player create a product for their current industry
+export function MakeProductPopup(props: IProps) {
+ const allCities = Object.keys(props.division.offices).
+ filter((cityName: string) => props.division.offices[cityName] !== 0);
+ const [city, setCity] = useState(allCities.length > 0 ? allCities[0] : '');
+ const [name, setName] = useState('');
+ const [design, setDesign] = useState(null);
+ const [marketing, setMarketing] = useState(null);
+ if (props.division.hasMaximumNumberProducts()) return (<>>);
+
+ function makeProduct(): void {
+ let designInvest = design;
+ let marketingInvest = marketing;
+ if (designInvest == null || designInvest < 0) { designInvest = 0; }
+ if (marketingInvest == null || marketingInvest < 0) { marketingInvest = 0; }
+ if (name == null || name === "") {
+ dialogBoxCreate("You must specify a name for your product!");
+ } else if (isNaN(designInvest)) {
+ dialogBoxCreate("Invalid value for design investment");
+ } else if (isNaN(marketingInvest)) {
+ dialogBoxCreate("Invalid value for marketing investment");
+ } else if (props.corp.funds.lt(designInvest + marketingInvest)) {
+ dialogBoxCreate("You don't have enough company funds to make this large of an investment");
+ } else {
+ const product = new Product({
+ name: name.replace(/[<>]/g, ''), //Sanitize for HTMl elements
+ createCity: city,
+ designCost: designInvest,
+ advCost: marketingInvest,
+ });
+ if (props.division.products[product.name] instanceof Product) {
+ dialogBoxCreate(`You already have a product with this name!`);
+ return;
+ }
+ props.corp.funds = props.corp.funds.minus(designInvest + marketingInvest);
+ props.division.products[product.name] = product;
+ removePopup(props.popupId);
+ }
+ }
+
+ function onCityChange(event: React.ChangeEvent): void {
+ setCity(event.target.value);
+ }
+
+ function onProductNameChange(event: React.ChangeEvent): void {
+ setName(event.target.value);
+ }
+
+ function onDesignChange(event: React.ChangeEvent): void {
+ if(event.target.value === "") setDesign(null);
+ else setDesign(parseFloat(event.target.value));
+ }
+
+ function onMarketingChange(event: React.ChangeEvent): void {
+ if(event.target.value === "") setMarketing(null);
+ else setMarketing(parseFloat(event.target.value));
+ }
+
+ function onKeyDown(event: React.KeyboardEvent): void {
+ if (event.keyCode === 13) makeProduct();
+ }
+
+ return (<>
+
+
+ {
+ allCities.map((cityName: string) =>
+ {cityName} )
+ }
+
+
+
+
+
+ Develop Product
+ >);
+}
diff --git a/src/Corporation/ui/MaterialMarketTaPopup.tsx b/src/Corporation/ui/MaterialMarketTaPopup.tsx
new file mode 100644
index 000000000..99febeeae
--- /dev/null
+++ b/src/Corporation/ui/MaterialMarketTaPopup.tsx
@@ -0,0 +1,121 @@
+import React, { useState } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+import { numeralWrapper } from "../../ui/numeralFormat";
+
+interface IMarketTA2Props {
+ industry: any;
+ mat: any;
+}
+
+function MarketTA2(props: IMarketTA2Props): React.ReactElement {
+ if(!props.industry.hasResearch("Market-TA.II")) return (<>>);
+
+ const [newCost, setNewCost] = useState(props.mat.bCost);
+ const setRerender = useState(false)[1];
+ function rerender(): void {
+ setRerender(old => !old);
+ }
+ const markupLimit = props.mat.getMarkupLimit();
+
+ function onChange(event: React.ChangeEvent): void {
+ if(event.target.value === "") setNewCost(0);
+ else setNewCost(parseFloat(event.target.value));
+ }
+
+ const sCost = newCost;
+ let markup = 1;
+ if (sCost > props.mat.bCost) {
+ //Penalty if difference between sCost and bCost is greater than markup limit
+ if ((sCost - props.mat.bCost) > markupLimit) {
+ markup = Math.pow(markupLimit / (sCost - props.mat.bCost), 2);
+ }
+ } else if (sCost < props.mat.bCost) {
+ if (sCost <= 0) {
+ markup = 1e12; //Sell everything, essentially discard
+ } else {
+ //Lower prices than market increases sales
+ markup = props.mat.bCost / sCost;
+ }
+ }
+
+ function onMarketTA2(event: React.ChangeEvent): void {
+ props.mat.marketTa2 = event.target.checked;
+ rerender();
+ }
+
+ return (<>
+
+ Market-TA.II
+ If you sell at {numeralWrapper.formatMoney(sCost)},
+ then you will sell {numeralWrapper.format(markup, "0.00000")}x as much compared
+ to if you sold at market price.
+
+
+
+
+ Use Market-TA.II for Auto-Sale Price
+
+ If this is enabled, then this Material will automatically
+ be sold at the optimal price such that the amount sold matches the
+ amount produced. (i.e. the highest possible price, while still ensuring
+ that all produced materials will be sold)
+
+
+
+
+
+ Note that Market-TA.II overrides Market-TA.I. This means that if
+ both are enabled, then Market-TA.II will take effect, not Market-TA.I
+
+ >);
+}
+
+interface IProps {
+ mat: any;
+ industry: any;
+ corp: any;
+ popupId: string;
+}
+
+// Create a popup that lets the player use the Market TA research for Materials
+export function MaterialMarketTaPopup(props: IProps): React.ReactElement {
+ const setRerender = useState(false)[1];
+ function rerender(): void {
+ setRerender(old => !old);
+ }
+ const markupLimit = props.mat.getMarkupLimit();
+
+ function onMarketTA1(event: React.ChangeEvent): void {
+ props.mat.marketTa1 = event.target.checked;
+ rerender();
+ }
+
+ return (<>
+
+ Market-TA.I
+ The maximum sale price you can mark this up to
+ is {numeralWrapper.formatMoney(props.mat.bCost + markupLimit)}.
+ This means that if you set the sale price higher than this,
+ you will begin to experience a loss in number of sales
+
+
+
+ Use Market-TA.I for Auto-Sale Price
+
+ If this is enabled, then this Material will automatically
+ be sold at the price identified by Market-TA.I (i.e. the price shown above)
+
+
+
+
+
+ >);
+
+}
diff --git a/src/Corporation/ui/NewIndustryPopup.tsx b/src/Corporation/ui/NewIndustryPopup.tsx
new file mode 100644
index 000000000..c61ed21d4
--- /dev/null
+++ b/src/Corporation/ui/NewIndustryPopup.tsx
@@ -0,0 +1,89 @@
+import React, { useState } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+import {
+ Industries,
+ IndustryStartingCosts,
+ IndustryDescriptions } from "../IndustryData";
+import { Industry } from "../Corporation";
+
+interface IProps {
+ corp: any;
+ popupId: string;
+ routing: any;
+}
+// Create a popup that lets the player create a new industry.
+// This is created when the player clicks the "Expand into new Industry" header tab
+export function NewIndustryPopup(props: IProps): React.ReactElement {
+ const allIndustries = Object.keys(Industries).sort();
+ const possibleIndustries = allIndustries.filter((industryType: string) =>
+ props.corp.divisions.find((division: any) =>
+ division.type === industryType) === undefined).sort();
+ const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : '');
+ const [name, setName] = useState('');
+
+ function newIndustry(): void {
+ const ind = industry;
+ const newDivisionName = name;
+
+ for (let i = 0; i < props.corp.divisions.length; ++i) {
+ if (props.corp.divisions[i].name === newDivisionName) {
+ dialogBoxCreate("This name is already in use!");
+ return;
+ }
+ }
+ if (props.corp.funds.lt(IndustryStartingCosts[ind])) {
+ dialogBoxCreate("Not enough money to create a new division in this industry");
+ } else if (newDivisionName === "") {
+ dialogBoxCreate("New division must have a name!");
+ } else {
+ props.corp.funds = props.corp.funds.minus(IndustryStartingCosts[ind]);
+ const newInd = new Industry({
+ corp: props.corp,
+ name: newDivisionName,
+ type: ind,
+ });
+ props.corp.divisions.push(newInd);
+
+ // Set routing to the new division so that the UI automatically switches to it
+ props.routing.routeTo(newDivisionName);
+
+ removePopup(props.popupId);
+ }
+ }
+
+ function onNameChange(event: React.ChangeEvent): void {
+ setName(event.target.value);
+ }
+
+ function onKeyDown(event: React.KeyboardEvent): void {
+ if (event.keyCode === 13) newIndustry();
+ }
+
+ function onIndustryChange(event: React.ChangeEvent): void {
+ setIndustry(event.target.value);
+ }
+
+ return (<>
+ Create a new division to expand into a new industry:
+
+ {
+ possibleIndustries.map((industry: string) =>
+ {industry} )
+ }
+
+ {IndustryDescriptions[industry]}
+
+
+ Division name:
+
+ Create Division
+ >);
+
+}
diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx
index 5972cd6a4..2d035d448 100644
--- a/src/Corporation/ui/Overview.tsx
+++ b/src/Corporation/ui/Overview.tsx
@@ -5,6 +5,8 @@ import { UnlockUpgrade } from "./UnlockUpgrade";
import { BribeFactionPopup } from "./BribeFactionPopup";
import { SellSharesPopup } from "./SellSharesPopup";
import { BuybackSharesPopup } from "./BuybackSharesPopup";
+import { IssueDividendsPopup } from "./IssueDividendsPopup";
+import { IssueNewSharesPopup } from "./IssueNewSharesPopup";
import { CorporationConstants } from "../data/Constants";
import { CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades";
@@ -245,6 +247,14 @@ export function Overview(props: IProps): React.ReactElement {
tooltip: "Buy back shares you that previously issued or sold at market price.",
});
+ function openIssueNewSharesPopup(): void {
+ const popupId = "cmpy-mgmt-issue-new-shares-popup";
+ createPopup(popupId, IssueNewSharesPopup, {
+ popupId: popupId,
+ corp: props.corp,
+ });
+ }
+
const issueNewSharesOnCd = (corp.issueNewSharesCooldown > 0);
const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button";
const issueNewSharesTooltip = issueNewSharesOnCd
@@ -253,15 +263,23 @@ export function Overview(props: IProps): React.ReactElement {
const issueNewSharesBtn = createButton({
class: issueNewSharesClass,
display: "inline-block",
- onClick: props.eventHandler.createIssueNewSharesPopup,
+ onClick: openIssueNewSharesPopup,
text: "Issue New Shares",
tooltip: issueNewSharesTooltip,
});
+ function openIssueDividendsPopup(): void {
+ const popupId = "cmpy-mgmt-issue-dividends-popup";
+ createPopup(popupId, IssueDividendsPopup, {
+ popupId: popupId,
+ corp: props.corp,
+ });
+ }
+
const issueDividendsBtn = createButton({
class: "std-button",
display: "inline-block",
- onClick: props.eventHandler.createIssueDividendsPopup,
+ onClick: openIssueDividendsPopup,
text: "Issue Dividends",
tooltip: "Manage the dividends that are paid out to shareholders (including yourself)",
});
diff --git a/src/DevMenu.jsx b/src/DevMenu.jsx
index 5f0e6caea..ea99a52e9 100644
--- a/src/DevMenu.jsx
+++ b/src/DevMenu.jsx
@@ -591,6 +591,13 @@ class DevMenuComponent extends Component {
});
}
+ addCorporationResearch() {
+ if(!Player.corporation) return;
+ Player.corporation.divisions.forEach(div => {
+ div.sciResearch.qty += 1e10;
+ });
+ }
+
specificContract() {
generateContract({
problemType: this.state.codingcontract,
@@ -1181,6 +1188,11 @@ class DevMenuComponent extends Component {
Finish products
+
+
+ Tons of research
+
+
From 3ba04220e146b1850b7765e01d46b30d540d8658 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Mon, 30 Aug 2021 03:18:12 -0400
Subject: [PATCH 15/30] I made a mistake but caught it.
---
src/Corporation/ui/IndustryWarehouse.tsx | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)
diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx
index ac5c0d2d0..9ac826feb 100644
--- a/src/Corporation/ui/IndustryWarehouse.tsx
+++ b/src/Corporation/ui/IndustryWarehouse.tsx
@@ -94,15 +94,8 @@ function ProductComponent(props: IProductProps): React.ReactElement {
});
}
- function openMaterialMarketTaPopup(): void {
- const popupId = "cmpy-mgmt-export-popup";
- createPopup(popupId, MaterialMarketTaPopup, {
- mat: product,
- industry: division,
- corp: props.corp,
- popupId: popupId,
- });
- }
+ // Market TA button
+ const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division);
// Unfinished Product
if (!product.fin) {
@@ -125,7 +118,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
{
division.hasResearch("Market-TA.I") &&
-
+
Market-TA
}
@@ -205,7 +198,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
{
division.hasResearch("Market-TA.I") &&
-
+
Market-TA
}
From cf72d72bb038ac2dd15037a6ce1a3a1d864274dd Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Mon, 30 Aug 2021 17:59:11 -0400
Subject: [PATCH 16/30] Finished converting all the popups.
---
src/Corporation/Corporation.jsx | 6 +-
src/Corporation/ui/CityTabs.tsx | 1 -
.../ui/CorporationUIEventHandler.js | 578 ------------------
src/Corporation/ui/HeaderTabs.tsx | 1 -
src/Corporation/ui/Industry.tsx | 6 +-
src/Corporation/ui/IndustryOffice.tsx | 1 -
src/Corporation/ui/IndustryOverview.tsx | 1 -
src/Corporation/ui/IndustryWarehouse.tsx | 65 +-
src/Corporation/ui/MainPanel.tsx | 1 -
src/Corporation/ui/Overview.tsx | 1 -
src/Corporation/ui/ProductMarketTaPopup.tsx | 105 ++++
src/Corporation/ui/PurchaseMaterialPopup.tsx | 130 ++++
src/Corporation/ui/Root.tsx | 5 +-
src/Corporation/ui/SellMaterialPopup.tsx | 129 ++++
src/Corporation/ui/SellProductPopup.tsx | 170 ++++++
15 files changed, 585 insertions(+), 615 deletions(-)
delete mode 100644 src/Corporation/ui/CorporationUIEventHandler.js
create mode 100644 src/Corporation/ui/ProductMarketTaPopup.tsx
create mode 100644 src/Corporation/ui/PurchaseMaterialPopup.tsx
create mode 100644 src/Corporation/ui/SellMaterialPopup.tsx
create mode 100644 src/Corporation/ui/SellProductPopup.tsx
diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx
index 764aac225..dbeb09ed5 100644
--- a/src/Corporation/Corporation.jsx
+++ b/src/Corporation/Corporation.jsx
@@ -47,7 +47,6 @@ import { yesNoBoxCreate,
// UI Related Imports
import React from "react";
import ReactDOM from "react-dom";
-import { CorporationEventHandler } from "./ui/CorporationUIEventHandler";
import { CorporationRoot } from "./ui/Root";
import { CorporationRouting } from "./ui/Routing";
@@ -1884,7 +1883,6 @@ Corporation.prototype.getStarterGuide = function() {
}
let corpRouting;
-let eventHandler;
let companyManagementDiv;
Corporation.prototype.createUI = function() {
companyManagementDiv = createElement("div", {
@@ -1895,13 +1893,12 @@ Corporation.prototype.createUI = function() {
document.getElementById("entire-game-container").appendChild(companyManagementDiv);
corpRouting = new CorporationRouting(this);
- eventHandler = new CorporationEventHandler(this, corpRouting);
this.rerender();
}
Corporation.prototype.rerender = function() {
- if (companyManagementDiv == null || corpRouting == null || eventHandler == null) {
+ if (companyManagementDiv == null || corpRouting == null) {
console.warn(`Corporation.rerender() called when companyManagementDiv, corpRouting, or eventHandler is null`);
return;
}
@@ -1910,7 +1907,6 @@ Corporation.prototype.rerender = function() {
ReactDOM.render( , companyManagementDiv);
}
diff --git a/src/Corporation/ui/CityTabs.tsx b/src/Corporation/ui/CityTabs.tsx
index 6f4e1205b..6e2255d8a 100644
--- a/src/Corporation/ui/CityTabs.tsx
+++ b/src/Corporation/ui/CityTabs.tsx
@@ -7,7 +7,6 @@ import { createPopup } from "../../ui/React/createPopup";
import { IDivision } from "../IDivision";
interface IProps {
- eventHandler: any;
routing: any;
onClicks: {[key: string]: () => void};
city: string; // currentCity
diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js
deleted file mode 100644
index 3f586d8cf..000000000
--- a/src/Corporation/ui/CorporationUIEventHandler.js
+++ /dev/null
@@ -1,578 +0,0 @@
-// Creates a class for handling UI events, such as clicks and keyboard events
-import { CorporationRouting } from "./Routing";
-import { Corporation,
- Industry,
- Warehouse,
- DividendMaxPercentage,
- IssueNewSharesCooldown } from "../Corporation";
-import { OfficeSpace } from "../OfficeSpace";
-
-import { Industries,
- IndustryStartingCosts,
- IndustryDescriptions,
-} from "../IndustryData";
-
-import { MaterialSizes } from "../MaterialSizes";
-
-import { Product } from "../Product";
-
-import { Cities } from "../../Locations/Cities";
-
-import { numeralWrapper } from "../../ui/numeralFormat";
-
-import { dialogBoxCreate } from "../../../utils/DialogBox";
-
-import { getRandomInt } from "../../../utils/helpers/getRandomInt";
-import { KEY } from "../../../utils/helpers/keyCodes";
-
-import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
-import { createElement } from "../../../utils/uiHelpers/createElement";
-import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
-import { createPopup } from "../../../utils/uiHelpers/createPopup";
-import { createPopupCloseButton } from "../../../utils/uiHelpers/createPopupCloseButton";
-import { getSelectText,
- getSelectValue } from "../../../utils/uiHelpers/getSelectData";
-import { removeElementById } from "../../../utils/uiHelpers/removeElementById";
-
-export class CorporationEventHandler {
- constructor(corp, routing) {
- if (!(corp instanceof Corporation)) {
- throw new Error(`CorporationEventHandler constructed without proper Corporation instance`);
- }
- if (!(routing instanceof CorporationRouting)) {
- throw new Error(`CorporationEventHandler constructed without proper CorporationRouting instance`);
- }
-
- this.corp = corp;
- this.routing = routing;
- }
-
- // Create a popup that lets the player use the Market TA research for Products
- createProductMarketTaPopup(product, industry) {
- const popupId = "cmpy-mgmt-marketta-popup";
- const markupLimit = product.rat / product.mku;
- const ta1 = createElement("p", {
- innerHTML: "Market-TA.I " +
- "The maximum sale price you can mark this up to is " +
- numeralWrapper.formatMoney(product.pCost + markupLimit) +
- ". This means that if you set the sale price higher than this, " +
- "you will begin to experience a loss in number of sales",
- });
-
- // Enable using Market-TA1 for automatically setting sale price
- const useTa1AutoSaleId = "cmpy-mgmt-marketa1-checkbox";
- const useTa1AutoSaleDiv = createElement("div", { display: "block" });
- const useTa1AutoSaleLabel = createElement("label", {
- color: "white",
- for: useTa1AutoSaleId,
- innerText: "Use Market-TA.I for Auto-Sale Price",
- tooltip: "If this is enabled, then this Product will automatically " +
- "be sold at the price identified by Market-TA.I (i.e. the price shown above)",
- })
- const useTa1AutoSaleCheckbox = createElement("input", {
- checked: product.marketTa1,
- id: useTa1AutoSaleId,
- margin: "3px",
- type: "checkbox",
- changeListener: (e) => {
- product.marketTa1 = e.target.checked;
- },
- });
- useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel);
- useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox);
-
- const closeBtn = createPopupCloseButton(popupId, {
- class: "std-button",
- display: "block",
- innerText: "Close",
- });
-
- if (industry.hasResearch("Market-TA.II")) {
- let updateTa2Text;
- const ta2Text = createElement("p");
- const ta2Input = createElement("input", {
- marginTop: "4px",
- onkeyup: (e) => {
- e.preventDefault();
- updateTa2Text();
- },
- type: "number",
- value: product.pCost,
- });
-
- // Function that updates the text in ta2Text element
- updateTa2Text = function() {
- const sCost = parseFloat(ta2Input.value);
- let markup = 1;
- if (sCost > product.pCost) {
- if ((sCost - product.pCost) > markupLimit) {
- markup = markupLimit / (sCost - product.pCost);
- }
- }
- ta2Text.innerHTML = `Market-TA.II ` +
- `If you sell at ${numeralWrapper.formatMoney(sCost)}, ` +
- `then you will sell ${numeralWrapper.format(markup, "0.00000")}x as much compared ` +
- `to if you sold at market price.`;
- }
- updateTa2Text();
-
- // Enable using Market-TA2 for automatically setting sale price
- const useTa2AutoSaleId = "cmpy-mgmt-marketa2-checkbox";
- const useTa2AutoSaleDiv = createElement("div", { display: "block" });
- const useTa2AutoSaleLabel = createElement("label", {
- color: "white",
- for: useTa2AutoSaleId,
- innerText: "Use Market-TA.II for Auto-Sale Price",
- tooltip: "If this is enabled, then this Product will automatically " +
- "be sold at the optimal price such that the amount sold matches the " +
- "amount produced. (i.e. the highest possible price, while still ensuring " +
- " that all produced materials will be sold)",
- })
- const useTa2AutoSaleCheckbox = createElement("input", {
- checked: product.marketTa2,
- id: useTa2AutoSaleId,
- margin: "3px",
- type: "checkbox",
- changeListener: (e) => {
- product.marketTa2 = e.target.checked;
- },
- });
- useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel);
- useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox);
-
- const ta2OverridesTa1 = createElement("p", {
- innerText: "Note that Market-TA.II overrides Market-TA.I. This means that if " +
- "both are enabled, then Market-TA.II will take effect, not Market-TA.I",
- });
-
- createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, ta2OverridesTa1, closeBtn]);
- } else {
- // Market-TA.I only
- createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]);
- }
- }
-
- // Create a popup that lets the player purchase a Material
- createPurchaseMaterialPopup(mat, industry, warehouse) {
- const corp = this.corp;
-
- const purchasePopupId = "cmpy-mgmt-material-purchase-popup";
- const txt = createElement("p", {
- innerHTML: "Enter the amount of " + mat.name + " you would like " +
- "to purchase per second. This material's cost changes constantly",
- });
- let confirmBtn;
- let input = createElement("input", {
- margin: "5px",
- placeholder: "Purchase amount",
- type: "number",
- value: mat.buy ? mat.buy : null,
- onkeyup: (e) => {
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
- },
- });
- confirmBtn = createElement("button", {
- innerText: "Confirm", class: "std-button",
- clickListener: () => {
- if (isNaN(input.value)) {
- dialogBoxCreate("Invalid amount");
- } else {
- mat.buy = parseFloat(input.value);
- if (isNaN(mat.buy)) {mat.buy = 0;}
- removeElementById(purchasePopupId);
- this.rerender();
- return false;
- }
- },
- });
- const clearButton = createElement("button", {
- innerText: "Clear Purchase", class: "std-button",
- clickListener: () => {
- mat.buy = 0;
- removeElementById(purchasePopupId);
- this.rerender();
- return false;
- },
- });
- const cancelBtn = createPopupCloseButton(purchasePopupId, {
- class: "std-button",
- innerText: "Cancel",
- });
-
- const elems = [txt, input, confirmBtn, clearButton, cancelBtn];
-
- if (industry.hasResearch("Bulk Purchasing")) {
- const bulkPurchaseInfo = createElement("p", {
- innerText: "Enter the amount of " + mat.name + " you would like " +
- "to bulk purchase. This purchases the specified amount instantly " +
- "(all at once).",
- });
-
- let bulkPurchaseCostTxt = createElement("p");
- function updateBulkPurchaseText(amount) {
- const parsedAmt = parseFloat(amount);
- const cost = parsedAmt * mat.bCost;
-
- const matSize = MaterialSizes[mat.name];
- const maxAmount = ((warehouse.size - warehouse.sizeUsed) / matSize);
-
- if (parsedAmt * matSize > maxAmount) {
- bulkPurchaseCostTxt.innerText = "Not enough warehouse space to purchase this amount";
- } else if (isNaN(cost)) {
- bulkPurchaseCostTxt.innerText = "Invalid put for Bulk Purchase amount";
- } else {
- bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(parsedAmt, "0,0.00")} of ` +
- `${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`;
- }
- }
-
- let bulkPurchaseConfirmBtn;
- const bulkPurchaseInput = createElement("input", {
- margin: "5px",
- placeholder: "Bulk Purchase amount",
- type: "number",
- onkeyup: (e) => {
- e.preventDefault();
- updateBulkPurchaseText(e.target.value);
- if (e.keyCode === KEY.ENTER) {bulkPurchaseConfirmBtn.click();}
- },
- });
-
- bulkPurchaseConfirmBtn = createElement("button", {
- class: "std-button",
- innerText: "Confirm Bulk Purchase",
- clickListener: () => {
- const amount = parseFloat(bulkPurchaseInput.value);
-
- const matSize = MaterialSizes[mat.name];
- const maxAmount = ((warehouse.size - warehouse.sizeUsed) / matSize);
- if (amount * matSize > maxAmount) {
- dialogBoxCreate(`You do not have enough warehouse size to fit this purchase`);
- return false;
- }
-
- if (isNaN(amount)) {
- dialogBoxCreate("Invalid input amount");
- } else {
- const cost = amount * mat.bCost;
- if (corp.funds.gt(cost)) {
- corp.funds = corp.funds.minus(cost);
- mat.qty += amount;
- } else {
- dialogBoxCreate(`You cannot afford this purchase.`);
- return false;
- }
-
- removeElementById(purchasePopupId);
- return false;
- }
- },
- })
-
- elems.push(bulkPurchaseInfo);
- elems.push(bulkPurchaseCostTxt);
- elems.push(bulkPurchaseInput);
- elems.push(bulkPurchaseConfirmBtn);
- }
-
- createPopup(purchasePopupId, elems);
- input.focus();
- }
-
- // Create a popup that let the player manage sales of a material
- createSellMaterialPopup(mat) {
- const sellPopupId = "cmpy-mgmt-material-sell-popup";
- const txt = createElement("p", {
- innerHTML: "Enter the maximum amount of " + mat.name + " you would like " +
- "to sell per second, as well as the price at which you would " +
- "like to sell at. " +
- "If the sell amount is set to 0, then the material will not be sold. If the sell price " +
- "if set to 0, then the material will be discarded " +
- "Setting the sell amount to 'MAX' will result in you always selling the " +
- "maximum possible amount of the material. " +
- "When setting the sell amount, you can use the 'PROD' variable to designate a dynamically " +
- "changing amount that depends on your production. For example, if you set the sell amount " +
- "to 'PROD-5' then you will always sell 5 less of the material than you produce. " +
- "When setting the sell price, you can use the 'MP' variable to designate a dynamically " +
- "changing price that depends on the market price. For example, if you set the sell price " +
- "to 'MP+10' then it will always be sold at $10 above the market price.",
- });
- const br = createElement("br");
- let confirmBtn;
- const inputQty = createElement("input", {
- type: "text", marginTop: "4px",
- value: mat.sllman[1] ? mat.sllman[1] : null,
- placeholder: "Sell amount",
- onkeyup: (e) => {
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
- },
- });
-
- let inputButtonInitValue = mat.sCost ? mat.sCost : null;
- if (mat.marketTa2) {
- inputButtonInitValue += " (Market-TA.II)";
- } else if (mat.marketTa1) {
- inputButtonInitValue += " (Market-TA.I)";
- }
-
- const inputPx = createElement("input", {
- type: "text", marginTop: "4px",
- value: inputButtonInitValue,
- placeholder: "Sell price",
- onkeyup: (e) => {
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
- },
- });
- confirmBtn = createElement("button", {
- class: "std-button",
- innerText: "Confirm",
- clickListener: () => {
- //Parse price
- let cost = inputPx.value.replace(/\s+/g, '');
- cost = cost.replace(/[^-()\d/*+.MP]/g, ''); //Sanitize cost
- let temp = cost.replace(/MP/g, mat.bCost);
- try {
- temp = eval(temp);
- } catch(e) {
- dialogBoxCreate("Invalid value or expression for sell price field: " + e);
- return false;
- }
-
- if (temp == null || isNaN(temp)) {
- dialogBoxCreate("Invalid value or expression for sell price field");
- return false;
- }
-
- if (cost.includes("MP")) {
- mat.sCost = cost; //Dynamically evaluated
- } else {
- mat.sCost = temp;
- }
-
- //Parse quantity
- if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) {
- let qty = inputQty.value.replace(/\s+/g, '');
- qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, '');
- let tempQty = qty.replace(/MAX/g, 1);
- tempQty = tempQty.replace(/PROD/g, 1);
- try {
- tempQty = eval(tempQty);
- } catch(e) {
- dialogBoxCreate("Invalid value or expression for sell price field: " + e);
- return false;
- }
-
- if (tempQty == null || isNaN(tempQty)) {
- dialogBoxCreate("Invalid value or expression for sell price field");
- return false;
- }
-
- mat.sllman[0] = true;
- mat.sllman[1] = qty; //Use sanitized input
- } else if (isNaN(inputQty.value)) {
- dialogBoxCreate("Invalid value for sell quantity field! Must be numeric or 'MAX'");
- return false;
- } else {
- var qty = parseFloat(inputQty.value);
- if (isNaN(qty)) {qty = 0;}
- if (qty === 0) {
- mat.sllman[0] = false;
- mat.sllman[1] = 0;
- } else {
- mat.sllman[0] = true;
- mat.sllman[1] = qty;
- }
- }
-
- removeElementById(sellPopupId);
- this.rerender();
- return false;
- },
- });
- const cancelBtn = createPopupCloseButton(sellPopupId, {
- class: "std-button",
- innerText: "Cancel",
- });
-
- createPopup(sellPopupId, [txt, br, inputQty, inputPx, confirmBtn, cancelBtn]);
- inputQty.focus();
- }
-
- // Create a popup that lets the player manage sales of the product
- createSellProductPopup(product, city) {
- const popupId = "cmpy-mgmt-sell-product-popup";
- const txt = createElement("p", {
- innerHTML:"Enter the maximum amount of " + product.name + " you would like " +
- "to sell per second, as well as the price at which you would like to " +
- "sell it at. " +
- "If the sell amount is set to 0, then the product will not be sold. If the " +
- "sell price is set to 0, then the product will be discarded. " +
- "Setting the sell amount to 'MAX' will result in you always selling the " +
- "maximum possible amount of the material. " +
- "When setting the sell amount, you can use the 'PROD' variable to designate a " +
- "dynamically changing amount that depends on your production. For example, " +
- "if you set the sell amount to 'PROD-1' then you will always sell 1 less of " +
- "the material than you produce. " +
- "When setting the sell price, you can use the 'MP' variable to set a " +
- "dynamically changing price that depends on the Product's estimated " +
- "market price. For example, if you set it to 'MP*5' then it " +
- "will always be sold at five times the estimated market price.",
- });
- let confirmBtn;
- const inputQty = createElement("input", {
- margin: "5px 0px 5px 0px",
- placeholder: "Sell amount",
- type: "text",
- value: product.sllman[city][1] ? product.sllman[city][1] : null,
- onkeyup: (e) => {
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
- },
- });
-
- let inputButtonInitValue = product.sCost ? product.sCost : null;
- if (product.marketTa2) {
- inputButtonInitValue += " (Market-TA.II)";
- } else if (product.marketTa1) {
- inputButtonInitValue += " (Market-TA.I)";
- }
-
- const inputPx = createElement("input", {
- margin: "5px 0px 5px 0px",
- placeholder: "Sell price",
- type: "text",
- value: inputButtonInitValue,
- onkeyup: (e) => {
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) {confirmBtn.click();}
- },
- });
- const checkboxDiv = createElement("div", {
- border: "1px solid white",
- display: "inline-block",
- })
- const checkboxLabel = createElement("label", {
- for: popupId + "-checkbox",
- innerText: "Use same 'Sell Amount' for all cities",
- });
- const checkbox = createElement("input", {
- checked: true,
- id: popupId + "-checkbox",
- margin: "2px",
- type: "checkbox",
- });
- checkboxDiv.appendChild(checkboxLabel);
- checkboxDiv.appendChild(checkbox);
-
- confirmBtn = createElement("button", {
- class: "std-button",
- innerText: "Confirm",
- clickListener: () => {
- //Parse price
- if (inputPx.value.includes("MP")) {
- //Dynamically evaluated quantity. First test to make sure its valid
- //Sanitize input, then replace dynamic variables with arbitrary numbers
- var price = inputPx.value.replace(/\s+/g, '');
- price = price.replace(/[^-()\d/*+.MP]/g, '');
- var temp = price.replace(/MP/g, 1);
- try {
- temp = eval(temp);
- } catch(e) {
- dialogBoxCreate("Invalid value or expression for sell quantity field: " + e);
- return false;
- }
- if (temp == null || isNaN(temp)) {
- dialogBoxCreate("Invalid value or expression for sell quantity field.");
- return false;
- }
- product.sCost = price; //Use sanitized price
- } else {
- var cost = parseFloat(inputPx.value);
- if (isNaN(cost)) {
- dialogBoxCreate("Invalid value for sell price field");
- return false;
- }
- product.sCost = cost;
- }
-
- // Array of all cities. Used later
- const cities = Object.keys(Cities);
-
- // Parse quantity
- if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) {
- //Dynamically evaluated quantity. First test to make sure its valid
- var qty = inputQty.value.replace(/\s+/g, '');
- qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, '');
- var temp = qty.replace(/MAX/g, 1);
- temp = temp.replace(/PROD/g, 1);
- try {
- temp = eval(temp);
- } catch(e) {
- dialogBoxCreate("Invalid value or expression for sell price field: " + e);
- return false;
- }
-
- if (temp == null || isNaN(temp)) {
- dialogBoxCreate("Invalid value or expression for sell price field");
- return false;
- }
- if (checkbox.checked) {
- for (let i = 0; i < cities.length; ++i) {
- const tempCity = cities[i];
- product.sllman[tempCity][0] = true;
- product.sllman[tempCity][1] = qty; //Use sanitized input
- }
- } else {
- product.sllman[city][0] = true;
- product.sllman[city][1] = qty; //Use sanitized input
- }
- } else if (isNaN(inputQty.value)) {
- dialogBoxCreate("Invalid value for sell quantity field! Must be numeric");
- return false;
- } else {
- var qty = parseFloat(inputQty.value);
- if (isNaN(qty)) {qty = 0;}
- if (qty === 0) {
- if (checkbox.checked) {
- for (let i = 0; i < cities.length; ++i) {
- const tempCity = cities[i];
- product.sllman[tempCity][0] = false;
- }
- } else {
- product.sllman[city][0] = false;
- }
- } else {
- if (checkbox.checked) {
- for (let i = 0; i < cities.length; ++i) {
- const tempCity = cities[i];
- product.sllman[tempCity][0] = true;
- product.sllman[tempCity][1] = qty;
- }
- } else {
- product.sllman[city][0] = true;
- product.sllman[city][1] = qty;
- }
- }
- }
-
- removeElementById(popupId);
- this.rerender();
- return false;
- },
- });
- const cancelBtn = createPopupCloseButton(popupId, { class: "std-button" });
-
- const linebreak1 = createElement("br");
-
- createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn, linebreak1,
- checkboxDiv]);
- inputQty.focus();
- }
-
- rerender() {
- this.corp.rerender();
- }
-}
diff --git a/src/Corporation/ui/HeaderTabs.tsx b/src/Corporation/ui/HeaderTabs.tsx
index b22bc0277..28d61c6c7 100644
--- a/src/Corporation/ui/HeaderTabs.tsx
+++ b/src/Corporation/ui/HeaderTabs.tsx
@@ -9,7 +9,6 @@ import { createPopup } from "../../ui/React/createPopup";
interface IProps {
corp: any;
- eventHandler: any;
routing: any;
}
diff --git a/src/Corporation/ui/Industry.tsx b/src/Corporation/ui/Industry.tsx
index ef2dac622..e68fcd2d4 100644
--- a/src/Corporation/ui/Industry.tsx
+++ b/src/Corporation/ui/Industry.tsx
@@ -8,7 +8,6 @@ import { IndustryWarehouse } from "./IndustryWarehouse";
interface IProps {
routing: any;
- eventHandler: any;
corp: any;
currentCity: string;
}
@@ -19,12 +18,10 @@ export function Industry(props: IProps): React.ReactElement {
@@ -32,8 +29,7 @@ export function Industry(props: IProps): React.ReactElement {
+ currentCity={props.currentCity} />
)
diff --git a/src/Corporation/ui/IndustryOffice.tsx b/src/Corporation/ui/IndustryOffice.tsx
index a71b12096..a88fb883e 100644
--- a/src/Corporation/ui/IndustryOffice.tsx
+++ b/src/Corporation/ui/IndustryOffice.tsx
@@ -15,7 +15,6 @@ import { ThrowPartyPopup } from "./ThrowPartyPopup";
interface IProps {
routing: any;
- eventHandler: any;
corp: any;
currentCity: string;
}
diff --git a/src/Corporation/ui/IndustryOverview.tsx b/src/Corporation/ui/IndustryOverview.tsx
index 06d4d2f44..258cbb879 100644
--- a/src/Corporation/ui/IndustryOverview.tsx
+++ b/src/Corporation/ui/IndustryOverview.tsx
@@ -13,7 +13,6 @@ import { createPopup } from "../../ui/React/createPopup";
interface IProps {
routing: any;
- eventHandler: any;
corp: any;
currentCity: string;
}
diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx
index 9ac826feb..a44259540 100644
--- a/src/Corporation/ui/IndustryWarehouse.tsx
+++ b/src/Corporation/ui/IndustryWarehouse.tsx
@@ -11,6 +11,10 @@ import { DiscontinueProductPopup } from "./DiscontinueProductPopup";
import { ExportPopup } from "./ExportPopup";
import { LimitProductProductionPopup } from "./LimitProductProductionPopup";
import { MaterialMarketTaPopup } from "./MaterialMarketTaPopup";
+import { SellMaterialPopup } from "./SellMaterialPopup";
+import { SellProductPopup } from "./SellProductPopup";
+import { PurchaseMaterialPopup } from "./PurchaseMaterialPopup";
+import { ProductMarketTaPopup } from "./ProductMarketTaPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
@@ -23,7 +27,6 @@ interface IProductProps {
division: any;
city: string;
product: any;
- eventHandler: any;
}
// Creates the UI for a single Product type
@@ -32,7 +35,6 @@ function ProductComponent(props: IProductProps): React.ReactElement {
const division = props.division;
const city = props.city;
const product = props.product;
- const eventHandler = props.eventHandler;
// Numeraljs formatters
const nf = "0.000";
@@ -67,7 +69,15 @@ function ProductComponent(props: IProductProps): React.ReactElement {
sellButtonText += (" @ " + numeralWrapper.format(product.sCost, "$0.000a"));
}
}
- const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product, city);
+
+ function openSellProductPopup(): void {
+ const popupId = "cmpy-mgmt-limit-product-production-popup";
+ createPopup(popupId, SellProductPopup, {
+ product: product,
+ city: city,
+ popupId: popupId,
+ });
+ }
// Limit Production button
let limitProductionButtonText = "Limit Production";
@@ -94,8 +104,14 @@ function ProductComponent(props: IProductProps): React.ReactElement {
});
}
- // Market TA button
- const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division);
+ function openProductMarketTaPopup(): void {
+ const popupId = "cmpy-mgmt-marketta-popup";
+ createPopup(popupId, ProductMarketTaPopup, {
+ product: product,
+ industry: division,
+ popupId: popupId,
+ });
+ }
// Unfinished Product
if (!product.fin) {
@@ -107,7 +123,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
-
+
{sellButtonText}
@@ -118,7 +134,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
{
division.hasResearch("Market-TA.I") &&
-
+
Market-TA
}
@@ -187,7 +203,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
-
+
{sellButtonText}
@@ -198,7 +214,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
{
division.hasResearch("Market-TA.I") &&
-
+
Market-TA
}
@@ -213,7 +229,6 @@ interface IMaterialProps {
warehouse: any;
city: string;
mat: any;
- eventHandler: any;
}
// Creates the UI for a single Material type
@@ -223,7 +238,6 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
const warehouse = props.warehouse;
const city = props.city;
const mat = props.mat;
- const eventHandler = props.eventHandler;
const markupLimit = mat.getMarkupLimit();
const office = division.offices[city];
if (!(office instanceof OfficeSpace)) {
@@ -245,7 +259,17 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
// Purchase material button
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
- const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
+
+ function openPurchaseMaterialPopup() {
+ const popupId = "cmpy-mgmt-material-purchase-popup";
+ createPopup(popupId, PurchaseMaterialPopup, {
+ mat: mat,
+ industry: division,
+ warehouse: warehouse,
+ corp: props.corp,
+ popupId: popupId,
+ });
+ }
function openExportPopup() {
const popupId = "cmpy-mgmt-export-popup";
@@ -280,7 +304,15 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
} else {
sellButtonText = "Sell (0.000/0.000)";
}
- const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
+
+ function openSellMaterialPopup(): void {
+ const popupId = "cmpy-mgmt-material-sell-popup";
+ createPopup(popupId, SellMaterialPopup, {
+ mat: mat,
+ corp: props.corp,
+ popupId: popupId,
+ });
+ }
function openMaterialMarketTaPopup(): void {
const popupId = "cmpy-mgmt-export-popup";
@@ -334,7 +366,7 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
-
+
{purchaseButtonText}
{
tutorial &&
@@ -352,7 +384,7 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
}
-
+
{sellButtonText}
@@ -372,7 +404,6 @@ interface IProps {
corp: any;
routing: any;
currentCity: string;
- eventHandler: any;
}
export function IndustryWarehouse(props: IProps): React.ReactElement {
@@ -485,7 +516,6 @@ export function IndustryWarehouse(props: IProps): React.ReactElement {
city={props.currentCity}
corp={corp}
division={division}
- eventHandler={props.eventHandler}
key={matName}
mat={warehouse.materials[matName]}
warehouse={warehouse} />);
@@ -502,7 +532,6 @@ export function IndustryWarehouse(props: IProps): React.ReactElement {
city={props.currentCity}
corp={corp}
division={division}
- eventHandler={props.eventHandler}
key={productName}
product={division.products[productName]}
/>);
diff --git a/src/Corporation/ui/MainPanel.tsx b/src/Corporation/ui/MainPanel.tsx
index e01d846d1..c1ca735da 100644
--- a/src/Corporation/ui/MainPanel.tsx
+++ b/src/Corporation/ui/MainPanel.tsx
@@ -14,7 +14,6 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
corp: any;
- eventHandler: any;
routing: any;
player: IPlayer;
}
diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx
index 2d035d448..c9ad865d8 100644
--- a/src/Corporation/ui/Overview.tsx
+++ b/src/Corporation/ui/Overview.tsx
@@ -20,7 +20,6 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
corp: any;
- eventHandler: any;
player: IPlayer;
}
diff --git a/src/Corporation/ui/ProductMarketTaPopup.tsx b/src/Corporation/ui/ProductMarketTaPopup.tsx
new file mode 100644
index 000000000..04b254e84
--- /dev/null
+++ b/src/Corporation/ui/ProductMarketTaPopup.tsx
@@ -0,0 +1,105 @@
+import React, { useState } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+import { numeralWrapper } from "../../ui/numeralFormat";
+
+interface IProps {
+ product: any;
+ industry: any;
+ popupId: string;
+}
+
+function MarketTA2(props: IProps): React.ReactElement {
+ const markupLimit = props.product.rat / props.product.mku;
+ const [value, setValue] = useState(props.product.pCost);
+ const setRerender = useState(false)[1];
+ function rerender(): void {
+ setRerender(old => !old);
+ }
+
+ function onChange(event: React.ChangeEvent): void {
+ setValue(event.target.value);
+ }
+
+
+ function onCheckedChange(event: React.ChangeEvent): void {
+ props.product.marketTa2 = event.target.checked;
+ rerender();
+ }
+
+ const sCost = parseFloat(value);
+ let markup = 1;
+ if (sCost > props.product.pCost) {
+ if ((sCost - props.product.pCost) > markupLimit) {
+ markup = markupLimit / (sCost - props.product.pCost);
+ }
+ }
+
+ return (<>
+
+ Market-TA.II
+ If you sell at {numeralWrapper.formatMoney(sCost)},
+ then you will sell {numeralWrapper.format(markup, "0.00000")}x as much compared
+ to if you sold at market price.
+
+
+
+
+ Use Market-TA.II for Auto-Sale Price
+
+ If this is enabled, then this Product will automatically
+ be sold at the optimal price such that the amount sold matches the
+ amount produced. (i.e. the highest possible price, while still ensuring
+ that all produced materials will be sold).
+
+
+
+
+
+ Note that Market-TA.II overrides Market-TA.I. This means that if
+ both are enabled, then Market-TA.II will take effect, not Market-TA.I
+
+ >);
+}
+
+// Create a popup that lets the player use the Market TA research for Products
+export function ProductMarketTaPopup(props: IProps): React.ReactElement {
+ const markupLimit = props.product.rat / props.product.mku;
+ const setRerender = useState(false)[1];
+ function rerender(): void {
+ setRerender(old => !old);
+ }
+
+ function onChange(event: React.ChangeEvent): void {
+ props.product.marketTa1 = event.target.checked;
+ rerender();
+ }
+
+ return (<>
+
+ Market-TA.I
+ The maximum sale price you can mark this up to
+ is {numeralWrapper.formatMoney(props.product.pCost + markupLimit)}.
+ This means that if you set the sale price higher than this,
+ you will begin to experience a loss in number of sales.
+
+
+
+ Use Market-TA.I for Auto-Sale Price
+
+ If this is enabled, then this Product will automatically
+ be sold at the price identified by Market-TA.I
+ (i.e. the price shown above).
+
+
+
+
+ {props.industry.hasResearch("Market-TA.II") && }
+ >);
+}
diff --git a/src/Corporation/ui/PurchaseMaterialPopup.tsx b/src/Corporation/ui/PurchaseMaterialPopup.tsx
new file mode 100644
index 000000000..891de894c
--- /dev/null
+++ b/src/Corporation/ui/PurchaseMaterialPopup.tsx
@@ -0,0 +1,130 @@
+import React, { useState } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+import { MaterialSizes } from "../MaterialSizes";
+import { numeralWrapper } from "../../ui/numeralFormat";
+
+interface IBulkPurchaseTextProps {
+ warehouse: any;
+ mat: any;
+ amount: string;
+}
+
+function BulkPurchaseText(props: IBulkPurchaseTextProps): React.ReactElement {
+ const parsedAmt = parseFloat(props.amount);
+ const cost = parsedAmt * props.mat.bCost;
+
+ const matSize = MaterialSizes[props.mat.name];
+ const maxAmount = ((props.warehouse.size - props.warehouse.sizeUsed) / matSize);
+
+ if (parsedAmt * matSize > maxAmount) {
+ return (<>Not enough warehouse space to purchase this amount>);
+ } else if (isNaN(cost)) {
+ return (<>Invalid put for Bulk Purchase amount>);
+ } else {
+ return (<>Purchasing {numeralWrapper.format(parsedAmt, "0,0.00")} of
+ {props.mat.name} will cost {numeralWrapper.formatMoney(cost)}>);
+ }
+}
+
+interface IProps {
+ mat: any;
+ industry: any;
+ warehouse: any;
+ corp: any;
+ popupId: string;
+}
+
+function BulkPurchase(props: IProps): React.ReactElement {
+ const [buyAmt, setBuyAmt] = useState('');
+
+ function bulkPurchase(): void {
+ const amount = parseFloat(buyAmt);
+
+ const matSize = MaterialSizes[props.mat.name];
+ const maxAmount = ((props.warehouse.size - props.warehouse.sizeUsed) / matSize);
+ if (amount * matSize > maxAmount) {
+ dialogBoxCreate(`You do not have enough warehouse size to fit this purchase`);
+ return;
+ }
+
+ if (isNaN(amount)) {
+ dialogBoxCreate("Invalid input amount");
+ } else {
+ const cost = amount * props.mat.bCost;
+ if (props.corp.funds.gt(cost)) {
+ props.corp.funds = props.corp.funds.minus(cost);
+ props.mat.qty += amount;
+ } else {
+ dialogBoxCreate(`You cannot afford this purchase.`);
+ return;
+ }
+
+ removePopup(props.popupId);
+ }
+ }
+
+ function onKeyDown(event: React.KeyboardEvent): void {
+ if (event.keyCode === 13) bulkPurchase();
+ }
+
+ function onChange(event: React.ChangeEvent): void {
+ setBuyAmt(event.target.value);
+ }
+
+ return (<>
+
+ Enter the amount of {props.mat.name} you would like
+ to bulk purchase. This purchases the specified amount instantly
+ (all at once).
+
+
+
+ Confirm Bulk Purchase
+ >);
+}
+
+// Create a popup that lets the player purchase a Material
+export function PurchaseMaterialPopup(props: IProps): React.ReactElement {
+ const [buyAmt, setBuyAmt] = useState(props.mat.buy ? props.mat.buy : null);
+
+ function purchaseMaterial(): void {
+ if (isNaN(parseFloat(buyAmt))) {
+ dialogBoxCreate("Invalid amount");
+ } else {
+ props.mat.buy = parseFloat(buyAmt);
+ if (isNaN(props.mat.buy)) props.mat.buy = 0;
+ removePopup(props.popupId);
+ }
+ }
+
+ function clearPurchase(): void {
+ props.mat.buy = 0;
+ removePopup(props.popupId);
+ }
+
+ function onKeyDown(event: React.KeyboardEvent): void {
+ if (event.keyCode === 13) purchaseMaterial();
+ }
+
+ function onChange(event: React.ChangeEvent): void {
+ setBuyAmt(event.target.value);
+ }
+
+ return (<>
+
+ Enter the amount of {props.mat.name} you would like
+ to purchase per second. This material's cost changes constantly.
+
+
+ Confirm
+ Clear Purchase
+ {props.industry.hasResearch("Bulk Purchasing") && }
+ >);
+}
diff --git a/src/Corporation/ui/Root.tsx b/src/Corporation/ui/Root.tsx
index f7c3cb348..f41b93a27 100644
--- a/src/Corporation/ui/Root.tsx
+++ b/src/Corporation/ui/Root.tsx
@@ -7,7 +7,6 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
corp: any;
- eventHandler: any;
routing: any;
player: IPlayer;
}
@@ -15,8 +14,8 @@ interface IProps {
export function CorporationRoot(props: IProps): React.ReactElement {
return (
-
-
+
+
)
}
diff --git a/src/Corporation/ui/SellMaterialPopup.tsx b/src/Corporation/ui/SellMaterialPopup.tsx
new file mode 100644
index 000000000..1859ed103
--- /dev/null
+++ b/src/Corporation/ui/SellMaterialPopup.tsx
@@ -0,0 +1,129 @@
+import React, { useState } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+
+function initialPrice(mat: any): string {
+ let val = mat.sCost ? mat.sCost : '';
+ if (mat.marketTa2) {
+ val += " (Market-TA.II)";
+ } else if (mat.marketTa1) {
+ val += " (Market-TA.I)";
+ }
+ return val;
+}
+
+interface IProps {
+ mat: any;
+ corp: any;
+ popupId: string;
+}
+
+// Create a popup that let the player manage sales of a material
+export function SellMaterialPopup(props: IProps): React.ReactElement {
+ const [amt, setAmt] = useState(props.mat.sllman[1] ? props.mat.sllman[1] : '');
+ const [price, setPrice] = useState(initialPrice(props.mat));
+
+ function sellMaterial(): void {
+ let p = price;
+ let qty = amt;
+ if(p === '') p = '0';
+ if(qty === '') qty = '0';
+ let cost = p.replace(/\s+/g, '');
+ cost = cost.replace(/[^-()\d/*+.MP]/g, ''); //Sanitize cost
+ let temp = cost.replace(/MP/g, props.mat.bCost);
+ try {
+ temp = eval(temp);
+ } catch(e) {
+ dialogBoxCreate("Invalid value or expression for sell price field: " + e);
+ return;
+ }
+
+ if (temp == null || isNaN(parseFloat(temp))) {
+ dialogBoxCreate("Invalid value or expression for sell price field");
+ return;
+ }
+
+ if (cost.includes("MP")) {
+ props.mat.sCost = cost; //Dynamically evaluated
+ } else {
+ props.mat.sCost = temp;
+ }
+
+ //Parse quantity
+ if (qty.includes("MAX") || qty.includes("PROD")) {
+ let q = qty.replace(/\s+/g, '');
+ q = q.replace(/[^-()\d/*+.MAXPROD]/g, '');
+ let tempQty = q.replace(/MAX/g, '1');
+ tempQty = tempQty.replace(/PROD/g, '1');
+ try {
+ tempQty = eval(tempQty);
+ } catch(e) {
+ dialogBoxCreate("Invalid value or expression for sell price field: " + e);
+ return;
+ }
+
+ if (tempQty == null || isNaN(parseFloat(tempQty))) {
+ dialogBoxCreate("Invalid value or expression for sell price field");
+ return;
+ }
+
+ props.mat.sllman[0] = true;
+ props.mat.sllman[1] = q; //Use sanitized input
+ } else if (isNaN(parseFloat(qty))) {
+ dialogBoxCreate("Invalid value for sell quantity field! Must be numeric or 'MAX'");
+ return;
+ } else {
+ let q = parseFloat(qty);
+ if (isNaN(q)) {q = 0;}
+ if (q === 0) {
+ props.mat.sllman[0] = false;
+ props.mat.sllman[1] = 0;
+ } else {
+ props.mat.sllman[0] = true;
+ props.mat.sllman[1] = q;
+ }
+ }
+
+ removePopup(props.popupId);
+ }
+
+ function onAmtChange(event: React.ChangeEvent): void {
+ setAmt(event.target.value);
+ }
+
+ function onPriceChange(event: React.ChangeEvent): void {
+ setPrice(event.target.value);
+ }
+
+ function onKeyDown(event: React.KeyboardEvent): void {
+ if (event.keyCode === 13) sellMaterial();
+ }
+
+ return (<>
+
+Enter the maximum amount of {props.mat.name} you would like
+to sell per second, as well as the price at which you would
+like to sell at.
+If the sell amount is set to 0, then the material will not be sold. If the sell price
+if set to 0, then the material will be discarded
+Setting the sell amount to 'MAX' will result in you always selling the
+maximum possible amount of the material.
+When setting the sell amount, you can use the 'PROD' variable to designate a dynamically
+changing amount that depends on your production. For example, if you set the sell amount
+to 'PROD-5' then you will always sell 5 less of the material than you produce.
+When setting the sell price, you can use the 'MP' variable to designate a dynamically
+changing price that depends on the market price. For example, if you set the sell price
+to 'MP+10' then it will always be sold at $10 above the market price.
+
+
+
+
+ Confirm
+ >);
+}
diff --git a/src/Corporation/ui/SellProductPopup.tsx b/src/Corporation/ui/SellProductPopup.tsx
new file mode 100644
index 000000000..72ea3556a
--- /dev/null
+++ b/src/Corporation/ui/SellProductPopup.tsx
@@ -0,0 +1,170 @@
+import React, { useState } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+import { Cities } from "../../Locations/Cities";
+
+function initialPrice(product: any): string {
+ let val = product.sCost ? product.sCost : '';
+ if (product.marketTa2) {
+ val += " (Market-TA.II)";
+ } else if (product.marketTa1) {
+ val += " (Market-TA.I)";
+ }
+ return val;
+}
+
+interface IProps {
+ product: any;
+ city: string;
+ popupId: string;
+}
+
+// Create a popup that let the player manage sales of a material
+export function SellProductPopup(props: IProps): React.ReactElement {
+ const [checked, setChecked] = useState(true);
+ const [iQty, setQty] = useState(props.product.sllman[props.city][1] ? props.product.sllman[props.city][1] : '');
+ const [px, setPx] = useState(initialPrice(props.product));
+
+ function onCheckedChange(event: React.ChangeEvent): void {
+ setChecked(event.target.checked);
+ }
+
+ function sellProduct(): void {
+ //Parse price
+ if (px.includes("MP")) {
+ //Dynamically evaluated quantity. First test to make sure its valid
+ //Sanitize input, then replace dynamic variables with arbitrary numbers
+ var price = px.replace(/\s+/g, '');
+ price = price.replace(/[^-()\d/*+.MP]/g, '');
+ var temp = price.replace(/MP/g, '1');
+ try {
+ temp = eval(temp);
+ } catch(e) {
+ dialogBoxCreate("Invalid value or expression for sell quantity field: " + e);
+ return;
+ }
+ if (temp == null || isNaN(parseFloat(temp))) {
+ dialogBoxCreate("Invalid value or expression for sell quantity field.");
+ return;
+ }
+ props.product.sCost = price; //Use sanitized price
+ } else {
+ var cost = parseFloat(px);
+ if (isNaN(cost)) {
+ dialogBoxCreate("Invalid value for sell price field");
+ return;
+ }
+ props.product.sCost = cost;
+ }
+
+ // Array of all cities. Used later
+ const cities = Object.keys(Cities);
+
+ // Parse quantity
+ if (iQty.includes("MAX") || iQty.includes("PROD")) {
+ //Dynamically evaluated quantity. First test to make sure its valid
+ var qty = iQty.replace(/\s+/g, '');
+ qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, '');
+ var temp = qty.replace(/MAX/g, '1');
+ temp = temp.replace(/PROD/g, '1');
+ try {
+ temp = eval(temp);
+ } catch(e) {
+ dialogBoxCreate("Invalid value or expression for sell price field: " + e);
+ return;
+ }
+
+ if (temp == null || isNaN(parseFloat(temp))) {
+ dialogBoxCreate("Invalid value or expression for sell price field");
+ return;
+ }
+ if (checked) {
+ for (let i = 0; i < cities.length; ++i) {
+ const tempCity = cities[i];
+ props.product.sllman[tempCity][0] = true;
+ props.product.sllman[tempCity][1] = qty; //Use sanitized input
+ }
+ } else {
+ props.product.sllman[props.city][0] = true;
+ props.product.sllman[props.city][1] = qty; //Use sanitized input
+ }
+ } else if (isNaN(parseFloat(iQty))) {
+ dialogBoxCreate("Invalid value for sell quantity field! Must be numeric");
+ return;
+ } else {
+ let qty = parseFloat(iQty);
+ if (isNaN(qty)) {qty = 0;}
+ if (qty === 0) {
+ if (checked) {
+ for (let i = 0; i < cities.length; ++i) {
+ const tempCity = cities[i];
+ props.product.sllman[tempCity][0] = false;
+ props.product.sllman[tempCity][1] = '';
+ }
+ } else {
+ props.product.sllman[props.city][0] = false;
+ props.product.sllman[props.city][1] = '';
+ }
+ } else {
+ if (checked) {
+ for (let i = 0; i < cities.length; ++i) {
+ const tempCity = cities[i];
+ props.product.sllman[tempCity][0] = true;
+ props.product.sllman[tempCity][1] = qty;
+ }
+ } else {
+ props.product.sllman[props.city][0] = true;
+ props.product.sllman[props.city][1] = qty;
+ }
+ }
+ }
+
+ removePopup(props.popupId);
+ }
+
+ function onAmtChange(event: React.ChangeEvent): void {
+ setQty(event.target.value);
+ }
+
+ function onPriceChange(event: React.ChangeEvent): void {
+ setPx(event.target.value);
+ }
+
+ function onKeyDown(event: React.KeyboardEvent): void {
+ if (event.keyCode === 13) sellProduct();
+ }
+
+ return (<>
+
+Enter the maximum amount of {props.product.name} you would like
+to sell per second, as well as the price at which you would like to
+sell it at.
+If the sell amount is set to 0, then the product will not be sold. If the
+sell price is set to 0, then the product will be discarded.
+Setting the sell amount to 'MAX' will result in you always selling the
+maximum possible amount of the material.
+When setting the sell amount, you can use the 'PROD' variable to designate a
+dynamically changing amount that depends on your production. For example,
+if you set the sell amount to 'PROD-1' then you will always sell 1 less of
+the material than you produce.
+When setting the sell price, you can use the 'MP' variable to set a
+dynamically changing price that depends on the Product's estimated
+market price. For example, if you set it to 'MP*5' then it
+will always be sold at five times the estimated market price.
+
+
+
+
+ Confirm
+
+ Use same 'Sell Amount' for all cities
+
+
+ >);
+}
From a721c49e1d70ed6fb6aa2aa26f8379f65cd18873 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Tue, 31 Aug 2021 00:54:57 -0400
Subject: [PATCH 17/30] more conversion
---
package-lock.json | 16 +-
package.json | 1 +
src/Corporation/Corporation.jsx | 105 --
src/Corporation/ui/IndustryOverview.tsx | 11 +-
src/Corporation/ui/ResearchPopup.tsx | 110 ++
src/ThirdParty/Treant.js | 2171 -----------------------
src/ThirdParty/treant-js.d.ts | 1 +
7 files changed, 135 insertions(+), 2280 deletions(-)
create mode 100644 src/Corporation/ui/ResearchPopup.tsx
delete mode 100644 src/ThirdParty/Treant.js
create mode 100644 src/ThirdParty/treant-js.d.ts
diff --git a/package-lock.json b/package-lock.json
index d8ff730c9..c01809608 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,11 @@
{
"name": "bitburner",
- "version": "0.52.8",
+ "version": "0.52.9",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "bitburner",
- "version": "0.52.8",
+ "version": "0.52.9",
"hasInstallScript": true,
"license": "SEE LICENSE IN license.txt",
"dependencies": {
@@ -48,6 +47,7 @@
"react-modal": "^3.12.1",
"sprintf-js": "^1.1.1",
"tapable": "^1.0.0",
+ "treant-js": "^1.0.1",
"uuid": "^3.2.1",
"w3c-blob": "0.0.1"
},
@@ -21562,6 +21562,11 @@
"node": ">=6"
}
},
+ "node_modules/treant-js": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/treant-js/-/treant-js-1.0.1.tgz",
+ "integrity": "sha1-aRdSt+9maSCzQiP8ZlJUaTtG/VQ="
+ },
"node_modules/trim": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
@@ -41058,6 +41063,11 @@
}
}
},
+ "treant-js": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/treant-js/-/treant-js-1.0.1.tgz",
+ "integrity": "sha1-aRdSt+9maSCzQiP8ZlJUaTtG/VQ="
+ },
"trim": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
diff --git a/package.json b/package.json
index 8e1025b3f..6268db05f 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,7 @@
"react-modal": "^3.12.1",
"sprintf-js": "^1.1.1",
"tapable": "^1.0.0",
+ "treant-js": "^1.0.1",
"uuid": "^3.2.1",
"w3c-blob": "0.0.1"
},
diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx
index dbeb09ed5..adbef4030 100644
--- a/src/Corporation/Corporation.jsx
+++ b/src/Corporation/Corporation.jsx
@@ -1333,111 +1333,6 @@ Industry.prototype.getStorageMultiplier = function() {
return IndustryResearchTrees[this.type].getStorageMultiplier();
}
-// Create the Research Tree UI for this Industry
-Industry.prototype.createResearchBox = function() {
- const boxId = "corporation-research-popup-box";
-
- if (researchTreeBoxOpened) {
- // It's already opened, so delete it to refresh content
- removeElementById(boxId);
- researchTreeBox = null;
- }
-
- const researchTree = IndustryResearchTrees[this.type];
-
- // Create the popup first, so that the tree diagram can be added to it
- // This is handled by Treant
- researchTreeBox = createPopup(boxId, [], { backgroundColor: "black" });
-
- // Get the tree's markup (i.e. config) for Treant
- const markup = researchTree.createTreantMarkup();
- markup.chart.container = "#" + boxId + "-content";
- markup.chart.nodeAlign = "BOTTOM";
- markup.chart.rootOrientation = "WEST";
- markup.chart.siblingSeparation = 40;
- markup.chart.connectors = {
- type: "step",
- style: {
- "arrow-end": "block-wide-long",
- "stroke": "white",
- "stroke-width": 2,
- },
- }
-
- // Construct the tree with Treant
- // This is required for side effect.
- // eslint-disable-next-line no-new
- new Treant(markup);
-
- // Add Event Listeners for all Nodes
- const allResearch = researchTree.getAllNodes();
- for (let i = 0; i < allResearch.length; ++i) {
- // If this is already Researched, skip it
- if (this.researched[allResearch[i]] === true) {
- continue;
- }
-
- // Get the Research object
- const research = ResearchMap[allResearch[i]];
-
- // Get the DOM Element to add a click listener to it
- const sanitizedName = allResearch[i].replace(/\s/g, '');
- const div = document.getElementById(sanitizedName + "-corp-research-click-listener");
- if (div == null) {
- console.warn(`Could not find Research Tree div for ${sanitizedName}`);
- continue;
- }
-
- div.addEventListener("click", () => {
- if (this.sciResearch.qty >= research.cost) {
- this.sciResearch.qty -= research.cost;
-
- // Get the Node from the Research Tree and set its 'researched' property
- researchTree.research(allResearch[i]);
- this.researched[allResearch[i]] = true;
-
- const researchBox = this.createResearchBox();
- dialogBoxCreate(`Researched ${allResearch[i]}. It may take a market cycle ` +
- `(~${SecsPerMarketCycle} seconds) before the effects of ` +
- `the Research apply.`);
-
- return researchBox;
- } else {
- dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`);
- }
- });
- }
-
-
- const boxContent = document.getElementById(`${boxId}-content`);
- if (boxContent != null) {
- // Add information about multipliers from research at the bottom of the popup
- appendLineBreaks(boxContent, 2);
- boxContent.appendChild(createElement("pre", {
- display: "block",
- innerText: `Multipliers from research:\n` +
- ` * Advertising Multiplier: x${researchTree.getAdvertisingMultiplier()}\n` +
- ` * Employee Charisma Multiplier: x${researchTree.getEmployeeChaMultiplier()}\n` +
- ` * Employee Creativity Multiplier: x${researchTree.getEmployeeCreMultiplier()}\n` +
- ` * Employee Efficiency Multiplier: x${researchTree.getEmployeeEffMultiplier()}\n` +
- ` * Employee Intelligence Multiplier: x${researchTree.getEmployeeIntMultiplier()}\n` +
- ` * Production Multiplier: x${researchTree.getProductionMultiplier()}\n` +
- ` * Sales Multiplier: x${researchTree.getSalesMultiplier()}\n` +
- ` * Scientific Research Multiplier: x${researchTree.getScientificResearchMultiplier()}\n` +
- ` * Storage Multiplier: x${researchTree.getStorageMultiplier()}`,
- }));
-
- // Close button
- boxContent.appendChild(createPopupCloseButton(researchTreeBox, {
- class: "std-button",
- display: "block",
- innerText: "Close",
- }));
- }
-
- researchTreeBoxOpened = true;
-}
-
Industry.prototype.toJSON = function() {
return Generic_toJSON("Industry", this);
}
diff --git a/src/Corporation/ui/IndustryOverview.tsx b/src/Corporation/ui/IndustryOverview.tsx
index 258cbb879..a0c03c118 100644
--- a/src/Corporation/ui/IndustryOverview.tsx
+++ b/src/Corporation/ui/IndustryOverview.tsx
@@ -9,6 +9,7 @@ import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { MakeProductPopup } from "./MakeProductPopup";
+import { ResearchPopup } from "./ResearchPopup";
import { createPopup } from "../../ui/React/createPopup";
interface IProps {
@@ -153,6 +154,14 @@ export function IndustryOverview(props: IProps): React.ReactElement {
`Real Estate: ${convertEffectFacToGraphic(division.reFac)}`);
}
+ function openResearchPopup(): void {
+ const popupId = "corporation-research-popup-box";
+ createPopup(popupId, ResearchPopup, {
+ industry: division,
+ popupId: popupId,
+ });
+ }
+
return (
{genInfo}
@@ -195,7 +204,7 @@ export function IndustryOverview(props: IProps): React.ReactElement {
products that you produce.
-
+
Research
diff --git a/src/Corporation/ui/ResearchPopup.tsx b/src/Corporation/ui/ResearchPopup.tsx
new file mode 100644
index 000000000..f9c7bf8fa
--- /dev/null
+++ b/src/Corporation/ui/ResearchPopup.tsx
@@ -0,0 +1,110 @@
+import React, { useState, useEffect } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+import { MaterialSizes } from "../MaterialSizes";
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { IndustryResearchTrees } from "../IndustryData";
+import { CorporationConstants } from "../data/Constants";
+import { ResearchMap } from "../ResearchMap";
+import { ResearchTree } from "../ResearchTree";
+import { Treant } from 'treant-js';
+
+interface IProps {
+ industry: any;
+ popupId: string;
+}
+
+// Create the Research Tree UI for this Industry
+export function ResearchPopup(props: IProps): React.ReactElement {
+
+ useEffect(() => {
+ const researchTree = IndustryResearchTrees[props.industry.type];
+ if(researchTree === undefined) return;
+
+ // Get the tree's markup (i.e. config) for Treant
+ const markup = researchTree.createTreantMarkup();
+ markup.chart.container = "#" + props.popupId + "-content";
+ markup.chart.nodeAlign = "BOTTOM";
+ markup.chart.rootOrientation = "WEST";
+ markup.chart.siblingSeparation = 40;
+ markup.chart.connectors = {
+ type: "step",
+ style: {
+ "arrow-end": "block-wide-long",
+ "stroke": "white",
+ "stroke-width": 2,
+ },
+ }
+
+ // Construct the tree with Treant
+ // This is required for side effect.
+ // eslint-disable-next-line no-new
+ Treant(markup);
+
+ // Add Event Listeners for all Nodes
+ const allResearch = researchTree.getAllNodes();
+ for (let i = 0; i < allResearch.length; ++i) {
+ // If this is already Researched, skip it
+ if (props.industry.researched[allResearch[i]] === true) {
+ continue;
+ }
+
+ // Get the Research object
+ const research = ResearchMap[allResearch[i]];
+
+ // Get the DOM Element to add a click listener to it
+ const sanitizedName = allResearch[i].replace(/\s/g, '');
+ const div = document.getElementById(sanitizedName + "-corp-research-click-listener");
+ if (div == null) {
+ console.warn(`Could not find Research Tree div for ${sanitizedName}`);
+ continue;
+ }
+
+ div.addEventListener("click", () => {
+ if (props.industry.sciResearch.qty >= research.cost) {
+ props.industry.sciResearch.qty -= research.cost;
+
+ // Get the Node from the Research Tree and set its 'researched' property
+ researchTree.research(allResearch[i]);
+ props.industry.researched[allResearch[i]] = true;
+
+ const researchBox = props.industry.createResearchBox();
+ dialogBoxCreate(`Researched ${allResearch[i]}. It may take a market cycle ` +
+ `(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +
+ `the Research apply.`);
+ } else {
+ dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`);
+ }
+ });
+ }
+
+
+ const boxContent = document.getElementById(`${props.popupId}-content`);
+ if (boxContent != null) {
+ // Add information about multipliers from research at the bottom of the popup
+ //appendLineBreaks(boxContent, 2);
+ boxContent.appendChild(createElement("pre", {
+ display: "block",
+ innerText: `Multipliers from research:\n` +
+ ` * Advertising Multiplier: x${researchTree.getAdvertisingMultiplier()}\n` +
+ ` * Employee Charisma Multiplier: x${researchTree.getEmployeeChaMultiplier()}\n` +
+ ` * Employee Creativity Multiplier: x${researchTree.getEmployeeCreMultiplier()}\n` +
+ ` * Employee Efficiency Multiplier: x${researchTree.getEmployeeEffMultiplier()}\n` +
+ ` * Employee Intelligence Multiplier: x${researchTree.getEmployeeIntMultiplier()}\n` +
+ ` * Production Multiplier: x${researchTree.getProductionMultiplier()}\n` +
+ ` * Sales Multiplier: x${researchTree.getSalesMultiplier()}\n` +
+ ` * Scientific Research Multiplier: x${researchTree.getScientificResearchMultiplier()}\n` +
+ ` * Storage Multiplier: x${researchTree.getStorageMultiplier()}`,
+ }));
+ }
+ });
+
+ return
+
+}
diff --git a/src/ThirdParty/Treant.js b/src/ThirdParty/Treant.js
deleted file mode 100644
index 2ac51885a..000000000
--- a/src/ThirdParty/Treant.js
+++ /dev/null
@@ -1,2171 +0,0 @@
-/*
- * Treant-js
- *
- * (c) 2013 Fran Peručić
- * Treant-js may be freely distributed under the MIT license.
- * For all details and documentation:
- * http://fperucic.github.io/treant-js
- *
- * Treant is an open-source JavaScipt library for visualization of tree diagrams.
- * It implements the node positioning algorithm of John Q. Walker II "Positioning nodes for General Trees".
- *
- * References:
- * Emilio Cortegoso Lobato: ECOTree.js v1.0 (October 26th, 2006)
- *
- * Contributors:
- * Fran Peručić, https://github.com/fperucic
- * Dave Goodchild, https://github.com/dlgoodchild
- */
-
-( function() {
- // Polyfill for IE to use startsWith
- if (!String.prototype.startsWith) {
- String.prototype.startsWith = function(searchString, position){
- return this.substr(position || 0, searchString.length) === searchString;
- };
- }
-
- var $ = null;
-
- var UTIL = {
-
- /**
- * Directly updates, recursively/deeply, the first object with all properties in the second object
- * @param {object} applyTo
- * @param {object} applyFrom
- * @return {object}
- */
- inheritAttrs: function( applyTo, applyFrom ) {
- for ( var attr in applyFrom ) {
- if ( applyFrom.hasOwnProperty( attr ) ) {
- if ( ( applyTo[attr] instanceof Object && applyFrom[attr] instanceof Object ) && ( typeof applyFrom[attr] !== 'function' ) ) {
- this.inheritAttrs( applyTo[attr], applyFrom[attr] );
- }
- else {
- applyTo[attr] = applyFrom[attr];
- }
- }
- }
- return applyTo;
- },
-
- /**
- * Returns a new object by merging the two supplied objects
- * @param {object} obj1
- * @param {object} obj2
- * @returns {object}
- */
- createMerge: function( obj1, obj2 ) {
- var newObj = {};
- if ( obj1 ) {
- this.inheritAttrs( newObj, this.cloneObj( obj1 ) );
- }
- if ( obj2 ) {
- this.inheritAttrs( newObj, obj2 );
- }
- return newObj;
- },
-
- /**
- * Takes any number of arguments
- * @returns {*}
- */
- extend: function() {
- if ( $ ) {
- Array.prototype.unshift.apply( arguments, [true, {}] );
- return $.extend.apply( $, arguments );
- }
- else {
- return UTIL.createMerge.apply( this, arguments );
- }
- },
-
- /**
- * @param {object} obj
- * @returns {*}
- */
- cloneObj: function ( obj ) {
- if ( Object( obj ) !== obj ) {
- return obj;
- }
- var res = new obj.constructor();
- for ( var key in obj ) {
- if ( obj.hasOwnProperty(key) ) {
- res[key] = this.cloneObj(obj[key]);
- }
- }
- return res;
- },
-
- /**
- * @param {Element} el
- * @param {string} eventType
- * @param {function} handler
- */
- addEvent: function( el, eventType, handler ) {
- if ( $ ) {
- $( el ).on( eventType+'.treant', handler );
- }
- else if ( el.addEventListener ) { // DOM Level 2 browsers
- el.addEventListener( eventType, handler, false );
- }
- else if ( el.attachEvent ) { // IE <= 8
- el.attachEvent( 'on' + eventType, handler );
- }
- else { // ancient browsers
- el['on' + eventType] = handler;
- }
- },
-
- /**
- * @param {string} selector
- * @param {boolean} raw
- * @param {Element} parentEl
- * @returns {Element|jQuery}
- */
- findEl: function( selector, raw, parentEl ) {
- parentEl = parentEl || document;
-
- if ( $ ) {
- var $element = $( selector, parentEl );
- return ( raw? $element.get( 0 ): $element );
- }
- else {
- // todo: getElementsByName()
- // todo: getElementsByTagName()
- // todo: getElementsByTagNameNS()
- if ( selector.charAt( 0 ) === '#' ) {
- return parentEl.getElementById( selector.substring( 1 ) );
- }
- else if ( selector.charAt( 0 ) === '.' ) {
- var oElements = parentEl.getElementsByClassName( selector.substring( 1 ) );
- return ( oElements.length? oElements[0]: null );
- }
-
- throw new Error( 'Unknown container element' );
- }
- },
-
- getOuterHeight: function( element ) {
- var nRoundingCompensation = 1;
- if ( typeof element.getBoundingClientRect === 'function' ) {
- return element.getBoundingClientRect().height;
- }
- else if ( $ ) {
- return Math.ceil( $( element ).outerHeight() ) + nRoundingCompensation;
- }
- else {
- return Math.ceil(
- element.clientHeight
- + UTIL.getStyle( element, 'border-top-width', true )
- + UTIL.getStyle( element, 'border-bottom-width', true )
- + UTIL.getStyle( element, 'padding-top', true )
- + UTIL.getStyle( element, 'padding-bottom', true )
- + nRoundingCompensation,
- );
- }
- },
-
- getOuterWidth: function( element ) {
- var nRoundingCompensation = 1;
- if ( typeof element.getBoundingClientRect === 'function' ) {
- return element.getBoundingClientRect().width;
- }
- else if ( $ ) {
- return Math.ceil( $( element ).outerWidth() ) + nRoundingCompensation;
- }
- else {
- return Math.ceil(
- element.clientWidth
- + UTIL.getStyle( element, 'border-left-width', true )
- + UTIL.getStyle( element, 'border-right-width', true )
- + UTIL.getStyle( element, 'padding-left', true )
- + UTIL.getStyle( element, 'padding-right', true )
- + nRoundingCompensation,
- );
- }
- },
-
- getStyle: function( element, strCssRule, asInt ) {
- var strValue = "";
- if ( document.defaultView && document.defaultView.getComputedStyle ) {
- strValue = document.defaultView.getComputedStyle( element, '' ).getPropertyValue( strCssRule );
- }
- else if( element.currentStyle ) {
- strCssRule = strCssRule.replace(/\-(\w)/g,
- function (strMatch, p1){
- return p1.toUpperCase();
- },
- );
- strValue = element.currentStyle[strCssRule];
- }
- //Number(elem.style.width.replace(/[^\d\.\-]/g, ''));
- return ( asInt? parseFloat( strValue ): strValue );
- },
-
- addClass: function( element, cssClass ) {
- if ( $ ) {
- $( element ).addClass( cssClass );
- }
- else {
- if ( !UTIL.hasClass( element, cssClass ) ) {
- if ( element.classList ) {
- element.classList.add( cssClass );
- }
- else {
- element.className += " "+cssClass;
- }
- }
- }
- },
-
- hasClass: function(element, my_class) {
- return (" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf(" "+my_class+" ") > -1;
- },
-
- toggleClass: function ( element, cls, apply ) {
- if ( $ ) {
- $( element ).toggleClass( cls, apply );
- }
- else {
- if ( apply ) {
- //element.className += " "+cls;
- element.classList.add( cls );
- }
- else {
- element.classList.remove( cls );
- }
- }
- },
-
- setDimensions: function( element, width, height ) {
- if ( $ ) {
- $( element ).width( width ).height( height );
- }
- else {
- element.style.width = width+'px';
- element.style.height = height+'px';
- }
- },
- isjQueryAvailable: function() {return(typeof ($) !== 'undefined' && $);},
- };
-
- /**
- * ImageLoader is used for determining if all the images from the Tree are loaded.
- * Node size (width, height) can be correctly determined only when all inner images are loaded
- */
- var ImageLoader = function() {
- this.reset();
- };
-
- ImageLoader.prototype = {
-
- /**
- * @returns {ImageLoader}
- */
- reset: function() {
- this.loading = [];
- return this;
- },
-
- /**
- * @param {TreeNode} node
- * @returns {ImageLoader}
- */
- processNode: function( node ) {
- var aImages = node.nodeDOM.getElementsByTagName( 'img' );
-
- var i = aImages.length;
- while ( i-- ) {
- this.create( node, aImages[i] );
- }
- return this;
- },
-
- /**
- * @returns {ImageLoader}
- */
- removeAll: function( img_src ) {
- var i = this.loading.length;
- while ( i-- ) {
- if ( this.loading[i] === img_src ) {
- this.loading.splice( i, 1 );
- }
- }
- return this;
- },
-
- /**
- * @param {TreeNode} node
- * @param {Element} image
- * @returns {*}
- */
- create: function ( node, image ) {
- var self = this, source = image.src;
-
- function imgTrigger() {
- self.removeAll( source );
- node.width = node.nodeDOM.offsetWidth;
- node.height = node.nodeDOM.offsetHeight;
- }
-
- if ( image.src.indexOf( 'data:' ) !== 0 ) {
- this.loading.push( source );
-
- if ( image.complete ) {
- return imgTrigger();
- }
-
- UTIL.addEvent( image, 'load', imgTrigger );
- UTIL.addEvent( image, 'error', imgTrigger ); // handle broken url-s
-
- // load event is not fired for cached images, force the load event
- image.src += ( ( image.src.indexOf( '?' ) > 0)? '&': '?' ) + new Date().getTime();
- }
- else {
- imgTrigger();
- }
- },
-
- /**
- * @returns {boolean}
- */
- isNotLoading: function() {
- return ( this.loading.length === 0 );
- },
- };
-
- /**
- * Class: TreeStore
- * TreeStore is used for holding initialized Tree objects
- * Its purpose is to avoid global variables and enable multiple Trees on the page.
- */
- var TreeStore = {
-
- store: [],
-
- /**
- * @param {object} jsonConfig
- * @returns {Tree}
- */
- createTree: function( jsonConfig ) {
- var nNewTreeId = this.store.length;
- this.store.push( new Tree( jsonConfig, nNewTreeId ) );
- return this.get( nNewTreeId );
- },
-
- /**
- * @param {number} treeId
- * @returns {Tree}
- */
- get: function ( treeId ) {
- return this.store[treeId];
- },
-
- /**
- * @param {number} treeId
- * @returns {TreeStore}
- */
- destroy: function( treeId ) {
- var tree = this.get( treeId );
- if ( tree ) {
- tree._R.remove();
- var draw_area = tree.drawArea;
-
- while ( draw_area.firstChild ) {
- draw_area.removeChild( draw_area.firstChild );
- }
-
- var classes = draw_area.className.split(' '),
- classes_to_stay = [];
-
- for ( var i = 0; i < classes.length; i++ ) {
- var cls = classes[i];
- if ( cls !== 'Treant' && cls !== 'Treant-loaded' ) {
- classes_to_stay.push(cls);
- }
- }
- draw_area.style.overflowY = '';
- draw_area.style.overflowX = '';
- draw_area.className = classes_to_stay.join(' ');
-
- this.store[treeId] = null;
- }
- return this;
- },
- };
-
- /**
- * Tree constructor.
- * @param {object} jsonConfig
- * @param {number} treeId
- * @constructor
- */
- var Tree = function (jsonConfig, treeId ) {
-
- /**
- * @param {object} jsonConfig
- * @param {number} treeId
- * @returns {Tree}
- */
- this.reset = function( jsonConfig, treeId ) {
- this.initJsonConfig = jsonConfig;
- this.initTreeId = treeId;
-
- this.id = treeId;
-
- this.CONFIG = UTIL.extend( Tree.CONFIG, jsonConfig.chart );
- this.drawArea = UTIL.findEl( this.CONFIG.container, true );
- if ( !this.drawArea ) {
- throw new Error( 'Failed to find element by selector "'+this.CONFIG.container+'"' );
- }
-
- UTIL.addClass( this.drawArea, 'Treant' );
-
- // kill of any child elements that may be there
- this.drawArea.innerHTML = '';
-
- this.imageLoader = new ImageLoader();
-
- this.nodeDB = new NodeDB( jsonConfig.nodeStructure, this );
-
- // key store for storing reference to node connectors,
- // key = nodeId where the connector ends
- this.connectionStore = {};
-
- this.loaded = false;
-
- this._R = new Raphael( this.drawArea, 100, 100 );
-
- return this;
- };
-
- /**
- * @returns {Tree}
- */
- this.reload = function() {
- this.reset( this.initJsonConfig, this.initTreeId ).redraw();
- return this;
- };
-
- this.reset( jsonConfig, treeId );
- };
-
- Tree.prototype = {
-
- /**
- * @returns {NodeDB}
- */
- getNodeDb: function() {
- return this.nodeDB;
- },
-
- /**
- * @param {TreeNode} parentTreeNode
- * @param {object} nodeDefinition
- * @returns {TreeNode}
- */
- addNode: function( parentTreeNode, nodeDefinition ) {
- var dbEntry = this.nodeDB.get( parentTreeNode.id );
-
- this.CONFIG.callback.onBeforeAddNode.apply( this, [parentTreeNode, nodeDefinition] );
-
- var oNewNode = this.nodeDB.createNode( nodeDefinition, parentTreeNode.id, this );
- oNewNode.createGeometry( this );
-
- oNewNode.parent().createSwitchGeometry( this );
-
- this.positionTree();
-
- this.CONFIG.callback.onAfterAddNode.apply( this, [oNewNode, parentTreeNode, nodeDefinition] );
-
- return oNewNode;
- },
-
- /**
- * @returns {Tree}
- */
- redraw: function() {
- this.positionTree();
- return this;
- },
-
- /**
- * @param {function} callback
- * @returns {Tree}
- */
- positionTree: function( callback ) {
- var self = this;
-
- if ( this.imageLoader.isNotLoading() ) {
- var root = this.root(),
- orient = this.CONFIG.rootOrientation;
-
- this.resetLevelData();
-
- this.firstWalk( root, 0 );
- this.secondWalk( root, 0, 0, 0 );
-
- this.positionNodes();
-
- if ( this.CONFIG.animateOnInit ) {
- setTimeout(
- function() {
- root.toggleCollapse();
- },
- this.CONFIG.animateOnInitDelay,
- );
- }
-
- if ( !this.loaded ) {
- UTIL.addClass( this.drawArea, 'Treant-loaded' ); // nodes are hidden until .loaded class is added
- if ( Object.prototype.toString.call( callback ) === "[object Function]" ) {
- callback( self );
- }
- self.CONFIG.callback.onTreeLoaded.apply( self, [root] );
- this.loaded = true;
- }
-
- }
- else {
- setTimeout(
- function() {
- self.positionTree( callback );
- }, 10,
- );
- }
- return this;
- },
-
- /**
- * In a first post-order walk, every node of the tree is assigned a preliminary
- * x-coordinate (held in field node->prelim).
- * In addition, internal nodes are given modifiers, which will be used to move their
- * children to the right (held in field node->modifier).
- * @param {TreeNode} node
- * @param {number} level
- * @returns {Tree}
- */
- firstWalk: function( node, level ) {
- node.prelim = null;
- node.modifier = null;
-
- this.setNeighbors( node, level );
- this.calcLevelDim( node, level );
-
- var leftSibling = node.leftSibling();
-
- if ( node.childrenCount() === 0 || level == this.CONFIG.maxDepth ) {
- // set preliminary x-coordinate
- if ( leftSibling ) {
- node.prelim = leftSibling.prelim + leftSibling.size() + this.CONFIG.siblingSeparation;
- }
- else {
- node.prelim = 0;
- }
- }
- else {
- //node is not a leaf, firstWalk for each child
- for ( var i = 0, n = node.childrenCount(); i < n; i++ ) {
- this.firstWalk(node.childAt(i), level + 1);
- }
-
- var midPoint = node.childrenCenter() - node.size() / 2;
-
- if ( leftSibling ) {
- node.prelim = leftSibling.prelim + leftSibling.size() + this.CONFIG.siblingSeparation;
- node.modifier = node.prelim - midPoint;
- this.apportion( node, level );
- }
- else {
- node.prelim = midPoint;
- }
-
- // handle stacked children positioning
- if ( node.stackParent ) { // handle the parent of stacked children
- node.modifier += this.nodeDB.get( node.stackChildren[0] ).size()/2 + node.connStyle.stackIndent;
- }
- else if ( node.stackParentId ) { // handle stacked children
- node.prelim = 0;
- }
- }
- return this;
- },
-
- /*
- * Clean up the positioning of small sibling subtrees.
- * Subtrees of a node are formed independently and
- * placed as close together as possible. By requiring
- * that the subtrees be rigid at the time they are put
- * together, we avoid the undesirable effects that can
- * accrue from positioning nodes rather than subtrees.
- */
- apportion: function (node, level) {
- var firstChild = node.firstChild(),
- firstChildLeftNeighbor = firstChild.leftNeighbor(),
- compareDepth = 1,
- depthToStop = this.CONFIG.maxDepth - level;
-
- while( firstChild && firstChildLeftNeighbor && compareDepth <= depthToStop ) {
- // calculate the position of the firstChild, according to the position of firstChildLeftNeighbor
-
- var modifierSumRight = 0,
- modifierSumLeft = 0,
- leftAncestor = firstChildLeftNeighbor,
- rightAncestor = firstChild;
-
- for ( var i = 0; i < compareDepth; i++ ) {
- leftAncestor = leftAncestor.parent();
- rightAncestor = rightAncestor.parent();
- modifierSumLeft += leftAncestor.modifier;
- modifierSumRight += rightAncestor.modifier;
-
- // all the stacked children are oriented towards right so use right variables
- if ( rightAncestor.stackParent !== undefined ) {
- modifierSumRight += rightAncestor.size() / 2;
- }
- }
-
- // find the gap between two trees and apply it to subTrees
- // and mathing smaller gaps to smaller subtrees
-
- var totalGap = (firstChildLeftNeighbor.prelim + modifierSumLeft + firstChildLeftNeighbor.size() + this.CONFIG.subTeeSeparation) - (firstChild.prelim + modifierSumRight );
-
- if ( totalGap > 0 ) {
- var subtreeAux = node,
- numSubtrees = 0;
-
- // count all the subtrees in the LeftSibling
- while ( subtreeAux && subtreeAux.id !== leftAncestor.id ) {
- subtreeAux = subtreeAux.leftSibling();
- numSubtrees++;
- }
-
- if ( subtreeAux ) {
- var subtreeMoveAux = node,
- singleGap = totalGap / numSubtrees;
-
- while ( subtreeMoveAux.id !== leftAncestor.id ) {
- subtreeMoveAux.prelim += totalGap;
- subtreeMoveAux.modifier += totalGap;
-
- totalGap -= singleGap;
- subtreeMoveAux = subtreeMoveAux.leftSibling();
- }
- }
- }
-
- compareDepth++;
-
- firstChild = ( firstChild.childrenCount() === 0 )?
- node.leftMost(0, compareDepth):
- firstChild = firstChild.firstChild();
-
- if ( firstChild ) {
- firstChildLeftNeighbor = firstChild.leftNeighbor();
- }
- }
- },
-
- /*
- * During a second pre-order walk, each node is given a
- * final x-coordinate by summing its preliminary
- * x-coordinate and the modifiers of all the node's
- * ancestors. The y-coordinate depends on the height of
- * the tree. (The roles of x and y are reversed for
- * RootOrientations of EAST or WEST.)
- */
- secondWalk: function( node, level, X, Y ) {
- if ( level <= this.CONFIG.maxDepth ) {
- var xTmp = node.prelim + X,
- yTmp = Y, align = this.CONFIG.nodeAlign,
- orient = this.CONFIG.rootOrientation,
- levelHeight, nodesizeTmp;
-
- if (orient === 'NORTH' || orient === 'SOUTH') {
- levelHeight = this.levelMaxDim[level].height;
- nodesizeTmp = node.height;
- if (node.pseudo) {
- node.height = levelHeight;
- } // assign a new size to pseudo nodes
- }
- else if (orient === 'WEST' || orient === 'EAST') {
- levelHeight = this.levelMaxDim[level].width;
- nodesizeTmp = node.width;
- if (node.pseudo) {
- node.width = levelHeight;
- } // assign a new size to pseudo nodes
- }
-
- node.X = xTmp;
-
- if (node.pseudo) { // pseudo nodes need to be properly aligned, otherwise position is not correct in some examples
- if (orient === 'NORTH' || orient === 'WEST') {
- node.Y = yTmp; // align "BOTTOM"
- }
- else if (orient === 'SOUTH' || orient === 'EAST') {
- node.Y = (yTmp + (levelHeight - nodesizeTmp)); // align "TOP"
- }
-
- } else {
- node.Y = ( align === 'CENTER' ) ? (yTmp + (levelHeight - nodesizeTmp) / 2) :
- ( align === 'TOP' ) ? (yTmp + (levelHeight - nodesizeTmp)) :
- yTmp;
- }
-
- if ( orient === 'WEST' || orient === 'EAST' ) {
- var swapTmp = node.X;
- node.X = node.Y;
- node.Y = swapTmp;
- }
-
- if (orient === 'SOUTH' ) {
- node.Y = -node.Y - nodesizeTmp;
- }
- else if ( orient === 'EAST' ) {
- node.X = -node.X - nodesizeTmp;
- }
-
- if ( node.childrenCount() !== 0 ) {
- if ( node.id === 0 && this.CONFIG.hideRootNode ) {
- // ako je root node Hiden onda nemoj njegovu dijecu pomaknut po Y osi za Level separation, neka ona budu na vrhu
- this.secondWalk(node.firstChild(), level + 1, X + node.modifier, Y);
- }
- else {
- this.secondWalk(node.firstChild(), level + 1, X + node.modifier, Y + levelHeight + this.CONFIG.levelSeparation);
- }
- }
-
- if ( node.rightSibling() ) {
- this.secondWalk( node.rightSibling(), level, X, Y );
- }
- }
- },
-
- /**
- * position all the nodes, center the tree in center of its container
- * 0,0 coordinate is in the upper left corner
- * @returns {Tree}
- */
- positionNodes: function() {
- var self = this,
- treeSize = {
- x: self.nodeDB.getMinMaxCoord('X', null, null),
- y: self.nodeDB.getMinMaxCoord('Y', null, null),
- },
-
- treeWidth = treeSize.x.max - treeSize.x.min,
- treeHeight = treeSize.y.max - treeSize.y.min,
-
- treeCenter = {
- x: treeSize.x.max - treeWidth/2,
- y: treeSize.y.max - treeHeight/2,
- };
-
- this.handleOverflow(treeWidth, treeHeight);
-
- var
- containerCenter = {
- x: self.drawArea.clientWidth/2,
- y: self.drawArea.clientHeight/2,
- },
-
- deltaX = containerCenter.x - treeCenter.x,
- deltaY = containerCenter.y - treeCenter.y,
-
- // all nodes must have positive X or Y coordinates, handle this with offsets
- negOffsetX = ((treeSize.x.min + deltaX) <= 0) ? Math.abs(treeSize.x.min) : 0,
- negOffsetY = ((treeSize.y.min + deltaY) <= 0) ? Math.abs(treeSize.y.min) : 0,
- i, len, node;
-
- // position all the nodes
- for ( i = 0, len = this.nodeDB.db.length; i < len; i++ ) {
-
- node = this.nodeDB.get(i);
-
- self.CONFIG.callback.onBeforePositionNode.apply( self, [node, i, containerCenter, treeCenter] );
-
- if ( node.id === 0 && this.CONFIG.hideRootNode ) {
- self.CONFIG.callback.onAfterPositionNode.apply( self, [node, i, containerCenter, treeCenter] );
- continue;
- }
-
- // if the tree is smaller than the draw area, then center the tree within drawing area
- node.X += negOffsetX + ((treeWidth < this.drawArea.clientWidth) ? deltaX : this.CONFIG.padding);
- node.Y += negOffsetY + ((treeHeight < this.drawArea.clientHeight) ? deltaY : this.CONFIG.padding);
-
- var collapsedParent = node.collapsedParent(),
- hidePoint = null;
-
- if (collapsedParent) {
- // position the node behind the connector point of the parent, so future animations can be visible
- hidePoint = collapsedParent.connectorPoint( true );
- node.hide(hidePoint);
-
- }
- else if (node.positioned) {
- // node is already positioned,
- node.show();
- }
- else { // inicijalno stvaranje nodeova, postavi lokaciju
- node.nodeDOM.style.left = node.X + 'px';
- node.nodeDOM.style.top = node.Y + 'px';
- node.positioned = true;
- }
-
- if (node.id !== 0 && !(node.parent().id === 0 && this.CONFIG.hideRootNode)) {
- this.setConnectionToParent(node, hidePoint); // skip the root node
- }
- else if (!this.CONFIG.hideRootNode && node.drawLineThrough) {
- // drawlinethrough is performed for for the root node also
- node.drawLineThroughMe();
- }
-
- self.CONFIG.callback.onAfterPositionNode.apply( self, [node, i, containerCenter, treeCenter] );
- }
- return this;
- },
-
- /**
- * Create Raphael instance, (optionally set scroll bars if necessary)
- * @param {number} treeWidth
- * @param {number} treeHeight
- * @returns {Tree}
- */
- handleOverflow: function( treeWidth, treeHeight ) {
- var viewWidth = (treeWidth < this.drawArea.clientWidth) ? this.drawArea.clientWidth : treeWidth + this.CONFIG.padding*2,
- viewHeight = (treeHeight < this.drawArea.clientHeight) ? this.drawArea.clientHeight : treeHeight + this.CONFIG.padding*2;
-
- this._R.setSize( viewWidth, viewHeight );
-
- if ( this.CONFIG.scrollbar === 'resize') {
- UTIL.setDimensions( this.drawArea, viewWidth, viewHeight );
- }
- else if ( !UTIL.isjQueryAvailable() || this.CONFIG.scrollbar === 'native' ) {
-
- if ( this.drawArea.clientWidth < treeWidth ) { // is overflow-x necessary
- this.drawArea.style.overflowX = "auto";
- }
-
- if ( this.drawArea.clientHeight < treeHeight ) { // is overflow-y necessary
- this.drawArea.style.overflowY = "auto";
- }
- }
- // Fancy scrollbar relies heavily on jQuery, so guarding with if ( $ )
- else if ( this.CONFIG.scrollbar === 'fancy') {
- var jq_drawArea = $( this.drawArea );
- if (jq_drawArea.hasClass('ps-container')) { // znaci da je 'fancy' vec inicijaliziran, treba updateat
- jq_drawArea.find('.Treant').css({
- width: viewWidth,
- height: viewHeight,
- });
-
- jq_drawArea.perfectScrollbar('update');
- }
- else {
- var mainContainer = jq_drawArea.wrapInner('
'),
- child = mainContainer.find('.Treant');
-
- child.css({
- width: viewWidth,
- height: viewHeight,
- });
-
- mainContainer.perfectScrollbar();
- }
- } // else this.CONFIG.scrollbar == 'None'
-
- return this;
- },
- /**
- * @param {TreeNode} treeNode
- * @param {boolean} hidePoint
- * @returns {Tree}
- */
- setConnectionToParent: function( treeNode, hidePoint ) {
- var stacked = treeNode.stackParentId,
- connLine,
- parent = ( stacked? this.nodeDB.get( stacked ): treeNode.parent() ),
-
- pathString = hidePoint?
- this.getPointPathString(hidePoint):
- this.getPathString(parent, treeNode, stacked);
-
- if ( this.connectionStore[treeNode.id] ) {
- // connector already exists, update the connector geometry
- connLine = this.connectionStore[treeNode.id];
- this.animatePath( connLine, pathString );
- }
- else {
- connLine = this._R.path( pathString );
- this.connectionStore[treeNode.id] = connLine;
-
- // don't show connector arrows por pseudo nodes
- if ( treeNode.pseudo ) {
- delete parent.connStyle.style['arrow-end'];
- }
- if ( parent.pseudo ) {
- delete parent.connStyle.style['arrow-start'];
- }
-
- connLine.attr( parent.connStyle.style );
-
- if ( treeNode.drawLineThrough || treeNode.pseudo ) {
- treeNode.drawLineThroughMe( hidePoint );
- }
- }
- treeNode.connector = connLine;
- return this;
- },
-
- /**
- * Create the path which is represented as a point, used for hiding the connection
- * A path with a leading "_" indicates the path will be hidden
- * See: http://dmitrybaranovskiy.github.io/raphael/reference.html#Paper.path
- * @param {object} hidePoint
- * @returns {string}
- */
- getPointPathString: function( hidePoint ) {
- return ["_M", hidePoint.x, ",", hidePoint.y, 'L', hidePoint.x, ",", hidePoint.y, hidePoint.x, ",", hidePoint.y].join(' ');
- },
-
- /**
- * This method relied on receiving a valid Raphael Paper.path.
- * See: http://dmitrybaranovskiy.github.io/raphael/reference.html#Paper.path
- * A pathString is typically in the format of "M10,20L30,40"
- * @param path
- * @param {string} pathString
- * @returns {Tree}
- */
- animatePath: function( path, pathString ) {
- if (path.hidden && pathString.charAt(0) !== "_") { // path will be shown, so show it
- path.show();
- path.hidden = false;
- }
-
- // See: http://dmitrybaranovskiy.github.io/raphael/reference.html#Element.animate
- path.animate(
- {
- path: pathString.charAt(0) === "_"?
- pathString.substring(1):
- pathString, // remove the "_" prefix if it exists
- },
- this.CONFIG.animation.connectorsSpeed,
- this.CONFIG.animation.connectorsAnimation,
- function() {
- if ( pathString.charAt(0) === "_" ) { // animation is hiding the path, hide it at the and of animation
- path.hide();
- path.hidden = true;
- }
- },
- );
- return this;
- },
-
- /**
- *
- * @param {TreeNode} from_node
- * @param {TreeNode} to_node
- * @param {boolean} stacked
- * @returns {string}
- */
- getPathString: function( from_node, to_node, stacked ) {
- var startPoint = from_node.connectorPoint( true ),
- endPoint = to_node.connectorPoint( false ),
- orientation = this.CONFIG.rootOrientation,
- connType = from_node.connStyle.type,
- P1 = {}, P2 = {};
-
- if ( orientation === 'NORTH' || orientation === 'SOUTH' ) {
- P1.y = P2.y = (startPoint.y + endPoint.y) / 2;
-
- P1.x = startPoint.x;
- P2.x = endPoint.x;
- }
- else if ( orientation === 'EAST' || orientation === 'WEST' ) {
- P1.x = P2.x = (startPoint.x + endPoint.x) / 2;
-
- P1.y = startPoint.y;
- P2.y = endPoint.y;
- }
-
- // sp, p1, pm, p2, ep == "x,y"
- var sp = startPoint.x+','+startPoint.y, p1 = P1.x+','+P1.y, p2 = P2.x+','+P2.y, ep = endPoint.x+','+endPoint.y,
- pm = (P1.x + P2.x)/2 +','+ (P1.y + P2.y)/2, pathString, stackPoint;
-
- if ( stacked ) { // STACKED CHILDREN
-
- stackPoint = (orientation === 'EAST' || orientation === 'WEST')?
- endPoint.x+','+startPoint.y:
- startPoint.x+','+endPoint.y;
-
- if ( connType === "step" || connType === "straight" ) {
- pathString = ["M", sp, 'L', stackPoint, 'L', ep];
- }
- else if ( connType === "curve" || connType === "bCurve" ) {
- var helpPoint, // used for nicer curve lines
- indent = from_node.connStyle.stackIndent;
-
- if ( orientation === 'NORTH' ) {
- helpPoint = (endPoint.x - indent)+','+(endPoint.y - indent);
- }
- else if ( orientation === 'SOUTH' ) {
- helpPoint = (endPoint.x - indent)+','+(endPoint.y + indent);
- }
- else if ( orientation === 'EAST' ) {
- helpPoint = (endPoint.x + indent) +','+startPoint.y;
- }
- else if ( orientation === 'WEST' ) {
- helpPoint = (endPoint.x - indent) +','+startPoint.y;
- }
- pathString = ["M", sp, 'L', helpPoint, 'S', stackPoint, ep];
- }
-
- }
- else { // NORMAL CHILDREN
- if ( connType === "step" ) {
- pathString = ["M", sp, 'L', p1, 'L', p2, 'L', ep];
- }
- else if ( connType === "curve" ) {
- pathString = ["M", sp, 'C', p1, p2, ep ];
- }
- else if ( connType === "bCurve" ) {
- pathString = ["M", sp, 'Q', p1, pm, 'T', ep];
- }
- else if (connType === "straight" ) {
- pathString = ["M", sp, 'L', sp, ep];
- }
- }
-
- return pathString.join(" ");
- },
-
- /**
- * Algorithm works from left to right, so previous processed node will be left neighbour of the next node
- * @param {TreeNode} node
- * @param {number} level
- * @returns {Tree}
- */
- setNeighbors: function( node, level ) {
- node.leftNeighborId = this.lastNodeOnLevel[level];
- if ( node.leftNeighborId ) {
- node.leftNeighbor().rightNeighborId = node.id;
- }
- this.lastNodeOnLevel[level] = node.id;
- return this;
- },
-
- /**
- * Used for calculation of height and width of a level (level dimensions)
- * @param {TreeNode} node
- * @param {number} level
- * @returns {Tree}
- */
- calcLevelDim: function( node, level ) { // root node is on level 0
- this.levelMaxDim[level] = {
- width: Math.max( this.levelMaxDim[level]? this.levelMaxDim[level].width: 0, node.width ),
- height: Math.max( this.levelMaxDim[level]? this.levelMaxDim[level].height: 0, node.height ),
- };
- return this;
- },
-
- /**
- * @returns {Tree}
- */
- resetLevelData: function() {
- this.lastNodeOnLevel = [];
- this.levelMaxDim = [];
- return this;
- },
-
- /**
- * @returns {TreeNode}
- */
- root: function() {
- return this.nodeDB.get( 0 );
- },
- };
-
- /**
- * NodeDB is used for storing the nodes. Each tree has its own NodeDB.
- * @param {object} nodeStructure
- * @param {Tree} tree
- * @constructor
- */
- var NodeDB = function ( nodeStructure, tree ) {
- this.reset( nodeStructure, tree );
- };
-
- NodeDB.prototype = {
-
- /**
- * @param {object} nodeStructure
- * @param {Tree} tree
- * @returns {NodeDB}
- */
- reset: function( nodeStructure, tree ) {
-
- this.db = [];
-
- var self = this;
-
- /**
- * @param {object} node
- * @param {number} parentId
- */
- function iterateChildren( node, parentId ) {
- var newNode = self.createNode( node, parentId, tree, null );
-
- if ( node.children ) {
- // pseudo node is used for descending children to the next level
- if ( node.childrenDropLevel && node.childrenDropLevel > 0 ) {
- while ( node.childrenDropLevel-- ) {
- // pseudo node needs to inherit the connection style from its parent for continuous connectors
- var connStyle = UTIL.cloneObj( newNode.connStyle );
- newNode = self.createNode( 'pseudo', newNode.id, tree, null );
- newNode.connStyle = connStyle;
- newNode.children = [];
- }
- }
-
- var stack = ( node.stackChildren && !self.hasGrandChildren( node ) )? newNode.id: null;
-
- // children are positioned on separate levels, one beneath the other
- if ( stack !== null ) {
- newNode.stackChildren = [];
- }
-
- for ( var i = 0, len = node.children.length; i < len ; i++ ) {
- if ( stack !== null ) {
- newNode = self.createNode( node.children[i], newNode.id, tree, stack );
- if ( ( i + 1 ) < len ) {
- // last node cant have children
- newNode.children = [];
- }
- }
- else {
- iterateChildren( node.children[i], newNode.id );
- }
- }
- }
- }
-
- if ( tree.CONFIG.animateOnInit ) {
- nodeStructure.collapsed = true;
- }
-
- iterateChildren( nodeStructure, -1 ); // root node
-
- this.createGeometries( tree );
-
- return this;
- },
-
- /**
- * @param {Tree} tree
- * @returns {NodeDB}
- */
- createGeometries: function( tree ) {
- var i = this.db.length;
-
- while ( i-- ) {
- this.get( i ).createGeometry( tree );
- }
- return this;
- },
-
- /**
- * @param {number} nodeId
- * @returns {TreeNode}
- */
- get: function ( nodeId ) {
- return this.db[nodeId]; // get TreeNode by ID
- },
-
- /**
- * @param {function} callback
- * @returns {NodeDB}
- */
- walk: function( callback ) {
- var i = this.db.length;
-
- while ( i-- ) {
- callback.apply( this, [ this.get( i ) ] );
- }
- return this;
- },
-
- /**
- *
- * @param {object} nodeStructure
- * @param {number} parentId
- * @param {Tree} tree
- * @param {number} stackParentId
- * @returns {TreeNode}
- */
- createNode: function( nodeStructure, parentId, tree, stackParentId ) {
- var node = new TreeNode( nodeStructure, this.db.length, parentId, tree, stackParentId );
-
- this.db.push( node );
-
- // skip root node (0)
- if ( parentId >= 0 ) {
- var parent = this.get( parentId );
-
- // todo: refactor into separate private method
- if ( nodeStructure.position ) {
- if ( nodeStructure.position === 'left' ) {
- parent.children.push( node.id );
- }
- else if ( nodeStructure.position === 'right' ) {
- parent.children.splice( 0, 0, node.id );
- }
- else if ( nodeStructure.position === 'center' ) {
- parent.children.splice( Math.floor( parent.children.length / 2 ), 0, node.id );
- }
- else {
- // edge case when there's only 1 child
- var position = parseInt( nodeStructure.position );
- if ( parent.children.length === 1 && position > 0 ) {
- parent.children.splice( 0, 0, node.id );
- }
- else {
- parent.children.splice(
- Math.max( position, parent.children.length - 1 ),
- 0, node.id,
- );
- }
- }
- }
- else {
- parent.children.push( node.id );
- }
- }
-
- if ( stackParentId ) {
- this.get( stackParentId ).stackParent = true;
- this.get( stackParentId ).stackChildren.push( node.id );
- }
-
- return node;
- },
-
- getMinMaxCoord: function( dim, parent, MinMax ) { // used for getting the dimensions of the tree, dim = 'X' || 'Y'
- // looks for min and max (X and Y) within the set of nodes
- parent = parent || this.get(0);
-
- MinMax = MinMax || { // start with root node dimensions
- min: parent[dim],
- max: parent[dim] + ( ( dim === 'X' )? parent.width: parent.height ),
- };
-
- var i = parent.childrenCount();
-
- while ( i-- ) {
- var node = parent.childAt( i ),
- maxTest = node[dim] + ( ( dim === 'X' )? node.width: node.height ),
- minTest = node[dim];
-
- if ( maxTest > MinMax.max ) {
- MinMax.max = maxTest;
- }
- if ( minTest < MinMax.min ) {
- MinMax.min = minTest;
- }
-
- this.getMinMaxCoord( dim, node, MinMax );
- }
- return MinMax;
- },
-
- /**
- * @param {object} nodeStructure
- * @returns {boolean}
- */
- hasGrandChildren: function( nodeStructure ) {
- var i = nodeStructure.children.length;
- while ( i-- ) {
- if ( nodeStructure.children[i].children ) {
- return true;
- }
- }
- return false;
- },
- };
-
- /**
- * TreeNode constructor.
- * @param {object} nodeStructure
- * @param {number} id
- * @param {number} parentId
- * @param {Tree} tree
- * @param {number} stackParentId
- * @constructor
- */
- var TreeNode = function( nodeStructure, id, parentId, tree, stackParentId ) {
- this.reset( nodeStructure, id, parentId, tree, stackParentId );
- };
-
- TreeNode.prototype = {
-
- /**
- * @param {object} nodeStructure
- * @param {number} id
- * @param {number} parentId
- * @param {Tree} tree
- * @param {number} stackParentId
- * @returns {TreeNode}
- */
- reset: function( nodeStructure, id, parentId, tree, stackParentId ) {
- this.id = id;
- this.parentId = parentId;
- this.treeId = tree.id;
-
- this.prelim = 0;
- this.modifier = 0;
- this.leftNeighborId = null;
-
- this.stackParentId = stackParentId;
-
- // pseudo node is a node with width=height=0, it is invisible, but necessary for the correct positioning of the tree
- this.pseudo = nodeStructure === 'pseudo' || nodeStructure['pseudo']; // todo: surely if nodeStructure is a scalar then the rest will error:
-
- this.meta = nodeStructure.meta || {};
- this.image = nodeStructure.image || null;
-
- this.link = UTIL.createMerge( tree.CONFIG.node.link, nodeStructure.link );
-
- this.connStyle = UTIL.createMerge( tree.CONFIG.connectors, nodeStructure.connectors );
- this.connector = null;
-
- this.drawLineThrough = nodeStructure.drawLineThrough === false ? false : ( nodeStructure.drawLineThrough || tree.CONFIG.node.drawLineThrough );
-
- this.collapsable = nodeStructure.collapsable === false ? false : ( nodeStructure.collapsable || tree.CONFIG.node.collapsable );
- this.collapsed = nodeStructure.collapsed;
-
- this.text = nodeStructure.text;
-
- // '.node' DIV
- this.nodeInnerHTML = nodeStructure.innerHTML;
- this.nodeHTMLclass = (tree.CONFIG.node.HTMLclass ? tree.CONFIG.node.HTMLclass : '') + // globally defined class for the nodex
- (nodeStructure.HTMLclass ? (' ' + nodeStructure.HTMLclass) : ''); // + specific node class
-
- this.nodeHTMLid = nodeStructure.HTMLid;
-
- this.children = [];
-
- return this;
- },
-
- /**
- * @returns {Tree}
- */
- getTree: function() {
- return TreeStore.get( this.treeId );
- },
-
- /**
- * @returns {object}
- */
- getTreeConfig: function() {
- return this.getTree().CONFIG;
- },
-
- /**
- * @returns {NodeDB}
- */
- getTreeNodeDb: function() {
- return this.getTree().getNodeDb();
- },
-
- /**
- * @param {number} nodeId
- * @returns {TreeNode}
- */
- lookupNode: function( nodeId ) {
- return this.getTreeNodeDb().get( nodeId );
- },
-
- /**
- * @returns {Tree}
- */
- Tree: function() {
- return TreeStore.get( this.treeId );
- },
-
- /**
- * @param {number} nodeId
- * @returns {TreeNode}
- */
- dbGet: function( nodeId ) {
- return this.getTreeNodeDb().get( nodeId );
- },
-
- /**
- * Returns the width of the node
- * @returns {float}
- */
- size: function() {
- var orientation = this.getTreeConfig().rootOrientation;
-
- if ( this.pseudo ) {
- // prevents separating the subtrees
- return ( -this.getTreeConfig().subTeeSeparation );
- }
-
- if ( orientation === 'NORTH' || orientation === 'SOUTH' ) {
- return this.width;
- }
- else if ( orientation === 'WEST' || orientation === 'EAST' ) {
- return this.height;
- }
- },
-
- /**
- * @returns {number}
- */
- childrenCount: function () {
- return ( ( this.collapsed || !this.children)? 0: this.children.length );
- },
-
- /**
- * @param {number} index
- * @returns {TreeNode}
- */
- childAt: function( index ) {
- return this.dbGet( this.children[index] );
- },
-
- /**
- * @returns {TreeNode}
- */
- firstChild: function() {
- return this.childAt( 0 );
- },
-
- /**
- * @returns {TreeNode}
- */
- lastChild: function() {
- return this.childAt( this.children.length - 1 );
- },
-
- /**
- * @returns {TreeNode}
- */
- parent: function() {
- return this.lookupNode( this.parentId );
- },
-
- /**
- * @returns {TreeNode}
- */
- leftNeighbor: function() {
- if ( this.leftNeighborId ) {
- return this.lookupNode( this.leftNeighborId );
- }
- },
-
- /**
- * @returns {TreeNode}
- */
- rightNeighbor: function() {
- if ( this.rightNeighborId ) {
- return this.lookupNode( this.rightNeighborId );
- }
- },
-
- /**
- * @returns {TreeNode}
- */
- leftSibling: function () {
- var leftNeighbor = this.leftNeighbor();
-
- if ( leftNeighbor && leftNeighbor.parentId === this.parentId ){
- return leftNeighbor;
- }
- },
-
- /**
- * @returns {TreeNode}
- */
- rightSibling: function () {
- var rightNeighbor = this.rightNeighbor();
-
- if ( rightNeighbor && rightNeighbor.parentId === this.parentId ) {
- return rightNeighbor;
- }
- },
-
- /**
- * @returns {number}
- */
- childrenCenter: function () {
- var first = this.firstChild(),
- last = this.lastChild();
-
- return ( first.prelim + ((last.prelim - first.prelim) + last.size()) / 2 );
- },
-
- /**
- * Find out if one of the node ancestors is collapsed
- * @returns {*}
- */
- collapsedParent: function() {
- var parent = this.parent();
- if ( !parent ) {
- return false;
- }
- if ( parent.collapsed ) {
- return parent;
- }
- return parent.collapsedParent();
- },
-
- /**
- * Returns the leftmost child at specific level, (initial level = 0)
- * @param level
- * @param depth
- * @returns {*}
- */
- leftMost: function ( level, depth ) {
- if ( level >= depth ) {
- return this;
- }
- if ( this.childrenCount() === 0 ) {
- return;
- }
-
- for ( var i = 0, n = this.childrenCount(); i < n; i++ ) {
- var leftmostDescendant = this.childAt( i ).leftMost( level + 1, depth );
- if ( leftmostDescendant ) {
- return leftmostDescendant;
- }
- }
- },
-
- // returns start or the end point of the connector line, origin is upper-left
- connectorPoint: function(startPoint) {
- var orient = this.Tree().CONFIG.rootOrientation, point = {};
-
- if ( this.stackParentId ) { // return different end point if node is a stacked child
- if ( orient === 'NORTH' || orient === 'SOUTH' ) {
- orient = 'WEST';
- }
- else if ( orient === 'EAST' || orient === 'WEST' ) {
- orient = 'NORTH';
- }
- }
-
- // if pseudo, a virtual center is used
- if ( orient === 'NORTH' ) {
- point.x = (this.pseudo) ? this.X - this.Tree().CONFIG.subTeeSeparation/2 : this.X + this.width/2;
- point.y = (startPoint) ? this.Y + this.height : this.Y;
- }
- else if (orient === 'SOUTH') {
- point.x = (this.pseudo) ? this.X - this.Tree().CONFIG.subTeeSeparation/2 : this.X + this.width/2;
- point.y = (startPoint) ? this.Y : this.Y + this.height;
- }
- else if (orient === 'EAST') {
- point.x = (startPoint) ? this.X : this.X + this.width;
- point.y = (this.pseudo) ? this.Y - this.Tree().CONFIG.subTeeSeparation/2 : this.Y + this.height/2;
- }
- else if (orient === 'WEST') {
- point.x = (startPoint) ? this.X + this.width : this.X;
- point.y = (this.pseudo) ? this.Y - this.Tree().CONFIG.subTeeSeparation/2 : this.Y + this.height/2;
- }
- return point;
- },
-
- /**
- * @returns {string}
- */
- pathStringThrough: function() { // get the geometry of a path going through the node
- var startPoint = this.connectorPoint( true ),
- endPoint = this.connectorPoint( false );
-
- return ["M", startPoint.x+","+startPoint.y, 'L', endPoint.x+","+endPoint.y].join(" ");
- },
-
- /**
- * @param {object} hidePoint
- */
- drawLineThroughMe: function( hidePoint ) { // hidepoint se proslijedjuje ako je node sakriven zbog collapsed
- var pathString = hidePoint?
- this.Tree().getPointPathString( hidePoint ):
- this.pathStringThrough();
-
- this.lineThroughMe = this.lineThroughMe || this.Tree()._R.path(pathString);
-
- var line_style = UTIL.cloneObj( this.connStyle.style );
-
- delete line_style['arrow-start'];
- delete line_style['arrow-end'];
-
- this.lineThroughMe.attr( line_style );
-
- if ( hidePoint ) {
- this.lineThroughMe.hide();
- this.lineThroughMe.hidden = true;
- }
- },
-
- addSwitchEvent: function( nodeSwitch ) {
- var self = this;
- UTIL.addEvent( nodeSwitch, 'click',
- function( e ) {
- e.preventDefault();
- if ( self.getTreeConfig().callback.onBeforeClickCollapseSwitch.apply( self, [ nodeSwitch, e ] ) === false ) {
- return false;
- }
-
- self.toggleCollapse();
-
- self.getTreeConfig().callback.onAfterClickCollapseSwitch.apply( self, [ nodeSwitch, e ] );
- },
- );
- },
-
- /**
- * @returns {TreeNode}
- */
- collapse: function() {
- if ( !this.collapsed ) {
- this.toggleCollapse();
- }
- return this;
- },
-
- /**
- * @returns {TreeNode}
- */
- expand: function() {
- if ( this.collapsed ) {
- this.toggleCollapse();
- }
- return this;
- },
-
- /**
- * @returns {TreeNode}
- */
- toggleCollapse: function() {
- var oTree = this.getTree();
-
- if ( !oTree.inAnimation ) {
- oTree.inAnimation = true;
-
- this.collapsed = !this.collapsed; // toggle the collapse at each click
- UTIL.toggleClass( this.nodeDOM, 'collapsed', this.collapsed );
-
- oTree.positionTree();
-
- var self = this;
-
- setTimeout(
- function() { // set the flag after the animation
- oTree.inAnimation = false;
- oTree.CONFIG.callback.onToggleCollapseFinished.apply( oTree, [ self, self.collapsed ] );
- },
- ( oTree.CONFIG.animation.nodeSpeed > oTree.CONFIG.animation.connectorsSpeed )?
- oTree.CONFIG.animation.nodeSpeed:
- oTree.CONFIG.animation.connectorsSpeed,
- );
- }
- return this;
- },
-
- hide: function( collapse_to_point ) {
- collapse_to_point = collapse_to_point || false;
-
- var bCurrentState = this.hidden;
- this.hidden = true;
-
- this.nodeDOM.style.overflow = 'hidden';
-
- var tree = this.getTree(),
- config = this.getTreeConfig(),
- oNewState = {
- opacity: 0,
- };
-
- if ( collapse_to_point ) {
- oNewState.left = collapse_to_point.x;
- oNewState.top = collapse_to_point.y;
- }
-
- // if parent was hidden in initial configuration, position the node behind the parent without animations
- if ( !this.positioned || bCurrentState ) {
- this.nodeDOM.style.visibility = 'hidden';
- if ( $ ) {
- $( this.nodeDOM ).css( oNewState );
- }
- else {
- this.nodeDOM.style.left = oNewState.left + 'px';
- this.nodeDOM.style.top = oNewState.top + 'px';
- }
- this.positioned = true;
- }
- else {
- // todo: fix flashy bug when a node is manually hidden and tree.redraw is called.
- if ( $ ) {
- $( this.nodeDOM ).animate(
- oNewState, config.animation.nodeSpeed, config.animation.nodeAnimation,
- function () {
- this.style.visibility = 'hidden';
- },
- );
- }
- else {
- this.nodeDOM.style.transition = 'all '+config.animation.nodeSpeed+'ms ease';
- this.nodeDOM.style.transitionProperty = 'opacity, left, top';
- this.nodeDOM.style.opacity = oNewState.opacity;
- this.nodeDOM.style.left = oNewState.left + 'px';
- this.nodeDOM.style.top = oNewState.top + 'px';
- this.nodeDOM.style.visibility = 'hidden';
- }
- }
-
- // animate the line through node if the line exists
- if ( this.lineThroughMe ) {
- var new_path = tree.getPointPathString( collapse_to_point );
- if ( bCurrentState ) {
- // update without animations
- this.lineThroughMe.attr( { path: new_path } );
- }
- else {
- // update with animations
- tree.animatePath( this.lineThroughMe, tree.getPointPathString( collapse_to_point ) );
- }
- }
-
- return this;
- },
-
- /**
- * @returns {TreeNode}
- */
- hideConnector: function() {
- var oTree = this.Tree();
- var oPath = oTree.connectionStore[this.id];
- if ( oPath ) {
- oPath.animate(
- { 'opacity': 0 },
- oTree.CONFIG.animation.connectorsSpeed,
- oTree.CONFIG.animation.connectorsAnimation,
- );
- }
- return this;
- },
-
- show: function() {
- var bCurrentState = this.hidden;
- this.hidden = false;
-
- this.nodeDOM.style.visibility = 'visible';
-
- var oTree = this.Tree();
-
- var oNewState = {
- left: this.X,
- top: this.Y,
- opacity: 1,
- },
- config = this.getTreeConfig();
-
- // if the node was hidden, update opacity and position
- if ( $ ) {
- $( this.nodeDOM ).animate(
- oNewState,
- config.animation.nodeSpeed, config.animation.nodeAnimation,
- function () {
- // $.animate applies "overflow:hidden" to the node, remove it to avoid visual problems
- this.style.overflow = "";
- },
- );
- }
- else {
- this.nodeDOM.style.transition = 'all '+config.animation.nodeSpeed+'ms ease';
- this.nodeDOM.style.transitionProperty = 'opacity, left, top';
- this.nodeDOM.style.left = oNewState.left + 'px';
- this.nodeDOM.style.top = oNewState.top + 'px';
- this.nodeDOM.style.opacity = oNewState.opacity;
- this.nodeDOM.style.overflow = '';
- }
-
- if ( this.lineThroughMe ) {
- this.getTree().animatePath( this.lineThroughMe, this.pathStringThrough() );
- }
-
- return this;
- },
-
- /**
- * @returns {TreeNode}
- */
- showConnector: function() {
- var oTree = this.Tree();
- var oPath = oTree.connectionStore[this.id];
- if ( oPath ) {
- oPath.animate(
- { 'opacity': 1 },
- oTree.CONFIG.animation.connectorsSpeed,
- oTree.CONFIG.animation.connectorsAnimation,
- );
- }
- return this;
- },
- };
-
-
- /**
- * Build a node from the 'text' and 'img' property and return with it.
- *
- * The node will contain all the fields that present under the 'text' property
- * Each field will refer to a css class with name defined as node-{$property_name}
- *
- * Example:
- * The definition:
- *
- * text: {
- * desc: "some description",
- * paragraph: "some text"
- * }
- *
- * will generate the following elements:
- *
- * some description
- * some text
- *
- * @Returns the configured node
- */
- TreeNode.prototype.buildNodeFromText = function (node) {
- // IMAGE
- if (this.image) {
- image = document.createElement('img');
- image.src = this.image;
- node.appendChild(image);
- }
-
- // TEXT
- if (this.text) {
- for (var key in this.text) {
- // adding DATA Attributes to the node
- if (key.startsWith("data-")) {
- node.setAttribute(key, this.text[key]);
- } else {
-
- var textElement = document.createElement(this.text[key].href ? 'a' : 'p');
-
- // make an element if required
- if (this.text[key].href) {
- textElement.href = this.text[key].href;
- if (this.text[key].target) {
- textElement.target = this.text[key].target;
- }
- }
-
- textElement.className = "node-"+key;
- textElement.appendChild(document.createTextNode(
- this.text[key].val ? this.text[key].val :
- this.text[key] instanceof Object ? "'val' param missing!" : this.text[key],
- ),
- );
-
- node.appendChild(textElement);
- }
- }
- }
- return node;
- };
-
- /**
- * Build a node from 'nodeInnerHTML' property that defines an existing HTML element, referenced by it's id, e.g: #someElement
- * Change the text in the passed node to 'Wrong ID selector' if the referenced element does ot exist,
- * return with a cloned and configured node otherwise
- *
- * @Returns node the configured node
- */
- TreeNode.prototype.buildNodeFromHtml = function(node) {
- // get some element by ID and clone its structure into a node
- if (this.nodeInnerHTML.charAt(0) === "#") {
- var elem = document.getElementById(this.nodeInnerHTML.substring(1));
- if (elem) {
- node = elem.cloneNode(true);
- node.id += "-clone";
- node.className += " node";
- }
- else {
- node.innerHTML = " Wrong ID selector ";
- }
- }
- else {
- // insert your custom HTML into a node
- node.innerHTML = this.nodeInnerHTML;
- }
- return node;
- };
-
- /**
- * @param {Tree} tree
- */
- TreeNode.prototype.createGeometry = function( tree ) {
- if ( this.id === 0 && tree.CONFIG.hideRootNode ) {
- this.width = 0;
- this.height = 0;
- return;
- }
-
- var drawArea = tree.drawArea,
- image,
-
- /////////// CREATE NODE //////////////
- node = document.createElement( this.link.href? 'a': 'div' );
-
- node.className = ( !this.pseudo )? TreeNode.CONFIG.nodeHTMLclass: 'pseudo';
- if ( this.nodeHTMLclass && !this.pseudo ) {
- node.className += ' ' + this.nodeHTMLclass;
- }
-
- if ( this.nodeHTMLid ) {
- node.id = this.nodeHTMLid;
- }
-
- if ( this.link.href ) {
- node.href = this.link.href;
- node.target = this.link.target;
- }
-
- if ( $ ) {
- $( node ).data( 'treenode', this );
- }
- else {
- node.data = {
- 'treenode': this,
- };
- }
-
- /////////// BUILD NODE CONTENT //////////////
- if ( !this.pseudo ) {
- node = this.nodeInnerHTML? this.buildNodeFromHtml(node) : this.buildNodeFromText(node)
-
- // handle collapse switch
- if ( this.collapsed || (this.collapsable && this.childrenCount() && !this.stackParentId) ) {
- this.createSwitchGeometry( tree, node );
- }
- }
-
- tree.CONFIG.callback.onCreateNode.apply( tree, [this, node] );
-
- /////////// APPEND all //////////////
- drawArea.appendChild(node);
-
- this.width = node.offsetWidth;
- this.height = node.offsetHeight;
-
- this.nodeDOM = node;
-
- tree.imageLoader.processNode(this);
- };
-
- /**
- * @param {Tree} tree
- * @param {Element} nodeEl
- */
- TreeNode.prototype.createSwitchGeometry = function( tree, nodeEl ) {
- nodeEl = nodeEl || this.nodeDOM;
-
- // safe guard and check to see if it has a collapse switch
- var nodeSwitchEl = UTIL.findEl( '.collapse-switch', true, nodeEl );
- if ( !nodeSwitchEl ) {
- nodeSwitchEl = document.createElement( 'a' );
- nodeSwitchEl.className = "collapse-switch";
-
- nodeEl.appendChild( nodeSwitchEl );
- this.addSwitchEvent( nodeSwitchEl );
- if ( this.collapsed ) {
- nodeEl.className += " collapsed";
- }
-
- tree.CONFIG.callback.onCreateNodeCollapseSwitch.apply( tree, [this, nodeEl, nodeSwitchEl] );
- }
- return nodeSwitchEl;
- };
-
-
- // ###########################################
- // Expose global + default CONFIG params
- // ###########################################
-
-
- Tree.CONFIG = {
- maxDepth: 100,
- rootOrientation: 'NORTH', // NORTH || EAST || WEST || SOUTH
- nodeAlign: 'CENTER', // CENTER || TOP || BOTTOM
- levelSeparation: 30,
- siblingSeparation: 30,
- subTeeSeparation: 30,
-
- hideRootNode: false,
-
- animateOnInit: false,
- animateOnInitDelay: 500,
-
- padding: 15, // the difference is seen only when the scrollbar is shown
- scrollbar: 'native', // "native" || "fancy" || "None" (PS: "fancy" requires jquery and perfect-scrollbar)
-
- connectors: {
- type: 'curve', // 'curve' || 'step' || 'straight' || 'bCurve'
- style: {
- stroke: 'black',
- },
- stackIndent: 15,
- },
-
- node: { // each node inherits this, it can all be overridden in node config
-
- // HTMLclass: 'node',
- // drawLineThrough: false,
- // collapsable: false,
- link: {
- target: '_self',
- },
- },
-
- animation: { // each node inherits this, it can all be overridden in node config
- nodeSpeed: 450,
- nodeAnimation: 'linear',
- connectorsSpeed: 450,
- connectorsAnimation: 'linear',
- },
-
- callback: {
- onCreateNode: function( treeNode, treeNodeDom ) {}, // this = Tree
- onCreateNodeCollapseSwitch: function( treeNode, treeNodeDom, switchDom ) {}, // this = Tree
- onAfterAddNode: function( newTreeNode, parentTreeNode, nodeStructure ) {}, // this = Tree
- onBeforeAddNode: function( parentTreeNode, nodeStructure ) {}, // this = Tree
- onAfterPositionNode: function( treeNode, nodeDbIndex, containerCenter, treeCenter) {}, // this = Tree
- onBeforePositionNode: function( treeNode, nodeDbIndex, containerCenter, treeCenter) {}, // this = Tree
- onToggleCollapseFinished: function ( treeNode, bIsCollapsed ) {}, // this = Tree
- onAfterClickCollapseSwitch: function( nodeSwitch, event ) {}, // this = TreeNode
- onBeforeClickCollapseSwitch: function( nodeSwitch, event ) {}, // this = TreeNode
- onTreeLoaded: function( rootTreeNode ) {}, // this = Tree
- },
- };
-
- TreeNode.CONFIG = {
- nodeHTMLclass: 'node',
- };
-
- // #############################################
- // Makes a JSON chart config out of Array config
- // #############################################
-
- var JSONconfig = {
- make: function( configArray ) {
-
- var i = configArray.length, node;
-
- this.jsonStructure = {
- chart: null,
- nodeStructure: null,
- };
- //fist loop: find config, find root;
- while(i--) {
- node = configArray[i];
- if (node.hasOwnProperty('container')) {
- this.jsonStructure.chart = node;
- continue;
- }
-
- if (!node.hasOwnProperty('parent') && ! node.hasOwnProperty('container')) {
- this.jsonStructure.nodeStructure = node;
- node._json_id = 0;
- }
- }
-
- this.findChildren(configArray);
-
- return this.jsonStructure;
- },
-
- findChildren: function(nodes) {
- var parents = [0]; // start with a a root node
-
- while(parents.length) {
- var parentId = parents.pop(),
- parent = this.findNode(this.jsonStructure.nodeStructure, parentId),
- i = 0, len = nodes.length,
- children = [];
-
- for(;i
Date: Tue, 31 Aug 2021 01:05:51 -0400
Subject: [PATCH 18/30] fix some of the research popup stuff
---
src/Corporation/ui/ResearchPopup.tsx | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/src/Corporation/ui/ResearchPopup.tsx b/src/Corporation/ui/ResearchPopup.tsx
index f9c7bf8fa..36bce633a 100644
--- a/src/Corporation/ui/ResearchPopup.tsx
+++ b/src/Corporation/ui/ResearchPopup.tsx
@@ -22,7 +22,6 @@ interface IProps {
// Create the Research Tree UI for this Industry
export function ResearchPopup(props: IProps): React.ReactElement {
-
useEffect(() => {
const researchTree = IndustryResearchTrees[props.industry.type];
if(researchTree === undefined) return;
@@ -42,9 +41,6 @@ export function ResearchPopup(props: IProps): React.ReactElement {
},
}
- // Construct the tree with Treant
- // This is required for side effect.
- // eslint-disable-next-line no-new
Treant(markup);
// Add Event Listeners for all Nodes
@@ -74,10 +70,10 @@ export function ResearchPopup(props: IProps): React.ReactElement {
researchTree.research(allResearch[i]);
props.industry.researched[allResearch[i]] = true;
- const researchBox = props.industry.createResearchBox();
dialogBoxCreate(`Researched ${allResearch[i]}. It may take a market cycle ` +
`(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +
`the Research apply.`);
+ removePopup(props.popupId);
} else {
dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`);
}
@@ -105,6 +101,8 @@ export function ResearchPopup(props: IProps): React.ReactElement {
}
});
- return
+ return
}
From 67be13c6d64019a91eb90b45f894f59094479c88 Mon Sep 17 00:00:00 2001
From: Olivier Gagnon
Date: Tue, 31 Aug 2021 02:49:57 -0400
Subject: [PATCH 19/30] more conversion
---
src/Corporation/Corporation.d.ts | 3 -
src/Corporation/Corporation.jsx | 1829 -----------------
src/Corporation/Corporation.tsx | 464 +++++
src/Corporation/Industry.ts | 1328 ++++++++++++
src/Corporation/IndustryUpgrades.ts | 4 +-
src/Corporation/Product.ts | 2 +-
.../data/CorporationUnlockUpgrades.ts | 4 +-
src/Corporation/data/CorporationUpgrades.ts | 4 +-
src/Corporation/ui/FindInvestorsPopup.tsx | 63 +
src/Corporation/ui/GoPublicPopup.tsx | 67 +
src/Corporation/ui/NewIndustryPopup.tsx | 2 +-
src/Corporation/ui/Overview.tsx | 23 +-
src/Corporation/ui/UnlockUpgrade.tsx | 3 +-
src/ThirdParty/decimal.js.d.ts | 1 +
src/engine.jsx | 6 +-
src/index.html | 1 -
16 files changed, 1958 insertions(+), 1846 deletions(-)
delete mode 100644 src/Corporation/Corporation.d.ts
delete mode 100644 src/Corporation/Corporation.jsx
create mode 100644 src/Corporation/Corporation.tsx
create mode 100644 src/Corporation/Industry.ts
create mode 100644 src/Corporation/ui/FindInvestorsPopup.tsx
create mode 100644 src/Corporation/ui/GoPublicPopup.tsx
create mode 100644 src/ThirdParty/decimal.js.d.ts
diff --git a/src/Corporation/Corporation.d.ts b/src/Corporation/Corporation.d.ts
deleted file mode 100644
index 36b7c3f3b..000000000
--- a/src/Corporation/Corporation.d.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export class Industry {
- constructor(props: any)
-}
\ No newline at end of file
diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx
deleted file mode 100644
index adbef4030..000000000
--- a/src/Corporation/Corporation.jsx
+++ /dev/null
@@ -1,1829 +0,0 @@
-import { AllCorporationStates,
- CorporationState } from "./CorporationState";
-import { CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades";
-import { CorporationUpgrades } from "./data/CorporationUpgrades";
-import { EmployeePositions } from "./EmployeePositions";
-import { Industries,
- IndustryStartingCosts,
- IndustryResearchTrees } from "./IndustryData";
-import { IndustryUpgrades } from "./IndustryUpgrades";
-import { Material } from "./Material";
-import { MaterialSizes } from "./MaterialSizes";
-import { Product } from "./Product";
-import { ResearchMap } from "./ResearchMap";
-import { Warehouse } from "./Warehouse";
-import { OfficeSpace } from "./OfficeSpace";
-
-import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
-import { showLiterature } from "../Literature/LiteratureHelpers";
-import { LiteratureNames } from "../Literature/data/LiteratureNames";
-import { CityName } from "../Locations/data/CityNames";
-import { Player } from "../Player";
-
-import { numeralWrapper } from "../ui/numeralFormat";
-import { Page, routing } from "../ui/navigationTracking";
-
-import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors";
-
-import { dialogBoxCreate } from "../../utils/DialogBox";
-import { Reviver,
- Generic_toJSON,
- Generic_fromJSON } from "../../utils/JSONReviver";
-import { appendLineBreaks } from "../../utils/uiHelpers/appendLineBreaks";
-import { createElement } from "../../utils/uiHelpers/createElement";
-import { createPopup } from "../../utils/uiHelpers/createPopup";
-import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton";
-import { formatNumber } from "../../utils/StringHelperFunctions";
-import { getRandomInt } from "../../utils/helpers/getRandomInt";
-import { isString } from "../../utils/helpers/isString";
-import { KEY } from "../../utils/helpers/keyCodes";
-import { removeElement } from "../../utils/uiHelpers/removeElement";
-import { removeElementById } from "../../utils/uiHelpers/removeElementById";
-import { yesNoBoxCreate,
- yesNoBoxGetYesButton,
- yesNoBoxGetNoButton,
- yesNoBoxClose } from "../../utils/YesNoBox";
-
-// UI Related Imports
-import React from "react";
-import ReactDOM from "react-dom";
-import { CorporationRoot } from "./ui/Root";
-import { CorporationRouting } from "./ui/Routing";
-
-import Decimal from "decimal.js";
-
-/* Constants */
-export const INITIALSHARES = 1e9; //Total number of shares you have at your company
-export const SHARESPERPRICEUPDATE = 1e6; //When selling large number of shares, price is dynamically updated for every batch of this amount
-export const IssueNewSharesCooldown = 216e3; // 12 Hour in terms of game cycles
-export const SellSharesCooldown = 18e3; // 1 Hour in terms of game cycles
-
-export const CyclesPerMarketCycle = 50;
-export const CyclesPerIndustryStateCycle = CyclesPerMarketCycle / AllCorporationStates.length;
-export const SecsPerMarketCycle = CyclesPerMarketCycle / 5;
-
-export const Cities = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"];
-
-export const WarehouseInitialCost = 5e9; //Initial purchase cost of warehouse
-export const WarehouseInitialSize = 100;
-export const WarehouseUpgradeBaseCost = 1e9;
-
-export const OfficeInitialCost = 4e9;
-export const OfficeInitialSize = 3;
-export const OfficeUpgradeBaseCost = 1e9;
-
-export const BribeThreshold = 100e12; //Money needed to be able to bribe for faction rep
-export const BribeToRepRatio = 1e9; //Bribe Value divided by this = rep gain
-
-export const ProductProductionCostRatio = 5; //Ratio of material cost of a product to its production cost
-
-export const DividendMaxPercentage = 50;
-
-export const EmployeeSalaryMultiplier = 3; // Employee stats multiplied by this to determine initial salary
-export const CyclesPerEmployeeRaise = 400; // All employees get a raise every X market cycles
-export const EmployeeRaiseAmount = 50; // Employee salary increases by this (additive)
-
-export const BaseMaxProducts = 3; // Initial value for maximum number of products allowed
-
-// Delete Research Popup Box when clicking outside of it
-let researchTreeBoxOpened = false;
-let researchTreeBox = null;
-$(document).mousedown(function(event) {
- const contentId = "corporation-research-popup-box-content";
- if (researchTreeBoxOpened) {
- if ( $(event.target).closest("#" + contentId).get(0) == null ) {
- // Delete the box
- removeElement(researchTreeBox);
- researchTreeBox = null;
- researchTreeBoxOpened = false;
- }
- }
-});
-
-function Industry(params={}) {
- this.offices = { //Maps locations to offices. 0 if no office at that location
- [CityName.Aevum]: 0,
- [CityName.Chongqing]: 0,
- [CityName.Sector12]: new OfficeSpace({
- loc:CityName.Sector12,
- size:OfficeInitialSize,
- }),
- [CityName.NewTokyo]: 0,
- [CityName.Ishima]: 0,
- [CityName.Volhaven]: 0,
- };
-
- this.name = params.name ? params.name : 0;
- this.type = params.type ? params.type : Industries.Agriculture;
-
- this.sciResearch = new Material({name: "Scientific Research"});
- this.researched = {}; // Object of acquired Research. Keys = research name
-
- //A map of the NAME of materials required to create produced materials to
- //how many are needed to produce 1 unit of produced materials
- this.reqMats = {};
-
- //An array of the name of materials being produced
- this.prodMats = [];
-
- this.products = {};
- this.makesProducts = false;
-
- this.awareness = 0;
- this.popularity = 0; //Should always be less than awareness
- this.startingCost = 0;
-
- /* The following are factors for how much production/other things are increased by
- different factors. The production increase always has diminishing returns,
- and they are all reprsented by exponentials of < 1 (e.g x ^ 0.5, x ^ 0.8)
- The number for these represent the exponential. A lower number means more
- diminishing returns */
- this.reFac = 0; //Real estate Factor
- this.sciFac = 0; //Scientific Research Factor, affects quality
- this.hwFac = 0; //Hardware factor
- this.robFac = 0; //Robotics Factor
- this.aiFac = 0; //AI Cores factor;
- this.advFac = 0; //Advertising factor, affects sales
-
- this.prodMult = 0; //Production multiplier
-
- //Financials
- this.lastCycleRevenue = new Decimal(0);
- this.lastCycleExpenses = new Decimal(0);
- this.thisCycleRevenue = new Decimal(0);
- this.thisCycleExpenses = new Decimal(0);
-
- //Upgrades
- var numUpgrades = Object.keys(IndustryUpgrades).length;
- this.upgrades = Array(numUpgrades).fill(0);
-
- this.state = "START";
- this.newInd = true;
-
- this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location
- [CityName.Aevum]: 0,
- [CityName.Chonqing]: 0,
- [CityName.Sector12]: new Warehouse({
- corp: params.corp,
- industry: this,
- loc: CityName.Sector12,
- size: WarehouseInitialSize,
- }),
- [CityName.NewTokyo]: 0,
- [CityName.Ishima]: 0,
- [CityName.Volhaven]: 0,
- };
-
- this.init();
-}
-
-Industry.prototype.init = function() {
- //Set the unique properties of an industry (how much its affected by real estate/scientific research, etc.)
- this.startingCost = IndustryStartingCosts[this.type];
- switch (this.type) {
- case Industries.Energy:
- this.reFac = 0.65;
- this.sciFac = 0.7;
- this.robFac = 0.05;
- this.aiFac = 0.3;
- this.advFac = 0.08;
- this.reqMats = {
- "Hardware": 0.1,
- "Metal": 0.2,
- };
- this.prodMats = ["Energy"];
- break;
- case Industries.Utilities:
- case "Utilities":
- this.reFac = 0.5;
- this.sciFac = 0.6;
- this.robFac = 0.4;
- this.aiFac = 0.4;
- this.advFac = 0.08;
- this.reqMats = {
- "Hardware": 0.1,
- "Metal": 0.1,
- }
- this.prodMats = ["Water"];
- break;
- case Industries.Agriculture:
- this.reFac = 0.72;
- this.sciFac = 0.5;
- this.hwFac = 0.2;
- this.robFac = 0.3;
- this.aiFac = 0.3;
- this.advFac = 0.04;
- this.reqMats = {
- "Water": 0.5,
- "Energy": 0.5,
- }
- this.prodMats = ["Plants", "Food"];
- break;
- case Industries.Fishing:
- this.reFac = 0.15;
- this.sciFac = 0.35;
- this.hwFac = 0.35;
- this.robFac = 0.5;
- this.aiFac = 0.2;
- this.advFac = 0.08;
- this.reqMats = {
- "Energy": 0.5,
- }
- this.prodMats = ["Food"];
- break;
- case Industries.Mining:
- this.reFac = 0.3;
- this.sciFac = 0.26;
- this.hwFac = 0.4;
- this.robFac = 0.45;
- this.aiFac = 0.45;
- this.advFac = 0.06;
- this.reqMats = {
- "Energy": 0.8,
- }
- this.prodMats = ["Metal"];
- break;
- case Industries.Food:
- //reFac is unique for this bc it diminishes greatly per city. Handle this separately in code?
- this.sciFac = 0.12;
- this.hwFac = 0.15;
- this.robFac = 0.3;
- this.aiFac = 0.25;
- this.advFac = 0.25;
- this.reFac = 0.05;
- this.reqMats = {
- "Food": 0.5,
- "Water": 0.5,
- "Energy": 0.2,
- }
- this.makesProducts = true;
- break;
- case Industries.Tobacco:
- this.reFac = 0.15;
- this.sciFac = 0.75;
- this.hwFac = 0.15;
- this.robFac = 0.2;
- this.aiFac = 0.15;
- this.advFac = 0.2;
- this.reqMats = {
- "Plants": 1,
- "Water": 0.2,
- }
- this.makesProducts = true;
- break;
- case Industries.Chemical:
- this.reFac = 0.25;
- this.sciFac = 0.75;
- this.hwFac = 0.2;
- this.robFac = 0.25;
- this.aiFac = 0.2;
- this.advFac = 0.07;
- this.reqMats = {
- "Plants": 1,
- "Energy": 0.5,
- "Water": 0.5,
- }
- this.prodMats = ["Chemicals"];
- break;
- case Industries.Pharmaceutical:
- this.reFac = 0.05;
- this.sciFac = 0.8;
- this.hwFac = 0.15;
- this.robFac = 0.25;
- this.aiFac = 0.2;
- this.advFac = 0.16;
- this.reqMats = {
- "Chemicals": 2,
- "Energy": 1,
- "Water": 0.5,
- }
- this.prodMats = ["Drugs"];
- this.makesProducts = true;
- break;
- case Industries.Computer:
- case "Computer":
- this.reFac = 0.2;
- this.sciFac = 0.62;
- this.robFac = 0.36;
- this.aiFac = 0.19;
- this.advFac = 0.17;
- this.reqMats = {
- "Metal": 2,
- "Energy": 1,
- }
- this.prodMats = ["Hardware"];
- this.makesProducts = true;
- break;
- case Industries.Robotics:
- this.reFac = 0.32;
- this.sciFac = 0.65;
- this.aiFac = 0.36;
- this.advFac = 0.18;
- this.hwFac = 0.19;
- this.reqMats = {
- "Hardware": 5,
- "Energy": 3,
- }
- this.prodMats = ["Robots"];
- this.makesProducts = true;
- break;
- case Industries.Software:
- this.sciFac = 0.62;
- this.advFac = 0.16;
- this.hwFac = 0.25;
- this.reFac = 0.15;
- this.aiFac = 0.18;
- this.robFac = 0.05;
- this.reqMats = {
- "Hardware": 0.5,
- "Energy": 0.5,
- }
- this.prodMats = ["AICores"];
- this.makesProducts = true;
- break;
- case Industries.Healthcare:
- this.reFac = 0.1;
- this.sciFac = 0.75;
- this.advFac = 0.11;
- this.hwFac = 0.1;
- this.robFac = 0.1;
- this.aiFac = 0.1;
- this.reqMats = {
- "Robots": 10,
- "AICores": 5,
- "Energy": 5,
- "Water": 5,
- }
- this.makesProducts = true;
- break;
- case Industries.RealEstate:
- this.robFac = 0.6;
- this.aiFac = 0.6;
- this.advFac = 0.25;
- this.sciFac = 0.05;
- this.hwFac = 0.05;
- this.reqMats = {
- "Metal": 5,
- "Energy": 5,
- "Water": 2,
- "Hardware": 4,
- }
- this.prodMats = ["RealEstate"];
- this.makesProducts = true;
- break;
- default:
- console.error(`Invalid Industry Type passed into Industry.init(): ${this.type}`);
- return;
- }
-}
-
-Industry.prototype.getProductDescriptionText = function() {
- if (!this.makesProducts) {return;}
- switch (this.type) {
- case Industries.Food:
- return "create and manage restaurants";
- case Industries.Tobacco:
- return "create tobacco and tobacco-related products";
- case Industries.Pharmaceutical:
- return "develop new pharmaceutical drugs";
- case Industries.Computer:
- case "Computer":
- return "create new computer hardware and networking infrastructures";
- case Industries.Robotics:
- return "build specialized robots and robot-related products";
- case Industries.Software:
- return "develop computer software";
- case Industries.Healthcare:
- return "build and manage hospitals";
- case Industries.RealEstate:
- return "develop and manage real estate properties";
- default:
- console.error("Invalid industry type in Industry.getProductDescriptionText");
- return "";
- }
-}
-
-Industry.prototype.getMaximumNumberProducts = function() {
- if (!this.makesProducts) { return 0; }
-
- // Calculate additional number of allowed Products from Research/Upgrades
- let additional = 0;
- if (this.hasResearch("uPgrade: Capacity.I")) { ++additional; }
- if (this.hasResearch("uPgrade: Capacity.II")) { ++additional; }
-
- return BaseMaxProducts + additional;
-}
-
-Industry.prototype.hasMaximumNumberProducts = function() {
- return (Object.keys(this.products).length >= this.getMaximumNumberProducts());
-}
-
-//Calculates the values that factor into the production and properties of
-//materials/products (such as quality, etc.)
-Industry.prototype.calculateProductionFactors = function() {
- var multSum = 0;
- for (var i = 0; i < Cities.length; ++i) {
- var city = Cities[i];
- var warehouse = this.warehouses[city];
- if (!(warehouse instanceof Warehouse)) {
- continue;
- }
-
- var materials = warehouse.materials;
-
- var cityMult = Math.pow(0.002 * materials.RealEstate.qty+1, this.reFac) *
- Math.pow(0.002 * materials.Hardware.qty+1, this.hwFac) *
- Math.pow(0.002 * materials.Robots.qty+1, this.robFac) *
- Math.pow(0.002 * materials.AICores.qty+1, this.aiFac);
- multSum += Math.pow(cityMult, 0.73);
- }
-
- multSum < 1 ? this.prodMult = 1 : this.prodMult = multSum;
-}
-
-Industry.prototype.updateWarehouseSizeUsed = function(warehouse) {
- if (warehouse instanceof Warehouse) {
- //This resets the size back to 0 and then accounts for materials
- warehouse.updateMaterialSizeUsed();
- }
-
- for (var prodName in this.products) {
- if (this.products.hasOwnProperty(prodName)) {
- var prod = this.products[prodName];
- warehouse.sizeUsed += (prod.data[warehouse.loc][0] * prod.siz);
- if (prod.data[warehouse.loc][0] > 0) {
- warehouse.breakdown += (prodName + ": " + formatNumber(prod.data[warehouse.loc][0] * prod.siz, 0) + " ");
- }
- }
- }
-}
-
-Industry.prototype.process = function(marketCycles=1, state, company) {
- this.state = state;
-
- //At the start of a cycle, store and reset revenue/expenses
- //Then calculate salaries and processs the markets
- if (state === "START") {
- if (isNaN(this.thisCycleRevenue) || isNaN(this.thisCycleExpenses)) {
- console.error("NaN in Corporation's computed revenue/expenses");
- dialogBoxCreate("Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer");
- this.thisCycleRevenue = new Decimal(0);
- this.thisCycleExpenses = new Decimal(0);
- }
- this.lastCycleRevenue = this.thisCycleRevenue.dividedBy(marketCycles * SecsPerMarketCycle);
- this.lastCycleExpenses = this.thisCycleExpenses.dividedBy(marketCycles * SecsPerMarketCycle);
- this.thisCycleRevenue = new Decimal(0);
- this.thisCycleExpenses = new Decimal(0);
-
- // Once you start making revenue, the player should no longer be
- // considered new, and therefore no longer needs the 'tutorial' UI elements
- if (this.lastCycleRevenue.gt(0)) {this.newInd = false;}
-
- // Process offices (and the employees in them)
- var employeeSalary = 0;
- for (var officeLoc in this.offices) {
- if (this.offices[officeLoc] instanceof OfficeSpace) {
- employeeSalary += this.offices[officeLoc].process(marketCycles, {industry:this, corporation:company});
- }
- }
- this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary);
-
- // Process change in demand/competition of materials/products
- this.processMaterialMarket(marketCycles);
- this.processProductMarket(marketCycles);
-
- // Process loss of popularity
- this.popularity -= (marketCycles * .0001);
- this.popularity = Math.max(0, this.popularity);
-
- // Process Dreamsense gains
- var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4;
- if (popularityGain > 0) {
- this.popularity += (popularityGain * marketCycles);
- this.awareness += (awarenessGain * marketCycles);
- }
-
- return;
- }
-
- // Process production, purchase, and import/export of materials
- let res = this.processMaterials(marketCycles, company);
- if (Array.isArray(res)) {
- this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
- this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
- }
-
- // Process creation, production & sale of products
- res = this.processProducts(marketCycles, company);
- if (Array.isArray(res)) {
- this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
- this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
- }
-}
-
-// Process change in demand and competition for this industry's materials
-Industry.prototype.processMaterialMarket = function() {
- //References to prodMats and reqMats
- var reqMats = this.reqMats, prodMats = this.prodMats;
-
- //Only 'process the market' for materials that this industry deals with
- for (var i = 0; i < Cities.length; ++i) {
- //If this industry has a warehouse in this city, process the market
- //for every material this industry requires or produces
- if (this.warehouses[Cities[i]] instanceof Warehouse) {
- var wh = this.warehouses[Cities[i]];
- for (var name in reqMats) {
- if (reqMats.hasOwnProperty(name)) {
- wh.materials[name].processMarket();
- }
- }
-
- //Produced materials are stored in an array
- for (var foo = 0; foo < prodMats.length; ++foo) {
- wh.materials[prodMats[foo]].processMarket();
- }
-
- //Process these twice because these boost production
- wh.materials["Hardware"].processMarket();
- wh.materials["Robots"].processMarket();
- wh.materials["AICores"].processMarket();
- wh.materials["RealEstate"].processMarket();
- }
- }
-}
-
-// Process change in demand and competition for this industry's products
-Industry.prototype.processProductMarket = function(marketCycles=1) {
- // Demand gradually decreases, and competition gradually increases
- for (const name in this.products) {
- if (this.products.hasOwnProperty(name)) {
- const product = this.products[name];
- let change = getRandomInt(0, 3) * 0.0004;
- if (change === 0) { continue; }
-
- if (this.type === Industries.Pharmaceutical || this.type === Industries.Software ||
- this.type === Industries.Robotics) {
- change *= 3;
- }
- change *= marketCycles;
- product.dmd -= change;
- product.cmp += change;
- product.cmp = Math.min(product.cmp, 99.99);
- product.dmd = Math.max(product.dmd, 0.001);
- }
- }
-}
-
-//Process production, purchase, and import/export of materials
-Industry.prototype.processMaterials = function(marketCycles=1, company) {
- var revenue = 0, expenses = 0;
- this.calculateProductionFactors();
-
- //At the start of the export state, set the imports of everything to 0
- if (this.state === "EXPORT") {
- for (let i = 0; i < Cities.length; ++i) {
- var city = Cities[i], office = this.offices[city];
- if (!(this.warehouses[city] instanceof Warehouse)) {
- continue;
- }
- var warehouse = this.warehouses[city];
- for (var matName in warehouse.materials) {
- if (warehouse.materials.hasOwnProperty(matName)) {
- var mat = warehouse.materials[matName];
- mat.imp = 0;
- }
- }
- }
- }
-
- for (let i = 0; i < Cities.length; ++i) {
- var city = Cities[i], office = this.offices[city];
-
- if (this.warehouses[city] instanceof Warehouse) {
- var warehouse = this.warehouses[city];
-
- switch(this.state) {
-
- case "PURCHASE":
- /* Process purchase of materials */
- for (var matName in warehouse.materials) {
- if (warehouse.materials.hasOwnProperty(matName)) {
- (function(matName, ind) {
- var mat = warehouse.materials[matName];
- var buyAmt, maxAmt;
- if (warehouse.smartSupplyEnabled && Object.keys(ind.reqMats).includes(matName)) {
- //Smart supply tracker is stored as per second rate
- mat.buy = ind.reqMats[matName] * warehouse.smartSupplyStore;
- buyAmt = mat.buy * SecsPerMarketCycle * marketCycles;
- } else {
- buyAmt = (mat.buy * SecsPerMarketCycle * marketCycles);
- }
-
- if (matName == "RealEstate") {
- maxAmt = buyAmt;
- } else {
- maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]);
- }
- var buyAmt = Math.min(buyAmt, maxAmt);
- if (buyAmt > 0) {
- mat.qty += buyAmt;
- expenses += (buyAmt * mat.bCost);
- }
- })(matName, this);
- this.updateWarehouseSizeUsed(warehouse);
- }
- } //End process purchase of materials
- break;
-
- case "PRODUCTION":
- warehouse.smartSupplyStore = 0; //Reset smart supply amount
-
- /* Process production of materials */
- if (this.prodMats.length > 0) {
- var mat = warehouse.materials[this.prodMats[0]];
- //Calculate the maximum production of this material based
- //on the office's productivity
- var maxProd = this.getOfficeProductivity(office)
- * this.prodMult // Multiplier from materials
- * company.getProductionMultiplier()
- * this.getProductionMultiplier(); // Multiplier from Research
- let prod;
-
- if (mat.prdman[0]) {
- //Production is manually limited
- prod = Math.min(maxProd, mat.prdman[1]);
- } else {
- prod = maxProd;
- }
- prod *= (SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle
-
- // Calculate net change in warehouse storage making the produced materials will cost
- var totalMatSize = 0;
- for (let tmp = 0; tmp < this.prodMats.length; ++tmp) {
- totalMatSize += (MaterialSizes[this.prodMats[tmp]]);
- }
- for (const reqMatName in this.reqMats) {
- var normQty = this.reqMats[reqMatName];
- totalMatSize -= (MaterialSizes[reqMatName] * normQty);
- }
- // If not enough space in warehouse, limit the amount of produced materials
- if (totalMatSize > 0) {
- var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize);
- prod = Math.min(maxAmt, prod);
- }
-
- if (prod < 0) {prod = 0;}
-
- // Keep track of production for smart supply (/s)
- warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles));
-
- // Make sure we have enough resource to make our materials
- var producableFrac = 1;
- for (var reqMatName in this.reqMats) {
- if (this.reqMats.hasOwnProperty(reqMatName)) {
- var req = this.reqMats[reqMatName] * prod;
- if (warehouse.materials[reqMatName].qty < req) {
- producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req);
- }
- }
- }
- if (producableFrac <= 0) {producableFrac = 0; prod = 0;}
-
- // Make our materials if they are producable
- if (producableFrac > 0 && prod > 0) {
- for (const reqMatName in this.reqMats) {
- var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac);
- warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
- warehouse.materials[reqMatName].prd = 0;
- warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles);
- }
- for (let j = 0; j < this.prodMats.length; ++j) {
- warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac);
- warehouse.materials[this.prodMats[j]].qlt =
- (office.employeeProd[EmployeePositions.Engineer] / 90 +
- Math.pow(this.sciResearch.qty, this.sciFac) +
- Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3);
- }
- } else {
- for (const reqMatName in this.reqMats) {
- if (this.reqMats.hasOwnProperty(reqMatName)) {
- warehouse.materials[reqMatName].prd = 0;
- }
- }
- }
-
- //Per second
- const fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles);
- for (let fooI = 0; fooI < this.prodMats.length; ++fooI) {
- warehouse.materials[this.prodMats[fooI]].prd = fooProd;
- }
- } else {
- //If this doesn't produce any materials, then it only creates
- //Products. Creating products will consume materials. The
- //Production of all consumed materials must be set to 0
- for (const reqMatName in this.reqMats) {
- warehouse.materials[reqMatName].prd = 0;
- }
- }
- break;
-
- case "SALE":
- /* Process sale of materials */
- for (var matName in warehouse.materials) {
- if (warehouse.materials.hasOwnProperty(matName)) {
- var mat = warehouse.materials[matName];
- if (mat.sCost < 0 || mat.sllman[0] === false) {
- mat.sll = 0;
- continue;
- }
-
- // Sale multipliers
- const businessFactor = this.getBusinessFactor(office); //Business employee productivity
- const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
- const marketFactor = this.getMarketFactor(mat); //Competition + demand
-
- // Determine the cost that the material will be sold at
- const markupLimit = mat.getMarkupLimit();
- var sCost;
- if (mat.marketTa2) {
- const prod = mat.prd;
-
- // Reverse engineer the 'maxSell' formula
- // 1. Set 'maxSell' = prod
- // 2. Substitute formula for 'markup'
- // 3. Solve for 'sCost'
- const numerator = markupLimit;
- const sqrtNumerator = prod;
- const sqrtDenominator = ((mat.qlt + .001)
- * marketFactor
- * businessFactor
- * company.getSalesMultiplier()
- * advertisingFactor
- * this.getSalesMultiplier());
- const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
- let optimalPrice;
- if (sqrtDenominator === 0 || denominator === 0) {
- if (sqrtNumerator === 0) {
- optimalPrice = 0; // No production
- } else {
- optimalPrice = mat.bCost + markupLimit;
- console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
- }
- } else {
- optimalPrice = (numerator / denominator) + mat.bCost;
- }
-
- // We'll store this "Optimal Price" in a property so that we don't have
- // to re-calculate it for the UI
- mat.marketTa2Price = optimalPrice;
-
- sCost = optimalPrice;
- } else if (mat.marketTa1) {
- sCost = mat.bCost + markupLimit;
- } else if (isString(mat.sCost)) {
- sCost = mat.sCost.replace(/MP/g, mat.bCost);
- sCost = eval(sCost);
- } else {
- sCost = mat.sCost;
- }
-
- // Calculate how much of the material sells (per second)
- let markup = 1;
- if (sCost > mat.bCost) {
- //Penalty if difference between sCost and bCost is greater than markup limit
- if ((sCost - mat.bCost) > markupLimit) {
- markup = Math.pow(markupLimit / (sCost - mat.bCost), 2);
- }
- } else if (sCost < mat.bCost) {
- if (sCost <= 0) {
- markup = 1e12; //Sell everything, essentially discard
- } else {
- //Lower prices than market increases sales
- markup = mat.bCost / sCost;
- }
- }
-
- var maxSell = (mat.qlt + .001)
- * marketFactor
- * markup
- * businessFactor
- * company.getSalesMultiplier()
- * advertisingFactor
- * this.getSalesMultiplier();
- var sellAmt;
- if (isString(mat.sllman[1])) {
- //Dynamically evaluated
- var tmp = mat.sllman[1].replace(/MAX/g, maxSell);
- tmp = tmp.replace(/PROD/g, mat.prd);
- try {
- sellAmt = eval(tmp);
- } catch(e) {
- dialogBoxCreate("Error evaluating your sell amount for material " + mat.name +
- " in " + this.name + "'s " + city + " office. The sell amount " +
- "is being set to zero");
- sellAmt = 0;
- }
- sellAmt = Math.min(maxSell, sellAmt);
- } else if (mat.sllman[1] === -1) {
- //Backwards compatibility, -1 = MAX
- sellAmt = maxSell;
- } else {
- //Player's input value is just a number
- sellAmt = Math.min(maxSell, mat.sllman[1]);
- }
-
- sellAmt = (sellAmt * SecsPerMarketCycle * marketCycles);
- sellAmt = Math.min(mat.qty, sellAmt);
- if (sellAmt < 0) {
- console.warn(`sellAmt calculated to be negative for ${matName} in ${city}`);
- mat.sll = 0;
- continue;
- }
- if (sellAmt && sCost >= 0) {
- mat.qty -= sellAmt;
- revenue += (sellAmt * sCost);
- mat.sll = sellAmt / (SecsPerMarketCycle * marketCycles);
- } else {
- mat.sll = 0;
- }
- }
- } //End processing of sale of materials
- break;
-
- case "EXPORT":
- for (var matName in warehouse.materials) {
- if (warehouse.materials.hasOwnProperty(matName)) {
- var mat = warehouse.materials[matName];
- mat.totalExp = 0; //Reset export
- for (var expI = 0; expI < mat.exp.length; ++expI) {
- var exp = mat.exp[expI];
- var amt = exp.amt.replace(/MAX/g, mat.qty / (SecsPerMarketCycle * marketCycles));
- try {
- amt = eval(amt);
- } catch(e) {
- dialogBoxCreate("Calculating export for " + mat.name + " in " +
- this.name + "'s " + city + " division failed with " +
- "error: " + e);
- continue;
- }
- if (isNaN(amt)) {
- dialogBoxCreate("Error calculating export amount for " + mat.name + " in " +
- this.name + "'s " + city + " division.");
- continue;
- }
- amt = amt * SecsPerMarketCycle * marketCycles;
-
- if (mat.qty < amt) {
- amt = mat.qty;
- }
- if (amt === 0) {
- break; //None left
- }
- for (var foo = 0; foo < company.divisions.length; ++foo) {
- if (company.divisions[foo].name === exp.ind) {
- var expIndustry = company.divisions[foo];
- var expWarehouse = expIndustry.warehouses[exp.city];
- if (!(expWarehouse instanceof Warehouse)) {
- console.error(`Invalid export! ${expIndustry.name} ${exp.city}`);
- break;
- }
-
- // Make sure theres enough space in warehouse
- if (expWarehouse.sizeUsed >= expWarehouse.size) {
- // Warehouse at capacity. Exporting doesnt
- // affect revenue so just return 0's
- return [0, 0];
- } else {
- var maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]);
- amt = Math.min(maxAmt, amt);
- }
- expWarehouse.materials[matName].imp += (amt / (SecsPerMarketCycle * marketCycles));
- expWarehouse.materials[matName].qty += amt;
- expWarehouse.materials[matName].qlt = mat.qlt;
- mat.qty -= amt;
- mat.totalExp += amt;
- expIndustry.updateWarehouseSizeUsed(expWarehouse);
- break;
- }
- }
- }
- //totalExp should be per second
- mat.totalExp /= (SecsPerMarketCycle * marketCycles);
- }
- }
-
- break;
-
- case "START":
- break;
- default:
- console.error(`Invalid state: ${this.state}`);
- break;
- } //End switch(this.state)
- this.updateWarehouseSizeUsed(warehouse);
-
- } // End warehouse
-
- //Produce Scientific Research based on R&D employees
- //Scientific Research can be produced without a warehouse
- if (office instanceof OfficeSpace) {
- this.sciResearch.qty += (.004
- * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5)
- * company.getScientificResearchMultiplier()
- * this.getScientificResearchMultiplier());
- }
- }
- return [revenue, expenses];
-}
-
-//Process production & sale of this industry's FINISHED products (including all of their stats)
-Industry.prototype.processProducts = function(marketCycles=1, corporation) {
- var revenue = 0, expenses = 0;
-
- //Create products
- if (this.state === "PRODUCTION") {
- for (const prodName in this.products) {
- const prod = this.products[prodName];
- if (!prod.fin) {
- const city = prod.createCity;
- const office = this.offices[city];
-
- // Designing/Creating a Product is based mostly off Engineers
- const engrProd = office.employeeProd[EmployeePositions.Engineer];
- const mgmtProd = office.employeeProd[EmployeePositions.Management];
- const opProd = office.employeeProd[EmployeePositions.Operations];
- const total = engrProd + mgmtProd + opProd;
- if (total <= 0) { break; }
-
- // Management is a multiplier for the production from Engineers
- const mgmtFactor = 1 + (mgmtProd / (1.2 * total));
-
- const progress = (Math.pow(engrProd, 0.34) + Math.pow(opProd, 0.2)) * mgmtFactor;
-
- prod.createProduct(marketCycles, progress);
- if (prod.prog >= 100) {
- prod.finishProduct(office.employeeProd, this);
- }
- break;
- }
- }
- }
-
- //Produce Products
- for (var prodName in this.products) {
- if (this.products.hasOwnProperty(prodName)) {
- var prod = this.products[prodName];
- if (prod instanceof Product && prod.fin) {
- revenue += this.processProduct(marketCycles, prod, corporation);
- }
- }
- }
- return [revenue, expenses];
-}
-
-//Processes FINISHED products
-Industry.prototype.processProduct = function(marketCycles=1, product, corporation) {
- let totalProfit = 0;
- for (let i = 0; i < Cities.length; ++i) {
- let city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city];
- if (warehouse instanceof Warehouse) {
- switch(this.state) {
-
- case "PRODUCTION": {
- //Calculate the maximum production of this material based
- //on the office's productivity
- var maxProd = this.getOfficeProductivity(office, {forProduct:true})
- * corporation.getProductionMultiplier()
- * this.prodMult // Multiplier from materials
- * this.getProductionMultiplier() // Multiplier from research
- * this.getProductProductionMultiplier(); // Multiplier from research
- let prod;
-
- //Account for whether production is manually limited
- if (product.prdman[city][0]) {
- prod = Math.min(maxProd, product.prdman[city][1]);
- } else {
- prod = maxProd;
- }
- prod *= (SecsPerMarketCycle * marketCycles);
-
- //Calculate net change in warehouse storage making the Products will cost
- var netStorageSize = product.siz;
- for (var reqMatName in product.reqMats) {
- if (product.reqMats.hasOwnProperty(reqMatName)) {
- var normQty = product.reqMats[reqMatName];
- netStorageSize -= (MaterialSizes[reqMatName] * normQty);
- }
- }
-
- //If there's not enough space in warehouse, limit the amount of Product
- if (netStorageSize > 0) {
- var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / netStorageSize);
- prod = Math.min(maxAmt, prod);
- }
-
- warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles));
-
- //Make sure we have enough resources to make our Products
- var producableFrac = 1;
- for (var reqMatName in product.reqMats) {
- if (product.reqMats.hasOwnProperty(reqMatName)) {
- var req = product.reqMats[reqMatName] * prod;
- if (warehouse.materials[reqMatName].qty < req) {
- producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req);
- }
- }
- }
-
- //Make our Products if they are producable
- if (producableFrac > 0 && prod > 0) {
- for (var reqMatName in product.reqMats) {
- if (product.reqMats.hasOwnProperty(reqMatName)) {
- var reqMatQtyNeeded = (product.reqMats[reqMatName] * prod * producableFrac);
- warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
- warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles);
- }
- }
- //Quantity
- product.data[city][0] += (prod * producableFrac);
- }
-
- //Keep track of production Per second
- product.data[city][1] = prod * producableFrac / (SecsPerMarketCycle * marketCycles);
- break;
- }
- case "SALE": {
- //Process sale of Products
- product.pCost = 0; //Estimated production cost
- for (var reqMatName in product.reqMats) {
- if (product.reqMats.hasOwnProperty(reqMatName)) {
- product.pCost += (product.reqMats[reqMatName] * warehouse.materials[reqMatName].bCost);
- }
- }
-
- // Since its a product, its production cost is increased for labor
- product.pCost *= ProductProductionCostRatio;
-
- // Sale multipliers
- const businessFactor = this.getBusinessFactor(office); //Business employee productivity
- const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
- const marketFactor = this.getMarketFactor(product); //Competition + demand
-
- // Calculate Sale Cost (sCost), which could be dynamically evaluated
- const markupLimit = product.rat / product.mku;
- var sCost;
- if (product.marketTa2) {
- const prod = product.data[city][1];
-
- // Reverse engineer the 'maxSell' formula
- // 1. Set 'maxSell' = prod
- // 2. Substitute formula for 'markup'
- // 3. Solve for 'sCost'roduct.pCost = sCost
- const numerator = markupLimit;
- const sqrtNumerator = prod;
- const sqrtDenominator = (0.5
- * Math.pow(product.rat, 0.65)
- * marketFactor
- * corporation.getSalesMultiplier()
- * businessFactor
- * advertisingFactor
- * this.getSalesMultiplier());
- const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
- let optimalPrice;
- if (sqrtDenominator === 0 || denominator === 0) {
- if (sqrtNumerator === 0) {
- optimalPrice = 0; // No production
- } else {
- optimalPrice = product.pCost + markupLimit;
- console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
- }
- } else {
- optimalPrice = (numerator / denominator) + product.pCost;
- }
-
- // Store this "optimal Price" in a property so we don't have to re-calculate for UI
- product.marketTa2Price[city] = optimalPrice;
- sCost = optimalPrice;
- } else if (product.marketTa1) {
- sCost = product.pCost + markupLimit;
- } else if (isString(product.sCost)) {
- if(product.mku === 0) {
- console.error(`mku is zero, reverting to 1 to avoid Infinity`);
- product.mku = 1;
- }
- sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku);
- sCost = eval(sCost);
-
- } else {
- sCost = product.sCost;
- }
-
- var markup = 1;
- if (sCost > product.pCost) {
- if ((sCost - product.pCost) > markupLimit) {
- markup = markupLimit / (sCost - product.pCost);
- }
- }
-
- var maxSell = 0.5
- * Math.pow(product.rat, 0.65)
- * marketFactor
- * corporation.getSalesMultiplier()
- * Math.pow(markup, 2)
- * businessFactor
- * advertisingFactor
- * this.getSalesMultiplier();
- var sellAmt;
- if (product.sllman[city][0] && isString(product.sllman[city][1])) {
- //Sell amount is dynamically evaluated
- var tmp = product.sllman[city][1].replace(/MAX/g, maxSell);
- tmp = tmp.replace(/PROD/g, product.data[city][1]);
- try {
- tmp = eval(tmp);
- } catch(e) {
- dialogBoxCreate("Error evaluating your sell price expression for " + product.name +
- " in " + this.name + "'s " + city + " office. Sell price is being set to MAX");
- tmp = maxSell;
- }
- sellAmt = Math.min(maxSell, tmp);
- } else if (product.sllman[city][0] && product.sllman[city][1] > 0) {
- //Sell amount is manually limited
- sellAmt = Math.min(maxSell, product.sllman[city][1]);
- } else if (product.sllman[city][0] === false){
- sellAmt = 0;
- } else {
- sellAmt = maxSell;
- }
- if (sellAmt < 0) { sellAmt = 0; }
- sellAmt = sellAmt * SecsPerMarketCycle * marketCycles;
- sellAmt = Math.min(product.data[city][0], sellAmt); //data[0] is qty
- if (sellAmt && sCost) {
- product.data[city][0] -= sellAmt; //data[0] is qty
- totalProfit += (sellAmt * sCost);
- product.data[city][2] = sellAmt / (SecsPerMarketCycle * marketCycles); //data[2] is sell property
- } else {
- product.data[city][2] = 0; //data[2] is sell property
- }
- break;
- }
- case "START":
- case "PURCHASE":
- case "EXPORT":
- break;
- default:
- console.error(`Invalid State: ${this.state}`);
- break;
- } //End switch(this.state)
- }
- }
- return totalProfit;
-}
-
-Industry.prototype.discontinueProduct = function(product) {
- for (var productName in this.products) {
- if (this.products.hasOwnProperty(productName)) {
- if (product === this.products[productName]) {
- delete this.products[productName];
- }
- }
- }
-}
-
-Industry.prototype.upgrade = function(upgrade, refs) {
- var corporation = refs.corporation;
- var office = refs.office;
- var upgN = upgrade[0];
- while (this.upgrades.length <= upgN) {this.upgrades.push(0);}
- ++this.upgrades[upgN];
-
- switch (upgN) {
- case 0: //Coffee, 5% energy per employee
- for (let i = 0; i < office.employees.length; ++i) {
- office.employees[i].ene = Math.min(office.employees[i].ene * 1.05, office.maxEne);
- }
- break;
- case 1: //AdVert.Inc,
- var advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier();
- this.awareness += (3 * advMult);
- this.popularity += (1 * advMult);
- this.awareness *= (1.01 * advMult);
- this.popularity *= ((1 + getRandomInt(1, 3) / 100) * advMult);
- break;
- default:
- console.error(`Un-implemented function index: ${upgN}`);
- break;
- }
-}
-
-// Returns how much of a material can be produced based of office productivity (employee stats)
-Industry.prototype.getOfficeProductivity = function(office, params) {
- const opProd = office.employeeProd[EmployeePositions.Operations];
- const engrProd = office.employeeProd[EmployeePositions.Engineer];
- const mgmtProd = office.employeeProd[EmployeePositions.Management]
- const total = opProd + engrProd + mgmtProd;
-
- if (total <= 0) { return 0; }
-
- // Management is a multiplier for the production from Operations and Engineers
- const mgmtFactor = 1 + (mgmtProd / (1.2 * total));
-
- // For production, Operations is slightly more important than engineering
- // Both Engineering and Operations have diminishing returns
- const prod = (Math.pow(opProd, 0.4) + Math.pow(engrProd, 0.3)) * mgmtFactor;
-
- // Generic multiplier for the production. Used for game-balancing purposes
- const balancingMult = 0.05;
-
- if (params && params.forProduct) {
- // Products are harder to create and therefore have less production
- return 0.5 * balancingMult * prod;
- } else {
- return balancingMult * prod;
- }
-}
-
-// Returns a multiplier based on the office' 'Business' employees that affects sales
-Industry.prototype.getBusinessFactor = function(office) {
- const businessProd = 1 + office.employeeProd[EmployeePositions.Business];
-
- return calculateEffectWithFactors(businessProd, 0.26, 10e3);
-}
-
-//Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This
-//multiplier affects sales. The result is:
-// [Total sales mult, total awareness mult, total pop mult, awareness/pop ratio mult]
-Industry.prototype.getAdvertisingFactors = function() {
- var awarenessFac = Math.pow(this.awareness + 1, this.advFac);
- var popularityFac = Math.pow(this.popularity + 1, this.advFac);
- var ratioFac = (this.awareness === 0 ? 0.01 : Math.max((this.popularity + .001) / this.awareness, 0.01));
- var totalFac = Math.pow(awarenessFac * popularityFac * ratioFac, 0.85);
- return [totalFac, awarenessFac, popularityFac, ratioFac];
-}
-
-//Returns a multiplier based on a materials demand and competition that affects sales
-Industry.prototype.getMarketFactor = function(mat) {
- return Math.max(0.1, mat.dmd * (100 - mat.cmp) / 100);
-}
-
-// Returns a boolean indicating whether this Industry has the specified Research
-Industry.prototype.hasResearch = function(name) {
- return (this.researched[name] === true);
-}
-
-Industry.prototype.updateResearchTree = function() {
- const researchTree = IndustryResearchTrees[this.type];
-
- // Since ResearchTree data isnt saved, we'll update the Research Tree data
- // based on the stored 'researched' property in the Industry object
- if (Object.keys(researchTree.researched).length !== Object.keys(this.researched).length) {
- for (let research in this.researched) {
- researchTree.research(research);
- }
- }
-}
-
-// Get multipliers from Research
-Industry.prototype.getAdvertisingMultiplier = function() {
- this.updateResearchTree();
- return IndustryResearchTrees[this.type].getAdvertisingMultiplier();
-}
-
-Industry.prototype.getEmployeeChaMultiplier = function() {
- this.updateResearchTree();
- return IndustryResearchTrees[this.type].getEmployeeChaMultiplier();
-}
-
-Industry.prototype.getEmployeeCreMultiplier = function() {
- this.updateResearchTree();
- return IndustryResearchTrees[this.type].getEmployeeCreMultiplier();
-}
-
-Industry.prototype.getEmployeeEffMultiplier = function() {
- this.updateResearchTree();
- return IndustryResearchTrees[this.type].getEmployeeEffMultiplier();
-}
-
-Industry.prototype.getEmployeeIntMultiplier = function() {
- this.updateResearchTree();
- return IndustryResearchTrees[this.type].getEmployeeIntMultiplier();
-}
-
-Industry.prototype.getProductionMultiplier = function() {
- this.updateResearchTree();
- return IndustryResearchTrees[this.type].getProductionMultiplier();
-}
-
-Industry.prototype.getProductProductionMultiplier = function() {
- this.updateResearchTree();
- return IndustryResearchTrees[this.type].getProductProductionMultiplier();
-}
-
-Industry.prototype.getSalesMultiplier = function() {
- this.updateResearchTree();
- return IndustryResearchTrees[this.type].getSalesMultiplier();
-}
-
-Industry.prototype.getScientificResearchMultiplier = function() {
- this.updateResearchTree();
- return IndustryResearchTrees[this.type].getScientificResearchMultiplier();
-}
-
-Industry.prototype.getStorageMultiplier = function() {
- this.updateResearchTree();
- return IndustryResearchTrees[this.type].getStorageMultiplier();
-}
-
-Industry.prototype.toJSON = function() {
- return Generic_toJSON("Industry", this);
-}
-
-Industry.fromJSON = function(value) {
- return Generic_fromJSON(Industry, value.data);
-}
-
-Reviver.constructors.Industry = Industry;
-
-function Corporation(params={}) {
- this.name = params.name ? params.name : "The Corporation";
-
- //A division/business sector is represented by the object:
- this.divisions = [];
-
- //Financial stats
- this.funds = new Decimal(150e9);
- this.revenue = new Decimal(0);
- this.expenses = new Decimal(0);
- this.fundingRound = 0;
- this.public = false; //Publicly traded
- this.totalShares = INITIALSHARES; // Total existing shares
- this.numShares = INITIALSHARES; // Total shares owned by player
- this.shareSalesUntilPriceUpdate = SHARESPERPRICEUPDATE;
- this.shareSaleCooldown = 0; // Game cycles until player can sell shares again
- this.issueNewSharesCooldown = 0; // Game cycles until player can issue shares again
- this.dividendPercentage = 0;
- this.dividendTaxPercentage = 50;
- this.issuedShares = 0;
- this.sharePrice = 0;
- this.storedCycles = 0;
-
- var numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length,
- numUpgrades = Object.keys(CorporationUpgrades).length;
-
- this.unlockUpgrades = Array(numUnlockUpgrades).fill(0);
- this.upgrades = Array(numUpgrades).fill(0);
- this.upgradeMultipliers = Array(numUpgrades).fill(1);
-
- this.state = new CorporationState();
-}
-
-Corporation.prototype.addFunds = function(amt) {
- if(!isFinite(amt)) {
- console.error('Trying to add invalid amount of funds. Report to a developper.');
- return;
- }
- this.funds = this.funds.plus(amt);
-}
-
-Corporation.prototype.getState = function() {
- return this.state.getState();
-}
-
-Corporation.prototype.storeCycles = function(numCycles=1) {
- this.storedCycles += numCycles;
-}
-
-Corporation.prototype.process = function() {
- if (this.storedCycles >= CyclesPerIndustryStateCycle) {
- const state = this.getState();
- const marketCycles = 1;
- const gameCycles = (marketCycles * CyclesPerIndustryStateCycle);
- this.storedCycles -= gameCycles;
-
- this.divisions.forEach((ind) => {
- ind.process(marketCycles, state, this);
- });
-
- // Process cooldowns
- if (this.shareSaleCooldown > 0) {
- this.shareSaleCooldown -= gameCycles;
- }
- if (this.issueNewSharesCooldown > 0) {
- this.issueNewSharesCooldown -= gameCycles;
- }
-
- //At the start of a new cycle, calculate profits from previous cycle
- if (state === "START") {
- this.revenue = new Decimal(0);
- this.expenses = new Decimal(0);
- this.divisions.forEach((ind) => {
- if (ind.lastCycleRevenue === -Infinity || ind.lastCycleRevenue === Infinity) { return; }
- if (ind.lastCycleExpenses === -Infinity || ind.lastCycleExpenses === Infinity) { return; }
- this.revenue = this.revenue.plus(ind.lastCycleRevenue);
- this.expenses = this.expenses.plus(ind.lastCycleExpenses);
- });
- var profit = this.revenue.minus(this.expenses);
- const cycleProfit = profit.times(marketCycles * SecsPerMarketCycle);
- if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
- dialogBoxCreate("There was an error calculating your Corporations funds and they got reset to 0. " +
- "This is a bug. Please report to game developer. " +
- "(Your funds have been set to $150b for the inconvenience)");
- this.funds = new Decimal(150e9);
- }
-
- // Process dividends
- if (this.dividendPercentage > 0 && cycleProfit > 0) {
- // Validate input again, just to be safe
- if (isNaN(this.dividendPercentage) || this.dividendPercentage < 0 || this.dividendPercentage > DividendMaxPercentage) {
- console.error(`Invalid Corporation dividend percentage: ${this.dividendPercentage}`);
- } else {
- const totalDividends = (this.dividendPercentage / 100) * cycleProfit;
- const retainedEarnings = cycleProfit - totalDividends;
- const dividendsPerShare = totalDividends / this.totalShares;
- const profit = this.numShares * dividendsPerShare * (1 - (this.dividendTaxPercentage / 100));
- Player.gainMoney(profit);
- Player.recordMoneySource(profit, "corporation");
- this.addFunds(retainedEarnings);
- }
- } else {
- this.addFunds(cycleProfit);
- }
-
- this.updateSharePrice();
- }
-
- this.state.nextState();
-
- if (routing.isOn(Page.Corporation)) { this.rerender(); }
- }
-}
-
-Corporation.prototype.determineValuation = function() {
- var val, profit = (this.revenue.minus(this.expenses)).toNumber();
- if (this.public) {
- // Account for dividends
- if (this.dividendPercentage > 0) {
- profit *= ((100 - this.dividendPercentage) / 100);
- }
-
- val = this.funds.toNumber() + (profit * 85e3);
- val *= (Math.pow(1.1, this.divisions.length));
- val = Math.max(val, 0);
- } else {
- val = 10e9 + Math.max(this.funds.toNumber(), 0) / 3; //Base valuation
- if (profit > 0) {
- val += (profit * 315e3);
- val *= (Math.pow(1.1, this.divisions.length));
- } else {
- val = 10e9 * Math.pow(1.1, this.divisions.length);
- }
- val -= (val % 1e6); //Round down to nearest millionth
- }
- return val * BitNodeMultipliers.CorporationValuation;
-}
-
-Corporation.prototype.getInvestment = function() {
- var val = this.determineValuation(), percShares;
- let roundMultiplier = 4;
- switch (this.fundingRound) {
- case 0: //Seed
- percShares = 0.10;
- roundMultiplier = 4;
- break;
- case 1: //Series A
- percShares = 0.35;
- roundMultiplier = 3;
- break;
- case 2: //Series B
- percShares = 0.25;
- roundMultiplier = 3;
- break;
- case 3: //Series C
- percShares = 0.20;
- roundMultiplier = 2.5;
- break;
- case 4:
- return;
- }
- var funding = val * percShares * roundMultiplier,
- investShares = Math.floor(INITIALSHARES * percShares),
- yesBtn = yesNoBoxGetYesButton(),
- noBtn = yesNoBoxGetNoButton();
- yesBtn.innerHTML = "Accept";
- noBtn.innerHML = "Reject";
- yesBtn.addEventListener("click", () => {
- ++this.fundingRound;
- this.addFunds(funding);
- this.numShares -= investShares;
- this.rerender();
- return yesNoBoxClose();
- });
- noBtn.addEventListener("click", () => {
- return yesNoBoxClose();
- });
- yesNoBoxCreate("An investment firm has offered you " + numeralWrapper.format(funding, '$0.000a') +
- " in funding in exchange for a " + numeralWrapper.format(percShares*100, "0.000a") +
- "% stake in the company (" + numeralWrapper.format(investShares, '0.000a') + " shares). " +
- "Do you accept or reject this offer? " +
- "Hint: Investment firms will offer more money if your corporation is turning a profit");
-}
-
-Corporation.prototype.goPublic = function() {
- var goPublicPopupId = "cmpy-mgmt-go-public-popup";
- var initialSharePrice = this.determineValuation() / (this.totalShares);
- var txt = createElement("p", {
- innerHTML: "Enter the number of shares you would like to issue " +
- "for your IPO. These shares will be publicly sold " +
- "and you will no longer own them. Your Corporation will receive " +
- numeralWrapper.format(initialSharePrice, '$0.000a') + " per share " +
- "(the IPO money will be deposited directly into your Corporation's funds). " +
- "You have a total of " + numeralWrapper.format(this.numShares, "0.000a") + " of shares that you can issue.",
- });
- var yesBtn;
- var input = createElement("input", {
- type:"number",
- placeholder: "Shares to issue",
- onkeyup:(e) => {
- e.preventDefault();
- if (e.keyCode === KEY.ENTER) {yesBtn.click();}
- },
- });
- var br = createElement("br", {});
- yesBtn = createElement("a", {
- class:"a-link-button",
- innerText:"Go Public",
- clickListener:() => {
- var numShares = Math.round(input.value);
- var initialSharePrice = this.determineValuation() / (this.totalShares);
- if (isNaN(numShares)) {
- dialogBoxCreate("Invalid value for number of issued shares");
- return false;
- }
- if (numShares > this.numShares) {
- dialogBoxCreate("Error: You don't have that many shares to issue!");
- return false;
- }
- this.public = true;
- this.sharePrice = initialSharePrice;
- this.issuedShares = numShares;
- this.numShares -= numShares;
- this.addFunds(numShares * initialSharePrice);
- this.rerender();
- removeElementById(goPublicPopupId);
- dialogBoxCreate(`You took your ${this.name} public and earned ` +
- `${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`);
- return false;
- },
- });
- var noBtn = createElement("a", {
- class:"a-link-button",
- innerText:"Cancel",
- clickListener:() => {
- removeElementById(goPublicPopupId);
- return false;
- },
- });
- createPopup(goPublicPopupId, [txt, br, input, yesBtn, noBtn]);
-}
-
-Corporation.prototype.getTargetSharePrice = function() {
- // Note: totalShares - numShares is not the same as issuedShares because
- // issuedShares does not account for private investors
- return this.determineValuation() / (2 * (this.totalShares - this.numShares) + 1);
-}
-
-Corporation.prototype.updateSharePrice = function() {
- const targetPrice = this.getTargetSharePrice();
- if (this.sharePrice <= targetPrice) {
- this.sharePrice *= (1 + (Math.random() * 0.01));
- } else {
- this.sharePrice *= (1 - (Math.random() * 0.01));
- }
- if (this.sharePrice <= 0.01) {this.sharePrice = 0.01;}
-}
-
-Corporation.prototype.immediatelyUpdateSharePrice = function() {
- this.sharePrice = this.getTargetSharePrice();
-}
-
-// Calculates how much money will be made and what the resulting stock price
-// will be when the player sells his/her shares
-// @return - [Player profit, final stock price, end shareSalesUntilPriceUpdate property]
-Corporation.prototype.calculateShareSale = function(numShares) {
- let sharesTracker = numShares;
- let sharesUntilUpdate = this.shareSalesUntilPriceUpdate;
- let sharePrice = this.sharePrice;
- let sharesSold = 0;
- let profit = 0;
-
- const maxIterations = Math.ceil(numShares / SHARESPERPRICEUPDATE);
- if (isNaN(maxIterations) || maxIterations > 10e6) {
- console.error(`Something went wrong or unexpected when calculating share sale. Maxiterations calculated to be ${maxIterations}`);
- return;
- }
-
- for (let i = 0; i < maxIterations; ++i) {
- if (sharesTracker < sharesUntilUpdate) {
- profit += (sharePrice * sharesTracker);
- sharesUntilUpdate -= sharesTracker;
- break;
- } else {
- profit += (sharePrice * sharesUntilUpdate);
- sharesUntilUpdate = SHARESPERPRICEUPDATE;
- sharesTracker -= sharesUntilUpdate;
- sharesSold += sharesUntilUpdate;
-
- // Calculate what new share price would be
- sharePrice = this.determineValuation() / (2 * (this.totalShares + sharesSold - this.numShares));
- }
- }
-
- return [profit, sharePrice, sharesUntilUpdate];
-}
-
-Corporation.prototype.convertCooldownToString = function(cd) {
- // The cooldown value is based on game cycles. Convert to a simple string
- const seconds = cd / 5;
-
- const SecondsPerMinute = 60;
- const SecondsPerHour = 3600;
-
- if (seconds > SecondsPerHour) {
- return `${Math.floor(seconds / SecondsPerHour)} hour(s)`;
- } else if (seconds > SecondsPerMinute) {
- return `${Math.floor(seconds / SecondsPerMinute)} minute(s)`;
- } else {
- return `${Math.floor(seconds)} second(s)`;
- }
-}
-
-//One time upgrades that unlock new features
-Corporation.prototype.unlock = function(upgrade) {
- const upgN = upgrade[0], price = upgrade[1];
- while (this.unlockUpgrades.length <= upgN) {
- this.unlockUpgrades.push(0);
- }
- if (this.funds.lt(price)) {
- dialogBoxCreate("You don't have enough funds to unlock this!");
- return;
- }
- this.unlockUpgrades[upgN] = 1;
- this.funds = this.funds.minus(price);
-
- // Apply effects for one-time upgrades
- if (upgN === 5) {
- this.dividendTaxPercentage -= 5;
- } else if (upgN === 6) {
- this.dividendTaxPercentage -= 10;
- }
-}
-
-//Levelable upgrades
-Corporation.prototype.upgrade = function(upgrade) {
- var upgN = upgrade[0], basePrice = upgrade[1], priceMult = upgrade[2],
- upgradeAmt = upgrade[3]; //Amount by which the upgrade multiplier gets increased (additive)
- while (this.upgrades.length <= upgN) {this.upgrades.push(0);}
- while (this.upgradeMultipliers.length <= upgN) {this.upgradeMultipliers.push(1);}
- var totalCost = basePrice * Math.pow(priceMult, this.upgrades[upgN]);
- if (this.funds.lt(totalCost)) {
- dialogBoxCreate("You don't have enough funds to purchase this!");
- return;
- }
- ++this.upgrades[upgN];
- this.funds = this.funds.minus(totalCost);
-
- //Increase upgrade multiplier
- this.upgradeMultipliers[upgN] = 1 + (this.upgrades[upgN] * upgradeAmt);
-
- //If storage size is being updated, update values in Warehouse objects
- if (upgN === 1) {
- for (var i = 0; i < this.divisions.length; ++i) {
- var industry = this.divisions[i];
- for (var city in industry.warehouses) {
- if (industry.warehouses.hasOwnProperty(city) && industry.warehouses[city] instanceof Warehouse) {
- industry.warehouses[city].updateSize(this, industry);
- }
- }
- }
- }
-}
-
-Corporation.prototype.getProductionMultiplier = function() {
- var mult = this.upgradeMultipliers[0];
- if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
-}
-
-Corporation.prototype.getStorageMultiplier = function() {
- var mult = this.upgradeMultipliers[1];
- if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
-}
-
-Corporation.prototype.getDreamSenseGain = function() {
- var gain = this.upgradeMultipliers[2] - 1;
- return gain <= 0 ? 0 : gain;
-}
-
-Corporation.prototype.getAdvertisingMultiplier = function() {
- var mult = this.upgradeMultipliers[3];
- if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
-}
-
-Corporation.prototype.getEmployeeCreMultiplier = function() {
- var mult = this.upgradeMultipliers[4];
- if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
-}
-
-Corporation.prototype.getEmployeeChaMultiplier = function() {
- var mult = this.upgradeMultipliers[5];
- if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
-}
-
-Corporation.prototype.getEmployeeIntMultiplier = function() {
- var mult = this.upgradeMultipliers[6];
- if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
-}
-
-Corporation.prototype.getEmployeeEffMultiplier = function() {
- var mult = this.upgradeMultipliers[7];
- if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
-}
-
-Corporation.prototype.getSalesMultiplier = function() {
- var mult = this.upgradeMultipliers[8];
- if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
-}
-
-Corporation.prototype.getScientificResearchMultiplier = function() {
- var mult = this.upgradeMultipliers[9];
- if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
-}
-
-// Adds the Corporation Handbook (Starter Guide) to the player's home computer.
-// This is a lit file that gives introductory info to the player
-// This occurs when the player clicks the "Getting Started Guide" button on the overview panel
-Corporation.prototype.getStarterGuide = function() {
- // Check if player already has Corporation Handbook
- let homeComp = Player.getHomeComputer(),
- hasHandbook = false,
- handbookFn = LiteratureNames.CorporationManagementHandbook;
- for (let i = 0; i < homeComp.messages.length; ++i) {
- if (isString(homeComp.messages[i]) && homeComp.messages[i] === handbookFn) {
- hasHandbook = true;
- break;
- }
- }
-
- if (!hasHandbook) { homeComp.messages.push(handbookFn); }
- showLiterature(handbookFn);
- return false;
-}
-
-let corpRouting;
-let companyManagementDiv;
-Corporation.prototype.createUI = function() {
- companyManagementDiv = createElement("div", {
- id:"cmpy-mgmt-container",
- position:"fixed",
- class:"generic-menupage-container",
- });
- document.getElementById("entire-game-container").appendChild(companyManagementDiv);
-
- corpRouting = new CorporationRouting(this);
-
- this.rerender();
-}
-
-Corporation.prototype.rerender = function() {
- if (companyManagementDiv == null || corpRouting == null) {
- console.warn(`Corporation.rerender() called when companyManagementDiv, corpRouting, or eventHandler is null`);
- return;
- }
- if (!routing.isOn(Page.Corporation)) { return; }
-
- ReactDOM.render( , companyManagementDiv);
-}
-
-Corporation.prototype.clearUI = function() {
- if (companyManagementDiv instanceof HTMLElement) {
- ReactDOM.unmountComponentAtNode(companyManagementDiv);
- removeElementById(companyManagementDiv.id);
- }
-
- companyManagementDiv = null;
- document.getElementById("character-overview-wrapper").style.visibility = "visible";
-}
-
-Corporation.prototype.toJSON = function() {
- return Generic_toJSON("Corporation", this);
-}
-
-Corporation.fromJSON = function(value) {
- return Generic_fromJSON(Corporation, value.data);
-}
-
-Reviver.constructors.Corporation = Corporation;
-
-export {Corporation, Industry, Warehouse};
diff --git a/src/Corporation/Corporation.tsx b/src/Corporation/Corporation.tsx
new file mode 100644
index 000000000..2e506d94a
--- /dev/null
+++ b/src/Corporation/Corporation.tsx
@@ -0,0 +1,464 @@
+import { AllCorporationStates,
+ CorporationState } from "./CorporationState";
+import {
+ CorporationUnlockUpgrade,
+ CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades";
+import { CorporationUpgrade, CorporationUpgrades } from "./data/CorporationUpgrades";
+import { EmployeePositions } from "./EmployeePositions";
+import { Industries,
+ IndustryStartingCosts,
+ IndustryResearchTrees } from "./IndustryData";
+import { IndustryUpgrades } from "./IndustryUpgrades";
+import { Material } from "./Material";
+import { MaterialSizes } from "./MaterialSizes";
+import { Product } from "./Product";
+import { ResearchMap } from "./ResearchMap";
+import { Warehouse } from "./Warehouse";
+import { OfficeSpace } from "./OfficeSpace";
+import { CorporationConstants } from "./data/Constants";
+import { Industry } from "./Industry";
+
+import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
+import { showLiterature } from "../Literature/LiteratureHelpers";
+import { LiteratureNames } from "../Literature/data/LiteratureNames";
+import { CityName } from "../Locations/data/CityNames";
+import { IPlayer } from "../PersonObjects/IPlayer";
+
+import { numeralWrapper } from "../ui/numeralFormat";
+import { Page, routing } from "../ui/navigationTracking";
+
+import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors";
+
+import { dialogBoxCreate } from "../../utils/DialogBox";
+import { Reviver,
+ Generic_toJSON,
+ Generic_fromJSON } from "../../utils/JSONReviver";
+import { appendLineBreaks } from "../../utils/uiHelpers/appendLineBreaks";
+import { createElement } from "../../utils/uiHelpers/createElement";
+import { createPopup } from "../../utils/uiHelpers/createPopup";
+import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton";
+import { formatNumber } from "../../utils/StringHelperFunctions";
+import { getRandomInt } from "../../utils/helpers/getRandomInt";
+import { isString } from "../../utils/helpers/isString";
+import { KEY } from "../../utils/helpers/keyCodes";
+import { removeElement } from "../../utils/uiHelpers/removeElement";
+import { removeElementById } from "../../utils/uiHelpers/removeElementById";
+
+// UI Related Imports
+import React from "react";
+import ReactDOM from "react-dom";
+import { CorporationRoot } from "./ui/Root";
+import { CorporationRouting } from "./ui/Routing";
+
+import Decimal from "decimal.js";
+
+/* Constants */
+export const INITIALSHARES = 1e9; //Total number of shares you have at your company
+export const SHARESPERPRICEUPDATE = 1e6; //When selling large number of shares, price is dynamically updated for every batch of this amount
+
+export const CyclesPerMarketCycle = 50;
+export const CyclesPerIndustryStateCycle = CyclesPerMarketCycle / AllCorporationStates.length;
+export const SecsPerMarketCycle = CyclesPerMarketCycle / 5;
+
+export const DividendMaxPercentage = 50;
+
+interface IParams {
+ name?: string;
+}
+
+let corpRouting: any;
+let companyManagementDiv: any;
+
+export class Corporation {
+ name = "The Corporation";
+
+ //A division/business sector is represented by the object:
+ divisions: Industry[] = [];
+
+ //Financial stats
+ funds = new Decimal(150e9);
+ revenue = new Decimal(0);
+ expenses = new Decimal(0);
+ fundingRound = 0;
+ public = false; //Publicly traded
+ totalShares = CorporationConstants.INITIALSHARES; // Total existing shares
+ numShares = CorporationConstants.INITIALSHARES; // Total shares owned by player
+ shareSalesUntilPriceUpdate = CorporationConstants.SHARESPERPRICEUPDATE;
+ shareSaleCooldown = 0; // Game cycles until player can sell shares again
+ issueNewSharesCooldown = 0; // Game cycles until player can issue shares again
+ dividendPercentage = 0;
+ dividendTaxPercentage = 50;
+ issuedShares = 0;
+ sharePrice = 0;
+ storedCycles = 0;
+
+ unlockUpgrades: number[];
+ upgrades: number[];
+ upgradeMultipliers: number[];
+
+ state = new CorporationState();
+
+ constructor(params: IParams = {}) {
+ this.name = params.name ? params.name : "The Corporation";
+ const numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length;
+ const numUpgrades = Object.keys(CorporationUpgrades).length;
+ this.unlockUpgrades = Array(numUnlockUpgrades).fill(0);
+ this.upgrades = Array(numUpgrades).fill(0);
+ this.upgradeMultipliers = Array(numUpgrades).fill(1);
+ }
+
+ addFunds(amt: number) {
+ if(!isFinite(amt)) {
+ console.error('Trying to add invalid amount of funds. Report to a developper.');
+ return;
+ }
+ this.funds = this.funds.plus(amt);
+ }
+
+ getState(): string {
+ return this.state.getState();
+ }
+
+ storeCycles(numCycles=1): void {
+ this.storedCycles += numCycles;
+ }
+
+ process(player: IPlayer) {
+ if (this.storedCycles >= CyclesPerIndustryStateCycle) {
+ const state = this.getState();
+ const marketCycles = 1;
+ const gameCycles = (marketCycles * CyclesPerIndustryStateCycle);
+ this.storedCycles -= gameCycles;
+
+ this.divisions.forEach((ind) => {
+ ind.process(marketCycles, state, this);
+ });
+
+ // Process cooldowns
+ if (this.shareSaleCooldown > 0) {
+ this.shareSaleCooldown -= gameCycles;
+ }
+ if (this.issueNewSharesCooldown > 0) {
+ this.issueNewSharesCooldown -= gameCycles;
+ }
+
+ //At the start of a new cycle, calculate profits from previous cycle
+ if (state === "START") {
+ this.revenue = new Decimal(0);
+ this.expenses = new Decimal(0);
+ this.divisions.forEach((ind) => {
+ if (ind.lastCycleRevenue === -Infinity || ind.lastCycleRevenue === Infinity) { return; }
+ if (ind.lastCycleExpenses === -Infinity || ind.lastCycleExpenses === Infinity) { return; }
+ this.revenue = this.revenue.plus(ind.lastCycleRevenue);
+ this.expenses = this.expenses.plus(ind.lastCycleExpenses);
+ });
+ var profit = this.revenue.minus(this.expenses);
+ const cycleProfit = profit.times(marketCycles * SecsPerMarketCycle);
+ if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
+ dialogBoxCreate("There was an error calculating your Corporations funds and they got reset to 0. " +
+ "This is a bug. Please report to game developer. " +
+ "(Your funds have been set to $150b for the inconvenience)");
+ this.funds = new Decimal(150e9);
+ }
+
+ // Process dividends
+ if (this.dividendPercentage > 0 && cycleProfit > 0) {
+ // Validate input again, just to be safe
+ if (isNaN(this.dividendPercentage) || this.dividendPercentage < 0 || this.dividendPercentage > DividendMaxPercentage) {
+ console.error(`Invalid Corporation dividend percentage: ${this.dividendPercentage}`);
+ } else {
+ const totalDividends = (this.dividendPercentage / 100) * cycleProfit;
+ const retainedEarnings = cycleProfit - totalDividends;
+ const dividendsPerShare = totalDividends / this.totalShares;
+ const profit = this.numShares * dividendsPerShare * (1 - (this.dividendTaxPercentage / 100));
+ player.gainMoney(profit);
+ player.recordMoneySource(profit, "corporation");
+ this.addFunds(retainedEarnings);
+ }
+ } else {
+ this.addFunds(cycleProfit);
+ }
+
+ this.updateSharePrice();
+ }
+
+ this.state.nextState();
+
+ if (routing.isOn(Page.Corporation)) this.rerender(player);
+ }
+ }
+
+ determineValuation() {
+ var val, profit = (this.revenue.minus(this.expenses)).toNumber();
+ if (this.public) {
+ // Account for dividends
+ if (this.dividendPercentage > 0) {
+ profit *= ((100 - this.dividendPercentage) / 100);
+ }
+
+ val = this.funds.toNumber() + (profit * 85e3);
+ val *= (Math.pow(1.1, this.divisions.length));
+ val = Math.max(val, 0);
+ } else {
+ val = 10e9 + Math.max(this.funds.toNumber(), 0) / 3; //Base valuation
+ if (profit > 0) {
+ val += (profit * 315e3);
+ val *= (Math.pow(1.1, this.divisions.length));
+ } else {
+ val = 10e9 * Math.pow(1.1, this.divisions.length);
+ }
+ val -= (val % 1e6); //Round down to nearest millionth
+ }
+ return val * BitNodeMultipliers.CorporationValuation;
+ }
+
+ getTargetSharePrice() {
+ // Note: totalShares - numShares is not the same as issuedShares because
+ // issuedShares does not account for private investors
+ return this.determineValuation() / (2 * (this.totalShares - this.numShares) + 1);
+ }
+
+ updateSharePrice() {
+ const targetPrice = this.getTargetSharePrice();
+ if (this.sharePrice <= targetPrice) {
+ this.sharePrice *= (1 + (Math.random() * 0.01));
+ } else {
+ this.sharePrice *= (1 - (Math.random() * 0.01));
+ }
+ if (this.sharePrice <= 0.01) {this.sharePrice = 0.01;}
+ }
+
+ immediatelyUpdateSharePrice() {
+ this.sharePrice = this.getTargetSharePrice();
+ }
+
+ // Calculates how much money will be made and what the resulting stock price
+ // will be when the player sells his/her shares
+ // @return - [Player profit, final stock price, end shareSalesUntilPriceUpdate property]
+ calculateShareSale(numShares: number): [number, number, number] {
+ let sharesTracker = numShares;
+ let sharesUntilUpdate = this.shareSalesUntilPriceUpdate;
+ let sharePrice = this.sharePrice;
+ let sharesSold = 0;
+ let profit = 0;
+
+ const maxIterations = Math.ceil(numShares / SHARESPERPRICEUPDATE);
+ if (isNaN(maxIterations) || maxIterations > 10e6) {
+ console.error(`Something went wrong or unexpected when calculating share sale. Maxiterations calculated to be ${maxIterations}`);
+ return [0, 0, 0];
+ }
+
+ for (let i = 0; i < maxIterations; ++i) {
+ if (sharesTracker < sharesUntilUpdate) {
+ profit += (sharePrice * sharesTracker);
+ sharesUntilUpdate -= sharesTracker;
+ break;
+ } else {
+ profit += (sharePrice * sharesUntilUpdate);
+ sharesUntilUpdate = SHARESPERPRICEUPDATE;
+ sharesTracker -= sharesUntilUpdate;
+ sharesSold += sharesUntilUpdate;
+
+ // Calculate what new share price would be
+ sharePrice = this.determineValuation() / (2 * (this.totalShares + sharesSold - this.numShares));
+ }
+ }
+
+ return [profit, sharePrice, sharesUntilUpdate];
+ }
+
+ convertCooldownToString(cd: number) {
+ // The cooldown value is based on game cycles. Convert to a simple string
+ const seconds = cd / 5;
+
+ const SecondsPerMinute = 60;
+ const SecondsPerHour = 3600;
+
+ if (seconds > SecondsPerHour) {
+ return `${Math.floor(seconds / SecondsPerHour)} hour(s)`;
+ } else if (seconds > SecondsPerMinute) {
+ return `${Math.floor(seconds / SecondsPerMinute)} minute(s)`;
+ } else {
+ return `${Math.floor(seconds)} second(s)`;
+ }
+ }
+
+ //One time upgrades that unlock new features
+ unlock(upgrade: CorporationUnlockUpgrade): void {
+ const upgN = upgrade[0], price = upgrade[1];
+ while (this.unlockUpgrades.length <= upgN) {
+ this.unlockUpgrades.push(0);
+ }
+ if (this.funds.lt(price)) {
+ dialogBoxCreate("You don't have enough funds to unlock this!");
+ return;
+ }
+ this.unlockUpgrades[upgN] = 1;
+ this.funds = this.funds.minus(price);
+
+ // Apply effects for one-time upgrades
+ if (upgN === 5) {
+ this.dividendTaxPercentage -= 5;
+ } else if (upgN === 6) {
+ this.dividendTaxPercentage -= 10;
+ }
+ }
+
+ //Levelable upgrades
+ upgrade(upgrade: CorporationUpgrade): void {
+ var upgN = upgrade[0], basePrice = upgrade[1], priceMult = upgrade[2],
+ upgradeAmt = upgrade[3]; //Amount by which the upgrade multiplier gets increased (additive)
+ while (this.upgrades.length <= upgN) {this.upgrades.push(0);}
+ while (this.upgradeMultipliers.length <= upgN) {this.upgradeMultipliers.push(1);}
+ var totalCost = basePrice * Math.pow(priceMult, this.upgrades[upgN]);
+ if (this.funds.lt(totalCost)) {
+ dialogBoxCreate("You don't have enough funds to purchase this!");
+ return;
+ }
+ ++this.upgrades[upgN];
+ this.funds = this.funds.minus(totalCost);
+
+ //Increase upgrade multiplier
+ this.upgradeMultipliers[upgN] = 1 + (this.upgrades[upgN] * upgradeAmt);
+
+ //If storage size is being updated, update values in Warehouse objects
+ if (upgN === 1) {
+ for (var i = 0; i < this.divisions.length; ++i) {
+ var industry = this.divisions[i];
+ for (var city in industry.warehouses) {
+ if (industry.warehouses.hasOwnProperty(city) && industry.warehouses[city] instanceof Warehouse) {
+ industry.warehouses[city].updateSize(this, industry);
+ }
+ }
+ }
+ }
+ }
+
+ getProductionMultiplier() {
+ var mult = this.upgradeMultipliers[0];
+ if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
+ }
+
+ getStorageMultiplier() {
+ var mult = this.upgradeMultipliers[1];
+ if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
+ }
+
+ getDreamSenseGain() {
+ var gain = this.upgradeMultipliers[2] - 1;
+ return gain <= 0 ? 0 : gain;
+ }
+
+ getAdvertisingMultiplier() {
+ var mult = this.upgradeMultipliers[3];
+ if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
+ }
+
+ getEmployeeCreMultiplier() {
+ var mult = this.upgradeMultipliers[4];
+ if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
+ }
+
+ getEmployeeChaMultiplier() {
+ var mult = this.upgradeMultipliers[5];
+ if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
+ }
+
+ getEmployeeIntMultiplier() {
+ var mult = this.upgradeMultipliers[6];
+ if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
+ }
+
+ getEmployeeEffMultiplier() {
+ var mult = this.upgradeMultipliers[7];
+ if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
+ }
+
+ getSalesMultiplier() {
+ var mult = this.upgradeMultipliers[8];
+ if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
+ }
+
+ getScientificResearchMultiplier() {
+ var mult = this.upgradeMultipliers[9];
+ if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
+ }
+
+ // Adds the Corporation Handbook (Starter Guide) to the player's home computer.
+ // This is a lit file that gives introductory info to the player
+ // This occurs when the player clicks the "Getting Started Guide" button on the overview panel
+ getStarterGuide(player: IPlayer) {
+ // Check if player already has Corporation Handbook
+ let homeComp = player.getHomeComputer(),
+ hasHandbook = false,
+ handbookFn = LiteratureNames.CorporationManagementHandbook;
+ for (let i = 0; i < homeComp.messages.length; ++i) {
+ if (isString(homeComp.messages[i]) && homeComp.messages[i] === handbookFn) {
+ hasHandbook = true;
+ break;
+ }
+ }
+
+ if (!hasHandbook) { homeComp.messages.push(handbookFn); }
+ showLiterature(handbookFn);
+ return false;
+ }
+
+ createUI(player: IPlayer) {
+ companyManagementDiv = createElement("div", {
+ id:"cmpy-mgmt-container",
+ position:"fixed",
+ class:"generic-menupage-container",
+ });
+ const game = document.getElementById("entire-game-container");
+ if(game)
+ game.appendChild(companyManagementDiv);
+
+ corpRouting = new CorporationRouting(this);
+
+ this.rerender(player);
+ }
+
+ rerender(player: IPlayer) {
+ if (companyManagementDiv == null || corpRouting == null) {
+ console.warn(`Corporation.rerender() called when companyManagementDiv, corpRouting, or eventHandler is null`);
+ return;
+ }
+ if (!routing.isOn(Page.Corporation)) return;
+
+ ReactDOM.render( , companyManagementDiv);
+ }
+
+ clearUI() {
+ if (companyManagementDiv instanceof HTMLElement) {
+ ReactDOM.unmountComponentAtNode(companyManagementDiv);
+ removeElementById(companyManagementDiv.id);
+ }
+
+ companyManagementDiv = null;
+ const character = document.getElementById("character-overview-wrapper");
+ if(character)
+ character.style.visibility = "visible";
+ }
+
+ /**
+ * Serialize the current object to a JSON save state.
+ */
+ toJSON(): any {
+ return Generic_toJSON("Corporation", this);
+ }
+
+ /**
+ * Initiatizes a Corporation object from a JSON save state.
+ */
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+ static fromJSON(value: any): Corporation {
+ return Generic_fromJSON(Corporation, value.data);
+ }
+}
+
+Reviver.constructors.Corporation = Corporation;
diff --git a/src/Corporation/Industry.ts b/src/Corporation/Industry.ts
new file mode 100644
index 000000000..5a9bc8cfd
--- /dev/null
+++ b/src/Corporation/Industry.ts
@@ -0,0 +1,1328 @@
+import {
+ Reviver,
+ Generic_toJSON,
+ Generic_fromJSON,
+} from "../../utils/JSONReviver";
+import { CityName } from "../Locations/data/CityNames";
+import Decimal from 'decimal.js';
+import { Industries,
+ IndustryStartingCosts,
+ IndustryResearchTrees } from "./IndustryData";
+import { CorporationConstants } from "./data/Constants";
+import { EmployeePositions } from "./EmployeePositions";
+import { Material } from "./Material";
+import { getRandomInt } from "../../utils/helpers/getRandomInt";
+import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors";
+import { OfficeSpace } from "./OfficeSpace";
+import { Product } from "./Product";
+import { dialogBoxCreate } from "../../utils/DialogBox";
+import { isString } from "../../utils/helpers/isString";
+import { MaterialSizes } from "./MaterialSizes";
+import { Warehouse } from "./Warehouse";
+import {
+ IndustryUpgrade,
+ IndustryUpgrades } from "./IndustryUpgrades";
+import { formatNumber } from "../../utils/StringHelperFunctions";
+
+interface IParams {
+ name?: string;
+ corp?: any;
+ type?: string;
+}
+
+export class Industry {
+ name = "";
+ type = Industries.Agriculture;
+ sciResearch = new Material({name: "Scientific Research"});
+ researched: any = {};
+ reqMats: any = {};
+
+ //An array of the name of materials being produced
+ prodMats: string[] = [];
+
+ products: any = {};
+ makesProducts = false;
+
+ awareness = 0;
+ popularity = 0; //Should always be less than awareness
+ startingCost = 0;
+
+ /* The following are factors for how much production/other things are increased by
+ different factors. The production increase always has diminishing returns,
+ and they are all reprsented by exponentials of < 1 (e.g x ^ 0.5, x ^ 0.8)
+ The number for these represent the exponential. A lower number means more
+ diminishing returns */
+ reFac = 0; //Real estate Factor
+ sciFac = 0; //Scientific Research Factor, affects quality
+ hwFac = 0; //Hardware factor
+ robFac = 0; //Robotics Factor
+ aiFac = 0; //AI Cores factor;
+ advFac = 0; //Advertising factor, affects sales
+
+ prodMult = 0; //Production multiplier
+
+ //Financials
+ lastCycleRevenue: any;
+ lastCycleExpenses: any;
+ thisCycleRevenue: any;
+ thisCycleExpenses: any;
+
+ //Upgrades
+ upgrades: number[] = [];
+
+ state = "START";
+ newInd = true;
+
+ //Maps locations to warehouses. 0 if no warehouse at that location
+ warehouses: any;
+
+ //Maps locations to offices. 0 if no office at that location
+ offices: any = {
+ [CityName.Aevum]: 0,
+ [CityName.Chongqing]: 0,
+ [CityName.Sector12]: new OfficeSpace({
+ loc: CityName.Sector12,
+ size: CorporationConstants.OfficeInitialSize,
+ }),
+ [CityName.NewTokyo]: 0,
+ [CityName.Ishima]: 0,
+ [CityName.Volhaven]: 0,
+ };
+
+ constructor(params: IParams = {}) {
+ this.name = params.name ? params.name : '';
+ this.type = params.type ? params.type : Industries.Agriculture;
+
+ //Financials
+ this.lastCycleRevenue = new Decimal(0);
+ this.lastCycleExpenses = new Decimal(0);
+ this.thisCycleRevenue = new Decimal(0);
+ this.thisCycleExpenses = new Decimal(0);
+
+ //Upgrades
+ const numUpgrades = Object.keys(IndustryUpgrades).length;
+ this.upgrades = Array(numUpgrades).fill(0);
+
+ this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location
+ [CityName.Aevum]: 0,
+ [CityName.Chongqing]: 0,
+ [CityName.Sector12]: new Warehouse({
+ corp: params.corp,
+ industry: this,
+ loc: CityName.Sector12,
+ size: CorporationConstants.WarehouseInitialSize,
+ }),
+ [CityName.NewTokyo]: 0,
+ [CityName.Ishima]: 0,
+ [CityName.Volhaven]: 0,
+ };
+
+ this.init();
+ }
+
+ init() {
+ //Set the unique properties of an industry (how much its affected by real estate/scientific research, etc.)
+ const startingCost = IndustryStartingCosts[this.type];
+ if(startingCost === undefined)
+ throw new Error(`Invalid industry: "${this.type}"`);
+ this.startingCost = startingCost;
+ switch (this.type) {
+ case Industries.Energy:
+ this.reFac = 0.65;
+ this.sciFac = 0.7;
+ this.robFac = 0.05;
+ this.aiFac = 0.3;
+ this.advFac = 0.08;
+ this.reqMats = {
+ "Hardware": 0.1,
+ "Metal": 0.2,
+ };
+ this.prodMats = ["Energy"];
+ break;
+ case Industries.Utilities:
+ case "Utilities":
+ this.reFac = 0.5;
+ this.sciFac = 0.6;
+ this.robFac = 0.4;
+ this.aiFac = 0.4;
+ this.advFac = 0.08;
+ this.reqMats = {
+ "Hardware": 0.1,
+ "Metal": 0.1,
+ }
+ this.prodMats = ["Water"];
+ break;
+ case Industries.Agriculture:
+ this.reFac = 0.72;
+ this.sciFac = 0.5;
+ this.hwFac = 0.2;
+ this.robFac = 0.3;
+ this.aiFac = 0.3;
+ this.advFac = 0.04;
+ this.reqMats = {
+ "Water": 0.5,
+ "Energy": 0.5,
+ }
+ this.prodMats = ["Plants", "Food"];
+ break;
+ case Industries.Fishing:
+ this.reFac = 0.15;
+ this.sciFac = 0.35;
+ this.hwFac = 0.35;
+ this.robFac = 0.5;
+ this.aiFac = 0.2;
+ this.advFac = 0.08;
+ this.reqMats = {
+ "Energy": 0.5,
+ }
+ this.prodMats = ["Food"];
+ break;
+ case Industries.Mining:
+ this.reFac = 0.3;
+ this.sciFac = 0.26;
+ this.hwFac = 0.4;
+ this.robFac = 0.45;
+ this.aiFac = 0.45;
+ this.advFac = 0.06;
+ this.reqMats = {
+ "Energy": 0.8,
+ }
+ this.prodMats = ["Metal"];
+ break;
+ case Industries.Food:
+ //reFac is unique for this bc it diminishes greatly per city. Handle this separately in code?
+ this.sciFac = 0.12;
+ this.hwFac = 0.15;
+ this.robFac = 0.3;
+ this.aiFac = 0.25;
+ this.advFac = 0.25;
+ this.reFac = 0.05;
+ this.reqMats = {
+ "Food": 0.5,
+ "Water": 0.5,
+ "Energy": 0.2,
+ }
+ this.makesProducts = true;
+ break;
+ case Industries.Tobacco:
+ this.reFac = 0.15;
+ this.sciFac = 0.75;
+ this.hwFac = 0.15;
+ this.robFac = 0.2;
+ this.aiFac = 0.15;
+ this.advFac = 0.2;
+ this.reqMats = {
+ "Plants": 1,
+ "Water": 0.2,
+ }
+ this.makesProducts = true;
+ break;
+ case Industries.Chemical:
+ this.reFac = 0.25;
+ this.sciFac = 0.75;
+ this.hwFac = 0.2;
+ this.robFac = 0.25;
+ this.aiFac = 0.2;
+ this.advFac = 0.07;
+ this.reqMats = {
+ "Plants": 1,
+ "Energy": 0.5,
+ "Water": 0.5,
+ }
+ this.prodMats = ["Chemicals"];
+ break;
+ case Industries.Pharmaceutical:
+ this.reFac = 0.05;
+ this.sciFac = 0.8;
+ this.hwFac = 0.15;
+ this.robFac = 0.25;
+ this.aiFac = 0.2;
+ this.advFac = 0.16;
+ this.reqMats = {
+ "Chemicals": 2,
+ "Energy": 1,
+ "Water": 0.5,
+ }
+ this.prodMats = ["Drugs"];
+ this.makesProducts = true;
+ break;
+ case Industries.Computer:
+ case "Computer":
+ this.reFac = 0.2;
+ this.sciFac = 0.62;
+ this.robFac = 0.36;
+ this.aiFac = 0.19;
+ this.advFac = 0.17;
+ this.reqMats = {
+ "Metal": 2,
+ "Energy": 1,
+ }
+ this.prodMats = ["Hardware"];
+ this.makesProducts = true;
+ break;
+ case Industries.Robotics:
+ this.reFac = 0.32;
+ this.sciFac = 0.65;
+ this.aiFac = 0.36;
+ this.advFac = 0.18;
+ this.hwFac = 0.19;
+ this.reqMats = {
+ "Hardware": 5,
+ "Energy": 3,
+ }
+ this.prodMats = ["Robots"];
+ this.makesProducts = true;
+ break;
+ case Industries.Software:
+ this.sciFac = 0.62;
+ this.advFac = 0.16;
+ this.hwFac = 0.25;
+ this.reFac = 0.15;
+ this.aiFac = 0.18;
+ this.robFac = 0.05;
+ this.reqMats = {
+ "Hardware": 0.5,
+ "Energy": 0.5,
+ }
+ this.prodMats = ["AICores"];
+ this.makesProducts = true;
+ break;
+ case Industries.Healthcare:
+ this.reFac = 0.1;
+ this.sciFac = 0.75;
+ this.advFac = 0.11;
+ this.hwFac = 0.1;
+ this.robFac = 0.1;
+ this.aiFac = 0.1;
+ this.reqMats = {
+ "Robots": 10,
+ "AICores": 5,
+ "Energy": 5,
+ "Water": 5,
+ }
+ this.makesProducts = true;
+ break;
+ case Industries.RealEstate:
+ this.robFac = 0.6;
+ this.aiFac = 0.6;
+ this.advFac = 0.25;
+ this.sciFac = 0.05;
+ this.hwFac = 0.05;
+ this.reqMats = {
+ "Metal": 5,
+ "Energy": 5,
+ "Water": 2,
+ "Hardware": 4,
+ }
+ this.prodMats = ["RealEstate"];
+ this.makesProducts = true;
+ break;
+ default:
+ console.error(`Invalid Industry Type passed into Industry.init(): ${this.type}`);
+ return;
+ }
+ }
+
+ getProductDescriptionText() {
+ if (!this.makesProducts) {return;}
+ switch (this.type) {
+ case Industries.Food:
+ return "create and manage restaurants";
+ case Industries.Tobacco:
+ return "create tobacco and tobacco-related products";
+ case Industries.Pharmaceutical:
+ return "develop new pharmaceutical drugs";
+ case Industries.Computer:
+ case "Computer":
+ return "create new computer hardware and networking infrastructures";
+ case Industries.Robotics:
+ return "build specialized robots and robot-related products";
+ case Industries.Software:
+ return "develop computer software";
+ case Industries.Healthcare:
+ return "build and manage hospitals";
+ case Industries.RealEstate:
+ return "develop and manage real estate properties";
+ default:
+ console.error("Invalid industry type in Industry.getProductDescriptionText");
+ return "";
+ }
+ }
+
+ getMaximumNumberProducts() {
+ if (!this.makesProducts) return 0;
+
+ // Calculate additional number of allowed Products from Research/Upgrades
+ let additional = 0;
+ if (this.hasResearch("uPgrade: Capacity.I")) ++additional;
+ if (this.hasResearch("uPgrade: Capacity.II")) ++additional;
+
+ return CorporationConstants.BaseMaxProducts + additional;
+ }
+
+ hasMaximumNumberProducts() {
+ return (Object.keys(this.products).length >= this.getMaximumNumberProducts());
+ }
+
+ //Calculates the values that factor into the production and properties of
+ //materials/products (such as quality, etc.)
+ calculateProductionFactors() {
+ var multSum = 0;
+ for (var i = 0; i < CorporationConstants.Cities.length; ++i) {
+ var city = CorporationConstants.Cities[i];
+ var warehouse = this.warehouses[city];
+ if (!(warehouse instanceof Warehouse)) {
+ continue;
+ }
+
+ var materials = warehouse.materials;
+
+ var cityMult = Math.pow(0.002 * materials.RealEstate.qty+1, this.reFac) *
+ Math.pow(0.002 * materials.Hardware.qty+1, this.hwFac) *
+ Math.pow(0.002 * materials.Robots.qty+1, this.robFac) *
+ Math.pow(0.002 * materials.AICores.qty+1, this.aiFac);
+ multSum += Math.pow(cityMult, 0.73);
+ }
+
+ multSum < 1 ? this.prodMult = 1 : this.prodMult = multSum;
+ }
+
+ updateWarehouseSizeUsed(warehouse: Warehouse): void {
+ warehouse.updateMaterialSizeUsed();
+
+ for (var prodName in this.products) {
+ if (this.products.hasOwnProperty(prodName)) {
+ var prod = this.products[prodName];
+ warehouse.sizeUsed += (prod.data[warehouse.loc][0] * prod.siz);
+ if (prod.data[warehouse.loc][0] > 0) {
+ warehouse.breakdown += (prodName + ": " + formatNumber(prod.data[warehouse.loc][0] * prod.siz, 0) + " ");
+ }
+ }
+ }
+ }
+
+ process(marketCycles=1, state: string, company: any) {
+ this.state = state;
+
+ //At the start of a cycle, store and reset revenue/expenses
+ //Then calculate salaries and processs the markets
+ if (state === "START") {
+ if (isNaN(this.thisCycleRevenue) || isNaN(this.thisCycleExpenses)) {
+ console.error("NaN in Corporation's computed revenue/expenses");
+ dialogBoxCreate("Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer");
+ this.thisCycleRevenue = new Decimal(0);
+ this.thisCycleExpenses = new Decimal(0);
+ }
+ this.lastCycleRevenue = this.thisCycleRevenue.dividedBy(marketCycles * CorporationConstants.SecsPerMarketCycle);
+ this.lastCycleExpenses = this.thisCycleExpenses.dividedBy(marketCycles * CorporationConstants.SecsPerMarketCycle);
+ this.thisCycleRevenue = new Decimal(0);
+ this.thisCycleExpenses = new Decimal(0);
+
+ // Once you start making revenue, the player should no longer be
+ // considered new, and therefore no longer needs the 'tutorial' UI elements
+ if (this.lastCycleRevenue.gt(0)) {this.newInd = false;}
+
+ // Process offices (and the employees in them)
+ var employeeSalary = 0;
+ for (var officeLoc in this.offices) {
+ if (this.offices[officeLoc] instanceof OfficeSpace) {
+ employeeSalary += this.offices[officeLoc].process(marketCycles, {industry:this, corporation:company});
+ }
+ }
+ this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary);
+
+ // Process change in demand/competition of materials/products
+ this.processMaterialMarket(marketCycles);
+ this.processProductMarket(marketCycles);
+
+ // Process loss of popularity
+ this.popularity -= (marketCycles * .0001);
+ this.popularity = Math.max(0, this.popularity);
+
+ // Process Dreamsense gains
+ var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4;
+ if (popularityGain > 0) {
+ this.popularity += (popularityGain * marketCycles);
+ this.awareness += (awarenessGain * marketCycles);
+ }
+
+ return;
+ }
+
+ // Process production, purchase, and import/export of materials
+ let res = this.processMaterials(marketCycles, company);
+ if (Array.isArray(res)) {
+ this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
+ this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
+ }
+
+ // Process creation, production & sale of products
+ res = this.processProducts(marketCycles, company);
+ if (Array.isArray(res)) {
+ this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]);
+ this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]);
+ }
+ }
+
+ // Process change in demand and competition for this industry's materials
+ processMaterialMarket(marketCycles=1) {
+ //References to prodMats and reqMats
+ var reqMats = this.reqMats, prodMats = this.prodMats;
+
+ //Only 'process the market' for materials that this industry deals with
+ for (var i = 0; i < CorporationConstants.Cities.length; ++i) {
+ //If this industry has a warehouse in this city, process the market
+ //for every material this industry requires or produces
+ if (this.warehouses[CorporationConstants.Cities[i]] instanceof Warehouse) {
+ var wh = this.warehouses[CorporationConstants.Cities[i]];
+ for (var name in reqMats) {
+ if (reqMats.hasOwnProperty(name)) {
+ wh.materials[name].processMarket();
+ }
+ }
+
+ //Produced materials are stored in an array
+ for (var foo = 0; foo < prodMats.length; ++foo) {
+ wh.materials[prodMats[foo]].processMarket();
+ }
+
+ //Process these twice because these boost production
+ wh.materials["Hardware"].processMarket();
+ wh.materials["Robots"].processMarket();
+ wh.materials["AICores"].processMarket();
+ wh.materials["RealEstate"].processMarket();
+ }
+ }
+ }
+
+ // Process change in demand and competition for this industry's products
+ processProductMarket(marketCycles=1) {
+ // Demand gradually decreases, and competition gradually increases
+ for (const name in this.products) {
+ if (this.products.hasOwnProperty(name)) {
+ const product = this.products[name];
+ let change = getRandomInt(0, 3) * 0.0004;
+ if (change === 0) { continue; }
+
+ if (this.type === Industries.Pharmaceutical || this.type === Industries.Software ||
+ this.type === Industries.Robotics) {
+ change *= 3;
+ }
+ change *= marketCycles;
+ product.dmd -= change;
+ product.cmp += change;
+ product.cmp = Math.min(product.cmp, 99.99);
+ product.dmd = Math.max(product.dmd, 0.001);
+ }
+ }
+ }
+
+ //Process production, purchase, and import/export of materials
+ processMaterials(marketCycles=1, company: any): [number, number] {
+ var revenue = 0, expenses = 0;
+ this.calculateProductionFactors();
+
+ //At the start of the export state, set the imports of everything to 0
+ if (this.state === "EXPORT") {
+ for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
+ var city = CorporationConstants.Cities[i], office = this.offices[city];
+ if (!(this.warehouses[city] instanceof Warehouse)) {
+ continue;
+ }
+ var warehouse = this.warehouses[city];
+ for (var matName in warehouse.materials) {
+ if (warehouse.materials.hasOwnProperty(matName)) {
+ var mat = warehouse.materials[matName];
+ mat.imp = 0;
+ }
+ }
+ }
+ }
+
+ for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
+ var city = CorporationConstants.Cities[i], office = this.offices[city];
+
+ if (this.warehouses[city] instanceof Warehouse) {
+ var warehouse = this.warehouses[city];
+
+ switch(this.state) {
+
+ case "PURCHASE":
+ /* Process purchase of materials */
+ for (var matName in warehouse.materials) {
+ if (warehouse.materials.hasOwnProperty(matName)) {
+ (function(matName, ind) {
+ var mat = warehouse.materials[matName];
+ var buyAmt, maxAmt;
+ if (warehouse.smartSupplyEnabled && Object.keys(ind.reqMats).includes(matName)) {
+ //Smart supply tracker is stored as per second rate
+ mat.buy = ind.reqMats[matName] * warehouse.smartSupplyStore;
+ buyAmt = mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles;
+ } else {
+ buyAmt = (mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles);
+ }
+
+ if (matName == "RealEstate") {
+ maxAmt = buyAmt;
+ } else {
+ maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]);
+ }
+ buyAmt = Math.min(buyAmt, maxAmt);
+ if (buyAmt > 0) {
+ mat.qty += buyAmt;
+ expenses += (buyAmt * mat.bCost);
+ }
+ })(matName, this);
+ this.updateWarehouseSizeUsed(warehouse);
+ }
+ } //End process purchase of materials
+ break;
+
+ case "PRODUCTION":
+ warehouse.smartSupplyStore = 0; //Reset smart supply amount
+
+ /* Process production of materials */
+ if (this.prodMats.length > 0) {
+ var mat = warehouse.materials[this.prodMats[0]];
+ //Calculate the maximum production of this material based
+ //on the office's productivity
+ var maxProd = this.getOfficeProductivity(office)
+ * this.prodMult // Multiplier from materials
+ * company.getProductionMultiplier()
+ * this.getProductionMultiplier(); // Multiplier from Research
+ let prod;
+
+ if (mat.prdman[0]) {
+ //Production is manually limited
+ prod = Math.min(maxProd, mat.prdman[1]);
+ } else {
+ prod = maxProd;
+ }
+ prod *= (CorporationConstants.SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle
+
+ // Calculate net change in warehouse storage making the produced materials will cost
+ var totalMatSize = 0;
+ for (let tmp = 0; tmp < this.prodMats.length; ++tmp) {
+ totalMatSize += (MaterialSizes[this.prodMats[tmp]]);
+ }
+ for (const reqMatName in this.reqMats) {
+ var normQty = this.reqMats[reqMatName];
+ totalMatSize -= (MaterialSizes[reqMatName] * normQty);
+ }
+ // If not enough space in warehouse, limit the amount of produced materials
+ if (totalMatSize > 0) {
+ var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize);
+ prod = Math.min(maxAmt, prod);
+ }
+
+ if (prod < 0) {prod = 0;}
+
+ // Keep track of production for smart supply (/s)
+ warehouse.smartSupplyStore += (prod / (CorporationConstants.SecsPerMarketCycle * marketCycles));
+
+ // Make sure we have enough resource to make our materials
+ var producableFrac = 1;
+ for (var reqMatName in this.reqMats) {
+ if (this.reqMats.hasOwnProperty(reqMatName)) {
+ var req = this.reqMats[reqMatName] * prod;
+ if (warehouse.materials[reqMatName].qty < req) {
+ producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req);
+ }
+ }
+ }
+ if (producableFrac <= 0) {producableFrac = 0; prod = 0;}
+
+ // Make our materials if they are producable
+ if (producableFrac > 0 && prod > 0) {
+ for (const reqMatName in this.reqMats) {
+ var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac);
+ warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
+ warehouse.materials[reqMatName].prd = 0;
+ warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (CorporationConstants.SecsPerMarketCycle * marketCycles);
+ }
+ for (let j = 0; j < this.prodMats.length; ++j) {
+ warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac);
+ warehouse.materials[this.prodMats[j]].qlt =
+ (office.employeeProd[EmployeePositions.Engineer] / 90 +
+ Math.pow(this.sciResearch.qty, this.sciFac) +
+ Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3);
+ }
+ } else {
+ for (const reqMatName in this.reqMats) {
+ if (this.reqMats.hasOwnProperty(reqMatName)) {
+ warehouse.materials[reqMatName].prd = 0;
+ }
+ }
+ }
+
+ //Per second
+ const fooProd = prod * producableFrac / (CorporationConstants.SecsPerMarketCycle * marketCycles);
+ for (let fooI = 0; fooI < this.prodMats.length; ++fooI) {
+ warehouse.materials[this.prodMats[fooI]].prd = fooProd;
+ }
+ } else {
+ //If this doesn't produce any materials, then it only creates
+ //Products. Creating products will consume materials. The
+ //Production of all consumed materials must be set to 0
+ for (const reqMatName in this.reqMats) {
+ warehouse.materials[reqMatName].prd = 0;
+ }
+ }
+ break;
+
+ case "SALE":
+ /* Process sale of materials */
+ for (var matName in warehouse.materials) {
+ if (warehouse.materials.hasOwnProperty(matName)) {
+ var mat = warehouse.materials[matName];
+ if (mat.sCost < 0 || mat.sllman[0] === false) {
+ mat.sll = 0;
+ continue;
+ }
+
+ // Sale multipliers
+ const businessFactor = this.getBusinessFactor(office); //Business employee productivity
+ const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
+ const marketFactor = this.getMarketFactor(mat); //Competition + demand
+
+ // Determine the cost that the material will be sold at
+ const markupLimit = mat.getMarkupLimit();
+ var sCost;
+ if (mat.marketTa2) {
+ const prod = mat.prd;
+
+ // Reverse engineer the 'maxSell' formula
+ // 1. Set 'maxSell' = prod
+ // 2. Substitute formula for 'markup'
+ // 3. Solve for 'sCost'
+ const numerator = markupLimit;
+ const sqrtNumerator = prod;
+ const sqrtDenominator = ((mat.qlt + .001)
+ * marketFactor
+ * businessFactor
+ * company.getSalesMultiplier()
+ * advertisingFactor
+ * this.getSalesMultiplier());
+ const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
+ let optimalPrice;
+ if (sqrtDenominator === 0 || denominator === 0) {
+ if (sqrtNumerator === 0) {
+ optimalPrice = 0; // No production
+ } else {
+ optimalPrice = mat.bCost + markupLimit;
+ console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
+ }
+ } else {
+ optimalPrice = (numerator / denominator) + mat.bCost;
+ }
+
+ // We'll store this "Optimal Price" in a property so that we don't have
+ // to re-calculate it for the UI
+ mat.marketTa2Price = optimalPrice;
+
+ sCost = optimalPrice;
+ } else if (mat.marketTa1) {
+ sCost = mat.bCost + markupLimit;
+ } else if (isString(mat.sCost)) {
+ sCost = mat.sCost.replace(/MP/g, mat.bCost);
+ sCost = eval(sCost);
+ } else {
+ sCost = mat.sCost;
+ }
+
+ // Calculate how much of the material sells (per second)
+ let markup = 1;
+ if (sCost > mat.bCost) {
+ //Penalty if difference between sCost and bCost is greater than markup limit
+ if ((sCost - mat.bCost) > markupLimit) {
+ markup = Math.pow(markupLimit / (sCost - mat.bCost), 2);
+ }
+ } else if (sCost < mat.bCost) {
+ if (sCost <= 0) {
+ markup = 1e12; //Sell everything, essentially discard
+ } else {
+ //Lower prices than market increases sales
+ markup = mat.bCost / sCost;
+ }
+ }
+
+ var maxSell = (mat.qlt + .001)
+ * marketFactor
+ * markup
+ * businessFactor
+ * company.getSalesMultiplier()
+ * advertisingFactor
+ * this.getSalesMultiplier();
+ var sellAmt;
+ if (isString(mat.sllman[1])) {
+ //Dynamically evaluated
+ var tmp = mat.sllman[1].replace(/MAX/g, maxSell);
+ tmp = tmp.replace(/PROD/g, mat.prd);
+ try {
+ sellAmt = eval(tmp);
+ } catch(e) {
+ dialogBoxCreate("Error evaluating your sell amount for material " + mat.name +
+ " in " + this.name + "'s " + city + " office. The sell amount " +
+ "is being set to zero");
+ sellAmt = 0;
+ }
+ sellAmt = Math.min(maxSell, sellAmt);
+ } else if (mat.sllman[1] === -1) {
+ //Backwards compatibility, -1 = MAX
+ sellAmt = maxSell;
+ } else {
+ //Player's input value is just a number
+ sellAmt = Math.min(maxSell, mat.sllman[1]);
+ }
+
+ sellAmt = (sellAmt * CorporationConstants.SecsPerMarketCycle * marketCycles);
+ sellAmt = Math.min(mat.qty, sellAmt);
+ if (sellAmt < 0) {
+ console.warn(`sellAmt calculated to be negative for ${matName} in ${city}`);
+ mat.sll = 0;
+ continue;
+ }
+ if (sellAmt && sCost >= 0) {
+ mat.qty -= sellAmt;
+ revenue += (sellAmt * sCost);
+ mat.sll = sellAmt / (CorporationConstants.SecsPerMarketCycle * marketCycles);
+ } else {
+ mat.sll = 0;
+ }
+ }
+ } //End processing of sale of materials
+ break;
+
+ case "EXPORT":
+ for (var matName in warehouse.materials) {
+ if (warehouse.materials.hasOwnProperty(matName)) {
+ var mat = warehouse.materials[matName];
+ mat.totalExp = 0; //Reset export
+ for (var expI = 0; expI < mat.exp.length; ++expI) {
+ var exp = mat.exp[expI];
+ var amt = exp.amt.replace(/MAX/g, mat.qty / (CorporationConstants.SecsPerMarketCycle * marketCycles));
+ try {
+ amt = eval(amt);
+ } catch(e) {
+ dialogBoxCreate("Calculating export for " + mat.name + " in " +
+ this.name + "'s " + city + " division failed with " +
+ "error: " + e);
+ continue;
+ }
+ if (isNaN(amt)) {
+ dialogBoxCreate("Error calculating export amount for " + mat.name + " in " +
+ this.name + "'s " + city + " division.");
+ continue;
+ }
+ amt = amt * CorporationConstants.SecsPerMarketCycle * marketCycles;
+
+ if (mat.qty < amt) {
+ amt = mat.qty;
+ }
+ if (amt === 0) {
+ break; //None left
+ }
+ for (var foo = 0; foo < company.divisions.length; ++foo) {
+ if (company.divisions[foo].name === exp.ind) {
+ var expIndustry = company.divisions[foo];
+ var expWarehouse = expIndustry.warehouses[exp.city];
+ if (!(expWarehouse instanceof Warehouse)) {
+ console.error(`Invalid export! ${expIndustry.name} ${exp.city}`);
+ break;
+ }
+
+ // Make sure theres enough space in warehouse
+ if (expWarehouse.sizeUsed >= expWarehouse.size) {
+ // Warehouse at capacity. Exporting doesnt
+ // affect revenue so just return 0's
+ return [0, 0];
+ } else {
+ var maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]);
+ amt = Math.min(maxAmt, amt);
+ }
+ expWarehouse.materials[matName].imp += (amt / (CorporationConstants.SecsPerMarketCycle * marketCycles));
+ expWarehouse.materials[matName].qty += amt;
+ expWarehouse.materials[matName].qlt = mat.qlt;
+ mat.qty -= amt;
+ mat.totalExp += amt;
+ expIndustry.updateWarehouseSizeUsed(expWarehouse);
+ break;
+ }
+ }
+ }
+ //totalExp should be per second
+ mat.totalExp /= (CorporationConstants.SecsPerMarketCycle * marketCycles);
+ }
+ }
+
+ break;
+
+ case "START":
+ break;
+ default:
+ console.error(`Invalid state: ${this.state}`);
+ break;
+ } //End switch(this.state)
+ this.updateWarehouseSizeUsed(warehouse);
+
+ } // End warehouse
+
+ //Produce Scientific Research based on R&D employees
+ //Scientific Research can be produced without a warehouse
+ if (office instanceof OfficeSpace) {
+ this.sciResearch.qty += (.004
+ * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5)
+ * company.getScientificResearchMultiplier()
+ * this.getScientificResearchMultiplier());
+ }
+ }
+ return [revenue, expenses];
+ }
+
+ //Process production & sale of this industry's FINISHED products (including all of their stats)
+ processProducts(marketCycles=1, corporation: any): [number, number] {
+ var revenue = 0, expenses = 0;
+
+ //Create products
+ if (this.state === "PRODUCTION") {
+ for (const prodName in this.products) {
+ const prod = this.products[prodName];
+ if (!prod.fin) {
+ const city = prod.createCity;
+ const office = this.offices[city];
+
+ // Designing/Creating a Product is based mostly off Engineers
+ const engrProd = office.employeeProd[EmployeePositions.Engineer];
+ const mgmtProd = office.employeeProd[EmployeePositions.Management];
+ const opProd = office.employeeProd[EmployeePositions.Operations];
+ const total = engrProd + mgmtProd + opProd;
+ if (total <= 0) { break; }
+
+ // Management is a multiplier for the production from Engineers
+ const mgmtFactor = 1 + (mgmtProd / (1.2 * total));
+
+ const progress = (Math.pow(engrProd, 0.34) + Math.pow(opProd, 0.2)) * mgmtFactor;
+
+ prod.createProduct(marketCycles, progress);
+ if (prod.prog >= 100) {
+ prod.finishProduct(office.employeeProd, this);
+ }
+ break;
+ }
+ }
+ }
+
+ //Produce Products
+ for (var prodName in this.products) {
+ if (this.products.hasOwnProperty(prodName)) {
+ var prod = this.products[prodName];
+ if (prod instanceof Product && prod.fin) {
+ revenue += this.processProduct(marketCycles, prod, corporation);
+ }
+ }
+ }
+ return [revenue, expenses];
+ }
+
+ //Processes FINISHED products
+ processProduct(marketCycles=1, product: Product, corporation: any): number {
+ let totalProfit = 0;
+ for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
+ let city = CorporationConstants.Cities[i], office = this.offices[city], warehouse = this.warehouses[city];
+ if (warehouse instanceof Warehouse) {
+ switch(this.state) {
+
+ case "PRODUCTION": {
+ //Calculate the maximum production of this material based
+ //on the office's productivity
+ var maxProd = this.getOfficeProductivity(office, {forProduct:true})
+ * corporation.getProductionMultiplier()
+ * this.prodMult // Multiplier from materials
+ * this.getProductionMultiplier() // Multiplier from research
+ * this.getProductProductionMultiplier(); // Multiplier from research
+ let prod;
+
+ //Account for whether production is manually limited
+ if (product.prdman[city][0]) {
+ prod = Math.min(maxProd, product.prdman[city][1]);
+ } else {
+ prod = maxProd;
+ }
+ prod *= (CorporationConstants.SecsPerMarketCycle * marketCycles);
+
+ //Calculate net change in warehouse storage making the Products will cost
+ var netStorageSize = product.siz;
+ for (var reqMatName in product.reqMats) {
+ if (product.reqMats.hasOwnProperty(reqMatName)) {
+ var normQty = product.reqMats[reqMatName];
+ netStorageSize -= (MaterialSizes[reqMatName] * normQty);
+ }
+ }
+
+ //If there's not enough space in warehouse, limit the amount of Product
+ if (netStorageSize > 0) {
+ var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / netStorageSize);
+ prod = Math.min(maxAmt, prod);
+ }
+
+ warehouse.smartSupplyStore += (prod / (CorporationConstants.SecsPerMarketCycle * marketCycles));
+
+ //Make sure we have enough resources to make our Products
+ var producableFrac = 1;
+ for (var reqMatName in product.reqMats) {
+ if (product.reqMats.hasOwnProperty(reqMatName)) {
+ var req = product.reqMats[reqMatName] * prod;
+ if (warehouse.materials[reqMatName].qty < req) {
+ producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req);
+ }
+ }
+ }
+
+ //Make our Products if they are producable
+ if (producableFrac > 0 && prod > 0) {
+ for (var reqMatName in product.reqMats) {
+ if (product.reqMats.hasOwnProperty(reqMatName)) {
+ var reqMatQtyNeeded = (product.reqMats[reqMatName] * prod * producableFrac);
+ warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
+ warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (CorporationConstants.SecsPerMarketCycle * marketCycles);
+ }
+ }
+ //Quantity
+ product.data[city][0] += (prod * producableFrac);
+ }
+
+ //Keep track of production Per second
+ product.data[city][1] = prod * producableFrac / (CorporationConstants.SecsPerMarketCycle * marketCycles);
+ break;
+ }
+ case "SALE": {
+ //Process sale of Products
+ product.pCost = 0; //Estimated production cost
+ for (var reqMatName in product.reqMats) {
+ if (product.reqMats.hasOwnProperty(reqMatName)) {
+ product.pCost += (product.reqMats[reqMatName] * warehouse.materials[reqMatName].bCost);
+ }
+ }
+
+ // Since its a product, its production cost is increased for labor
+ product.pCost *= CorporationConstants.ProductProductionCostRatio;
+
+ // Sale multipliers
+ const businessFactor = this.getBusinessFactor(office); //Business employee productivity
+ const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
+ const marketFactor = this.getMarketFactor(product); //Competition + demand
+
+ // Calculate Sale Cost (sCost), which could be dynamically evaluated
+ const markupLimit = product.rat / product.mku;
+ var sCost;
+ if (product.marketTa2) {
+ const prod = product.data[city][1];
+
+ // Reverse engineer the 'maxSell' formula
+ // 1. Set 'maxSell' = prod
+ // 2. Substitute formula for 'markup'
+ // 3. Solve for 'sCost'roduct.pCost = sCost
+ const numerator = markupLimit;
+ const sqrtNumerator = prod;
+ const sqrtDenominator = (0.5
+ * Math.pow(product.rat, 0.65)
+ * marketFactor
+ * corporation.getSalesMultiplier()
+ * businessFactor
+ * advertisingFactor
+ * this.getSalesMultiplier());
+ const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
+ let optimalPrice;
+ if (sqrtDenominator === 0 || denominator === 0) {
+ if (sqrtNumerator === 0) {
+ optimalPrice = 0; // No production
+ } else {
+ optimalPrice = product.pCost + markupLimit;
+ console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
+ }
+ } else {
+ optimalPrice = (numerator / denominator) + product.pCost;
+ }
+
+ // Store this "optimal Price" in a property so we don't have to re-calculate for UI
+ product.marketTa2Price[city] = optimalPrice;
+ sCost = optimalPrice;
+ } else if (product.marketTa1) {
+ sCost = product.pCost + markupLimit;
+ } else if (isString(product.sCost)) {
+ const sCostString = product.sCost as string;
+ if(product.mku === 0) {
+ console.error(`mku is zero, reverting to 1 to avoid Infinity`);
+ product.mku = 1;
+ }
+ sCost = sCostString.replace(/MP/g, (product.pCost + product.rat / product.mku)+'');
+ sCost = eval(sCost);
+
+ } else {
+ sCost = product.sCost;
+ }
+
+ var markup = 1;
+ if (sCost > product.pCost) {
+ if ((sCost - product.pCost) > markupLimit) {
+ markup = markupLimit / (sCost - product.pCost);
+ }
+ }
+
+ var maxSell = 0.5
+ * Math.pow(product.rat, 0.65)
+ * marketFactor
+ * corporation.getSalesMultiplier()
+ * Math.pow(markup, 2)
+ * businessFactor
+ * advertisingFactor
+ * this.getSalesMultiplier();
+ var sellAmt;
+ if (product.sllman[city][0] && isString(product.sllman[city][1])) {
+ //Sell amount is dynamically evaluated
+ var tmp = product.sllman[city][1].replace(/MAX/g, maxSell);
+ tmp = tmp.replace(/PROD/g, product.data[city][1]);
+ try {
+ tmp = eval(tmp);
+ } catch(e) {
+ dialogBoxCreate("Error evaluating your sell price expression for " + product.name +
+ " in " + this.name + "'s " + city + " office. Sell price is being set to MAX");
+ tmp = maxSell;
+ }
+ sellAmt = Math.min(maxSell, tmp);
+ } else if (product.sllman[city][0] && product.sllman[city][1] > 0) {
+ //Sell amount is manually limited
+ sellAmt = Math.min(maxSell, product.sllman[city][1]);
+ } else if (product.sllman[city][0] === false){
+ sellAmt = 0;
+ } else {
+ sellAmt = maxSell;
+ }
+ if (sellAmt < 0) { sellAmt = 0; }
+ sellAmt = sellAmt * CorporationConstants.SecsPerMarketCycle * marketCycles;
+ sellAmt = Math.min(product.data[city][0], sellAmt); //data[0] is qty
+ if (sellAmt && sCost) {
+ product.data[city][0] -= sellAmt; //data[0] is qty
+ totalProfit += (sellAmt * sCost);
+ product.data[city][2] = sellAmt / (CorporationConstants.SecsPerMarketCycle * marketCycles); //data[2] is sell property
+ } else {
+ product.data[city][2] = 0; //data[2] is sell property
+ }
+ break;
+ }
+ case "START":
+ case "PURCHASE":
+ case "EXPORT":
+ break;
+ default:
+ console.error(`Invalid State: ${this.state}`);
+ break;
+ } //End switch(this.state)
+ }
+ }
+ return totalProfit;
+ }
+
+ discontinueProduct(product: Product): void {
+ for (var productName in this.products) {
+ if (this.products.hasOwnProperty(productName)) {
+ if (product === this.products[productName]) {
+ delete this.products[productName];
+ }
+ }
+ }
+ }
+
+ upgrade(upgrade: IndustryUpgrade, refs: {corporation: any, office: OfficeSpace}): void {
+ const corporation = refs.corporation;
+ const office = refs.office;
+ const upgN = upgrade[0];
+ while (this.upgrades.length <= upgN) {this.upgrades.push(0);}
+ ++this.upgrades[upgN];
+
+ switch (upgN) {
+ case 0: //Coffee, 5% energy per employee
+ for (let i = 0; i < office.employees.length; ++i) {
+ office.employees[i].ene = Math.min(office.employees[i].ene * 1.05, office.maxEne);
+ }
+ break;
+ case 1: //AdVert.Inc,
+ const advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier();
+ this.awareness += (3 * advMult);
+ this.popularity += (1 * advMult);
+ this.awareness *= (1.01 * advMult);
+ this.popularity *= ((1 + getRandomInt(1, 3) / 100) * advMult);
+ break;
+ default:
+ console.error(`Un-implemented function index: ${upgN}`);
+ break;
+ }
+ }
+
+ // Returns how much of a material can be produced based of office productivity (employee stats)
+ getOfficeProductivity(office: OfficeSpace, params: any = {}): number {
+ const opProd = office.employeeProd[EmployeePositions.Operations];
+ const engrProd = office.employeeProd[EmployeePositions.Engineer];
+ const mgmtProd = office.employeeProd[EmployeePositions.Management]
+ const total = opProd + engrProd + mgmtProd;
+
+ if (total <= 0) return 0;
+
+ // Management is a multiplier for the production from Operations and Engineers
+ const mgmtFactor = 1 + (mgmtProd / (1.2 * total));
+
+ // For production, Operations is slightly more important than engineering
+ // Both Engineering and Operations have diminishing returns
+ const prod = (Math.pow(opProd, 0.4) + Math.pow(engrProd, 0.3)) * mgmtFactor;
+
+ // Generic multiplier for the production. Used for game-balancing purposes
+ const balancingMult = 0.05;
+
+ if (params && params.forProduct) {
+ // Products are harder to create and therefore have less production
+ return 0.5 * balancingMult * prod;
+ } else {
+ return balancingMult * prod;
+ }
+ }
+
+ // Returns a multiplier based on the office' 'Business' employees that affects sales
+ getBusinessFactor(office: OfficeSpace): number {
+ const businessProd = 1 + office.employeeProd[EmployeePositions.Business];
+
+ return calculateEffectWithFactors(businessProd, 0.26, 10e3);
+ }
+
+ //Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This
+ //multiplier affects sales. The result is:
+ // [Total sales mult, total awareness mult, total pop mult, awareness/pop ratio mult]
+ getAdvertisingFactors(): [number, number, number, number] {
+ const awarenessFac = Math.pow(this.awareness + 1, this.advFac);
+ const popularityFac = Math.pow(this.popularity + 1, this.advFac);
+ const ratioFac = (this.awareness === 0 ? 0.01 : Math.max((this.popularity + .001) / this.awareness, 0.01));
+ const totalFac = Math.pow(awarenessFac * popularityFac * ratioFac, 0.85);
+ return [totalFac, awarenessFac, popularityFac, ratioFac];
+ }
+
+ //Returns a multiplier based on a materials demand and competition that affects sales
+ getMarketFactor(mat: {dmd: number, cmp: number}): number {
+ return Math.max(0.1, mat.dmd * (100 - mat.cmp) / 100);
+ }
+
+ // Returns a boolean indicating whether this Industry has the specified Research
+ hasResearch(name: string): boolean {
+ return (this.researched[name] === true);
+ }
+
+ updateResearchTree(): void {
+ const researchTree = IndustryResearchTrees[this.type];
+ if(researchTree === undefined)
+ throw new Error(`Invalid industry "${this.type}"`);
+
+ // Since ResearchTree data isnt saved, we'll update the Research Tree data
+ // based on the stored 'researched' property in the Industry object
+ if (Object.keys(researchTree.researched).length !== Object.keys(this.researched).length) {
+ for (const research in this.researched) {
+ researchTree.research(research);
+ }
+ }
+ }
+
+ // Get multipliers from Research
+ getAdvertisingMultiplier(): number {
+ const researchTree = IndustryResearchTrees[this.type];
+ if(researchTree === undefined)
+ throw new Error(`Invalid industry: "${this.type}"`);
+ this.updateResearchTree();
+ return researchTree.getAdvertisingMultiplier();
+ }
+
+ getEmployeeChaMultiplier(): number {
+ const researchTree = IndustryResearchTrees[this.type];
+ if(researchTree === undefined)
+ throw new Error(`Invalid industry: "${this.type}"`);
+ this.updateResearchTree();
+ return researchTree.getEmployeeChaMultiplier();
+ }
+
+ getEmployeeCreMultiplier(): number {
+ const researchTree = IndustryResearchTrees[this.type];
+ if(researchTree === undefined)
+ throw new Error(`Invalid industry: "${this.type}"`);
+ this.updateResearchTree();
+ return researchTree.getEmployeeCreMultiplier();
+ }
+
+ getEmployeeEffMultiplier(): number {
+ const researchTree = IndustryResearchTrees[this.type];
+ if(researchTree === undefined)
+ throw new Error(`Invalid industry: "${this.type}"`);
+ this.updateResearchTree();
+ return researchTree.getEmployeeEffMultiplier();
+ }
+
+ getEmployeeIntMultiplier(): number {
+ const researchTree = IndustryResearchTrees[this.type];
+ if(researchTree === undefined)
+ throw new Error(`Invalid industry: "${this.type}"`);
+ this.updateResearchTree();
+ return researchTree.getEmployeeIntMultiplier();
+ }
+
+ getProductionMultiplier(): number {
+ const researchTree = IndustryResearchTrees[this.type];
+ if(researchTree === undefined)
+ throw new Error(`Invalid industry: "${this.type}"`);
+ this.updateResearchTree();
+ return researchTree.getProductionMultiplier();
+ }
+
+ getProductProductionMultiplier(): number {
+ const researchTree = IndustryResearchTrees[this.type];
+ if(researchTree === undefined)
+ throw new Error(`Invalid industry: "${this.type}"`);
+ this.updateResearchTree();
+ return researchTree.getProductProductionMultiplier();
+ }
+
+ getSalesMultiplier(): number {
+ const researchTree = IndustryResearchTrees[this.type];
+ if(researchTree === undefined)
+ throw new Error(`Invalid industry: "${this.type}"`);
+ this.updateResearchTree();
+ return researchTree.getSalesMultiplier();
+ }
+
+ getScientificResearchMultiplier(): number {
+ const researchTree = IndustryResearchTrees[this.type];
+ if(researchTree === undefined)
+ throw new Error(`Invalid industry: "${this.type}"`);
+ this.updateResearchTree();
+ return researchTree.getScientificResearchMultiplier();
+ }
+
+ getStorageMultiplier(): number {
+ const researchTree = IndustryResearchTrees[this.type];
+ if(researchTree === undefined)
+ throw new Error(`Invalid industry: "${this.type}"`);
+ this.updateResearchTree();
+ return researchTree.getStorageMultiplier();
+ }
+
+ /**
+ * Serialize the current object to a JSON save state.
+ */
+ toJSON(): any {
+ return Generic_toJSON("Industry", this);
+ }
+
+ /**
+ * Initiatizes a Industry object from a JSON save state.
+ */
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+ static fromJSON(value: any): Industry {
+ return Generic_fromJSON(Industry, value.data);
+ }
+}
+
+Reviver.constructors.Industry = Industry;
diff --git a/src/Corporation/IndustryUpgrades.ts b/src/Corporation/IndustryUpgrades.ts
index d83ada87f..234ca88e5 100644
--- a/src/Corporation/IndustryUpgrades.ts
+++ b/src/Corporation/IndustryUpgrades.ts
@@ -1,9 +1,11 @@
import { IMap } from "../types";
+export type IndustryUpgrade = [number, number, number, number, string, string];
+
// Industry upgrades
// The data structure is an array with the following format:
// [index in array, base price, price mult, benefit mult (if applicable), name, desc]
-export const IndustryUpgrades: IMap = {
+export const IndustryUpgrades: IMap = {
"0": [0, 500e3, 1, 1.05,
"Coffee", "Provide your employees with coffee, increasing their energy by 5%."],
"1": [1, 1e9, 1.06, 1.03,
diff --git a/src/Corporation/Product.ts b/src/Corporation/Product.ts
index 6a688d1f1..c63a79507 100644
--- a/src/Corporation/Product.ts
+++ b/src/Corporation/Product.ts
@@ -60,7 +60,7 @@ export class Product {
pCost = 0;
// Sell cost
- sCost = 0;
+ sCost: string | number = 0;
// Variables for handling the creation process of this Product
fin = false; // Whether this Product has finished being created
diff --git a/src/Corporation/data/CorporationUnlockUpgrades.ts b/src/Corporation/data/CorporationUnlockUpgrades.ts
index 5cbed6abc..aae13fe5a 100644
--- a/src/Corporation/data/CorporationUnlockUpgrades.ts
+++ b/src/Corporation/data/CorporationUnlockUpgrades.ts
@@ -1,10 +1,12 @@
import { IMap } from "../../types";
+export type CorporationUnlockUpgrade = [number, number, string, string];
+
// Corporation Unlock Upgrades
// Upgrades for entire corporation, unlocks features, either you have it or you dont
// The data structure is an array with the following format:
// [index in Corporation feature upgrades array, price, name, description]
-export const CorporationUnlockUpgrades: IMap = {
+export const CorporationUnlockUpgrades: IMap = {
//Lets you export goods
"0": [0, 20e9, "Export",
"Develop infrastructure to export your materials to your other facilities. " +
diff --git a/src/Corporation/data/CorporationUpgrades.ts b/src/Corporation/data/CorporationUpgrades.ts
index 519977ff8..57c97331c 100644
--- a/src/Corporation/data/CorporationUpgrades.ts
+++ b/src/Corporation/data/CorporationUpgrades.ts
@@ -1,10 +1,12 @@
import { IMap } from "../../types";
+export type CorporationUpgrade = [number, number, number, number, string, string];
+
// Corporation Upgrades
// Upgrades for entire corporation, levelable upgrades
// The data structure is an array with the following format
// [index in Corporation upgrades array, base price, price mult, benefit mult (additive), name, desc]
-export const CorporationUpgrades: IMap = {
+export const CorporationUpgrades: IMap = {
//Smart factories, increases production
"0": [0, 2e9, 1.06, 0.03,
"Smart Factories", "Advanced AI automatically optimizes the operation and productivity " +
diff --git a/src/Corporation/ui/FindInvestorsPopup.tsx b/src/Corporation/ui/FindInvestorsPopup.tsx
new file mode 100644
index 000000000..68c5da8f0
--- /dev/null
+++ b/src/Corporation/ui/FindInvestorsPopup.tsx
@@ -0,0 +1,63 @@
+import React, { useState } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { CorporationConstants } from "../data/Constants";
+
+interface IProps {
+ corp: any;
+ popupId: string;
+}
+
+// Create a popup that lets the player manage exports
+export function FindInvestorsPopup(props: IProps): React.ReactElement {
+ const val = props.corp.determineValuation()
+ let percShares = 0;
+ let roundMultiplier = 4;
+ switch (props.corp.fundingRound) {
+ case 0: //Seed
+ percShares = 0.10;
+ roundMultiplier = 4;
+ break;
+ case 1: //Series A
+ percShares = 0.35;
+ roundMultiplier = 3;
+ break;
+ case 2: //Series B
+ percShares = 0.25;
+ roundMultiplier = 3;
+ break;
+ case 3: //Series C
+ percShares = 0.20;
+ roundMultiplier = 2.5;
+ break;
+ default:
+ return (<>>);
+ }
+ const funding = val * percShares * roundMultiplier;
+ const investShares = Math.floor(CorporationConstants.INITIALSHARES * percShares);
+
+ function findInvestors(): void {
+ props.corp.fundingRound++;
+ props.corp.addFunds(funding);
+ props.corp.numShares -= investShares;
+ props.corp.rerender();
+ removePopup(props.popupId);
+ }
+ return (<>
+
+ An investment firm has offered you {numeralWrapper.formatMoney(funding)} in
+ funding in exchange for a {numeralWrapper.format(percShares*100, "0.000a")}%
+ stake in the company ({numeralWrapper.format(investShares, '0.000a')} shares).
+ Do you accept or reject this offer?
+ Hint: Investment firms will offer more money if your corporation is turning a profit
+
+ Accept
+ >);
+}
diff --git a/src/Corporation/ui/GoPublicPopup.tsx b/src/Corporation/ui/GoPublicPopup.tsx
new file mode 100644
index 000000000..114c3ccf8
--- /dev/null
+++ b/src/Corporation/ui/GoPublicPopup.tsx
@@ -0,0 +1,67 @@
+import React, { useState } from 'react';
+import { Warehouse } from "../Warehouse";
+import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { createElement } from "../../../utils/uiHelpers/createElement";
+import { removePopup } from "../../ui/React/createPopup";
+import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
+import { clearSelector } from "../../../utils/uiHelpers/clearSelector";
+import { getSelectText,
+ getSelectValue } from "../../../utils/uiHelpers/getSelectData";
+import { numeralWrapper } from "../../ui/numeralFormat";
+import { CorporationConstants } from "../data/Constants";
+
+interface IProps {
+ corp: any;
+ popupId: string;
+}
+
+// Create a popup that lets the player manage exports
+export function GoPublicPopup(props: IProps): React.ReactElement {
+ const [shares, setShares] = useState('');
+ const initialSharePrice = props.corp.determineValuation() / (props.corp.totalShares);
+
+ function goPublic(): void {
+ const numShares = parseFloat(shares);
+ const initialSharePrice = props.corp.determineValuation() / (props.corp.totalShares);
+ if (isNaN(numShares)) {
+ dialogBoxCreate("Invalid value for number of issued shares");
+ return;
+ }
+ if (numShares > props.corp.numShares) {
+ dialogBoxCreate("Error: You don't have that many shares to issue!");
+ return;
+ }
+ props.corp.public = true;
+ props.corp.sharePrice = initialSharePrice;
+ props.corp.issuedShares = numShares;
+ props.corp.numShares -= numShares;
+ props.corp.addFunds(numShares * initialSharePrice);
+ props.corp.rerender();
+ dialogBoxCreate(`You took your ${props.corp.name} public and earned ` +
+ `${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`);
+ removePopup(props.popupId);
+ }
+
+ function onKeyDown(event: React.KeyboardEvent): void {
+ if (event.keyCode === 13) goPublic();
+ }
+
+ function onChange(event: React.ChangeEvent): void {
+ setShares(event.target.value);
+ }
+
+ return (<>
+
+ Enter the number of shares you would like to issue
+ for your IPO. These shares will be publicly sold
+ and you will no longer own them. Your Corporation will
+ receive {numeralWrapper.formatMoney(initialSharePrice)} per share
+ (the IPO money will be deposited directly into your Corporation's funds).
+
+ You have a total of {numeralWrapper.format(props.corp.numShares, "0.000a")} of
+ shares that you can issue.
+
+
+ Go Public
+ >);
+}
\ No newline at end of file
diff --git a/src/Corporation/ui/NewIndustryPopup.tsx b/src/Corporation/ui/NewIndustryPopup.tsx
index c61ed21d4..811216cdc 100644
--- a/src/Corporation/ui/NewIndustryPopup.tsx
+++ b/src/Corporation/ui/NewIndustryPopup.tsx
@@ -11,7 +11,7 @@ import {
Industries,
IndustryStartingCosts,
IndustryDescriptions } from "../IndustryData";
-import { Industry } from "../Corporation";
+import { Industry } from "../Industry";
interface IProps {
corp: any;
diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx
index c9ad865d8..bd534175d 100644
--- a/src/Corporation/ui/Overview.tsx
+++ b/src/Corporation/ui/Overview.tsx
@@ -7,6 +7,8 @@ import { SellSharesPopup } from "./SellSharesPopup";
import { BuybackSharesPopup } from "./BuybackSharesPopup";
import { IssueDividendsPopup } from "./IssueDividendsPopup";
import { IssueNewSharesPopup } from "./IssueNewSharesPopup";
+import { FindInvestorsPopup } from "./FindInvestorsPopup";
+import { GoPublicPopup } from "./GoPublicPopup";
import { CorporationConstants } from "../data/Constants";
import { CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades";
@@ -168,12 +170,25 @@ export function Overview(props: IProps): React.ReactElement {
const findInvestorsClassName = fundingAvailable ? "std-button" : "a-link-button-inactive";
const findInvestorsTooltip = fundingAvailable ? "Search for private investors who will give you startup funding in exchangefor equity (stock shares) in your company" : null;
- const findInvestorsOnClick = props.corp.getInvestment.bind(props.corp);
- const goPublicOnClick = props.corp.goPublic.bind(props.corp);
+ function openFindInvestorsPopup(): void {
+ const popupId = "cmpy-mgmt-find-investors-popup";
+ createPopup(popupId, FindInvestorsPopup, {
+ popupId: popupId,
+ corp: props.corp,
+ });
+ }
+
+ function openGoPublicPopup(): void {
+ const popupId = "cmpy-mgmt-go-public-popup";
+ createPopup(popupId, GoPublicPopup, {
+ popupId: popupId,
+ corp: props.corp,
+ });
+ }
const findInvestorsBtn = createButton({
class: findInvestorsClassName,
- onClick: findInvestorsOnClick,
+ onClick: openFindInvestorsPopup,
style: "inline-block",
text: "Find Investors",
tooltip: findInvestorsTooltip,
@@ -181,7 +196,7 @@ export function Overview(props: IProps): React.ReactElement {
});
const goPublicBtn = createButton({
class: "std-button",
- onClick: goPublicOnClick,
+ onClick: openGoPublicPopup,
style: "inline-block",
display: "inline-block",
text: "Go Public",
diff --git a/src/Corporation/ui/UnlockUpgrade.tsx b/src/Corporation/ui/UnlockUpgrade.tsx
index 77bb92873..c42b73554 100644
--- a/src/Corporation/ui/UnlockUpgrade.tsx
+++ b/src/Corporation/ui/UnlockUpgrade.tsx
@@ -3,9 +3,10 @@ import React from "react";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
+import { CorporationUnlockUpgrade } from "../data/CorporationUnlockUpgrades";
interface IProps {
- upgradeData: number[];
+ upgradeData: CorporationUnlockUpgrade;
corp: any;
}
diff --git a/src/ThirdParty/decimal.js.d.ts b/src/ThirdParty/decimal.js.d.ts
new file mode 100644
index 000000000..408f735e4
--- /dev/null
+++ b/src/ThirdParty/decimal.js.d.ts
@@ -0,0 +1 @@
+declare module "decimal.js";
\ No newline at end of file
diff --git a/src/engine.jsx b/src/engine.jsx
index a29d6fcde..ec0bac4d9 100644
--- a/src/engine.jsx
+++ b/src/engine.jsx
@@ -454,7 +454,7 @@ const Engine = {
if (Player.corporation instanceof Corporation) {
Engine.hideAllContent();
routing.navigateTo(Page.Corporation);
- Player.corporation.createUI();
+ Player.corporation.createUI(Player);
}
},
@@ -537,7 +537,7 @@ const Engine = {
}
if (Player.corporation instanceof Corporation) {
- Player.corporation.clearUI();
+ Player.corporation.clearUI(Player);
}
clearResleevesPage();
@@ -856,7 +856,7 @@ const Engine = {
if (Engine.Counters.mechanicProcess <= 0) {
if (Player.corporation instanceof Corporation) {
- Player.corporation.process();
+ Player.corporation.process(Player);
}
if (Player.bladeburner instanceof Bladeburner) {
try {
diff --git a/src/index.html b/src/index.html
index ae82924bd..ab22a3c44 100644
--- a/src/index.html
+++ b/src/index.html
@@ -641,6 +641,5 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
-