more conversion

This commit is contained in:
Olivier Gagnon 2021-08-30 03:07:14 -04:00
parent 21008ba65a
commit a72d1aa99f
16 changed files with 934 additions and 817 deletions

3
src/Corporation/Corporation.d.ts vendored Normal file

@ -0,0 +1,3 @@
export class Industry {
constructor(props: any)
}

@ -1,140 +0,0 @@
import { ResearchTree } from "./ResearchTree";
import { getBaseResearchTreeCopy,
getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree";
import { numeralWrapper } from "../ui/numeralFormat";
interface IIndustryMap<T> {
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<string> = {
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<number> = {
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<string> = {
Energy: "Engage in the production and distribution of energy.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Energy, "$0.000a") + "<br>" +
"Recommended starting Industry: NO",
Utilities: "Distribute water and provide wastewater services.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Utilities, "$0.000a") + "<br>" +
"Recommended starting Industry: NO",
Agriculture: "Cultivate crops and breed livestock to produce food.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Agriculture, "$0.000a") + "<br>" +
"Recommended starting Industry: YES",
Fishing: "Produce food through the breeding and processing of fish and fish products.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Fishing, "$0.000a") + "<br>" +
"Recommended starting Industry: NO",
Mining: "Extract and process metals from the earth.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Mining, "$0.000a") + "<br>" +
"Recommended starting Industry: NO",
Food: "Create your own restaurants all around the world.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Food, "$0.000a") + "<br>" +
"Recommended starting Industry: YES",
Tobacco: "Create and distribute tobacco and tobacco-related products.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Tobacco, "$0.000a") + "<br>" +
"Recommended starting Industry: YES",
Chemical: "Produce industrial chemicals.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Chemical, "$0.000a") + "<br>" +
"Recommended starting Industry: NO",
Pharmaceutical: "Discover, develop, and create new pharmaceutical drugs.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Pharmaceutical, "$0.000a") + "<br>" +
"Recommended starting Industry: NO",
Computer: "Develop and manufacture new computer hardware and networking infrastructures.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Computer, "$0.000a") + "<br>" +
"Recommended starting Industry: NO",
Robotics: "Develop and create robots.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Robotics, "$0.000a") + "<br>" +
"Recommended starting Industry: NO",
Software: "Develop computer software and create AI Cores.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Software, "$0.000a") + "<br>" +
"Recommended starting Industry: YES",
Healthcare: "Create and manage hospitals.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.Healthcare, "$0.000a") + "<br>" +
"Recommended starting Industry: NO",
RealEstate: "Develop and manage real estate properties.<br><br>" +
"Starting cost: " + numeralWrapper.format(IndustryStartingCosts.RealEstate, "$0.000a") + "<br>" +
"Recommended starting Industry: NO",
}
// Map of available Research for each Industry. This data is held in a
// ResearchTree object
export const IndustryResearchTrees: IIndustryMap<ResearchTree> = {
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();
}

@ -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<T> {
[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<string> = {
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<number> = {
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<JSX.Element> = {
Energy: (<>Engage in the production and distribution of energy.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Energy)}<br />
Recommended starting Industry: NO</>),
Utilities: (<>Distribute water and provide wastewater services.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Utilities)}<br />
Recommended starting Industry: NO</>),
Agriculture: (<>Cultivate crops and breed livestock to produce food.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Agriculture)}<br />
Recommended starting Industry: YES</>),
Fishing: (<>Produce food through the breeding and processing of fish and fish products.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Fishing)}<br />
Recommended starting Industry: NO</>),
Mining: (<>Extract and process metals from the earth.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Mining)}<br />
Recommended starting Industry: NO</>),
Food: (<>Create your own restaurants all around the world.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Food)}<br />
Recommended starting Industry: YES</>),
Tobacco: (<>Create and distribute tobacco and tobacco-related products.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Tobacco)}<br />
Recommended starting Industry: YES</>),
Chemical: (<>Produce industrial chemicals.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Chemical)}<br />
Recommended starting Industry: NO</>),
Pharmaceutical: (<>Discover, develop, and create new pharmaceutical drugs.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Pharmaceutical)}<br />
Recommended starting Industry: NO</>),
Computer: (<>Develop and manufacture new computer hardware and networking infrastructures.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Computer)}<br />
Recommended starting Industry: NO</>),
Robotics: (<>Develop and create robots.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Robotics)}<br />
Recommended starting Industry: NO</>),
Software: (<>Develop computer software and create AI Cores.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Software)}<br />
Recommended starting Industry: YES</>),
Healthcare: (<>Create and manage hospitals.<br /><br />
Starting cost: {Money(IndustryStartingCosts.Healthcare)}<br />
Recommended starting Industry: NO</>),
RealEstate: (<>Develop and manage real estate properties.<br /><br />
Starting cost: {Money(IndustryStartingCosts.RealEstate)}<br />
Recommended starting Industry: NO</>),
}
// Map of available Research for each Industry. This data is held in a
// ResearchTree object
export const IndustryResearchTrees: IIndustryMap<ResearchTree> = {
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();
}

@ -47,666 +47,6 @@ export class CorporationEventHandler {
this.routing = routing; 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 + "<br>" +
"City: " + mat.exp[i].city + "<br>" +
"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.<br><br>" +
"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<br><br>" +
"Two important things to note:<br>" +
" * Issuing dividends will negatively affect your corporation's stock price<br>" +
" * Dividends are taxed. Taxes start at 50%, but can be decreased<br><br>" +
"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.<br><br>" +
`&nbsp;* You can issue at most ${numeralWrapper.format(maxNewShares, "0.000a")} new shares<br>` +
`&nbsp;* New shares are sold at a 10% discount<br>` +
`&nbsp;* You can only issue new shares once every 12 hours<br>` +
`&nbsp;* Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share<br>` +
`&nbsp;* Number of new shares issued must be a multiple of 10 million<br><br>` +
`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.<br><br>` +
`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: "<u><strong>Market-TA.I</strong></u><br>" +
"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 = `<br><u><strong>Market-TA.II</strong></u><br>` +
`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] + "<br><br>");
//Change the industry description text based on selected option
selector.addEventListener("change", function() {
var ind = selector.options[selector.selectedIndex].value;
industryDescription.innerHTML = IndustryDescriptions[ind] + "<br><br>";
});
//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 // Create a popup that lets the player use the Market TA research for Products
createProductMarketTaPopup(product, industry) { createProductMarketTaPopup(product, industry) {
const popupId = "cmpy-mgmt-marketta-popup"; const popupId = "cmpy-mgmt-marketta-popup";

@ -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<string>(props.corp.divisions[0].name);
const [city, setCity] = useState<string>(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<HTMLSelectElement>): void {
setCity(event.target.value);
}
function onIndustryChange(event: React.ChangeEvent<HTMLSelectElement>): void {
setIndustry(event.target.value);
}
function onAmtChange(event: React.ChangeEvent<HTMLInputElement>): 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 (<>
<p>
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.
</p>
<select className="dropdown" onChange={onIndustryChange} defaultValue={industry}>
{
props.corp.divisions.map((division: any) =>
<option key={division.name} value={division.name}>{division.name}</option>)
}
</select>
<select className="dropdown" onChange={onCityChange} defaultValue={city}>
{
currentDivision && Object.keys(currentDivision.warehouses).map((cityName: any) => {
if(currentDivision.warehouses[cityName] === 0) return;
return (<option key={cityName} value={cityName}>{cityName}</option>);
})
}
</select>
<input className="text-input" placeholder="Export amount / s" onChange={onAmtChange} />
<button className="std-button" style={{display:"inline-block"}} onClick={exportMaterial}>Export</button>
<p>
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.
</p>
{
props.mat.exp.map((exp: any, index: number) =>
<div key={index} className="cmpy-mgmt-existing-export" onClick={() => removeExport(exp)}>
Industry: {exp.ind}<br />
City: {exp.city}<br />
Amount/s: {exp.amt}
</div>)
}
</>);
}

@ -4,6 +4,8 @@
import React from "react"; import React from "react";
import { HeaderTab } from "./HeaderTab"; import { HeaderTab } from "./HeaderTab";
import { IDivision } from "../IDivision"; import { IDivision } from "../IDivision";
import { NewIndustryPopup } from "./NewIndustryPopup";
import { createPopup } from "../../ui/React/createPopup";
interface IProps { interface IProps {
corp: any; corp: any;
@ -17,6 +19,15 @@ export function HeaderTabs(props: IProps): React.ReactElement {
props.corp.rerender(); 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 ( return (
<div> <div>
<HeaderTab <HeaderTab
@ -38,7 +49,7 @@ export function HeaderTabs(props: IProps): React.ReactElement {
} }
<HeaderTab <HeaderTab
current={false} current={false}
onClick={() => props.eventHandler.createNewIndustryPopup()} onClick={openNewIndustryPopup}
text={"Expand into new Industry"} text={"Expand into new Industry"}
/> />
</div> </div>

@ -8,6 +8,8 @@ import { IndustryUpgrades } from "../IndustryUpgrades";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { MakeProductPopup } from "./MakeProductPopup";
import { createPopup } from "../../ui/React/createPopup";
interface IProps { interface IProps {
routing: any; routing: any;
@ -77,8 +79,18 @@ export function IndustryOverview(props: IProps): React.ReactElement {
display: "inline-block", 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 ( return (
<button className={className} onClick={() => props.eventHandler.createMakeProductPopup(createProductPopupText, division)} style={buttonStyle}> <button className={className} onClick={openMakeProductPopup} style={buttonStyle}>
{createProductButtonText} {createProductButtonText}
{ {
hasMaxProducts && hasMaxProducts &&

@ -8,6 +8,9 @@ import { Material } from "../Material";
import { Product } from "../Product"; import { Product } from "../Product";
import { Warehouse } from "../Warehouse"; import { Warehouse } from "../Warehouse";
import { DiscontinueProductPopup } from "./DiscontinueProductPopup"; import { DiscontinueProductPopup } from "./DiscontinueProductPopup";
import { ExportPopup } from "./ExportPopup";
import { LimitProductProductionPopup } from "./LimitProductProductionPopup";
import { MaterialMarketTaPopup } from "./MaterialMarketTaPopup";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox"; import { dialogBoxCreate } from "../../../utils/DialogBox";
@ -71,7 +74,15 @@ function ProductComponent(props: IProductProps): React.ReactElement {
if (product.prdman[city][0]) { if (product.prdman[city][0]) {
limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")"; limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")";
} }
const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city);
function openLimitProductProdutionPopup(): void {
const popupId = "cmpy-mgmt-limit-product-production-popup";
createPopup(popupId, LimitProductProductionPopup, {
product: product,
city: city,
popupId: popupId,
});
}
function openDiscontinueProductPopup(): void { function openDiscontinueProductPopup(): void {
const popupId = "cmpy-mgmt-discontinue-product-popup"; const popupId = "cmpy-mgmt-discontinue-product-popup";
@ -83,8 +94,15 @@ function ProductComponent(props: IProductProps): React.ReactElement {
}); });
} }
// Market TA button function openMaterialMarketTaPopup(): void {
const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division); const popupId = "cmpy-mgmt-export-popup";
createPopup(popupId, MaterialMarketTaPopup, {
mat: product,
industry: division,
corp: props.corp,
popupId: popupId,
});
}
// Unfinished Product // Unfinished Product
if (!product.fin) { if (!product.fin) {
@ -99,7 +117,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
<button className={"std-button"} onClick={sellButtonOnClick}> <button className={"std-button"} onClick={sellButtonOnClick}>
{sellButtonText} {sellButtonText}
</button><br /> </button><br />
<button className={"std-button"} onClick={limitProductionButtonOnClick}> <button className={"std-button"} onClick={openLimitProductProdutionPopup}>
{limitProductionButtonText} {limitProductionButtonText}
</button> </button>
<button className={"std-button"} onClick={openDiscontinueProductPopup}> <button className={"std-button"} onClick={openDiscontinueProductPopup}>
@ -107,7 +125,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
</button> </button>
{ {
division.hasResearch("Market-TA.I") && division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}> <button className={"std-button"} onClick={openMaterialMarketTaPopup}>
Market-TA Market-TA
</button> </button>
} }
@ -179,7 +197,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
<button className={"std-button"} onClick={sellButtonOnClick}> <button className={"std-button"} onClick={sellButtonOnClick}>
{sellButtonText} {sellButtonText}
</button><br /> </button><br />
<button className={"std-button"} onClick={limitProductionButtonOnClick}> <button className={"std-button"} onClick={openLimitProductProdutionPopup}>
{limitProductionButtonText} {limitProductionButtonText}
</button> </button>
<button className={"std-button"} onClick={openDiscontinueProductPopup}> <button className={"std-button"} onClick={openDiscontinueProductPopup}>
@ -187,7 +205,7 @@ function ProductComponent(props: IProductProps): React.ReactElement {
</button> </button>
{ {
division.hasResearch("Market-TA.I") && division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}> <button className={"std-button"} onClick={openMaterialMarketTaPopup}>
Market-TA Market-TA
</button> </button>
} }
@ -236,8 +254,14 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button"; const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse); const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
// Export material button function openExportPopup() {
const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat); const popupId = "cmpy-mgmt-export-popup";
createPopup(popupId, ExportPopup, {
mat: mat,
corp: props.corp,
popupId: popupId,
});
}
// Sell material button // Sell material button
let sellButtonText; let sellButtonText;
@ -265,8 +289,15 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
} }
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat); const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
// Market TA button function openMaterialMarketTaPopup(): void {
const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division); const popupId = "cmpy-mgmt-export-popup";
createPopup(popupId, MaterialMarketTaPopup, {
mat: mat,
industry: division,
corp: props.corp,
popupId: popupId,
});
}
return ( return (
<div className={"cmpy-mgmt-warehouse-material-div"}> <div className={"cmpy-mgmt-warehouse-material-div"}>
@ -322,7 +353,7 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
{ {
corp.unlockUpgrades[0] === 1 && corp.unlockUpgrades[0] === 1 &&
<button className={"std-button"} onClick={exportButtonOnClick}> <button className={"std-button"} onClick={openExportPopup}>
Export Export
</button> </button>
} }
@ -334,7 +365,7 @@ function MaterialComponent(props: IMaterialProps): React.ReactElement {
{ {
division.hasResearch("Market-TA.I") && division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}> <button className={"std-button"} onClick={openMaterialMarketTaPopup}>
Market-TA Market-TA
</button> </button>
} }

@ -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<number | null>(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<HTMLInputElement>): void {
if (event.keyCode === 13) issueDividends();
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
if(event.target.value === "") setPercent(null);
else setPercent(parseFloat(event.target.value));
}
return (<>
<p>
"Dividends are a distribution of a portion of the corporation's
profits to the shareholders. This includes yourself, as well.<br /><br />
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<br /><br />
Two important things to note:<br />
* Issuing dividends will negatively affect your corporation's stock price<br />
* Dividends are taxed. Taxes start at 50%, but can be decreased<br /><br />
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.
</p>
<input autoFocus={true} onChange={onChange} onKeyDown={onKeyDown} className="text-input" placeholder="Dividend %" type="number" style={{margin: "5px"}} />
<button onClick={issueDividends} className="std-button" style={{display: "inline-block"}}>Allocate Dividend Percentage</button>
</>);
}

@ -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 (<p>Invalid input</p>);
}
// Round to nearest ten-millionth
newShares /= 10e6;
newShares = Math.round(newShares) * 10e6;
if (newShares < 10e6) {
return (<p>Must issue at least 10 million new shares</p>);
}
if (newShares > maxNewShares) {
return (<p>You cannot issue that many shares</p>);
}
return (<p>
Issue ${numeralWrapper.format(newShares, "0.000a")} new
shares for {numeralWrapper.formatMoney(newShares * newSharePrice)}?
</p>);
}
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<number | null>(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.<br><br>` +
`Stock price decreased to ${numeralWrapper.formatMoney(props.corp.sharePrice)}`);
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) issueNewShares();
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
if(event.target.value === "") setShares(null);
else setShares(parseFloat(event.target.value));
}
return (<>
<p>
You can issue new equity shares (i.e. stocks) in order to raise
capital for your corporation.<br /><br />
&nbsp;* You can issue at most {numeralWrapper.formatMoney(maxNewShares)} new shares<br />
&nbsp;* New shares are sold at a 10% discount<br />
&nbsp;* You can only issue new shares once every 12 hours<br />
&nbsp;* Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share<br />
&nbsp;* Number of new shares issued must be a multiple of 10 million<br /><br />
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.
</p>
<EffectText corp={props.corp} shares={shares} />
<input className="text-input" autoFocus={true} placeholder="# New Shares" style={{margin: "5px"}} onChange={onChange} onKeyDown={onKeyDown} />
<button onClick={issueNewShares} className="std-button" style={{display: "inline-block"}}>Issue New Shares</button>
</>);
// let issueBtn, newSharesInput;
// const dynamicText = createElement("p", {
// display: "block",
// });
// function updateDynamicText(corp) {
// }
// createPopup(popupId, [descText, dynamicText, newSharesInput, issueBtn, cancelBtn]);
// newSharesInput.focus();
}

@ -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<number | null>(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<HTMLInputElement>): void {
if (event.keyCode === 13) limitProductProduction();
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
if(event.target.value === "") setLimit(null);
else setLimit(parseFloat(event.target.value));
}
return (<>
<p>
Enter a limit to the amount of this product you would
like to product per second. Leave the box empty to set no limit.
</p>
<input autoFocus={true} className="text-input" style={{margin: "5px"}} placeholder="Limit" type="number" onChange={onChange} onKeyDown={onKeyDown} />
<button className="std-button" style={{margin:"5px", display:"inline-block"}} onClick={limitProductProduction}>Limit production</button>
</>);
}

@ -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<number | null>(null);
const [marketing, setMarketing] = useState<number | null>(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<HTMLSelectElement>): void {
setCity(event.target.value);
}
function onProductNameChange(event: React.ChangeEvent<HTMLInputElement>): void {
setName(event.target.value);
}
function onDesignChange(event: React.ChangeEvent<HTMLInputElement>): void {
if(event.target.value === "") setDesign(null);
else setDesign(parseFloat(event.target.value));
}
function onMarketingChange(event: React.ChangeEvent<HTMLInputElement>): void {
if(event.target.value === "") setMarketing(null);
else setMarketing(parseFloat(event.target.value));
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) makeProduct();
}
return (<>
<p dangerouslySetInnerHTML={{__html: props.popupText}} />
<select className="dropdown" style={{margin: "5px"}} onChange={onCityChange} defaultValue={city}>
{
allCities.map((cityName: string) =>
<option key={cityName} value={cityName}>{cityName}</option>)
}
</select>
<input onChange={onProductNameChange} className="text-input" style={{margin:"5px"}} placeholder={productPlaceholder(props.division.type)} />
<br />
<input onChange={onDesignChange} autoFocus={true} type="number" className="text-input" style={{margin:"5px"}} placeholder={"Design investment"}/>
<input onChange={onMarketingChange} onKeyDown={onKeyDown} type="number" className="text-input" style={{margin:"5px"}} placeholder={"Marketing investment"}/>
<button className="std-button" onClick={makeProduct}>Develop Product</button>
</>);
}

@ -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<number>(props.mat.bCost);
const setRerender = useState(false)[1];
function rerender(): void {
setRerender(old => !old);
}
const markupLimit = props.mat.getMarkupLimit();
function onChange(event: React.ChangeEvent<HTMLInputElement>): 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<HTMLInputElement>): void {
props.mat.marketTa2 = event.target.checked;
rerender();
}
return (<>
<p>
<br /><u><strong>Market-TA.II</strong></u><br />
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.
</p>
<input className="text-input" type="number" style={{marginTop: "4px"}} onChange={onChange} value={newCost} />
<div style={{display: "block"}}>
<label className="tooltip" htmlFor="cmpy-mgmt-marketa2-checkbox" style={{color: "white"}}>
Use Market-TA.II for Auto-Sale Price
<span className="tooltiptext">
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)
</span>
</label>
<input id="cmpy-mgmt-marketa2-checkbox" type="checkbox" onChange={onMarketTA2} checked={props.mat.marketTa2} style={{margin: "3px"}} />
</div>
<p>
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
</p>
</>);
}
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<HTMLInputElement>): void {
props.mat.marketTa1 = event.target.checked;
rerender();
}
return (<>
<p>
<u><strong>Market-TA.I</strong></u><br />
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
</p>
<div style={{display: 'block'}}>
<label className="tooltip" htmlFor="cmpy-mgmt-marketa1-checkbox" style={{color: "white"}}>
Use Market-TA.I for Auto-Sale Price
<span className="tooltiptext">
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)
</span>
</label>
<input id="cmpy-mgmt-marketa1-checkbox" type="checkbox" onChange={onMarketTA1} checked={props.mat.marketTa1} style={{margin: "3px"}} />
</div>
<MarketTA2 mat={props.mat} industry={props.industry} />
</>);
}

@ -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<HTMLInputElement>): void {
setName(event.target.value);
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) newIndustry();
}
function onIndustryChange(event: React.ChangeEvent<HTMLSelectElement>): void {
setIndustry(event.target.value);
}
return (<>
<p>Create a new division to expand into a new industry:</p>
<select className="dropdown" defaultValue={industry} onChange={onIndustryChange}>
{
possibleIndustries.map((industry: string) =>
<option key={industry} value={industry}>{industry}</option>)
}
</select>
<p>{IndustryDescriptions[industry]}</p>
<br /><br />
<p>Division name:</p>
<input autoFocus={true} value={name} onChange={onNameChange} onKeyDown={onKeyDown} type="text" className="text-input" style={{display:"block"}} maxLength={30} pattern="[a-zA-Z0-9-_]" />
<span onClick={newIndustry} className="popup-box-button">Create Division</span>
</>);
}

@ -5,6 +5,8 @@ import { UnlockUpgrade } from "./UnlockUpgrade";
import { BribeFactionPopup } from "./BribeFactionPopup"; import { BribeFactionPopup } from "./BribeFactionPopup";
import { SellSharesPopup } from "./SellSharesPopup"; import { SellSharesPopup } from "./SellSharesPopup";
import { BuybackSharesPopup } from "./BuybackSharesPopup"; import { BuybackSharesPopup } from "./BuybackSharesPopup";
import { IssueDividendsPopup } from "./IssueDividendsPopup";
import { IssueNewSharesPopup } from "./IssueNewSharesPopup";
import { CorporationConstants } from "../data/Constants"; import { CorporationConstants } from "../data/Constants";
import { CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades"; 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.", 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 issueNewSharesOnCd = (corp.issueNewSharesCooldown > 0);
const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button"; const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button";
const issueNewSharesTooltip = issueNewSharesOnCd const issueNewSharesTooltip = issueNewSharesOnCd
@ -253,15 +263,23 @@ export function Overview(props: IProps): React.ReactElement {
const issueNewSharesBtn = createButton({ const issueNewSharesBtn = createButton({
class: issueNewSharesClass, class: issueNewSharesClass,
display: "inline-block", display: "inline-block",
onClick: props.eventHandler.createIssueNewSharesPopup, onClick: openIssueNewSharesPopup,
text: "Issue New Shares", text: "Issue New Shares",
tooltip: issueNewSharesTooltip, tooltip: issueNewSharesTooltip,
}); });
function openIssueDividendsPopup(): void {
const popupId = "cmpy-mgmt-issue-dividends-popup";
createPopup(popupId, IssueDividendsPopup, {
popupId: popupId,
corp: props.corp,
});
}
const issueDividendsBtn = createButton({ const issueDividendsBtn = createButton({
class: "std-button", class: "std-button",
display: "inline-block", display: "inline-block",
onClick: props.eventHandler.createIssueDividendsPopup, onClick: openIssueDividendsPopup,
text: "Issue Dividends", text: "Issue Dividends",
tooltip: "Manage the dividends that are paid out to shareholders (including yourself)", tooltip: "Manage the dividends that are paid out to shareholders (including yourself)",
}); });

@ -591,6 +591,13 @@ class DevMenuComponent extends Component {
}); });
} }
addCorporationResearch() {
if(!Player.corporation) return;
Player.corporation.divisions.forEach(div => {
div.sciResearch.qty += 1e10;
});
}
specificContract() { specificContract() {
generateContract({ generateContract({
problemType: this.state.codingcontract, problemType: this.state.codingcontract,
@ -1181,6 +1188,11 @@ class DevMenuComponent extends Component {
<button className="std-button" onClick={this.finishCorporationProducts}>Finish products</button> <button className="std-button" onClick={this.finishCorporationProducts}>Finish products</button>
</td> </td>
</tr> </tr>
<tr>
<td>
<button className="std-button" onClick={this.addCorporationResearch}>Tons of research</button>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>