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 ( + + ) +} \ 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 ( - - ) -} +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 ( + + ) +} \ 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 ( - - ) - } - - 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')} +

+ + + ); +} \ 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 ( -

- -
- ) - } + if (warehouse instanceof Warehouse) { + return renderWarehouseUI(); + } else { + return ( +
+ +
+ ) } } 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 ( - -

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() } - 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 && @@ -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

- - -
- - { - !division.hasResearch("AutoPartyManager") && - - } -
- - {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

+ + +
+ + { + !division.hasResearch("AutoPartyManager") && + + } +
+ + {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.

+ +

{getRepText(money ? money : 0, stock ? stock : 0)}

+ + + + ); +} 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 {
- { !division.hasResearch("AutoPartyManager") && -
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

+ + + + ); +} \ 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 (); + } + + 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)} +

+ +
+ + + ); + +} \ 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. +

+ +
+ + + ); +} \ 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

+ + ); +} \ 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) { - { @@ -173,7 +182,7 @@ function ProductComponent(props: IProductProps) { - { 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 { /> + + + + + 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() } - 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. +

+ + + + +

+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 ( -
- { division.hasResearch("Market-TA.I") && - } @@ -179,7 +197,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
- { division.hasResearch("Market-TA.I") && - } @@ -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 && - } @@ -334,7 +365,7 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement { { division.hasResearch("Market-TA.I") && - } 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. +

+ + + ); +} 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. +

+ + + + ); + + + // 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. +

+ + + ); +} \ 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 (<> +

+ + +
+ + + + ); +} 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. +

+ +
+ + +
+

+ 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 +

+
+ + +
+ + ); + +} 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:

+ +

{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 { + + + + +
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") && - } @@ -205,7 +198,7 @@ function ProductComponent(props: IProductProps): React.ReactElement { { division.hasResearch("Market-TA.I") && - } 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 {
-
{ division.hasResearch("Market-TA.I") && - } @@ -187,7 +203,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {

-
{ division.hasResearch("Market-TA.I") && - } @@ -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 {
- @@ -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. +

+ +
+ + +
+

+ 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. +

+
+ + +
+ {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). +

+ + + + ); +} + +// 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. +

+ + + + {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. +

+
+ + + + ); +} 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. +

+
+ + + +
+ + +
+ ); +} 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.

-
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 +

+ + ); +} 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. +

+ + + ); +} \ 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) { %> - From 1ae17677c0312ab11005994b59b045840ce0ee48 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 31 Aug 2021 03:07:20 -0400 Subject: [PATCH 20/30] jsts corporation --- src/Corporation/Corporation.tsx | 129 +++++-------- src/Corporation/Industry.ts | 178 +++++++++--------- src/Corporation/IndustryData.tsx | 2 - src/Corporation/ui/ExportPopup.tsx | 12 +- src/Corporation/ui/FindInvestorsPopup.tsx | 9 +- src/Corporation/ui/GoPublicPopup.tsx | 7 - src/Corporation/ui/IndustryOverview.tsx | 2 +- src/Corporation/ui/IndustryWarehouse.tsx | 4 +- src/Corporation/ui/IssueNewSharesPopup.tsx | 1 - .../ui/LimitProductProductionPopup.tsx | 9 +- src/Corporation/ui/MakeProductPopup.tsx | 15 +- src/Corporation/ui/MaterialMarketTaPopup.tsx | 8 - src/Corporation/ui/NewIndustryPopup.tsx | 13 +- src/Corporation/ui/ProductMarketTaPopup.tsx | 8 - src/Corporation/ui/PurchaseMaterialPopup.tsx | 8 +- src/Corporation/ui/ResearchPopup.tsx | 10 +- src/Corporation/ui/SellMaterialPopup.tsx | 6 - src/Corporation/ui/SellProductPopup.tsx | 16 +- 18 files changed, 160 insertions(+), 277 deletions(-) diff --git a/src/Corporation/Corporation.tsx b/src/Corporation/Corporation.tsx index 2e506d94a..8d615ef7b 100644 --- a/src/Corporation/Corporation.tsx +++ b/src/Corporation/Corporation.tsx @@ -1,47 +1,26 @@ -import { AllCorporationStates, - CorporationState } from "./CorporationState"; +import { 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 @@ -52,16 +31,6 @@ 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; } @@ -107,7 +76,7 @@ export class Corporation { this.upgradeMultipliers = Array(numUpgrades).fill(1); } - addFunds(amt: number) { + addFunds(amt: number): void { if(!isFinite(amt)) { console.error('Trying to add invalid amount of funds. Report to a developper.'); return; @@ -123,11 +92,11 @@ export class Corporation { this.storedCycles += numCycles; } - process(player: IPlayer) { - if (this.storedCycles >= CyclesPerIndustryStateCycle) { + process(player: IPlayer): void { + if (this.storedCycles >= CorporationConstants.CyclesPerIndustryStateCycle) { const state = this.getState(); const marketCycles = 1; - const gameCycles = (marketCycles * CyclesPerIndustryStateCycle); + const gameCycles = (marketCycles * CorporationConstants.CyclesPerIndustryStateCycle); this.storedCycles -= gameCycles; this.divisions.forEach((ind) => { @@ -152,8 +121,8 @@ export class Corporation { 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); + const profit = this.revenue.minus(this.expenses); + const cycleProfit = profit.times(marketCycles * CorporationConstants.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.

" + @@ -164,7 +133,7 @@ export class Corporation { // 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) { + if (isNaN(this.dividendPercentage) || this.dividendPercentage < 0 || this.dividendPercentage > CorporationConstants.DividendMaxPercentage) { console.error(`Invalid Corporation dividend percentage: ${this.dividendPercentage}`); } else { const totalDividends = (this.dividendPercentage / 100) * cycleProfit; @@ -188,8 +157,8 @@ export class Corporation { } } - determineValuation() { - var val, profit = (this.revenue.minus(this.expenses)).toNumber(); + determineValuation(): number { + let val, profit = (this.revenue.minus(this.expenses)).toNumber(); if (this.public) { // Account for dividends if (this.dividendPercentage > 0) { @@ -212,13 +181,13 @@ export class Corporation { return val * BitNodeMultipliers.CorporationValuation; } - getTargetSharePrice() { + getTargetSharePrice(): number { // 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() { + updateSharePrice(): void { const targetPrice = this.getTargetSharePrice(); if (this.sharePrice <= targetPrice) { this.sharePrice *= (1 + (Math.random() * 0.01)); @@ -228,7 +197,7 @@ export class Corporation { if (this.sharePrice <= 0.01) {this.sharePrice = 0.01;} } - immediatelyUpdateSharePrice() { + immediatelyUpdateSharePrice(): void { this.sharePrice = this.getTargetSharePrice(); } @@ -242,7 +211,7 @@ export class Corporation { let sharesSold = 0; let profit = 0; - const maxIterations = Math.ceil(numShares / SHARESPERPRICEUPDATE); + const maxIterations = Math.ceil(numShares / CorporationConstants.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]; @@ -255,7 +224,7 @@ export class Corporation { break; } else { profit += (sharePrice * sharesUntilUpdate); - sharesUntilUpdate = SHARESPERPRICEUPDATE; + sharesUntilUpdate = CorporationConstants.SHARESPERPRICEUPDATE; sharesTracker -= sharesUntilUpdate; sharesSold += sharesUntilUpdate; @@ -267,7 +236,7 @@ export class Corporation { return [profit, sharePrice, sharesUntilUpdate]; } - convertCooldownToString(cd: number) { + convertCooldownToString(cd: number): string { // The cooldown value is based on game cycles. Convert to a simple string const seconds = cd / 5; @@ -306,11 +275,11 @@ export class Corporation { //Levelable upgrades upgrade(upgrade: CorporationUpgrade): void { - var upgN = upgrade[0], basePrice = upgrade[1], priceMult = upgrade[2], + const 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]); + const totalCost = basePrice * Math.pow(priceMult, this.upgrades[upgN]); if (this.funds.lt(totalCost)) { dialogBoxCreate("You don't have enough funds to purchase this!"); return; @@ -323,9 +292,9 @@ export class Corporation { //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) { + for (let i = 0; i < this.divisions.length; ++i) { + const industry = this.divisions[i]; + for (const city in industry.warehouses) { if (industry.warehouses.hasOwnProperty(city) && industry.warehouses[city] instanceof Warehouse) { industry.warehouses[city].updateSize(this, industry); } @@ -334,64 +303,64 @@ export class Corporation { } } - getProductionMultiplier() { - var mult = this.upgradeMultipliers[0]; + getProductionMultiplier(): number { + const mult = this.upgradeMultipliers[0]; if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} } - getStorageMultiplier() { - var mult = this.upgradeMultipliers[1]; + getStorageMultiplier(): number { + const mult = this.upgradeMultipliers[1]; if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} } - getDreamSenseGain() { - var gain = this.upgradeMultipliers[2] - 1; + getDreamSenseGain(): number { + const gain = this.upgradeMultipliers[2] - 1; return gain <= 0 ? 0 : gain; } - getAdvertisingMultiplier() { - var mult = this.upgradeMultipliers[3]; + getAdvertisingMultiplier(): number { + const mult = this.upgradeMultipliers[3]; if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} } - getEmployeeCreMultiplier() { - var mult = this.upgradeMultipliers[4]; + getEmployeeCreMultiplier(): number { + const mult = this.upgradeMultipliers[4]; if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} } - getEmployeeChaMultiplier() { - var mult = this.upgradeMultipliers[5]; + getEmployeeChaMultiplier(): number { + const mult = this.upgradeMultipliers[5]; if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} } - getEmployeeIntMultiplier() { - var mult = this.upgradeMultipliers[6]; + getEmployeeIntMultiplier(): number { + const mult = this.upgradeMultipliers[6]; if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} } - getEmployeeEffMultiplier() { - var mult = this.upgradeMultipliers[7]; + getEmployeeEffMultiplier(): number { + const mult = this.upgradeMultipliers[7]; if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} } - getSalesMultiplier() { - var mult = this.upgradeMultipliers[8]; + getSalesMultiplier(): number { + const mult = this.upgradeMultipliers[8]; if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} } - getScientificResearchMultiplier() { - var mult = this.upgradeMultipliers[9]; + getScientificResearchMultiplier(): number { + const 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) { + getStarterGuide(player: IPlayer): void { // Check if player already has Corporation Handbook - let homeComp = player.getHomeComputer(), - hasHandbook = false, - handbookFn = LiteratureNames.CorporationManagementHandbook; + const homeComp = player.getHomeComputer(); + let hasHandbook = false; + const handbookFn = LiteratureNames.CorporationManagementHandbook; for (let i = 0; i < homeComp.messages.length; ++i) { if (isString(homeComp.messages[i]) && homeComp.messages[i] === handbookFn) { hasHandbook = true; @@ -401,10 +370,10 @@ export class Corporation { if (!hasHandbook) { homeComp.messages.push(handbookFn); } showLiterature(handbookFn); - return false; + return; } - createUI(player: IPlayer) { + createUI(player: IPlayer): void { companyManagementDiv = createElement("div", { id:"cmpy-mgmt-container", position:"fixed", @@ -419,7 +388,7 @@ export class Corporation { this.rerender(player); } - rerender(player: IPlayer) { + rerender(player: IPlayer): void { if (companyManagementDiv == null || corpRouting == null) { console.warn(`Corporation.rerender() called when companyManagementDiv, corpRouting, or eventHandler is null`); return; @@ -433,7 +402,7 @@ export class Corporation { />, companyManagementDiv); } - clearUI() { + clearUI(): void { if (companyManagementDiv instanceof HTMLElement) { ReactDOM.unmountComponentAtNode(companyManagementDiv); removeElementById(companyManagementDiv.id); diff --git a/src/Corporation/Industry.ts b/src/Corporation/Industry.ts index 5a9bc8cfd..ebdb96835 100644 --- a/src/Corporation/Industry.ts +++ b/src/Corporation/Industry.ts @@ -120,7 +120,7 @@ export class Industry { this.init(); } - init() { + init(): void { //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) @@ -323,8 +323,8 @@ export class Industry { } } - getProductDescriptionText() { - if (!this.makesProducts) {return;} + getProductDescriptionText(): string { + if (!this.makesProducts) return ''; switch (this.type) { case Industries.Food: return "create and manage restaurants"; @@ -349,7 +349,7 @@ export class Industry { } } - getMaximumNumberProducts() { + getMaximumNumberProducts(): number { if (!this.makesProducts) return 0; // Calculate additional number of allowed Products from Research/Upgrades @@ -360,24 +360,24 @@ export class Industry { return CorporationConstants.BaseMaxProducts + additional; } - hasMaximumNumberProducts() { + hasMaximumNumberProducts(): boolean { 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]; + calculateProductionFactors(): void { + let multSum = 0; + for (let i = 0; i < CorporationConstants.Cities.length; ++i) { + const city = CorporationConstants.Cities[i]; + const warehouse = this.warehouses[city]; if (!(warehouse instanceof Warehouse)) { continue; } - var materials = warehouse.materials; + const materials = warehouse.materials; - var cityMult = Math.pow(0.002 * materials.RealEstate.qty+1, this.reFac) * + const 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); @@ -390,9 +390,9 @@ export class Industry { updateWarehouseSizeUsed(warehouse: Warehouse): void { warehouse.updateMaterialSizeUsed(); - for (var prodName in this.products) { + for (const prodName in this.products) { if (this.products.hasOwnProperty(prodName)) { - var prod = this.products[prodName]; + const 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) + "
"); @@ -401,7 +401,7 @@ export class Industry { } } - process(marketCycles=1, state: string, company: any) { + process(marketCycles=1, state: string, company: any): void { this.state = state; //At the start of a cycle, store and reset revenue/expenses @@ -423,8 +423,8 @@ export class Industry { if (this.lastCycleRevenue.gt(0)) {this.newInd = false;} // Process offices (and the employees in them) - var employeeSalary = 0; - for (var officeLoc in this.offices) { + let employeeSalary = 0; + for (const officeLoc in this.offices) { if (this.offices[officeLoc] instanceof OfficeSpace) { employeeSalary += this.offices[officeLoc].process(marketCycles, {industry:this, corporation:company}); } @@ -432,7 +432,7 @@ export class Industry { this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary); // Process change in demand/competition of materials/products - this.processMaterialMarket(marketCycles); + this.processMaterialMarket(); this.processProductMarket(marketCycles); // Process loss of popularity @@ -440,7 +440,7 @@ export class Industry { this.popularity = Math.max(0, this.popularity); // Process Dreamsense gains - var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4; + const popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4; if (popularityGain > 0) { this.popularity += (popularityGain * marketCycles); this.awareness += (awarenessGain * marketCycles); @@ -465,24 +465,24 @@ export class Industry { } // Process change in demand and competition for this industry's materials - processMaterialMarket(marketCycles=1) { + processMaterialMarket(): void { //References to prodMats and reqMats - var reqMats = this.reqMats, prodMats = this.prodMats; + const 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) { + for (let 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) { + const wh = this.warehouses[CorporationConstants.Cities[i]]; + for (const 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) { + for (let foo = 0; foo < prodMats.length; ++foo) { wh.materials[prodMats[foo]].processMarket(); } @@ -496,13 +496,13 @@ export class Industry { } // Process change in demand and competition for this industry's products - processProductMarket(marketCycles=1) { + processProductMarket(marketCycles=1): void { // 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 (change === 0) continue; if (this.type === Industries.Pharmaceutical || this.type === Industries.Software || this.type === Industries.Robotics) { @@ -519,20 +519,20 @@ export class Industry { //Process production, purchase, and import/export of materials processMaterials(marketCycles=1, company: any): [number, number] { - var revenue = 0, expenses = 0; + let 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]; + const city = CorporationConstants.Cities[i]; if (!(this.warehouses[city] instanceof Warehouse)) { continue; } - var warehouse = this.warehouses[city]; - for (var matName in warehouse.materials) { + const warehouse = this.warehouses[city]; + for (const matName in warehouse.materials) { if (warehouse.materials.hasOwnProperty(matName)) { - var mat = warehouse.materials[matName]; + const mat = warehouse.materials[matName]; mat.imp = 0; } } @@ -540,20 +540,20 @@ export class Industry { } for (let i = 0; i < CorporationConstants.Cities.length; ++i) { - var city = CorporationConstants.Cities[i], office = this.offices[city]; + const city = CorporationConstants.Cities[i], office = this.offices[city]; if (this.warehouses[city] instanceof Warehouse) { - var warehouse = this.warehouses[city]; + const warehouse = this.warehouses[city]; switch(this.state) { case "PURCHASE": /* Process purchase of materials */ - for (var matName in warehouse.materials) { + for (const matName in warehouse.materials) { if (warehouse.materials.hasOwnProperty(matName)) { (function(matName, ind) { - var mat = warehouse.materials[matName]; - var buyAmt, maxAmt; + const mat = warehouse.materials[matName]; + let 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; @@ -583,10 +583,10 @@ export class Industry { /* Process production of materials */ if (this.prodMats.length > 0) { - var mat = warehouse.materials[this.prodMats[0]]; + const mat = warehouse.materials[this.prodMats[0]]; //Calculate the maximum production of this material based //on the office's productivity - var maxProd = this.getOfficeProductivity(office) + const maxProd = this.getOfficeProductivity(office) * this.prodMult // Multiplier from materials * company.getProductionMultiplier() * this.getProductionMultiplier(); // Multiplier from Research @@ -601,17 +601,17 @@ export class Industry { 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; + let 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]; + const 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); + const maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize); prod = Math.min(maxAmt, prod); } @@ -621,10 +621,10 @@ export class Industry { 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) { + let producableFrac = 1; + for (const reqMatName in this.reqMats) { if (this.reqMats.hasOwnProperty(reqMatName)) { - var req = this.reqMats[reqMatName] * prod; + const req = this.reqMats[reqMatName] * prod; if (warehouse.materials[reqMatName].qty < req) { producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); } @@ -635,7 +635,7 @@ export class Industry { // 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); + const reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac); warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; warehouse.materials[reqMatName].prd = 0; warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (CorporationConstants.SecsPerMarketCycle * marketCycles); @@ -672,9 +672,9 @@ export class Industry { case "SALE": /* Process sale of materials */ - for (var matName in warehouse.materials) { + for (const matName in warehouse.materials) { if (warehouse.materials.hasOwnProperty(matName)) { - var mat = warehouse.materials[matName]; + const mat = warehouse.materials[matName]; if (mat.sCost < 0 || mat.sllman[0] === false) { mat.sll = 0; continue; @@ -687,7 +687,7 @@ export class Industry { // Determine the cost that the material will be sold at const markupLimit = mat.getMarkupLimit(); - var sCost; + let sCost; if (mat.marketTa2) { const prod = mat.prd; @@ -746,17 +746,17 @@ export class Industry { } } - var maxSell = (mat.qlt + .001) + const maxSell = (mat.qlt + .001) * marketFactor * markup * businessFactor * company.getSalesMultiplier() * advertisingFactor * this.getSalesMultiplier(); - var sellAmt; + let sellAmt; if (isString(mat.sllman[1])) { //Dynamically evaluated - var tmp = mat.sllman[1].replace(/MAX/g, maxSell); + let tmp = mat.sllman[1].replace(/MAX/g, maxSell); tmp = tmp.replace(/PROD/g, mat.prd); try { sellAmt = eval(tmp); @@ -794,13 +794,13 @@ export class Industry { break; case "EXPORT": - for (var matName in warehouse.materials) { + for (const matName in warehouse.materials) { if (warehouse.materials.hasOwnProperty(matName)) { - var mat = warehouse.materials[matName]; + const 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)); + for (let expI = 0; expI < mat.exp.length; ++expI) { + const exp = mat.exp[expI]; + let amt = exp.amt.replace(/MAX/g, mat.qty / (CorporationConstants.SecsPerMarketCycle * marketCycles)); try { amt = eval(amt); } catch(e) { @@ -822,10 +822,10 @@ export class Industry { if (amt === 0) { break; //None left } - for (var foo = 0; foo < company.divisions.length; ++foo) { + for (let 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]; + const expIndustry = company.divisions[foo]; + const expWarehouse = expIndustry.warehouses[exp.city]; if (!(expWarehouse instanceof Warehouse)) { console.error(`Invalid export! ${expIndustry.name} ${exp.city}`); break; @@ -837,7 +837,7 @@ export class Industry { // affect revenue so just return 0's return [0, 0]; } else { - var maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]); + const maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]); amt = Math.min(maxAmt, amt); } expWarehouse.materials[matName].imp += (amt / (CorporationConstants.SecsPerMarketCycle * marketCycles)); @@ -881,7 +881,8 @@ export class Industry { //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; + let revenue = 0; + const expenses = 0; //Create products if (this.state === "PRODUCTION") { @@ -913,9 +914,9 @@ export class Industry { } //Produce Products - for (var prodName in this.products) { + for (const prodName in this.products) { if (this.products.hasOwnProperty(prodName)) { - var prod = this.products[prodName]; + const prod = this.products[prodName]; if (prod instanceof Product && prod.fin) { revenue += this.processProduct(marketCycles, prod, corporation); } @@ -928,14 +929,14 @@ export class Industry { 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]; + const 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}) + const maxProd = this.getOfficeProductivity(office, {forProduct:true}) * corporation.getProductionMultiplier() * this.prodMult // Multiplier from materials * this.getProductionMultiplier() // Multiplier from research @@ -951,27 +952,27 @@ export class Industry { 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) { + let netStorageSize = product.siz; + for (const reqMatName in product.reqMats) { if (product.reqMats.hasOwnProperty(reqMatName)) { - var normQty = product.reqMats[reqMatName]; + const 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); + const 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) { + let producableFrac = 1; + for (const reqMatName in product.reqMats) { if (product.reqMats.hasOwnProperty(reqMatName)) { - var req = product.reqMats[reqMatName] * prod; + const req = product.reqMats[reqMatName] * prod; if (warehouse.materials[reqMatName].qty < req) { producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); } @@ -980,9 +981,9 @@ export class Industry { //Make our Products if they are producable if (producableFrac > 0 && prod > 0) { - for (var reqMatName in product.reqMats) { + for (const reqMatName in product.reqMats) { if (product.reqMats.hasOwnProperty(reqMatName)) { - var reqMatQtyNeeded = (product.reqMats[reqMatName] * prod * producableFrac); + const reqMatQtyNeeded = (product.reqMats[reqMatName] * prod * producableFrac); warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (CorporationConstants.SecsPerMarketCycle * marketCycles); } @@ -998,7 +999,7 @@ export class Industry { case "SALE": { //Process sale of Products product.pCost = 0; //Estimated production cost - for (var reqMatName in product.reqMats) { + for (const reqMatName in product.reqMats) { if (product.reqMats.hasOwnProperty(reqMatName)) { product.pCost += (product.reqMats[reqMatName] * warehouse.materials[reqMatName].bCost); } @@ -1014,7 +1015,7 @@ export class Industry { // Calculate Sale Cost (sCost), which could be dynamically evaluated const markupLimit = product.rat / product.mku; - var sCost; + let sCost; if (product.marketTa2) { const prod = product.data[city][1]; @@ -1062,14 +1063,14 @@ export class Industry { sCost = product.sCost; } - var markup = 1; + let markup = 1; if (sCost > product.pCost) { if ((sCost - product.pCost) > markupLimit) { markup = markupLimit / (sCost - product.pCost); } } - var maxSell = 0.5 + const maxSell = 0.5 * Math.pow(product.rat, 0.65) * marketFactor * corporation.getSalesMultiplier() @@ -1077,10 +1078,10 @@ export class Industry { * businessFactor * advertisingFactor * this.getSalesMultiplier(); - var sellAmt; + let 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); + let tmp = product.sllman[city][1].replace(/MAX/g, maxSell); tmp = tmp.replace(/PROD/g, product.data[city][1]); try { tmp = eval(tmp); @@ -1124,7 +1125,7 @@ export class Industry { } discontinueProduct(product: Product): void { - for (var productName in this.products) { + for (const productName in this.products) { if (this.products.hasOwnProperty(productName)) { if (product === this.products[productName]) { delete this.products[productName]; @@ -1133,7 +1134,7 @@ export class Industry { } } - upgrade(upgrade: IndustryUpgrade, refs: {corporation: any, office: OfficeSpace}): void { + upgrade(upgrade: IndustryUpgrade, refs: {corporation: any; office: OfficeSpace}): void { const corporation = refs.corporation; const office = refs.office; const upgN = upgrade[0]; @@ -1141,21 +1142,24 @@ export class Industry { ++this.upgrades[upgN]; switch (upgN) { - case 0: //Coffee, 5% energy per employee + 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, + } + 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: + } + default: { console.error(`Un-implemented function index: ${upgN}`); break; + } } } @@ -1205,7 +1209,7 @@ export class Industry { } //Returns a multiplier based on a materials demand and competition that affects sales - getMarketFactor(mat: {dmd: number, cmp: number}): number { + getMarketFactor(mat: {dmd: number; cmp: number}): number { return Math.max(0.1, mat.dmd * (100 - mat.cmp) / 100); } diff --git a/src/Corporation/IndustryData.tsx b/src/Corporation/IndustryData.tsx index 250a53be4..6f006a6a1 100644 --- a/src/Corporation/IndustryData.tsx +++ b/src/Corporation/IndustryData.tsx @@ -2,8 +2,6 @@ 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 { diff --git a/src/Corporation/ui/ExportPopup.tsx b/src/Corporation/ui/ExportPopup.tsx index 9431ad116..d4fe7bca7 100644 --- a/src/Corporation/ui/ExportPopup.tsx +++ b/src/Corporation/ui/ExportPopup.tsx @@ -1,12 +1,6 @@ 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; @@ -90,8 +84,7 @@ amount to 'MAX' to export all of the materials in this warehouse.

{ - allCities.map((cityName: string) => - ) + allCities.map((cityName: string) => ) } diff --git a/src/Corporation/ui/MaterialMarketTaPopup.tsx b/src/Corporation/ui/MaterialMarketTaPopup.tsx index 99febeeae..fa7a4273c 100644 --- a/src/Corporation/ui/MaterialMarketTaPopup.tsx +++ b/src/Corporation/ui/MaterialMarketTaPopup.tsx @@ -1,12 +1,4 @@ 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 { diff --git a/src/Corporation/ui/NewIndustryPopup.tsx b/src/Corporation/ui/NewIndustryPopup.tsx index 811216cdc..8814938f9 100644 --- a/src/Corporation/ui/NewIndustryPopup.tsx +++ b/src/Corporation/ui/NewIndustryPopup.tsx @@ -1,12 +1,6 @@ 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, @@ -22,9 +16,7 @@ interface IProps { // 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 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(''); @@ -74,8 +66,7 @@ export function NewIndustryPopup(props: IProps): React.ReactElement {

Create a new division to expand into a new industry:

{IndustryDescriptions[industry]}

diff --git a/src/Corporation/ui/ProductMarketTaPopup.tsx b/src/Corporation/ui/ProductMarketTaPopup.tsx index 04b254e84..7b2d288b1 100644 --- a/src/Corporation/ui/ProductMarketTaPopup.tsx +++ b/src/Corporation/ui/ProductMarketTaPopup.tsx @@ -1,12 +1,4 @@ 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 { diff --git a/src/Corporation/ui/PurchaseMaterialPopup.tsx b/src/Corporation/ui/PurchaseMaterialPopup.tsx index 891de894c..9156d20fd 100644 --- a/src/Corporation/ui/PurchaseMaterialPopup.tsx +++ b/src/Corporation/ui/PurchaseMaterialPopup.tsx @@ -1,12 +1,6 @@ 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"; @@ -85,7 +79,7 @@ function BulkPurchase(props: IProps): React.ReactElement { (all at once).

- + ); } diff --git a/src/Corporation/ui/ResearchPopup.tsx b/src/Corporation/ui/ResearchPopup.tsx index 36bce633a..eb63a54a9 100644 --- a/src/Corporation/ui/ResearchPopup.tsx +++ b/src/Corporation/ui/ResearchPopup.tsx @@ -1,18 +1,10 @@ -import React, { useState, useEffect } from 'react'; -import { Warehouse } from "../Warehouse"; +import React, { useEffect } from 'react'; 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 { diff --git a/src/Corporation/ui/SellMaterialPopup.tsx b/src/Corporation/ui/SellMaterialPopup.tsx index 1859ed103..cc79c2d21 100644 --- a/src/Corporation/ui/SellMaterialPopup.tsx +++ b/src/Corporation/ui/SellMaterialPopup.tsx @@ -1,12 +1,6 @@ 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 : ''; diff --git a/src/Corporation/ui/SellProductPopup.tsx b/src/Corporation/ui/SellProductPopup.tsx index 72ea3556a..ae4a40270 100644 --- a/src/Corporation/ui/SellProductPopup.tsx +++ b/src/Corporation/ui/SellProductPopup.tsx @@ -1,12 +1,6 @@ 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 { @@ -40,9 +34,9 @@ export function SellProductPopup(props: IProps): React.ReactElement { 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, ''); + let price = px.replace(/\s+/g, ''); price = price.replace(/[^-()\d/*+.MP]/g, ''); - var temp = price.replace(/MP/g, '1'); + let temp = price.replace(/MP/g, '1'); try { temp = eval(temp); } catch(e) { @@ -55,7 +49,7 @@ export function SellProductPopup(props: IProps): React.ReactElement { } props.product.sCost = price; //Use sanitized price } else { - var cost = parseFloat(px); + const cost = parseFloat(px); if (isNaN(cost)) { dialogBoxCreate("Invalid value for sell price field"); return; @@ -69,9 +63,9 @@ export function SellProductPopup(props: IProps): React.ReactElement { // 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, ''); + let qty = iQty.replace(/\s+/g, ''); qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); - var temp = qty.replace(/MAX/g, '1'); + let temp = qty.replace(/MAX/g, '1'); temp = temp.replace(/PROD/g, '1'); try { temp = eval(temp); From 68885ceff57158bf09b4c003f104ff66a20e57be Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 31 Aug 2021 03:18:44 -0400 Subject: [PATCH 21/30] more conversion --- src/Corporation/ICorporation.ts | 61 +++++++++++++++++++++++++++++++ src/Corporation/ui/CityTabs.tsx | 6 ++- src/Corporation/ui/HeaderTabs.tsx | 12 ++++-- src/Corporation/ui/Industry.tsx | 6 ++- src/Corporation/ui/MainPanel.tsx | 8 ++-- src/Corporation/ui/Overview.tsx | 3 +- src/Corporation/ui/Root.tsx | 8 ++-- 7 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 src/Corporation/ICorporation.ts diff --git a/src/Corporation/ICorporation.ts b/src/Corporation/ICorporation.ts new file mode 100644 index 000000000..775774458 --- /dev/null +++ b/src/Corporation/ICorporation.ts @@ -0,0 +1,61 @@ +import { Industry } from "./Industry"; +import { IPlayer } from "../PersonObjects/IPlayer"; +import { CorporationUnlockUpgrade } from "./data/CorporationUnlockUpgrades"; +import { CorporationUpgrade } from "./data/CorporationUpgrades"; +import { CorporationState } from "./CorporationState"; + +export interface ICorporation { + name: string; + + divisions: Industry[]; + + funds: any; + revenue: any; + expenses: any; + fundingRound: number; + public: boolean; + totalShares: number; + numShares: number; + shareSalesUntilPriceUpdate: number; + shareSaleCooldown: number; + issueNewSharesCooldown: number; + dividendPercentage: number; + dividendTaxPercentage: number; + issuedShares: number; + sharePrice: number; + storedCycles: number; + + unlockUpgrades: number[]; + upgrades: number[]; + upgradeMultipliers: number[]; + + state: CorporationState; + + addFunds(amt: number): void; + getState(): string; + storeCycles(numCycles: number): void; + process(player: IPlayer): void; + determineValuation(): number; + getTargetSharePrice(): number; + updateSharePrice(): void; + immediatelyUpdateSharePrice(): void; + calculateShareSale(numShares: number): [number, number, number]; + convertCooldownToString(cd: number): string; + unlock(upgrade: CorporationUnlockUpgrade): void; + upgrade(upgrade: CorporationUpgrade): void; + getProductionMultiplier(): number; + getStorageMultiplier(): number; + getDreamSenseGain(): number; + getAdvertisingMultiplier(): number; + getEmployeeCreMultiplier(): number; + getEmployeeChaMultiplier(): number; + getEmployeeIntMultiplier(): number; + getEmployeeEffMultiplier(): number; + getSalesMultiplier(): number; + getScientificResearchMultiplier(): number; + getStarterGuide(player: IPlayer): void; + createUI(player: IPlayer): void; + rerender(player: IPlayer): void; + clearUI(): void; + toJSON(): any; +} \ No newline at end of file diff --git a/src/Corporation/ui/CityTabs.tsx b/src/Corporation/ui/CityTabs.tsx index 6e2255d8a..347d6b8e2 100644 --- a/src/Corporation/ui/CityTabs.tsx +++ b/src/Corporation/ui/CityTabs.tsx @@ -5,13 +5,15 @@ import { CityTab } from "./CityTab"; import { ExpandNewCityPopup } from "./ExpandNewCityPopup"; import { createPopup } from "../../ui/React/createPopup"; import { IDivision } from "../IDivision"; +import { ICorporation } from "../ICorporation"; +import { CorporationRouting } from "./Routing"; interface IProps { - routing: any; + routing: CorporationRouting; onClicks: {[key: string]: () => void}; city: string; // currentCity cityStateSetter: (city: string) => void; - corp: any; + corp: ICorporation; } export function CityTabs(props: IProps): React.ReactElement { diff --git a/src/Corporation/ui/HeaderTabs.tsx b/src/Corporation/ui/HeaderTabs.tsx index 28d61c6c7..3dff46d4d 100644 --- a/src/Corporation/ui/HeaderTabs.tsx +++ b/src/Corporation/ui/HeaderTabs.tsx @@ -6,16 +6,20 @@ import { HeaderTab } from "./HeaderTab"; import { IDivision } from "../IDivision"; import { NewIndustryPopup } from "./NewIndustryPopup"; import { createPopup } from "../../ui/React/createPopup"; +import { ICorporation } from "../ICorporation"; +import { CorporationRouting } from "./Routing"; +import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - corp: any; - routing: any; + corp: ICorporation; + routing: CorporationRouting; + player: IPlayer; } export function HeaderTabs(props: IProps): React.ReactElement { function overviewOnClick(): void { props.routing.routeToOverviewPage(); - props.corp.rerender(); + props.corp.rerender(props.player); } function openNewIndustryPopup(): void { @@ -41,7 +45,7 @@ export function HeaderTabs(props: IProps): React.ReactElement { key={division.name} onClick={() => { props.routing.routeTo(division.name); - props.corp.rerender(); + props.corp.rerender(props.player); }} text={division.name} />) diff --git a/src/Corporation/ui/Industry.tsx b/src/Corporation/ui/Industry.tsx index e68fcd2d4..5254c3676 100644 --- a/src/Corporation/ui/Industry.tsx +++ b/src/Corporation/ui/Industry.tsx @@ -5,10 +5,12 @@ import React from "react"; import { IndustryOffice } from "./IndustryOffice"; import { IndustryOverview } from "./IndustryOverview"; import { IndustryWarehouse } from "./IndustryWarehouse"; +import { ICorporation } from "../ICorporation"; +import { CorporationRouting } from "./Routing"; interface IProps { - routing: any; - corp: any; + routing: CorporationRouting; + corp: ICorporation; currentCity: string; } diff --git a/src/Corporation/ui/MainPanel.tsx b/src/Corporation/ui/MainPanel.tsx index c1ca735da..aa353291a 100644 --- a/src/Corporation/ui/MainPanel.tsx +++ b/src/Corporation/ui/MainPanel.tsx @@ -11,10 +11,12 @@ import { OfficeSpace } from "../OfficeSpace"; import { CityName } from "../../Locations/data/CityNames"; import { IPlayer } from "../../PersonObjects/IPlayer"; +import { ICorporation } from "../ICorporation"; +import { CorporationRouting } from "./Routing"; interface IProps { - corp: any; - routing: any; + corp: ICorporation; + routing: CorporationRouting; player: IPlayer; } @@ -52,7 +54,7 @@ export function MainPanel(props: IProps): React.ReactElement { if (division.offices[cityName] instanceof OfficeSpace) { onClicks[cityName] = () => { setCity(cityName); - props.corp.rerender(); + props.corp.rerender(props.player); } } } diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx index bd534175d..b44e58032 100644 --- a/src/Corporation/ui/Overview.tsx +++ b/src/Corporation/ui/Overview.tsx @@ -19,9 +19,10 @@ import { numeralWrapper } from "../../ui/numeralFormat"; import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions"; import { createPopup } from "../../ui/React/createPopup"; import { IPlayer } from "../../PersonObjects/IPlayer"; +import { ICorporation } from "../ICorporation"; interface IProps { - corp: any; + corp: ICorporation; player: IPlayer; } diff --git a/src/Corporation/ui/Root.tsx b/src/Corporation/ui/Root.tsx index f41b93a27..f253e0157 100644 --- a/src/Corporation/ui/Root.tsx +++ b/src/Corporation/ui/Root.tsx @@ -4,17 +4,19 @@ import React from "react"; import { HeaderTabs } from "./HeaderTabs"; import { MainPanel } from "./MainPanel"; import { IPlayer } from "../../PersonObjects/IPlayer"; +import { ICorporation } from "../ICorporation"; +import { CorporationRouting } from "./Routing"; interface IProps { - corp: any; - routing: any; + corp: ICorporation; + routing: CorporationRouting; player: IPlayer; } export function CorporationRoot(props: IProps): React.ReactElement { return (
- +
) From a2379b21ec68a5dd3ebb4916d419c2169dff8b31 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 31 Aug 2021 03:39:04 -0400 Subject: [PATCH 22/30] more conversion --- src/Corporation/Employee.ts | 5 ++- src/Corporation/Industry.ts | 31 ++++++------- src/Corporation/ui/BribeFactionPopup.tsx | 5 ++- src/Corporation/ui/BuybackSharesPopup.tsx | 7 +-- .../ui/DiscontinueProductPopup.tsx | 7 ++- src/Corporation/ui/ExpandNewCityPopup.tsx | 3 +- src/Corporation/ui/ExportPopup.tsx | 3 +- src/Corporation/ui/FindInvestorsPopup.tsx | 7 ++- src/Corporation/ui/GoPublicPopup.tsx | 7 ++- src/Corporation/ui/Industry.tsx | 7 ++- src/Corporation/ui/IndustryOffice.tsx | 44 ++++++++++--------- src/Corporation/ui/IndustryOverview.tsx | 7 ++- src/Corporation/ui/IndustryWarehouse.tsx | 14 ++++-- src/Corporation/ui/IssueDividendsPopup.tsx | 3 +- src/Corporation/ui/IssueNewSharesPopup.tsx | 3 +- src/Corporation/ui/LevelableUpgrade.tsx | 10 +++-- src/Corporation/ui/MakeProductPopup.tsx | 3 +- src/Corporation/ui/NewIndustryPopup.tsx | 3 +- src/Corporation/ui/Overview.tsx | 4 ++ src/Corporation/ui/SellMaterialPopup.tsx | 3 +- src/Corporation/ui/SellSharesPopup.tsx | 7 +-- src/Corporation/ui/ThrowPartyPopup.tsx | 3 +- src/Corporation/ui/UnlockUpgrade.tsx | 7 ++- src/Corporation/ui/UpgradeOfficeSizePopup.tsx | 7 ++- 24 files changed, 127 insertions(+), 73 deletions(-) diff --git a/src/Corporation/Employee.ts b/src/Corporation/Employee.ts index b69aae632..d5053ff24 100644 --- a/src/Corporation/Employee.ts +++ b/src/Corporation/Employee.ts @@ -3,6 +3,7 @@ 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 { ICorporation } from "./ICorporation"; import { numeralWrapper } from "../ui/numeralFormat"; import { formatNumber } from "../../utils/StringHelperFunctions"; @@ -86,7 +87,7 @@ export class Employee { return salary; } - calculateProductivity(corporation: any, industry: any): number { + calculateProductivity(corporation: ICorporation, 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(), @@ -137,7 +138,7 @@ export class Employee { } //'panel' is the DOM element on which to create the UI - createUI(panel: any, corporation: any, industry: any): void { + createUI(panel: any, corporation: ICorporation, 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/Industry.ts b/src/Corporation/Industry.ts index ebdb96835..18e3dbd11 100644 --- a/src/Corporation/Industry.ts +++ b/src/Corporation/Industry.ts @@ -19,6 +19,7 @@ import { dialogBoxCreate } from "../../utils/DialogBox"; import { isString } from "../../utils/helpers/isString"; import { MaterialSizes } from "./MaterialSizes"; import { Warehouse } from "./Warehouse"; +import { ICorporation } from "./ICorporation"; import { IndustryUpgrade, IndustryUpgrades } from "./IndustryUpgrades"; @@ -401,7 +402,7 @@ export class Industry { } } - process(marketCycles=1, state: string, company: any): void { + process(marketCycles=1, state: string, corporation: ICorporation): void { this.state = state; //At the start of a cycle, store and reset revenue/expenses @@ -426,7 +427,7 @@ export class Industry { let employeeSalary = 0; for (const officeLoc in this.offices) { if (this.offices[officeLoc] instanceof OfficeSpace) { - employeeSalary += this.offices[officeLoc].process(marketCycles, {industry:this, corporation:company}); + employeeSalary += this.offices[officeLoc].process(marketCycles, {industry:this, corporation:corporation}); } } this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary); @@ -440,7 +441,7 @@ export class Industry { this.popularity = Math.max(0, this.popularity); // Process Dreamsense gains - const popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4; + const popularityGain = corporation.getDreamSenseGain(), awarenessGain = popularityGain * 4; if (popularityGain > 0) { this.popularity += (popularityGain * marketCycles); this.awareness += (awarenessGain * marketCycles); @@ -450,14 +451,14 @@ export class Industry { } // Process production, purchase, and import/export of materials - let res = this.processMaterials(marketCycles, company); + let res = this.processMaterials(marketCycles, corporation); 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); + res = this.processProducts(marketCycles, corporation); if (Array.isArray(res)) { this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); @@ -518,7 +519,7 @@ export class Industry { } //Process production, purchase, and import/export of materials - processMaterials(marketCycles=1, company: any): [number, number] { + processMaterials(marketCycles=1, corporation: ICorporation): [number, number] { let revenue = 0, expenses = 0; this.calculateProductionFactors(); @@ -588,7 +589,7 @@ export class Industry { //on the office's productivity const maxProd = this.getOfficeProductivity(office) * this.prodMult // Multiplier from materials - * company.getProductionMultiplier() + * corporation.getProductionMultiplier() * this.getProductionMultiplier(); // Multiplier from Research let prod; @@ -700,7 +701,7 @@ export class Industry { const sqrtDenominator = ((mat.qlt + .001) * marketFactor * businessFactor - * company.getSalesMultiplier() + * corporation.getSalesMultiplier() * advertisingFactor * this.getSalesMultiplier()); const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator); @@ -750,7 +751,7 @@ export class Industry { * marketFactor * markup * businessFactor - * company.getSalesMultiplier() + * corporation.getSalesMultiplier() * advertisingFactor * this.getSalesMultiplier(); let sellAmt; @@ -822,9 +823,9 @@ export class Industry { if (amt === 0) { break; //None left } - for (let foo = 0; foo < company.divisions.length; ++foo) { - if (company.divisions[foo].name === exp.ind) { - const expIndustry = company.divisions[foo]; + for (let foo = 0; foo < corporation.divisions.length; ++foo) { + if (corporation.divisions[foo].name === exp.ind) { + const expIndustry = corporation.divisions[foo]; const expWarehouse = expIndustry.warehouses[exp.city]; if (!(expWarehouse instanceof Warehouse)) { console.error(`Invalid export! ${expIndustry.name} ${exp.city}`); @@ -872,7 +873,7 @@ export class Industry { if (office instanceof OfficeSpace) { this.sciResearch.qty += (.004 * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5) - * company.getScientificResearchMultiplier() + * corporation.getScientificResearchMultiplier() * this.getScientificResearchMultiplier()); } } @@ -880,7 +881,7 @@ export class Industry { } //Process production & sale of this industry's FINISHED products (including all of their stats) - processProducts(marketCycles=1, corporation: any): [number, number] { + processProducts(marketCycles=1, corporation: ICorporation): [number, number] { let revenue = 0; const expenses = 0; @@ -926,7 +927,7 @@ export class Industry { } //Processes FINISHED products - processProduct(marketCycles=1, product: Product, corporation: any): number { + processProduct(marketCycles=1, product: Product, corporation: ICorporation): number { let totalProfit = 0; for (let i = 0; i < CorporationConstants.Cities.length; ++i) { const city = CorporationConstants.Cities[i], office = this.offices[city], warehouse = this.warehouses[city]; diff --git a/src/Corporation/ui/BribeFactionPopup.tsx b/src/Corporation/ui/BribeFactionPopup.tsx index e50ebfb11..3ded57c1c 100644 --- a/src/Corporation/ui/BribeFactionPopup.tsx +++ b/src/Corporation/ui/BribeFactionPopup.tsx @@ -5,10 +5,11 @@ import { CorporationConstants } from "../data/Constants"; import { numeralWrapper } from "../../ui/numeralFormat"; import { removePopup } from "../../ui/React/createPopup"; import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { ICorporation } from "../ICorporation"; interface IProps { popupId: string; - corp: any; + corp: ICorporation; player: IPlayer; } @@ -39,7 +40,7 @@ export function BribeFactionPopup(props: IProps): React.ReactElement { 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) { + } else if (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)) + diff --git a/src/Corporation/ui/BuybackSharesPopup.tsx b/src/Corporation/ui/BuybackSharesPopup.tsx index 6b45d87ac..eda57a330 100644 --- a/src/Corporation/ui/BuybackSharesPopup.tsx +++ b/src/Corporation/ui/BuybackSharesPopup.tsx @@ -3,11 +3,12 @@ import { IPlayer } from "../../PersonObjects/IPlayer"; import { removePopup } from "../../ui/React/createPopup"; import { numeralWrapper } from "../../ui/numeralFormat"; import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { ICorporation } from "../ICorporation"; interface IProps { player: IPlayer; popupId: string; - corp: any; + corp: ICorporation; } // Create a popup that lets the player buyback shares @@ -39,7 +40,7 @@ export function BuybackSharesPopup(props: IProps): React.ReactElement { 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); + const res = props.corp.issuedShares; if (isNaN(res)) { props.corp.issuedShares = 0; } else { @@ -49,7 +50,7 @@ export function BuybackSharesPopup(props: IProps): React.ReactElement { props.corp.issuedShares -= shares; props.player.loseMoney(shares * buybackPrice); removePopup(props.popupId); - props.corp.rerender(); + props.corp.rerender(props.player); } } diff --git a/src/Corporation/ui/DiscontinueProductPopup.tsx b/src/Corporation/ui/DiscontinueProductPopup.tsx index d22b139f3..bc81a7567 100644 --- a/src/Corporation/ui/DiscontinueProductPopup.tsx +++ b/src/Corporation/ui/DiscontinueProductPopup.tsx @@ -1,11 +1,14 @@ import React from 'react'; import { removePopup } from "../../ui/React/createPopup"; +import { ICorporation } from "../ICorporation"; +import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { product: any; industry: any; - corp: any; + corp: ICorporation; popupId: string; + player: IPlayer; } // Create a popup that lets the player discontinue a product @@ -13,7 +16,7 @@ export function DiscontinueProductPopup(props: IProps): React.ReactElement { function discontinue(): void { props.industry.discontinueProduct(props.product); removePopup(props.popupId); - props.corp.rerender(); + props.corp.rerender(props.player); } return (<> diff --git a/src/Corporation/ui/ExpandNewCityPopup.tsx b/src/Corporation/ui/ExpandNewCityPopup.tsx index c9d91087f..ba02f6c81 100644 --- a/src/Corporation/ui/ExpandNewCityPopup.tsx +++ b/src/Corporation/ui/ExpandNewCityPopup.tsx @@ -5,10 +5,11 @@ import { CorporationConstants } from "../data/Constants"; import { removePopup } from "../../ui/React/createPopup"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { OfficeSpace } from "../OfficeSpace"; +import { ICorporation } from "../ICorporation"; interface IProps { popupId: string; - corp: any; + corp: ICorporation; division: IDivision; cityStateSetter: (city: string) => void; } diff --git a/src/Corporation/ui/ExportPopup.tsx b/src/Corporation/ui/ExportPopup.tsx index d4fe7bca7..39e5936be 100644 --- a/src/Corporation/ui/ExportPopup.tsx +++ b/src/Corporation/ui/ExportPopup.tsx @@ -1,10 +1,11 @@ import React, { useState } from 'react'; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; +import { ICorporation } from "../ICorporation"; interface IProps { mat: any; - corp: any; + corp: ICorporation; popupId: string; } diff --git a/src/Corporation/ui/FindInvestorsPopup.tsx b/src/Corporation/ui/FindInvestorsPopup.tsx index ee10eb87b..3c52331ba 100644 --- a/src/Corporation/ui/FindInvestorsPopup.tsx +++ b/src/Corporation/ui/FindInvestorsPopup.tsx @@ -2,10 +2,13 @@ import React from 'react'; import { removePopup } from "../../ui/React/createPopup"; import { numeralWrapper } from "../../ui/numeralFormat"; import { CorporationConstants } from "../data/Constants"; +import { ICorporation } from "../ICorporation"; +import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - corp: any; + corp: ICorporation; popupId: string; + player: IPlayer; } // Create a popup that lets the player manage exports @@ -40,7 +43,7 @@ export function FindInvestorsPopup(props: IProps): React.ReactElement { props.corp.fundingRound++; props.corp.addFunds(funding); props.corp.numShares -= investShares; - props.corp.rerender(); + props.corp.rerender(props.player); removePopup(props.popupId); } return (<> diff --git a/src/Corporation/ui/GoPublicPopup.tsx b/src/Corporation/ui/GoPublicPopup.tsx index 60f874d80..305c502d4 100644 --- a/src/Corporation/ui/GoPublicPopup.tsx +++ b/src/Corporation/ui/GoPublicPopup.tsx @@ -2,10 +2,13 @@ import React, { useState } from 'react'; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { numeralWrapper } from "../../ui/numeralFormat"; +import { ICorporation } from "../ICorporation"; +import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - corp: any; + corp: ICorporation; popupId: string; + player: IPlayer; } // Create a popup that lets the player manage exports @@ -29,7 +32,7 @@ export function GoPublicPopup(props: IProps): React.ReactElement { props.corp.issuedShares = numShares; props.corp.numShares -= numShares; props.corp.addFunds(numShares * initialSharePrice); - props.corp.rerender(); + props.corp.rerender(props.player); dialogBoxCreate(`You took your ${props.corp.name} public and earned ` + `${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`); removePopup(props.popupId); diff --git a/src/Corporation/ui/Industry.tsx b/src/Corporation/ui/Industry.tsx index 5254c3676..c96596212 100644 --- a/src/Corporation/ui/Industry.tsx +++ b/src/Corporation/ui/Industry.tsx @@ -7,11 +7,13 @@ import { IndustryOverview } from "./IndustryOverview"; import { IndustryWarehouse } from "./IndustryWarehouse"; import { ICorporation } from "../ICorporation"; import { CorporationRouting } from "./Routing"; +import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { routing: CorporationRouting; corp: ICorporation; currentCity: string; + player: IPlayer; } export function Industry(props: IProps): React.ReactElement { @@ -19,16 +21,19 @@ export function Industry(props: IProps): React.ReactElement {
-
diff --git a/src/Corporation/ui/IndustryOffice.tsx b/src/Corporation/ui/IndustryOffice.tsx index a88fb883e..ac6e701b3 100644 --- a/src/Corporation/ui/IndustryOffice.tsx +++ b/src/Corporation/ui/IndustryOffice.tsx @@ -12,11 +12,14 @@ import { getSelectText } from "../../../utils/uiHelpers/getSelectData import { createPopup } from "../../ui/React/createPopup"; import { UpgradeOfficeSizePopup } from "./UpgradeOfficeSizePopup"; import { ThrowPartyPopup } from "./ThrowPartyPopup"; +import { ICorporation } from "../ICorporation"; +import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { routing: any; - corp: any; + corp: ICorporation; currentCity: string; + player: IPlayer; } export function IndustryOffice(props: IProps): React.ReactElement { @@ -132,7 +135,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { function switchModeOnClick(): void { setEmployeeManualAssignMode(true); - props.corp.rerender(); + props.corp.rerender(props.player); } // Calculate average morale, happiness, and energy. Also salary @@ -189,7 +192,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { office.assignEmployeeToJob(to); office.calculateEmployeeProductivity({ corporation: props.corp, industry:division }); - props.corp.rerender(); + props.corp.rerender(props.player); } function unassignEmployee(from: string): void { @@ -233,7 +236,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { office.unassignEmployeeFromJob(from); office.calculateEmployeeProductivity({ corporation: props.corp, industry:division }); - props.corp.rerender(); + props.corp.rerender(props.player); } const positionHeaderStyle = { @@ -245,61 +248,61 @@ export function IndustryOffice(props: IProps): React.ReactElement { function operationAssignButtonOnClick(): void { assignEmployee(EmployeePositions.Operations); - props.corp.rerender(); + props.corp.rerender(props.player); } function operationUnassignButtonOnClick(): void { unassignEmployee(EmployeePositions.Operations); - props.corp.rerender(); + props.corp.rerender(props.player); } const operationUnassignButtonClass = numOperations > 0 ? "std-button" : "a-link-button-inactive"; function engineerAssignButtonOnClick(): void { assignEmployee(EmployeePositions.Engineer); - props.corp.rerender(); + props.corp.rerender(props.player); } function engineerUnassignButtonOnClick(): void { unassignEmployee(EmployeePositions.Engineer); - props.corp.rerender(); + props.corp.rerender(props.player); } const engineerUnassignButtonClass = numEngineers > 0 ? "std-button" : "a-link-button-inactive"; function businessAssignButtonOnClick(): void { assignEmployee(EmployeePositions.Business); - props.corp.rerender(); + props.corp.rerender(props.player); } function businessUnassignButtonOnClick(): void { unassignEmployee(EmployeePositions.Business); - props.corp.rerender(); + props.corp.rerender(props.player); } const businessUnassignButtonClass = numBusiness > 0 ? "std-button" : "a-link-button-inactive"; function managementAssignButtonOnClick(): void { assignEmployee(EmployeePositions.Management); - props.corp.rerender(); + props.corp.rerender(props.player); } function managementUnassignButtonOnClick(): void { unassignEmployee(EmployeePositions.Management); - props.corp.rerender(); + props.corp.rerender(props.player); } const managementUnassignButtonClass = numManagement > 0 ? "std-button" : "a-link-button-inactive"; function rndAssignButtonOnClick(): void { assignEmployee(EmployeePositions.RandD); - props.corp.rerender(); + props.corp.rerender(props.player); } function rndUnassignButtonOnClick(): void { unassignEmployee(EmployeePositions.RandD); - props.corp.rerender(); + props.corp.rerender(props.player); } const rndUnassignButtonClass = numResearch > 0 ? "std-button" : "a-link-button-inactive"; function trainingAssignButtonOnClick(): void { assignEmployee(EmployeePositions.Training); - props.corp.rerender(); + props.corp.rerender(props.player); } function trainingUnassignButtonOnClick(): void { unassignEmployee(EmployeePositions.Training); - props.corp.rerender(); + props.corp.rerender(props.player); } const trainingUnassignButtonClass = numTraining > 0 ? "std-button" : "a-link-button-inactive"; @@ -433,7 +436,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { function switchModeOnClick(): void { setEmployeeManualAssignMode(false); - props.corp.rerender(); + props.corp.rerender(props.player); } const employeeInfoDivStyle = { @@ -457,7 +460,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { } } - corp.rerender(); + corp.rerender(props.player); } // Employee Positions Selector @@ -477,7 +480,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { const pos = getSelectText(e.target); employee.pos = pos; resetEmployeeCount(); - corp.rerender(); + corp.rerender(props.player); } // Numeraljs formatter @@ -570,7 +573,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { function autohireEmployeeButtonOnClick(): void { if (office.atCapacity()) return; office.hireRandomEmployee(); - props.corp.rerender(); + props.corp.rerender(props.player); } function openUpgradeOfficeSizePopup(): void { @@ -579,6 +582,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { office: office, corp: props.corp, popupId: popupId, + player: props.player, }); } diff --git a/src/Corporation/ui/IndustryOverview.tsx b/src/Corporation/ui/IndustryOverview.tsx index a8389514d..2f7ed490e 100644 --- a/src/Corporation/ui/IndustryOverview.tsx +++ b/src/Corporation/ui/IndustryOverview.tsx @@ -11,11 +11,14 @@ import { createProgressBarText } from "../../../utils/helpers/createProgressB import { MakeProductPopup } from "./MakeProductPopup"; import { ResearchPopup } from "./ResearchPopup"; import { createPopup } from "../../ui/React/createPopup"; +import { ICorporation } from "../ICorporation"; +import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { routing: any; - corp: any; + corp: ICorporation; currentCity: string; + player: IPlayer; } export function IndustryOverview(props: IProps): React.ReactElement { @@ -249,7 +252,7 @@ export function IndustryOverview(props: IProps): React.ReactElement { office: office, }); // corp.displayDivisionContent(division, city); - corp.rerender(); + corp.rerender(props.player); } } diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx index 2d8526ddb..f47cf6811 100644 --- a/src/Corporation/ui/IndustryWarehouse.tsx +++ b/src/Corporation/ui/IndustryWarehouse.tsx @@ -21,12 +21,15 @@ import { dialogBoxCreate } from "../../../utils/DialogBox"; import { createPopup } from "../../ui/React/createPopup"; import { isString } from "../../../utils/helpers/isString"; +import { ICorporation } from "../ICorporation"; +import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProductProps { - corp: any; + corp: ICorporation; division: any; city: string; product: any; + player: IPlayer; } // Creates the UI for a single Product type @@ -101,6 +104,7 @@ function ProductComponent(props: IProductProps): React.ReactElement { industry: division, corp: props.corp, popupId: popupId, + player: props.player, }); } @@ -404,6 +408,7 @@ interface IProps { corp: any; routing: any; currentCity: string; + player: IPlayer; } export function IndustryWarehouse(props: IProps): React.ReactElement { @@ -439,7 +444,7 @@ export function IndustryWarehouse(props: IProps): React.ReactElement { ++warehouse.level; warehouse.updateSize(corp, division); corp.funds = corp.funds.minus(sizeUpgradeCost); - corp.rerender(); + corp.rerender(props.player); } // Industry material Requirements @@ -503,7 +508,7 @@ export function IndustryWarehouse(props: IProps): React.ReactElement { const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox"; function smartSupplyOnChange(e: React.ChangeEvent): void { warehouse.smartSupplyEnabled = e.target.checked; - corp.rerender(); + corp.rerender(props.player); } // Create React components for materials @@ -529,6 +534,7 @@ export function IndustryWarehouse(props: IProps): React.ReactElement { for (const productName in division.products) { if (division.products[productName] instanceof Product) { products.push( { if (props.corp.unlockUpgrades[unlockData[0]] === 0) { unlockUpgrades.push( Upgrades

{ levelableUpgradeProps.map((data: any) => Date: Tue, 31 Aug 2021 13:04:33 -0400 Subject: [PATCH 23/30] No more use of any in Corporations. --- src/Corporation/Corporation.tsx | 1 - src/Corporation/Employee.ts | 8 +- src/Corporation/Export.ts | 5 ++ src/Corporation/IIndustry.ts | 78 +++++++++++++++++++ src/Corporation/Material.ts | 5 +- src/Corporation/OfficeSpace.ts | 25 +++--- .../ui/DiscontinueProductPopup.tsx | 6 +- src/Corporation/ui/ExportPopup.tsx | 15 ++-- src/Corporation/ui/IndustryOffice.tsx | 8 +- src/Corporation/ui/IndustryOverview.tsx | 20 ++++- src/Corporation/ui/IndustryWarehouse.tsx | 33 ++++---- src/Corporation/ui/IssueNewSharesPopup.tsx | 2 +- .../ui/LimitProductProductionPopup.tsx | 5 +- src/Corporation/ui/MakeProductPopup.tsx | 3 +- src/Corporation/ui/MaterialMarketTaPopup.tsx | 13 ++-- src/Corporation/ui/NewIndustryPopup.tsx | 6 +- src/Corporation/ui/Overview.tsx | 40 +++++++--- src/Corporation/ui/ProductMarketTaPopup.tsx | 10 ++- src/Corporation/ui/PurchaseMaterialPopup.tsx | 23 +++--- src/Corporation/ui/ResearchPopup.tsx | 3 +- src/Corporation/ui/Routing.ts | 30 +------ src/Corporation/ui/SellMaterialPopup.tsx | 9 ++- src/Corporation/ui/SellProductPopup.tsx | 7 +- src/Corporation/ui/SellSharesPopup.tsx | 2 +- src/Corporation/ui/UpgradeOfficeSizePopup.tsx | 2 +- 25 files changed, 240 insertions(+), 119 deletions(-) create mode 100644 src/Corporation/Export.ts create mode 100644 src/Corporation/IIndustry.ts diff --git a/src/Corporation/Corporation.tsx b/src/Corporation/Corporation.tsx index 8d615ef7b..e2de46372 100644 --- a/src/Corporation/Corporation.tsx +++ b/src/Corporation/Corporation.tsx @@ -14,7 +14,6 @@ import { IPlayer } from "../PersonObjects/I import { Page, routing } from "../ui/navigationTracking"; - import { dialogBoxCreate } from "../../utils/DialogBox"; import { Reviver, Generic_toJSON, diff --git a/src/Corporation/Employee.ts b/src/Corporation/Employee.ts index d5053ff24..2750a6c0c 100644 --- a/src/Corporation/Employee.ts +++ b/src/Corporation/Employee.ts @@ -6,6 +6,8 @@ import { EmployeePositions } from "./EmployeePositions"; import { ICorporation } from "./ICorporation"; import { numeralWrapper } from "../ui/numeralFormat"; import { formatNumber } from "../../utils/StringHelperFunctions"; +import { OfficeSpace } from "./OfficeSpace"; +import { IIndustry } from "./IIndustry"; interface IParams { name?: string; @@ -57,7 +59,7 @@ export class Employee { } //Returns the amount the employee needs to be paid - process(marketCycles = 1, office: any): number { + process(marketCycles = 1, office: OfficeSpace): number { const gain = 0.003 * marketCycles, det = gain * Math.random(); this.exp += gain; @@ -87,7 +89,7 @@ export class Employee { return salary; } - calculateProductivity(corporation: ICorporation, industry: any): number { + calculateProductivity(corporation: ICorporation, industry: IIndustry): number { const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(), effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(), effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(), @@ -138,7 +140,7 @@ export class Employee { } //'panel' is the DOM element on which to create the UI - createUI(panel: any, corporation: ICorporation, industry: any): void { + createUI(panel: HTMLElement, corporation: ICorporation, industry: IIndustry): 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/Export.ts b/src/Corporation/Export.ts new file mode 100644 index 000000000..ff4ae9ec6 --- /dev/null +++ b/src/Corporation/Export.ts @@ -0,0 +1,5 @@ +export interface Export { + ind: string; + city: string; + amt: string; +} \ No newline at end of file diff --git a/src/Corporation/IIndustry.ts b/src/Corporation/IIndustry.ts new file mode 100644 index 000000000..37368f464 --- /dev/null +++ b/src/Corporation/IIndustry.ts @@ -0,0 +1,78 @@ +import { Material } from "./Material"; +import { Warehouse } from "./Warehouse"; +import { ICorporation } from "./ICorporation"; +import { OfficeSpace } from "./OfficeSpace"; +import { Product } from "./Product"; +import { IndustryUpgrade } from "./IndustryUpgrades"; + +export interface IIndustry { + name: string; + type: string; + sciResearch: Material; + researched: any; + reqMats: any; + + prodMats: string[]; + + products: any; + makesProducts: boolean; + + awareness: number; + popularity: number; + startingCost: number; + + reFac: number; + sciFac: number; + hwFac: number; + robFac: number; + aiFac: number; + advFac: number; + + prodMult: number; + + // Decimal + lastCycleRevenue: any; + lastCycleExpenses: any; + thisCycleRevenue: any; + thisCycleExpenses: any; + + upgrades: number[]; + + state: string; + newInd: boolean; + warehouses: any; + offices: any; + + + init(): void; + getProductDescriptionText(): string; + getMaximumNumberProducts(): number; + hasMaximumNumberProducts(): boolean; + calculateProductionFactors(): void; + updateWarehouseSizeUsed(warehouse: Warehouse): void; + process(marketCycles: number, state: string, corporation: ICorporation): void; + processMaterialMarket(): void; + processProductMarket(marketCycles: number): void; + processMaterials(marketCycles: number, corporation: ICorporation): [number, number]; + processProducts(marketCycles: number, corporation: ICorporation): [number, number]; + processProduct(marketCycles: number, product: Product, corporation: ICorporation): number; + discontinueProduct(product: Product): void; + upgrade(upgrade: IndustryUpgrade, refs: {corporation: any; office: OfficeSpace}): void; + getOfficeProductivity(office: OfficeSpace, params?: any): number; + getBusinessFactor(office: OfficeSpace): number; + getAdvertisingFactors(): [number, number, number, number]; + getMarketFactor(mat: {dmd: number; cmp: number}): number; + hasResearch(name: string): boolean; + updateResearchTree(): void; + getAdvertisingMultiplier(): number; + getEmployeeChaMultiplier(): number; + getEmployeeCreMultiplier(): number; + getEmployeeEffMultiplier(): number; + getEmployeeIntMultiplier(): number; + getProductionMultiplier(): number; + getProductProductionMultiplier(): number; + getSalesMultiplier(): number; + getScientificResearchMultiplier(): number; + getStorageMultiplier(): number; + toJSON(): any; +} \ No newline at end of file diff --git a/src/Corporation/Material.ts b/src/Corporation/Material.ts index 29a165dd7..7ce69dc43 100644 --- a/src/Corporation/Material.ts +++ b/src/Corporation/Material.ts @@ -1,6 +1,7 @@ import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; +import { Export } from "./Export"; interface IConstructorParams { name?: string; @@ -43,7 +44,7 @@ export class Material { imp = 0; // Exports of this material to another warehouse/industry - exp: any[] = []; + exp: Export[] = []; // Total amount of this material exported in the last cycle totalExp = 0; @@ -52,7 +53,7 @@ export class Material { bCost = 0; // Cost / sec to sell this material - sCost = 0; + sCost: string | number = 0; // Flags to keep track of whether production and/or sale of this material is limited // [Whether production/sale is limited, limit amount] diff --git a/src/Corporation/OfficeSpace.ts b/src/Corporation/OfficeSpace.ts index 7262ce348..299156ee5 100644 --- a/src/Corporation/OfficeSpace.ts +++ b/src/Corporation/OfficeSpace.ts @@ -14,6 +14,9 @@ import { removeElementById } from "../../utils/uiHelpers/removeElementById"; import { createElement } from "../../utils/uiHelpers/createElement"; import { numeralWrapper } from "../ui/numeralFormat"; import { Employee } from "./Employee"; +import { IIndustry } from "./IIndustry"; +import { ICorporation } from './ICorporation'; +import { IPlayer } from "../PersonObjects/IPlayer"; interface IParams { loc?: string; @@ -58,7 +61,7 @@ export class OfficeSpace { return (this.employees.length) >= this.size; } - process(marketCycles = 1, parentRefs: any): number { + process(marketCycles = 1, parentRefs: {industry: IIndustry; corporation: ICorporation}): number { const industry = parentRefs.industry; // HRBuddy AutoRecruitment and training @@ -85,9 +88,9 @@ export class OfficeSpace { // 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) { + if (parentRefs.corporation.funds < 0 && industry.lastCycleRevenue < 0) { perfMult = Math.pow(0.99, marketCycles); - } else if (industry.funds > 0 && industry.lastCycleRevenue > 0) { + } else if (parentRefs.corporation.funds > 0 && industry.lastCycleRevenue > 0) { perfMult = Math.pow(1.01, marketCycles); } @@ -122,7 +125,7 @@ export class OfficeSpace { return salaryPaid; } - calculateEmployeeProductivity(parentRefs: any): void { + calculateEmployeeProductivity(parentRefs: {corporation: ICorporation; industry: IIndustry}): void { const company = parentRefs.corporation, industry = parentRefs.industry; //Reset @@ -141,7 +144,7 @@ export class OfficeSpace { } //Takes care of UI as well - findEmployees(parentRefs: any): void { + findEmployees(player: IPlayer, parentRefs: {corporation: ICorporation}): void { if (this.atCapacity()) { return; } if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} @@ -197,7 +200,7 @@ export class OfficeSpace { "Efficiency: " + formatNumber(employee.eff, 1) + "
" + "Salary: " + numeralWrapper.format(employee.sal, '$0.000a') + " \ s
", clickListener: () => { - office.hireEmployee(employee, parentRefs); + office.hireEmployee(player, employee, parentRefs); removeElementById("cmpy-mgmt-hire-employee-popup"); return false; }, @@ -224,8 +227,8 @@ export class OfficeSpace { createPopup("cmpy-mgmt-hire-employee-popup", elems); } - hireEmployee(employee: Employee, parentRefs: any): void { - const company = parentRefs.corporation; + hireEmployee(player: IPlayer, employee: Employee, parentRefs: {corporation: ICorporation}): void { + const corporation = parentRefs.corporation; const yesBtn = yesNoTxtInpBoxGetYesButton(), noBtn = yesNoTxtInpBoxGetNoButton(); yesBtn.innerHTML = "Hire"; @@ -240,7 +243,7 @@ export class OfficeSpace { } employee.name = name; this.employees.push(employee); - company.rerender(); + corporation.rerender(player); return yesNoTxtInpBoxClose(); }); noBtn.addEventListener("click", () => { @@ -285,7 +288,7 @@ export class OfficeSpace { } //Finds the first unassigned employee and assigns its to the specified job - assignEmployeeToJob(job: any): boolean { + assignEmployeeToJob(job: string): boolean { for (let i = 0; i < this.employees.length; ++i) { if (this.employees[i].pos === EmployeePositions.Unassigned) { this.employees[i].pos = job; @@ -296,7 +299,7 @@ export class OfficeSpace { } //Finds the first employee with the given job and unassigns it - unassignEmployeeFromJob(job: any): boolean { + unassignEmployeeFromJob(job: string): boolean { for (let i = 0; i < this.employees.length; ++i) { if (this.employees[i].pos === job) { this.employees[i].pos = EmployeePositions.Unassigned; diff --git a/src/Corporation/ui/DiscontinueProductPopup.tsx b/src/Corporation/ui/DiscontinueProductPopup.tsx index bc81a7567..41c803f01 100644 --- a/src/Corporation/ui/DiscontinueProductPopup.tsx +++ b/src/Corporation/ui/DiscontinueProductPopup.tsx @@ -1,11 +1,13 @@ import React from 'react'; import { removePopup } from "../../ui/React/createPopup"; import { ICorporation } from "../ICorporation"; +import { Product } from "../Product"; +import { IIndustry } from "../IIndustry"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - product: any; - industry: any; + product: Product; + industry: IIndustry; corp: ICorporation; popupId: string; player: IPlayer; diff --git a/src/Corporation/ui/ExportPopup.tsx b/src/Corporation/ui/ExportPopup.tsx index 39e5936be..7ff9f5a89 100644 --- a/src/Corporation/ui/ExportPopup.tsx +++ b/src/Corporation/ui/ExportPopup.tsx @@ -2,9 +2,12 @@ import React, { useState } from 'react'; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { ICorporation } from "../ICorporation"; +import { Material } from "../Material"; +import { Export } from "../Export"; +import { IIndustry } from "../IIndustry"; interface IProps { - mat: any; + mat: Material; corp: ICorporation; popupId: string; } @@ -64,7 +67,7 @@ export function ExportPopup(props: IProps): React.ReactElement { removePopup(props.popupId); } - function removeExport(exp: any): void { + function removeExport(exp: Export): 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 || @@ -75,7 +78,7 @@ export function ExportPopup(props: IProps): React.ReactElement { rerender(); } - const currentDivision = props.corp.divisions.find((division: any) => division.name === industry); + const currentDivision = props.corp.divisions.find((division: IIndustry) => division.name === industry); return (<>

@@ -85,12 +88,12 @@ amount to 'MAX' to export all of the materials in this warehouse.

",g.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue,e.innerHTML="",g.option=!!e.lastChild}();var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&O(e,t)?E.merge([e],n):n}function be(e,t){for(var n=0,r=e.length;n",""]);var ye=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,d=t.createDocumentFragment(),f=[],p=0,h=e.length;p-1)i&&i.push(o);else if(l=ae(o),a=ve(d.appendChild(o),"script"),l&&be(a),n)for(c=0;o=a[c++];)me.test(o.type||"")&&n.push(o);return d}var _e=/^key/,we=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function Ce(){return!0}function Se(){return!1}function ke(e,t){return e===function(){try{return y.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return E().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=E.guid++)),e.each(function(){E.event.add(this,t,i,r,n)})}function De(e,t,n){n?(Z.set(e,t,!1),E.event.add(e,t,{namespace:!1,handler:function(e){var r,i,o=Z.get(this,t);if(1&e.isTrigger&&this[t]){if(o.length)(E.event.special[t]||{}).delegateType&&e.stopPropagation();else if(o=s.call(arguments),Z.set(this,t,o),r=n(this,t),this[t](),o!==(i=Z.get(this,t))||r?Z.set(this,t,!1):i={},o!==i)return e.stopImmediatePropagation(),e.preventDefault(),i.value}else o.length&&(Z.set(this,t,{value:E.event.trigger(E.extend(o[0],E.Event.prototype),o.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Z.get(e,t)&&E.event.add(e,t,Ce)}E.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,d,f,p,h,m,g=Z.get(e);if(Y(e))for(n.handler&&(n=(o=n).handler,i=o.selector),i&&E.find.matchesSelector(oe,i),n.guid||(n.guid=E.guid++),(u=g.events)||(u=g.events=Object.create(null)),(a=g.handle)||(a=g.handle=function(t){return void 0!==E&&E.event.triggered!==t.type?E.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(N)||[""]).length;l--;)p=m=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),p&&(d=E.event.special[p]||{},p=(i?d.delegateType:d.bindType)||p,d=E.event.special[p]||{},c=E.extend({type:p,origType:m,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&E.expr.match.needsContext.test(i),namespace:h.join(".")},o),(f=u[p])||((f=u[p]=[]).delegateCount=0,d.setup&&!1!==d.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(p,a)),d.add&&(d.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?f.splice(f.delegateCount++,0,c):f.push(c),E.event.global[p]=!0)},remove:function(e,t,n,r,i){var o,a,s,u,l,c,d,f,p,h,m,g=Z.hasData(e)&&Z.get(e);if(g&&(u=g.events)){for(l=(t=(t||"").match(N)||[""]).length;l--;)if(p=m=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),p){for(d=E.event.special[p]||{},f=u[p=(r?d.delegateType:d.bindType)||p]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=f.length;o--;)c=f[o],!i&&m!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(f.splice(o,1),c.selector&&f.delegateCount--,d.remove&&d.remove.call(e,c));a&&!f.length&&(d.teardown&&!1!==d.teardown.call(e,h,g.handle)||E.removeEvent(e,p,g.handle),delete u[p])}else for(p in u)E.event.remove(e,p+t[l],n,r,!0);E.isEmptyObject(u)&&Z.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=E.event.fix(e),l=(Z.get(this,"events")||Object.create(null))[u.type]||[],c=E.event.special[u.type]||{};for(s[0]=u,t=1;t=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:E.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\s*$/g;function Pe(e,t){return O(e,"table")&&O(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function Fe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Ie(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Z.hasData(e)&&(s=Z.get(e).events))for(i in Z.remove(t,"handle events"),s)for(n=0,r=s[i].length;n1&&"string"==typeof h&&!g.checkClone&&je.test(h))return e.each(function(i){var o=e.eq(i);m&&(t[0]=h.call(this,i,o.html())),Ne(o,t,n,r)});if(f&&(o=(i=xe(t,e[0].ownerDocument,!1,e,r)).firstChild,1===i.childNodes.length&&(i=o),o||r)){for(s=(a=E.map(ve(i,"script"),Fe)).length;d0&&be(a,!u&&ve(e,"script")),s},cleanData:function(e){for(var t,n,r,i=E.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[Z.expando]){if(t.events)for(r in t.events)i[r]?E.event.remove(n,r):E.removeEvent(n,r,t.handle);n[Z.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),E.fn.extend({detach:function(e){return Le(this,e,!0)},remove:function(e){return Le(this,e)},text:function(e){return H(this,function(e){return void 0===e?E.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Ne(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Pe(this,e).appendChild(e)})},prepend:function(){return Ne(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Pe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Ne(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Ne(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(E.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return E.clone(this,e,t)})},html:function(e){return H(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Oe.test(e)&&!ge[(he.exec(e)||["",""])[1].toLowerCase()]){e=E.htmlPrefilter(e);try{for(;n3,oe.removeChild(e)),s}}))}();var qe=["Webkit","Moz","ms"],$e=y.createElement("div").style,Ke={};function Ge(e){var t=E.cssProps[e]||Ke[e];return t||(e in $e?e:Ke[e]=function(e){for(var t=e[0].toUpperCase()+e.slice(1),n=qe.length;n--;)if((e=qe[n]+t)in $e)return e}(e)||e)}var Ye=/^(none|table(?!-c[ea]).+)/,Xe=/^--/,Ze={position:"absolute",visibility:"hidden",display:"block"},Qe={letterSpacing:"0",fontWeight:"400"};function Je(e,t,n){var r=re.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function et(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=E.css(e,n+ie[a],!0,i)),r?("content"===n&&(u-=E.css(e,"padding"+ie[a],!0,i)),"margin"!==n&&(u-=E.css(e,"border"+ie[a]+"Width",!0,i))):(u+=E.css(e,"padding"+ie[a],!0,i),"padding"!==n?u+=E.css(e,"border"+ie[a]+"Width",!0,i):s+=E.css(e,"border"+ie[a]+"Width",!0,i));return!r&&o>=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function tt(e,t,n){var r=ze(e),i=(!g.boxSizingReliable()||n)&&"border-box"===E.css(e,"boxSizing",!1,r),o=i,a=Ve(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(Me.test(a)){if(!n)return a;a="auto"}return(!g.boxSizingReliable()&&i||!g.reliableTrDimensions()&&O(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===E.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===E.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+et(e,t,n||(i?"border":"content"),o,r,a)+"px"}function nt(e,t,n,r,i){return new nt.prototype.init(e,t,n,r,i)}E.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Ve(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Ge(s)),a=E.cssHooks[t]||E.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=re.exec(n))&&i[1]&&(n=le(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(E.cssNumber[s]?"":"px")),g.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Ge(s)),(a=E.cssHooks[t]||E.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Ve(e,t,r)),"normal"===i&&t in Qe&&(i=Qe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),E.each(["height","width"],function(e,t){E.cssHooks[t]={get:function(e,n,r){if(n)return!Ye.test(E.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?tt(e,t,r):Ue(e,Ze,function(){return tt(e,t,r)})},set:function(e,n,r){var i,o=ze(e),a=!g.scrollboxSize()&&"absolute"===o.position,s=(a||r)&&"border-box"===E.css(e,"boxSizing",!1,o),u=r?et(e,t,r,s,o):0;return s&&a&&(u-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-et(e,t,"border",!1,o)-.5)),u&&(i=re.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=E.css(e,t)),Je(0,n,u)}}}),E.cssHooks.marginLeft=He(g.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Ve(e,"marginLeft"))||e.getBoundingClientRect().left-Ue(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),E.each({margin:"",padding:"",border:"Width"},function(e,t){E.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+ie[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(E.cssHooks[e+t].set=Je)}),E.fn.extend({css:function(e,t){return H(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=ze(e),i=t.length;a1)}}),E.Tween=nt,nt.prototype={constructor:nt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||E.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(E.cssNumber[n]?"":"px")},cur:function(){var e=nt.propHooks[this.prop];return e&&e.get?e.get(this):nt.propHooks._default.get(this)},run:function(e){var t,n=nt.propHooks[this.prop];return this.options.duration?this.pos=t=E.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):nt.propHooks._default.set(this),this}},nt.prototype.init.prototype=nt.prototype,nt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=E.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){E.fx.step[e.prop]?E.fx.step[e.prop](e):1!==e.elem.nodeType||!E.cssHooks[e.prop]&&null==e.elem.style[Ge(e.prop)]?e.elem[e.prop]=e.now:E.style(e.elem,e.prop,e.now+e.unit)}}},nt.propHooks.scrollTop=nt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},E.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},E.fx=nt.prototype.init,E.fx.step={};var rt,it,ot=/^(?:toggle|show|hide)$/,at=/queueHooks$/;function st(){it&&(!1===y.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(st):n.setTimeout(st,E.fx.interval),E.fx.tick())}function ut(){return n.setTimeout(function(){rt=void 0}),rt=Date.now()}function lt(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=ie[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function ct(e,t,n){for(var r,i=(dt.tweeners[t]||[]).concat(dt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){E.removeAttr(this,e)})}}),E.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===e.getAttribute?E.prop(e,t,n):(1===o&&E.isXMLDoc(e)||(i=E.attrHooks[t.toLowerCase()]||(E.expr.match.bool.test(t)?ft:void 0)),void 0!==n?null===n?void E.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=E.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!g.radioValue&&"radio"===t&&O(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(N);if(i&&1===e.nodeType)for(;n=i[r++];)e.removeAttribute(n)}}),ft={set:function(e,t,n){return!1===t?E.removeAttr(e,n):e.setAttribute(n,n),n}},E.each(E.expr.match.bool.source.match(/\w+/g),function(e,t){var n=pt[t]||E.find.attr;pt[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=pt[a],pt[a]=i,i=null!=n(e,t,r)?a:null,pt[a]=o),i}});var ht=/^(?:input|select|textarea|button)$/i,mt=/^(?:a|area)$/i;function gt(e){return(e.match(N)||[]).join(" ")}function vt(e){return e.getAttribute&&e.getAttribute("class")||""}function bt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(N)||[]}E.fn.extend({prop:function(e,t){return H(this,E.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[E.propFix[e]||e]})}}),E.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&E.isXMLDoc(e)||(t=E.propFix[t]||t,i=E.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=E.find.attr(e,"tabindex");return t?parseInt(t,10):ht.test(e.nodeName)||mt.test(e.nodeName)&&e.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),g.optSelected||(E.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),E.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){E.propFix[this.toLowerCase()]=this}),E.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(v(e))return this.each(function(t){E(this).addClass(e.call(this,t,vt(this)))});if((t=bt(e)).length)for(;n=this[u++];)if(i=vt(n),r=1===n.nodeType&&" "+gt(i)+" "){for(a=0;o=t[a++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=gt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(v(e))return this.each(function(t){E(this).removeClass(e.call(this,t,vt(this)))});if(!arguments.length)return this.attr("class","");if((t=bt(e)).length)for(;n=this[u++];)if(i=vt(n),r=1===n.nodeType&&" "+gt(i)+" "){for(a=0;o=t[a++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");i!==(s=gt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):v(e)?this.each(function(n){E(this).toggleClass(e.call(this,n,vt(this),t),t)}):this.each(function(){var t,i,o,a;if(r)for(i=0,o=E(this),a=bt(e);t=a[i++];)o.hasClass(t)?o.removeClass(t):o.addClass(t);else void 0!==e&&"boolean"!==n||((t=vt(this))&&Z.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":Z.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+gt(vt(n))+" ").indexOf(t)>-1)return!0;return!1}});var yt=/\r/g;E.fn.extend({val:function(e){var t,n,r,i=this[0];return arguments.length?(r=v(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,E(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=E.map(i,function(e){return null==e?"":e+""})),(t=E.valHooks[this.type]||E.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))})):i?(t=E.valHooks[i.type]||E.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(yt,""):null==n?"":n:void 0}}),E.extend({valHooks:{option:{get:function(e){var t=E.find.attr(e,"value");return null!=t?t:gt(E.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),E.each(["radio","checkbox"],function(){E.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=E.inArray(E(e).val(),t)>-1}},g.checkOn||(E.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),g.focusin="onfocusin"in n;var xt=/^(?:focusinfocus|focusoutblur)$/,_t=function(e){e.stopPropagation()};E.extend(E.event,{trigger:function(e,t,r,i){var o,a,s,u,l,c,d,f,h=[r||y],m=p.call(e,"type")?e.type:e,g=p.call(e,"namespace")?e.namespace.split("."):[];if(a=f=s=r=r||y,3!==r.nodeType&&8!==r.nodeType&&!xt.test(m+E.event.triggered)&&(m.indexOf(".")>-1&&(m=(g=m.split(".")).shift(),g.sort()),l=m.indexOf(":")<0&&"on"+m,(e=e[E.expando]?e:new E.Event(m,"object"==typeof e&&e)).isTrigger=i?2:3,e.namespace=g.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=r),t=null==t?[e]:E.makeArray(t,[e]),d=E.event.special[m]||{},i||!d.trigger||!1!==d.trigger.apply(r,t))){if(!i&&!d.noBubble&&!b(r)){for(u=d.delegateType||m,xt.test(u+m)||(a=a.parentNode);a;a=a.parentNode)h.push(a),s=a;s===(r.ownerDocument||y)&&h.push(s.defaultView||s.parentWindow||n)}for(o=0;(a=h[o++])&&!e.isPropagationStopped();)f=a,e.type=o>1?u:d.bindType||m,(c=(Z.get(a,"events")||Object.create(null))[e.type]&&Z.get(a,"handle"))&&c.apply(a,t),(c=l&&a[l])&&c.apply&&Y(a)&&(e.result=c.apply(a,t),!1===e.result&&e.preventDefault());return e.type=m,i||e.isDefaultPrevented()||d._default&&!1!==d._default.apply(h.pop(),t)||!Y(r)||l&&v(r[m])&&!b(r)&&((s=r[l])&&(r[l]=null),E.event.triggered=m,e.isPropagationStopped()&&f.addEventListener(m,_t),r[m](),e.isPropagationStopped()&&f.removeEventListener(m,_t),E.event.triggered=void 0,s&&(r[l]=s)),e.result}},simulate:function(e,t,n){var r=E.extend(new E.Event,n,{type:e,isSimulated:!0});E.event.trigger(r,null,t)}}),E.fn.extend({trigger:function(e,t){return this.each(function(){E.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return E.event.trigger(e,t,n,!0)}}),g.focusin||E.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){E.event.simulate(t,e.target,E.event.fix(e))};E.event.special[t]={setup:function(){var r=this.ownerDocument||this.document||this,i=Z.access(r,t);i||r.addEventListener(e,n,!0),Z.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this.document||this,i=Z.access(r,t)-1;i?Z.access(r,t,i):(r.removeEventListener(e,n,!0),Z.remove(r,t))}}});var wt=n.location,Et={guid:Date.now()},Ct=/\?/;E.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new n.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||E.error("Invalid XML: "+e),t};var St=/\[\]$/,kt=/\r?\n/g,At=/^(?:submit|button|image|reset|file)$/i,Dt=/^(?:input|select|textarea|keygen)/i;function Ot(e,t,n,r){var i;if(Array.isArray(t))E.each(t,function(t,i){n||St.test(e)?r(e,i):Ot(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==w(t))r(e,t);else for(i in t)Ot(e+"["+i+"]",t[i],n,r)}E.param=function(e,t){var n,r=[],i=function(e,t){var n=v(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!E.isPlainObject(e))E.each(e,function(){i(this.name,this.value)});else for(n in e)Ot(n,e[n],t,i);return r.join("&")},E.fn.extend({serialize:function(){return E.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=E.prop(this,"elements");return e?E.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!E(this).is(":disabled")&&Dt.test(this.nodeName)&&!At.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=E(this).val();return null==n?null:Array.isArray(n)?E.map(n,function(e){return{name:t.name,value:e.replace(kt,"\r\n")}}):{name:t.name,value:n.replace(kt,"\r\n")}}).get()}});var jt=/%20/g,Tt=/#.*$/,Pt=/([?&])_=[^&]*/,Ft=/^(.*?):[ \t]*([^\r\n]*)$/gm,Rt=/^(?:GET|HEAD)$/,It=/^\/\//,Bt={},Nt={},Lt="*/".concat("*"),Mt=y.createElement("a");function zt(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(N)||[];if(v(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function Ut(e,t,n,r){var i={},o=e===Nt;function a(s){var u;return i[s]=!0,E.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function Wt(e,t){var n,r,i=E.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&E.extend(!0,e,r),e}Mt.href=wt.href,E.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:wt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(wt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Lt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":E.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Wt(Wt(e,E.ajaxSettings),t):Wt(E.ajaxSettings,e)},ajaxPrefilter:zt(Bt),ajaxTransport:zt(Nt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var r,i,o,a,s,u,l,c,d,f,p=E.ajaxSetup({},t),h=p.context||p,m=p.context&&(h.nodeType||h.jquery)?E(h):E.event,g=E.Deferred(),v=E.Callbacks("once memory"),b=p.statusCode||{},x={},_={},w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(l){if(!a)for(a={};t=Ft.exec(o);)a[t[1].toLowerCase()+" "]=(a[t[1].toLowerCase()+" "]||[]).concat(t[2]);t=a[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return l?o:null},setRequestHeader:function(e,t){return null==l&&(e=_[e.toLowerCase()]=_[e.toLowerCase()]||e,x[e]=t),this},overrideMimeType:function(e){return null==l&&(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(l)C.always(e[C.status]);else for(t in e)b[t]=[b[t],e[t]];return this},abort:function(e){var t=e||w;return r&&r.abort(t),S(0,t),this}};if(g.promise(C),p.url=((e||p.url||wt.href)+"").replace(It,wt.protocol+"//"),p.type=t.method||t.type||p.method||p.type,p.dataTypes=(p.dataType||"*").toLowerCase().match(N)||[""],null==p.crossDomain){u=y.createElement("a");try{u.href=p.url,u.href=u.href,p.crossDomain=Mt.protocol+"//"+Mt.host!=u.protocol+"//"+u.host}catch(e){p.crossDomain=!0}}if(p.data&&p.processData&&"string"!=typeof p.data&&(p.data=E.param(p.data,p.traditional)),Ut(Bt,p,t,C),l)return C;for(d in(c=E.event&&p.global)&&0==E.active++&&E.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Rt.test(p.type),i=p.url.replace(Tt,""),p.hasContent?p.data&&p.processData&&0===(p.contentType||"").indexOf("application/x-www-form-urlencoded")&&(p.data=p.data.replace(jt,"+")):(f=p.url.slice(i.length),p.data&&(p.processData||"string"==typeof p.data)&&(i+=(Ct.test(i)?"&":"?")+p.data,delete p.data),!1===p.cache&&(i=i.replace(Pt,"$1"),f=(Ct.test(i)?"&":"?")+"_="+Et.guid+++f),p.url=i+f),p.ifModified&&(E.lastModified[i]&&C.setRequestHeader("If-Modified-Since",E.lastModified[i]),E.etag[i]&&C.setRequestHeader("If-None-Match",E.etag[i])),(p.data&&p.hasContent&&!1!==p.contentType||t.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Lt+"; q=0.01":""):p.accepts["*"]),p.headers)C.setRequestHeader(d,p.headers[d]);if(p.beforeSend&&(!1===p.beforeSend.call(h,C,p)||l))return C.abort();if(w="abort",v.add(p.complete),C.done(p.success),C.fail(p.error),r=Ut(Nt,p,t,C)){if(C.readyState=1,c&&m.trigger("ajaxSend",[C,p]),l)return C;p.async&&p.timeout>0&&(s=n.setTimeout(function(){C.abort("timeout")},p.timeout));try{l=!1,r.send(x,S)}catch(e){if(l)throw e;S(-1,e)}}else S(-1,"No Transport");function S(e,t,a,u){var d,f,y,x,_,w=t;l||(l=!0,s&&n.clearTimeout(s),r=void 0,o=u||"",C.readyState=e>0?4:0,d=e>=200&&e<300||304===e,a&&(x=function(e,t,n){for(var r,i,o,a,s=e.contents,u=e.dataTypes;"*"===u[0];)u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(p,C,a)),!d&&E.inArray("script",p.dataTypes)>-1&&(p.converters["text script"]=function(){}),x=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e.throws)t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(p,x,C,d),d?(p.ifModified&&((_=C.getResponseHeader("Last-Modified"))&&(E.lastModified[i]=_),(_=C.getResponseHeader("etag"))&&(E.etag[i]=_)),204===e||"HEAD"===p.type?w="nocontent":304===e?w="notmodified":(w=x.state,f=x.data,d=!(y=x.error))):(y=w,!e&&w||(w="error",e<0&&(e=0))),C.status=e,C.statusText=(t||w)+"",d?g.resolveWith(h,[f,w,C]):g.rejectWith(h,[C,w,y]),C.statusCode(b),b=void 0,c&&m.trigger(d?"ajaxSuccess":"ajaxError",[C,p,d?f:y]),v.fireWith(h,[C,w]),c&&(m.trigger("ajaxComplete",[C,p]),--E.active||E.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return E.get(e,t,n,"json")},getScript:function(e,t){return E.get(e,void 0,t,"script")}}),E.each(["get","post"],function(e,t){E[t]=function(e,n,r,i){return v(n)&&(i=i||r,r=n,n=void 0),E.ajax(E.extend({url:e,type:t,dataType:i,data:n,success:r},E.isPlainObject(e)&&e))}}),E.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),E._evalUrl=function(e,t,n){return E.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){E.globalEval(e,t,n)}})},E.fn.extend({wrapAll:function(e){var t;return this[0]&&(v(e)&&(e=e.call(this[0])),t=E(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return v(e)?this.each(function(t){E(this).wrapInner(e.call(this,t))}):this.each(function(){var t=E(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v(e);return this.each(function(n){E(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){E(this).replaceWith(this.childNodes)}),this}}),E.expr.pseudos.hidden=function(e){return!E.expr.pseudos.visible(e)},E.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},E.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Ht=E.ajaxSettings.xhr();g.cors=!!Ht&&"withCredentials"in Ht,g.ajax=Ht=!!Ht,E.ajaxTransport(function(e){var t,r;if(g.cors||Ht&&!e.crossDomain)return{send:function(i,o){var a,s=e.xhr();if(s.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(a in e.xhrFields)s[a]=e.xhrFields[a];for(a in e.mimeType&&s.overrideMimeType&&s.overrideMimeType(e.mimeType),e.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest"),i)s.setRequestHeader(a,i[a]);t=function(e){return function(){t&&(t=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=t(),r=s.onerror=s.ontimeout=t("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&n.setTimeout(function(){t&&r()})},t=t("abort");try{s.send(e.hasContent&&e.data||null)}catch(e){if(t)throw e}},abort:function(){t&&t()}}}),E.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),E.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return E.globalEval(e),e}}}),E.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),E.ajaxTransport("script",function(e){var t,n;if(e.crossDomain||e.scriptAttrs)return{send:function(r,i){t=E(" - From d65cbf07f4aeef70d27749c893d18aef5db5d151 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 31 Aug 2021 14:47:07 -0400 Subject: [PATCH 25/30] Narrow down corporation types --- src/Corporation/Corporation.tsx | 6 +- src/Corporation/IDivision.ts | 7 -- src/Corporation/IIndustry.ts | 13 ++-- src/Corporation/IOfficeSpace.ts | 15 ---- src/Corporation/Industry.ts | 74 ++++++++++++------- src/Corporation/Material.ts | 4 +- src/Corporation/OfficeSpace.ts | 23 +++--- src/Corporation/Product.ts | 32 ++------ src/Corporation/Warehouse.ts | 12 ++- src/Corporation/ui/CityTabs.tsx | 4 +- src/Corporation/ui/ExpandNewCityPopup.tsx | 4 +- src/Corporation/ui/HeaderTabs.tsx | 4 +- src/Corporation/ui/IndustryOffice.tsx | 19 ++++- src/Corporation/ui/IndustryOverview.tsx | 1 + src/Corporation/ui/IndustryWarehouse.tsx | 11 ++- src/Corporation/ui/Overview.tsx | 2 +- src/Corporation/ui/Routing.ts | 1 - src/Corporation/ui/SellMaterialPopup.tsx | 2 +- src/Corporation/ui/ThrowPartyPopup.tsx | 6 +- src/Corporation/ui/UpgradeOfficeSizePopup.tsx | 4 +- 20 files changed, 118 insertions(+), 126 deletions(-) delete mode 100644 src/Corporation/IDivision.ts delete mode 100644 src/Corporation/IOfficeSpace.ts diff --git a/src/Corporation/Corporation.tsx b/src/Corporation/Corporation.tsx index e2de46372..d35b1dd64 100644 --- a/src/Corporation/Corporation.tsx +++ b/src/Corporation/Corporation.tsx @@ -294,8 +294,10 @@ export class Corporation { for (let i = 0; i < this.divisions.length; ++i) { const industry = this.divisions[i]; for (const city in industry.warehouses) { - if (industry.warehouses.hasOwnProperty(city) && industry.warehouses[city] instanceof Warehouse) { - industry.warehouses[city].updateSize(this, industry); + const warehouse = industry.warehouses[city] + if(warehouse === 0) continue + if (industry.warehouses.hasOwnProperty(city) && warehouse instanceof Warehouse) { + warehouse.updateSize(this, industry); } } } diff --git a/src/Corporation/IDivision.ts b/src/Corporation/IDivision.ts deleted file mode 100644 index 53b63e1cf..000000000 --- a/src/Corporation/IDivision.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { IOfficeSpace } from "./IOfficeSpace"; -import { IMap } from "../types"; - -export interface IDivision { - name: string; - offices: IMap; -} diff --git a/src/Corporation/IIndustry.ts b/src/Corporation/IIndustry.ts index 37368f464..dd82016d3 100644 --- a/src/Corporation/IIndustry.ts +++ b/src/Corporation/IIndustry.ts @@ -9,12 +9,12 @@ export interface IIndustry { name: string; type: string; sciResearch: Material; - researched: any; - reqMats: any; + researched: {[key: string]: boolean | undefined}; + reqMats: {[key: string]: number | undefined}; prodMats: string[]; - products: any; + products: {[key: string]: Product | undefined}; makesProducts: boolean; awareness: number; @@ -40,9 +40,8 @@ export interface IIndustry { state: string; newInd: boolean; - warehouses: any; - offices: any; - + warehouses: {[key: string]: Warehouse | 0}; + offices: {[key: string]: OfficeSpace | 0}; init(): void; getProductDescriptionText(): string; @@ -57,7 +56,7 @@ export interface IIndustry { processProducts(marketCycles: number, corporation: ICorporation): [number, number]; processProduct(marketCycles: number, product: Product, corporation: ICorporation): number; discontinueProduct(product: Product): void; - upgrade(upgrade: IndustryUpgrade, refs: {corporation: any; office: OfficeSpace}): void; + upgrade(upgrade: IndustryUpgrade, refs: {corporation: ICorporation; office: OfficeSpace}): void; getOfficeProductivity(office: OfficeSpace, params?: any): number; getBusinessFactor(office: OfficeSpace): number; getAdvertisingFactors(): [number, number, number, number]; diff --git a/src/Corporation/IOfficeSpace.ts b/src/Corporation/IOfficeSpace.ts deleted file mode 100644 index 2138e5e19..000000000 --- a/src/Corporation/IOfficeSpace.ts +++ /dev/null @@ -1,15 +0,0 @@ -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/Industry.ts b/src/Corporation/Industry.ts index 18e3dbd11..92d3ff004 100644 --- a/src/Corporation/Industry.ts +++ b/src/Corporation/Industry.ts @@ -20,6 +20,7 @@ import { isString } from "../../utils/helpers/isString"; import { MaterialSizes } from "./MaterialSizes"; import { Warehouse } from "./Warehouse"; import { ICorporation } from "./ICorporation"; +import { IIndustry } from "./IIndustry"; import { IndustryUpgrade, IndustryUpgrades } from "./IndustryUpgrades"; @@ -27,21 +28,21 @@ import { formatNumber } from "../../utils/StringHelperFunctions"; interface IParams { name?: string; - corp?: any; + corp?: ICorporation; type?: string; } -export class Industry { +export class Industry implements IIndustry { name = ""; type = Industries.Agriculture; sciResearch = new Material({name: "Scientific Research"}); - researched: any = {}; - reqMats: any = {}; + researched: {[key: string]: boolean | undefined} = {}; + reqMats: {[key: string]: number | undefined} = {}; //An array of the name of materials being produced prodMats: string[] = []; - products: any = {}; + products: {[key: string]: Product | undefined} = {}; makesProducts = false; awareness = 0; @@ -69,16 +70,16 @@ export class Industry { thisCycleExpenses: any; //Upgrades - upgrades: number[] = []; + upgrades: number[] = Array(Object.keys(IndustryUpgrades).length).fill(0); state = "START"; newInd = true; //Maps locations to warehouses. 0 if no warehouse at that location - warehouses: any; + warehouses: {[key: string]: Warehouse | 0}; //Maps locations to offices. 0 if no office at that location - offices: any = { + offices: {[key: string]: OfficeSpace | 0} = { [CityName.Aevum]: 0, [CityName.Chongqing]: 0, [CityName.Sector12]: new OfficeSpace({ @@ -100,11 +101,7 @@ export class Industry { 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 + this.warehouses = { [CityName.Aevum]: 0, [CityName.Chongqing]: 0, [CityName.Sector12]: new Warehouse({ @@ -394,6 +391,7 @@ export class Industry { for (const prodName in this.products) { if (this.products.hasOwnProperty(prodName)) { const prod = this.products[prodName]; + if(prod === undefined) continue; 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) + "
"); @@ -426,8 +424,10 @@ export class Industry { // Process offices (and the employees in them) let employeeSalary = 0; for (const officeLoc in this.offices) { - if (this.offices[officeLoc] instanceof OfficeSpace) { - employeeSalary += this.offices[officeLoc].process(marketCycles, {industry:this, corporation:corporation}); + const office = this.offices[officeLoc]; + if(office === 0) continue; + if (office instanceof OfficeSpace) { + employeeSalary += office.process(marketCycles, corporation, this); } } this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary); @@ -476,6 +476,7 @@ export class Industry { //for every material this industry requires or produces if (this.warehouses[CorporationConstants.Cities[i]] instanceof Warehouse) { const wh = this.warehouses[CorporationConstants.Cities[i]]; + if(wh === 0) continue; for (const name in reqMats) { if (reqMats.hasOwnProperty(name)) { wh.materials[name].processMarket(); @@ -502,6 +503,7 @@ export class Industry { for (const name in this.products) { if (this.products.hasOwnProperty(name)) { const product = this.products[name]; + if(product === undefined) continue; let change = getRandomInt(0, 3) * 0.0004; if (change === 0) continue; @@ -531,6 +533,7 @@ export class Industry { continue; } const warehouse = this.warehouses[city]; + if(warehouse === 0) continue; for (const matName in warehouse.materials) { if (warehouse.materials.hasOwnProperty(matName)) { const mat = warehouse.materials[matName]; @@ -541,10 +544,13 @@ export class Industry { } for (let i = 0; i < CorporationConstants.Cities.length; ++i) { - const city = CorporationConstants.Cities[i], office = this.offices[city]; + const city = CorporationConstants.Cities[i]; + const office = this.offices[city]; + if(office === 0) continue; if (this.warehouses[city] instanceof Warehouse) { const warehouse = this.warehouses[city]; + if(warehouse === 0) continue; switch(this.state) { @@ -557,7 +563,10 @@ export class Industry { let 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; + const reqMat = ind.reqMats[matName]; + if(reqMat === undefined) + throw new Error(`reqMat "${matName}" is undefined`); + mat.buy = reqMat * warehouse.smartSupplyStore; buyAmt = mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles; } else { buyAmt = (mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles); @@ -608,6 +617,7 @@ export class Industry { } for (const reqMatName in this.reqMats) { const normQty = this.reqMats[reqMatName]; + if(normQty === undefined) continue; totalMatSize -= (MaterialSizes[reqMatName] * normQty); } // If not enough space in warehouse, limit the amount of produced materials @@ -625,7 +635,9 @@ export class Industry { let producableFrac = 1; for (const reqMatName in this.reqMats) { if (this.reqMats.hasOwnProperty(reqMatName)) { - const req = this.reqMats[reqMatName] * prod; + const reqMat = this.reqMats[reqMatName]; + if(reqMat === undefined) continue; + const req = reqMat * prod; if (warehouse.materials[reqMatName].qty < req) { producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); } @@ -636,7 +648,9 @@ export class Industry { // Make our materials if they are producable if (producableFrac > 0 && prod > 0) { for (const reqMatName in this.reqMats) { - const reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac); + const reqMat = this.reqMats[reqMatName]; + if(reqMat === undefined) continue; + const reqMatQtyNeeded = (reqMat * prod * producableFrac); warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; warehouse.materials[reqMatName].prd = 0; warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (CorporationConstants.SecsPerMarketCycle * marketCycles); @@ -725,7 +739,7 @@ export class Industry { } else if (mat.marketTa1) { sCost = mat.bCost + markupLimit; } else if (isString(mat.sCost)) { - sCost = mat.sCost.replace(/MP/g, mat.bCost); + sCost = (mat.sCost as string).replace(/MP/g, mat.bCost+''); sCost = eval(sCost); } else { sCost = mat.sCost; @@ -757,8 +771,8 @@ export class Industry { let sellAmt; if (isString(mat.sllman[1])) { //Dynamically evaluated - let tmp = mat.sllman[1].replace(/MAX/g, maxSell); - tmp = tmp.replace(/PROD/g, mat.prd); + let tmp = (mat.sllman[1] as string).replace(/MAX/g, maxSell+''); + tmp = tmp.replace(/PROD/g, mat.prd+''); try { sellAmt = eval(tmp); } catch(e) { @@ -773,7 +787,7 @@ export class Industry { sellAmt = maxSell; } else { //Player's input value is just a number - sellAmt = Math.min(maxSell, mat.sllman[1]); + sellAmt = Math.min(maxSell, mat.sllman[1] as number); } sellAmt = (sellAmt * CorporationConstants.SecsPerMarketCycle * marketCycles); @@ -801,9 +815,10 @@ export class Industry { mat.totalExp = 0; //Reset export for (let expI = 0; expI < mat.exp.length; ++expI) { const exp = mat.exp[expI]; - let amt = exp.amt.replace(/MAX/g, mat.qty / (CorporationConstants.SecsPerMarketCycle * marketCycles)); + const amtStr = exp.amt.replace(/MAX/g, (mat.qty / (CorporationConstants.SecsPerMarketCycle * marketCycles))+''); + let amt = 0; try { - amt = eval(amt); + amt = eval(amtStr); } catch(e) { dialogBoxCreate("Calculating export for " + mat.name + " in " + this.name + "'s " + city + " division failed with " + @@ -889,9 +904,11 @@ export class Industry { if (this.state === "PRODUCTION") { for (const prodName in this.products) { const prod = this.products[prodName]; + if(prod === undefined) continue; if (!prod.fin) { const city = prod.createCity; const office = this.offices[city]; + if(office === 0) continue; // Designing/Creating a Product is based mostly off Engineers const engrProd = office.employeeProd[EmployeePositions.Engineer]; @@ -930,7 +947,10 @@ export class Industry { processProduct(marketCycles=1, product: Product, corporation: ICorporation): number { let totalProfit = 0; for (let i = 0; i < CorporationConstants.Cities.length; ++i) { - const city = CorporationConstants.Cities[i], office = this.offices[city], warehouse = this.warehouses[city]; + const city = CorporationConstants.Cities[i]; + const office = this.offices[city]; + if(office === 0) continue; + const warehouse = this.warehouses[city]; if (warehouse instanceof Warehouse) { switch(this.state) { @@ -1135,7 +1155,7 @@ export class Industry { } } - upgrade(upgrade: IndustryUpgrade, refs: {corporation: any; office: OfficeSpace}): void { + upgrade(upgrade: IndustryUpgrade, refs: {corporation: ICorporation; office: OfficeSpace}): void { const corporation = refs.corporation; const office = refs.office; const upgN = upgrade[0]; diff --git a/src/Corporation/Material.ts b/src/Corporation/Material.ts index 7ce69dc43..9c5d1ca19 100644 --- a/src/Corporation/Material.ts +++ b/src/Corporation/Material.ts @@ -57,8 +57,8 @@ export class Material { // Flags to keep track of whether production and/or sale of this material is limited // [Whether production/sale is limited, limit amount] - prdman: any[] = [false, 0]; // Production - sllman: any[] = [false, 0]; // Sale + prdman: [boolean, number] = [false, 0]; // Production + sllman: [boolean, string | number] = [false, 0]; // Sale // Flags that signal whether automatic sale pricing through Market TA is enabled marketTa1 = false; diff --git a/src/Corporation/OfficeSpace.ts b/src/Corporation/OfficeSpace.ts index 299156ee5..ed3655f50 100644 --- a/src/Corporation/OfficeSpace.ts +++ b/src/Corporation/OfficeSpace.ts @@ -61,9 +61,7 @@ export class OfficeSpace { return (this.employees.length) >= this.size; } - process(marketCycles = 1, parentRefs: {industry: IIndustry; corporation: ICorporation}): number { - const industry = parentRefs.industry; - + process(marketCycles = 1, corporation: ICorporation, industry: IIndustry): number { // HRBuddy AutoRecruitment and training if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) { const emp = this.hireRandomEmployee(); @@ -88,9 +86,9 @@ export class OfficeSpace { // Calculate changes in Morale/Happiness/Energy for Employees let perfMult=1; //Multiplier for employee morale/happiness/energy based on company performance - if (parentRefs.corporation.funds < 0 && industry.lastCycleRevenue < 0) { + if (corporation.funds < 0 && industry.lastCycleRevenue < 0) { perfMult = Math.pow(0.99, marketCycles); - } else if (parentRefs.corporation.funds > 0 && industry.lastCycleRevenue > 0) { + } else if (corporation.funds > 0 && industry.lastCycleRevenue > 0) { perfMult = Math.pow(1.01, marketCycles); } @@ -121,13 +119,11 @@ export class OfficeSpace { salaryPaid += salary; } - this.calculateEmployeeProductivity(parentRefs); + this.calculateEmployeeProductivity(corporation, industry); return salaryPaid; } - calculateEmployeeProductivity(parentRefs: {corporation: ICorporation; industry: IIndustry}): void { - const company = parentRefs.corporation, industry = parentRefs.industry; - + calculateEmployeeProductivity(corporation: ICorporation, industry: IIndustry): void { //Reset for (const name in this.employeeProd) { this.employeeProd[name] = 0; @@ -136,7 +132,7 @@ export class OfficeSpace { let total = 0; for (let i = 0; i < this.employees.length; ++i) { const employee = this.employees[i]; - const prod = employee.calculateProductivity(company, industry); + const prod = employee.calculateProductivity(corporation, industry); this.employeeProd[employee.pos] += prod; total += prod; } @@ -144,7 +140,7 @@ export class OfficeSpace { } //Takes care of UI as well - findEmployees(player: IPlayer, parentRefs: {corporation: ICorporation}): void { + findEmployees(player: IPlayer, corporation: ICorporation): void { if (this.atCapacity()) { return; } if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} @@ -200,7 +196,7 @@ export class OfficeSpace { "Efficiency: " + formatNumber(employee.eff, 1) + "
" + "Salary: " + numeralWrapper.format(employee.sal, '$0.000a') + " \ s
", clickListener: () => { - office.hireEmployee(player, employee, parentRefs); + office.hireEmployee(player, employee, corporation); removeElementById("cmpy-mgmt-hire-employee-popup"); return false; }, @@ -227,8 +223,7 @@ export class OfficeSpace { createPopup("cmpy-mgmt-hire-employee-popup", elems); } - hireEmployee(player: IPlayer, employee: Employee, parentRefs: {corporation: ICorporation}): void { - const corporation = parentRefs.corporation; + hireEmployee(player: IPlayer, employee: Employee, corporation: ICorporation): void { const yesBtn = yesNoTxtInpBoxGetYesButton(), noBtn = yesNoTxtInpBoxGetNoButton(); yesBtn.innerHTML = "Hire"; diff --git a/src/Corporation/Product.ts b/src/Corporation/Product.ts index c63a79507..3ac15ff6d 100644 --- a/src/Corporation/Product.ts +++ b/src/Corporation/Product.ts @@ -1,5 +1,6 @@ import { EmployeePositions } from "./EmployeePositions"; import { MaterialSizes } from "./MaterialSizes"; +import { IIndustry } from "./IIndustry"; import { ProductRatingWeights, IProductRatingWeight } from "./ProductRatingWeights"; @@ -30,17 +31,6 @@ interface IConstructorParams { req?: IMap; } -// Interface for an Industry object - Used for type checking method arguments -interface IIndustry { - awareness: number; - popularity: number; - reqMats: IMap; - sciFac: number; - sciResearch: any; - type: string; -} - - export class Product { // Product name @@ -137,7 +127,7 @@ export class Product { } // @param industry - Industry object. Reference to industry that makes this Product - finishProduct(employeeProd: IMap, industry: IIndustry): void { + finishProduct(employeeProd: {[key: string]: number}, industry: IIndustry): void { this.fin = true; //Calculate properties @@ -199,7 +189,9 @@ export class Product { //For now, just set it to be the same as the requirements to make materials for (const matName in industry.reqMats) { if (industry.reqMats.hasOwnProperty(matName)) { - this.reqMats[matName] = industry.reqMats[matName]; + const reqMat = industry.reqMats[matName]; + if(reqMat === undefined) continue; + this.reqMats[matName] = reqMat; } } @@ -207,18 +199,10 @@ export class Product { //For now, just set it to be the same size as the requirements to make materials this.siz = 0; for (const matName in industry.reqMats) { - this.siz += MaterialSizes[matName] * industry.reqMats[matName]; + const reqMat = industry.reqMats[matName]; + if(reqMat === undefined) continue; + this.siz += MaterialSizes[matName] * reqMat; } - - //Delete unneeded variables - // @ts-ignore - delete this.prog; - // @ts-ignore - delete this.createCity; - // @ts-ignore - delete this.designCost; - // @ts-ignore - delete this.advCost; } diff --git a/src/Corporation/Warehouse.ts b/src/Corporation/Warehouse.ts index ec912fa28..100573c05 100644 --- a/src/Corporation/Warehouse.ts +++ b/src/Corporation/Warehouse.ts @@ -1,4 +1,6 @@ import { Material } from "./Material"; +import { ICorporation } from "./ICorporation"; +import { IIndustry } from "./IIndustry"; import { MaterialSizes } from "./MaterialSizes"; import { IMap } from "../types"; import { numeralWrapper } from "../ui/numeralFormat"; @@ -7,13 +9,9 @@ import { Generic_fromJSON, Reviver } from "../../utils/JSONReviver"; import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; -interface IParent { - getStorageMultiplier(): number; -} - interface IConstructorParams { - corp?: IParent; - industry?: IParent; + corp?: ICorporation; + industry?: IIndustry; loc?: string; size?: number; } @@ -92,7 +90,7 @@ export class Warehouse { } } - updateSize(corporation: IParent, industry: IParent): void { + updateSize(corporation: ICorporation, industry: IIndustry): void { try { this.size = (this.level * 100) * corporation.getStorageMultiplier() diff --git a/src/Corporation/ui/CityTabs.tsx b/src/Corporation/ui/CityTabs.tsx index 347d6b8e2..17cc606f1 100644 --- a/src/Corporation/ui/CityTabs.tsx +++ b/src/Corporation/ui/CityTabs.tsx @@ -4,7 +4,6 @@ import React from "react"; import { CityTab } from "./CityTab"; import { ExpandNewCityPopup } from "./ExpandNewCityPopup"; import { createPopup } from "../../ui/React/createPopup"; -import { IDivision } from "../IDivision"; import { ICorporation } from "../ICorporation"; import { CorporationRouting } from "./Routing"; @@ -20,11 +19,12 @@ export function CityTabs(props: IProps): React.ReactElement { const division = props.routing.currentDivision; function openExpandNewCityModal(): void { + if(division === null) return; const popupId = "cmpy-mgmt-expand-city-popup"; createPopup(popupId, ExpandNewCityPopup, { popupId: popupId, corp: props.corp, - division: division as IDivision, + division: division, cityStateSetter: props.cityStateSetter, }); } diff --git a/src/Corporation/ui/ExpandNewCityPopup.tsx b/src/Corporation/ui/ExpandNewCityPopup.tsx index ba02f6c81..b9ef280ad 100644 --- a/src/Corporation/ui/ExpandNewCityPopup.tsx +++ b/src/Corporation/ui/ExpandNewCityPopup.tsx @@ -1,5 +1,5 @@ import React, { useRef } from "react"; -import { IDivision } from "../IDivision"; +import { IIndustry } from "../IIndustry"; import { numeralWrapper } from "../../ui/numeralFormat"; import { CorporationConstants } from "../data/Constants"; import { removePopup } from "../../ui/React/createPopup"; @@ -10,7 +10,7 @@ import { ICorporation } from "../ICorporation"; interface IProps { popupId: string; corp: ICorporation; - division: IDivision; + division: IIndustry; cityStateSetter: (city: string) => void; } diff --git a/src/Corporation/ui/HeaderTabs.tsx b/src/Corporation/ui/HeaderTabs.tsx index 3dff46d4d..67b368977 100644 --- a/src/Corporation/ui/HeaderTabs.tsx +++ b/src/Corporation/ui/HeaderTabs.tsx @@ -3,7 +3,7 @@ // divisions, see an overview of your corporation, or create a new industry import React from "react"; import { HeaderTab } from "./HeaderTab"; -import { IDivision } from "../IDivision"; +import { IIndustry } from "../IIndustry"; import { NewIndustryPopup } from "./NewIndustryPopup"; import { createPopup } from "../../ui/React/createPopup"; import { ICorporation } from "../ICorporation"; @@ -40,7 +40,7 @@ export function HeaderTabs(props: IProps): React.ReactElement { text={props.corp.name} /> { - props.corp.divisions.map((division: IDivision) => { diff --git a/src/Corporation/ui/IndustryOffice.tsx b/src/Corporation/ui/IndustryOffice.tsx index 1e872373a..faa35b039 100644 --- a/src/Corporation/ui/IndustryOffice.tsx +++ b/src/Corporation/ui/IndustryOffice.tsx @@ -133,6 +133,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { const division = props.routing.currentDivision; // Validated in constructor if(division === null) return(<>); const office = division.offices[props.currentCity]; // Validated in constructor + if(office === 0) return (<>); const vechain = (props.corp.unlockUpgrades[4] === 1); // Has Vechain upgrade function switchModeOnClick(): void { @@ -159,6 +160,8 @@ export function IndustryOffice(props: IProps): React.ReactElement { // Helper functions for (re-)assigning employees to different positions function assignEmployee(to: string): void { + if(office === 0) return; + if(division === null) return; if (numUnassigned <= 0) { console.warn("Cannot assign employee. No unassigned employees available"); return; @@ -193,11 +196,13 @@ export function IndustryOffice(props: IProps): React.ReactElement { setNumUnassigned(n => n-1); office.assignEmployeeToJob(to); - office.calculateEmployeeProductivity({ corporation: props.corp, industry:division }); + office.calculateEmployeeProductivity(props.corp, division); props.corp.rerender(props.player); } function unassignEmployee(from: string): void { + if(office === 0) return; + if(division === null) return; function logWarning(pos: string): void { console.warn(`Cannot unassign from ${pos} because there is nobody assigned to that position`); } @@ -237,7 +242,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { setNumUnassigned(n => n+1); office.unassignEmployeeFromJob(from); - office.calculateEmployeeProductivity({ corporation: props.corp, industry:division }); + office.calculateEmployeeProductivity(props.corp, division); props.corp.rerender(props.player); } @@ -436,6 +441,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { const division = props.routing.currentDivision; // Validated in constructor if(division === null) return (<>); const office = division.offices[props.currentCity]; // Validated in constructor + if(office === 0) return (<>); function switchModeOnClick(): void { setEmployeeManualAssignMode(false); @@ -455,6 +461,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { } function employeeSelectorOnChange(e: React.ChangeEvent): void { + if(office === 0) return; const name = getSelectText(e.target); for (let i = 0; i < office.employees.length; ++i) { if (name === office.employees[i].name) { @@ -547,7 +554,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { const division = props.routing.currentDivision; // Validated in constructor if(division === null) return (<>); const office = division.offices[props.currentCity]; // Validated in constructor - + if(office === 0) return (<>); const buttonStyle = { fontSize: "13px", } @@ -564,7 +571,8 @@ export function IndustryOffice(props: IProps): React.ReactElement { } function hireEmployeeButtonOnClick(): void { - office.findEmployees(props.player, { corporation: corp, industry: division }); + if(office === 0) return; + office.findEmployees(props.player, corp); } // Autohire employee button @@ -575,12 +583,14 @@ export function IndustryOffice(props: IProps): React.ReactElement { autohireEmployeeButtonClass += " std-button"; } function autohireEmployeeButtonOnClick(): void { + if(office === 0) return; if (office.atCapacity()) return; office.hireRandomEmployee(); props.corp.rerender(props.player); } function openUpgradeOfficeSizePopup(): void { + if(office === 0) return; const popupId = "cmpy-mgmt-upgrade-office-size-popup"; createPopup(popupId, UpgradeOfficeSizePopup, { office: office, @@ -591,6 +601,7 @@ export function IndustryOffice(props: IProps): React.ReactElement { } function openThrowPartyPopup(): void { + if(office === 0) return; const popupId = "cmpy-mgmt-throw-office-party-popup"; createPopup(popupId, ThrowPartyPopup, { office: office, diff --git a/src/Corporation/ui/IndustryOverview.tsx b/src/Corporation/ui/IndustryOverview.tsx index 5aad72ba0..d9bfec9c1 100644 --- a/src/Corporation/ui/IndustryOverview.tsx +++ b/src/Corporation/ui/IndustryOverview.tsx @@ -248,6 +248,7 @@ export function IndustryOverview(props: IProps): React.ReactElement { } function onClick(): void { + if(office === 0) return; if(division === null) return; if (corp.funds.lt(cost)) { dialogBoxCreate("Insufficient funds"); diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx index cf436ba08..521115f72 100644 --- a/src/Corporation/ui/IndustryWarehouse.tsx +++ b/src/Corporation/ui/IndustryWarehouse.tsx @@ -292,7 +292,7 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement { if (isString(mat.sllman[1])) { sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${mat.sllman[1]})` } else { - sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1], nfB)})`; + sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1] as number, nfB)})`; } if (mat.marketTa2) { @@ -432,6 +432,7 @@ export function IndustryWarehouse(props: IProps): React.ReactElement { const division = props.routing.currentDivision; // Validated in render() if(division === null) return (<>); const warehouse = division.warehouses[props.currentCity]; // Validated in render() + if(warehouse === 0) return (<>); // General Storage information at the top const sizeUsageStyle = { @@ -444,6 +445,8 @@ export function IndustryWarehouse(props: IProps): React.ReactElement { const canAffordUpgrade = (corp.funds.gt(sizeUpgradeCost)); const upgradeWarehouseClass = canAffordUpgrade ? "std-button" : "a-link-button-inactive"; function upgradeWarehouseOnClick(): void { + if(division === null) return; + if(warehouse === 0) return; ++warehouse.level; warehouse.updateSize(corp, division); corp.funds = corp.funds.minus(sizeUpgradeCost); @@ -510,6 +513,7 @@ export function IndustryWarehouse(props: IProps): React.ReactElement { // Smart Supply Checkbox const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox"; function smartSupplyOnChange(e: React.ChangeEvent): void { + if(warehouse === 0) return; warehouse.smartSupplyEnabled = e.target.checked; corp.rerender(props.player); } @@ -535,14 +539,15 @@ export function IndustryWarehouse(props: IProps): React.ReactElement { const products = []; if (division.makesProducts && Object.keys(division.products).length > 0) { for (const productName in division.products) { - if (division.products[productName] instanceof Product) { + const product = division.products[productName]; + if (product instanceof Product) { products.push(); } } diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx index 2a3cdc314..39e9ad6b6 100644 --- a/src/Corporation/ui/Overview.tsx +++ b/src/Corporation/ui/Overview.tsx @@ -36,7 +36,7 @@ interface GeneralBtns { export function Overview(props: IProps): React.ReactElement { // Generic Function for Creating a button interface ICreateButtonProps { - text: string + text: string; class?: string; display?: string; tooltip?: string; diff --git a/src/Corporation/ui/Routing.ts b/src/Corporation/ui/Routing.ts index b3ae08967..b39d8d164 100644 --- a/src/Corporation/ui/Routing.ts +++ b/src/Corporation/ui/Routing.ts @@ -1,4 +1,3 @@ -import { IMap } from "../../types"; import { ICorporation } from "../ICorporation"; import { IIndustry } from "../IIndustry"; diff --git a/src/Corporation/ui/SellMaterialPopup.tsx b/src/Corporation/ui/SellMaterialPopup.tsx index e209c8125..4b4828ba0 100644 --- a/src/Corporation/ui/SellMaterialPopup.tsx +++ b/src/Corporation/ui/SellMaterialPopup.tsx @@ -22,7 +22,7 @@ interface IProps { // 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 [amt, setAmt] = useState(props.mat.sllman[1] ? props.mat.sllman[1]+'' : ''); const [price, setPrice] = useState(initialPrice(props.mat)); function sellMaterial(): void { diff --git a/src/Corporation/ui/ThrowPartyPopup.tsx b/src/Corporation/ui/ThrowPartyPopup.tsx index fa37c3574..0b18186c7 100644 --- a/src/Corporation/ui/ThrowPartyPopup.tsx +++ b/src/Corporation/ui/ThrowPartyPopup.tsx @@ -2,11 +2,11 @@ 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"; +import { OfficeSpace } from "../OfficeSpace"; import { ICorporation } from "../ICorporation"; interface IProps { - office: IOfficeSpace; + office: OfficeSpace; corp: ICorporation; popupId: string; } @@ -38,7 +38,7 @@ export function ThrowPartyPopup(props: IProps): React.ReactElement { } } - function EffectText(props: {cost: number | null; office: IOfficeSpace}): React.ReactElement { + function EffectText(props: {cost: number | null; office: OfficeSpace}): React.ReactElement { let cost = props.cost; if(cost !== null && (isNaN(cost) || cost < 0)) return

Invalid value entered!

if(cost === null) cost = 0; diff --git a/src/Corporation/ui/UpgradeOfficeSizePopup.tsx b/src/Corporation/ui/UpgradeOfficeSizePopup.tsx index ceddedccd..a7b948140 100644 --- a/src/Corporation/ui/UpgradeOfficeSizePopup.tsx +++ b/src/Corporation/ui/UpgradeOfficeSizePopup.tsx @@ -3,12 +3,12 @@ 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"; +import { OfficeSpace } from "../OfficeSpace"; import { ICorporation } from "../ICorporation"; import { IPlayer } from "../../PersonObjects/IPlayer"; interface IProps { - office: IOfficeSpace; + office: OfficeSpace; corp: ICorporation; popupId: string; player: IPlayer; From f987ff9e2a2a0b192e8d7025561d3d5a3333a36e Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 31 Aug 2021 15:51:27 -0400 Subject: [PATCH 26/30] no more any in corp --- src/Corporation/Corporation.tsx | 6 +++--- src/Corporation/IIndustry.ts | 2 +- src/Corporation/Industry.ts | 2 +- src/Corporation/OfficeSpace.ts | 4 ++-- src/Corporation/ui/ThrowPartyPopup.tsx | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Corporation/Corporation.tsx b/src/Corporation/Corporation.tsx index d35b1dd64..f7f3df41c 100644 --- a/src/Corporation/Corporation.tsx +++ b/src/Corporation/Corporation.tsx @@ -34,8 +34,8 @@ interface IParams { name?: string; } -let corpRouting: any; -let companyManagementDiv: any; +let corpRouting: CorporationRouting; +let companyManagementDiv: HTMLDivElement | null = null; export class Corporation { name = "The Corporation"; @@ -379,7 +379,7 @@ export class Corporation { id:"cmpy-mgmt-container", position:"fixed", class:"generic-menupage-container", - }); + }) as HTMLDivElement; const game = document.getElementById("entire-game-container"); if(game) game.appendChild(companyManagementDiv); diff --git a/src/Corporation/IIndustry.ts b/src/Corporation/IIndustry.ts index dd82016d3..298d92fd5 100644 --- a/src/Corporation/IIndustry.ts +++ b/src/Corporation/IIndustry.ts @@ -57,7 +57,7 @@ export interface IIndustry { processProduct(marketCycles: number, product: Product, corporation: ICorporation): number; discontinueProduct(product: Product): void; upgrade(upgrade: IndustryUpgrade, refs: {corporation: ICorporation; office: OfficeSpace}): void; - getOfficeProductivity(office: OfficeSpace, params?: any): number; + getOfficeProductivity(office: OfficeSpace, params?: {forProduct?: boolean}): number; getBusinessFactor(office: OfficeSpace): number; getAdvertisingFactors(): [number, number, number, number]; getMarketFactor(mat: {dmd: number; cmp: number}): number; diff --git a/src/Corporation/Industry.ts b/src/Corporation/Industry.ts index 92d3ff004..49c68203d 100644 --- a/src/Corporation/Industry.ts +++ b/src/Corporation/Industry.ts @@ -1185,7 +1185,7 @@ export class Industry implements IIndustry { } // Returns how much of a material can be produced based of office productivity (employee stats) - getOfficeProductivity(office: OfficeSpace, params: any = {}): number { + getOfficeProductivity(office: OfficeSpace, params: {forProduct?: boolean} = {}): number { const opProd = office.employeeProd[EmployeePositions.Operations]; const engrProd = office.employeeProd[EmployeePositions.Engineer]; const mgmtProd = office.employeeProd[EmployeePositions.Management] diff --git a/src/Corporation/OfficeSpace.ts b/src/Corporation/OfficeSpace.ts index ed3655f50..4a419ffea 100644 --- a/src/Corporation/OfficeSpace.ts +++ b/src/Corporation/OfficeSpace.ts @@ -38,7 +38,7 @@ export class OfficeSpace { minHap = 0; maxHap = 100; maxMor = 100; - employees: any[] = []; + employees: Employee[] = []; employeeProd: {[key: string]: number} = { [EmployeePositions.Operations]: 0, [EmployeePositions.Engineer]: 0, @@ -186,7 +186,7 @@ export class OfficeSpace { innerHTML: "Select one of the following candidates for hire:", }); - function createEmpDiv(employee: any, office: any): HTMLElement { + function createEmpDiv(employee: Employee, office: OfficeSpace): HTMLElement { const div = createElement("div", { class:"cmpy-mgmt-find-employee-option", innerHTML: "Intelligence: " + formatNumber(employee.int, 1) + "
" + diff --git a/src/Corporation/ui/ThrowPartyPopup.tsx b/src/Corporation/ui/ThrowPartyPopup.tsx index 0b18186c7..6f6fc774d 100644 --- a/src/Corporation/ui/ThrowPartyPopup.tsx +++ b/src/Corporation/ui/ThrowPartyPopup.tsx @@ -27,9 +27,9 @@ export function ThrowPartyPopup(props: IProps): React.ReactElement { 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); + let mult = 0; + for (let i = 0; i < props.office.employees.length; ++i) { + mult = props.office.employees[i].throwParty(cost); } dialogBoxCreate("You threw a party for the office! The morale and happiness " + "of each employee increased by " + numeralWrapper.formatPercentage((mult-1))); From d9c9c30fdda98f3ec6924271c0b4a64b09f70786 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Tue, 31 Aug 2021 16:19:58 -0400 Subject: [PATCH 27/30] more conversion --- src/Corporation/OfficeSpace.ts | 121 +------------------- src/Corporation/ui/HireEmployeePopup.tsx | 137 +++++++++++++++++++++++ src/Corporation/ui/IndustryOffice.tsx | 14 ++- 3 files changed, 148 insertions(+), 124 deletions(-) create mode 100644 src/Corporation/ui/HireEmployeePopup.tsx diff --git a/src/Corporation/OfficeSpace.ts b/src/Corporation/OfficeSpace.ts index 4a419ffea..057813df2 100644 --- a/src/Corporation/OfficeSpace.ts +++ b/src/Corporation/OfficeSpace.ts @@ -1,22 +1,11 @@ import { EmployeePositions } from "./EmployeePositions"; import { CorporationConstants } from "./data/Constants"; import { getRandomInt } from "../../utils/helpers/getRandomInt"; -import { formatNumber, generateRandomString } from "../../utils/StringHelperFunctions"; +import { generateRandomString } from "../../utils/StringHelperFunctions"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver"; -import { yesNoTxtInpBoxCreate, - yesNoTxtInpBoxGetYesButton, - yesNoTxtInpBoxGetNoButton, - yesNoTxtInpBoxGetInput, - 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"; import { IIndustry } from "./IIndustry"; import { ICorporation } from './ICorporation'; -import { IPlayer } from "../PersonObjects/IPlayer"; interface IParams { loc?: string; @@ -139,114 +128,6 @@ export class OfficeSpace { this.employeeProd.total = total; } - //Takes care of UI as well - findEmployees(player: IPlayer, corporation: ICorporation): void { - 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:", - }); - - function createEmpDiv(employee: Employee, office: OfficeSpace): HTMLElement { - 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(player, employee, corporation); - 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(player: IPlayer, employee: Employee, corporation: ICorporation): void { - 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); - corporation.rerender(player); - 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; diff --git a/src/Corporation/ui/HireEmployeePopup.tsx b/src/Corporation/ui/HireEmployeePopup.tsx new file mode 100644 index 000000000..e4571d220 --- /dev/null +++ b/src/Corporation/ui/HireEmployeePopup.tsx @@ -0,0 +1,137 @@ +import React, { useState } from 'react'; +import { createPopup, removePopup } from "../../ui/React/createPopup"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { CorporationConstants } from "../data/Constants"; +import { ICorporation } from "../ICorporation"; +import { OfficeSpace } from "../OfficeSpace"; +import { IPlayer } from "../../PersonObjects/IPlayer"; +import { getRandomInt } from "../../../utils/helpers/getRandomInt"; +import { formatNumber } from "../../../utils/StringHelperFunctions"; +import { Employee } from "../Employee"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; + +interface INameEmployeeProps { + office: OfficeSpace; + corp: ICorporation; + popupId: string; + employee: Employee; + player: IPlayer; +} + +function NameEmployeePopup(props: INameEmployeeProps): React.ReactElement { + const [name, setName] = useState(''); + function nameEmployee(): void { + for (let i = 0; i < props.office.employees.length; ++i) { + if (props.office.employees[i].name === name) { + dialogBoxCreate("You already have an employee with this nickname!"); + return; + } + } + props.employee.name = name; + props.office.employees.push(props.employee); + props.corp.rerender(props.player); + removePopup(props.popupId); + } + + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) nameEmployee(); + } + + function onChange(event: React.ChangeEvent): void { + setName(event.target.value); + } + + return (<> +

Give your employee a nickname!

+ + + ); +} + +interface IHireEmployeeProps { + employee: Employee; + office: OfficeSpace; + popupId: string; + player: IPlayer; + corp: ICorporation; +} + +function HireEmployeeButton(props: IHireEmployeeProps): React.ReactElement { + function hire(): void { + const popupId = "cmpy-mgmt-name-employee-popup"; + createPopup(popupId, NameEmployeePopup, { + office: props.office, + corp: props.corp, + popupId: popupId, + player: props.player, + employee: props.employee, + }); + removePopup(props.popupId); + } + + return (
+ Intelligence: {formatNumber(props.employee.int, 1)}
+ Charisma: {formatNumber(props.employee.cha, 1)}
+ Experience: {formatNumber(props.employee.exp, 1)}
+ Creativity: {formatNumber(props.employee.cre, 1)}
+ Efficiency: {formatNumber(props.employee.eff, 1)}
+ Salary: {numeralWrapper.formatMoney(props.employee.sal)} \ s
+
); +} + +interface IProps { + office: OfficeSpace; + corp: ICorporation; + popupId: string; + player: IPlayer; +} + +// Create a popup that lets the player manage exports +export function HireEmployeePopup(props: IProps): React.ReactElement { + if (props.office.atCapacity()) return (<>); + + //Generate three random employees (meh, decent, amazing) + const mult1 = getRandomInt(25, 50)/100; + const mult2 = getRandomInt(51, 75)/100; + const mult3 = getRandomInt(76, 100)/100; + const int = getRandomInt(50, 100); + const cha = getRandomInt(50, 100); + const exp = getRandomInt(50, 100); + const cre = getRandomInt(50, 100); + const eff = getRandomInt(50, 100); + const 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, + }); + + return (<> +

Select one of the following candidates for hire:

+ + + + ); +} diff --git a/src/Corporation/ui/IndustryOffice.tsx b/src/Corporation/ui/IndustryOffice.tsx index faa35b039..fa09e3a12 100644 --- a/src/Corporation/ui/IndustryOffice.tsx +++ b/src/Corporation/ui/IndustryOffice.tsx @@ -11,6 +11,7 @@ import { numeralWrapper } from "../../ui/numeralFormat"; import { getSelectText } from "../../../utils/uiHelpers/getSelectData"; import { createPopup } from "../../ui/React/createPopup"; import { UpgradeOfficeSizePopup } from "./UpgradeOfficeSizePopup"; +import { HireEmployeePopup } from "./HireEmployeePopup"; import { ThrowPartyPopup } from "./ThrowPartyPopup"; import { ICorporation } from "../ICorporation"; import { IPlayer } from "../../PersonObjects/IPlayer"; @@ -550,7 +551,6 @@ export function IndustryOffice(props: IProps): React.ReactElement { ) } - const corp = props.corp; const division = props.routing.currentDivision; // Validated in constructor if(division === null) return (<>); const office = division.offices[props.currentCity]; // Validated in constructor @@ -570,9 +570,15 @@ export function IndustryOffice(props: IProps): React.ReactElement { } } - function hireEmployeeButtonOnClick(): void { + function openHireEmployeePopup(): void { if(office === 0) return; - office.findEmployees(props.player, corp); + const popupId = "cmpy-mgmt-hire-employee-popup"; + createPopup(popupId, HireEmployeePopup, { + office: office, + corp: props.corp, + popupId: popupId, + player: props.player, + }); } // Autohire employee button @@ -614,7 +620,7 @@ export function IndustryOffice(props: IProps): React.ReactElement {

Office Space

Size: {office.employees.length} / {office.size} employees

-
+ ) } + function openSellSharesPopup(): void { + const popupId = "cmpy-mgmt-sell-shares-popup"; + createPopup(popupId, SellSharesPopup, { + corp: props.corp, + player: props.player, + popupId: popupId, + }); + } + + function openBuybackSharesPopup(): void { + const popupId = "corp-buyback-shares-popup"; + createPopup(popupId, BuybackSharesPopup, { + player: props.player, + popupId: popupId, + corp: props.corp, + }); + } + + function openIssueNewSharesPopup(): void { + const popupId = "cmpy-mgmt-issue-new-shares-popup"; + createPopup(popupId, IssueNewSharesPopup, { + popupId: popupId, + corp: props.corp, + }); + } + + function openIssueDividendsPopup(): void { + const popupId = "cmpy-mgmt-issue-dividends-popup"; + createPopup(popupId, IssueDividendsPopup, { + popupId: popupId, + corp: props.corp, + }); + } + // Render the buttons for when your Corporation has gone public - function renderPublicButtons(generalBtns: GeneralBtns): React.ReactElement { + function PublicButtons(): React.ReactElement { const corp = props.corp; const sellSharesOnCd = (corp.shareSaleCooldown > 0); @@ -242,153 +233,135 @@ export function Overview(props: IProps): React.ReactElement { : "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 = createButton({ - class: sellSharesClass, - display: "inline-block", - onClick: function(event: React.MouseEvent) { - if(!event.isTrusted) return; - const popupId = "cmpy-mgmt-sell-shares-popup"; - createPopup(popupId, SellSharesPopup, { - corp: props.corp, - player: props.player, - popupId: popupId, - }); - }, - text: "Sell Shares", - tooltip: sellSharesTooltip, - }); - - function openBuybackSharesPopup(): void { - 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: openBuybackSharesPopup, - text: "Buyback shares", - 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 ? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown) : "Issue new equity shares to raise capital."; - const issueNewSharesBtn = createButton({ - class: issueNewSharesClass, - display: "inline-block", - 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: openIssueDividendsPopup, - text: "Issue Dividends", - tooltip: "Manage the dividends that are paid out to shareholders (including yourself)", - }); return ( -
- {generalBtns.getStarterGuide} - {sellSharesBtn} - {buybackSharesBtn} + <> +
+ ) } // Render the UI for Corporation upgrades - function renderUpgrades(): React.ReactElement { + function Upgrades(): React.ReactElement { // Don't show upgrades if (props.corp.divisions.length <= 0) { return (<>); } - // Create an array of all Unlocks - const unlockUpgrades: React.ReactElement[] = []; - Object.values(CorporationUnlockUpgrades).forEach((unlockData) => { - if (props.corp.unlockUpgrades[unlockData[0]] === 0) { - unlockUpgrades.push(); - } - }); - - interface UpgradeData { - upgradeData: CorporationUpgrade; - upgradeLevel: number; - } - - // Create an array of properties of all unlocks - const levelableUpgradeProps: UpgradeData[] = []; - for (let i = 0; i < props.corp.upgrades.length; ++i) { - const upgradeData = CorporationUpgrades[i]; - const level = props.corp.upgrades[i]; - - levelableUpgradeProps.push({ - upgradeData: upgradeData, - upgradeLevel: level, - }); - } - - return (

Unlocks

- {unlockUpgrades} + { + Object.values(CorporationUnlockUpgrades) + .filter((upgrade: CorporationUnlockUpgrade) => props.corp.unlockUpgrades[upgrade[0]] === 0) + .map((upgrade: CorporationUnlockUpgrade) => + ) + }

Upgrades

{ - levelableUpgradeProps.map((data: UpgradeData) => CorporationUpgrades[i]) + .map((upgrade: CorporationUpgrade) => , ) }
- ) + ); } return (
-

- {renderButtons()} +

+ Total Funds: {Money(props.corp.funds.toNumber())}
+ Total Revenue: {Money(props.corp.revenue.toNumber())} / s
+ Total Expenses: {Money(props.corp.expenses.toNumber())} / s
+ Total Profits: {Money(profit)} / s
+ + Publicly Traded: {(props.corp.public ? "Yes" : "No")}
+ Owned Stock Shares: {numeralWrapper.format(props.corp.numShares, '0.000a')}
+ Stock Price: {(props.corp.public ? Money(props.corp.sharePrice) : "N/A")}
+

+

+ Total Stock Shares: {numeralWrapper.format(props.corp.totalShares, "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")} +
+

+

+ + + + + + + + +
- {renderUpgrades()} + +
+
+
+
) } diff --git a/src/Corporation/ui/UnlockUpgrade.tsx b/src/Corporation/ui/UnlockUpgrade.tsx index 5749bb180..785893d83 100644 --- a/src/Corporation/ui/UnlockUpgrade.tsx +++ b/src/Corporation/ui/UnlockUpgrade.tsx @@ -6,6 +6,8 @@ import { dialogBoxCreate } from "../../../utils/DialogBox"; import { CorporationUnlockUpgrade } from "../data/CorporationUnlockUpgrades"; import { ICorporation } from "../ICorporation"; import { IPlayer } from "../../PersonObjects/IPlayer"; +import { UnlockUpgrade as UU } from "../Actions"; +import { Money } from "../../ui/React/Money"; interface IProps { upgradeData: CorporationUnlockUpgrade; @@ -15,16 +17,15 @@ interface IProps { export function UnlockUpgrade(props: IProps): React.ReactElement { const data = props.upgradeData; - const text = `${data[2]} - ${numeralWrapper.formatMoney(data[1])}`; + const text = <>{data[2]} - {Money(data[1])}; const tooltip = data[3]; function onClick(): void { - const corp = props.corp; - if (corp.funds.lt(data[1])) { - dialogBoxCreate("Insufficient funds"); - } else { - corp.unlock(data); - corp.rerender(props.player); + try { + UU(props.corp, props.upgradeData); + } catch(err) { + dialogBoxCreate(err); } + props.corp.rerender(props.player); } return ( diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 05b56914b..cdba2f86d 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -20,6 +20,14 @@ import { CompanyPosition } from "./Company/CompanyPosition"; import { CompanyPositions } from "./Company/CompanyPositions"; import { CONSTANTS } from "./Constants"; import { DarkWebItems } from "./DarkWeb/DarkWebItems"; +import { + NewIndustry, + NewCity, + UnlockUpgrade, + LevelUpgrade, + IssueDividends } from "./Corporation/Actions"; +import { CorporationUnlockUpgrades } from "./Corporation/data/CorporationUnlockUpgrades"; +import { CorporationUpgrades } from "./Corporation/data/CorporationUpgrades"; import { calculateHackingChance, calculateHackingExpGain, @@ -4097,6 +4105,32 @@ function NetscriptFunctions(workerScript) { }, }, // End Bladeburner + corporation: { + expandIndustry: function(industryName, divisionName) { + NewIndustry(Player.corporation, industryName, divisionName); + }, + expandCity: function(divisionName, cityName) { + const division = Player.corporation.divisions.find(div => div.name === divisionName); + if(division === undefined) throw new Error("No division named '${divisionName}'"); + NewCity(Player.corporation, division, cityName); + }, + unlockUpgrade: function(upgradeName) { + const upgrade = Object.values(CorporationUnlockUpgrades). + find(upgrade => upgrade[2] === upgradeName); + if(upgrade === undefined) throw new Error("No upgrade named '${upgradeName}'") + UnlockUpgrade(Player.corporation, upgrade); + }, + levelUpgrade: function(upgradeName) { + const upgrade = Object.values(CorporationUpgrades). + find(upgrade => upgrade[4] === upgradeName); + if(upgrade === undefined) throw new Error("No upgrade named '${upgradeName}'") + LevelUpgrade(Player.corporation, upgrade); + }, + issueDividends: function(percent) { + IssueDividends(Player.corporation, percent); + }, + }, // End Corporation API + // Coding Contract API codingcontract: { attempt: function(answer, fn, ip=workerScript.serverIp, { returnReward } = {}) { diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts index 76894ed75..bed14af84 100644 --- a/src/PersonObjects/IPlayer.ts +++ b/src/PersonObjects/IPlayer.ts @@ -19,7 +19,8 @@ import { LocationName } from "../Locations/data/LocationNames"; import { Server } from "../Server/Server"; import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile"; import { MoneySourceTracker } from "../utils/MoneySourceTracker"; -import { Exploit } from "../Exploits/Exploit"; +import { Exploit } from "../Exploits/Exploit"; +import { ICorporation } from "../Corporation/ICorporation"; export interface IPlayer { // Class members @@ -28,7 +29,7 @@ export interface IPlayer { bitNodeN: number; city: CityName; companyName: string; - corporation: any; + corporation: ICorporation; currentServer: string; factions: string[]; factionInvitations: string[]; From 8d17495e85861fa13956ac5c82765a3311eb8f93 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Thu, 2 Sep 2021 00:36:33 -0400 Subject: [PATCH 29/30] corp API --- src/Corporation/Actions.ts | 148 +++++++++++++++++++++ src/Corporation/ui/ExpandNewCityPopup.tsx | 2 +- src/Corporation/ui/IndustryWarehouse.tsx | 4 +- src/Corporation/ui/IssueDividendsPopup.tsx | 2 +- src/Corporation/ui/LevelableUpgrade.tsx | 2 +- src/Corporation/ui/NewIndustryPopup.tsx | 2 +- src/Corporation/ui/SellMaterialPopup.tsx | 61 +-------- src/Corporation/ui/SellProductPopup.tsx | 92 +------------ src/Corporation/ui/UnlockUpgrade.tsx | 2 +- src/NetscriptFunctions.js | 53 +++++++- 10 files changed, 215 insertions(+), 153 deletions(-) diff --git a/src/Corporation/Actions.ts b/src/Corporation/Actions.ts index bfa6be10e..3d77b76a1 100644 --- a/src/Corporation/Actions.ts +++ b/src/Corporation/Actions.ts @@ -4,8 +4,12 @@ import { IndustryStartingCosts } from "./IndustryData"; import { Industry } from "./Industry"; import { CorporationConstants } from "./data/Constants"; import { OfficeSpace } from "./OfficeSpace"; +import { Material } from "./Material"; +import { Product } from "./Product"; +import { Warehouse } from "./Warehouse"; import { CorporationUnlockUpgrade } from "./data/CorporationUnlockUpgrades"; import { CorporationUpgrade } from "./data/CorporationUpgrades"; +import { Cities } from "../Locations/Cities"; export function NewIndustry(corporation: ICorporation, industry: string, name: string): void { for (let i = 0; i < corporation.divisions.length; ++i) { @@ -70,4 +74,148 @@ export function IssueDividends(corporation: ICorporation, percent: number): void } corporation.dividendPercentage = percent*100; +} + +export function SellMaterial(mat: Material, amt: string, price: string): void { + if(price === '') price = '0'; + if(amt === '') amt = '0'; + let cost = price.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) { + throw new Error("Invalid value or expression for sell price field: " + e); + } + + if (temp == null || isNaN(parseFloat(temp))) { + throw new Error("Invalid value or expression for sell price field"); + } + + if (cost.includes("MP")) { + mat.sCost = cost; //Dynamically evaluated + } else { + mat.sCost = temp; + } + + //Parse quantity + if (amt.includes("MAX") || amt.includes("PROD")) { + let q = amt.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) { + throw new Error("Invalid value or expression for sell price field: " + e); + } + + if (tempQty == null || isNaN(parseFloat(tempQty))) { + throw new Error("Invalid value or expression for sell price field"); + } + + mat.sllman[0] = true; + mat.sllman[1] = q; //Use sanitized input + } else if (isNaN(parseFloat(amt))) { + throw new Error("Invalid value for sell quantity field! Must be numeric or 'MAX'"); + } else { + let q = parseFloat(amt); + if (isNaN(q)) {q = 0;} + if (q === 0) { + mat.sllman[0] = false; + mat.sllman[1] = 0; + } else { + mat.sllman[0] = true; + mat.sllman[1] = q; + } + } +} + +export function SellProduct(product: Product, city: string, amt: string, price: string, all: boolean): void { + //Parse price + if (price.includes("MP")) { + //Dynamically evaluated quantity. First test to make sure its valid + //Sanitize input, then replace dynamic variables with arbitrary numbers + price = price.replace(/\s+/g, ''); + price = price.replace(/[^-()\d/*+.MP]/g, ''); + let temp = price.replace(/MP/g, '1'); + try { + temp = eval(temp); + } catch(e) { + throw new Error("Invalid value or expression for sell quantity field: " + e); + } + if (temp == null || isNaN(parseFloat(temp))) { + throw new Error("Invalid value or expression for sell quantity field."); + } + product.sCost = price; //Use sanitized price + } else { + const cost = parseFloat(price); + if (isNaN(cost)) { + throw new Error("Invalid value for sell price field"); + } + product.sCost = cost; + } + + // Array of all cities. Used later + const cities = Object.keys(Cities); + + // Parse quantity + if (amt.includes("MAX") || amt.includes("PROD")) { + //Dynamically evaluated quantity. First test to make sure its valid + let qty = amt.replace(/\s+/g, ''); + qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); + let temp = qty.replace(/MAX/g, '1'); + temp = temp.replace(/PROD/g, '1'); + try { + temp = eval(temp); + } catch(e) { + throw new Error("Invalid value or expression for sell price field: " + e); + } + + if (temp == null || isNaN(parseFloat(temp))) { + throw new Error("Invalid value or expression for sell price field"); + } + if (all) { + 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(parseFloat(amt))) { + throw new Error("Invalid value for sell quantity field! Must be numeric"); + } else { + let qty = parseFloat(amt); + if (isNaN(qty)) {qty = 0;} + if (qty === 0) { + if (all) { + for (let i = 0; i < cities.length; ++i) { + const tempCity = cities[i]; + product.sllman[tempCity][0] = false; + product.sllman[tempCity][1] = ''; + } + } else { + product.sllman[city][0] = false; + product.sllman[city][1] = ''; + } + } else { + if (all) { + 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; + } + } + } +} + +export function SetSmartSupply(warehouse: Warehouse, smartSupply: boolean): void { + warehouse.smartSupplyEnabled = smartSupply; } \ No newline at end of file diff --git a/src/Corporation/ui/ExpandNewCityPopup.tsx b/src/Corporation/ui/ExpandNewCityPopup.tsx index f987103f4..f40807fcc 100644 --- a/src/Corporation/ui/ExpandNewCityPopup.tsx +++ b/src/Corporation/ui/ExpandNewCityPopup.tsx @@ -23,7 +23,7 @@ export function ExpandNewCityPopup(props: IProps): React.ReactElement { try { NewCity(props.corp, props.division, dropdown.current.value); } catch(err) { - dialogBoxCreate(err); + dialogBoxCreate(err+''); return; } diff --git a/src/Corporation/ui/IndustryWarehouse.tsx b/src/Corporation/ui/IndustryWarehouse.tsx index 521115f72..cce46cefb 100644 --- a/src/Corporation/ui/IndustryWarehouse.tsx +++ b/src/Corporation/ui/IndustryWarehouse.tsx @@ -25,6 +25,8 @@ import { ICorporation } from "../ICorporation"; import { IIndustry } from "../IIndustry"; import { CorporationRouting } from "./Routing"; import { IPlayer } from "../../PersonObjects/IPlayer"; +import { SetSmartSupply } from "../Actions"; + interface IProductProps { corp: ICorporation; @@ -514,7 +516,7 @@ export function IndustryWarehouse(props: IProps): React.ReactElement { const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox"; function smartSupplyOnChange(e: React.ChangeEvent): void { if(warehouse === 0) return; - warehouse.smartSupplyEnabled = e.target.checked; + SetSmartSupply(warehouse, e.target.checked); corp.rerender(props.player); } diff --git a/src/Corporation/ui/IssueDividendsPopup.tsx b/src/Corporation/ui/IssueDividendsPopup.tsx index eb022d58e..881af18fa 100644 --- a/src/Corporation/ui/IssueDividendsPopup.tsx +++ b/src/Corporation/ui/IssueDividendsPopup.tsx @@ -20,7 +20,7 @@ export function IssueDividendsPopup(props: IProps): React.ReactElement { try { IssueDividends(props.corp, percent/100); } catch(err) { - dialogBoxCreate(err); + dialogBoxCreate(err+''); } removePopup(props.popupId); diff --git a/src/Corporation/ui/LevelableUpgrade.tsx b/src/Corporation/ui/LevelableUpgrade.tsx index a1a9e5170..358bc13bd 100644 --- a/src/Corporation/ui/LevelableUpgrade.tsx +++ b/src/Corporation/ui/LevelableUpgrade.tsx @@ -29,7 +29,7 @@ export function LevelableUpgrade(props: IProps): React.ReactElement { try { LevelUpgrade(props.corp, props.upgrade); } catch(err) { - dialogBoxCreate(err); + dialogBoxCreate(err+''); } props.corp.rerender(props.player); } diff --git a/src/Corporation/ui/NewIndustryPopup.tsx b/src/Corporation/ui/NewIndustryPopup.tsx index a841aa295..8fa4f0aff 100644 --- a/src/Corporation/ui/NewIndustryPopup.tsx +++ b/src/Corporation/ui/NewIndustryPopup.tsx @@ -28,7 +28,7 @@ export function NewIndustryPopup(props: IProps): React.ReactElement { try { NewIndustry(props.corp, industry, name); } catch(err) { - dialogBoxCreate(err); + dialogBoxCreate(err+''); return; } diff --git a/src/Corporation/ui/SellMaterialPopup.tsx b/src/Corporation/ui/SellMaterialPopup.tsx index 4b4828ba0..b209a90e0 100644 --- a/src/Corporation/ui/SellMaterialPopup.tsx +++ b/src/Corporation/ui/SellMaterialPopup.tsx @@ -3,6 +3,7 @@ import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { ICorporation } from "../ICorporation"; import { Material } from "../Material"; +import { SellMaterial } from "../Actions"; function initialPrice(mat: Material): string { let val = mat.sCost ? mat.sCost+'' : ''; @@ -26,64 +27,10 @@ export function SellMaterialPopup(props: IProps): React.ReactElement { 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; - } + SellMaterial(props.mat, amt, price); + } catch(err) { + dialogBoxCreate(err+''); } removePopup(props.popupId); diff --git a/src/Corporation/ui/SellProductPopup.tsx b/src/Corporation/ui/SellProductPopup.tsx index 3c60fbc32..d04f13f60 100644 --- a/src/Corporation/ui/SellProductPopup.tsx +++ b/src/Corporation/ui/SellProductPopup.tsx @@ -3,6 +3,7 @@ import { dialogBoxCreate } from "../../../utils/DialogBox"; import { removePopup } from "../../ui/React/createPopup"; import { Cities } from "../../Locations/Cities"; import { Product } from "../Product"; +import { SellProduct } from "../Actions"; function initialPrice(product: Product): string { let val = product.sCost ? product.sCost+'' : ''; @@ -31,93 +32,10 @@ export function SellProductPopup(props: IProps): React.ReactElement { } 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 - let price = px.replace(/\s+/g, ''); - price = price.replace(/[^-()\d/*+.MP]/g, ''); - let 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 { - const 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 - let qty = iQty.replace(/\s+/g, ''); - qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); - let 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; - } - } + try { + SellProduct(props.product, props.city, iQty, px, checked); + } catch(err) { + dialogBoxCreate(err+''); } removePopup(props.popupId); diff --git a/src/Corporation/ui/UnlockUpgrade.tsx b/src/Corporation/ui/UnlockUpgrade.tsx index 785893d83..5b8db7059 100644 --- a/src/Corporation/ui/UnlockUpgrade.tsx +++ b/src/Corporation/ui/UnlockUpgrade.tsx @@ -23,7 +23,7 @@ export function UnlockUpgrade(props: IProps): React.ReactElement { try { UU(props.corp, props.upgradeData); } catch(err) { - dialogBoxCreate(err); + dialogBoxCreate(err+''); } props.corp.rerender(props.player); } diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index cdba2f86d..be376d144 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -25,7 +25,10 @@ import { NewCity, UnlockUpgrade, LevelUpgrade, - IssueDividends } from "./Corporation/Actions"; + IssueDividends, + SellMaterial, + SellProduct, + SetSmartSupply } from "./Corporation/Actions"; import { CorporationUnlockUpgrades } from "./Corporation/data/CorporationUnlockUpgrades"; import { CorporationUpgrades } from "./Corporation/data/CorporationUpgrades"; import { @@ -563,6 +566,39 @@ function NetscriptFunctions(workerScript) { return Augmentations[name]; } + function getDivision(divisionName) { + const division = Player.corporation.divisions.find(div => div.name === divisionName); + if(division === undefined) + throw new Error(`No division named '${divisionName}'`); + return division; + } + + function getWarehouse(divisionName, cityName) { + const division = getDivision(divisionName); + if(!(cityName in division.warehouses)) + throw new Error(`Invalid city name '${cityName}'`); + const warehouse = division.warehouses[cityName]; + if(warehouse === 0) + throw new Error(`${division.name} has not expanded to '${cityName}'`); + return warehouse; + } + + function getMaterial(divisionName, cityName, materialName) { + const warehouse = getWarehouse(divisionName, cityName); + const material = warehouse.materials[materialName]; + if(material === undefined) + throw new Error(`Invalid material name: '${materialName}'`); + return material; + } + + function getProduct(divisionName, productName) { + const division = getDivision(divisionName); + const product = division.products[productName]; + if(product === undefined) + throw new Error(`Invalid product name: '${productName}'`); + return product; + } + const runAfterReset = function(cbScript=null) { //Run a script after reset if (cbScript && isString(cbScript)) { @@ -4110,8 +4146,7 @@ function NetscriptFunctions(workerScript) { NewIndustry(Player.corporation, industryName, divisionName); }, expandCity: function(divisionName, cityName) { - const division = Player.corporation.divisions.find(div => div.name === divisionName); - if(division === undefined) throw new Error("No division named '${divisionName}'"); + const division = getDivision(divisionName); NewCity(Player.corporation, division, cityName); }, unlockUpgrade: function(upgradeName) { @@ -4129,6 +4164,18 @@ function NetscriptFunctions(workerScript) { issueDividends: function(percent) { IssueDividends(Player.corporation, percent); }, + sellMaterial: function(divisionName, cityName, materialName, amt, price) { + const material = getMaterial(divisionName, cityName, materialName); + SellMaterial(material, amt, price); + }, + sellProduct: function(divisionName, cityName, productName, amt, price, all) { + const product = getProduct(divisionName, productName); + SellProduct(product, cityName, amt, price, all); + }, + setSmartSupply: function(divisionName, cityName, enabled) { + const warehouse = getWarehouse(divisionName, cityName); + SetSmartSupply(warehouse, enabled); + }, }, // End Corporation API // Coding Contract API From 2866bfaa7040bb0f0376a7c28c68ad0f2e851128 Mon Sep 17 00:00:00 2001 From: Olivier Gagnon Date: Fri, 3 Sep 2021 16:02:41 -0400 Subject: [PATCH 30/30] more corp API --- src/Corporation/Actions.ts | 7 +++++++ src/Corporation/ui/PurchaseMaterialPopup.tsx | 13 +++++++------ src/NetscriptFunctions.js | 6 +++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Corporation/Actions.ts b/src/Corporation/Actions.ts index 3d77b76a1..d3bfbc80f 100644 --- a/src/Corporation/Actions.ts +++ b/src/Corporation/Actions.ts @@ -218,4 +218,11 @@ export function SellProduct(product: Product, city: string, amt: string, price: export function SetSmartSupply(warehouse: Warehouse, smartSupply: boolean): void { warehouse.smartSupplyEnabled = smartSupply; +} + +export function BuyMaterial(material: Material, amt: number): void { + if (isNaN(amt)) { + throw new Error(`Invalid amount '${amt}' to buy material '${material.name}'`); + } + material.buy = amt; } \ No newline at end of file diff --git a/src/Corporation/ui/PurchaseMaterialPopup.tsx b/src/Corporation/ui/PurchaseMaterialPopup.tsx index 141176855..19166a030 100644 --- a/src/Corporation/ui/PurchaseMaterialPopup.tsx +++ b/src/Corporation/ui/PurchaseMaterialPopup.tsx @@ -7,6 +7,7 @@ import { Material } from "../Material"; import { IIndustry } from "../IIndustry"; import { ICorporation } from "../ICorporation"; import { numeralWrapper } from "../../ui/numeralFormat"; +import { BuyMaterial } from "../Actions"; interface IBulkPurchaseTextProps { warehouse: Warehouse; @@ -94,13 +95,13 @@ export function PurchaseMaterialPopup(props: IProps): React.ReactElement { function purchaseMaterial(): void { if(buyAmt === null) return; - if (isNaN(buyAmt)) { - dialogBoxCreate("Invalid amount"); - } else { - props.mat.buy = buyAmt; - if (isNaN(props.mat.buy)) props.mat.buy = 0; - removePopup(props.popupId); + try { + BuyMaterial(props.mat, buyAmt) + } catch(err) { + dialogBoxCreate(err+''); } + + removePopup(props.popupId); } function clearPurchase(): void { diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index be376d144..7745d2312 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -28,7 +28,8 @@ import { IssueDividends, SellMaterial, SellProduct, - SetSmartSupply } from "./Corporation/Actions"; + SetSmartSupply, + BuyMaterial } from "./Corporation/Actions"; import { CorporationUnlockUpgrades } from "./Corporation/data/CorporationUnlockUpgrades"; import { CorporationUpgrades } from "./Corporation/data/CorporationUpgrades"; import { @@ -4176,6 +4177,9 @@ function NetscriptFunctions(workerScript) { const warehouse = getWarehouse(divisionName, cityName); SetSmartSupply(warehouse, enabled); }, + BuyMaterial: function(divisionName, cityName, materialName, amt) { + + }, }, // End Corporation API // Coding Contract API