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) { %>
-