diff --git a/src/Corporation/Corporation.d.ts b/src/Corporation/Corporation.d.ts new file mode 100644 index 000000000..36b7c3f3b --- /dev/null +++ b/src/Corporation/Corporation.d.ts @@ -0,0 +1,3 @@ +export class Industry { + constructor(props: any) +} \ No newline at end of file diff --git a/src/Corporation/IndustryData.ts b/src/Corporation/IndustryData.ts deleted file mode 100644 index a7154026f..000000000 --- a/src/Corporation/IndustryData.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { ResearchTree } from "./ResearchTree"; -import { getBaseResearchTreeCopy, - getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree"; - -import { numeralWrapper } from "../ui/numeralFormat"; - -interface IIndustryMap { - Energy: T; - Utilities: T; - Agriculture: T; - Fishing: T; - Mining: T; - Food: T; - Tobacco: T; - Chemical: T; - Pharmaceutical: T; - Computer: T; - Robotics: T; - Software: T; - Healthcare: T; - RealEstate: T; -} - -// Map of official names for each Industry -export const Industries: IIndustryMap = { - Energy: "Energy", - Utilities: "Water Utilities", - Agriculture: "Agriculture", - Fishing: "Fishing", - Mining: "Mining", - Food: "Food", - Tobacco: "Tobacco", - Chemical: "Chemical", - Pharmaceutical: "Pharmaceutical", - Computer: "Computer Hardware", - Robotics: "Robotics", - Software: "Software", - Healthcare: "Healthcare", - RealEstate: "RealEstate", -} - -// Map of how much money it takes to start each industry -export const IndustryStartingCosts: IIndustryMap = { - Energy: 225e9, - Utilities: 150e9, - Agriculture: 40e9, - Fishing: 80e9, - Mining: 300e9, - Food: 10e9, - Tobacco: 20e9, - Chemical: 70e9, - Pharmaceutical: 200e9, - Computer: 500e9, - Robotics: 1e12, - Software: 25e9, - Healthcare: 750e9, - RealEstate: 600e9, -} - -// Map of description for each industry -export const IndustryDescriptions: IIndustryMap = { - Energy: "Engage in the production and distribution of energy.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Energy, "$0.000a") + "
" + - "Recommended starting Industry: NO", - Utilities: "Distribute water and provide wastewater services.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Utilities, "$0.000a") + "
" + - "Recommended starting Industry: NO", - Agriculture: "Cultivate crops and breed livestock to produce food.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Agriculture, "$0.000a") + "
" + - "Recommended starting Industry: YES", - Fishing: "Produce food through the breeding and processing of fish and fish products.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Fishing, "$0.000a") + "
" + - "Recommended starting Industry: NO", - Mining: "Extract and process metals from the earth.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Mining, "$0.000a") + "
" + - "Recommended starting Industry: NO", - Food: "Create your own restaurants all around the world.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Food, "$0.000a") + "
" + - "Recommended starting Industry: YES", - Tobacco: "Create and distribute tobacco and tobacco-related products.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Tobacco, "$0.000a") + "
" + - "Recommended starting Industry: YES", - Chemical: "Produce industrial chemicals.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Chemical, "$0.000a") + "
" + - "Recommended starting Industry: NO", - Pharmaceutical: "Discover, develop, and create new pharmaceutical drugs.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Pharmaceutical, "$0.000a") + "
" + - "Recommended starting Industry: NO", - Computer: "Develop and manufacture new computer hardware and networking infrastructures.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Computer, "$0.000a") + "
" + - "Recommended starting Industry: NO", - Robotics: "Develop and create robots.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Robotics, "$0.000a") + "
" + - "Recommended starting Industry: NO", - Software: "Develop computer software and create AI Cores.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Software, "$0.000a") + "
" + - "Recommended starting Industry: YES", - Healthcare: "Create and manage hospitals.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Healthcare, "$0.000a") + "
" + - "Recommended starting Industry: NO", - RealEstate: "Develop and manage real estate properties.

" + - "Starting cost: " + numeralWrapper.format(IndustryStartingCosts.RealEstate, "$0.000a") + "
" + - "Recommended starting Industry: NO", -} - -// Map of available Research for each Industry. This data is held in a -// ResearchTree object -export const IndustryResearchTrees: IIndustryMap = { - Energy: getBaseResearchTreeCopy(), - Utilities: getBaseResearchTreeCopy(), - Agriculture: getBaseResearchTreeCopy(), - Fishing: getBaseResearchTreeCopy(), - Mining: getBaseResearchTreeCopy(), - Food: getProductIndustryResearchTreeCopy(), - Tobacco: getProductIndustryResearchTreeCopy(), - Chemical: getBaseResearchTreeCopy(), - Pharmaceutical: getProductIndustryResearchTreeCopy(), - Computer: getProductIndustryResearchTreeCopy(), - Robotics: getProductIndustryResearchTreeCopy(), - Software: getProductIndustryResearchTreeCopy(), - Healthcare: getProductIndustryResearchTreeCopy(), - RealEstate: getProductIndustryResearchTreeCopy(), -} - -export function resetIndustryResearchTrees(): void { - IndustryResearchTrees.Energy = getBaseResearchTreeCopy(); - IndustryResearchTrees.Utilities = getBaseResearchTreeCopy(); - IndustryResearchTrees.Agriculture = getBaseResearchTreeCopy(); - IndustryResearchTrees.Fishing = getBaseResearchTreeCopy(); - IndustryResearchTrees.Mining = getBaseResearchTreeCopy(); - IndustryResearchTrees.Food = getBaseResearchTreeCopy(); - IndustryResearchTrees.Tobacco = getBaseResearchTreeCopy(); - IndustryResearchTrees.Chemical = getBaseResearchTreeCopy(); - IndustryResearchTrees.Pharmaceutical = getBaseResearchTreeCopy(); - IndustryResearchTrees.Computer = getBaseResearchTreeCopy(); - IndustryResearchTrees.Robotics = getBaseResearchTreeCopy(); - IndustryResearchTrees.Software = getBaseResearchTreeCopy(); - IndustryResearchTrees.Healthcare = getBaseResearchTreeCopy(); - IndustryResearchTrees.RealEstate = getBaseResearchTreeCopy(); -} diff --git a/src/Corporation/IndustryData.tsx b/src/Corporation/IndustryData.tsx new file mode 100644 index 000000000..250a53be4 --- /dev/null +++ b/src/Corporation/IndustryData.tsx @@ -0,0 +1,143 @@ +import React from 'react'; +import { ResearchTree } from "./ResearchTree"; +import { getBaseResearchTreeCopy, + getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree"; + +import { numeralWrapper } from "../ui/numeralFormat"; +import { Money } from "../ui/React/Money"; + +interface IIndustryMap { + [key: string]: T | undefined; + Energy: T; + Utilities: T; + Agriculture: T; + Fishing: T; + Mining: T; + Food: T; + Tobacco: T; + Chemical: T; + Pharmaceutical: T; + Computer: T; + Robotics: T; + Software: T; + Healthcare: T; + RealEstate: T; +} + +// Map of official names for each Industry +export const Industries: IIndustryMap = { + Energy: "Energy", + Utilities: "Water Utilities", + Agriculture: "Agriculture", + Fishing: "Fishing", + Mining: "Mining", + Food: "Food", + Tobacco: "Tobacco", + Chemical: "Chemical", + Pharmaceutical: "Pharmaceutical", + Computer: "Computer Hardware", + Robotics: "Robotics", + Software: "Software", + Healthcare: "Healthcare", + RealEstate: "RealEstate", +} + +// Map of how much money it takes to start each industry +export const IndustryStartingCosts: IIndustryMap = { + Energy: 225e9, + Utilities: 150e9, + Agriculture: 40e9, + Fishing: 80e9, + Mining: 300e9, + Food: 10e9, + Tobacco: 20e9, + Chemical: 70e9, + Pharmaceutical: 200e9, + Computer: 500e9, + Robotics: 1e12, + Software: 25e9, + Healthcare: 750e9, + RealEstate: 600e9, +} + +// Map of description for each industry +export const IndustryDescriptions: IIndustryMap = { + Energy: (<>Engage in the production and distribution of energy.

+ Starting cost: {Money(IndustryStartingCosts.Energy)}
+ Recommended starting Industry: NO), + Utilities: (<>Distribute water and provide wastewater services.

+ Starting cost: {Money(IndustryStartingCosts.Utilities)}
+ Recommended starting Industry: NO), + Agriculture: (<>Cultivate crops and breed livestock to produce food.

+ Starting cost: {Money(IndustryStartingCosts.Agriculture)}
+ Recommended starting Industry: YES), + Fishing: (<>Produce food through the breeding and processing of fish and fish products.

+ Starting cost: {Money(IndustryStartingCosts.Fishing)}
+ Recommended starting Industry: NO), + Mining: (<>Extract and process metals from the earth.

+ Starting cost: {Money(IndustryStartingCosts.Mining)}
+ Recommended starting Industry: NO), + Food: (<>Create your own restaurants all around the world.

+ Starting cost: {Money(IndustryStartingCosts.Food)}
+ Recommended starting Industry: YES), + Tobacco: (<>Create and distribute tobacco and tobacco-related products.

+ Starting cost: {Money(IndustryStartingCosts.Tobacco)}
+ Recommended starting Industry: YES), + Chemical: (<>Produce industrial chemicals.

+ Starting cost: {Money(IndustryStartingCosts.Chemical)}
+ Recommended starting Industry: NO), + Pharmaceutical: (<>Discover, develop, and create new pharmaceutical drugs.

+ Starting cost: {Money(IndustryStartingCosts.Pharmaceutical)}
+ Recommended starting Industry: NO), + Computer: (<>Develop and manufacture new computer hardware and networking infrastructures.

+ Starting cost: {Money(IndustryStartingCosts.Computer)}
+ Recommended starting Industry: NO), + Robotics: (<>Develop and create robots.

+ Starting cost: {Money(IndustryStartingCosts.Robotics)}
+ Recommended starting Industry: NO), + Software: (<>Develop computer software and create AI Cores.

+ Starting cost: {Money(IndustryStartingCosts.Software)}
+ Recommended starting Industry: YES), + Healthcare: (<>Create and manage hospitals.

+ Starting cost: {Money(IndustryStartingCosts.Healthcare)}
+ Recommended starting Industry: NO), + RealEstate: (<>Develop and manage real estate properties.

+ Starting cost: {Money(IndustryStartingCosts.RealEstate)}
+ Recommended starting Industry: NO), +} + +// Map of available Research for each Industry. This data is held in a +// ResearchTree object +export const IndustryResearchTrees: IIndustryMap = { + Energy: getBaseResearchTreeCopy(), + Utilities: getBaseResearchTreeCopy(), + Agriculture: getBaseResearchTreeCopy(), + Fishing: getBaseResearchTreeCopy(), + Mining: getBaseResearchTreeCopy(), + Food: getProductIndustryResearchTreeCopy(), + Tobacco: getProductIndustryResearchTreeCopy(), + Chemical: getBaseResearchTreeCopy(), + Pharmaceutical: getProductIndustryResearchTreeCopy(), + Computer: getProductIndustryResearchTreeCopy(), + Robotics: getProductIndustryResearchTreeCopy(), + Software: getProductIndustryResearchTreeCopy(), + Healthcare: getProductIndustryResearchTreeCopy(), + RealEstate: getProductIndustryResearchTreeCopy(), +} + +export function resetIndustryResearchTrees(): void { + IndustryResearchTrees.Energy = getBaseResearchTreeCopy(); + IndustryResearchTrees.Utilities = getBaseResearchTreeCopy(); + IndustryResearchTrees.Agriculture = getBaseResearchTreeCopy(); + IndustryResearchTrees.Fishing = getBaseResearchTreeCopy(); + IndustryResearchTrees.Mining = getBaseResearchTreeCopy(); + IndustryResearchTrees.Food = getBaseResearchTreeCopy(); + IndustryResearchTrees.Tobacco = getBaseResearchTreeCopy(); + IndustryResearchTrees.Chemical = getBaseResearchTreeCopy(); + IndustryResearchTrees.Pharmaceutical = getBaseResearchTreeCopy(); + IndustryResearchTrees.Computer = getBaseResearchTreeCopy(); + IndustryResearchTrees.Robotics = getBaseResearchTreeCopy(); + IndustryResearchTrees.Software = getBaseResearchTreeCopy(); + IndustryResearchTrees.Healthcare = getBaseResearchTreeCopy(); + IndustryResearchTrees.RealEstate = getBaseResearchTreeCopy(); +} diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js index bfe6d3e53..3f586d8cf 100644 --- a/src/Corporation/ui/CorporationUIEventHandler.js +++ b/src/Corporation/ui/CorporationUIEventHandler.js @@ -47,666 +47,6 @@ export class CorporationEventHandler { this.routing = routing; } - // Create a popup that lets the player manage exports - createExportMaterialPopup(mat) { - const corp = this.corp; - - const popupId = "cmpy-mgmt-export-popup"; - const exportTxt = createElement("p", { - innerText:"Select the industry and city to export this material to, as well as " + - "how much of this material to export per second. You can set the export " + - "amount to 'MAX' to export all of the materials in this warehouse.", - }); - - //Select industry and city to export to - const citySelector = createElement("select", {class: "dropdown"}); - const industrySelector = createElement("select", { - class: "dropdown", - changeListener: () => { - const industryName = getSelectValue(industrySelector); - for (let i = 0; i < corp.divisions.length; ++i) { - if (corp.divisions[i].name == industryName) { - clearSelector(citySelector); - for (const cityName in corp.divisions[i].warehouses) { - if (corp.divisions[i].warehouses[cityName] instanceof Warehouse) { - citySelector.add(createElement("option", { - value:cityName, text:cityName, - })); - } - } - return; - } - } - }, - }); - - for (let i = 0; i < corp.divisions.length; ++i) { - industrySelector.add(createOptionElement(corp.divisions[i].name)); - } - - // Force change listener to initialize citySelector - industrySelector.dispatchEvent(new Event("change")); - - //Select amount to export - const exportAmount = createElement("input", { - class: "text-input", - placeholder:"Export amount / s", - }); - - const exportBtn = createElement("button", { - class: "std-button", display:"inline-block", innerText:"Export", - clickListener: () => { - const industryName = getSelectText(industrySelector); - const cityName = citySelector.options[citySelector.selectedIndex].text; - const amt = exportAmount.value; - - // Sanitize amt - let sanitizedAmt = amt.replace(/\s+/g, ''); - sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, ''); - let temp = sanitizedAmt.replace(/MAX/g, 1); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid expression entered for export amount: " + e); - return false; - } - - if (temp == null || isNaN(temp) || temp < 0) { - dialogBoxCreate("Invalid amount entered for export"); - return; - } - var exportObj = {ind:industryName, city:cityName, amt:sanitizedAmt}; - mat.exp.push(exportObj); - removeElementById(popupId); - return false; - }, - }); - - const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); - - const currExportsText = createElement("p", { - innerText:"Below is a list of all current exports of this material from this warehouse. " + - "Clicking on one of the exports below will REMOVE that export.", - }); - const currExports = []; - for (var i = 0; i < mat.exp.length; ++i) { - (function(i, mat, currExports){ - currExports.push(createElement("div", { - class:"cmpy-mgmt-existing-export", - innerHTML: "Industry: " + mat.exp[i].ind + "
" + - "City: " + mat.exp[i].city + "
" + - "Amount/s: " + mat.exp[i].amt, - clickListener:()=>{ - mat.exp.splice(i, 1); //Remove export object - removeElementById(popupId); - createExportMaterialPopup(mat); - }, - })); - })(i, mat, currExports); - } - createPopup(popupId, [exportTxt, industrySelector, citySelector, exportAmount, - exportBtn, cancelBtn, currExportsText].concat(currExports)); - } - - // Create a popup that lets the player issue & manage dividends - // This is created when the player clicks the "Issue Dividends" button in the overview panel - createIssueDividendsPopup() { - const popupId = "cmpy-mgmt-issue-dividends-popup"; - const descText = "Dividends are a distribution of a portion of the corporation's " + - "profits to the shareholders. This includes yourself, as well.

" + - "In order to issue dividends, simply allocate some percentage " + - "of your corporation's profits to dividends. This percentage must be an " + - `integer between 0 and ${DividendMaxPercentage}. (A percentage of 0 means no dividends will be ` + - "issued

" + - "Two important things to note:
" + - " * Issuing dividends will negatively affect your corporation's stock price
" + - " * Dividends are taxed. Taxes start at 50%, but can be decreased

" + - "Example: Assume your corporation makes $100m / sec in profit and you allocate " + - "40% of that towards dividends. That means your corporation will gain $60m / sec " + - "in funds and the remaining $40m / sec will be paid as dividends. Since your " + - "corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share " + - "per second before taxes."; - const txt = createElement("p", { innerHTML: descText }); - - let allocateBtn; - const dividendPercentInput = createElement("input", { - margin: "5px", - placeholder: "Dividend %", - type: "number", - onkeyup: (e) => { - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {allocateBtn.click();} - }, - }); - - allocateBtn = createElement("button", { - class: "std-button", - display: "inline-block", - innerText: "Allocate Dividend Percentage", - clickListener: () => { - const percentage = Math.round(parseInt(dividendPercentInput.value)); - if (isNaN(percentage) || percentage < 0 || percentage > DividendMaxPercentage) { - return dialogBoxCreate(`Invalid value. Must be an integer between 0 and ${DividendMaxPercentage}`); - } - - this.corp.dividendPercentage = percentage; - - removeElementById(popupId); - - this.rerender(); - return false; - }, - }); - - const cancelBtn = createPopupCloseButton(popupId, { - class: "std-button", - display: "inline-block", - innerText: "Cancel", - }); - - createPopup(popupId, [txt, dividendPercentInput, allocateBtn, cancelBtn]); - dividendPercentInput.focus(); - } - - // Create a popup that lets the player issue new shares - // This is created when the player clicks the "Issue New Shares" buttons in the overview panel - createIssueNewSharesPopup() { - const popupId = "cmpy-mgmt-issue-new-shares-popup"; - const maxNewSharesUnrounded = Math.round(this.corp.totalShares * 0.2); - const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); - - const descText = createElement("p", { - innerHTML: "You can issue new equity shares (i.e. stocks) in order to raise " + - "capital for your corporation.

" + - ` * You can issue at most ${numeralWrapper.format(maxNewShares, "0.000a")} new shares
` + - ` * New shares are sold at a 10% discount
` + - ` * You can only issue new shares once every 12 hours
` + - ` * Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share
` + - ` * Number of new shares issued must be a multiple of 10 million

` + - `When you choose to issue new equity, private shareholders have first priority for up to 50% of the new shares. ` + - `If they choose to exercise this option, these newly issued shares become private, restricted shares, which means ` + - `you cannot buy them back.`, - }); - - let issueBtn, newSharesInput; - const dynamicText = createElement("p", { - display: "block", - }); - - function updateDynamicText(corp) { - const newSharePrice = Math.round(corp.sharePrice * 0.9); - let newShares = parseInt(newSharesInput.value); - if (isNaN(newShares)) { - dynamicText.innerText = "Invalid input"; - return; - } - - // Round to nearest ten-millionth - newShares /= 10e6; - newShares = Math.round(newShares) * 10e6; - - if (newShares < 10e6) { - dynamicText.innerText = "Must issue at least 10 million new shares"; - return; - } - - if (newShares > maxNewShares) { - dynamicText.innerText = "You cannot issue that many shares"; - return; - } - - dynamicText.innerText = `Issue ${numeralWrapper.format(newShares, "0.000a")} new shares ` + - `for ${numeralWrapper.formatMoney(newShares * newSharePrice)}?` - } - newSharesInput = createElement("input", { - margin: "5px", - placeholder: "# New Shares", - type: "number", - onkeyup: (e) => { - e.preventDefault(); - if (e.keyCode === KEY.ENTER) { - issueBtn.click(); - } else { - updateDynamicText(this.corp); - } - }, - }); - - issueBtn = createElement("button", { - class: "std-button", - display: "inline-block", - innerText: "Issue New Shares", - clickListener: () => { - const newSharePrice = Math.round(this.corp.sharePrice * 0.9); - let newShares = parseInt(newSharesInput.value); - if (isNaN(newShares)) { - dialogBoxCreate("Invalid input for number of new shares"); - return; - } - - // Round to nearest ten-millionth - newShares = Math.round(newShares / 10e6) * 10e6; - - if (newShares < 10e6 || newShares > maxNewShares) { - dialogBoxCreate("Invalid input for number of new shares"); - return; - } - - const profit = newShares * newSharePrice; - this.corp.issueNewSharesCooldown = IssueNewSharesCooldown; - this.corp.totalShares += newShares; - - // Determine how many are bought by private investors - // Private investors get up to 50% at most - // Round # of private shares to the nearest millionth - let privateShares = getRandomInt(0, Math.round(newShares / 2)); - privateShares = Math.round(privateShares / 1e6) * 1e6; - - this.corp.issuedShares += (newShares - privateShares); - this.corp.funds = this.corp.funds.plus(profit); - this.corp.immediatelyUpdateSharePrice(); - - removeElementById(popupId); - dialogBoxCreate(`Issued ${numeralWrapper.format(newShares, "0.000a")} and raised ` + - `${numeralWrapper.formatMoney(profit)}. ${numeralWrapper.format(privateShares, "0.000a")} ` + - `of these shares were bought by private investors.

` + - `Stock price decreased to ${numeralWrapper.formatMoney(this.corp.sharePrice)}`); - - this.rerender(); - return false; - }, - }); - - const cancelBtn = createPopupCloseButton(popupId, { - class: "std-button", - display: "inline-block", - innerText: "Cancel", - }); - - createPopup(popupId, [descText, dynamicText, newSharesInput, issueBtn, cancelBtn]); - newSharesInput.focus(); - } - - // Create a popup that lets the player limit the production of a product - createLimitProductProdutionPopup(product, city) { - const popupId = "cmpy-mgmt-limit-product-production-popup"; - const txt = createElement("p", { - innerText:"Enter a limit to the amount of this product you would " + - "like to product per second. Leave the box empty to set no limit.", - }); - let confirmBtn; - const input = createElement("input", { - margin: "5px", - placeholder:"Limit", - type:"number", - onkeyup: (e) => { - e.preventDefault(); - if (e.keyCode === KEY.ENTER) { confirmBtn.click(); } - }, - }); - confirmBtn = createElement("button", { - class: "std-button", - display: "inline-block", - innerText: "Limit production", - margin: "5px", - clickListener: () => { - if (input.value === "") { - product.prdman[city][0] = false; - removeElementById(popupId); - return false; - } - var qty = parseFloat(input.value); - if (isNaN(qty)) { - dialogBoxCreate("Invalid value entered"); - return false; - } - if (qty < 0) { - product.prdman[city][0] = false; - } else { - product.prdman[city][0] = true; - product.prdman[city][1] = qty; - } - removeElementById(popupId); - this.rerender(); - return false; - }, - }); - const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); - cancelBtn.style.margin = "6px"; - - createPopup(popupId, [txt, input, confirmBtn, cancelBtn]); - input.focus(); - } - - // Create a popup that lets the player create a product for their current industry - createMakeProductPopup(popupText, division) { - if (division.hasMaximumNumberProducts()) { return; } - - const popupId = "cmpy-mgmt-create-product-popup"; - const txt = createElement("p", { - innerHTML: popupText, - }); - const designCity = createElement("select", { - class: "dropdown", - margin: "5px", - }); - for (const cityName in division.offices) { - if (division.offices[cityName] instanceof OfficeSpace) { - designCity.add(createElement("option", { - value: cityName, - text: cityName, - })); - } - } - let productNamePlaceholder = "Product Name"; - if (division.type === Industries.Food) { - productNamePlaceholder = "Restaurant Name"; - } else if (division.type === Industries.Healthcare) { - productNamePlaceholder = "Hospital Name"; - } else if (division.type === Industries.RealEstate) { - productNamePlaceholder = "Property Name"; - } - var productNameInput = createElement("input", { - class: "text-input", - margin: "5px", - placeholder: productNamePlaceholder, - }); - var lineBreak1 = createElement("br"); - var designInvestInput = createElement("input", { - class: "text-input", - margin: "5px", - placeholder: "Design investment", - type: "number", - }); - let confirmBtn; - var marketingInvestInput = createElement("input", { - class: "text-input", - margin: "5px", - placeholder: "Marketing investment", - type: "number", - onkeyup: (e) => { - e.preventDefault(); - if (e.keyCode === KEY.ENTER) { confirmBtn.click(); } - }, - }); - confirmBtn = createElement("button", { - class: "std-button", - innerText: "Develop Product", - clickListener: () => { - if (designInvestInput.value == null || designInvestInput.value < 0) { designInvestInput.value = 0; } - if (marketingInvestInput.value == null || marketingInvestInput.value < 0) { marketingInvestInput.value = 0; } - var designInvest = parseFloat(designInvestInput.value), - marketingInvest = parseFloat(marketingInvestInput.value); - if (productNameInput.value == null || productNameInput.value === "") { - dialogBoxCreate("You must specify a name for your product!"); - } else if (isNaN(designInvest)) { - dialogBoxCreate("Invalid value for design investment"); - } else if (isNaN(marketingInvest)) { - dialogBoxCreate("Invalid value for marketing investment"); - } else if (this.corp.funds.lt(designInvest + marketingInvest)) { - dialogBoxCreate("You don't have enough company funds to make this large of an investment"); - } else { - const product = new Product({ - name:productNameInput.value.replace(/[<>]/g, ''), //Sanitize for HTMl elements - createCity:designCity.options[designCity.selectedIndex].value, - designCost: designInvest, - advCost: marketingInvest, - }); - if (division.products[product.name] instanceof Product) { - dialogBoxCreate(`You already have a product with this name!`); - return; - } - this.corp.funds = this.corp.funds.minus(designInvest + marketingInvest); - division.products[product.name] = product; - removeElementById(popupId); - } - this.rerender(); - return false; - }, - }) - const cancelBtn = createPopupCloseButton(popupId, { - class: "std-button", - innerText: "Cancel", - }); - - createPopup(popupId, [txt, designCity, productNameInput, lineBreak1, - designInvestInput, marketingInvestInput, confirmBtn, cancelBtn]); - productNameInput.focus(); - } - - // Create a popup that lets the player use the Market TA research for Materials - createMaterialMarketTaPopup(mat, industry) { - const popupId = "cmpy-mgmt-marketta-popup"; - const markupLimit = mat.getMarkupLimit(); - const ta1 = createElement("p", { - innerHTML: "Market-TA.I
" + - "The maximum sale price you can mark this up to is " + - numeralWrapper.formatMoney(mat.bCost + markupLimit) + - ". This means that if you set the sale price higher than this, " + - "you will begin to experience a loss in number of sales", - }); - - // Enable using Market-TA1 for automatically setting sale price - const useTa1AutoSaleId = "cmpy-mgmt-marketa1-checkbox"; - const useTa1AutoSaleDiv = createElement("div", { display: "block" }); - const useTa1AutoSaleLabel = createElement("label", { - color: "white", - for: useTa1AutoSaleId, - innerText: "Use Market-TA.I for Auto-Sale Price", - tooltip: "If this is enabled, then this Material will automatically " + - "be sold at the price identified by Market-TA.I (i.e. the price shown above)", - }) - const useTa1AutoSaleCheckbox = createElement("input", { - checked: mat.marketTa1, - id: useTa1AutoSaleId, - margin: "3px", - type: "checkbox", - changeListener: (e) => { - mat.marketTa1 = e.target.checked; - }, - }); - useTa1AutoSaleDiv.appendChild(useTa1AutoSaleLabel); - useTa1AutoSaleDiv.appendChild(useTa1AutoSaleCheckbox); - - const closeBtn = createPopupCloseButton(popupId, { - class: "std-button", - display: "block", - innerText: "Close", - }); - - if (industry.hasResearch("Market-TA.II")) { - let updateTa2Text; - const ta2Text = createElement("p"); - const ta2Input = createElement("input", { - marginTop: "4px", - onkeyup: (e) => { - e.preventDefault(); - updateTa2Text(); - }, - type: "number", - value: mat.bCost, - }); - - // Function that updates the text in ta2Text element - updateTa2Text = function() { - const sCost = parseFloat(ta2Input.value); - let markup = 1; - if (sCost > mat.bCost) { - //Penalty if difference between sCost and bCost is greater than markup limit - if ((sCost - mat.bCost) > markupLimit) { - markup = Math.pow(markupLimit / (sCost - mat.bCost), 2); - } - } else if (sCost < mat.bCost) { - if (sCost <= 0) { - markup = 1e12; //Sell everything, essentially discard - } else { - //Lower prices than market increases sales - markup = mat.bCost / sCost; - } - } - ta2Text.innerHTML = `
Market-TA.II
` + - `If you sell at ${numeralWrapper.formatMoney(sCost)}, ` + - `then you will sell ${numeralWrapper.format(markup, "0.00000")}x as much compared ` + - `to if you sold at market price.`; - } - updateTa2Text(); - - // Enable using Market-TA2 for automatically setting sale price - const useTa2AutoSaleId = "cmpy-mgmt-marketa2-checkbox"; - const useTa2AutoSaleDiv = createElement("div", { display: "block" }); - const useTa2AutoSaleLabel = createElement("label", { - color: "white", - for: useTa2AutoSaleId, - innerText: "Use Market-TA.II for Auto-Sale Price", - tooltip: "If this is enabled, then this Material will automatically " + - "be sold at the optimal price such that the amount sold matches the " + - "amount produced. (i.e. the highest possible price, while still ensuring " + - " that all produced materials will be sold)", - }) - const useTa2AutoSaleCheckbox = createElement("input", { - checked: mat.marketTa2, - id: useTa2AutoSaleId, - margin: "3px", - type: "checkbox", - changeListener: (e) => { - mat.marketTa2 = e.target.checked; - }, - }); - useTa2AutoSaleDiv.appendChild(useTa2AutoSaleLabel); - useTa2AutoSaleDiv.appendChild(useTa2AutoSaleCheckbox); - - const ta2OverridesTa1 = createElement("p", { - innerText: "Note that Market-TA.II overrides Market-TA.I. This means that if " + - "both are enabled, then Market-TA.II will take effect, not Market-TA.I", - }); - - createPopup(popupId, [ta1, useTa1AutoSaleDiv, ta2Text, ta2Input, useTa2AutoSaleDiv, ta2OverridesTa1, closeBtn]); - } else { - // Market-TA.I only - createPopup(popupId, [ta1, useTa1AutoSaleDiv, closeBtn]); - } - } - - // Create a popup that lets the player create a new industry. - // This is created when the player clicks the "Expand into new Industry" header tab - createNewIndustryPopup() { - const popupId = "cmpy-mgmt-expand-industry-popup"; - if (document.getElementById(popupId) != null) { return; } - - var txt = createElement("p", { - innerHTML: "Create a new division to expand into a new industry:", - }); - var selector = createElement("select", { - class:"dropdown", - }); - var industryDescription = createElement("p", {}); - var yesBtn; - var nameInput = createElement("input", { - type:"text", - id:"cmpy-mgmt-expand-industry-name-input", - class: "text-input", - display:"block", - maxLength: 30, - pattern:"[a-zA-Z0-9-_]", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {yesBtn.click();} - }, - }); - var nameLabel = createElement("label", { - for:"cmpy-mgmt-expand-industry-name-input", - innerText:"Division name: ", - }); - yesBtn = createElement("span", { - class:"popup-box-button", - innerText:"Create Division", - clickListener: ()=>{ - const ind = selector.options[selector.selectedIndex].value; - const newDivisionName = nameInput.value; - - for (let i = 0; i < this.corp.divisions.length; ++i) { - if (this.corp.divisions[i].name === newDivisionName) { - dialogBoxCreate("This name is already in use!"); - return false; - } - } - if (this.corp.funds.lt(IndustryStartingCosts[ind])) { - dialogBoxCreate("Not enough money to create a new division in this industry"); - } else if (newDivisionName === "") { - dialogBoxCreate("New division must have a name!"); - } else { - this.corp.funds = this.corp.funds.minus(IndustryStartingCosts[ind]); - var newInd = new Industry({ - corp: this.corp, - name: newDivisionName, - type: ind, - }); - this.corp.divisions.push(newInd); - - // Set routing to the new division so that the UI automatically switches to it - this.routing.routeTo(newDivisionName); - - removeElementById("cmpy-mgmt-expand-industry-popup"); - this.rerender(); - } - return false; - }, - }); - - const noBtn = createPopupCloseButton(popupId, { - display: "inline-block", - innerText: "Cancel", - }); - - // Make an object to keep track of what industries you're already in - const ownedIndustries = {}; - for (let i = 0; i < this.corp.divisions.length; ++i) { - ownedIndustries[this.corp.divisions[i].type] = true; - } - - // Add industry types to selector - // Have Agriculture be first as recommended option - if (!ownedIndustries["Agriculture"]) { - selector.add(createElement("option", { - text:Industries["Agriculture"], value:"Agriculture", - })); - } - - for (var key in Industries) { - if (key !== "Agriculture" && Industries.hasOwnProperty(key) && !ownedIndustries[key]) { - var ind = Industries[key]; - selector.add(createElement("option", { - text: ind,value:key, - })); - } - } - - //Initial Industry Description - var ind = selector.options[selector.selectedIndex].value; - industryDescription.innerHTML = (IndustryDescriptions[ind] + "

"); - - //Change the industry description text based on selected option - selector.addEventListener("change", function() { - var ind = selector.options[selector.selectedIndex].value; - industryDescription.innerHTML = IndustryDescriptions[ind] + "

"; - }); - - //Add to DOM - const elems = []; - elems.push(txt); - elems.push(selector); - elems.push(industryDescription); - elems.push(nameLabel); - elems.push(nameInput); - elems.push(noBtn); - elems.push(yesBtn); - - createPopup(popupId, elems); - nameInput.focus(); - - return false; - } - // Create a popup that lets the player use the Market TA research for Products createProductMarketTaPopup(product, industry) { const popupId = "cmpy-mgmt-marketta-popup"; diff --git a/src/Corporation/ui/ExportPopup.tsx b/src/Corporation/ui/ExportPopup.tsx new file mode 100644 index 000000000..9431ad116 --- /dev/null +++ b/src/Corporation/ui/ExportPopup.tsx @@ -0,0 +1,120 @@ +import React, { useState } from 'react'; +import { Warehouse } from "../Warehouse"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { createElement } from "../../../utils/uiHelpers/createElement"; +import { removePopup } from "../../ui/React/createPopup"; +import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement"; +import { clearSelector } from "../../../utils/uiHelpers/clearSelector"; +import { getSelectText, + getSelectValue } from "../../../utils/uiHelpers/getSelectData"; + +interface IProps { + mat: any; + corp: any; + popupId: string; +} + +// Create a popup that lets the player manage exports +export function ExportPopup(props: IProps): React.ReactElement { + if(props.corp.divisions.length === 0) + throw new Error('Export popup created with no divisions.'); + if(Object.keys(props.corp.divisions[0].warehouses).length === 0) + throw new Error('Export popup created in a division with no warehouses.'); + const [industry, setIndustry] = useState(props.corp.divisions[0].name); + const [city, setCity] = useState(Object.keys(props.corp.divisions[0].warehouses)[0]); + const [amt, setAmt] = useState(''); + const setRerender = useState(false)[1]; + + function rerender(): void { + setRerender(old => !old); + } + + function onCityChange(event: React.ChangeEvent): void { + setCity(event.target.value); + } + + function onIndustryChange(event: React.ChangeEvent): void { + setIndustry(event.target.value); + } + + function onAmtChange(event: React.ChangeEvent): void { + setAmt(event.target.value); + } + + function exportMaterial(): void { + const industryName = industry; + const cityName = city; + console.log(`${industryName}, ${cityName}, ${amt}`) + + // Sanitize amt + let sanitizedAmt = amt.replace(/\s+/g, ''); + sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, ''); + let temp = sanitizedAmt.replace(/MAX/g, '1'); + try { + temp = eval(temp); + } catch(e) { + dialogBoxCreate("Invalid expression entered for export amount: " + e); + return; + } + + const n = parseFloat(temp); + + if (n == null || isNaN(n) || n < 0) { + dialogBoxCreate("Invalid amount entered for export"); + return; + } + const exportObj = {ind:industryName, city:cityName, amt:sanitizedAmt}; + console.log(exportObj); + props.mat.exp.push(exportObj); + removePopup(props.popupId); + } + + function removeExport(exp: any): void { + for (let i = 0; i < props.mat.exp.length; ++i) { + if(props.mat.exp[i].ind !== exp.ind || + props.mat.exp[i].city !== exp.city || + props.mat.exp[i].amt !== exp.amt) continue; + props.mat.exp.splice(i, 1); + break + } + rerender(); + } + + const currentDivision = props.corp.divisions.find((division: any) => division.name === industry); + + return (<> +

+Select the industry and city to export this material to, as well as +how much of this material to export per second. You can set the export +amount to 'MAX' to export all of the materials in this warehouse. +

+ + + + +

+Below is a list of all current exports of this material from this warehouse. +Clicking on one of the exports below will REMOVE that export. +

+ { + props.mat.exp.map((exp: any, index: number) => +
removeExport(exp)}> + Industry: {exp.ind}
+ City: {exp.city}
+ Amount/s: {exp.amt} +
) + } + ); +} diff --git a/src/Corporation/ui/HeaderTabs.tsx b/src/Corporation/ui/HeaderTabs.tsx index ec5dd8ab9..b22bc0277 100644 --- a/src/Corporation/ui/HeaderTabs.tsx +++ b/src/Corporation/ui/HeaderTabs.tsx @@ -4,6 +4,8 @@ import React from "react"; import { HeaderTab } from "./HeaderTab"; import { IDivision } from "../IDivision"; +import { NewIndustryPopup } from "./NewIndustryPopup"; +import { createPopup } from "../../ui/React/createPopup"; interface IProps { corp: any; @@ -17,6 +19,15 @@ export function HeaderTabs(props: IProps): React.ReactElement { props.corp.rerender(); } + function openNewIndustryPopup(): void { + const popupId = "cmpy-mgmt-expand-industry-popup"; + createPopup(popupId, NewIndustryPopup, { + corp: props.corp, + routing: props.routing, + popupId: popupId, + }); + } + return (
props.eventHandler.createNewIndustryPopup()} + onClick={openNewIndustryPopup} text={"Expand into new Industry"} />
diff --git a/src/Corporation/ui/IndustryOverview.tsx b/src/Corporation/ui/IndustryOverview.tsx index 5e900e652..06d4d2f44 100644 --- a/src/Corporation/ui/IndustryOverview.tsx +++ b/src/Corporation/ui/IndustryOverview.tsx @@ -8,6 +8,8 @@ import { IndustryUpgrades } from "../IndustryUpgrades"; import { numeralWrapper } from "../../ui/numeralFormat"; import { dialogBoxCreate } from "../../../utils/DialogBox"; import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; +import { MakeProductPopup } from "./MakeProductPopup"; +import { createPopup } from "../../ui/React/createPopup"; interface IProps { routing: any; @@ -77,8 +79,18 @@ export function IndustryOverview(props: IProps): React.ReactElement { display: "inline-block", } + function openMakeProductPopup() { + const popupId = "cmpy-mgmt-create-product-popup"; + createPopup(popupId, MakeProductPopup, { + popupText: createProductPopupText, + division: division, + corp: props.corp, + popupId: popupId, + }); + } + return ( -
- { division.hasResearch("Market-TA.I") && - } @@ -179,7 +197,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
- { division.hasResearch("Market-TA.I") && - } @@ -236,8 +254,14 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement { const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button"; const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse); - // Export material button - const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat); + function openExportPopup() { + const popupId = "cmpy-mgmt-export-popup"; + createPopup(popupId, ExportPopup, { + mat: mat, + corp: props.corp, + popupId: popupId, + }); + } // Sell material button let sellButtonText; @@ -265,8 +289,15 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement { } const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat); - // Market TA button - const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division); + function openMaterialMarketTaPopup(): void { + const popupId = "cmpy-mgmt-export-popup"; + createPopup(popupId, MaterialMarketTaPopup, { + mat: mat, + industry: division, + corp: props.corp, + popupId: popupId, + }); + } return (
@@ -322,7 +353,7 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement { { corp.unlockUpgrades[0] === 1 && - } @@ -334,7 +365,7 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement { { division.hasResearch("Market-TA.I") && - } diff --git a/src/Corporation/ui/IssueDividendsPopup.tsx b/src/Corporation/ui/IssueDividendsPopup.tsx new file mode 100644 index 000000000..f04a06647 --- /dev/null +++ b/src/Corporation/ui/IssueDividendsPopup.tsx @@ -0,0 +1,57 @@ +import React, { useState } from 'react'; +import { removePopup } from "../../ui/React/createPopup"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { CorporationConstants } from "../data/Constants"; + +interface IProps { + popupId: string; + corp: any; +} + +// Create a popup that lets the player issue & manage dividends +// This is created when the player clicks the "Issue Dividends" button in the overview panel +export function IssueDividendsPopup(props: IProps): React.ReactElement { + const [percent, setPercent] = useState(null); + + function issueDividends(): void { + if(percent === null) return; + if (isNaN(percent) || percent < 0 || percent > CorporationConstants.DividendMaxPercentage) { + dialogBoxCreate(`Invalid value. Must be an integer between 0 and ${CorporationConstants.DividendMaxPercentage}`); + return; + } + + props.corp.dividendPercentage = percent; + + removePopup(props.popupId); + } + + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) issueDividends(); + } + + function onChange(event: React.ChangeEvent): void { + if(event.target.value === "") setPercent(null); + else setPercent(parseFloat(event.target.value)); + } + + return (<> +

+"Dividends are a distribution of a portion of the corporation's +profits to the shareholders. This includes yourself, as well.

+In order to issue dividends, simply allocate some percentage +of your corporation's profits to dividends. This percentage must be an +integer between 0 and {CorporationConstants.DividendMaxPercentage}. (A percentage of 0 means no dividends will be +issued

+Two important things to note:
+ * Issuing dividends will negatively affect your corporation's stock price
+ * Dividends are taxed. Taxes start at 50%, but can be decreased

+Example: Assume your corporation makes $100m / sec in profit and you allocate +40% of that towards dividends. That means your corporation will gain $60m / sec +in funds and the remaining $40m / sec will be paid as dividends. Since your +corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share +per second before taxes. +

+ + + ); +} diff --git a/src/Corporation/ui/IssueNewSharesPopup.tsx b/src/Corporation/ui/IssueNewSharesPopup.tsx new file mode 100644 index 000000000..f1c8d82f7 --- /dev/null +++ b/src/Corporation/ui/IssueNewSharesPopup.tsx @@ -0,0 +1,134 @@ +import React, { useState } from 'react'; +import { createElement } from "../../../utils/uiHelpers/createElement"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { removePopup } from "../../ui/React/createPopup"; +import { getRandomInt } from "../../../utils/helpers/getRandomInt"; +import { CorporationConstants } from "../data/Constants"; + +interface IEffectTextProps { + corp: any; + shares: number | null; +} + +function EffectText(props: IEffectTextProps): React.ReactElement { + if(props.shares === null) return (<>); + const newSharePrice = Math.round(props.corp.sharePrice * 0.9); + const maxNewSharesUnrounded = Math.round(props.corp.totalShares * 0.2); + const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); + let newShares = props.shares; + if (isNaN(newShares)) { + return (

Invalid input

); + } + + // Round to nearest ten-millionth + newShares /= 10e6; + newShares = Math.round(newShares) * 10e6; + + if (newShares < 10e6) { + return (

Must issue at least 10 million new shares

); + } + + if (newShares > maxNewShares) { + return (

You cannot issue that many shares

); + } + + return (

+ Issue ${numeralWrapper.format(newShares, "0.000a")} new + shares for {numeralWrapper.formatMoney(newShares * newSharePrice)}? +

); +} + +interface IProps { + corp: any; + popupId: string; +} + +// Create a popup that lets the player issue new shares +// This is created when the player clicks the "Issue New Shares" buttons in the overview panel +export function IssueNewSharesPopup(props: IProps): React.ReactElement { + const [shares, setShares] = useState(null); + const maxNewSharesUnrounded = Math.round(props.corp.totalShares * 0.2); + const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); + + function issueNewShares(): void { + if(shares === null) return; + const newSharePrice = Math.round(props.corp.sharePrice * 0.9); + let newShares = shares; + if (isNaN(newShares)) { + dialogBoxCreate("Invalid input for number of new shares"); + return; + } + + // Round to nearest ten-millionth + newShares = Math.round(newShares / 10e6) * 10e6; + + if (newShares < 10e6 || newShares > maxNewShares) { + dialogBoxCreate("Invalid input for number of new shares"); + return; + } + + const profit = newShares * newSharePrice; + props.corp.issueNewSharesCooldown = CorporationConstants.IssueNewSharesCooldown; + props.corp.totalShares += newShares; + + // Determine how many are bought by private investors + // Private investors get up to 50% at most + // Round # of private shares to the nearest millionth + let privateShares = getRandomInt(0, Math.round(newShares / 2)); + privateShares = Math.round(privateShares / 1e6) * 1e6; + + props.corp.issuedShares += (newShares - privateShares); + props.corp.funds = props.corp.funds.plus(profit); + props.corp.immediatelyUpdateSharePrice(); + + removePopup(props.popupId); + dialogBoxCreate(`Issued ${numeralWrapper.format(newShares, "0.000a")} and raised ` + + `${numeralWrapper.formatMoney(profit)}. ${numeralWrapper.format(privateShares, "0.000a")} ` + + `of these shares were bought by private investors.

` + + `Stock price decreased to ${numeralWrapper.formatMoney(props.corp.sharePrice)}`); + } + + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) issueNewShares(); + } + + function onChange(event: React.ChangeEvent): void { + if(event.target.value === "") setShares(null); + else setShares(parseFloat(event.target.value)); + } + + return (<> +

+You can issue new equity shares (i.e. stocks) in order to raise +capital for your corporation.

+ * You can issue at most {numeralWrapper.formatMoney(maxNewShares)} new shares
+ * New shares are sold at a 10% discount
+ * You can only issue new shares once every 12 hours
+ * Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share
+ * Number of new shares issued must be a multiple of 10 million

+When you choose to issue new equity, private shareholders have first priority for up to 50% of the new shares. +If they choose to exercise this option, these newly issued shares become private, restricted shares, which means +you cannot buy them back. +

+ + + + ); + + + // let issueBtn, newSharesInput; + // const dynamicText = createElement("p", { + // display: "block", + // }); + + // function updateDynamicText(corp) { + + // } + + + + + // createPopup(popupId, [descText, dynamicText, newSharesInput, issueBtn, cancelBtn]); + // newSharesInput.focus(); +} diff --git a/src/Corporation/ui/LimitProductProductionPopup.tsx b/src/Corporation/ui/LimitProductProductionPopup.tsx new file mode 100644 index 000000000..e734daff3 --- /dev/null +++ b/src/Corporation/ui/LimitProductProductionPopup.tsx @@ -0,0 +1,59 @@ +import React, { useState } from 'react'; +import { Warehouse } from "../Warehouse"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { createElement } from "../../../utils/uiHelpers/createElement"; +import { removePopup } from "../../ui/React/createPopup"; +import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement"; +import { clearSelector } from "../../../utils/uiHelpers/clearSelector"; +import { getSelectText, + getSelectValue } from "../../../utils/uiHelpers/getSelectData"; + + +interface IProps { + product: any; + city: any; + popupId: string; +} + +// Create a popup that lets the player limit the production of a product +export function LimitProductProductionPopup(props: IProps): React.ReactElement { + const [limit, setLimit] = useState(null); + + function limitProductProduction(): void { + if (limit === null) { + props.product.prdman[props.city][0] = false; + removePopup(props.popupId); + return; + } + var qty = limit; + if (isNaN(qty)) { + dialogBoxCreate("Invalid value entered"); + return; + } + if (qty < 0) { + props.product.prdman[props.city][0] = false; + } else { + props.product.prdman[props.city][0] = true; + props.product.prdman[props.city][1] = qty; + } + removePopup(props.popupId); + } + + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) limitProductProduction(); + } + + function onChange(event: React.ChangeEvent): void { + if(event.target.value === "") setLimit(null); + else setLimit(parseFloat(event.target.value)); + } + + return (<> +

+Enter a limit to the amount of this product you would +like to product per second. Leave the box empty to set no limit. +

+ + + ); +} \ No newline at end of file diff --git a/src/Corporation/ui/MakeProductPopup.tsx b/src/Corporation/ui/MakeProductPopup.tsx new file mode 100644 index 000000000..451dc06e9 --- /dev/null +++ b/src/Corporation/ui/MakeProductPopup.tsx @@ -0,0 +1,107 @@ +import React, { useState } from 'react'; +import { Warehouse } from "../Warehouse"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { createElement } from "../../../utils/uiHelpers/createElement"; +import { removePopup } from "../../ui/React/createPopup"; +import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement"; +import { clearSelector } from "../../../utils/uiHelpers/clearSelector"; +import { getSelectText, + getSelectValue } from "../../../utils/uiHelpers/getSelectData"; +import { Industries } from "../IndustryData"; +import { Product } from "../Product"; + +interface IProps { + popupText: string; + division: any; + corp: any; + popupId: string; +} + +function productPlaceholder(tpe: string): string { + if (tpe === Industries.Food) { + return "Restaurant Name"; + } else if (tpe === Industries.Healthcare) { + return "Hospital Name"; + } else if (tpe === Industries.RealEstate) { + return "Property Name"; + } + return "Product Name"; +} + +// Create a popup that lets the player create a product for their current industry +export function MakeProductPopup(props: IProps) { + const allCities = Object.keys(props.division.offices). + filter((cityName: string) => props.division.offices[cityName] !== 0); + const [city, setCity] = useState(allCities.length > 0 ? allCities[0] : ''); + const [name, setName] = useState(''); + const [design, setDesign] = useState(null); + const [marketing, setMarketing] = useState(null); + if (props.division.hasMaximumNumberProducts()) return (<>); + + function makeProduct(): void { + let designInvest = design; + let marketingInvest = marketing; + if (designInvest == null || designInvest < 0) { designInvest = 0; } + if (marketingInvest == null || marketingInvest < 0) { marketingInvest = 0; } + if (name == null || name === "") { + dialogBoxCreate("You must specify a name for your product!"); + } else if (isNaN(designInvest)) { + dialogBoxCreate("Invalid value for design investment"); + } else if (isNaN(marketingInvest)) { + dialogBoxCreate("Invalid value for marketing investment"); + } else if (props.corp.funds.lt(designInvest + marketingInvest)) { + dialogBoxCreate("You don't have enough company funds to make this large of an investment"); + } else { + const product = new Product({ + name: name.replace(/[<>]/g, ''), //Sanitize for HTMl elements + createCity: city, + designCost: designInvest, + advCost: marketingInvest, + }); + if (props.division.products[product.name] instanceof Product) { + dialogBoxCreate(`You already have a product with this name!`); + return; + } + props.corp.funds = props.corp.funds.minus(designInvest + marketingInvest); + props.division.products[product.name] = product; + removePopup(props.popupId); + } + } + + function onCityChange(event: React.ChangeEvent): void { + setCity(event.target.value); + } + + function onProductNameChange(event: React.ChangeEvent): void { + setName(event.target.value); + } + + function onDesignChange(event: React.ChangeEvent): void { + if(event.target.value === "") setDesign(null); + else setDesign(parseFloat(event.target.value)); + } + + function onMarketingChange(event: React.ChangeEvent): void { + if(event.target.value === "") setMarketing(null); + else setMarketing(parseFloat(event.target.value)); + } + + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) makeProduct(); + } + + return (<> +

+ + +
+ + + + ); +} diff --git a/src/Corporation/ui/MaterialMarketTaPopup.tsx b/src/Corporation/ui/MaterialMarketTaPopup.tsx new file mode 100644 index 000000000..99febeeae --- /dev/null +++ b/src/Corporation/ui/MaterialMarketTaPopup.tsx @@ -0,0 +1,121 @@ +import React, { useState } from 'react'; +import { Warehouse } from "../Warehouse"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { createElement } from "../../../utils/uiHelpers/createElement"; +import { removePopup } from "../../ui/React/createPopup"; +import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement"; +import { clearSelector } from "../../../utils/uiHelpers/clearSelector"; +import { getSelectText, + getSelectValue } from "../../../utils/uiHelpers/getSelectData"; +import { numeralWrapper } from "../../ui/numeralFormat"; + +interface IMarketTA2Props { + industry: any; + mat: any; +} + +function MarketTA2(props: IMarketTA2Props): React.ReactElement { + if(!props.industry.hasResearch("Market-TA.II")) return (<>); + + const [newCost, setNewCost] = useState(props.mat.bCost); + const setRerender = useState(false)[1]; + function rerender(): void { + setRerender(old => !old); + } + const markupLimit = props.mat.getMarkupLimit(); + + function onChange(event: React.ChangeEvent): void { + if(event.target.value === "") setNewCost(0); + else setNewCost(parseFloat(event.target.value)); + } + + const sCost = newCost; + let markup = 1; + if (sCost > props.mat.bCost) { + //Penalty if difference between sCost and bCost is greater than markup limit + if ((sCost - props.mat.bCost) > markupLimit) { + markup = Math.pow(markupLimit / (sCost - props.mat.bCost), 2); + } + } else if (sCost < props.mat.bCost) { + if (sCost <= 0) { + markup = 1e12; //Sell everything, essentially discard + } else { + //Lower prices than market increases sales + markup = props.mat.bCost / sCost; + } + } + + function onMarketTA2(event: React.ChangeEvent): void { + props.mat.marketTa2 = event.target.checked; + rerender(); + } + + return (<> +

+
Market-TA.II
+ If you sell at {numeralWrapper.formatMoney(sCost)}, + then you will sell {numeralWrapper.format(markup, "0.00000")}x as much compared + to if you sold at market price. +

+ +
+ + +
+

+ Note that Market-TA.II overrides Market-TA.I. This means that if + both are enabled, then Market-TA.II will take effect, not Market-TA.I +

+ ); +} + +interface IProps { + mat: any; + industry: any; + corp: any; + popupId: string; +} + +// Create a popup that lets the player use the Market TA research for Materials +export function MaterialMarketTaPopup(props: IProps): React.ReactElement { + const setRerender = useState(false)[1]; + function rerender(): void { + setRerender(old => !old); + } + const markupLimit = props.mat.getMarkupLimit(); + + function onMarketTA1(event: React.ChangeEvent): void { + props.mat.marketTa1 = event.target.checked; + rerender(); + } + + return (<> +

+ Market-TA.I
+ The maximum sale price you can mark this up to + is {numeralWrapper.formatMoney(props.mat.bCost + markupLimit)}. + This means that if you set the sale price higher than this, + you will begin to experience a loss in number of sales +

+
+ + +
+ + ); + +} diff --git a/src/Corporation/ui/NewIndustryPopup.tsx b/src/Corporation/ui/NewIndustryPopup.tsx new file mode 100644 index 000000000..c61ed21d4 --- /dev/null +++ b/src/Corporation/ui/NewIndustryPopup.tsx @@ -0,0 +1,89 @@ +import React, { useState } from 'react'; +import { Warehouse } from "../Warehouse"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; +import { createElement } from "../../../utils/uiHelpers/createElement"; +import { removePopup } from "../../ui/React/createPopup"; +import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement"; +import { clearSelector } from "../../../utils/uiHelpers/clearSelector"; +import { getSelectText, + getSelectValue } from "../../../utils/uiHelpers/getSelectData"; +import { + Industries, + IndustryStartingCosts, + IndustryDescriptions } from "../IndustryData"; +import { Industry } from "../Corporation"; + +interface IProps { + corp: any; + popupId: string; + routing: any; +} +// Create a popup that lets the player create a new industry. +// This is created when the player clicks the "Expand into new Industry" header tab +export function NewIndustryPopup(props: IProps): React.ReactElement { + const allIndustries = Object.keys(Industries).sort(); + const possibleIndustries = allIndustries.filter((industryType: string) => + props.corp.divisions.find((division: any) => + division.type === industryType) === undefined).sort(); + const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : ''); + const [name, setName] = useState(''); + + function newIndustry(): void { + const ind = industry; + const newDivisionName = name; + + for (let i = 0; i < props.corp.divisions.length; ++i) { + if (props.corp.divisions[i].name === newDivisionName) { + dialogBoxCreate("This name is already in use!"); + return; + } + } + if (props.corp.funds.lt(IndustryStartingCosts[ind])) { + dialogBoxCreate("Not enough money to create a new division in this industry"); + } else if (newDivisionName === "") { + dialogBoxCreate("New division must have a name!"); + } else { + props.corp.funds = props.corp.funds.minus(IndustryStartingCosts[ind]); + const newInd = new Industry({ + corp: props.corp, + name: newDivisionName, + type: ind, + }); + props.corp.divisions.push(newInd); + + // Set routing to the new division so that the UI automatically switches to it + props.routing.routeTo(newDivisionName); + + removePopup(props.popupId); + } + } + + function onNameChange(event: React.ChangeEvent): void { + setName(event.target.value); + } + + function onKeyDown(event: React.KeyboardEvent): void { + if (event.keyCode === 13) newIndustry(); + } + + function onIndustryChange(event: React.ChangeEvent): void { + setIndustry(event.target.value); + } + + return (<> +

Create a new division to expand into a new industry:

+ +

{IndustryDescriptions[industry]}

+

+ +

Division name:

+ + Create Division + ); + +} diff --git a/src/Corporation/ui/Overview.tsx b/src/Corporation/ui/Overview.tsx index 5972cd6a4..2d035d448 100644 --- a/src/Corporation/ui/Overview.tsx +++ b/src/Corporation/ui/Overview.tsx @@ -5,6 +5,8 @@ import { UnlockUpgrade } from "./UnlockUpgrade"; import { BribeFactionPopup } from "./BribeFactionPopup"; import { SellSharesPopup } from "./SellSharesPopup"; import { BuybackSharesPopup } from "./BuybackSharesPopup"; +import { IssueDividendsPopup } from "./IssueDividendsPopup"; +import { IssueNewSharesPopup } from "./IssueNewSharesPopup"; import { CorporationConstants } from "../data/Constants"; import { CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades"; @@ -245,6 +247,14 @@ export function Overview(props: IProps): React.ReactElement { tooltip: "Buy back shares you that previously issued or sold at market price.", }); + function openIssueNewSharesPopup(): void { + const popupId = "cmpy-mgmt-issue-new-shares-popup"; + createPopup(popupId, IssueNewSharesPopup, { + popupId: popupId, + corp: props.corp, + }); + } + const issueNewSharesOnCd = (corp.issueNewSharesCooldown > 0); const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button"; const issueNewSharesTooltip = issueNewSharesOnCd @@ -253,15 +263,23 @@ export function Overview(props: IProps): React.ReactElement { const issueNewSharesBtn = createButton({ class: issueNewSharesClass, display: "inline-block", - onClick: props.eventHandler.createIssueNewSharesPopup, + onClick: openIssueNewSharesPopup, text: "Issue New Shares", tooltip: issueNewSharesTooltip, }); + function openIssueDividendsPopup(): void { + const popupId = "cmpy-mgmt-issue-dividends-popup"; + createPopup(popupId, IssueDividendsPopup, { + popupId: popupId, + corp: props.corp, + }); + } + const issueDividendsBtn = createButton({ class: "std-button", display: "inline-block", - onClick: props.eventHandler.createIssueDividendsPopup, + onClick: openIssueDividendsPopup, text: "Issue Dividends", tooltip: "Manage the dividends that are paid out to shareholders (including yourself)", }); diff --git a/src/DevMenu.jsx b/src/DevMenu.jsx index 5f0e6caea..ea99a52e9 100644 --- a/src/DevMenu.jsx +++ b/src/DevMenu.jsx @@ -591,6 +591,13 @@ class DevMenuComponent extends Component { }); } + addCorporationResearch() { + if(!Player.corporation) return; + Player.corporation.divisions.forEach(div => { + div.sciResearch.qty += 1e10; + }); + } + specificContract() { generateContract({ problemType: this.state.codingcontract, @@ -1181,6 +1188,11 @@ class DevMenuComponent extends Component { + + + + +