Merge pull request #1115 from danielyxie/react-corp

React corp
This commit is contained in:
hydroflame 2021-09-04 15:11:43 -04:00 committed by GitHub
commit 2d322e7a6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 6147 additions and 7644 deletions

File diff suppressed because one or more lines are too long

@ -1,2 +1,2 @@
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([940,0]),o()}({877:function(n,t,o){},879:function(n,t,o){},881:function(n,t,o){},883:function(n,t,o){},885:function(n,t,o){},887:function(n,t,o){},889:function(n,t,o){},891:function(n,t,o){},893:function(n,t,o){},895:function(n,t,o){},897:function(n,t,o){},899:function(n,t,o){},901:function(n,t,o){},903:function(n,t,o){},905:function(n,t,o){},907:function(n,t,o){},909:function(n,t,o){},911:function(n,t,o){},913:function(n,t,o){},915:function(n,t,o){},917:function(n,t,o){},919:function(n,t,o){},921:function(n,t,o){},923:function(n,t,o){},925:function(n,t,o){},927:function(n,t,o){},929:function(n,t,o){},931:function(n,t,o){},933:function(n,t,o){},935:function(n,t,o){},937:function(n,t,o){},940:function(n,t,o){"use strict";o.r(t);o(939),o(937),o(935),o(933),o(931),o(929),o(927),o(925),o(923),o(921),o(919),o(917),o(915),o(913),o(911),o(909),o(907),o(905),o(903),o(901),o(899),o(897),o(895),o(893),o(891),o(889),o(887),o(885),o(883),o(881),o(879),o(877)}});
!function(n){function t(t){for(var e,i,f=t[0],c=t[1],l=t[2],p=0,s=[];p<f.length;p++)i=f[p],u[i]&&s.push(u[i][0]),u[i]=0;for(e in c)Object.prototype.hasOwnProperty.call(c,e)&&(n[e]=c[e]);for(a&&a(t);s.length;)s.shift()();return r.push.apply(r,l||[]),o()}function o(){for(var n,t=0;t<r.length;t++){for(var o=r[t],e=!0,f=1;f<o.length;f++){var c=o[f];0!==u[c]&&(e=!1)}e&&(r.splice(t--,1),n=i(i.s=o[0]))}return n}var e={},u={1:0},r=[];function i(t){if(e[t])return e[t].exports;var o=e[t]={i:t,l:!1,exports:{}};return n[t].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=n,i.c=e,i.d=function(n,t,o){i.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:o})},i.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var o=Object.create(null);if(i.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(o,e,function(t){return n[t]}.bind(null,e));return o},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,"a",t),t},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="";var f=window.webpackJsonp=window.webpackJsonp||[],c=f.push.bind(f);f.push=t,f=f.slice();for(var l=0;l<f.length;l++)t(f[l]);var a=c;r.push([926,0]),o()}({863:function(n,t,o){},865:function(n,t,o){},867:function(n,t,o){},869:function(n,t,o){},871:function(n,t,o){},873:function(n,t,o){},875:function(n,t,o){},877:function(n,t,o){},879:function(n,t,o){},881:function(n,t,o){},883:function(n,t,o){},885:function(n,t,o){},887:function(n,t,o){},889:function(n,t,o){},891:function(n,t,o){},893:function(n,t,o){},895:function(n,t,o){},897:function(n,t,o){},899:function(n,t,o){},901:function(n,t,o){},903:function(n,t,o){},905:function(n,t,o){},907:function(n,t,o){},909:function(n,t,o){},911:function(n,t,o){},913:function(n,t,o){},915:function(n,t,o){},917:function(n,t,o){},919:function(n,t,o){},921:function(n,t,o){},923:function(n,t,o){},926:function(n,t,o){"use strict";o.r(t);o(925),o(923),o(921),o(919),o(917),o(915),o(913),o(911),o(909),o(907),o(905),o(903),o(901),o(899),o(897),o(895),o(893),o(891),o(889),o(887),o(885),o(883),o(881),o(879),o(877),o(875),o(873),o(871),o(869),o(867),o(865),o(863)}});
//# sourceMappingURL=engineStyle.bundle.js.map

32
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -628,6 +628,5 @@
<!-- Misc Scripts -->
<script src="src/ThirdParty/raphael.min.js"></script>
<script src="src/ThirdParty/Treant.js"></script>
</html>

11
package-lock.json generated

@ -48,6 +48,7 @@
"react-modal": "^3.12.1",
"sprintf-js": "^1.1.1",
"tapable": "^1.0.0",
"treant-js": "^1.0.1",
"uuid": "^3.2.1",
"w3c-blob": "0.0.1"
},
@ -21630,6 +21631,11 @@
"node": ">=6"
}
},
"node_modules/treant-js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/treant-js/-/treant-js-1.0.1.tgz",
"integrity": "sha1-aRdSt+9maSCzQiP8ZlJUaTtG/VQ="
},
"node_modules/trim": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
@ -41190,6 +41196,11 @@
}
}
},
"treant-js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/treant-js/-/treant-js-1.0.1.tgz",
"integrity": "sha1-aRdSt+9maSCzQiP8ZlJUaTtG/VQ="
},
"trim": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",

@ -45,6 +45,7 @@
"react-modal": "^3.12.1",
"sprintf-js": "^1.1.1",
"tapable": "^1.0.0",
"treant-js": "^1.0.1",
"uuid": "^3.2.1",
"w3c-blob": "0.0.1"
},

228
src/Corporation/Actions.ts Normal file

@ -0,0 +1,228 @@
import { ICorporation } from "./ICorporation";
import { IIndustry } from "./IIndustry";
import { IndustryStartingCosts } from "./IndustryData";
import { Industry } from "./Industry";
import { CorporationConstants } from "./data/Constants";
import { OfficeSpace } from "./OfficeSpace";
import { Material } from "./Material";
import { Product } from "./Product";
import { Warehouse } from "./Warehouse";
import { CorporationUnlockUpgrade } from "./data/CorporationUnlockUpgrades";
import { CorporationUpgrade } from "./data/CorporationUpgrades";
import { Cities } from "../Locations/Cities";
export function NewIndustry(corporation: ICorporation, industry: string, name: string): void {
for (let i = 0; i < corporation.divisions.length; ++i) {
if (corporation.divisions[i].name === name) {
throw new Error("This division name is already in use!");
return;
}
}
const cost = IndustryStartingCosts[industry];
if(cost === undefined) {
throw new Error("Invalid industry: ${industry}");
}
if (corporation.funds.lt(cost)) {
throw new Error("Not enough money to create a new division in this industry");
} else if (name === "") {
throw new Error("New division must have a name!");
} else {
corporation.funds = corporation.funds.minus(cost);
corporation.divisions.push(new Industry({
corp: corporation,
name: name,
type: industry,
}));
}
}
export function NewCity(corporation: ICorporation, division: IIndustry, city: string): void {
if (corporation.funds.lt(CorporationConstants.OfficeInitialCost)) {
throw new Error("You don't have enough company funds to open a new office!");
} else {
corporation.funds = corporation.funds.minus(CorporationConstants.OfficeInitialCost);
division.offices[city] = new OfficeSpace({
loc: city,
size: CorporationConstants.OfficeInitialSize,
});
}
}
export function UnlockUpgrade(corporation: ICorporation, upgrade: CorporationUnlockUpgrade): void {
if (corporation.funds.lt(upgrade[1])) {
throw new Error("Insufficient funds");
}
corporation.unlock(upgrade);
}
export function LevelUpgrade(corporation: ICorporation, upgrade: CorporationUpgrade): void {
const baseCost = upgrade[1];
const priceMult = upgrade[2];
const level = corporation.upgrades[upgrade[0]];
const cost = baseCost * Math.pow(priceMult, level);
if (corporation.funds.lt(cost)) {
throw new Error("Insufficient funds");
} else {
corporation.upgrade(upgrade);
}
}
export function IssueDividends(corporation: ICorporation, percent: number): void {
if (isNaN(percent) || percent < 0 || percent > CorporationConstants.DividendMaxPercentage) {
throw new Error(`Invalid value. Must be an integer between 0 and ${CorporationConstants.DividendMaxPercentage}`);
}
corporation.dividendPercentage = percent*100;
}
export function SellMaterial(mat: Material, amt: string, price: string): void {
if(price === '') price = '0';
if(amt === '') amt = '0';
let cost = price.replace(/\s+/g, '');
cost = cost.replace(/[^-()\d/*+.MP]/g, ''); //Sanitize cost
let temp = cost.replace(/MP/g, mat.bCost+'');
try {
temp = eval(temp);
} catch(e) {
throw new Error("Invalid value or expression for sell price field: " + e);
}
if (temp == null || isNaN(parseFloat(temp))) {
throw new Error("Invalid value or expression for sell price field");
}
if (cost.includes("MP")) {
mat.sCost = cost; //Dynamically evaluated
} else {
mat.sCost = temp;
}
//Parse quantity
if (amt.includes("MAX") || amt.includes("PROD")) {
let q = amt.replace(/\s+/g, '');
q = q.replace(/[^-()\d/*+.MAXPROD]/g, '');
let tempQty = q.replace(/MAX/g, '1');
tempQty = tempQty.replace(/PROD/g, '1');
try {
tempQty = eval(tempQty);
} catch(e) {
throw new Error("Invalid value or expression for sell price field: " + e);
}
if (tempQty == null || isNaN(parseFloat(tempQty))) {
throw new Error("Invalid value or expression for sell price field");
}
mat.sllman[0] = true;
mat.sllman[1] = q; //Use sanitized input
} else if (isNaN(parseFloat(amt))) {
throw new Error("Invalid value for sell quantity field! Must be numeric or 'MAX'");
} else {
let q = parseFloat(amt);
if (isNaN(q)) {q = 0;}
if (q === 0) {
mat.sllman[0] = false;
mat.sllman[1] = 0;
} else {
mat.sllman[0] = true;
mat.sllman[1] = q;
}
}
}
export function SellProduct(product: Product, city: string, amt: string, price: string, all: boolean): void {
//Parse price
if (price.includes("MP")) {
//Dynamically evaluated quantity. First test to make sure its valid
//Sanitize input, then replace dynamic variables with arbitrary numbers
price = price.replace(/\s+/g, '');
price = price.replace(/[^-()\d/*+.MP]/g, '');
let temp = price.replace(/MP/g, '1');
try {
temp = eval(temp);
} catch(e) {
throw new Error("Invalid value or expression for sell quantity field: " + e);
}
if (temp == null || isNaN(parseFloat(temp))) {
throw new Error("Invalid value or expression for sell quantity field.");
}
product.sCost = price; //Use sanitized price
} else {
const cost = parseFloat(price);
if (isNaN(cost)) {
throw new Error("Invalid value for sell price field");
}
product.sCost = cost;
}
// Array of all cities. Used later
const cities = Object.keys(Cities);
// Parse quantity
if (amt.includes("MAX") || amt.includes("PROD")) {
//Dynamically evaluated quantity. First test to make sure its valid
let qty = amt.replace(/\s+/g, '');
qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, '');
let temp = qty.replace(/MAX/g, '1');
temp = temp.replace(/PROD/g, '1');
try {
temp = eval(temp);
} catch(e) {
throw new Error("Invalid value or expression for sell price field: " + e);
}
if (temp == null || isNaN(parseFloat(temp))) {
throw new Error("Invalid value or expression for sell price field");
}
if (all) {
for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = true;
product.sllman[tempCity][1] = qty; //Use sanitized input
}
} else {
product.sllman[city][0] = true;
product.sllman[city][1] = qty; //Use sanitized input
}
} else if (isNaN(parseFloat(amt))) {
throw new Error("Invalid value for sell quantity field! Must be numeric");
} else {
let qty = parseFloat(amt);
if (isNaN(qty)) {qty = 0;}
if (qty === 0) {
if (all) {
for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = false;
product.sllman[tempCity][1] = '';
}
} else {
product.sllman[city][0] = false;
product.sllman[city][1] = '';
}
} else {
if (all) {
for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = true;
product.sllman[tempCity][1] = qty;
}
} else {
product.sllman[city][0] = true;
product.sllman[city][1] = qty;
}
}
}
}
export function SetSmartSupply(warehouse: Warehouse, smartSupply: boolean): void {
warehouse.smartSupplyEnabled = smartSupply;
}
export function BuyMaterial(material: Material, amt: number): void {
if (isNaN(amt)) {
throw new Error(`Invalid amount '${amt}' to buy material '${material.name}'`);
}
material.buy = amt;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,434 @@
import { CorporationState } from "./CorporationState";
import {
CorporationUnlockUpgrade,
CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades";
import { CorporationUpgrade, CorporationUpgrades } from "./data/CorporationUpgrades";
import { Warehouse } from "./Warehouse";
import { CorporationConstants } from "./data/Constants";
import { Industry } from "./Industry";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { showLiterature } from "../Literature/LiteratureHelpers";
import { LiteratureNames } from "../Literature/data/LiteratureNames";
import { IPlayer } from "../PersonObjects/IPlayer";
import { Page, routing } from "../ui/navigationTracking";
import { dialogBoxCreate } from "../../utils/DialogBox";
import { Reviver,
Generic_toJSON,
Generic_fromJSON } from "../../utils/JSONReviver";
import { createElement } from "../../utils/uiHelpers/createElement";
import { isString } from "../../utils/helpers/isString";
import { removeElementById } from "../../utils/uiHelpers/removeElementById";
// UI Related Imports
import React from "react";
import ReactDOM from "react-dom";
import { CorporationRoot } from "./ui/Root";
import { CorporationRouting } from "./ui/Routing";
import Decimal from "decimal.js";
interface IParams {
name?: string;
}
let corpRouting: CorporationRouting;
let companyManagementDiv: HTMLDivElement | null = null;
export class Corporation {
name = "The Corporation";
//A division/business sector is represented by the object:
divisions: Industry[] = [];
//Financial stats
funds = new Decimal(150e9);
revenue = new Decimal(0);
expenses = new Decimal(0);
fundingRound = 0;
public = false; //Publicly traded
totalShares = CorporationConstants.INITIALSHARES; // Total existing shares
numShares = CorporationConstants.INITIALSHARES; // Total shares owned by player
shareSalesUntilPriceUpdate = CorporationConstants.SHARESPERPRICEUPDATE;
shareSaleCooldown = 0; // Game cycles until player can sell shares again
issueNewSharesCooldown = 0; // Game cycles until player can issue shares again
dividendPercentage = 0;
dividendTaxPercentage = 50;
issuedShares = 0;
sharePrice = 0;
storedCycles = 0;
unlockUpgrades: number[];
upgrades: number[];
upgradeMultipliers: number[];
state = new CorporationState();
constructor(params: IParams = {}) {
this.name = params.name ? params.name : "The Corporation";
const numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length;
const numUpgrades = Object.keys(CorporationUpgrades).length;
this.unlockUpgrades = Array(numUnlockUpgrades).fill(0);
this.upgrades = Array(numUpgrades).fill(0);
this.upgradeMultipliers = Array(numUpgrades).fill(1);
}
addFunds(amt: number): void {
if(!isFinite(amt)) {
console.error('Trying to add invalid amount of funds. Report to a developper.');
return;
}
this.funds = this.funds.plus(amt);
}
getState(): string {
return this.state.getState();
}
storeCycles(numCycles=1): void {
this.storedCycles += numCycles;
}
process(player: IPlayer): void {
if (this.storedCycles >= CorporationConstants.CyclesPerIndustryStateCycle) {
const state = this.getState();
const marketCycles = 1;
const gameCycles = (marketCycles * CorporationConstants.CyclesPerIndustryStateCycle);
this.storedCycles -= gameCycles;
this.divisions.forEach((ind) => {
ind.process(marketCycles, state, this);
});
// Process cooldowns
if (this.shareSaleCooldown > 0) {
this.shareSaleCooldown -= gameCycles;
}
if (this.issueNewSharesCooldown > 0) {
this.issueNewSharesCooldown -= gameCycles;
}
//At the start of a new cycle, calculate profits from previous cycle
if (state === "START") {
this.revenue = new Decimal(0);
this.expenses = new Decimal(0);
this.divisions.forEach((ind) => {
if (ind.lastCycleRevenue === -Infinity || ind.lastCycleRevenue === Infinity) { return; }
if (ind.lastCycleExpenses === -Infinity || ind.lastCycleExpenses === Infinity) { return; }
this.revenue = this.revenue.plus(ind.lastCycleRevenue);
this.expenses = this.expenses.plus(ind.lastCycleExpenses);
});
const profit = this.revenue.minus(this.expenses);
const cycleProfit = profit.times(marketCycles * CorporationConstants.SecsPerMarketCycle);
if (isNaN(this.funds) || this.funds === Infinity || this.funds === -Infinity) {
dialogBoxCreate("There was an error calculating your Corporations funds and they got reset to 0. " +
"This is a bug. Please report to game developer.<br><br>" +
"(Your funds have been set to $150b for the inconvenience)");
this.funds = new Decimal(150e9);
}
// Process dividends
if (this.dividendPercentage > 0 && cycleProfit > 0) {
// Validate input again, just to be safe
if (isNaN(this.dividendPercentage) || this.dividendPercentage < 0 || this.dividendPercentage > CorporationConstants.DividendMaxPercentage*100) {
console.error(`Invalid Corporation dividend percentage: ${this.dividendPercentage}`);
} else {
const totalDividends = (this.dividendPercentage / 100) * cycleProfit;
const retainedEarnings = cycleProfit - totalDividends;
const dividendsPerShare = totalDividends / this.totalShares;
const profit = this.numShares * dividendsPerShare * (1 - (this.dividendTaxPercentage / 100));
player.gainMoney(profit);
player.recordMoneySource(profit, "corporation");
this.addFunds(retainedEarnings);
}
} else {
this.addFunds(cycleProfit);
}
this.updateSharePrice();
}
this.state.nextState();
if (routing.isOn(Page.Corporation)) this.rerender(player);
}
}
determineValuation(): number {
let val, profit = (this.revenue.minus(this.expenses)).toNumber();
if (this.public) {
// Account for dividends
if (this.dividendPercentage > 0) {
profit *= ((100 - this.dividendPercentage) / 100);
}
val = this.funds.toNumber() + (profit * 85e3);
val *= (Math.pow(1.1, this.divisions.length));
val = Math.max(val, 0);
} else {
val = 10e9 + Math.max(this.funds.toNumber(), 0) / 3; //Base valuation
if (profit > 0) {
val += (profit * 315e3);
val *= (Math.pow(1.1, this.divisions.length));
} else {
val = 10e9 * Math.pow(1.1, this.divisions.length);
}
val -= (val % 1e6); //Round down to nearest millionth
}
return val * BitNodeMultipliers.CorporationValuation;
}
getTargetSharePrice(): number {
// Note: totalShares - numShares is not the same as issuedShares because
// issuedShares does not account for private investors
return this.determineValuation() / (2 * (this.totalShares - this.numShares) + 1);
}
updateSharePrice(): void {
const targetPrice = this.getTargetSharePrice();
if (this.sharePrice <= targetPrice) {
this.sharePrice *= (1 + (Math.random() * 0.01));
} else {
this.sharePrice *= (1 - (Math.random() * 0.01));
}
if (this.sharePrice <= 0.01) {this.sharePrice = 0.01;}
}
immediatelyUpdateSharePrice(): void {
this.sharePrice = this.getTargetSharePrice();
}
// Calculates how much money will be made and what the resulting stock price
// will be when the player sells his/her shares
// @return - [Player profit, final stock price, end shareSalesUntilPriceUpdate property]
calculateShareSale(numShares: number): [number, number, number] {
let sharesTracker = numShares;
let sharesUntilUpdate = this.shareSalesUntilPriceUpdate;
let sharePrice = this.sharePrice;
let sharesSold = 0;
let profit = 0;
const maxIterations = Math.ceil(numShares / CorporationConstants.SHARESPERPRICEUPDATE);
if (isNaN(maxIterations) || maxIterations > 10e6) {
console.error(`Something went wrong or unexpected when calculating share sale. Maxiterations calculated to be ${maxIterations}`);
return [0, 0, 0];
}
for (let i = 0; i < maxIterations; ++i) {
if (sharesTracker < sharesUntilUpdate) {
profit += (sharePrice * sharesTracker);
sharesUntilUpdate -= sharesTracker;
break;
} else {
profit += (sharePrice * sharesUntilUpdate);
sharesUntilUpdate = CorporationConstants.SHARESPERPRICEUPDATE;
sharesTracker -= sharesUntilUpdate;
sharesSold += sharesUntilUpdate;
// Calculate what new share price would be
sharePrice = this.determineValuation() / (2 * (this.totalShares + sharesSold - this.numShares));
}
}
return [profit, sharePrice, sharesUntilUpdate];
}
convertCooldownToString(cd: number): string {
// The cooldown value is based on game cycles. Convert to a simple string
const seconds = cd / 5;
const SecondsPerMinute = 60;
const SecondsPerHour = 3600;
if (seconds > SecondsPerHour) {
return `${Math.floor(seconds / SecondsPerHour)} hour(s)`;
} else if (seconds > SecondsPerMinute) {
return `${Math.floor(seconds / SecondsPerMinute)} minute(s)`;
} else {
return `${Math.floor(seconds)} second(s)`;
}
}
//One time upgrades that unlock new features
unlock(upgrade: CorporationUnlockUpgrade): void {
const upgN = upgrade[0], price = upgrade[1];
while (this.unlockUpgrades.length <= upgN) {
this.unlockUpgrades.push(0);
}
if (this.funds.lt(price)) {
dialogBoxCreate("You don't have enough funds to unlock this!");
return;
}
this.unlockUpgrades[upgN] = 1;
this.funds = this.funds.minus(price);
// Apply effects for one-time upgrades
if (upgN === 5) {
this.dividendTaxPercentage -= 5;
} else if (upgN === 6) {
this.dividendTaxPercentage -= 10;
}
}
//Levelable upgrades
upgrade(upgrade: CorporationUpgrade): void {
const upgN = upgrade[0], basePrice = upgrade[1], priceMult = upgrade[2],
upgradeAmt = upgrade[3]; //Amount by which the upgrade multiplier gets increased (additive)
while (this.upgrades.length <= upgN) {this.upgrades.push(0);}
while (this.upgradeMultipliers.length <= upgN) {this.upgradeMultipliers.push(1);}
const totalCost = basePrice * Math.pow(priceMult, this.upgrades[upgN]);
if (this.funds.lt(totalCost)) {
dialogBoxCreate("You don't have enough funds to purchase this!");
return;
}
++this.upgrades[upgN];
this.funds = this.funds.minus(totalCost);
//Increase upgrade multiplier
this.upgradeMultipliers[upgN] = 1 + (this.upgrades[upgN] * upgradeAmt);
//If storage size is being updated, update values in Warehouse objects
if (upgN === 1) {
for (let i = 0; i < this.divisions.length; ++i) {
const industry = this.divisions[i];
for (const city in industry.warehouses) {
const warehouse = industry.warehouses[city]
if(warehouse === 0) continue
if (industry.warehouses.hasOwnProperty(city) && warehouse instanceof Warehouse) {
warehouse.updateSize(this, industry);
}
}
}
}
}
getProductionMultiplier(): number {
const mult = this.upgradeMultipliers[0];
if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
}
getStorageMultiplier(): number {
const mult = this.upgradeMultipliers[1];
if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
}
getDreamSenseGain(): number {
const gain = this.upgradeMultipliers[2] - 1;
return gain <= 0 ? 0 : gain;
}
getAdvertisingMultiplier(): number {
const mult = this.upgradeMultipliers[3];
if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
}
getEmployeeCreMultiplier(): number {
const mult = this.upgradeMultipliers[4];
if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
}
getEmployeeChaMultiplier(): number {
const mult = this.upgradeMultipliers[5];
if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
}
getEmployeeIntMultiplier(): number {
const mult = this.upgradeMultipliers[6];
if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
}
getEmployeeEffMultiplier(): number {
const mult = this.upgradeMultipliers[7];
if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
}
getSalesMultiplier(): number {
const mult = this.upgradeMultipliers[8];
if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
}
getScientificResearchMultiplier(): number {
const mult = this.upgradeMultipliers[9];
if (isNaN(mult) || mult < 1) {return 1;} else {return mult;}
}
// Adds the Corporation Handbook (Starter Guide) to the player's home computer.
// This is a lit file that gives introductory info to the player
// This occurs when the player clicks the "Getting Started Guide" button on the overview panel
getStarterGuide(player: IPlayer): void {
// Check if player already has Corporation Handbook
const homeComp = player.getHomeComputer();
let hasHandbook = false;
const handbookFn = LiteratureNames.CorporationManagementHandbook;
for (let i = 0; i < homeComp.messages.length; ++i) {
if (isString(homeComp.messages[i]) && homeComp.messages[i] === handbookFn) {
hasHandbook = true;
break;
}
}
if (!hasHandbook) { homeComp.messages.push(handbookFn); }
showLiterature(handbookFn);
return;
}
createUI(player: IPlayer): void {
companyManagementDiv = createElement("div", {
id:"cmpy-mgmt-container",
position:"fixed",
class:"generic-menupage-container",
}) as HTMLDivElement;
const game = document.getElementById("entire-game-container");
if(game)
game.appendChild(companyManagementDiv);
corpRouting = new CorporationRouting(this);
this.rerender(player);
}
rerender(player: IPlayer): void {
if (companyManagementDiv == null || corpRouting == null) {
console.warn(`Corporation.rerender() called when companyManagementDiv, corpRouting, or eventHandler is null`);
return;
}
if (!routing.isOn(Page.Corporation)) return;
ReactDOM.render(<CorporationRoot
corp={this}
routing={corpRouting}
player={player}
/>, companyManagementDiv);
}
clearUI(): void {
if (companyManagementDiv instanceof HTMLElement) {
ReactDOM.unmountComponentAtNode(companyManagementDiv);
removeElementById(companyManagementDiv.id);
}
companyManagementDiv = null;
const character = document.getElementById("character-overview-wrapper");
if(character)
character.style.visibility = "visible";
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): any {
return Generic_toJSON("Corporation", this);
}
/**
* Initiatizes a Corporation object from a JSON save state.
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromJSON(value: any): Corporation {
return Generic_fromJSON(Corporation, value.data);
}
}
Reviver.constructors.Corporation = Corporation;

197
src/Corporation/Employee.ts Normal file

@ -0,0 +1,197 @@
import { CorporationConstants } from "./data/Constants";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
import { createElement } from "../../utils/uiHelpers/createElement";
import { EmployeePositions } from "./EmployeePositions";
import { ICorporation } from "./ICorporation";
import { numeralWrapper } from "../ui/numeralFormat";
import { formatNumber } from "../../utils/StringHelperFunctions";
import { OfficeSpace } from "./OfficeSpace";
import { IIndustry } from "./IIndustry";
interface IParams {
name?: string;
morale?: number;
happiness?: number;
energy?: number;
intelligence?: number;
charisma?: number;
experience?: number;
creativity?: number;
efficiency?: number;
salary?: number;
loc?: string;
}
export class Employee {
name: string;
mor: number;
hap: number;
ene: number;
int: number;
cha: number;
exp: number;
cre: number;
eff: number;
sal: number;
pro = 0;
cyclesUntilRaise = CorporationConstants.CyclesPerEmployeeRaise;
loc: string;
pos: string;
constructor(params: IParams = {}) {
this.name = params.name ? params.name : "Bobby";
//Morale, happiness, and energy are 0-100
this.mor = params.morale ? params.morale : getRandomInt(50, 100);
this.hap = params.happiness ? params.happiness : getRandomInt(50, 100);
this.ene = params.energy ? params.energy : getRandomInt(50, 100);
this.int = params.intelligence ? params.intelligence : getRandomInt(10, 50);
this.cha = params.charisma ? params.charisma : getRandomInt(10, 50);
this.exp = params.experience ? params.experience : getRandomInt(10, 50);
this.cre = params.creativity ? params.creativity : getRandomInt(10, 50);
this.eff = params.efficiency ? params.efficiency : getRandomInt(10, 50);
this.sal = params.salary ? params.salary : getRandomInt(0.1, 5);
this.loc = params.loc ? params.loc : "";
this.pos = EmployeePositions.Unassigned;
}
//Returns the amount the employee needs to be paid
process(marketCycles = 1, office: OfficeSpace): number {
const gain = 0.003 * marketCycles,
det = gain * Math.random();
this.exp += gain;
// Employee salaries slowly go up over time
this.cyclesUntilRaise -= marketCycles;
if (this.cyclesUntilRaise <= 0) {
this.sal += CorporationConstants.EmployeeRaiseAmount;
this.cyclesUntilRaise += CorporationConstants.CyclesPerEmployeeRaise;
}
//Training
const trainingEff = gain * Math.random();
if (this.pos === EmployeePositions.Training) {
//To increase creativity and intelligence special upgrades are needed
this.cha += trainingEff;
this.exp += trainingEff;
this.eff += trainingEff;
}
this.ene -= det;
this.hap -= det;
if (this.ene < office.minEne) {this.ene = office.minEne;}
if (this.hap < office.minHap) {this.hap = office.minHap;}
const salary = this.sal * marketCycles * CorporationConstants.SecsPerMarketCycle;
return salary;
}
calculateProductivity(corporation: ICorporation, industry: IIndustry): number {
const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),
effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),
effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(),
effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier();
const prodBase = this.mor * this.hap * this.ene * 1e-6;
let prodMult = 0;
switch(this.pos) {
//Calculate productivity based on position. This is multipled by prodBase
//to get final value
case EmployeePositions.Operations:
prodMult = (0.6 * effInt) + (0.1 * effCha) + (this.exp) +
(0.5 * effCre) + (effEff);
break;
case EmployeePositions.Engineer:
prodMult = (effInt) + (0.1 * effCha) + (1.5 * this.exp) +
(effEff);
break;
case EmployeePositions.Business:
prodMult = (0.4 * effInt) + (effCha) + (0.5 * this.exp);
break;
case EmployeePositions.Management:
prodMult = (2 * effCha) + (this.exp) + (0.2 * effCre) +
(0.7 * effEff);
break;
case EmployeePositions.RandD:
prodMult = (1.5 * effInt) + (0.8 * this.exp) + (effCre) +
(0.5 * effEff);
break;
case EmployeePositions.Unassigned:
case EmployeePositions.Training:
prodMult = 0;
break;
default:
console.error(`Invalid employee position: ${this.pos}`);
break;
}
return prodBase * prodMult;
}
//Process benefits from having an office party thrown
throwParty(money: number): number {
const mult = 1 + (money / 10e6);
this.mor *= mult;
this.mor = Math.min(100, this.mor);
this.hap *= mult;
this.hap = Math.min(100, this.hap);
return mult;
}
//'panel' is the DOM element on which to create the UI
createUI(panel: HTMLElement, corporation: ICorporation, industry: IIndustry): void {
const effCre = this.cre * corporation.getEmployeeCreMultiplier() * industry.getEmployeeCreMultiplier(),
effCha = this.cha * corporation.getEmployeeChaMultiplier() * industry.getEmployeeChaMultiplier(),
effInt = this.int * corporation.getEmployeeIntMultiplier() * industry.getEmployeeIntMultiplier(),
effEff = this.eff * corporation.getEmployeeEffMultiplier() * industry.getEmployeeEffMultiplier();
panel.style.color = "white";
panel.appendChild(createElement("p", {
id:"cmpy-mgmt-employee-" + this.name + "-panel-text",
innerHTML:"Morale: " + formatNumber(this.mor, 3) + "<br>" +
"Happiness: " + formatNumber(this.hap, 3) + "<br>" +
"Energy: " + formatNumber(this.ene, 3) + "<br>" +
"Intelligence: " + formatNumber(effInt, 3) + "<br>" +
"Charisma: " + formatNumber(effCha, 3) + "<br>" +
"Experience: " + formatNumber(this.exp, 3) + "<br>" +
"Creativity: " + formatNumber(effCre, 3) + "<br>" +
"Efficiency: " + formatNumber(effEff, 3) + "<br>" +
"Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s<br>",
}));
//Selector for employee position
const selector = createElement("select", {}) as HTMLSelectElement;
for (const key in EmployeePositions) {
if (EmployeePositions.hasOwnProperty(key)) {
selector.add(createElement("option", {
text: EmployeePositions[key],
value: EmployeePositions[key],
}) as HTMLOptionElement);
}
}
selector.addEventListener("change", () => {
this.pos = selector.options[selector.selectedIndex].value;
});
//Set initial value of selector
for (let i = 0; i < selector.length; ++i) {
if (selector.options[i].value === this.pos) {
selector.selectedIndex = i;
break;
}
}
panel.appendChild(selector);
}
toJSON(): any {
return Generic_toJSON("Employee", this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromJSON(value: any): Employee {
return Generic_fromJSON(Employee, value.data);
}
}
Reviver.constructors.Employee = Employee;

@ -0,0 +1,5 @@
export interface Export {
ind: string;
city: string;
amt: string;
}

@ -0,0 +1,61 @@
import { Industry } from "./Industry";
import { IPlayer } from "../PersonObjects/IPlayer";
import { CorporationUnlockUpgrade } from "./data/CorporationUnlockUpgrades";
import { CorporationUpgrade } from "./data/CorporationUpgrades";
import { CorporationState } from "./CorporationState";
export interface ICorporation {
name: string;
divisions: Industry[];
funds: any;
revenue: any;
expenses: any;
fundingRound: number;
public: boolean;
totalShares: number;
numShares: number;
shareSalesUntilPriceUpdate: number;
shareSaleCooldown: number;
issueNewSharesCooldown: number;
dividendPercentage: number;
dividendTaxPercentage: number;
issuedShares: number;
sharePrice: number;
storedCycles: number;
unlockUpgrades: number[];
upgrades: number[];
upgradeMultipliers: number[];
state: CorporationState;
addFunds(amt: number): void;
getState(): string;
storeCycles(numCycles: number): void;
process(player: IPlayer): void;
determineValuation(): number;
getTargetSharePrice(): number;
updateSharePrice(): void;
immediatelyUpdateSharePrice(): void;
calculateShareSale(numShares: number): [number, number, number];
convertCooldownToString(cd: number): string;
unlock(upgrade: CorporationUnlockUpgrade): void;
upgrade(upgrade: CorporationUpgrade): void;
getProductionMultiplier(): number;
getStorageMultiplier(): number;
getDreamSenseGain(): number;
getAdvertisingMultiplier(): number;
getEmployeeCreMultiplier(): number;
getEmployeeChaMultiplier(): number;
getEmployeeIntMultiplier(): number;
getEmployeeEffMultiplier(): number;
getSalesMultiplier(): number;
getScientificResearchMultiplier(): number;
getStarterGuide(player: IPlayer): void;
createUI(player: IPlayer): void;
rerender(player: IPlayer): void;
clearUI(): void;
toJSON(): any;
}

@ -0,0 +1,77 @@
import { Material } from "./Material";
import { Warehouse } from "./Warehouse";
import { ICorporation } from "./ICorporation";
import { OfficeSpace } from "./OfficeSpace";
import { Product } from "./Product";
import { IndustryUpgrade } from "./IndustryUpgrades";
export interface IIndustry {
name: string;
type: string;
sciResearch: Material;
researched: {[key: string]: boolean | undefined};
reqMats: {[key: string]: number | undefined};
prodMats: string[];
products: {[key: string]: Product | undefined};
makesProducts: boolean;
awareness: number;
popularity: number;
startingCost: number;
reFac: number;
sciFac: number;
hwFac: number;
robFac: number;
aiFac: number;
advFac: number;
prodMult: number;
// Decimal
lastCycleRevenue: any;
lastCycleExpenses: any;
thisCycleRevenue: any;
thisCycleExpenses: any;
upgrades: number[];
state: string;
newInd: boolean;
warehouses: {[key: string]: Warehouse | 0};
offices: {[key: string]: OfficeSpace | 0};
init(): void;
getProductDescriptionText(): string;
getMaximumNumberProducts(): number;
hasMaximumNumberProducts(): boolean;
calculateProductionFactors(): void;
updateWarehouseSizeUsed(warehouse: Warehouse): void;
process(marketCycles: number, state: string, corporation: ICorporation): void;
processMaterialMarket(): void;
processProductMarket(marketCycles: number): void;
processMaterials(marketCycles: number, corporation: ICorporation): [number, number];
processProducts(marketCycles: number, corporation: ICorporation): [number, number];
processProduct(marketCycles: number, product: Product, corporation: ICorporation): number;
discontinueProduct(product: Product): void;
upgrade(upgrade: IndustryUpgrade, refs: {corporation: ICorporation; office: OfficeSpace}): void;
getOfficeProductivity(office: OfficeSpace, params?: {forProduct?: boolean}): number;
getBusinessFactor(office: OfficeSpace): number;
getAdvertisingFactors(): [number, number, number, number];
getMarketFactor(mat: {dmd: number; cmp: number}): number;
hasResearch(name: string): boolean;
updateResearchTree(): void;
getAdvertisingMultiplier(): number;
getEmployeeChaMultiplier(): number;
getEmployeeCreMultiplier(): number;
getEmployeeEffMultiplier(): number;
getEmployeeIntMultiplier(): number;
getProductionMultiplier(): number;
getProductProductionMultiplier(): number;
getSalesMultiplier(): number;
getScientificResearchMultiplier(): number;
getStorageMultiplier(): number;
toJSON(): any;
}

1353
src/Corporation/Industry.ts Normal file

File diff suppressed because it is too large Load Diff

@ -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,141 @@
import React from 'react';
import { ResearchTree } from "./ResearchTree";
import { getBaseResearchTreeCopy,
getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree";
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 money={IndustryStartingCosts.Energy} /><br />
Recommended starting Industry: NO</>),
Utilities: (<>Distribute water and provide wastewater services.<br /><br />
Starting cost: <Money money={IndustryStartingCosts.Utilities} /><br />
Recommended starting Industry: NO</>),
Agriculture: (<>Cultivate crops and breed livestock to produce food.<br /><br />
Starting cost: <Money 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 money={IndustryStartingCosts.Fishing} /><br />
Recommended starting Industry: NO</>),
Mining: (<>Extract and process metals from the earth.<br /><br />
Starting cost: <Money money={IndustryStartingCosts.Mining} /><br />
Recommended starting Industry: NO</>),
Food: (<>Create your own restaurants all around the world.<br /><br />
Starting cost: <Money money={IndustryStartingCosts.Food} /><br />
Recommended starting Industry: YES</>),
Tobacco: (<>Create and distribute tobacco and tobacco-related products.<br /><br />
Starting cost: <Money money={IndustryStartingCosts.Tobacco} /><br />
Recommended starting Industry: YES</>),
Chemical: (<>Produce industrial chemicals.<br /><br />
Starting cost: <Money money={IndustryStartingCosts.Chemical} /><br />
Recommended starting Industry: NO</>),
Pharmaceutical: (<>Discover, develop, and create new pharmaceutical drugs.<br /><br />
Starting cost: <Money money={IndustryStartingCosts.Pharmaceutical} /><br />
Recommended starting Industry: NO</>),
Computer: (<>Develop and manufacture new computer hardware and networking infrastructures.<br /><br />
Starting cost: <Money money={IndustryStartingCosts.Computer} /><br />
Recommended starting Industry: NO</>),
Robotics: (<>Develop and create robots.<br /><br />
Starting cost: <Money money={IndustryStartingCosts.Robotics} /><br />
Recommended starting Industry: NO</>),
Software: (<>Develop computer software and create AI Cores.<br /><br />
Starting cost: <Money money={IndustryStartingCosts.Software} /><br />
Recommended starting Industry: YES</>),
Healthcare: (<>Create and manage hospitals.<br /><br />
Starting cost: <Money money={IndustryStartingCosts.Healthcare} /><br />
Recommended starting Industry: NO</>),
RealEstate: (<>Develop and manage real estate properties.<br /><br />
Starting cost: <Money 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();
}

@ -1,9 +1,11 @@
import { IMap } from "../types";
export type IndustryUpgrade = [number, number, number, number, string, string];
// Industry upgrades
// The data structure is an array with the following format:
// [index in array, base price, price mult, benefit mult (if applicable), name, desc]
export const IndustryUpgrades: IMap<any[]> = {
export const IndustryUpgrades: IMap<IndustryUpgrade> = {
"0": [0, 500e3, 1, 1.05,
"Coffee", "Provide your employees with coffee, increasing their energy by 5%."],
"1": [1, 1e9, 1.06, 1.03,

@ -1,6 +1,7 @@
import { Generic_fromJSON,
Generic_toJSON,
Reviver } from "../../utils/JSONReviver";
import { Export } from "./Export";
interface IConstructorParams {
name?: string;
@ -43,7 +44,7 @@ export class Material {
imp = 0;
// Exports of this material to another warehouse/industry
exp: any[] = [];
exp: Export[] = [];
// Total amount of this material exported in the last cycle
totalExp = 0;
@ -52,12 +53,12 @@ export class Material {
bCost = 0;
// Cost / sec to sell this material
sCost = 0;
sCost: string | number = 0;
// Flags to keep track of whether production and/or sale of this material is limited
// [Whether production/sale is limited, limit amount]
prdman: any[] = [false, 0]; // Production
sllman: any[] = [false, 0]; // Sale
prdman: [boolean, number] = [false, 0]; // Production
sllman: [boolean, string | number] = [false, 0]; // Sale
// Flags that signal whether automatic sale pricing through Market TA is enabled
marketTa1 = false;

@ -0,0 +1,198 @@
import { EmployeePositions } from "./EmployeePositions";
import { CorporationConstants } from "./data/Constants";
import { getRandomInt } from "../../utils/helpers/getRandomInt";
import { generateRandomString } from "../../utils/StringHelperFunctions";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../utils/JSONReviver";
import { Employee } from "./Employee";
import { IIndustry } from "./IIndustry";
import { ICorporation } from './ICorporation';
interface IParams {
loc?: string;
cost?: number;
size?: number;
comfort?: number;
beauty?: number;
}
export class OfficeSpace {
loc: string;
cost: number;
size: number;
comf: number;
beau: number;
tier = "Basic";
minEne = 0;
maxEne = 100;
minHap = 0;
maxHap = 100;
maxMor = 100;
employees: Employee[] = [];
employeeProd: {[key: string]: number} = {
[EmployeePositions.Operations]: 0,
[EmployeePositions.Engineer]: 0,
[EmployeePositions.Business]: 0,
[EmployeePositions.Management]: 0,
[EmployeePositions.RandD]: 0,
total: 0,
};
constructor(params: IParams = {}) {
this.loc = params.loc ? params.loc : "";
this.cost = params.cost ? params.cost : 1;
this.size = params.size ? params.size : 1;
this.comf = params.comfort ? params.comfort : 1;
this.beau = params.beauty ? params.beauty : 1;
}
atCapacity(): boolean {
return (this.employees.length) >= this.size;
}
process(marketCycles = 1, corporation: ICorporation, industry: IIndustry): number {
// HRBuddy AutoRecruitment and training
if (industry.hasResearch("HRBuddy-Recruitment") && !this.atCapacity()) {
const emp = this.hireRandomEmployee();
if (industry.hasResearch("HRBuddy-Training") && emp !== undefined) {
emp.pos = EmployeePositions.Training;
}
}
// Process Office properties
this.maxEne = 100;
this.maxHap = 100;
this.maxMor = 100;
if (industry.hasResearch("Go-Juice")) {
this.maxEne += 10;
}
if (industry.hasResearch("JoyWire")) {
this.maxHap += 10;
}
if (industry.hasResearch("Sti.mu")) {
this.maxMor += 10;
}
// Calculate changes in Morale/Happiness/Energy for Employees
let perfMult=1; //Multiplier for employee morale/happiness/energy based on company performance
if (corporation.funds < 0 && industry.lastCycleRevenue < 0) {
perfMult = Math.pow(0.99, marketCycles);
} else if (corporation.funds > 0 && industry.lastCycleRevenue > 0) {
perfMult = Math.pow(1.01, marketCycles);
}
const hasAutobrew = industry.hasResearch("AutoBrew");
const hasAutoparty = industry.hasResearch("AutoPartyManager");
let salaryPaid = 0;
for (let i = 0; i < this.employees.length; ++i) {
const emp = this.employees[i];
if (hasAutoparty) {
emp.mor = this.maxMor;
emp.hap = this.maxHap;
} else {
emp.mor *= perfMult;
emp.hap *= perfMult;
emp.mor = Math.min(emp.mor, this.maxMor);
emp.hap = Math.min(emp.hap, this.maxHap);
}
if (hasAutobrew) {
emp.ene = this.maxEne;
} else {
emp.ene *= perfMult;
emp.ene = Math.min(emp.ene, this.maxEne);
}
const salary = emp.process(marketCycles, this);
salaryPaid += salary;
}
this.calculateEmployeeProductivity(corporation, industry);
return salaryPaid;
}
calculateEmployeeProductivity(corporation: ICorporation, industry: IIndustry): void {
//Reset
for (const name in this.employeeProd) {
this.employeeProd[name] = 0;
}
let total = 0;
for (let i = 0; i < this.employees.length; ++i) {
const employee = this.employees[i];
const prod = employee.calculateProductivity(corporation, industry);
this.employeeProd[employee.pos] += prod;
total += prod;
}
this.employeeProd.total = total;
}
hireRandomEmployee(): Employee | undefined {
if (this.atCapacity()) return;
if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) return;
//Generate three random employees (meh, decent, amazing)
const mult = getRandomInt(76, 100)/100;
const int = getRandomInt(50, 100),
cha = getRandomInt(50, 100),
exp = getRandomInt(50, 100),
cre = getRandomInt(50, 100),
eff = getRandomInt(50, 100),
sal = CorporationConstants.EmployeeSalaryMultiplier * (int + cha + exp + cre + eff);
const emp = new Employee({
intelligence: int * mult,
charisma: cha * mult,
experience: exp * mult,
creativity: cre * mult,
efficiency: eff * mult,
salary: sal * mult,
});
const name = generateRandomString(7);
for (let i = 0; i < this.employees.length; ++i) {
if (this.employees[i].name === name) {
return this.hireRandomEmployee();
}
}
emp.name = name;
this.employees.push(emp);
return emp;
}
//Finds the first unassigned employee and assigns its to the specified job
assignEmployeeToJob(job: string): boolean {
for (let i = 0; i < this.employees.length; ++i) {
if (this.employees[i].pos === EmployeePositions.Unassigned) {
this.employees[i].pos = job;
return true;
}
}
return false;
}
//Finds the first employee with the given job and unassigns it
unassignEmployeeFromJob(job: string): boolean {
for (let i = 0; i < this.employees.length; ++i) {
if (this.employees[i].pos === job) {
this.employees[i].pos = EmployeePositions.Unassigned;
return true;
}
}
return false;
}
toJSON(): any {
return Generic_toJSON("OfficeSpace", this);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromJSON(value: any): OfficeSpace {
return Generic_fromJSON(OfficeSpace, value.data);
}
}
Reviver.constructors.OfficeSpace = OfficeSpace;

@ -1,5 +1,6 @@
import { EmployeePositions } from "./EmployeePositions";
import { MaterialSizes } from "./MaterialSizes";
import { IIndustry } from "./IIndustry";
import { ProductRatingWeights,
IProductRatingWeight } from "./ProductRatingWeights";
@ -30,17 +31,6 @@ interface IConstructorParams {
req?: IMap<number>;
}
// Interface for an Industry object - Used for type checking method arguments
interface IIndustry {
awareness: number;
popularity: number;
reqMats: IMap<number>;
sciFac: number;
sciResearch: any;
type: string;
}
export class Product {
// Product name
@ -60,7 +50,7 @@ export class Product {
pCost = 0;
// Sell cost
sCost = 0;
sCost: string | number = 0;
// Variables for handling the creation process of this Product
fin = false; // Whether this Product has finished being created
@ -137,7 +127,7 @@ export class Product {
}
// @param industry - Industry object. Reference to industry that makes this Product
finishProduct(employeeProd: IMap<number>, industry: IIndustry): void {
finishProduct(employeeProd: {[key: string]: number}, industry: IIndustry): void {
this.fin = true;
//Calculate properties
@ -199,7 +189,9 @@ export class Product {
//For now, just set it to be the same as the requirements to make materials
for (const matName in industry.reqMats) {
if (industry.reqMats.hasOwnProperty(matName)) {
this.reqMats[matName] = industry.reqMats[matName];
const reqMat = industry.reqMats[matName];
if(reqMat === undefined) continue;
this.reqMats[matName] = reqMat;
}
}
@ -207,18 +199,10 @@ export class Product {
//For now, just set it to be the same size as the requirements to make materials
this.siz = 0;
for (const matName in industry.reqMats) {
this.siz += MaterialSizes[matName] * industry.reqMats[matName];
const reqMat = industry.reqMats[matName];
if(reqMat === undefined) continue;
this.siz += MaterialSizes[matName] * reqMat;
}
//Delete unneeded variables
// @ts-ignore
delete this.prog;
// @ts-ignore
delete this.createCity;
// @ts-ignore
delete this.designCost;
// @ts-ignore
delete this.advCost;
}

@ -1,4 +1,6 @@
import { Material } from "./Material";
import { ICorporation } from "./ICorporation";
import { IIndustry } from "./IIndustry";
import { MaterialSizes } from "./MaterialSizes";
import { IMap } from "../types";
import { numeralWrapper } from "../ui/numeralFormat";
@ -7,13 +9,9 @@ import { Generic_fromJSON,
Reviver } from "../../utils/JSONReviver";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
interface IParent {
getStorageMultiplier(): number;
}
interface IConstructorParams {
corp?: IParent;
industry?: IParent;
corp?: ICorporation;
industry?: IIndustry;
loc?: string;
size?: number;
}
@ -92,7 +90,7 @@ export class Warehouse {
}
}
updateSize(corporation: IParent, industry: IParent): void {
updateSize(corporation: ICorporation, industry: IIndustry): void {
try {
this.size = (this.level * 100)
* corporation.getStorageMultiplier()

@ -0,0 +1,60 @@
const CyclesPerMarketCycle = 50;
const AllCorporationStates = ["START", "PURCHASE", "PRODUCTION", "SALE", "EXPORT"];
export const CorporationConstants: {
INITIALSHARES: number;
SHARESPERPRICEUPDATE: number;
IssueNewSharesCooldown: number;
SellSharesCooldown: number;
CyclesPerMarketCycle: number;
CyclesPerIndustryStateCycle: number;
SecsPerMarketCycle: number;
Cities: string[];
WarehouseInitialCost: number;
WarehouseInitialSize: number;
WarehouseUpgradeBaseCost: number;
OfficeInitialCost: number;
OfficeInitialSize: number;
OfficeUpgradeBaseCost: number;
BribeThreshold: number;
BribeToRepRatio: number;
ProductProductionCostRatio: number;
DividendMaxPercentage: number;
EmployeeSalaryMultiplier: number;
CyclesPerEmployeeRaise: number;
EmployeeRaiseAmount: number;
BaseMaxProducts: number;
AllCorporationStates: string[];
} = {
INITIALSHARES: 1e9, //Total number of shares you have at your company
SHARESPERPRICEUPDATE: 1e6, //When selling large number of shares, price is dynamically updated for every batch of this amount
IssueNewSharesCooldown: 216e3, // 12 Hour in terms of game cycles
SellSharesCooldown: 18e3, // 1 Hour in terms of game cycles
CyclesPerMarketCycle: CyclesPerMarketCycle,
CyclesPerIndustryStateCycle: CyclesPerMarketCycle / AllCorporationStates.length,
SecsPerMarketCycle: CyclesPerMarketCycle / 5,
Cities: ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"],
WarehouseInitialCost: 5e9, //Initial purchase cost of warehouse
WarehouseInitialSize: 100,
WarehouseUpgradeBaseCost: 1e9,
OfficeInitialCost: 4e9,
OfficeInitialSize: 3,
OfficeUpgradeBaseCost: 1e9,
BribeThreshold: 100e12, //Money needed to be able to bribe for faction rep
BribeToRepRatio: 1e9, //Bribe Value divided by this = rep gain
ProductProductionCostRatio: 5, //Ratio of material cost of a product to its production cost
DividendMaxPercentage: .5,
EmployeeSalaryMultiplier: 3, // Employee stats multiplied by this to determine initial salary
CyclesPerEmployeeRaise: 400, // All employees get a raise every X market cycles
EmployeeRaiseAmount: 50, // Employee salary increases by this (additive)
BaseMaxProducts: 3, // Initial value for maximum number of products allowed
AllCorporationStates: AllCorporationStates,
};

@ -1,10 +1,12 @@
import { IMap } from "../../types";
export type CorporationUnlockUpgrade = [number, number, string, string];
// Corporation Unlock Upgrades
// Upgrades for entire corporation, unlocks features, either you have it or you dont
// The data structure is an array with the following format:
// [index in Corporation feature upgrades array, price, name, description]
export const CorporationUnlockUpgrades: IMap<any[]> = {
export const CorporationUnlockUpgrades: IMap<CorporationUnlockUpgrade> = {
//Lets you export goods
"0": [0, 20e9, "Export",
"Develop infrastructure to export your materials to your other facilities. " +

@ -1,10 +1,12 @@
import { IMap } from "../../types";
export type CorporationUpgrade = [number, number, number, number, string, string];
// Corporation Upgrades
// Upgrades for entire corporation, levelable upgrades
// The data structure is an array with the following format
// [index in Corporation upgrades array, base price, price mult, benefit mult (additive), name, desc]
export const CorporationUpgrades: IMap<any[]> = {
export const CorporationUpgrades: IMap<CorporationUpgrade> = {
//Smart factories, increases production
"0": [0, 2e9, 1.06, 0.03,
"Smart Factories", "Advanced AI automatically optimizes the operation and productivity " +

@ -1,20 +0,0 @@
// Base class for React Components for Corporation UI
// Contains a few helper functions that let derived classes easily
// access Corporation properties
import React from "react";
const Component = React.Component;
export class BaseReactComponent extends Component {
corp() {
return this.props.corp;
}
eventHandler() {
return this.props.eventHandler;
}
routing() {
return this.props.routing;
}
}

@ -0,0 +1,88 @@
import React, { useState } from 'react';
import { IPlayer } from "../../PersonObjects/IPlayer";
import { Factions } from "../../Faction/Factions";
import { CorporationConstants } from "../data/Constants";
import { numeralWrapper } from "../../ui/numeralFormat";
import { removePopup } from "../../ui/React/createPopup";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { ICorporation } from "../ICorporation";
interface IProps {
popupId: string;
corp: ICorporation;
player: IPlayer;
}
export function BribeFactionPopup(props: IProps): React.ReactElement {
const [money, setMoney] = useState<number | null>(0);
const [stock, setStock] = useState<number | null>(0);
const [selectedFaction, setSelectedFaction] = useState(props.player.factions.length > 0 ? props.player.factions[0] : "");
function onMoneyChange(event: React.ChangeEvent<HTMLInputElement>): void {
setMoney(parseFloat(event.target.value));
}
function onStockChange(event: React.ChangeEvent<HTMLInputElement>): void {
setStock(parseFloat(event.target.value));
}
function changeFaction(event: React.ChangeEvent<HTMLSelectElement>): void {
setSelectedFaction(event.target.value);
}
function repGain(money: number, stock: number): number {
return (money + (stock * props.corp.sharePrice)) / CorporationConstants.BribeToRepRatio;
}
function getRepText(money: number, stock: number): string {
if(money === 0 && stock === 0) return "";
if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) {
return "ERROR: Invalid value(s) entered";
} else if (props.corp.funds.lt(money)) {
return "ERROR: You do not have this much money to bribe with";
} else if (stock > props.corp.numShares) {
return "ERROR: You do not have this many shares to bribe with";
} else {
return "You will gain " + numeralWrapper.formatReputation(repGain(money, stock)) +
" reputation with " +
selectedFaction +
" with this bribe";
}
}
function bribe(money: number, stock: number): void {
const fac = Factions[selectedFaction];
if (fac == null) {
dialogBoxCreate("ERROR: You must select a faction to bribe");
}
if (isNaN(money) || isNaN(stock) || money < 0 || stock < 0) {
} else if (props.corp.funds.lt(money)) {
} else if (stock > props.corp.numShares) {
} else {
const rep = repGain(money, stock);
dialogBoxCreate("You gained " + numeralWrapper.formatReputation(rep) +
" reputation with " + fac.name + " by bribing them.");
fac.playerReputation += rep;
props.corp.funds = props.corp.funds.minus(money);
props.corp.numShares -= stock;
removePopup(props.popupId);
}
}
return (<>
<p>You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation.</p>
<select className="dropdown" style={{margin: "3px"}} defaultValue={selectedFaction} onChange={changeFaction}>
{
props.player.factions.map((name: string) => {
const info = Factions[name].getInfo();
if(!info.offersWork()) return;
return <option key={name} value={name}>{name}</option>
})
}
</select>
<p>{getRepText(money ? money : 0, stock ? stock : 0)}</p>
<input className="text-input" onChange={onMoneyChange} placeholder="Corporation funds" style={{margin: "5px"}} />
<input className="text-input" onChange={onStockChange} placeholder="Stock Shares" style={{margin: "5px"}} />
<button className="a-link-button" onClick={() => bribe(money ? money : 0, stock ? stock : 0)} style={{display:"inline-block"}}>Bribe</button>
</>);
}

@ -0,0 +1,87 @@
import React, { useState } from 'react';
import { IPlayer } from "../../PersonObjects/IPlayer";
import { removePopup } from "../../ui/React/createPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { ICorporation } from "../ICorporation";
interface IProps {
player: IPlayer;
popupId: string;
corp: ICorporation;
}
// Create a popup that lets the player buyback shares
// This is created when the player clicks the "Buyback Shares" button in the overview panel
export function BuybackSharesPopup(props: IProps): React.ReactElement {
const [shares, setShares] = useState<number | null>(null);
function changeShares(event: React.ChangeEvent<HTMLInputElement>): void {
if(event.target.value === "") setShares(null);
else setShares(Math.round(parseFloat(event.target.value)));
}
const currentStockPrice = props.corp.sharePrice;
const buybackPrice = currentStockPrice * 1.1;
function buy(): void {
if(shares === null) return;
const tempStockPrice = props.corp.sharePrice;
const buybackPrice = tempStockPrice * 1.1;
if (isNaN(shares) || shares <= 0) {
dialogBoxCreate("ERROR: Invalid value for number of shares");
} else if (shares > props.corp.issuedShares) {
dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back");
} else if (shares * buybackPrice > props.player.money) {
dialogBoxCreate("ERROR: You do not have enough money to purchase this many shares (you need " +
numeralWrapper.format(shares * buybackPrice, "$0.000a") + ")");
} else {
props.corp.numShares += shares;
if (isNaN(props.corp.issuedShares)) {
console.warn("Corporation issuedShares is NaN: " + props.corp.issuedShares);
console.warn("Converting to number now");
const res = props.corp.issuedShares;
if (isNaN(res)) {
props.corp.issuedShares = 0;
} else {
props.corp.issuedShares = res;
}
}
props.corp.issuedShares -= shares;
props.player.loseMoney(shares * buybackPrice);
removePopup(props.popupId);
props.corp.rerender(props.player);
}
}
function CostIndicator(): React.ReactElement {
if(shares === null) return (<></>);
if (isNaN(shares) || shares <= 0) {
return (<>ERROR: Invalid value entered for number of shares to buyback</>);
} else if (shares > props.corp.issuedShares) {
return (<>There are not this many shares available to buy back.
There are only {numeralWrapper.formatBigNumber(props.corp.issuedShares)} outstanding shares.</>);
} else {
return (<>Purchase {shares} shares for a total of {numeralWrapper.formatMoney(shares * buybackPrice)}</>);
}
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) buy();
}
return (<>
<p>
Enter the number of outstanding shares you would like to buy back.
These shares must be bought at a 10% premium. However,
repurchasing shares from the market tends to lead to an increase in stock price.<br /><br />
To purchase these shares, you must use your own money (NOT your Corporation's funds).<br /><br />
The current buyback price of your company's stock is {numeralWrapper.formatMoney(buybackPrice)}.
Your company currently has {numeralWrapper.formatBigNumber(props.corp.issuedShares)} outstanding stock shares.
</p>
<CostIndicator />
<br />
<input autoFocus={true} className="text-input" type="number" placeholder="Shares to buyback" style={{margin: "5px"}} onChange={changeShares} onKeyDown={onKeyDown} />
<button onClick={buy} className="a-link-button" style={{display:"inline-block"}}>Buy shares</button>
</>);
}

@ -0,0 +1,20 @@
import React from "react";
interface IProps {
onClick: () => void;
name: string;
current: boolean;
}
export function CityTab(props: IProps): React.ReactElement {
let className = "cmpy-mgmt-city-tab";
if (props.current) {
className += " current";
}
return (
<button className={className} onClick={props.onClick}>
{props.name}
</button>
)
}

@ -1,62 +0,0 @@
// React Components for the Corporation UI's City navigation tabs
// These allow player to navigate between different cities for each industry
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
export class CityTabs extends BaseReactComponent {
constructor(props) {
// An object with [key = city name] and [value = click handler]
// needs to be passed into the constructor as the "onClicks" property.
// We'll make sure that that happens here
if (props.onClicks == null) {
throw new Error(`CityTabs component constructed without onClick handlers`);
}
if (props.city == null) {
throw new Error(`CityTabs component constructed without 'city' property`)
}
if (props.cityStateSetter == null) {
throw new Error(`CityTabs component constructed without 'cityStateSetter' property`)
}
super(props);
}
renderTab(props) {
let className = "cmpy-mgmt-city-tab";
if (props.current) {
className += " current";
}
return (
<button className={className} onClick={props.onClick} key={props.key}>
{props.key}
</button>
)
}
render() {
const division = this.routing().currentDivision;
const tabs = [];
// Tabs for each city
for (const cityName in this.props.onClicks) {
tabs.push(this.renderTab({
current: this.props.city === cityName,
key: cityName,
onClick: this.props.onClicks[cityName],
}));
}
// Tab to "Expand into new City"
const newCityOnClick = this.eventHandler().createNewCityPopup.bind(this.eventHandler(), division, this.props.cityStateSetter);
tabs.push(this.renderTab({
current: false,
key: "Expand into new City",
onClick: newCityOnClick,
}));
return tabs;
}
}

@ -0,0 +1,45 @@
// React Components for the Corporation UI's City navigation tabs
// These allow player to navigate between different cities for each industry
import React from "react";
import { CityTab } from "./CityTab";
import { ExpandNewCityPopup } from "./ExpandNewCityPopup";
import { createPopup } from "../../ui/React/createPopup";
import { ICorporation } from "../ICorporation";
import { CorporationRouting } from "./Routing";
interface IProps {
routing: CorporationRouting;
onClicks: {[key: string]: () => void};
city: string; // currentCity
cityStateSetter: (city: string) => void;
corp: ICorporation;
}
export function CityTabs(props: IProps): React.ReactElement {
const division = props.routing.currentDivision;
function openExpandNewCityModal(): void {
if(division === null) return;
const popupId = "cmpy-mgmt-expand-city-popup";
createPopup(popupId, ExpandNewCityPopup, {
popupId: popupId,
corp: props.corp,
division: division,
cityStateSetter: props.cityStateSetter,
});
}
return <>
{
Object.keys(props.onClicks).map((cityName: string) =>
<CityTab current={props.city === cityName} key={cityName} name={cityName} onClick={props.onClicks[cityName]} />,
)
}
<CityTab
current={false}
key={"Expand into new City"}
name={"Expand into new City"}
onClick={openExpandNewCityModal}
/>
</>;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,32 @@
import React from 'react';
import { removePopup } from "../../ui/React/createPopup";
import { ICorporation } from "../ICorporation";
import { Product } from "../Product";
import { IIndustry } from "../IIndustry";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
product: Product;
industry: IIndustry;
corp: ICorporation;
popupId: string;
player: IPlayer;
}
// Create a popup that lets the player discontinue a product
export function DiscontinueProductPopup(props: IProps): React.ReactElement {
function discontinue(): void {
props.industry.discontinueProduct(props.product);
removePopup(props.popupId);
props.corp.rerender(props.player);
}
return (<>
<p>
Are you sure you want to do this? Discontinuing a product
removes it completely and permanently. You will no longer
produce this product and all of its existing stock will be
removed and left unsold</p>
<button className="popup-box-button" onClick={discontinue}>Discontinue</button>
</>);
}

@ -0,0 +1,50 @@
import React, { useRef } from "react";
import { IIndustry } from "../IIndustry";
import { numeralWrapper } from "../../ui/numeralFormat";
import { CorporationConstants } from "../data/Constants";
import { removePopup } from "../../ui/React/createPopup";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { OfficeSpace } from "../OfficeSpace";
import { ICorporation } from "../ICorporation";
import { NewCity } from "../Actions";
interface IProps {
popupId: string;
corp: ICorporation;
division: IIndustry;
cityStateSetter: (city: string) => void;
}
export function ExpandNewCityPopup(props: IProps): React.ReactElement {
const dropdown = useRef<HTMLSelectElement>(null);
function expand(): void {
if(dropdown.current === null) return;
try {
NewCity(props.corp, props.division, dropdown.current.value);
} catch(err) {
dialogBoxCreate(err+'');
return;
}
dialogBoxCreate(`Opened a new office in ${dropdown.current.value}!`);
props.cityStateSetter(dropdown.current.value);
removePopup(props.popupId);
}
return (<>
<p>
Would you like to expand into a new city by opening an office?
This would cost {numeralWrapper.format(CorporationConstants.OfficeInitialCost, '$0.000a')}
</p>
<select ref={dropdown} className="dropdown" style={{margin:"5px"}}>
{
Object.keys(props.division.offices)
.filter((cityName: string) => props.division.offices[cityName] === 0)
.map((cityName: string) => <option key={cityName} value={cityName}>{cityName}</option>,
)
}
</select>
<button className="std-button" style={{display:"inline-block"}} onClick={expand}>Confirm</button>
</>);
}

@ -0,0 +1,116 @@
import React, { useState } from 'react';
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { ICorporation } from "../ICorporation";
import { Material } from "../Material";
import { Export } from "../Export";
import { IIndustry } from "../IIndustry";
interface IProps {
mat: Material;
corp: ICorporation;
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: Export): void {
for (let i = 0; i < props.mat.exp.length; ++i) {
if(props.mat.exp[i].ind !== exp.ind ||
props.mat.exp[i].city !== exp.city ||
props.mat.exp[i].amt !== exp.amt) continue;
props.mat.exp.splice(i, 1);
break
}
rerender();
}
const currentDivision = props.corp.divisions.find((division: IIndustry) => 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: IIndustry) => <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: string) => {
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: Export, 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>)
}
</>);
}

@ -0,0 +1,59 @@
import React from 'react';
import { removePopup } from "../../ui/React/createPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { CorporationConstants } from "../data/Constants";
import { ICorporation } from "../ICorporation";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
corp: ICorporation;
popupId: string;
player: IPlayer;
}
// Create a popup that lets the player manage exports
export function FindInvestorsPopup(props: IProps): React.ReactElement {
const val = props.corp.determineValuation()
let percShares = 0;
let roundMultiplier = 4;
switch (props.corp.fundingRound) {
case 0: //Seed
percShares = 0.10;
roundMultiplier = 4;
break;
case 1: //Series A
percShares = 0.35;
roundMultiplier = 3;
break;
case 2: //Series B
percShares = 0.25;
roundMultiplier = 3;
break;
case 3: //Series C
percShares = 0.20;
roundMultiplier = 2.5;
break;
default:
return (<></>);
}
const funding = val * percShares * roundMultiplier;
const investShares = Math.floor(CorporationConstants.INITIALSHARES * percShares);
function findInvestors(): void {
props.corp.fundingRound++;
props.corp.addFunds(funding);
props.corp.numShares -= investShares;
props.corp.rerender(props.player);
removePopup(props.popupId);
}
return (<>
<p>
An investment firm has offered you {numeralWrapper.formatMoney(funding)} in
funding in exchange for a {numeralWrapper.format(percShares*100, "0.000a")}%
stake in the company ({numeralWrapper.format(investShares, '0.000a')} shares).<br /><br />
Do you accept or reject this offer?<br /><br />
Hint: Investment firms will offer more money if your corporation is turning a profit
</p>
<button onClick={findInvestors} className="std-button">Accept</button>
</>);
}

@ -0,0 +1,63 @@
import React, { useState } from 'react';
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { ICorporation } from "../ICorporation";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
corp: ICorporation;
popupId: string;
player: IPlayer;
}
// Create a popup that lets the player manage exports
export function GoPublicPopup(props: IProps): React.ReactElement {
const [shares, setShares] = useState('');
const initialSharePrice = props.corp.determineValuation() / (props.corp.totalShares);
function goPublic(): void {
const numShares = parseFloat(shares);
const initialSharePrice = props.corp.determineValuation() / (props.corp.totalShares);
if (isNaN(numShares)) {
dialogBoxCreate("Invalid value for number of issued shares");
return;
}
if (numShares > props.corp.numShares) {
dialogBoxCreate("Error: You don't have that many shares to issue!");
return;
}
props.corp.public = true;
props.corp.sharePrice = initialSharePrice;
props.corp.issuedShares = numShares;
props.corp.numShares -= numShares;
props.corp.addFunds(numShares * initialSharePrice);
props.corp.rerender(props.player);
dialogBoxCreate(`You took your ${props.corp.name} public and earned ` +
`${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`);
removePopup(props.popupId);
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) goPublic();
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
setShares(event.target.value);
}
return (<>
<p>
Enter the number of shares you would like to issue
for your IPO. These shares will be publicly sold
and you will no longer own them. Your Corporation will
receive {numeralWrapper.formatMoney(initialSharePrice)} per share
(the IPO money will be deposited directly into your Corporation's funds).
<br /><br />
You have a total of {numeralWrapper.format(props.corp.numShares, "0.000a")} of
shares that you can issue.
</p>
<input className="text-input" value={shares} onChange={onChange} autoFocus={true} type="number" placeholder="Shares to issue" onKeyDown={onKeyDown} />
<button className="std-button" onClick={goPublic}>Go Public</button>
</>);
}

@ -0,0 +1,20 @@
import React from 'react';
interface IProps {
current: boolean;
text: string;
onClick: () => void;
}
export function HeaderTab(props: IProps): React.ReactElement {
let className = "cmpy-mgmt-header-tab";
if (props.current) {
className += " current";
}
return (
<button className={className} onClick={props.onClick}>
{props.text}
</button>
)
}

@ -1,78 +0,0 @@
// React Components for the Corporation UI's navigation tabs
// These are the tabs at the top of the UI that let you switch to different
// divisions, see an overview of your corporation, or create a new industry
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
function HeaderTab(props) {
let className = "cmpy-mgmt-header-tab";
if (props.current) {
className += " current";
}
return (
<button className={className} onClick={props.onClick}>
{props.text}
</button>
)
}
export class HeaderTabs extends BaseReactComponent {
renderTab(props) {
return (
<HeaderTab
current={props.current}
key={props.key}
onClick={props.onClick}
text={props.text}
/>
)
}
render() {
const overviewOnClick = () => {
this.routing().routeToOverviewPage();
this.corp().rerender();
}
const divisionOnClicks = {};
for (const division of this.corp().divisions) {
const name = division.name;
const onClick = () => {
this.routing().routeTo(name);
this.corp().rerender();
}
divisionOnClicks[name] = onClick;
}
return (
<div>
{
this.renderTab({
current: this.routing().isOnOverviewPage(),
key: "overview",
onClick: overviewOnClick,
text: this.corp().name,
})
}
{
this.corp().divisions.map((division) => {
return this.renderTab({
current: this.routing().isOn(division.name),
key: division.name,
onClick: divisionOnClicks[division.name],
text: division.name,
});
})
}
{
this.renderTab({
onClick: this.eventHandler().createNewIndustryPopup.bind(this.eventHandler()),
text: "Expand into new Industry",
})
}
</div>
)
}
}

@ -0,0 +1,61 @@
// React Components for the Corporation UI's navigation tabs
// These are the tabs at the top of the UI that let you switch to different
// divisions, see an overview of your corporation, or create a new industry
import React from "react";
import { HeaderTab } from "./HeaderTab";
import { IIndustry } from "../IIndustry";
import { NewIndustryPopup } from "./NewIndustryPopup";
import { createPopup } from "../../ui/React/createPopup";
import { ICorporation } from "../ICorporation";
import { CorporationRouting } from "./Routing";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
corp: ICorporation;
routing: CorporationRouting;
player: IPlayer;
}
export function HeaderTabs(props: IProps): React.ReactElement {
function overviewOnClick(): void {
props.routing.routeToOverviewPage();
props.corp.rerender(props.player);
}
function openNewIndustryPopup(): void {
const popupId = "cmpy-mgmt-expand-industry-popup";
createPopup(popupId, NewIndustryPopup, {
corp: props.corp,
routing: props.routing,
popupId: popupId,
});
}
return (
<div>
<HeaderTab
current={props.routing.isOnOverviewPage()}
key={"overview"}
onClick={overviewOnClick}
text={props.corp.name}
/>
{
props.corp.divisions.map((division: IIndustry) => <HeaderTab
current={props.routing.isOn(division.name)}
key={division.name}
onClick={() => {
props.routing.routeTo(division.name);
props.corp.rerender(props.player);
}}
text={division.name}
/>)
}
<HeaderTab
current={false}
onClick={openNewIndustryPopup}
text={"Expand into new Industry"}
/>
</div>
)
}

@ -0,0 +1,137 @@
import React, { useState } from 'react';
import { createPopup, removePopup } from "../../ui/React/createPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { CorporationConstants } from "../data/Constants";
import { ICorporation } from "../ICorporation";
import { OfficeSpace } from "../OfficeSpace";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { getRandomInt } from "../../../utils/helpers/getRandomInt";
import { formatNumber } from "../../../utils/StringHelperFunctions";
import { Employee } from "../Employee";
import { dialogBoxCreate } from "../../../utils/DialogBox";
interface INameEmployeeProps {
office: OfficeSpace;
corp: ICorporation;
popupId: string;
employee: Employee;
player: IPlayer;
}
function NameEmployeePopup(props: INameEmployeeProps): React.ReactElement {
const [name, setName] = useState('');
function nameEmployee(): void {
for (let i = 0; i < props.office.employees.length; ++i) {
if (props.office.employees[i].name === name) {
dialogBoxCreate("You already have an employee with this nickname!");
return;
}
}
props.employee.name = name;
props.office.employees.push(props.employee);
props.corp.rerender(props.player);
removePopup(props.popupId);
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) nameEmployee();
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
setName(event.target.value);
}
return (<>
<p>Give your employee a nickname!</p>
<input value={name} className="text-input" type="text" placeholder="Employee nickname" onKeyDown={onKeyDown} onChange={onChange} />
<button className="std-button" onClick={nameEmployee}>Hire!</button>
</>);
}
interface IHireEmployeeProps {
employee: Employee;
office: OfficeSpace;
popupId: string;
player: IPlayer;
corp: ICorporation;
}
function HireEmployeeButton(props: IHireEmployeeProps): React.ReactElement {
function hire(): void {
const popupId = "cmpy-mgmt-name-employee-popup";
createPopup(popupId, NameEmployeePopup, {
office: props.office,
corp: props.corp,
popupId: popupId,
player: props.player,
employee: props.employee,
});
removePopup(props.popupId);
}
return (<div onClick={hire} className="cmpy-mgmt-find-employee-option">
Intelligence: {formatNumber(props.employee.int, 1)}<br />
Charisma: {formatNumber(props.employee.cha, 1)}<br />
Experience: {formatNumber(props.employee.exp, 1)}<br />
Creativity: {formatNumber(props.employee.cre, 1)}<br />
Efficiency: {formatNumber(props.employee.eff, 1)}<br />
Salary: {numeralWrapper.formatMoney(props.employee.sal)} \ s<br />
</div>);
}
interface IProps {
office: OfficeSpace;
corp: ICorporation;
popupId: string;
player: IPlayer;
}
// Create a popup that lets the player manage exports
export function HireEmployeePopup(props: IProps): React.ReactElement {
if (props.office.atCapacity()) return (<></>);
//Generate three random employees (meh, decent, amazing)
const mult1 = getRandomInt(25, 50)/100;
const mult2 = getRandomInt(51, 75)/100;
const mult3 = getRandomInt(76, 100)/100;
const int = getRandomInt(50, 100);
const cha = getRandomInt(50, 100);
const exp = getRandomInt(50, 100);
const cre = getRandomInt(50, 100);
const eff = getRandomInt(50, 100);
const sal = CorporationConstants.EmployeeSalaryMultiplier * (int + cha + exp + cre + eff);
const emp1 = new Employee({
intelligence: int * mult1,
charisma: cha * mult1,
experience: exp * mult1,
creativity: cre * mult1,
efficiency: eff * mult1,
salary: sal * mult1,
});
const emp2 = new Employee({
intelligence: int * mult2,
charisma: cha * mult2,
experience: exp * mult2,
creativity: cre * mult2,
efficiency: eff * mult2,
salary: sal * mult2,
});
const emp3 = new Employee({
intelligence: int * mult3,
charisma: cha * mult3,
experience: exp * mult3,
creativity: cre * mult3,
efficiency: eff * mult3,
salary: sal * mult3,
});
return (<>
<h1>Select one of the following candidates for hire:</h1>
<HireEmployeeButton employee={emp1} office={props.office} corp={props.corp} popupId={props.popupId} player={props.player} />
<HireEmployeeButton employee={emp2} office={props.office} corp={props.corp} popupId={props.popupId} player={props.player} />
<HireEmployeeButton employee={emp3} office={props.office} corp={props.corp} popupId={props.popupId} player={props.player} />
</>);
}

@ -1,34 +0,0 @@
// React Component for managing the Corporation's Industry UI
// This Industry component does NOT include the city tabs at the top
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { IndustryOffice } from "./IndustryOffice";
import { IndustryOverview } from "./IndustryOverview";
import { IndustryWarehouse } from "./IndustryWarehouse";
export class Industry extends BaseReactComponent {
constructor(props) {
if (props.currentCity == null) {
throw new Error(`Industry component constructed without 'city' prop`);
}
super(props);
}
render() {
return (
<div>
<div className={"cmpy-mgmt-industry-left-panel"}>
<IndustryOverview {...this.props} />
<IndustryOffice {...this.props} />
</div>
<div className={"cmpy-mgmt-industry-right-panel"}>
<IndustryWarehouse {...this.props} />
</div>
</div>
)
}
}

@ -0,0 +1,43 @@
// React Component for managing the Corporation's Industry UI
// This Industry component does NOT include the city tabs at the top
import React from "react";
import { IndustryOffice } from "./IndustryOffice";
import { IndustryOverview } from "./IndustryOverview";
import { IndustryWarehouse } from "./IndustryWarehouse";
import { ICorporation } from "../ICorporation";
import { CorporationRouting } from "./Routing";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
routing: CorporationRouting;
corp: ICorporation;
currentCity: string;
player: IPlayer;
}
export function Industry(props: IProps): React.ReactElement {
return (
<div>
<div className={"cmpy-mgmt-industry-left-panel"}>
<IndustryOverview
player={props.player}
routing={props.routing}
corp={props.corp}
currentCity={props.currentCity} />
<IndustryOffice
player={props.player}
routing={props.routing}
corp={props.corp}
currentCity={props.currentCity} />
</div>
<div className={"cmpy-mgmt-industry-right-panel"}>
<IndustryWarehouse
player={props.player}
corp={props.corp}
routing={props.routing}
currentCity={props.currentCity} />
</div>
</div>
)
}

@ -1,120 +1,145 @@
// React Component for displaying an Industry's OfficeSpace information
// (bottom-left panel in the Industry UI)
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import React, { useState } from "react";
import { OfficeSpace } from "../Corporation";
import { OfficeSpace } from "../OfficeSpace";
import { Employee } from "../Employee";
import { EmployeePositions } from "../EmployeePositions";
import { numeralWrapper } from "../../ui/numeralFormat";
import { getSelectText } from "../../../utils/uiHelpers/getSelectData";
import { createPopup } from "../../ui/React/createPopup";
import { UpgradeOfficeSizePopup } from "./UpgradeOfficeSizePopup";
import { HireEmployeePopup } from "./HireEmployeePopup";
import { ThrowPartyPopup } from "./ThrowPartyPopup";
import { ICorporation } from "../ICorporation";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { CorporationRouting } from "./Routing";
export class IndustryOffice extends BaseReactComponent {
constructor(props) {
super(props);
interface IProps {
routing: CorporationRouting;
corp: ICorporation;
currentCity: string;
player: IPlayer;
}
this.state = {
city: "",
division: "",
employeeManualAssignMode: false,
employee: null, // Reference to employee being referenced if in Manual Mode
numEmployees: 0,
numOperations: 0,
numEngineers: 0,
numBusiness: 0,
numManagement: 0,
numResearch: 0,
numUnassigned: 0,
numTraining: 0,
}
export function IndustryOffice(props: IProps): React.ReactElement {
const [employeeManualAssignMode, setEmployeeManualAssignMode] = useState(false);
const [city, setCity] = useState("");
const [divisionName, setDivisionName] = useState("");
const [employee, setEmployee] = useState<Employee | null>(null);
const [numEmployees, setNumEmployees] = useState(0);
const [numOperations, setNumOperations] = useState(0);
const [numEngineers, setNumEngineers] = useState(0);
const [numBusiness, setNumBusiness] = useState(0);
const [numManagement, setNumManagement] = useState(0);
const [numResearch, setNumResearch] = useState(0);
const [numUnassigned, setNumUnassigned] = useState(0);
const [numTraining, setNumTraining] = useState(0);
this.updateEmployeeCount(); // This function validates division and office refs
function resetEmployeeCount(): void {
setNumEmployees(0);
setNumOperations(0);
setNumEngineers(0);
setNumBusiness(0);
setNumManagement(0);
setNumResearch(0);
setNumUnassigned(0);
setNumTraining(0);
}
resetEmployeeCount() {
this.state.numEmployees = 0;
this.state.numOperations = 0;
this.state.numEngineers = 0;
this.state.numBusiness = 0;
this.state.numManagement = 0;
this.state.numResearch = 0;
this.state.numUnassigned = 0;
this.state.numTraining = 0;
}
updateEmployeeCount() {
const division = this.routing().currentDivision;
function updateEmployeeCount(): void {
const division = props.routing.currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
const office = division.offices[this.props.currentCity];
const office = division.offices[props.currentCity];
if (!(office instanceof OfficeSpace)) {
throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`);
throw new Error(`Current City (${props.currentCity}) for UI does not have an OfficeSpace object`);
}
// If we're in a new city, we have to reset the state
if (division.name !== this.state.division || this.props.currentCity !== this.state.city) {
this.resetEmployeeCount();
this.state.division = division.name;
this.state.city = this.props.currentCity;
if (division.name !== divisionName || props.currentCity !== city) {
resetEmployeeCount();
setDivisionName(division.name);
setCity(props.currentCity);
}
// Calculate how many NEW emplyoees we need to account for
// Calculate how many NEW employees we need to account for
const currentNumEmployees = office.employees.length;
let newOperations = numOperations;
let newEngineers = numEngineers;
let newBusiness = numBusiness;
let newManagement = numManagement;
let newResearch = numResearch;
let newUnassigned = numUnassigned;
let newTraining = numTraining;
// Record the number of employees in each position, for NEW employees only
for (let i = this.state.numEmployees; i < office.employees.length; ++i) {
for (let i = numEmployees; i < office.employees.length; ++i) {
switch (office.employees[i].pos) {
case EmployeePositions.Operations:
++this.state.numOperations;
newOperations++;
break;
case EmployeePositions.Engineer:
++this.state.numEngineers;
newEngineers++;
break;
case EmployeePositions.Business:
++this.state.numBusiness;
newBusiness++;
break;
case EmployeePositions.Management:
++this.state.numManagement;
newManagement++;
break;
case EmployeePositions.RandD:
++this.state.numResearch;
newResearch++;
break;
case EmployeePositions.Unassigned:
++this.state.numUnassigned;
newUnassigned++;
break;
case EmployeePositions.Training:
++this.state.numTraining;
newTraining++;
break;
default:
console.error("Unrecognized employee position: " + office.employees[i].pos);
break;
}
}
if(newOperations !== numOperations) setNumOperations(newOperations);
if(newEngineers !== numEngineers) setNumEngineers(newEngineers);
if(newBusiness !== numBusiness) setNumBusiness(newBusiness);
if(newManagement !== numManagement) setNumManagement(newManagement);
if(newResearch !== numResearch) setNumResearch(newResearch);
if(newUnassigned !== numUnassigned) setNumUnassigned(newUnassigned);
if(newTraining !== numTraining) setNumTraining(newTraining);
this.state.numEmployees = currentNumEmployees;
if(currentNumEmployees !== numEmployees) setNumEmployees(currentNumEmployees);
}
// Renders the "Employee Management" section of the Office UI
renderEmployeeManagement() {
this.updateEmployeeCount();
updateEmployeeCount();
if (this.state.employeeManualAssignMode) {
return this.renderManualEmployeeManagement();
// Renders the "Employee Management" section of the Office UI
function renderEmployeeManagement(): React.ReactElement {
updateEmployeeCount();
if (employeeManualAssignMode) {
return renderManualEmployeeManagement();
} else {
return this.renderAutomaticEmployeeManagement();
return renderAutomaticEmployeeManagement();
}
}
renderAutomaticEmployeeManagement() {
const division = this.routing().currentDivision; // Validated in constructor
const office = division.offices[this.props.currentCity]; // Validated in constructor
const vechain = (this.corp().unlockUpgrades[4] === 1); // Has Vechain upgrade
function renderAutomaticEmployeeManagement(): React.ReactElement {
const division = props.routing.currentDivision; // Validated in constructor
if(division === null) return(<></>);
const office = division.offices[props.currentCity]; // Validated in constructor
if(office === 0) return (<></>);
const vechain = (props.corp.unlockUpgrades[4] === 1); // Has Vechain upgrade
const switchModeOnClick = () => {
this.state.employeeManualAssignMode = true;
this.corp().rerender();
function switchModeOnClick(): void {
setEmployeeManualAssignMode(true);
props.corp.rerender(props.player);
}
// Calculate average morale, happiness, and energy. Also salary
@ -135,87 +160,91 @@ export class IndustryOffice extends BaseReactComponent {
}
// Helper functions for (re-)assigning employees to different positions
const assignEmployee = (to) => {
if (this.state.numUnassigned <= 0) {
function assignEmployee(to: string): void {
if(office === 0) return;
if(division === null) return;
if (numUnassigned <= 0) {
console.warn("Cannot assign employee. No unassigned employees available");
return;
}
switch (to) {
case EmployeePositions.Operations:
++this.state.numOperations;
setNumOperations(n => n+1);
break;
case EmployeePositions.Engineer:
++this.state.numEngineers;
setNumEngineers(n => n+1);
break;
case EmployeePositions.Business:
++this.state.numBusiness;
setNumBusiness(n => n+1);
break;
case EmployeePositions.Management:
++this.state.numManagement;
setNumManagement(n => n+1);
break;
case EmployeePositions.RandD:
++this.state.numResearch;
setNumResearch(n => n+1);
break;
case EmployeePositions.Unassigned:
++this.state.numUnassigned;
setNumUnassigned(n => n+1);
break;
case EmployeePositions.Training:
++this.state.numTraining;
setNumTraining(n => n+1);
break;
default:
console.error("Unrecognized employee position: " + to);
break;
}
--this.state.numUnassigned;
setNumUnassigned(n => n-1);
office.assignEmployeeToJob(to);
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
this.corp().rerender();
office.calculateEmployeeProductivity(props.corp, division);
props.corp.rerender(props.player);
}
const unassignEmployee = (from) => {
function logWarning(pos) {
function unassignEmployee(from: string): void {
if(office === 0) return;
if(division === null) return;
function logWarning(pos: string): void {
console.warn(`Cannot unassign from ${pos} because there is nobody assigned to that position`);
}
switch (from) {
case EmployeePositions.Operations:
if (this.state.numOperations <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numOperations;
if (numOperations <= 0) { return logWarning(EmployeePositions.Operations); }
setNumOperations(n => n-1);
break;
case EmployeePositions.Engineer:
if (this.state.numEngineers <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numEngineers;
if (numEngineers <= 0) { return logWarning(EmployeePositions.Operations); }
setNumEngineers(n => n-1);
break;
case EmployeePositions.Business:
if (this.state.numBusiness <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numBusiness;
if (numBusiness <= 0) { return logWarning(EmployeePositions.Operations); }
setNumBusiness(n => n-1);
break;
case EmployeePositions.Management:
if (this.state.numManagement <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numManagement;
if (numManagement <= 0) { return logWarning(EmployeePositions.Operations); }
setNumManagement(n => n-1);
break;
case EmployeePositions.RandD:
if (this.state.numResearch <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numResearch;
if (numResearch <= 0) { return logWarning(EmployeePositions.Operations); }
setNumResearch(n => n-1);
break;
case EmployeePositions.Unassigned:
console.warn(`Tried to unassign from the Unassigned position`);
break;
case EmployeePositions.Training:
if (this.state.numTraining <= 0) { return logWarning(EmployeePositions.Operations); }
--this.state.numTraining;
if (numTraining <= 0) { return logWarning(EmployeePositions.Operations); }
setNumTraining(n => n-1);
break;
default:
console.error("Unrecognized employee position: " + from);
break;
}
++this.state.numUnassigned;
setNumUnassigned(n => n+1);
office.unassignEmployeeFromJob(from);
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
this.corp().rerender();
office.calculateEmployeeProductivity(props.corp, division);
props.corp.rerender(props.player);
}
const positionHeaderStyle = {
@ -223,67 +252,67 @@ export class IndustryOffice extends BaseReactComponent {
margin: "5px 0px 5px 0px",
width: "50%",
}
const assignButtonClass = this.state.numUnassigned > 0 ? "std-button" : "a-link-button-inactive";
const assignButtonClass = numUnassigned > 0 ? "std-button" : "a-link-button-inactive";
const operationAssignButtonOnClick = () => {
function operationAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Operations);
this.corp().rerender();
props.corp.rerender(props.player);
}
const operationUnassignButtonOnClick = () => {
function operationUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Operations);
this.corp().rerender();
props.corp.rerender(props.player);
}
const operationUnassignButtonClass = this.state.numOperations > 0 ? "std-button" : "a-link-button-inactive";
const operationUnassignButtonClass = numOperations > 0 ? "std-button" : "a-link-button-inactive";
const engineerAssignButtonOnClick = () => {
function engineerAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Engineer);
this.corp().rerender();
props.corp.rerender(props.player);
}
const engineerUnassignButtonOnClick = () => {
function engineerUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Engineer);
this.corp().rerender();
props.corp.rerender(props.player);
}
const engineerUnassignButtonClass = this.state.numEngineers > 0 ? "std-button" : "a-link-button-inactive";
const engineerUnassignButtonClass = numEngineers > 0 ? "std-button" : "a-link-button-inactive";
const businessAssignButtonOnClick = () => {
function businessAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Business);
this.corp().rerender();
props.corp.rerender(props.player);
}
const businessUnassignButtonOnClick = () => {
function businessUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Business);
this.corp().rerender();
props.corp.rerender(props.player);
}
const businessUnassignButtonClass = this.state.numBusiness > 0 ? "std-button" : "a-link-button-inactive";
const businessUnassignButtonClass = numBusiness > 0 ? "std-button" : "a-link-button-inactive";
const managementAssignButtonOnClick = () => {
function managementAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Management);
this.corp().rerender();
props.corp.rerender(props.player);
}
const managementUnassignButtonOnClick = () => {
function managementUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Management);
this.corp().rerender();
props.corp.rerender(props.player);
}
const managementUnassignButtonClass = this.state.numManagement > 0 ? "std-button" : "a-link-button-inactive";
const managementUnassignButtonClass = numManagement > 0 ? "std-button" : "a-link-button-inactive";
const rndAssignButtonOnClick = () => {
function rndAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.RandD);
this.corp().rerender();
props.corp.rerender(props.player);
}
const rndUnassignButtonOnClick = () => {
function rndUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.RandD);
this.corp().rerender();
props.corp.rerender(props.player);
}
const rndUnassignButtonClass = this.state.numResearch > 0 ? "std-button" : "a-link-button-inactive";
const rndUnassignButtonClass = numResearch > 0 ? "std-button" : "a-link-button-inactive";
const trainingAssignButtonOnClick = () => {
function trainingAssignButtonOnClick(): void {
assignEmployee(EmployeePositions.Training);
this.corp().rerender();
props.corp.rerender(props.player);
}
const trainingUnassignButtonOnClick = () => {
function trainingUnassignButtonOnClick(): void {
unassignEmployee(EmployeePositions.Training);
this.corp().rerender();
props.corp.rerender(props.player);
}
const trainingUnassignButtonClass = this.state.numTraining > 0 ? "std-button" : "a-link-button-inactive";
const trainingUnassignButtonClass = numTraining > 0 ? "std-button" : "a-link-button-inactive";
return (
<div>
@ -295,7 +324,7 @@ export class IndustryOffice extends BaseReactComponent {
</span>
</button>
<p><strong>Unassigned Employees: {this.state.numUnassigned}</strong></p>
<p><strong>Unassigned Employees: {numUnassigned}</strong></p>
<br />
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
@ -344,7 +373,7 @@ export class IndustryOffice extends BaseReactComponent {
}
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Operations} ({this.state.numOperations})
{EmployeePositions.Operations} ({numOperations})
<span className={"tooltiptext"}>
Manages supply chain operations. Improves the amount of Materials and Products you produce.
</span>
@ -354,7 +383,7 @@ export class IndustryOffice extends BaseReactComponent {
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Engineer} ({this.state.numEngineers})
{EmployeePositions.Engineer} ({numEngineers})
<span className={"tooltiptext"}>
Develops and maintains products and production systems. Increases the quality of
everything you produce. Also increases the amount you produce (not as much
@ -366,7 +395,7 @@ export class IndustryOffice extends BaseReactComponent {
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Business} ({this.state.numBusiness})
{EmployeePositions.Business} ({numBusiness})
<span className={"tooltiptext"}>
Handles sales and finances. Improves the amount of Materials and Products you can sell.
</span>
@ -376,7 +405,7 @@ export class IndustryOffice extends BaseReactComponent {
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Management} ({this.state.numManagement})
{EmployeePositions.Management} ({numManagement})
<span className={"tooltiptext"}>
Leads and oversees employees and office operations. Improves the effectiveness of
Engineer and Operations employees
@ -387,7 +416,7 @@ export class IndustryOffice extends BaseReactComponent {
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.RandD} ({this.state.numResearch})
{EmployeePositions.RandD} ({numResearch})
<span className={"tooltiptext"}>
Research new innovative ways to improve the company. Generates Scientific Research
</span>
@ -397,7 +426,7 @@ export class IndustryOffice extends BaseReactComponent {
<br />
<h2 className={"tooltip"} style={positionHeaderStyle}>
{EmployeePositions.Training} ({this.state.numTraining})
{EmployeePositions.Training} ({numTraining})
<span className={"tooltiptext"}>
Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations.
</span>
@ -408,14 +437,16 @@ export class IndustryOffice extends BaseReactComponent {
)
}
renderManualEmployeeManagement() {
const corp = this.corp();
const division = this.routing().currentDivision; // Validated in constructor
const office = division.offices[this.props.currentCity]; // Validated in constructor
function renderManualEmployeeManagement(): React.ReactElement {
const corp = props.corp;
const division = props.routing.currentDivision; // Validated in constructor
if(division === null) return (<></>);
const office = division.offices[props.currentCity]; // Validated in constructor
if(office === 0) return (<></>);
const switchModeOnClick = () => {
this.state.employeeManualAssignMode = false;
this.corp().rerender();
function switchModeOnClick(): void {
setEmployeeManualAssignMode(false);
props.corp.rerender(props.player);
}
const employeeInfoDivStyle = {
@ -430,21 +461,22 @@ export class IndustryOffice extends BaseReactComponent {
employees.push(<option key={office.employees[i].name}>{office.employees[i].name}</option>)
}
const employeeSelectorOnChange = (e) => {
function employeeSelectorOnChange(e: React.ChangeEvent<HTMLSelectElement>): void {
if(office === 0) return;
const name = getSelectText(e.target);
for (let i = 0; i < office.employees.length; ++i) {
if (name === office.employees[i].name) {
this.state.employee = office.employees[i];
setEmployee(office.employees[i]);
break;
}
}
corp.rerender();
corp.rerender(props.player);
}
// Employee Positions Selector
const emp = this.state.employee;
let employeePositionSelectorInitialValue = null;
const emp = employee;
let employeePositionSelectorInitialValue = "";
const employeePositions = [];
const positionNames = Object.values(EmployeePositions);
for (let i = 0; i < positionNames.length; ++i) {
@ -454,11 +486,12 @@ export class IndustryOffice extends BaseReactComponent {
}
}
const employeePositionSelectorOnChange = (e) => {
function employeePositionSelectorOnChange(e: React.ChangeEvent<HTMLSelectElement>): void {
if(employee === null) return;
const pos = getSelectText(e.target);
this.state.employee.pos = pos;
this.resetEmployeeCount();
corp.rerender();
employee.pos = pos;
resetEmployeeCount();
corp.rerender(props.player);
}
// Numeraljs formatter
@ -486,29 +519,29 @@ export class IndustryOffice extends BaseReactComponent {
{employees}
</select>
{
this.state.employee != null &&
employee != null &&
<p>
Morale: {numeralWrapper.format(this.state.employee.mor, nf)}
Morale: {numeralWrapper.format(employee.mor, nf)}
<br />
Happiness: {numeralWrapper.format(this.state.employee.hap, nf)}
Happiness: {numeralWrapper.format(employee.hap, nf)}
<br />
Energy: {numeralWrapper.format(this.state.employee.ene, nf)}
Energy: {numeralWrapper.format(employee.ene, nf)}
<br />
Intelligence: {numeralWrapper.format(effInt, nf)}
<br />
Charisma: {numeralWrapper.format(effCha, nf)}
<br />
Experience: {numeralWrapper.format(this.state.employee.exp, nf)}
Experience: {numeralWrapper.format(employee.exp, nf)}
<br />
Creativity: {numeralWrapper.format(effCre, nf)}
<br />
Efficiency: {numeralWrapper.format(effEff, nf)}
<br />
Salary: {numeralWrapper.formatMoney(this.state.employee.sal)}
Salary: {numeralWrapper.formatMoney(employee.sal)}
</p>
}
{
this.state.employee != null &&
employee != null &&
<select onChange={employeePositionSelectorOnChange} value={employeePositionSelectorInitialValue}>
{employeePositions}
</select>
@ -518,89 +551,110 @@ export class IndustryOffice extends BaseReactComponent {
)
}
render() {
const corp = this.corp();
const division = this.routing().currentDivision; // Validated in constructor
const office = division.offices[this.props.currentCity]; // Validated in constructor
const buttonStyle = {
fontSize: "13px",
}
// Hire Employee button
let hireEmployeeButtonClass = "tooltip";
if (office.atCapacity()) {
hireEmployeeButtonClass += " a-link-button-inactive";
} else {
hireEmployeeButtonClass += " std-button";
if (office.employees.length === 0) {
hireEmployeeButtonClass += " flashing-button";
}
}
const hireEmployeeButtonOnClick = () => {
office.findEmployees({ corporation: corp, industry: division });
}
// Autohire employee button
let autohireEmployeeButtonClass = "tooltip";
if (office.atCapacity()) {
autohireEmployeeButtonClass += " a-link-button-inactive";
} else {
autohireEmployeeButtonClass += " std-button";
}
const autohireEmployeeButtonOnClick = () => {
if (office.atCapacity()) { return; }
office.hireRandomEmployee();
this.corp().rerender();
}
// Upgrade Office Size Button
const upgradeOfficeSizeOnClick = this.eventHandler().createUpgradeOfficeSizePopup.bind(this.eventHandler(), office);
// Throw Office Party
const throwOfficePartyOnClick = this.eventHandler().createThrowOfficePartyPopup.bind(this.eventHandler(), office);
return (
<div className={"cmpy-mgmt-employee-panel"}>
<h1 style={{ margin: "4px 0px 5px 0px" }}>Office Space</h1>
<p>Size: {office.employees.length} / {office.size} employees</p>
<button className={hireEmployeeButtonClass} onClick={hireEmployeeButtonOnClick} style={buttonStyle}>
Hire Employee
{
office.employees.length === 0 &&
<span className={"tooltiptext"}>
You'll need to hire some employees to get your operations started!
It's recommended to have at least one employee in every position
</span>
}
</button>
<button className={autohireEmployeeButtonClass} onClick={autohireEmployeeButtonOnClick} style={buttonStyle}>
Autohire Employee
<span className={"tooltiptext"}>
Automatically hires an employee and gives him/her a random name
</span>
</button>
<br />
<button className={"std-button tooltip"} onClick={upgradeOfficeSizeOnClick} style={buttonStyle}>
Upgrade size
<span className={"tooltiptext"}>
Upgrade the office's size so that it can hold more employees!
</span>
</button>
{
!division.hasResearch("AutoPartyManager") &&
<button className={"std-button tooltip"} onClick={throwOfficePartyOnClick} style={buttonStyle}>
Throw Party
<span className={"tooltiptext"}>
"Throw an office party to increase your employee's morale and happiness"
</span>
</button>
}
<br />
{this.renderEmployeeManagement()}
</div>
)
const division = props.routing.currentDivision; // Validated in constructor
if(division === null) return (<></>);
const office = division.offices[props.currentCity]; // Validated in constructor
if(office === 0) return (<></>);
const buttonStyle = {
fontSize: "13px",
}
// Hire Employee button
let hireEmployeeButtonClass = "tooltip";
if (office.atCapacity()) {
hireEmployeeButtonClass += " a-link-button-inactive";
} else {
hireEmployeeButtonClass += " std-button";
if (office.employees.length === 0) {
hireEmployeeButtonClass += " flashing-button";
}
}
function openHireEmployeePopup(): void {
if(office === 0) return;
const popupId = "cmpy-mgmt-hire-employee-popup";
createPopup(popupId, HireEmployeePopup, {
office: office,
corp: props.corp,
popupId: popupId,
player: props.player,
});
}
// Autohire employee button
let autohireEmployeeButtonClass = "tooltip";
if (office.atCapacity()) {
autohireEmployeeButtonClass += " a-link-button-inactive";
} else {
autohireEmployeeButtonClass += " std-button";
}
function autohireEmployeeButtonOnClick(): void {
if(office === 0) return;
if (office.atCapacity()) return;
office.hireRandomEmployee();
props.corp.rerender(props.player);
}
function openUpgradeOfficeSizePopup(): void {
if(office === 0) return;
const popupId = "cmpy-mgmt-upgrade-office-size-popup";
createPopup(popupId, UpgradeOfficeSizePopup, {
office: office,
corp: props.corp,
popupId: popupId,
player: props.player,
});
}
function openThrowPartyPopup(): void {
if(office === 0) return;
const popupId = "cmpy-mgmt-throw-office-party-popup";
createPopup(popupId, ThrowPartyPopup, {
office: office,
corp: props.corp,
popupId: popupId,
});
}
return (
<div className={"cmpy-mgmt-employee-panel"}>
<h1 style={{ margin: "4px 0px 5px 0px" }}>Office Space</h1>
<p>Size: {office.employees.length} / {office.size} employees</p>
<button className={hireEmployeeButtonClass} onClick={openHireEmployeePopup} style={buttonStyle}>
Hire Employee
{
office.employees.length === 0 &&
<span className={"tooltiptext"}>
You'll need to hire some employees to get your operations started!
It's recommended to have at least one employee in every position
</span>
}
</button>
<button className={autohireEmployeeButtonClass} onClick={autohireEmployeeButtonOnClick} style={buttonStyle}>
Autohire Employee
<span className={"tooltiptext"}>
Automatically hires an employee and gives him/her a random name
</span>
</button>
<br />
<button className={"std-button tooltip"} onClick={openUpgradeOfficeSizePopup} style={buttonStyle}>
Upgrade size
<span className={"tooltiptext"}>
Upgrade the office's size so that it can hold more employees!
</span>
</button>
{
!division.hasResearch("AutoPartyManager") &&
<button className={"std-button tooltip"} onClick={openThrowPartyPopup} style={buttonStyle}>
Throw Party
<span className={"tooltiptext"}>
"Throw an office party to increase your employee's morale and happiness"
</span>
</button>
}
<br />
{renderEmployeeManagement()}
</div>
)
}

@ -1,20 +1,34 @@
// React Component for displaying an Industry's overview information
// (top-left panel in the Industry UI)
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { OfficeSpace } from "../Corporation";
import { OfficeSpace } from "../OfficeSpace";
import { Industries } from "../IndustryData";
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 { ResearchPopup } from "./ResearchPopup";
import { createPopup } from "../../ui/React/createPopup";
import { Money } from "../../ui/React/Money";
import { ICorporation } from "../ICorporation";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { CorporationRouting } from "./Routing";
export class IndustryOverview extends BaseReactComponent {
renderMakeProductButton() {
const division = this.routing().currentDivision; // Validated inside render()
interface IProps {
routing: CorporationRouting;
corp: ICorporation;
currentCity: string;
player: IPlayer;
}
var createProductButtonText, createProductPopupText;
export function IndustryOverview(props: IProps): React.ReactElement {
function renderMakeProductButton(): React.ReactElement {
const division = props.routing.currentDivision; // Validated inside render()
if(division === null) return (<></>);
let createProductButtonText = "";
let createProductPopupText = "";
switch(division.type) {
case Industries.Food:
createProductButtonText = "Build Restaurant";
@ -52,7 +66,7 @@ export class IndustryOverview extends BaseReactComponent {
default:
createProductButtonText = "Create Product";
createProductPopupText = "Create a new product!";
return "";
return (<></>);
}
createProductPopupText += "<br><br>To begin developing a product, " +
"first choose the city in which it will be designed. The stats of your employees " +
@ -65,14 +79,24 @@ export class IndustryOverview extends BaseReactComponent {
const hasMaxProducts = division.hasMaximumNumberProducts();
const className = hasMaxProducts ? "a-link-button-inactive tooltip" : "std-button";
const onClick = this.eventHandler().createMakeProductPopup.bind(this.eventHandler(), createProductPopupText, division);
const buttonStyle = {
margin: "6px",
display: "inline-block",
}
function openMakeProductPopup(): void {
if(division === null) return;
const popupId = "cmpy-mgmt-create-product-popup";
createPopup(popupId, MakeProductPopup, {
popupText: createProductPopupText,
division: division,
corp: props.corp,
popupId: popupId,
});
}
return (
<button className={className} onClick={onClick} style={buttonStyle}>
<button className={className} onClick={openMakeProductPopup} style={buttonStyle}>
{createProductButtonText}
{
hasMaxProducts &&
@ -84,17 +108,13 @@ export class IndustryOverview extends BaseReactComponent {
)
}
renderText() {
const corp = this.corp();
const division = this.routing().currentDivision; // Validated inside render()
function renderText(): React.ReactElement {
const corp = props.corp;
const division = props.routing.currentDivision; // Validated inside render()
if(division === null) return (<></>);
const vechain = (corp.unlockUpgrades[4] === 1);
const profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber();
const genInfo = `Industry: ${division.type} (Corp Funds: ${numeralWrapper.formatMoney(corp.funds.toNumber())})`;
const awareness = `Awareness: ${numeralWrapper.format(division.awareness, "0.000")}`;
const popularity = `Popularity: ${numeralWrapper.format(division.popularity, "0.000")}`;
let advertisingInfo = false;
const advertisingFactors = division.getAdvertisingFactors();
const awarenessFac = advertisingFactors[1];
@ -103,15 +123,12 @@ export class IndustryOverview extends BaseReactComponent {
const totalAdvertisingFac = advertisingFactors[0];
if (vechain) { advertisingInfo = true; }
const revenue = `Revenue: ${numeralWrapper.formatMoney(division.lastCycleRevenue.toNumber())} / s`;
const expenses = `Expenses: ${numeralWrapper.formatMoney(division.lastCycleExpenses.toNumber())} /s`;
const profitStr = `Profit: ${numeralWrapper.formatMoney(profit)} / s`;
const productionMultHelpTipOnClick = () => {
function productionMultHelpTipOnClick(): void {
if(division === null) return;
// Wrapper for createProgressBarText()
// Converts the industry's "effectiveness factors"
// into a graphic (string) depicting how high that effectiveness is
function convertEffectFacToGraphic(fac) {
function convertEffectFacToGraphic(fac: number): string {
return createProgressBarText({
progress: fac,
totalTicks: 20,
@ -136,12 +153,21 @@ export class IndustryOverview extends BaseReactComponent {
`Real Estate: ${convertEffectFacToGraphic(division.reFac)}`);
}
function openResearchPopup(): void {
if(division === null) return;
const popupId = "corporation-research-popup-box";
createPopup(popupId, ResearchPopup, {
industry: division,
popupId: popupId,
});
}
return (
<div>
{genInfo}
Industry: {division.type} (Corp Funds: <Money money={corp.funds.toNumber()} />)
<br /> <br />
{awareness} <br />
{popularity} <br />
Awareness: {numeralWrapper.format(division.awareness, "0.000")} <br />
Popularity: {numeralWrapper.format(division.popularity, "0.000")} <br />
{
(advertisingInfo !== false) &&
<p className={"tooltip"}>Advertising Multiplier: x{numeralWrapper.format(totalAdvertisingFac, "0.000")}
@ -158,9 +184,9 @@ export class IndustryOverview extends BaseReactComponent {
}
{advertisingInfo}
<br /><br />
{revenue} <br />
{expenses} <br />
{profitStr}
Revenue: <Money money={division.lastCycleRevenue.toNumber()} /> / s <br />
Expenses: <Money money={division.lastCycleExpenses.toNumber()} /> /s <br />
Profit: <Money money={profit} /> / s
<br /> <br />
<p className={"tooltip"}>
Production Multiplier: {numeralWrapper.format(division.prodMult, "0.00")}
@ -178,19 +204,20 @@ export class IndustryOverview extends BaseReactComponent {
products that you produce.
</span>
</p>
<button className={"help-tip"} onClick={division.createResearchBox.bind(division)}>
<button className={"help-tip"} onClick={openResearchPopup}>
Research
</button>
</div>
)
}
renderUpgrades() {
const corp = this.corp();
const division = this.routing().currentDivision; // Validated inside render()
const office = division.offices[this.props.currentCity];
function renderUpgrades(): React.ReactElement[] {
const corp = props.corp;
const division = props.routing.currentDivision; // Validated inside render()
if(division === null) return ([<></>]);
const office = division.offices[props.currentCity];
if (!(office instanceof OfficeSpace)) {
throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`);
throw new Error(`Current City (${props.currentCity}) for UI does not have an OfficeSpace object`);
}
const upgrades = [];
@ -213,7 +240,9 @@ export class IndustryOverview extends BaseReactComponent {
break;
}
const onClick = () => {
function onClick(): void {
if(office === 0) return;
if(division === null) return;
if (corp.funds.lt(cost)) {
dialogBoxCreate("Insufficient funds");
} else {
@ -223,13 +252,14 @@ export class IndustryOverview extends BaseReactComponent {
office: office,
});
// corp.displayDivisionContent(division, city);
corp.rerender();
corp.rerender(props.player);
}
}
upgrades.push(this.renderUpgrade({
upgrades.push(renderUpgrade({
key: index,
onClick: onClick,
text: `${upgrade[4]} - ${numeralWrapper.formatMoney(cost)}`,
text: <>{upgrade[4]} - <Money money={cost} /></>,
tooltip: upgrade[5],
}));
}
@ -237,9 +267,16 @@ export class IndustryOverview extends BaseReactComponent {
return upgrades;
}
renderUpgrade(props) {
interface IRenderUpgradeProps {
key: string;
onClick: () => void;
text: JSX.Element;
tooltip: string;
}
function renderUpgrade(props: IRenderUpgradeProps): React.ReactElement {
return (
<div className={"cmpy-mgmt-upgrade-div tooltip"} onClick={props.onClick} key={props.text}>
<div className={"cmpy-mgmt-upgrade-div tooltip"} onClick={props.onClick} key={props.key}>
{props.text}
{
props.tooltip != null &&
@ -249,27 +286,25 @@ export class IndustryOverview extends BaseReactComponent {
)
}
render() {
const division = this.routing().currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
const makeProductButton = this.renderMakeProductButton();
return (
<div className={"cmpy-mgmt-industry-overview-panel"}>
{this.renderText()}
<br />
<u className={"industry-purchases-and-upgrades-header"}>Purchases & Upgrades</u><br />
{this.renderUpgrades()} <br />
{
division.makesProducts &&
makeProductButton
}
</div>
)
const division = props.routing.currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
const makeProductButton = renderMakeProductButton();
return (
<div className={"cmpy-mgmt-industry-overview-panel"}>
{renderText()}
<br />
<u className={"industry-purchases-and-upgrades-header"}>Purchases & Upgrades</u><br />
{renderUpgrades()} <br />
{
division.makesProducts &&
makeProductButton
}
</div>
)
}

@ -1,27 +1,47 @@
// React Component for displaying an Industry's warehouse information
// (right-side panel in the Industry UI)
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { OfficeSpace,
WarehouseInitialCost,
WarehouseUpgradeBaseCost,
ProductProductionCostRatio } from "../Corporation";
import { CorporationConstants } from "../data/Constants";
import { OfficeSpace } from "../OfficeSpace";
import { Material } from "../Material";
import { Product } from "../Product";
import { Warehouse } from "../Warehouse";
import { DiscontinueProductPopup } from "./DiscontinueProductPopup";
import { ExportPopup } from "./ExportPopup";
import { LimitProductProductionPopup } from "./LimitProductProductionPopup";
import { MaterialMarketTaPopup } from "./MaterialMarketTaPopup";
import { SellMaterialPopup } from "./SellMaterialPopup";
import { SellProductPopup } from "./SellProductPopup";
import { PurchaseMaterialPopup } from "./PurchaseMaterialPopup";
import { ProductMarketTaPopup } from "./ProductMarketTaPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createPopup } from "../../ui/React/createPopup";
import { isString } from "../../../utils/helpers/isString";
import { ICorporation } from "../ICorporation";
import { IIndustry } from "../IIndustry";
import { CorporationRouting } from "./Routing";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { SetSmartSupply } from "../Actions";
interface IProductProps {
corp: ICorporation;
division: IIndustry;
city: string;
product: Product;
player: IPlayer;
}
// Creates the UI for a single Product type
function ProductComponent(props) {
function ProductComponent(props: IProductProps): React.ReactElement {
const corp = props.corp;
const division = props.division;
const city = props.city;
const product = props.product;
const eventHandler = props.eventHandler;
// Numeraljs formatters
const nf = "0.000";
@ -53,23 +73,53 @@ function ProductComponent(props) {
if (isString(product.sCost)) {
sellButtonText += (" @ " + product.sCost);
} else {
sellButtonText += (" @ " + numeralWrapper.format(product.sCost, "$0.000a"));
sellButtonText += (" @ " + numeralWrapper.formatMoney(product.sCost as number));
}
}
const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product, city);
function openSellProductPopup(): void {
const popupId = "cmpy-mgmt-limit-product-production-popup";
createPopup(popupId, SellProductPopup, {
product: product,
city: city,
popupId: popupId,
});
}
// Limit Production button
let limitProductionButtonText = "Limit Production";
if (product.prdman[city][0]) {
limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")";
}
const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city);
// Discontinue Button
const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product, division);
function openLimitProductProdutionPopup(): void {
const popupId = "cmpy-mgmt-limit-product-production-popup";
createPopup(popupId, LimitProductProductionPopup, {
product: product,
city: city,
popupId: popupId,
});
}
// Market TA button
const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division);
function openDiscontinueProductPopup(): void {
const popupId = "cmpy-mgmt-discontinue-product-popup";
createPopup(popupId, DiscontinueProductPopup, {
product: product,
industry: division,
corp: props.corp,
popupId: popupId,
player: props.player,
});
}
function openProductMarketTaPopup(): void {
const popupId = "cmpy-mgmt-marketta-popup";
createPopup(popupId, ProductMarketTaPopup, {
product: product,
industry: division,
popupId: popupId,
});
}
// Unfinished Product
if (!product.fin) {
@ -81,18 +131,18 @@ function ProductComponent(props) {
<br />
<div>
<button className={"std-button"} onClick={sellButtonOnClick}>
<button className={"std-button"} onClick={openSellProductPopup}>
{sellButtonText}
</button><br />
<button className={"std-button"} onClick={limitProductionButtonOnClick}>
<button className={"std-button"} onClick={openLimitProductProdutionPopup}>
{limitProductionButtonText}
</button>
<button className={"std-button"} onClick={discontinueButtonOnClick}>
<button className={"std-button"} onClick={openDiscontinueProductPopup}>
Discontinue
</button>
{
division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}>
<button className={"std-button"} onClick={openProductMarketTaPopup}>
Market-TA
</button>
}
@ -146,7 +196,7 @@ function ProductComponent(props) {
</span>
</p><br />
<p className={"tooltip"}>
Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)}
Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / CorporationConstants.ProductProductionCostRatio)}
<span className={"tooltiptext"}>
An estimate of the material cost it takes to create this Product.
</span>
@ -161,18 +211,18 @@ function ProductComponent(props) {
</p>
<div>
<button className={"std-button"} onClick={sellButtonOnClick}>
<button className={"std-button"} onClick={openSellProductPopup}>
{sellButtonText}
</button><br />
<button className={"std-button"} onClick={limitProductionButtonOnClick}>
<button className={"std-button"} onClick={openLimitProductProdutionPopup}>
{limitProductionButtonText}
</button>
<button className={"std-button"} onClick={discontinueButtonOnClick}>
<button className={"std-button"} onClick={openDiscontinueProductPopup}>
Discontinue
</button>
{
division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}>
<button className={"std-button"} onClick={openProductMarketTaPopup}>
Market-TA
</button>
}
@ -181,14 +231,21 @@ function ProductComponent(props) {
)
}
interface IMaterialProps {
corp: ICorporation;
division: IIndustry;
warehouse: Warehouse;
city: string;
mat: Material;
}
// Creates the UI for a single Material type
function MaterialComponent(props) {
function MaterialComponent(props: IMaterialProps): React.ReactElement {
const corp = props.corp;
const division = props.division;
const warehouse = props.warehouse;
const city = props.city;
const mat = props.mat;
const eventHandler = props.eventHandler;
const markupLimit = mat.getMarkupLimit();
const office = division.offices[city];
if (!(office instanceof OfficeSpace)) {
@ -210,10 +267,26 @@ function MaterialComponent(props) {
// Purchase material button
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
// Export material button
const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat);
function openPurchaseMaterialPopup(): void {
const popupId = "cmpy-mgmt-material-purchase-popup";
createPopup(popupId, PurchaseMaterialPopup, {
mat: mat,
industry: division,
warehouse: warehouse,
corp: props.corp,
popupId: popupId,
});
}
function openExportPopup(): void {
const popupId = "cmpy-mgmt-export-popup";
createPopup(popupId, ExportPopup, {
mat: mat,
corp: props.corp,
popupId: popupId,
});
}
// Sell material button
let sellButtonText;
@ -221,7 +294,7 @@ function MaterialComponent(props) {
if (isString(mat.sllman[1])) {
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${mat.sllman[1]})`
} else {
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1], nfB)})`;
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1] as number, nfB)})`;
}
if (mat.marketTa2) {
@ -230,19 +303,34 @@ function MaterialComponent(props) {
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit);
} else if (mat.sCost) {
if (isString(mat.sCost)) {
var sCost = mat.sCost.replace(/MP/g, mat.bCost);
const sCost = (mat.sCost as string).replace(/MP/g, mat.bCost+'');
sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost));
} else {
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.sCost);
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.sCost as number);
}
}
} else {
sellButtonText = "Sell (0.000/0.000)";
}
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
// Market TA button
const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division);
function openSellMaterialPopup(): void {
const popupId = "cmpy-mgmt-material-sell-popup";
createPopup(popupId, SellMaterialPopup, {
mat: mat,
corp: props.corp,
popupId: popupId,
});
}
function openMaterialMarketTaPopup(): void {
const popupId = "cmpy-mgmt-export-popup";
createPopup(popupId, MaterialMarketTaPopup, {
mat: mat,
industry: division,
corp: props.corp,
popupId: popupId,
});
}
return (
<div className={"cmpy-mgmt-warehouse-material-div"}>
@ -286,7 +374,7 @@ function MaterialComponent(props) {
</div>
<div style={{display: "inline-block"}}>
<button className={purchaseButtonClass} onClick={purchaseButtonOnClick}>
<button className={purchaseButtonClass} onClick={openPurchaseMaterialPopup}>
{purchaseButtonText}
{
tutorial &&
@ -298,19 +386,19 @@ function MaterialComponent(props) {
{
corp.unlockUpgrades[0] === 1 &&
<button className={"std-button"} onClick={exportButtonOnClick}>
<button className={"std-button"} onClick={openExportPopup}>
Export
</button>
}
<br />
<button className={"std-button"} onClick={sellButtonOnClick}>
<button className={"std-button"} onClick={openSellMaterialPopup}>
{sellButtonText}
</button>
{
division.hasResearch("Market-TA.I") &&
<button className={"std-button"} onClick={marketTaButtonOnClick}>
<button className={"std-button"} onClick={openMaterialMarketTaPopup}>
Market-TA
</button>
}
@ -320,10 +408,17 @@ function MaterialComponent(props) {
)
}
export class IndustryWarehouse extends BaseReactComponent {
interface IProps {
corp: ICorporation;
routing: CorporationRouting;
currentCity: string;
player: IPlayer;
}
export function IndustryWarehouse(props: IProps): React.ReactElement {
// Returns a boolean indicating whether the given material is relevant for the
// current industry.
isRelevantMaterial(matName, division) {
function isRelevantMaterial(matName: string, division: IIndustry): boolean {
// Materials that affect Production multiplier
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"];
@ -334,10 +429,12 @@ export class IndustryWarehouse extends BaseReactComponent {
return false;
}
renderWarehouseUI() {
const corp = this.corp();
const division = this.routing().currentDivision; // Validated in render()
const warehouse = division.warehouses[this.props.currentCity]; // Validated in render()
function renderWarehouseUI(): React.ReactElement {
const corp = props.corp;
const division = props.routing.currentDivision; // Validated in render()
if(division === null) return (<></>);
const warehouse = division.warehouses[props.currentCity]; // Validated in render()
if(warehouse === 0) return (<></>);
// General Storage information at the top
const sizeUsageStyle = {
@ -346,15 +443,16 @@ export class IndustryWarehouse extends BaseReactComponent {
}
// Upgrade Warehouse size button
const sizeUpgradeCost = WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
const sizeUpgradeCost = CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
const canAffordUpgrade = (corp.funds.gt(sizeUpgradeCost));
const upgradeWarehouseClass = canAffordUpgrade ? "std-button" : "a-link-button-inactive";
const upgradeWarehouseOnClick = () => {
function upgradeWarehouseOnClick(): void {
if(division === null) return;
if(warehouse === 0) return;
++warehouse.level;
warehouse.updateSize(corp, division);
corp.funds = corp.funds.minus(sizeUpgradeCost);
corp.rerender();
return;
corp.rerender(props.player);
}
// Industry material Requirements
@ -416,9 +514,10 @@ export class IndustryWarehouse extends BaseReactComponent {
// Smart Supply Checkbox
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
const smartSupplyOnChange = (e) => {
warehouse.smartSupplyEnabled = e.target.checked;
corp.rerender();
function smartSupplyOnChange(e: React.ChangeEvent<HTMLInputElement>): void {
if(warehouse === 0) return;
SetSmartSupply(warehouse, e.target.checked);
corp.rerender(props.player);
}
// Create React components for materials
@ -426,12 +525,11 @@ export class IndustryWarehouse extends BaseReactComponent {
for (const matName in warehouse.materials) {
if (warehouse.materials[matName] instanceof Material) {
// Only create UI for materials that are relevant for the industry
if (this.isRelevantMaterial(matName, division)) {
if (isRelevantMaterial(matName, division)) {
mats.push(<MaterialComponent
city={this.props.currentCity}
city={props.currentCity}
corp={corp}
division={division}
eventHandler={this.eventHandler()}
key={matName}
mat={warehouse.materials[matName]}
warehouse={warehouse} />);
@ -443,15 +541,16 @@ export class IndustryWarehouse extends BaseReactComponent {
const products = [];
if (division.makesProducts && Object.keys(division.products).length > 0) {
for (const productName in division.products) {
if (division.products[productName] instanceof Product) {
const product = division.products[productName];
if (product instanceof Product) {
products.push(<ProductComponent
city={this.props.currentCity}
player={props.player}
city={props.currentCity}
corp={corp}
division={division}
eventHandler={this.eventHandler()}
key={productName}
product={division.products[productName]}
warehouse={warehouse} />);
product={product}
/>);
}
}
}
@ -500,25 +599,36 @@ export class IndustryWarehouse extends BaseReactComponent {
)
}
render() {
const division = this.routing().currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
const warehouse = division.warehouses[this.props.currentCity];
const division = props.routing.currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
const warehouse = division.warehouses[props.currentCity];
const newWarehouseOnClick = this.eventHandler().purchaseWarehouse.bind(this.eventHandler(), division, this.props.currentCity);
if (warehouse instanceof Warehouse) {
return this.renderWarehouseUI();
function purchaseWarehouse(division: IIndustry, city: string): void {
if (props.corp.funds.lt(CorporationConstants.WarehouseInitialCost)) {
dialogBoxCreate("You do not have enough funds to do this!");
} else {
return (
<div className={"cmpy-mgmt-warehouse-panel"}>
<button className={"std-button"} onClick={newWarehouseOnClick}>
Purchase Warehouse ({numeralWrapper.formatMoney(WarehouseInitialCost)})
</button>
</div>
)
division.warehouses[city] = new Warehouse({
corp: props.corp,
industry: division,
loc: city,
size: CorporationConstants.WarehouseInitialSize,
});
props.corp.funds = props.corp.funds.minus(CorporationConstants.WarehouseInitialCost);
props.corp.rerender(props.player);
}
}
if (warehouse instanceof Warehouse) {
return renderWarehouseUI();
} else {
return (
<div className={"cmpy-mgmt-warehouse-panel"}>
<button className={"std-button"} onClick={() => purchaseWarehouse(division, props.currentCity)}>
Purchase Warehouse ({numeralWrapper.formatMoney(CorporationConstants.WarehouseInitialCost)})
</button>
</div>
)
}
}

@ -0,0 +1,58 @@
import React, { useState } from 'react';
import { removePopup } from "../../ui/React/createPopup";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { CorporationConstants } from "../data/Constants";
import { ICorporation } from "../ICorporation";
import { IssueDividends } from "../Actions";
interface IProps {
popupId: string;
corp: ICorporation;
}
// 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;
try {
IssueDividends(props.corp, percent/100);
} catch(err) {
dialogBoxCreate(err+'');
}
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 { 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";
import { ICorporation } from "../ICorporation";
interface IEffectTextProps {
corp: ICorporation;
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: ICorporation;
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();
}

@ -1,36 +0,0 @@
// React components for the levelable upgrade buttons on the overview panel
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
export class LevelableUpgrade extends BaseReactComponent {
render() {
const data = this.props.upgradeData;
const level = this.props.upgradeLevel;
const baseCost = data[1];
const priceMult = data[2];
const cost = baseCost * Math.pow(priceMult, level);
const text = `${data[4]} - ${numeralWrapper.formatMoney(cost)}`
const tooltip = data[5];
const onClick = () => {
const corp = this.corp();
if (corp.funds.lt(cost)) {
dialogBoxCreate("Insufficient funds");
} else {
corp.upgrade(data);
corp.rerender();
}
}
return (
<div className={"cmpy-mgmt-upgrade-div tooltip"} style={{"width" : "45%"}} onClick={onClick}>
{text}
<span className={"tooltiptext"}>{tooltip}</span>
</div>
)
}
}

@ -0,0 +1,43 @@
// React components for the levelable upgrade buttons on the overview panel
import React from "react";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { ICorporation } from "../ICorporation";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { CorporationUpgrade } from "../data/CorporationUpgrades";
import { LevelUpgrade } from "../Actions";
import { Money } from "../../ui/React/Money";
interface IProps {
upgrade: CorporationUpgrade;
corp: ICorporation;
player: IPlayer;
}
export function LevelableUpgrade(props: IProps): React.ReactElement {
const data = props.upgrade;
const level = props.corp.upgrades[data[0]];
const baseCost = data[1];
const priceMult = data[2];
const cost = baseCost * Math.pow(priceMult, level);
const text = <>{data[4]} - <Money money={cost} /></>
const tooltip = data[5];
function onClick(): void {
try {
LevelUpgrade(props.corp, props.upgrade);
} catch(err) {
dialogBoxCreate(err+'');
}
props.corp.rerender(props.player);
}
return (
<div className={"cmpy-mgmt-upgrade-div tooltip"} style={{"width" : "45%"}} onClick={onClick}>
{text}
<span className={"tooltiptext"}>{tooltip}</span>
</div>
)
}

@ -0,0 +1,53 @@
import React, { useState } from 'react';
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { Product } from "../Product";
interface IProps {
product: Product;
city: string;
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;
}
const 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>
</>);
}

@ -1,105 +0,0 @@
// React Component for the element that contains the actual info/data
// for the Corporation UI. This panel lies below the header tabs and will
// be filled with whatever is needed based on the routing/navigation
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { CityTabs } from "./CityTabs";
import { Industry } from "./Industry";
import { Overview } from "./Overview";
import { OfficeSpace } from "../Corporation";
import { CityName } from "../../Locations/data/CityNames";
export class MainPanel extends BaseReactComponent {
constructor(props) {
super(props);
this.state = {
division: "",
city: CityName.Sector12,
}
}
// We can pass this setter to child components
changeCityState(newCity) {
if (Object.values(CityName).includes(newCity)) {
this.state.city = newCity;
} else {
console.error(`Tried to change MainPanel's city state to an invalid city: ${newCity}`);
}
}
// Determines what UI content to render based on routing
renderContent() {
if (this.routing().isOnOverviewPage()) {
// Corporation overview Content
return this.renderOverviewPage();
} else {
// Division content
// First, check if we're at a new division. If so, we need to reset the city to Sector-12
// Otherwise, just switch the 'city' state
const currentDivision = this.routing().current();
if (currentDivision !== this.state.division) {
this.state.division = currentDivision;
this.state.city = CityName.Sector12;
}
return this.renderDivisionPage();
}
}
renderOverviewPage() {
return (
<div id="cmpy-mgmt-panel">
<Overview {...this.props} />
</div>
)
}
renderDivisionPage() {
// Note: Division is the same thing as Industry...I wasn't consistent with naming
const division = this.routing().currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
// City tabs
const onClicks = {};
for (const cityName in division.offices) {
if (division.offices[cityName] instanceof OfficeSpace) {
onClicks[cityName] = () => {
this.state.city = cityName;
this.corp().rerender();
}
}
}
const cityTabs = (
<CityTabs
{...this.props}
city={this.state.city}
onClicks={onClicks}
cityStateSetter={this.changeCityState.bind(this)}
/>
)
// Rest of Industry UI
const industry = (
<Industry {...this.props} currentCity={this.state.city} />
)
return (
<div id="cmpy-mgmt-panel">
{cityTabs}
{industry}
</div>
)
}
render() {
return this.renderContent();
}
}

@ -0,0 +1,96 @@
// React Component for the element that contains the actual info/data
// for the Corporation UI. This panel lies below the header tabs and will
// be filled with whatever is needed based on the routing/navigation
import React, { useState } from "react";
import { CityTabs } from "./CityTabs";
import { Industry } from "./Industry";
import { Overview } from "./Overview";
import { OfficeSpace } from "../OfficeSpace";
import { CityName } from "../../Locations/data/CityNames";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { ICorporation } from "../ICorporation";
import { CorporationRouting } from "./Routing";
interface IProps {
corp: ICorporation;
routing: CorporationRouting;
player: IPlayer;
}
export function MainPanel(props: IProps): React.ReactElement {
const [division, setDivision] = useState("");
const [city, setCity] = useState<string>(CityName.Sector12);
// We can pass this setter to child components
function changeCityState(newCity: string): void {
if (Object.values(CityName).includes(newCity as CityName)) {
setCity(newCity);
} else {
console.error(`Tried to change MainPanel's city state to an invalid city: ${newCity}`);
}
}
function renderOverviewPage(): React.ReactElement {
return (
<div id="cmpy-mgmt-panel">
<Overview {...props} />
</div>
)
}
function renderDivisionPage(): React.ReactElement {
// Note: Division is the same thing as Industry...I wasn't consistent with naming
const division = props.routing.currentDivision;
if (division == null) {
throw new Error(`Routing does not hold reference to the current Industry`);
}
// City tabs
const onClicks: { [key: string]: () => void } = {};
for (const cityName in division.offices) {
if (division.offices[cityName] instanceof OfficeSpace) {
onClicks[cityName] = () => {
setCity(cityName);
props.corp.rerender(props.player);
}
}
}
const cityTabs = (
<CityTabs
{...props}
corp={props.corp}
city={city}
onClicks={onClicks}
cityStateSetter={changeCityState}
/>
)
return (
<div id="cmpy-mgmt-panel">
{cityTabs}
<Industry {...props} currentCity={city} />
</div>
)
}
if (props.routing.isOnOverviewPage()) {
// Corporation overview Content
return renderOverviewPage();
} else {
// Division content
// First, check if we're at a new division. If so, we need to reset the city to Sector-12
// Otherwise, just switch the 'city' state
const currentDivision = props.routing.current();
if (currentDivision !== division) {
setDivision(currentDivision);
setCity(CityName.Sector12);
}
return renderDivisionPage();
}
}

@ -0,0 +1,102 @@
import React, { useState } from 'react';
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { Industries } from "../IndustryData";
import { Product } from "../Product";
import { ICorporation } from "../ICorporation";
import { IIndustry } from "../IIndustry";
interface IProps {
popupText: string;
division: IIndustry;
corp: ICorporation;
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): React.ReactElement {
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,116 @@
import React, { useState } from 'react';
import { numeralWrapper } from "../../ui/numeralFormat";
import { IIndustry } from "../IIndustry";
import { ICorporation } from "../ICorporation";
import { Material } from "../Material";
interface IMarketTA2Props {
industry: IIndustry;
mat: Material;
}
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: Material;
industry: IIndustry;
corp: ICorporation;
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,68 @@
import React, { useState } from 'react';
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import {
Industries,
IndustryStartingCosts,
IndustryDescriptions } from "../IndustryData";
import { Industry } from "../Industry";
import { ICorporation } from "../ICorporation";
import { IIndustry } from "../IIndustry";
import { CorporationRouting } from "./Routing";
import { NewIndustry } from "../Actions";
interface IProps {
corp: ICorporation;
popupId: string;
routing: CorporationRouting;
}
// 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: IIndustry) => division.type === industryType) === undefined).sort();
const [industry, setIndustry] = useState(possibleIndustries.length > 0 ? possibleIndustries[0] : '');
const [name, setName] = useState('');
function newIndustry(): void {
try {
NewIndustry(props.corp, industry, name);
} catch(err) {
dialogBoxCreate(err+'');
return;
}
// Set routing to the new division so that the UI automatically switches to it
props.routing.routeTo(name);
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>
</>);
}

@ -1,327 +0,0 @@
// React Component for displaying Corporation Overview info
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { LevelableUpgrade } from "./LevelableUpgrade";
import { UnlockUpgrade } from "./UnlockUpgrade";
import { BribeThreshold } from "../Corporation";
import { CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades";
import { CorporationUpgrades } from "../data/CorporationUpgrades";
import { CONSTANTS } from "../../Constants";
import { numeralWrapper } from "../../ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
export class Overview extends BaseReactComponent {
// Generic Function for Creating a button
createButton(props) {
let className = props.class ? props.class : "std-button";
const displayStyle = props.display ? props.display : "block";
const hasTooltip = (props.tooltip != null);
if (hasTooltip) {
className += " tooltip";
}
return (
<a className={className} onClick={props.onClick} style={{display: {displayStyle}}}>
{props.text}
{
hasTooltip &&
<span className={"tooltiptext"}>
{props.tooltip}
</span>
}
</a>
)
}
// Returns a string with general information about Corporation
getOverviewText() {
// Formatted text for profit
var profit = this.corp().revenue.minus(this.corp().expenses).toNumber(),
profitStr = profit >= 0 ? numeralWrapper.formatMoney(profit) : "-" + numeralWrapper.format(-1 * profit, "$0.000a");
// Formatted text for dividend information, if applicable
let dividendStr = "";
if (this.corp().dividendPercentage > 0 && profit > 0) {
const totalDividends = (this.corp().dividendPercentage / 100) * profit;
const retainedEarnings = profit - totalDividends;
const dividendsPerShare = totalDividends / this.corp().totalShares;
const playerEarnings = this.corp().numShares * dividendsPerShare;
dividendStr = `Retained Profits (after dividends): ${numeralWrapper.format(retainedEarnings, "$0.000a")} / s<br><br>` +
`Dividend Percentage: ${numeralWrapper.format(this.corp().dividendPercentage / 100, "0%")}<br>` +
`Dividends per share: ${numeralWrapper.format(dividendsPerShare, "$0.000a")} / s<br>` +
`Your earnings as a shareholder (Pre-Tax): ${numeralWrapper.format(playerEarnings, "$0.000a")} / s<br>` +
`Dividend Tax Rate: ${this.corp().dividendTaxPercentage}%<br>` +
`Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (this.corp().dividendTaxPercentage / 100)), "$0.000a")} / s<br><br>`;
}
let txt = "Total Funds: " + numeralWrapper.format(this.corp().funds.toNumber(), '$0.000a') + "<br>" +
"Total Revenue: " + numeralWrapper.format(this.corp().revenue.toNumber(), "$0.000a") + " / s<br>" +
"Total Expenses: " + numeralWrapper.format(this.corp().expenses.toNumber(), "$0.000a") + " / s<br>" +
"Total Profits: " + profitStr + " / s<br>" +
dividendStr +
"Publicly Traded: " + (this.corp().public ? "Yes" : "No") + "<br>" +
"Owned Stock Shares: " + numeralWrapper.format(this.corp().numShares, '0.000a') + "<br>" +
"Stock Price: " + (this.corp().public ? numeralWrapper.formatMoney(this.corp().sharePrice) : "N/A") + "<br>" +
"<p class='tooltip'>Total Stock Shares: " + numeralWrapper.format(this.corp().totalShares, "0.000a") +
"<span class='tooltiptext'>" +
`Outstanding Shares: ${numeralWrapper.format(this.corp().issuedShares, "0.000a")}<br>` +
`Private Shares: ${numeralWrapper.format(this.corp().totalShares - this.corp().issuedShares - this.corp().numShares, "0.000a")}` +
"</span></p><br><br>";
const storedTime = this.corp().storedCycles * CONSTANTS.MilliPerCycle;
if (storedTime > 15000) {
txt += `Bonus time: ${convertTimeMsToTimeElapsedString(storedTime)}<br><br>`;
}
let prodMult = this.corp().getProductionMultiplier(),
storageMult = this.corp().getStorageMultiplier(),
advMult = this.corp().getAdvertisingMultiplier(),
empCreMult = this.corp().getEmployeeCreMultiplier(),
empChaMult = this.corp().getEmployeeChaMultiplier(),
empIntMult = this.corp().getEmployeeIntMultiplier(),
empEffMult = this.corp().getEmployeeEffMultiplier(),
salesMult = this.corp().getSalesMultiplier(),
sciResMult = this.corp().getScientificResearchMultiplier();
if (prodMult > 1) {txt += "Production Multiplier: " + numeralWrapper.format(prodMult, "0.000") + "<br>";}
if (storageMult > 1) {txt += "Storage Multiplier: " + numeralWrapper.format(storageMult, "0.000") + "<br>";}
if (advMult > 1) {txt += "Advertising Multiplier: " + numeralWrapper.format(advMult, "0.000") + "<br>";}
if (empCreMult > 1) {txt += "Empl. Creativity Multiplier: " + numeralWrapper.format(empCreMult, "0.000") + "<br>";}
if (empChaMult > 1) {txt += "Empl. Charisma Multiplier: " + numeralWrapper.format(empChaMult, "0.000") + "<br>";}
if (empIntMult > 1) {txt += "Empl. Intelligence Multiplier: " + numeralWrapper.format(empIntMult, "0.000") + "<br>";}
if (empEffMult > 1) {txt += "Empl. Efficiency Multiplier: " + numeralWrapper.format(empEffMult, "0.000") + "<br>";}
if (salesMult > 1) {txt += "Sales Multiplier: " + numeralWrapper.format(salesMult, "0.000") + "<br>";}
if (sciResMult > 1) {txt += "Scientific Research Multiplier: " + numeralWrapper.format(sciResMult, "0.000") + "<br>";}
return txt;
}
// Render the buttons that lie below the overview text.
// These are mainly for things such as managing finances/stock
renderButtons() {
// Create a "Getting Started Guide" button that lets player view the
// handbook and adds it to the players home computer
const getStarterGuideOnClick = this.corp().getStarterGuide.bind(this.corp());
const getStarterGuideBtn = this.createButton({
class: "a-link-button",
display: "inline-block",
onClick: getStarterGuideOnClick,
text: "Getting Started Guide",
tooltip: "Get a copy of and read 'The Complete Handbook for Creating a Successful Corporation.' " +
"This is a .lit file that guides you through the beginning of setting up a Corporation and " +
"provides some tips/pointers for helping you get started with managing it.",
});
// Create a "Bribe Factions" button if your Corporation is powerful enough.
// This occurs regardless of whether you're public or private
const canBribe = (this.corp().determineValuation() >= BribeThreshold);
const bribeFactionsOnClick = this.eventHandler().createBribeFactionsPopup.bind(this.eventHandler());
const bribeFactionsClass = (canBribe ? "a-link-button" : "a-link-button-inactive");
const bribeFactionsBtn = this.createButton({
class: bribeFactionsClass,
display: "inline-block",
onClick: bribeFactionsOnClick,
text: "Bribe Factions",
tooltip: (canBribe
? "Use your Corporations power and influence to bribe Faction leaders in exchange for reputation"
: "Your Corporation is not powerful enough to bribe Faction leaders"),
});
const generalBtns = {
bribeFactions: bribeFactionsBtn,
getStarterGuide: getStarterGuideBtn,
};
if (this.corp().public) {
return this.renderPublicButtons(generalBtns);
} else {
return this.renderPrivateButtons(generalBtns);
}
}
// Render the buttons for when your Corporation is still private
renderPrivateButtons(generalBtns) {
const fundingAvailable = (this.corp().fundingRound < 4);
const findInvestorsClassName = fundingAvailable ? "std-button" : "a-link-button-inactive";
const findInvestorsTooltip = fundingAvailable ? "Search for private investors who will give you startup funding in exchangefor equity (stock shares) in your company" : null;
const findInvestorsOnClick = this.corp().getInvestment.bind(this.corp());
const goPublicOnClick = this.corp().goPublic.bind(this.corp());
const findInvestorsBtn = this.createButton({
class: findInvestorsClassName,
onClick: findInvestorsOnClick,
style: "inline-block",
text: "Find Investors",
tooltip: findInvestorsTooltip,
});
const goPublicBtn = this.createButton({
class: "std-button",
onClick: goPublicOnClick,
style: "inline-block",
text: "Go Public",
tooltip: "Become a publicly traded and owned entity. Going public " +
"involves issuing shares for an IPO. Once you are a public " +
"company, your shares will be traded on the stock market.",
});
return (
<div>
{generalBtns.getStarterGuide}
{findInvestorsBtn}
{goPublicBtn}
<br />
{generalBtns.bribeFactions}
</div>
)
}
// Render the buttons for when your Corporation has gone public
renderPublicButtons(generalBtns) {
const corp = this.corp();
const sellSharesOnClick = this.eventHandler().createSellSharesPopup.bind(this.eventHandler());
const sellSharesOnCd = (corp.shareSaleCooldown > 0);
const sellSharesClass = sellSharesOnCd ? "a-link-button-inactive" : "std-button";
const sellSharesTooltip = sellSharesOnCd
? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown)
: "Sell your shares in the company. The money earned from selling your " +
"shares goes into your personal account, not the Corporation's. " +
"This is one of the only ways to profit from your business venture."
const sellSharesBtn = this.createButton({
class: sellSharesClass,
display: "inline-block",
onClick: function(event) {
if(!event.isTrusted) return;
sellSharesOnClick(event);
},
text: "Sell Shares",
tooltip: sellSharesTooltip,
});
const buybackSharesOnClick = this.eventHandler().createBuybackSharesPopup.bind(this.eventHandler());
const buybackSharesBtn = this.createButton({
class: "std-button",
display: "inline-block",
onClick: buybackSharesOnClick,
text: "Buyback shares",
tooltip: "Buy back shares you that previously issued or sold at market price.",
});
const issueNewSharesOnClick = this.eventHandler().createIssueNewSharesPopup.bind(this.eventHandler());
const issueNewSharesOnCd = (corp.issueNewSharesCooldown > 0);
const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button";
const issueNewSharesTooltip = issueNewSharesOnCd
? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown)
: "Issue new equity shares to raise capital.";
const issueNewSharesBtn = this.createButton({
class: issueNewSharesClass,
display: "inline-block",
onClick: issueNewSharesOnClick,
text: "Issue New Shares",
tooltip: issueNewSharesTooltip,
});
const issueDividendsOnClick = this.eventHandler().createIssueDividendsPopup.bind(this.eventHandler());
const issueDividendsBtn = this.createButton({
class: "std-button",
display: "inline-block",
onClick: issueDividendsOnClick,
text: "Issue Dividends",
tooltip: "Manage the dividends that are paid out to shareholders (including yourself)",
});
return (
<div>
{generalBtns.getStarterGuide}
{sellSharesBtn}
{buybackSharesBtn}
<br />
{issueNewSharesBtn}
{issueDividendsBtn}
<br />
{generalBtns.bribeFactions}
</div>
)
}
// Render the UI for Corporation upgrades
renderUpgrades() {
// Don't show upgrades
if (this.corp().divisions.length <= 0) { return; }
// Create an array of all Unlocks
const unlockUpgrades = [];
Object.values(CorporationUnlockUpgrades).forEach((unlockData) => {
if (this.corp().unlockUpgrades[unlockData[0]] === 0) {
unlockUpgrades.push(this.renderUnlockUpgrade(unlockData));
}
});
// Create an array of properties of all unlocks
const levelableUpgradeProps = [];
for (let i = 0; i < this.corp().upgrades.length; ++i) {
const upgradeData = CorporationUpgrades[i];
const level = this.corp().upgrades[i];
levelableUpgradeProps.push({
upgradeData: upgradeData,
upgradeLevel: level,
});
}
return (
<div className={"cmpy-mgmt-upgrade-container"}>
<h1 className={"cmpy-mgmt-upgrade-header"}> Unlocks </h1>
{unlockUpgrades}
<h1 className={"cmpy-mgmt-upgrade-header"}> Upgrades </h1>
{
levelableUpgradeProps.map((data) => {
return this.renderLevelableUpgrade(data);
})
}
</div>
)
}
renderUnlockUpgrade(data) {
return (
<UnlockUpgrade
{...this.props}
upgradeData={data}
key={data[0]}
/>
)
}
renderLevelableUpgrade(data) {
return (
<LevelableUpgrade
{...this.props}
upgradeData={data.upgradeData}
upgradeLevel={data.upgradeLevel}
key={data.upgradeData[0]}
/>
)
}
render() {
return (
<div>
<p dangerouslySetInnerHTML={{__html: this.getOverviewText()}}></p>
{this.renderButtons()}
<br />
{this.renderUpgrades()}
</div>
)
}
}

@ -0,0 +1,367 @@
// React Component for displaying Corporation Overview info
import React from "react";
import { LevelableUpgrade } from "./LevelableUpgrade";
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 { FindInvestorsPopup } from "./FindInvestorsPopup";
import { GoPublicPopup } from "./GoPublicPopup";
import { CorporationConstants } from "../data/Constants";
import {
CorporationUnlockUpgrade,
CorporationUnlockUpgrades } from "../data/CorporationUnlockUpgrades";
import {
CorporationUpgrade,
CorporationUpgrades } from "../data/CorporationUpgrades";
import { CONSTANTS } from "../../Constants";
import { numeralWrapper } from "../../ui/numeralFormat";
import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
import { createPopup } from "../../ui/React/createPopup";
import { Money } from "../../ui/React/Money";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { ICorporation } from "../ICorporation";
interface IProps {
corp: ICorporation;
player: IPlayer;
}
interface GeneralBtns {
bribeFactions: React.ReactElement;
}
export function Overview(props: IProps): React.ReactElement {
// Generic Function for Creating a button
interface ICreateButtonProps {
text: string;
class?: string;
className?: string;
display?: string;
tooltip?: string;
onClick?: (event: React.MouseEvent) => void;
}
function Button(props: ICreateButtonProps): React.ReactElement {
let className = props.className ? props.className : "std-button";
const hasTooltip = props.tooltip != null;
if(hasTooltip) className += " tooltip";
return (
<a
className={className}
onClick={props.onClick}
style={{display: props.display ? props.display : "block"}}>
{props.text}
{
hasTooltip &&
<span className={"tooltiptext"}>
{props.tooltip}
</span>
}
</a>
);
}
function createButton(props: ICreateButtonProps): React.ReactElement {
let className = props.class ? props.class : "std-button";
const displayStyle = props.display ? props.display : "block";
const hasTooltip = (props.tooltip != null);
if (hasTooltip) {
className += " tooltip";
}
return (
<a className={className} onClick={props.onClick} style={{display: displayStyle}}>
{props.text}
{
hasTooltip &&
<span className={"tooltiptext"}>
{props.tooltip}
</span>
}
</a>
)
}
function openBribeFactionPopup(): void {
const popupId = "corp-bribe-popup";
createPopup(popupId, BribeFactionPopup, {
player: props.player,
popupId: popupId,
corp: props.corp,
});
}
const profit: number = props.corp.revenue.minus(props.corp.expenses).toNumber();
function DividendsStats() {
if(props.corp.dividendPercentage <= 0 || profit <= 0) return (<></>);
const totalDividends = (props.corp.dividendPercentage / 100) * profit;
const retainedEarnings = profit - totalDividends;
const dividendsPerShare = totalDividends / props.corp.totalShares;
const playerEarnings = props.corp.numShares * dividendsPerShare;
return (<>
Retained Profits (after dividends): <Money money={retainedEarnings} /> / s<br /><br />
Dividend Percentage: {numeralWrapper.format(props.corp.dividendPercentage / 100, "0%")}<br />
Dividends per share: <Money money={dividendsPerShare} /> / s<br />
Your earnings as a shareholder (Pre-Tax): <Money money={playerEarnings} /> / s<br />
Dividend Tax Rate: {props.corp.dividendTaxPercentage}%<br />
Your earnings as a shareholder (Post-Tax): <Money money={playerEarnings * (1 - (props.corp.dividendTaxPercentage / 100))} /> / s<br /><br />
</>);
}
function Mult(props: {name: string, mult: number}): React.ReactElement {
if(props.mult <= 1) return (<></>);
return (<p>{props.name}{numeralWrapper.format(props.mult, "0.000")}<br /></p>);
}
// Returns a string with general information about Corporation
function BonusTime(): React.ReactElement {
const storedTime = props.corp.storedCycles * CONSTANTS.MilliPerCycle;
if (storedTime <= 15000) return (<></>);
return (<p>Bonus time: {convertTimeMsToTimeElapsedString(storedTime)}<br /><br /></p>);
}
function BribeButton(): React.ReactElement {
const canBribe = (props.corp.determineValuation() >= CorporationConstants.BribeThreshold) || true;
const bribeFactionsClass = (canBribe ? "a-link-button" : "a-link-button-inactive");
return <Button
className={bribeFactionsClass}
display="inline-block"
onClick={openBribeFactionPopup}
text="Bribe Factions"
tooltip={(canBribe
? "Use your Corporations power and influence to bribe Faction leaders in exchange for reputation"
: "Your Corporation is not powerful enough to bribe Faction leaders")}
/>
}
function openFindInvestorsPopup(): void {
const popupId = "cmpy-mgmt-find-investors-popup";
createPopup(popupId, FindInvestorsPopup, {
player: props.player,
popupId: popupId,
corp: props.corp,
});
}
function openGoPublicPopup(): void {
const popupId = "cmpy-mgmt-go-public-popup";
createPopup(popupId, GoPublicPopup, {
player: props.player,
popupId: popupId,
corp: props.corp,
});
}
// Render the buttons for when your Corporation is still private
function PrivateButtons(): React.ReactElement {
const fundingAvailable = (props.corp.fundingRound < 4);
const findInvestorsClassName = fundingAvailable ? "std-button" : "a-link-button-inactive";
const findInvestorsTooltip = fundingAvailable ? "Search for private investors who will give you startup funding in exchangefor equity (stock shares) in your company" : undefined;
return (
<>
<Button
className={findInvestorsClassName}
onClick={openFindInvestorsPopup}
text="Find Investors"
tooltip={findInvestorsTooltip}
display="inline-block"
/>
<Button
className="std-button"
onClick={openGoPublicPopup}
display="inline-block"
text="Go Public"
tooltip={"Become a publicly traded and owned entity. Going public " +
"involves issuing shares for an IPO. Once you are a public " +
"company, your shares will be traded on the stock market."}
/>
<br />
</>
)
}
function openSellSharesPopup(): void {
const popupId = "cmpy-mgmt-sell-shares-popup";
createPopup(popupId, SellSharesPopup, {
corp: props.corp,
player: props.player,
popupId: popupId,
});
}
function openBuybackSharesPopup(): void {
const popupId = "corp-buyback-shares-popup";
createPopup(popupId, BuybackSharesPopup, {
player: props.player,
popupId: popupId,
corp: props.corp,
});
}
function openIssueNewSharesPopup(): void {
const popupId = "cmpy-mgmt-issue-new-shares-popup";
createPopup(popupId, IssueNewSharesPopup, {
popupId: popupId,
corp: props.corp,
});
}
function openIssueDividendsPopup(): void {
const popupId = "cmpy-mgmt-issue-dividends-popup";
createPopup(popupId, IssueDividendsPopup, {
popupId: popupId,
corp: props.corp,
});
}
// Render the buttons for when your Corporation has gone public
function PublicButtons(): React.ReactElement {
const corp = props.corp;
const sellSharesOnCd = (corp.shareSaleCooldown > 0);
const sellSharesClass = sellSharesOnCd ? "a-link-button-inactive" : "std-button";
const sellSharesTooltip = sellSharesOnCd
? "Cannot sell shares for " + corp.convertCooldownToString(corp.shareSaleCooldown)
: "Sell your shares in the company. The money earned from selling your " +
"shares goes into your personal account, not the Corporation's. " +
"This is one of the only ways to profit from your business venture."
const issueNewSharesOnCd = (corp.issueNewSharesCooldown > 0);
const issueNewSharesClass = issueNewSharesOnCd ? "a-link-button-inactive" : "std-button";
const issueNewSharesTooltip = issueNewSharesOnCd
? "Cannot issue new shares for " + corp.convertCooldownToString(corp.issueNewSharesCooldown)
: "Issue new equity shares to raise capital.";
return (
<>
<Button
className={sellSharesClass}
display="inline-block"
onClick={openSellSharesPopup}
text="Sell Shares"
tooltip={sellSharesTooltip}
/>
<Button
className="std-button"
display="inline-block"
onClick={openBuybackSharesPopup}
text="Buyback shares"
tooltip="Buy back shares you that previously issued or sold at market price."
/>
<br />
<Button
className={issueNewSharesClass}
display="inline-block"
onClick={openIssueNewSharesPopup}
text="Issue New Shares"
tooltip={issueNewSharesTooltip}
/>
<Button
className="std-button"
display="inline-block"
onClick={openIssueDividendsPopup}
text="Issue Dividends"
tooltip="Manage the dividends that are paid out to shareholders (including yourself)"
/>
<br />
</>
)
}
// Render the UI for Corporation upgrades
function Upgrades(): React.ReactElement {
// Don't show upgrades
if (props.corp.divisions.length <= 0) { return (<></>); }
return (
<div className={"cmpy-mgmt-upgrade-container"}>
<h1 className={"cmpy-mgmt-upgrade-header"}> Unlocks </h1>
{
Object.values(CorporationUnlockUpgrades)
.filter((upgrade: CorporationUnlockUpgrade) => props.corp.unlockUpgrades[upgrade[0]] === 0)
.map((upgrade: CorporationUnlockUpgrade) =>
<UnlockUpgrade
player={props.player}
corp={props.corp}
upgradeData={upgrade}
key={upgrade[0]}
/>)
}
<h1 className={"cmpy-mgmt-upgrade-header"}> Upgrades </h1>
{
props.corp.upgrades
.map((level: number, i: number) => CorporationUpgrades[i])
.map((upgrade: CorporationUpgrade) => <LevelableUpgrade
player={props.player}
corp={props.corp}
upgrade={upgrade}
key={upgrade[0]}
/>,
)
}
</div>
);
}
return (
<div>
<p>
Total Funds: <Money money={props.corp.funds.toNumber()} /><br />
Total Revenue: <Money money={props.corp.revenue.toNumber()} /> / s<br />
Total Expenses: <Money money={props.corp.expenses.toNumber()} /> / s<br />
Total Profits: <Money money={profit} /> / s<br />
<DividendsStats />
Publicly Traded: {(props.corp.public ? "Yes" : "No")}<br />
Owned Stock Shares: {numeralWrapper.format(props.corp.numShares, '0.000a')}<br />
Stock Price: {(props.corp.public ? <Money money={props.corp.sharePrice} /> : "N/A")}<br />
</p>
<p className='tooltip'>
Total Stock Shares: {numeralWrapper.format(props.corp.totalShares, "0.000a")}
<span className='tooltiptext'>
Outstanding Shares: {numeralWrapper.format(props.corp.issuedShares, "0.000a")}<br />
Private Shares: {numeralWrapper.format(props.corp.totalShares - props.corp.issuedShares - props.corp.numShares, "0.000a")}
</span>
</p>
<br /><br />
<Mult name="Production Multiplier: " mult={props.corp.getProductionMultiplier()} />
<Mult name="Storage Multiplier: " mult={props.corp.getStorageMultiplier()} />
<Mult name="Advertising Multiplier: " mult={props.corp.getAdvertisingMultiplier()} />
<Mult name="Empl. Creativity Multiplier: " mult={props.corp.getEmployeeCreMultiplier()} />
<Mult name="Empl. Charisma Multiplier: " mult={props.corp.getEmployeeChaMultiplier()} />
<Mult name="Empl. Intelligence Multiplier: " mult={props.corp.getEmployeeIntMultiplier()} />
<Mult name="Empl. Efficiency Multiplier: " mult={props.corp.getEmployeeEffMultiplier()} />
<Mult name="Sales Multiplier: " mult={props.corp.getSalesMultiplier()} />
<Mult name="Scientific Research Multiplier: " mult={props.corp.getScientificResearchMultiplier()} />
<br />
<BonusTime />
<div>
<Button
className="a-link-button"
display="inline-block"
onClick={() => props.corp.getStarterGuide(props.player)}
text="Getting Started Guide"
tooltip={"Get a copy of and read 'The Complete Handbook for Creating a Successful Corporation.' " +
"This is a .lit file that guides you through the beginning of setting up a Corporation and " +
"provides some tips/pointers for helping you get started with managing it."}
/>
{ props.corp.public ?
<PublicButtons /> :
<PrivateButtons />
}
<BribeButton />
</div>
<br />
<Upgrades />
</div>
)
}

@ -0,0 +1,99 @@
import React, { useState } from 'react';
import { numeralWrapper } from "../../ui/numeralFormat";
import { IIndustry } from "../IIndustry";
import { Product } from "../Product";
interface IProps {
product: Product;
industry: IIndustry;
popupId: string;
}
function MarketTA2(props: IProps): React.ReactElement {
const markupLimit = props.product.rat / props.product.mku;
const [value, setValue] = useState(props.product.pCost);
const setRerender = useState(false)[1];
function rerender(): void {
setRerender(old => !old);
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
setValue(parseFloat(event.target.value));
}
function onCheckedChange(event: React.ChangeEvent<HTMLInputElement>): void {
props.product.marketTa2 = event.target.checked;
rerender();
}
const sCost = value;
let markup = 1;
if (sCost > props.product.pCost) {
if ((sCost - props.product.pCost) > markupLimit) {
markup = markupLimit / (sCost - props.product.pCost);
}
}
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" onChange={onChange} value={value} type="number" style={{marginTop: "4px"}} />
<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 Product will automatically
be sold at the optimal price such that the amount sold matches the
amount produced. (i.e. the highest possible price, while still ensuring
that all produced materials will be sold).
</span>
</label>
<input className="text-input" onChange={onCheckedChange} id="cmpy-mgmt-marketa2-checkbox" style={{margin: "3px"}} type="checkbox" checked={props.product.marketTa2} />
</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>
</>);
}
// Create a popup that lets the player use the Market TA research for Products
export function ProductMarketTaPopup(props: IProps): React.ReactElement {
const markupLimit = props.product.rat / props.product.mku;
const setRerender = useState(false)[1];
function rerender(): void {
setRerender(old => !old);
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
props.product.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.product.pCost + markupLimit)}.
This means that if you set the sale price higher than this,
you will begin to experience a loss in number of sales.
</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 Product will automatically
be sold at the price identified by Market-TA.I
(i.e. the price shown above).
</span>
</label>
<input onChange={onChange} className="text-input" id="cmpy-mgmt-marketa1-checkbox" style={{margin: "3px"}} type="checkbox" checked={props.product.marketTa1} />
</div>
{props.industry.hasResearch("Market-TA.II") && <MarketTA2 product={props.product} industry={props.industry} popupId={props.popupId} />}
</>);
}

@ -0,0 +1,130 @@
import React, { useState } from 'react';
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { MaterialSizes } from "../MaterialSizes";
import { Warehouse } from "../Warehouse";
import { Material } from "../Material";
import { IIndustry } from "../IIndustry";
import { ICorporation } from "../ICorporation";
import { numeralWrapper } from "../../ui/numeralFormat";
import { BuyMaterial } from "../Actions";
interface IBulkPurchaseTextProps {
warehouse: Warehouse;
mat: Material;
amount: string;
}
function BulkPurchaseText(props: IBulkPurchaseTextProps): React.ReactElement {
const parsedAmt = parseFloat(props.amount);
const cost = parsedAmt * props.mat.bCost;
const matSize = MaterialSizes[props.mat.name];
const maxAmount = ((props.warehouse.size - props.warehouse.sizeUsed) / matSize);
if (parsedAmt * matSize > maxAmount) {
return (<>Not enough warehouse space to purchase this amount</>);
} else if (isNaN(cost)) {
return (<>Invalid put for Bulk Purchase amount</>);
} else {
return (<>Purchasing {numeralWrapper.format(parsedAmt, "0,0.00")} of
{props.mat.name} will cost {numeralWrapper.formatMoney(cost)}</>);
}
}
interface IProps {
mat: Material;
industry: IIndustry;
warehouse: Warehouse;
corp: ICorporation;
popupId: string;
}
function BulkPurchase(props: IProps): React.ReactElement {
const [buyAmt, setBuyAmt] = useState('');
function bulkPurchase(): void {
const amount = parseFloat(buyAmt);
const matSize = MaterialSizes[props.mat.name];
const maxAmount = ((props.warehouse.size - props.warehouse.sizeUsed) / matSize);
if (amount * matSize > maxAmount) {
dialogBoxCreate(`You do not have enough warehouse size to fit this purchase`);
return;
}
if (isNaN(amount)) {
dialogBoxCreate("Invalid input amount");
} else {
const cost = amount * props.mat.bCost;
if (props.corp.funds.gt(cost)) {
props.corp.funds = props.corp.funds.minus(cost);
props.mat.qty += amount;
} else {
dialogBoxCreate(`You cannot afford this purchase.`);
return;
}
removePopup(props.popupId);
}
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) bulkPurchase();
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
setBuyAmt(event.target.value);
}
return (<>
<p>
Enter the amount of {props.mat.name} you would like
to bulk purchase. This purchases the specified amount instantly
(all at once).
</p>
<BulkPurchaseText warehouse={props.warehouse} mat={props.mat} amount={buyAmt} />
<input onChange={onChange} type="number" placeholder="Bulk Purchase amount" style={{margin: "5px"}} onKeyDown={onKeyDown} />
<button className="std-button">Confirm Bulk Purchase</button>
</>);
}
// Create a popup that lets the player purchase a Material
export function PurchaseMaterialPopup(props: IProps): React.ReactElement {
const [buyAmt, setBuyAmt] = useState(props.mat.buy ? props.mat.buy : null);
function purchaseMaterial(): void {
if(buyAmt === null) return;
try {
BuyMaterial(props.mat, buyAmt)
} catch(err) {
dialogBoxCreate(err+'');
}
removePopup(props.popupId);
}
function clearPurchase(): void {
props.mat.buy = 0;
removePopup(props.popupId);
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) purchaseMaterial();
}
function onChange(event: React.ChangeEvent<HTMLInputElement>): void {
setBuyAmt(parseFloat(event.target.value));
}
return (<>
<p>
Enter the amount of {props.mat.name} you would like
to purchase per second. This material's cost changes constantly.
</p>
<input onChange={onChange} className="text-input" autoFocus={true} placeholder="Purchase amount" type="number" style={{margin: "5px"}} onKeyDown={onKeyDown} />
<button onClick={purchaseMaterial} className="std-button">Confirm</button>
<button onClick={clearPurchase} className="std-button">Clear Purchase</button>
{props.industry.hasResearch("Bulk Purchasing") && <BulkPurchase corp={props.corp} mat={props.mat} industry={props.industry} warehouse={props.warehouse} popupId={props.popupId} />}
</>);
}

@ -0,0 +1,101 @@
import React, { useEffect } from 'react';
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createElement } from "../../../utils/uiHelpers/createElement";
import { removePopup } from "../../ui/React/createPopup";
import { IndustryResearchTrees } from "../IndustryData";
import { CorporationConstants } from "../data/Constants";
import { ResearchMap } from "../ResearchMap";
import { Treant } from 'treant-js';
import { IIndustry } from "../IIndustry";
interface IProps {
industry: IIndustry;
popupId: string;
}
// Create the Research Tree UI for this Industry
export function ResearchPopup(props: IProps): React.ReactElement {
useEffect(() => {
const researchTree = IndustryResearchTrees[props.industry.type];
if(researchTree === undefined) return;
// Get the tree's markup (i.e. config) for Treant
const markup = researchTree.createTreantMarkup();
markup.chart.container = "#" + props.popupId + "-content";
markup.chart.nodeAlign = "BOTTOM";
markup.chart.rootOrientation = "WEST";
markup.chart.siblingSeparation = 40;
markup.chart.connectors = {
type: "step",
style: {
"arrow-end": "block-wide-long",
"stroke": "white",
"stroke-width": 2,
},
}
Treant(markup);
// Add Event Listeners for all Nodes
const allResearch = researchTree.getAllNodes();
for (let i = 0; i < allResearch.length; ++i) {
// If this is already Researched, skip it
if (props.industry.researched[allResearch[i]] === true) {
continue;
}
// Get the Research object
const research = ResearchMap[allResearch[i]];
// Get the DOM Element to add a click listener to it
const sanitizedName = allResearch[i].replace(/\s/g, '');
const div = document.getElementById(sanitizedName + "-corp-research-click-listener");
if (div == null) {
console.warn(`Could not find Research Tree div for ${sanitizedName}`);
continue;
}
div.addEventListener("click", () => {
if (props.industry.sciResearch.qty >= research.cost) {
props.industry.sciResearch.qty -= research.cost;
// Get the Node from the Research Tree and set its 'researched' property
researchTree.research(allResearch[i]);
props.industry.researched[allResearch[i]] = true;
dialogBoxCreate(`Researched ${allResearch[i]}. It may take a market cycle ` +
`(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +
`the Research apply.`);
removePopup(props.popupId);
} else {
dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`);
}
});
}
const boxContent = document.getElementById(`${props.popupId}-content`);
if (boxContent != null) {
// Add information about multipliers from research at the bottom of the popup
//appendLineBreaks(boxContent, 2);
boxContent.appendChild(createElement("pre", {
display: "block",
innerText: `Multipliers from research:\n` +
` * Advertising Multiplier: x${researchTree.getAdvertisingMultiplier()}\n` +
` * Employee Charisma Multiplier: x${researchTree.getEmployeeChaMultiplier()}\n` +
` * Employee Creativity Multiplier: x${researchTree.getEmployeeCreMultiplier()}\n` +
` * Employee Efficiency Multiplier: x${researchTree.getEmployeeEffMultiplier()}\n` +
` * Employee Intelligence Multiplier: x${researchTree.getEmployeeIntMultiplier()}\n` +
` * Production Multiplier: x${researchTree.getProductionMultiplier()}\n` +
` * Sales Multiplier: x${researchTree.getSalesMultiplier()}\n` +
` * Scientific Research Multiplier: x${researchTree.getScientificResearchMultiplier()}\n` +
` * Storage Multiplier: x${researchTree.getStorageMultiplier()}`,
}));
}
});
return <div id={props.popupId}>
<div id={props.popupId+'outer-box'}></div>
</div>
}

@ -1,17 +0,0 @@
// Root React Component for the Corporation UI
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { HeaderTabs } from "./HeaderTabs";
import { MainPanel } from "./MainPanel";
export class CorporationRoot extends BaseReactComponent {
render() {
return (
<div>
<HeaderTabs {...this.props} />
<MainPanel {...this.props} />
</div>
)
}
}

@ -0,0 +1,23 @@
// Root React Component for the Corporation UI
import React from "react";
import { HeaderTabs } from "./HeaderTabs";
import { MainPanel } from "./MainPanel";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { ICorporation } from "../ICorporation";
import { CorporationRouting } from "./Routing";
interface IProps {
corp: ICorporation;
routing: CorporationRouting;
player: IPlayer;
}
export function CorporationRoot(props: IProps): React.ReactElement {
return (
<div>
<HeaderTabs corp={props.corp} routing={props.routing} player={props.player} />
<MainPanel corp={props.corp} routing={props.routing} player={props.player} />
</div>
)
}

@ -1,33 +1,8 @@
import { IMap } from "../../types";
import { ICorporation } from "../ICorporation";
import { IIndustry } from "../IIndustry";
export const overviewPage = "Overview";
// Interfaces for whatever's required to sanitize routing with Corporation Data
interface IOfficeSpace {
loc: string;
cost: number;
size: number;
comf: number;
beau: number;
tier: any;
minEne: number;
maxEne: number;
minHap: number;
maxHap: number;
maxMor: number;
employees: any;
employeeProd: any;
}
interface IDivision {
name: string;
offices: IMap<IOfficeSpace>;
}
interface ICorporation {
divisions: IDivision[];
}
/**
* Keeps track of what content is currently being displayed for the Corporation UI
*/
@ -39,7 +14,7 @@ export class CorporationRouting {
// Stores a reference to the Division instance that the routing is currently on
// This will be null if routing is on the overview page
currentDivision: IDivision | null = null;
currentDivision: IIndustry | null = null;
constructor(corp: ICorporation) {
this.corp = corp;

@ -0,0 +1,72 @@
import React, { useState } from 'react';
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { ICorporation } from "../ICorporation";
import { Material } from "../Material";
import { SellMaterial } from "../Actions";
function initialPrice(mat: Material): string {
let val = mat.sCost ? mat.sCost+'' : '';
if (mat.marketTa2) {
val += " (Market-TA.II)";
} else if (mat.marketTa1) {
val += " (Market-TA.I)";
}
return val;
}
interface IProps {
mat: Material;
corp: ICorporation;
popupId: string;
}
// Create a popup that let the player manage sales of a material
export function SellMaterialPopup(props: IProps): React.ReactElement {
const [amt, setAmt] = useState<string>(props.mat.sllman[1] ? props.mat.sllman[1]+'' : '');
const [price, setPrice] = useState<string>(initialPrice(props.mat));
function sellMaterial(): void {
try {
SellMaterial(props.mat, amt, price);
} catch(err) {
dialogBoxCreate(err+'');
}
removePopup(props.popupId);
}
function onAmtChange(event: React.ChangeEvent<HTMLInputElement>): void {
setAmt(event.target.value);
}
function onPriceChange(event: React.ChangeEvent<HTMLInputElement>): void {
setPrice(event.target.value);
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) sellMaterial();
}
return (<>
<p>
Enter the maximum amount of {props.mat.name} you would like
to sell per second, as well as the price at which you would
like to sell at.<br /><br />
If the sell amount is set to 0, then the material will not be sold. If the sell price
if set to 0, then the material will be discarded<br /><br />
Setting the sell amount to 'MAX' will result in you always selling the
maximum possible amount of the material.<br /><br />
When setting the sell amount, you can use the 'PROD' variable to designate a dynamically
changing amount that depends on your production. For example, if you set the sell amount
to 'PROD-5' then you will always sell 5 less of the material than you produce.<br /><br />
When setting the sell price, you can use the 'MP' variable to designate a dynamically
changing price that depends on the market price. For example, if you set the sell price
to 'MP+10' then it will always be sold at $10 above the market price.
</p>
<br />
<input className="text-input" value={amt} autoFocus={true} type="text" placeholder="Sell amount" style={{marginTop: "4px"}} onChange={onAmtChange} onKeyDown={onKeyDown} />
<input className="text-input" value={price} type="text" placeholder="Sell price" style={{marginTop: "4px"}} onChange={onPriceChange} onKeyDown={onKeyDown} />
<button className="std-button" onClick={sellMaterial}>Confirm</button>
</>);
}

@ -0,0 +1,83 @@
import React, { useState } from 'react';
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { Cities } from "../../Locations/Cities";
import { Product } from "../Product";
import { SellProduct } from "../Actions";
function initialPrice(product: Product): string {
let val = product.sCost ? product.sCost+'' : '';
if (product.marketTa2) {
val += " (Market-TA.II)";
} else if (product.marketTa1) {
val += " (Market-TA.I)";
}
return val;
}
interface IProps {
product: Product;
city: string;
popupId: string;
}
// Create a popup that let the player manage sales of a material
export function SellProductPopup(props: IProps): React.ReactElement {
const [checked, setChecked] = useState(true);
const [iQty, setQty] = useState<string>(props.product.sllman[props.city][1] ? props.product.sllman[props.city][1] : '');
const [px, setPx] = useState<string>(initialPrice(props.product));
function onCheckedChange(event: React.ChangeEvent<HTMLInputElement>): void {
setChecked(event.target.checked);
}
function sellProduct(): void {
try {
SellProduct(props.product, props.city, iQty, px, checked);
} catch(err) {
dialogBoxCreate(err+'');
}
removePopup(props.popupId);
}
function onAmtChange(event: React.ChangeEvent<HTMLInputElement>): void {
setQty(event.target.value);
}
function onPriceChange(event: React.ChangeEvent<HTMLInputElement>): void {
setPx(event.target.value);
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) sellProduct();
}
return (<>
<p>
Enter the maximum amount of {props.product.name} you would like
to sell per second, as well as the price at which you would like to
sell it at.<br /><br />
If the sell amount is set to 0, then the product will not be sold. If the
sell price is set to 0, then the product will be discarded.<br /><br />
Setting the sell amount to 'MAX' will result in you always selling the
maximum possible amount of the material.<br /><br />
When setting the sell amount, you can use the 'PROD' variable to designate a
dynamically changing amount that depends on your production. For example,
if you set the sell amount to 'PROD-1' then you will always sell 1 less of
the material than you produce.<br /><br />
When setting the sell price, you can use the 'MP' variable to set a
dynamically changing price that depends on the Product's estimated
market price. For example, if you set it to 'MP*5' then it
will always be sold at five times the estimated market price.
</p>
<br />
<input className="text-input" value={iQty} autoFocus={true} type="text" placeholder="Sell amount" style={{marginTop: "4px"}} onChange={onAmtChange} onKeyDown={onKeyDown} />
<input className="text-input" value={px} type="text" placeholder="Sell price" style={{marginTop: "4px"}} onChange={onPriceChange} onKeyDown={onKeyDown} />
<button className="std-button" onClick={sellProduct}>Confirm</button>
<div style={{border: "1px solid white", display: "inline-block"}}>
<label htmlFor={props.popupId+'-checkbox'}>Use same 'Sell Amount' for all cities</label>
<input checked={checked} onChange={onCheckedChange} id={props.popupId + "-checkbox"} style={{margin:"2px"}} type="checkbox" />
</div>
</>);
}

@ -0,0 +1,97 @@
import React, { useState } from 'react';
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { removePopup } from "../../ui/React/createPopup";
import { CorporationConstants } from "../data/Constants";
import { ICorporation } from "../ICorporation";
interface IProps {
corp: ICorporation;
player: IPlayer;
popupId: string;
}
// Create a popup that lets the player sell Corporation shares
// This is created when the player clicks the "Sell Shares" button in the overview panel
export function SellSharesPopup(props: IProps): React.ReactElement {
const [shares, setShares] = useState<number | null>(null);
function changeShares(event: React.ChangeEvent<HTMLInputElement>): void {
if(event.target.value === "") setShares(null);
else setShares(Math.round(parseFloat(event.target.value)));
}
function ProfitIndicator(props: {shares: number | null; corp: ICorporation}): React.ReactElement {
if(props.shares === null) return (<></>);
if (isNaN(props.shares) || props.shares <= 0) {
return (<>ERROR: Invalid value entered for number of shares to sell</>);
} else if (props.shares > props.corp.numShares) {
return (<>You don't have this many shares to sell!</>);
} else {
const stockSaleResults = props.corp.calculateShareSale(props.shares);
const profit = stockSaleResults[0];
return (<>Sell {props.shares} shares for a total of {numeralWrapper.formatMoney(profit)}</>);
}
}
function sell(): void {
if(shares === null) return;
if (isNaN(shares) || shares <= 0) {
dialogBoxCreate("ERROR: Invalid value for number of shares");
} else if (shares > props.corp.numShares) {
dialogBoxCreate("ERROR: You don't have this many shares to sell");
} else {
const stockSaleResults = props.corp.calculateShareSale(shares);
const profit = stockSaleResults[0];
const newSharePrice = stockSaleResults[1];
const newSharesUntilUpdate = stockSaleResults[2];
props.corp.numShares -= shares;
if (isNaN(props.corp.issuedShares)) {
console.error(`Corporation issuedShares is NaN: ${props.corp.issuedShares}`);
const res = props.corp.issuedShares;
if (isNaN(res)) {
props.corp.issuedShares = 0;
} else {
props.corp.issuedShares = res;
}
}
props.corp.issuedShares += shares;
props.corp.sharePrice = newSharePrice;
props.corp.shareSalesUntilPriceUpdate = newSharesUntilUpdate;
props.corp.shareSaleCooldown = CorporationConstants.SellSharesCooldown;
props.player.gainMoney(profit);
props.player.recordMoneySource(profit, "corporation");
removePopup(props.popupId);
dialogBoxCreate(`Sold ${numeralWrapper.formatMoney(shares)} shares for ` +
`${numeralWrapper.formatMoney(profit)}. ` +
`The corporation's stock price fell to ${numeralWrapper.formatMoney(props.corp.sharePrice)} ` +
`as a result of dilution.`);
props.corp.rerender(props.player);
}
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) sell();
}
return (<>
<p>
Enter the number of shares you would like to sell. The money from
selling your shares will go directly to you (NOT your Corporation).<br /><br />
Selling your shares will cause your corporation's stock price to fall due to
dilution. Furthermore, selling a large number of shares all at once will have an immediate effect
in reducing your stock price.<br /><br />
The current price of your
company's stock is {numeralWrapper.formatMoney(props.corp.sharePrice)}
</p>
<ProfitIndicator shares={shares} corp={props.corp} />
<br />
<input autoFocus={true} className="text-input" type="number" placeholder="Shares to sell" style={{margin: "5px"}} onChange={changeShares} onKeyDown={onKeyDown} />
<button onClick={sell} className="a-link-button" style={{display:"inline-block"}}>Sell shares</button>
</>);
}

@ -0,0 +1,58 @@
import React, { useState } from 'react';
import { removePopup } from "../../ui/React/createPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { OfficeSpace } from "../OfficeSpace";
import { ICorporation } from "../ICorporation";
interface IProps {
office: OfficeSpace;
corp: ICorporation;
popupId: string;
}
export function ThrowPartyPopup(props: IProps): React.ReactElement {
const [cost, setCost] = useState<number | null>(null);
function changeCost(event: React.ChangeEvent<HTMLInputElement>): void {
setCost(parseFloat(event.target.value));
}
function throwParty(): void {
if (cost === null || isNaN(cost) || cost < 0) {
dialogBoxCreate("Invalid value entered");
} else {
const totalCost = cost * props.office.employees.length;
if (props.corp.funds.lt(totalCost)) {
dialogBoxCreate("You don't have enough company funds to throw a party!");
} else {
props.corp.funds = props.corp.funds.minus(totalCost);
let mult = 0;
for (let i = 0; i < props.office.employees.length; ++i) {
mult = props.office.employees[i].throwParty(cost);
}
dialogBoxCreate("You threw a party for the office! The morale and happiness " +
"of each employee increased by " + numeralWrapper.formatPercentage((mult-1)));
removePopup(props.popupId);
}
}
}
function EffectText(props: {cost: number | null; office: OfficeSpace}): React.ReactElement {
let cost = props.cost;
if(cost !== null && (isNaN(cost) || cost < 0)) return <p>Invalid value entered!</p>
if(cost === null) cost = 0;
return <p>Throwing this party will cost a total of {numeralWrapper.formatMoney(cost * props.office.employees.length)}</p>
}
function onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
if (event.keyCode === 13) throwParty();
}
return (<>
<p>Enter the amount of money you would like to spend PER EMPLOYEE on this office party</p>
<EffectText cost={cost} office={props.office} />
<input autoFocus={true} className="text-input" type="number" style={{margin: "5px"}} placeholder="$ / employee" onChange={changeCost} onKeyDown={onKeyDown} />
<button className="std-button" onClick={throwParty}>Throw Party</button>
</>);
}

@ -1,30 +0,0 @@
// React Components for the Unlock upgrade buttons on the overview page
import React from "react";
import { BaseReactComponent } from "./BaseReactComponent";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
export class UnlockUpgrade extends BaseReactComponent {
render() {
const data = this.props.upgradeData;
const text = `${data[2]} - ${numeralWrapper.formatMoney(data[1])}`;
const tooltip = data[3];
const onClick = () => {
const corp = this.corp();
if (corp.funds.lt(data[1])) {
dialogBoxCreate("Insufficient funds");
} else {
corp.unlock(data);
corp.rerender();
}
}
return (
<div className={"cmpy-mgmt-upgrade-div tooltip"} style={{"width" : "45%"}} onClick={onClick}>
{text}
<span className={"tooltiptext"}>{tooltip}</span>
</div>
)
}
}

@ -0,0 +1,37 @@
// React Components for the Unlock upgrade buttons on the overview page
import React from "react";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { CorporationUnlockUpgrade } from "../data/CorporationUnlockUpgrades";
import { ICorporation } from "../ICorporation";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { UnlockUpgrade as UU } from "../Actions";
import { Money } from "../../ui/React/Money";
interface IProps {
upgradeData: CorporationUnlockUpgrade;
corp: ICorporation;
player: IPlayer;
}
export function UnlockUpgrade(props: IProps): React.ReactElement {
const data = props.upgradeData;
const text = <>{data[2]} - <Money money={data[1]} /></>;
const tooltip = data[3];
function onClick(): void {
try {
UU(props.corp, props.upgradeData);
} catch(err) {
dialogBoxCreate(err+'');
}
props.corp.rerender(props.player);
}
return (
<div className={"cmpy-mgmt-upgrade-div tooltip"} style={{"width" : "45%"}} onClick={onClick}>
{text}
<span className={"tooltiptext"}>{tooltip}</span>
</div>
)
}

@ -0,0 +1,80 @@
import React from "react";
import { removePopup } from "../../ui/React/createPopup";
import { numeralWrapper } from "../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { CorporationConstants } from "../data/Constants";
import { OfficeSpace } from "../OfficeSpace";
import { ICorporation } from "../ICorporation";
import { IPlayer } from "../../PersonObjects/IPlayer";
interface IProps {
office: OfficeSpace;
corp: ICorporation;
popupId: string;
player: IPlayer;
}
export function UpgradeOfficeSizePopup(props: IProps): React.ReactElement {
const initialPriceMult = Math.round(props.office.size / CorporationConstants.OfficeInitialSize);
const costMultiplier = 1.09;
const upgradeCost = CorporationConstants.OfficeInitialCost * Math.pow(costMultiplier, initialPriceMult);
// Calculate cost to upgrade size by 15 employees
let mult = 0;
for (let i = 0; i < 5; ++i) {
mult += (Math.pow(costMultiplier, initialPriceMult + i));
}
const upgradeCost15 = CorporationConstants.OfficeInitialCost * mult;
//Calculate max upgrade size and cost
const maxMult = (props.corp.funds.dividedBy(CorporationConstants.OfficeInitialCost)).toNumber();
let maxNum = 1;
mult = Math.pow(costMultiplier, initialPriceMult);
while(maxNum < 50) { //Hard cap of 50x (extra 150 employees)
if (mult >= maxMult) break;
const multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum);
if (mult + multIncrease > maxMult) {
break;
} else {
mult += multIncrease;
}
++maxNum;
}
const upgradeCostMax = CorporationConstants.OfficeInitialCost * mult;
function upgradeSize(cost: number, size: number): void {
if (props.corp.funds.lt(cost)) {
dialogBoxCreate("You don't have enough company funds to purchase this upgrade!");
} else {
props.office.size += size;
props.corp.funds = props.corp.funds.minus(cost);
dialogBoxCreate("Office space increased! It can now hold " + props.office.size + " employees");
props.corp.rerender(props.player);
}
removePopup(props.popupId);
}
interface IUpgradeButton {
cost: number;
size: number;
corp: ICorporation;
}
function UpgradeSizeButton(props: IUpgradeButton): React.ReactElement {
return (<button
className={"tooltip "+(props.corp.funds.lt(props.cost) ? "a-link-button-inactive" : "a-link-button")}
style={{display:"inline-block", margin:"4px"}}
onClick={() => upgradeSize(props.cost, props.size)}
>by {props.size}
<span className="tooltiptext">{numeralWrapper.formatMoney(props.cost)}</span>
</button>);
}
return (<>
<p>Increase the size of your office space to fit additional employees!</p>
<p>Upgrade size: </p>
<UpgradeSizeButton corp={props.corp} cost={upgradeCost} size={CorporationConstants.OfficeInitialSize} />
<UpgradeSizeButton corp={props.corp} cost={upgradeCost15} size={CorporationConstants.OfficeInitialSize * 5} />
<UpgradeSizeButton corp={props.corp} cost={upgradeCostMax} size={maxNum*CorporationConstants.OfficeInitialSize} />
</>);
}

@ -584,6 +584,20 @@ class DevMenuComponent extends Component {
}
}
finishCorporationProducts() {
if(!Player.corporation) return;
Player.corporation.divisions.forEach(div => {
Object.keys(div.products).forEach(prod => div.products[prod].prog = 99.9)
});
}
addCorporationResearch() {
if(!Player.corporation) return;
Player.corporation.divisions.forEach(div => {
div.sciResearch.qty += 1e10;
});
}
specificContract() {
generateContract({
problemType: this.state.codingcontract,
@ -1169,6 +1183,16 @@ class DevMenuComponent extends Component {
/>
</td>
</tr>
<tr>
<td>
<button className="std-button" onClick={this.finishCorporationProducts}>Finish products</button>
</td>
</tr>
<tr>
<td>
<button className="std-button" onClick={this.addCorporationResearch}>Tons of research</button>
</td>
</tr>
</tbody>
</table>
</div>

@ -1,3 +1,3 @@
export declare function iTutorialNextStep(): void;
export declare const ITutorial: {isRunning: boolean, currStep: number};
export declare const ITutorial: {isRunning: boolean; currStep: number};
export declare const iTutorialSteps: {[key: string]: number};

@ -20,6 +20,18 @@ import { CompanyPosition } from "./Company/CompanyPosition";
import { CompanyPositions } from "./Company/CompanyPositions";
import { CONSTANTS } from "./Constants";
import { DarkWebItems } from "./DarkWeb/DarkWebItems";
import {
NewIndustry,
NewCity,
UnlockUpgrade,
LevelUpgrade,
IssueDividends,
SellMaterial,
SellProduct,
SetSmartSupply,
BuyMaterial } from "./Corporation/Actions";
import { CorporationUnlockUpgrades } from "./Corporation/data/CorporationUnlockUpgrades";
import { CorporationUpgrades } from "./Corporation/data/CorporationUpgrades";
import {
calculateHackingChance,
calculateHackingExpGain,
@ -555,6 +567,39 @@ function NetscriptFunctions(workerScript) {
return Augmentations[name];
}
function getDivision(divisionName) {
const division = Player.corporation.divisions.find(div => div.name === divisionName);
if(division === undefined)
throw new Error(`No division named '${divisionName}'`);
return division;
}
function getWarehouse(divisionName, cityName) {
const division = getDivision(divisionName);
if(!(cityName in division.warehouses))
throw new Error(`Invalid city name '${cityName}'`);
const warehouse = division.warehouses[cityName];
if(warehouse === 0)
throw new Error(`${division.name} has not expanded to '${cityName}'`);
return warehouse;
}
function getMaterial(divisionName, cityName, materialName) {
const warehouse = getWarehouse(divisionName, cityName);
const material = warehouse.materials[materialName];
if(material === undefined)
throw new Error(`Invalid material name: '${materialName}'`);
return material;
}
function getProduct(divisionName, productName) {
const division = getDivision(divisionName);
const product = division.products[productName];
if(product === undefined)
throw new Error(`Invalid product name: '${productName}'`);
return product;
}
const runAfterReset = function(cbScript=null) {
//Run a script after reset
if (cbScript && isString(cbScript)) {
@ -4097,6 +4142,46 @@ function NetscriptFunctions(workerScript) {
},
}, // End Bladeburner
// corporation: {
// expandIndustry: function(industryName, divisionName) {
// NewIndustry(Player.corporation, industryName, divisionName);
// },
// expandCity: function(divisionName, cityName) {
// const division = getDivision(divisionName);
// NewCity(Player.corporation, division, cityName);
// },
// unlockUpgrade: function(upgradeName) {
// const upgrade = Object.values(CorporationUnlockUpgrades).
// find(upgrade => upgrade[2] === upgradeName);
// if(upgrade === undefined) throw new Error("No upgrade named '${upgradeName}'")
// UnlockUpgrade(Player.corporation, upgrade);
// },
// levelUpgrade: function(upgradeName) {
// const upgrade = Object.values(CorporationUpgrades).
// find(upgrade => upgrade[4] === upgradeName);
// if(upgrade === undefined) throw new Error("No upgrade named '${upgradeName}'")
// LevelUpgrade(Player.corporation, upgrade);
// },
// issueDividends: function(percent) {
// IssueDividends(Player.corporation, percent);
// },
// sellMaterial: function(divisionName, cityName, materialName, amt, price) {
// const material = getMaterial(divisionName, cityName, materialName);
// SellMaterial(material, amt, price);
// },
// sellProduct: function(divisionName, cityName, productName, amt, price, all) {
// const product = getProduct(divisionName, productName);
// SellProduct(product, cityName, amt, price, all);
// },
// setSmartSupply: function(divisionName, cityName, enabled) {
// const warehouse = getWarehouse(divisionName, cityName);
// SetSmartSupply(warehouse, enabled);
// },
// BuyMaterial: function(divisionName, cityName, materialName, amt) {
// },
// }, // End Corporation API
// Coding Contract API
codingcontract: {
attempt: function(answer, fn, ip=workerScript.serverIp, { returnReward } = {}) {

@ -19,7 +19,8 @@ import { LocationName } from "../Locations/data/LocationNames";
import { Server } from "../Server/Server";
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
import { Exploit } from "../Exploits/Exploit";
import { Exploit } from "../Exploits/Exploit";
import { ICorporation } from "../Corporation/ICorporation";
export interface IPlayer {
// Class members
@ -28,7 +29,7 @@ export interface IPlayer {
bitNodeN: number;
city: CityName;
companyName: string;
corporation: any;
corporation: ICorporation;
currentServer: string;
factions: string[];
factionInvitations: string[];

2171
src/ThirdParty/Treant.js vendored

File diff suppressed because it is too large Load Diff

1
src/ThirdParty/decimal.js.d.ts vendored Normal file

@ -0,0 +1 @@
declare module "decimal.js";

1
src/ThirdParty/treant-js.d.ts vendored Normal file

@ -0,0 +1 @@
declare module "treant-js";

@ -454,7 +454,7 @@ const Engine = {
if (Player.corporation instanceof Corporation) {
Engine.hideAllContent();
routing.navigateTo(Page.Corporation);
Player.corporation.createUI();
Player.corporation.createUI(Player);
}
},
@ -537,7 +537,7 @@ const Engine = {
}
if (Player.corporation instanceof Corporation) {
Player.corporation.clearUI();
Player.corporation.clearUI(Player);
}
clearResleevesPage();
@ -856,7 +856,7 @@ const Engine = {
if (Engine.Counters.mechanicProcess <= 0) {
if (Player.corporation instanceof Corporation) {
Player.corporation.process();
Player.corporation.process(Player);
}
if (Player.bladeburner instanceof Bladeburner) {
try {

@ -641,6 +641,5 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<!-- Misc Scripts -->
<script src="src/ThirdParty/raphael.min.js"></script>
<script src="src/ThirdParty/Treant.js"></script>
</html>