mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-22 15:43:49 +01:00
commit
2d322e7a6a
4
dist/engine.bundle.js
vendored
4
dist/engine.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
dist/engineStyle.bundle.js
vendored
2
dist/engineStyle.bundle.js
vendored
@ -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
|
//# sourceMappingURL=engineStyle.bundle.js.map
|
32
dist/vendor.bundle.js
vendored
32
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
@ -628,6 +628,5 @@
|
|||||||
|
|
||||||
<!-- Misc Scripts -->
|
<!-- Misc Scripts -->
|
||||||
<script src="src/ThirdParty/raphael.min.js"></script>
|
<script src="src/ThirdParty/raphael.min.js"></script>
|
||||||
<script src="src/ThirdParty/Treant.js"></script>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
11
package-lock.json
generated
11
package-lock.json
generated
@ -48,6 +48,7 @@
|
|||||||
"react-modal": "^3.12.1",
|
"react-modal": "^3.12.1",
|
||||||
"sprintf-js": "^1.1.1",
|
"sprintf-js": "^1.1.1",
|
||||||
"tapable": "^1.0.0",
|
"tapable": "^1.0.0",
|
||||||
|
"treant-js": "^1.0.1",
|
||||||
"uuid": "^3.2.1",
|
"uuid": "^3.2.1",
|
||||||
"w3c-blob": "0.0.1"
|
"w3c-blob": "0.0.1"
|
||||||
},
|
},
|
||||||
@ -21630,6 +21631,11 @@
|
|||||||
"node": ">=6"
|
"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": {
|
"node_modules/trim": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
|
"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": {
|
"trim": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
"react-modal": "^3.12.1",
|
"react-modal": "^3.12.1",
|
||||||
"sprintf-js": "^1.1.1",
|
"sprintf-js": "^1.1.1",
|
||||||
"tapable": "^1.0.0",
|
"tapable": "^1.0.0",
|
||||||
|
"treant-js": "^1.0.1",
|
||||||
"uuid": "^3.2.1",
|
"uuid": "^3.2.1",
|
||||||
"w3c-blob": "0.0.1"
|
"w3c-blob": "0.0.1"
|
||||||
},
|
},
|
||||||
|
228
src/Corporation/Actions.ts
Normal file
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
434
src/Corporation/Corporation.tsx
Normal file
434
src/Corporation/Corporation.tsx
Normal file
@ -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
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;
|
5
src/Corporation/Export.ts
Normal file
5
src/Corporation/Export.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface Export {
|
||||||
|
ind: string;
|
||||||
|
city: string;
|
||||||
|
amt: string;
|
||||||
|
}
|
61
src/Corporation/ICorporation.ts
Normal file
61
src/Corporation/ICorporation.ts
Normal file
@ -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;
|
||||||
|
}
|
77
src/Corporation/IIndustry.ts
Normal file
77
src/Corporation/IIndustry.ts
Normal file
@ -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
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();
|
|
||||||
}
|
|
141
src/Corporation/IndustryData.tsx
Normal file
141
src/Corporation/IndustryData.tsx
Normal file
@ -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";
|
import { IMap } from "../types";
|
||||||
|
|
||||||
|
export type IndustryUpgrade = [number, number, number, number, string, string];
|
||||||
|
|
||||||
// Industry upgrades
|
// Industry upgrades
|
||||||
// The data structure is an array with the following format:
|
// The data structure is an array with the following format:
|
||||||
// [index in array, base price, price mult, benefit mult (if applicable), name, desc]
|
// [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,
|
"0": [0, 500e3, 1, 1.05,
|
||||||
"Coffee", "Provide your employees with coffee, increasing their energy by 5%."],
|
"Coffee", "Provide your employees with coffee, increasing their energy by 5%."],
|
||||||
"1": [1, 1e9, 1.06, 1.03,
|
"1": [1, 1e9, 1.06, 1.03,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Generic_fromJSON,
|
import { Generic_fromJSON,
|
||||||
Generic_toJSON,
|
Generic_toJSON,
|
||||||
Reviver } from "../../utils/JSONReviver";
|
Reviver } from "../../utils/JSONReviver";
|
||||||
|
import { Export } from "./Export";
|
||||||
|
|
||||||
interface IConstructorParams {
|
interface IConstructorParams {
|
||||||
name?: string;
|
name?: string;
|
||||||
@ -43,7 +44,7 @@ export class Material {
|
|||||||
imp = 0;
|
imp = 0;
|
||||||
|
|
||||||
// Exports of this material to another warehouse/industry
|
// Exports of this material to another warehouse/industry
|
||||||
exp: any[] = [];
|
exp: Export[] = [];
|
||||||
|
|
||||||
// Total amount of this material exported in the last cycle
|
// Total amount of this material exported in the last cycle
|
||||||
totalExp = 0;
|
totalExp = 0;
|
||||||
@ -52,12 +53,12 @@ export class Material {
|
|||||||
bCost = 0;
|
bCost = 0;
|
||||||
|
|
||||||
// Cost / sec to sell this material
|
// 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
|
// Flags to keep track of whether production and/or sale of this material is limited
|
||||||
// [Whether production/sale is limited, limit amount]
|
// [Whether production/sale is limited, limit amount]
|
||||||
prdman: any[] = [false, 0]; // Production
|
prdman: [boolean, number] = [false, 0]; // Production
|
||||||
sllman: any[] = [false, 0]; // Sale
|
sllman: [boolean, string | number] = [false, 0]; // Sale
|
||||||
|
|
||||||
// Flags that signal whether automatic sale pricing through Market TA is enabled
|
// Flags that signal whether automatic sale pricing through Market TA is enabled
|
||||||
marketTa1 = false;
|
marketTa1 = false;
|
||||||
|
198
src/Corporation/OfficeSpace.ts
Normal file
198
src/Corporation/OfficeSpace.ts
Normal file
@ -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 { EmployeePositions } from "./EmployeePositions";
|
||||||
import { MaterialSizes } from "./MaterialSizes";
|
import { MaterialSizes } from "./MaterialSizes";
|
||||||
|
import { IIndustry } from "./IIndustry";
|
||||||
import { ProductRatingWeights,
|
import { ProductRatingWeights,
|
||||||
IProductRatingWeight } from "./ProductRatingWeights";
|
IProductRatingWeight } from "./ProductRatingWeights";
|
||||||
|
|
||||||
@ -30,17 +31,6 @@ interface IConstructorParams {
|
|||||||
req?: IMap<number>;
|
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 {
|
export class Product {
|
||||||
|
|
||||||
// Product name
|
// Product name
|
||||||
@ -60,7 +50,7 @@ export class Product {
|
|||||||
pCost = 0;
|
pCost = 0;
|
||||||
|
|
||||||
// Sell cost
|
// Sell cost
|
||||||
sCost = 0;
|
sCost: string | number = 0;
|
||||||
|
|
||||||
// Variables for handling the creation process of this Product
|
// Variables for handling the creation process of this Product
|
||||||
fin = false; // Whether this Product has finished being created
|
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
|
// @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;
|
this.fin = true;
|
||||||
|
|
||||||
//Calculate properties
|
//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 now, just set it to be the same as the requirements to make materials
|
||||||
for (const matName in industry.reqMats) {
|
for (const matName in industry.reqMats) {
|
||||||
if (industry.reqMats.hasOwnProperty(matName)) {
|
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
|
//For now, just set it to be the same size as the requirements to make materials
|
||||||
this.siz = 0;
|
this.siz = 0;
|
||||||
for (const matName in industry.reqMats) {
|
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 { Material } from "./Material";
|
||||||
|
import { ICorporation } from "./ICorporation";
|
||||||
|
import { IIndustry } from "./IIndustry";
|
||||||
import { MaterialSizes } from "./MaterialSizes";
|
import { MaterialSizes } from "./MaterialSizes";
|
||||||
import { IMap } from "../types";
|
import { IMap } from "../types";
|
||||||
import { numeralWrapper } from "../ui/numeralFormat";
|
import { numeralWrapper } from "../ui/numeralFormat";
|
||||||
@ -7,13 +9,9 @@ import { Generic_fromJSON,
|
|||||||
Reviver } from "../../utils/JSONReviver";
|
Reviver } from "../../utils/JSONReviver";
|
||||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||||
|
|
||||||
interface IParent {
|
|
||||||
getStorageMultiplier(): number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IConstructorParams {
|
interface IConstructorParams {
|
||||||
corp?: IParent;
|
corp?: ICorporation;
|
||||||
industry?: IParent;
|
industry?: IIndustry;
|
||||||
loc?: string;
|
loc?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
}
|
}
|
||||||
@ -92,7 +90,7 @@ export class Warehouse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSize(corporation: IParent, industry: IParent): void {
|
updateSize(corporation: ICorporation, industry: IIndustry): void {
|
||||||
try {
|
try {
|
||||||
this.size = (this.level * 100)
|
this.size = (this.level * 100)
|
||||||
* corporation.getStorageMultiplier()
|
* corporation.getStorageMultiplier()
|
||||||
|
60
src/Corporation/data/Constants.ts
Normal file
60
src/Corporation/data/Constants.ts
Normal file
@ -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";
|
import { IMap } from "../../types";
|
||||||
|
|
||||||
|
export type CorporationUnlockUpgrade = [number, number, string, string];
|
||||||
|
|
||||||
// Corporation Unlock Upgrades
|
// Corporation Unlock Upgrades
|
||||||
// Upgrades for entire corporation, unlocks features, either you have it or you dont
|
// Upgrades for entire corporation, unlocks features, either you have it or you dont
|
||||||
// The data structure is an array with the following format:
|
// The data structure is an array with the following format:
|
||||||
// [index in Corporation feature upgrades array, price, name, description]
|
// [index in Corporation feature upgrades array, price, name, description]
|
||||||
export const CorporationUnlockUpgrades: IMap<any[]> = {
|
export const CorporationUnlockUpgrades: IMap<CorporationUnlockUpgrade> = {
|
||||||
//Lets you export goods
|
//Lets you export goods
|
||||||
"0": [0, 20e9, "Export",
|
"0": [0, 20e9, "Export",
|
||||||
"Develop infrastructure to export your materials to your other facilities. " +
|
"Develop infrastructure to export your materials to your other facilities. " +
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { IMap } from "../../types";
|
import { IMap } from "../../types";
|
||||||
|
|
||||||
|
export type CorporationUpgrade = [number, number, number, number, string, string];
|
||||||
|
|
||||||
// Corporation Upgrades
|
// Corporation Upgrades
|
||||||
// Upgrades for entire corporation, levelable upgrades
|
// Upgrades for entire corporation, levelable upgrades
|
||||||
// The data structure is an array with the following format
|
// The data structure is an array with the following format
|
||||||
// [index in Corporation upgrades array, base price, price mult, benefit mult (additive), name, desc]
|
// [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
|
//Smart factories, increases production
|
||||||
"0": [0, 2e9, 1.06, 0.03,
|
"0": [0, 2e9, 1.06, 0.03,
|
||||||
"Smart Factories", "Advanced AI automatically optimizes the operation and productivity " +
|
"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;
|
|
||||||
}
|
|
||||||
}
|
|
88
src/Corporation/ui/BribeFactionPopup.tsx
Normal file
88
src/Corporation/ui/BribeFactionPopup.tsx
Normal file
@ -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>
|
||||||
|
</>);
|
||||||
|
}
|
87
src/Corporation/ui/BuybackSharesPopup.tsx
Normal file
87
src/Corporation/ui/BuybackSharesPopup.tsx
Normal file
@ -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>
|
||||||
|
</>);
|
||||||
|
}
|
20
src/Corporation/ui/CityTab.tsx
Normal file
20
src/Corporation/ui/CityTab.tsx
Normal file
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
45
src/Corporation/ui/CityTabs.tsx
Normal file
45
src/Corporation/ui/CityTabs.tsx
Normal file
@ -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
32
src/Corporation/ui/DiscontinueProductPopup.tsx
Normal file
32
src/Corporation/ui/DiscontinueProductPopup.tsx
Normal file
@ -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>
|
||||||
|
</>);
|
||||||
|
}
|
50
src/Corporation/ui/ExpandNewCityPopup.tsx
Normal file
50
src/Corporation/ui/ExpandNewCityPopup.tsx
Normal file
@ -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>
|
||||||
|
</>);
|
||||||
|
}
|
116
src/Corporation/ui/ExportPopup.tsx
Normal file
116
src/Corporation/ui/ExportPopup.tsx
Normal file
@ -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>)
|
||||||
|
}
|
||||||
|
</>);
|
||||||
|
}
|
59
src/Corporation/ui/FindInvestorsPopup.tsx
Normal file
59
src/Corporation/ui/FindInvestorsPopup.tsx
Normal file
@ -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>
|
||||||
|
</>);
|
||||||
|
}
|
63
src/Corporation/ui/GoPublicPopup.tsx
Normal file
63
src/Corporation/ui/GoPublicPopup.tsx
Normal file
@ -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>
|
||||||
|
</>);
|
||||||
|
}
|
20
src/Corporation/ui/HeaderTab.tsx
Normal file
20
src/Corporation/ui/HeaderTab.tsx
Normal file
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
61
src/Corporation/ui/HeaderTabs.tsx
Normal file
61
src/Corporation/ui/HeaderTabs.tsx
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
137
src/Corporation/ui/HireEmployeePopup.tsx
Normal file
137
src/Corporation/ui/HireEmployeePopup.tsx
Normal file
@ -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>
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
43
src/Corporation/ui/Industry.tsx
Normal file
43
src/Corporation/ui/Industry.tsx
Normal file
@ -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
|
// React Component for displaying an Industry's OfficeSpace information
|
||||||
// (bottom-left panel in the Industry UI)
|
// (bottom-left panel in the Industry UI)
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { BaseReactComponent } from "./BaseReactComponent";
|
|
||||||
|
|
||||||
import { OfficeSpace } from "../Corporation";
|
import { OfficeSpace } from "../OfficeSpace";
|
||||||
|
import { Employee } from "../Employee";
|
||||||
import { EmployeePositions } from "../EmployeePositions";
|
import { EmployeePositions } from "../EmployeePositions";
|
||||||
|
|
||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
|
||||||
import { getSelectText } from "../../../utils/uiHelpers/getSelectData";
|
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 {
|
interface IProps {
|
||||||
constructor(props) {
|
routing: CorporationRouting;
|
||||||
super(props);
|
corp: ICorporation;
|
||||||
|
currentCity: string;
|
||||||
|
player: IPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
export function IndustryOffice(props: IProps): React.ReactElement {
|
||||||
city: "",
|
const [employeeManualAssignMode, setEmployeeManualAssignMode] = useState(false);
|
||||||
division: "",
|
const [city, setCity] = useState("");
|
||||||
employeeManualAssignMode: false,
|
const [divisionName, setDivisionName] = useState("");
|
||||||
employee: null, // Reference to employee being referenced if in Manual Mode
|
const [employee, setEmployee] = useState<Employee | null>(null);
|
||||||
numEmployees: 0,
|
const [numEmployees, setNumEmployees] = useState(0);
|
||||||
numOperations: 0,
|
const [numOperations, setNumOperations] = useState(0);
|
||||||
numEngineers: 0,
|
const [numEngineers, setNumEngineers] = useState(0);
|
||||||
numBusiness: 0,
|
const [numBusiness, setNumBusiness] = useState(0);
|
||||||
numManagement: 0,
|
const [numManagement, setNumManagement] = useState(0);
|
||||||
numResearch: 0,
|
const [numResearch, setNumResearch] = useState(0);
|
||||||
numUnassigned: 0,
|
const [numUnassigned, setNumUnassigned] = useState(0);
|
||||||
numTraining: 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() {
|
function updateEmployeeCount(): void {
|
||||||
this.state.numEmployees = 0;
|
const division = props.routing.currentDivision;
|
||||||
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;
|
|
||||||
if (division == null) {
|
if (division == null) {
|
||||||
throw new Error(`Routing does not hold reference to the current Industry`);
|
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)) {
|
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 we're in a new city, we have to reset the state
|
||||||
if (division.name !== this.state.division || this.props.currentCity !== this.state.city) {
|
if (division.name !== divisionName || props.currentCity !== city) {
|
||||||
this.resetEmployeeCount();
|
resetEmployeeCount();
|
||||||
this.state.division = division.name;
|
setDivisionName(division.name);
|
||||||
this.state.city = this.props.currentCity;
|
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;
|
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
|
// 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) {
|
switch (office.employees[i].pos) {
|
||||||
case EmployeePositions.Operations:
|
case EmployeePositions.Operations:
|
||||||
++this.state.numOperations;
|
newOperations++;
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Engineer:
|
case EmployeePositions.Engineer:
|
||||||
++this.state.numEngineers;
|
newEngineers++;
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Business:
|
case EmployeePositions.Business:
|
||||||
++this.state.numBusiness;
|
newBusiness++;
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Management:
|
case EmployeePositions.Management:
|
||||||
++this.state.numManagement;
|
newManagement++;
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.RandD:
|
case EmployeePositions.RandD:
|
||||||
++this.state.numResearch;
|
newResearch++;
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Unassigned:
|
case EmployeePositions.Unassigned:
|
||||||
++this.state.numUnassigned;
|
newUnassigned++;
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Training:
|
case EmployeePositions.Training:
|
||||||
++this.state.numTraining;
|
newTraining++;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error("Unrecognized employee position: " + office.employees[i].pos);
|
console.error("Unrecognized employee position: " + office.employees[i].pos);
|
||||||
break;
|
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
|
updateEmployeeCount();
|
||||||
renderEmployeeManagement() {
|
|
||||||
this.updateEmployeeCount();
|
|
||||||
|
|
||||||
if (this.state.employeeManualAssignMode) {
|
// Renders the "Employee Management" section of the Office UI
|
||||||
return this.renderManualEmployeeManagement();
|
function renderEmployeeManagement(): React.ReactElement {
|
||||||
|
updateEmployeeCount();
|
||||||
|
|
||||||
|
if (employeeManualAssignMode) {
|
||||||
|
return renderManualEmployeeManagement();
|
||||||
} else {
|
} else {
|
||||||
return this.renderAutomaticEmployeeManagement();
|
return renderAutomaticEmployeeManagement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAutomaticEmployeeManagement() {
|
function renderAutomaticEmployeeManagement(): React.ReactElement {
|
||||||
const division = this.routing().currentDivision; // Validated in constructor
|
const division = props.routing.currentDivision; // Validated in constructor
|
||||||
const office = division.offices[this.props.currentCity]; // Validated in constructor
|
if(division === null) return(<></>);
|
||||||
const vechain = (this.corp().unlockUpgrades[4] === 1); // Has Vechain upgrade
|
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 = () => {
|
function switchModeOnClick(): void {
|
||||||
this.state.employeeManualAssignMode = true;
|
setEmployeeManualAssignMode(true);
|
||||||
this.corp().rerender();
|
props.corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate average morale, happiness, and energy. Also salary
|
// 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
|
// Helper functions for (re-)assigning employees to different positions
|
||||||
const assignEmployee = (to) => {
|
function assignEmployee(to: string): void {
|
||||||
if (this.state.numUnassigned <= 0) {
|
if(office === 0) return;
|
||||||
|
if(division === null) return;
|
||||||
|
if (numUnassigned <= 0) {
|
||||||
console.warn("Cannot assign employee. No unassigned employees available");
|
console.warn("Cannot assign employee. No unassigned employees available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (to) {
|
switch (to) {
|
||||||
case EmployeePositions.Operations:
|
case EmployeePositions.Operations:
|
||||||
++this.state.numOperations;
|
setNumOperations(n => n+1);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Engineer:
|
case EmployeePositions.Engineer:
|
||||||
++this.state.numEngineers;
|
setNumEngineers(n => n+1);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Business:
|
case EmployeePositions.Business:
|
||||||
++this.state.numBusiness;
|
setNumBusiness(n => n+1);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Management:
|
case EmployeePositions.Management:
|
||||||
++this.state.numManagement;
|
setNumManagement(n => n+1);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.RandD:
|
case EmployeePositions.RandD:
|
||||||
++this.state.numResearch;
|
setNumResearch(n => n+1);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Unassigned:
|
case EmployeePositions.Unassigned:
|
||||||
++this.state.numUnassigned;
|
setNumUnassigned(n => n+1);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Training:
|
case EmployeePositions.Training:
|
||||||
++this.state.numTraining;
|
setNumTraining(n => n+1);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error("Unrecognized employee position: " + to);
|
console.error("Unrecognized employee position: " + to);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
--this.state.numUnassigned;
|
setNumUnassigned(n => n-1);
|
||||||
|
|
||||||
office.assignEmployeeToJob(to);
|
office.assignEmployeeToJob(to);
|
||||||
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
|
office.calculateEmployeeProductivity(props.corp, division);
|
||||||
this.corp().rerender();
|
props.corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
|
|
||||||
const unassignEmployee = (from) => {
|
function unassignEmployee(from: string): void {
|
||||||
function logWarning(pos) {
|
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`);
|
console.warn(`Cannot unassign from ${pos} because there is nobody assigned to that position`);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (from) {
|
switch (from) {
|
||||||
case EmployeePositions.Operations:
|
case EmployeePositions.Operations:
|
||||||
if (this.state.numOperations <= 0) { return logWarning(EmployeePositions.Operations); }
|
if (numOperations <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||||
--this.state.numOperations;
|
setNumOperations(n => n-1);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Engineer:
|
case EmployeePositions.Engineer:
|
||||||
if (this.state.numEngineers <= 0) { return logWarning(EmployeePositions.Operations); }
|
if (numEngineers <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||||
--this.state.numEngineers;
|
setNumEngineers(n => n-1);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Business:
|
case EmployeePositions.Business:
|
||||||
if (this.state.numBusiness <= 0) { return logWarning(EmployeePositions.Operations); }
|
if (numBusiness <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||||
--this.state.numBusiness;
|
setNumBusiness(n => n-1);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Management:
|
case EmployeePositions.Management:
|
||||||
if (this.state.numManagement <= 0) { return logWarning(EmployeePositions.Operations); }
|
if (numManagement <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||||
--this.state.numManagement;
|
setNumManagement(n => n-1);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.RandD:
|
case EmployeePositions.RandD:
|
||||||
if (this.state.numResearch <= 0) { return logWarning(EmployeePositions.Operations); }
|
if (numResearch <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||||
--this.state.numResearch;
|
setNumResearch(n => n-1);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Unassigned:
|
case EmployeePositions.Unassigned:
|
||||||
console.warn(`Tried to unassign from the Unassigned position`);
|
console.warn(`Tried to unassign from the Unassigned position`);
|
||||||
break;
|
break;
|
||||||
case EmployeePositions.Training:
|
case EmployeePositions.Training:
|
||||||
if (this.state.numTraining <= 0) { return logWarning(EmployeePositions.Operations); }
|
if (numTraining <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||||
--this.state.numTraining;
|
setNumTraining(n => n-1);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error("Unrecognized employee position: " + from);
|
console.error("Unrecognized employee position: " + from);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++this.state.numUnassigned;
|
setNumUnassigned(n => n+1);
|
||||||
|
|
||||||
office.unassignEmployeeFromJob(from);
|
office.unassignEmployeeFromJob(from);
|
||||||
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
|
office.calculateEmployeeProductivity(props.corp, division);
|
||||||
this.corp().rerender();
|
props.corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionHeaderStyle = {
|
const positionHeaderStyle = {
|
||||||
@ -223,67 +252,67 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
margin: "5px 0px 5px 0px",
|
margin: "5px 0px 5px 0px",
|
||||||
width: "50%",
|
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);
|
assignEmployee(EmployeePositions.Operations);
|
||||||
this.corp().rerender();
|
props.corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
const operationUnassignButtonOnClick = () => {
|
function operationUnassignButtonOnClick(): void {
|
||||||
unassignEmployee(EmployeePositions.Operations);
|
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);
|
assignEmployee(EmployeePositions.Engineer);
|
||||||
this.corp().rerender();
|
props.corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
const engineerUnassignButtonOnClick = () => {
|
function engineerUnassignButtonOnClick(): void {
|
||||||
unassignEmployee(EmployeePositions.Engineer);
|
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);
|
assignEmployee(EmployeePositions.Business);
|
||||||
this.corp().rerender();
|
props.corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
const businessUnassignButtonOnClick = () => {
|
function businessUnassignButtonOnClick(): void {
|
||||||
unassignEmployee(EmployeePositions.Business);
|
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);
|
assignEmployee(EmployeePositions.Management);
|
||||||
this.corp().rerender();
|
props.corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
const managementUnassignButtonOnClick = () => {
|
function managementUnassignButtonOnClick(): void {
|
||||||
unassignEmployee(EmployeePositions.Management);
|
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);
|
assignEmployee(EmployeePositions.RandD);
|
||||||
this.corp().rerender();
|
props.corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
const rndUnassignButtonOnClick = () => {
|
function rndUnassignButtonOnClick(): void {
|
||||||
unassignEmployee(EmployeePositions.RandD);
|
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);
|
assignEmployee(EmployeePositions.Training);
|
||||||
this.corp().rerender();
|
props.corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
const trainingUnassignButtonOnClick = () => {
|
function trainingUnassignButtonOnClick(): void {
|
||||||
unassignEmployee(EmployeePositions.Training);
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -295,7 +324,7 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p><strong>Unassigned Employees: {this.state.numUnassigned}</strong></p>
|
<p><strong>Unassigned Employees: {numUnassigned}</strong></p>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
|
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
|
||||||
@ -344,7 +373,7 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||||
{EmployeePositions.Operations} ({this.state.numOperations})
|
{EmployeePositions.Operations} ({numOperations})
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Manages supply chain operations. Improves the amount of Materials and Products you produce.
|
Manages supply chain operations. Improves the amount of Materials and Products you produce.
|
||||||
</span>
|
</span>
|
||||||
@ -354,7 +383,7 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||||
{EmployeePositions.Engineer} ({this.state.numEngineers})
|
{EmployeePositions.Engineer} ({numEngineers})
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Develops and maintains products and production systems. Increases the quality of
|
Develops and maintains products and production systems. Increases the quality of
|
||||||
everything you produce. Also increases the amount you produce (not as much
|
everything you produce. Also increases the amount you produce (not as much
|
||||||
@ -366,7 +395,7 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||||
{EmployeePositions.Business} ({this.state.numBusiness})
|
{EmployeePositions.Business} ({numBusiness})
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Handles sales and finances. Improves the amount of Materials and Products you can sell.
|
Handles sales and finances. Improves the amount of Materials and Products you can sell.
|
||||||
</span>
|
</span>
|
||||||
@ -376,7 +405,7 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||||
{EmployeePositions.Management} ({this.state.numManagement})
|
{EmployeePositions.Management} ({numManagement})
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Leads and oversees employees and office operations. Improves the effectiveness of
|
Leads and oversees employees and office operations. Improves the effectiveness of
|
||||||
Engineer and Operations employees
|
Engineer and Operations employees
|
||||||
@ -387,7 +416,7 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||||
{EmployeePositions.RandD} ({this.state.numResearch})
|
{EmployeePositions.RandD} ({numResearch})
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Research new innovative ways to improve the company. Generates Scientific Research
|
Research new innovative ways to improve the company. Generates Scientific Research
|
||||||
</span>
|
</span>
|
||||||
@ -397,7 +426,7 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||||
{EmployeePositions.Training} ({this.state.numTraining})
|
{EmployeePositions.Training} ({numTraining})
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations.
|
Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations.
|
||||||
</span>
|
</span>
|
||||||
@ -408,14 +437,16 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderManualEmployeeManagement() {
|
function renderManualEmployeeManagement(): React.ReactElement {
|
||||||
const corp = this.corp();
|
const corp = props.corp;
|
||||||
const division = this.routing().currentDivision; // Validated in constructor
|
const division = props.routing.currentDivision; // Validated in constructor
|
||||||
const office = division.offices[this.props.currentCity]; // Validated in constructor
|
if(division === null) return (<></>);
|
||||||
|
const office = division.offices[props.currentCity]; // Validated in constructor
|
||||||
|
if(office === 0) return (<></>);
|
||||||
|
|
||||||
const switchModeOnClick = () => {
|
function switchModeOnClick(): void {
|
||||||
this.state.employeeManualAssignMode = false;
|
setEmployeeManualAssignMode(false);
|
||||||
this.corp().rerender();
|
props.corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
|
|
||||||
const employeeInfoDivStyle = {
|
const employeeInfoDivStyle = {
|
||||||
@ -430,21 +461,22 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
employees.push(<option key={office.employees[i].name}>{office.employees[i].name}</option>)
|
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);
|
const name = getSelectText(e.target);
|
||||||
for (let i = 0; i < office.employees.length; ++i) {
|
for (let i = 0; i < office.employees.length; ++i) {
|
||||||
if (name === office.employees[i].name) {
|
if (name === office.employees[i].name) {
|
||||||
this.state.employee = office.employees[i];
|
setEmployee(office.employees[i]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
corp.rerender();
|
corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Employee Positions Selector
|
// Employee Positions Selector
|
||||||
const emp = this.state.employee;
|
const emp = employee;
|
||||||
let employeePositionSelectorInitialValue = null;
|
let employeePositionSelectorInitialValue = "";
|
||||||
const employeePositions = [];
|
const employeePositions = [];
|
||||||
const positionNames = Object.values(EmployeePositions);
|
const positionNames = Object.values(EmployeePositions);
|
||||||
for (let i = 0; i < positionNames.length; ++i) {
|
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);
|
const pos = getSelectText(e.target);
|
||||||
this.state.employee.pos = pos;
|
employee.pos = pos;
|
||||||
this.resetEmployeeCount();
|
resetEmployeeCount();
|
||||||
corp.rerender();
|
corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Numeraljs formatter
|
// Numeraljs formatter
|
||||||
@ -486,29 +519,29 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
{employees}
|
{employees}
|
||||||
</select>
|
</select>
|
||||||
{
|
{
|
||||||
this.state.employee != null &&
|
employee != null &&
|
||||||
<p>
|
<p>
|
||||||
Morale: {numeralWrapper.format(this.state.employee.mor, nf)}
|
Morale: {numeralWrapper.format(employee.mor, nf)}
|
||||||
<br />
|
<br />
|
||||||
Happiness: {numeralWrapper.format(this.state.employee.hap, nf)}
|
Happiness: {numeralWrapper.format(employee.hap, nf)}
|
||||||
<br />
|
<br />
|
||||||
Energy: {numeralWrapper.format(this.state.employee.ene, nf)}
|
Energy: {numeralWrapper.format(employee.ene, nf)}
|
||||||
<br />
|
<br />
|
||||||
Intelligence: {numeralWrapper.format(effInt, nf)}
|
Intelligence: {numeralWrapper.format(effInt, nf)}
|
||||||
<br />
|
<br />
|
||||||
Charisma: {numeralWrapper.format(effCha, nf)}
|
Charisma: {numeralWrapper.format(effCha, nf)}
|
||||||
<br />
|
<br />
|
||||||
Experience: {numeralWrapper.format(this.state.employee.exp, nf)}
|
Experience: {numeralWrapper.format(employee.exp, nf)}
|
||||||
<br />
|
<br />
|
||||||
Creativity: {numeralWrapper.format(effCre, nf)}
|
Creativity: {numeralWrapper.format(effCre, nf)}
|
||||||
<br />
|
<br />
|
||||||
Efficiency: {numeralWrapper.format(effEff, nf)}
|
Efficiency: {numeralWrapper.format(effEff, nf)}
|
||||||
<br />
|
<br />
|
||||||
Salary: {numeralWrapper.formatMoney(this.state.employee.sal)}
|
Salary: {numeralWrapper.formatMoney(employee.sal)}
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.employee != null &&
|
employee != null &&
|
||||||
<select onChange={employeePositionSelectorOnChange} value={employeePositionSelectorInitialValue}>
|
<select onChange={employeePositionSelectorOnChange} value={employeePositionSelectorInitialValue}>
|
||||||
{employeePositions}
|
{employeePositions}
|
||||||
</select>
|
</select>
|
||||||
@ -518,89 +551,110 @@ export class IndustryOffice extends BaseReactComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
const division = props.routing.currentDivision; // Validated in constructor
|
||||||
const corp = this.corp();
|
if(division === null) return (<></>);
|
||||||
const division = this.routing().currentDivision; // Validated in constructor
|
const office = division.offices[props.currentCity]; // Validated in constructor
|
||||||
const office = division.offices[this.props.currentCity]; // Validated in constructor
|
if(office === 0) return (<></>);
|
||||||
|
const buttonStyle = {
|
||||||
const buttonStyle = {
|
fontSize: "13px",
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// React Component for displaying an Industry's overview information
|
||||||
// (top-left panel in the Industry UI)
|
// (top-left panel in the Industry UI)
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { BaseReactComponent } from "./BaseReactComponent";
|
|
||||||
|
|
||||||
import { OfficeSpace } from "../Corporation";
|
import { OfficeSpace } from "../OfficeSpace";
|
||||||
import { Industries } from "../IndustryData";
|
import { Industries } from "../IndustryData";
|
||||||
import { IndustryUpgrades } from "../IndustryUpgrades";
|
import { IndustryUpgrades } from "../IndustryUpgrades";
|
||||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||||
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
||||||
|
import { MakeProductPopup } from "./MakeProductPopup";
|
||||||
|
import { 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 {
|
interface IProps {
|
||||||
renderMakeProductButton() {
|
routing: CorporationRouting;
|
||||||
const division = this.routing().currentDivision; // Validated inside render()
|
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) {
|
switch(division.type) {
|
||||||
case Industries.Food:
|
case Industries.Food:
|
||||||
createProductButtonText = "Build Restaurant";
|
createProductButtonText = "Build Restaurant";
|
||||||
@ -52,7 +66,7 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
default:
|
default:
|
||||||
createProductButtonText = "Create Product";
|
createProductButtonText = "Create Product";
|
||||||
createProductPopupText = "Create a new product!";
|
createProductPopupText = "Create a new product!";
|
||||||
return "";
|
return (<></>);
|
||||||
}
|
}
|
||||||
createProductPopupText += "<br><br>To begin developing a product, " +
|
createProductPopupText += "<br><br>To begin developing a product, " +
|
||||||
"first choose the city in which it will be designed. The stats of your employees " +
|
"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 hasMaxProducts = division.hasMaximumNumberProducts();
|
||||||
|
|
||||||
const className = hasMaxProducts ? "a-link-button-inactive tooltip" : "std-button";
|
const className = hasMaxProducts ? "a-link-button-inactive tooltip" : "std-button";
|
||||||
const onClick = this.eventHandler().createMakeProductPopup.bind(this.eventHandler(), createProductPopupText, division);
|
|
||||||
const buttonStyle = {
|
const buttonStyle = {
|
||||||
margin: "6px",
|
margin: "6px",
|
||||||
display: "inline-block",
|
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 (
|
return (
|
||||||
<button className={className} onClick={onClick} style={buttonStyle}>
|
<button className={className} onClick={openMakeProductPopup} style={buttonStyle}>
|
||||||
{createProductButtonText}
|
{createProductButtonText}
|
||||||
{
|
{
|
||||||
hasMaxProducts &&
|
hasMaxProducts &&
|
||||||
@ -84,17 +108,13 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderText() {
|
function renderText(): React.ReactElement {
|
||||||
const corp = this.corp();
|
const corp = props.corp;
|
||||||
const division = this.routing().currentDivision; // Validated inside render()
|
const division = props.routing.currentDivision; // Validated inside render()
|
||||||
|
if(division === null) return (<></>);
|
||||||
const vechain = (corp.unlockUpgrades[4] === 1);
|
const vechain = (corp.unlockUpgrades[4] === 1);
|
||||||
const profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber();
|
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;
|
let advertisingInfo = false;
|
||||||
const advertisingFactors = division.getAdvertisingFactors();
|
const advertisingFactors = division.getAdvertisingFactors();
|
||||||
const awarenessFac = advertisingFactors[1];
|
const awarenessFac = advertisingFactors[1];
|
||||||
@ -103,15 +123,12 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
const totalAdvertisingFac = advertisingFactors[0];
|
const totalAdvertisingFac = advertisingFactors[0];
|
||||||
if (vechain) { advertisingInfo = true; }
|
if (vechain) { advertisingInfo = true; }
|
||||||
|
|
||||||
const revenue = `Revenue: ${numeralWrapper.formatMoney(division.lastCycleRevenue.toNumber())} / s`;
|
function productionMultHelpTipOnClick(): void {
|
||||||
const expenses = `Expenses: ${numeralWrapper.formatMoney(division.lastCycleExpenses.toNumber())} /s`;
|
if(division === null) return;
|
||||||
const profitStr = `Profit: ${numeralWrapper.formatMoney(profit)} / s`;
|
|
||||||
|
|
||||||
const productionMultHelpTipOnClick = () => {
|
|
||||||
// Wrapper for createProgressBarText()
|
// Wrapper for createProgressBarText()
|
||||||
// Converts the industry's "effectiveness factors"
|
// Converts the industry's "effectiveness factors"
|
||||||
// into a graphic (string) depicting how high that effectiveness is
|
// into a graphic (string) depicting how high that effectiveness is
|
||||||
function convertEffectFacToGraphic(fac) {
|
function convertEffectFacToGraphic(fac: number): string {
|
||||||
return createProgressBarText({
|
return createProgressBarText({
|
||||||
progress: fac,
|
progress: fac,
|
||||||
totalTicks: 20,
|
totalTicks: 20,
|
||||||
@ -136,12 +153,21 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
`Real Estate: ${convertEffectFacToGraphic(division.reFac)}`);
|
`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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{genInfo}
|
Industry: {division.type} (Corp Funds: <Money money={corp.funds.toNumber()} />)
|
||||||
<br /> <br />
|
<br /> <br />
|
||||||
{awareness} <br />
|
Awareness: {numeralWrapper.format(division.awareness, "0.000")} <br />
|
||||||
{popularity} <br />
|
Popularity: {numeralWrapper.format(division.popularity, "0.000")} <br />
|
||||||
{
|
{
|
||||||
(advertisingInfo !== false) &&
|
(advertisingInfo !== false) &&
|
||||||
<p className={"tooltip"}>Advertising Multiplier: x{numeralWrapper.format(totalAdvertisingFac, "0.000")}
|
<p className={"tooltip"}>Advertising Multiplier: x{numeralWrapper.format(totalAdvertisingFac, "0.000")}
|
||||||
@ -158,9 +184,9 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
}
|
}
|
||||||
{advertisingInfo}
|
{advertisingInfo}
|
||||||
<br /><br />
|
<br /><br />
|
||||||
{revenue} <br />
|
Revenue: <Money money={division.lastCycleRevenue.toNumber()} /> / s <br />
|
||||||
{expenses} <br />
|
Expenses: <Money money={division.lastCycleExpenses.toNumber()} /> /s <br />
|
||||||
{profitStr}
|
Profit: <Money money={profit} /> / s
|
||||||
<br /> <br />
|
<br /> <br />
|
||||||
<p className={"tooltip"}>
|
<p className={"tooltip"}>
|
||||||
Production Multiplier: {numeralWrapper.format(division.prodMult, "0.00")}
|
Production Multiplier: {numeralWrapper.format(division.prodMult, "0.00")}
|
||||||
@ -178,19 +204,20 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
products that you produce.
|
products that you produce.
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<button className={"help-tip"} onClick={division.createResearchBox.bind(division)}>
|
<button className={"help-tip"} onClick={openResearchPopup}>
|
||||||
Research
|
Research
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUpgrades() {
|
function renderUpgrades(): React.ReactElement[] {
|
||||||
const corp = this.corp();
|
const corp = props.corp;
|
||||||
const division = this.routing().currentDivision; // Validated inside render()
|
const division = props.routing.currentDivision; // Validated inside render()
|
||||||
const office = division.offices[this.props.currentCity];
|
if(division === null) return ([<></>]);
|
||||||
|
const office = division.offices[props.currentCity];
|
||||||
if (!(office instanceof OfficeSpace)) {
|
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 = [];
|
const upgrades = [];
|
||||||
@ -213,7 +240,9 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick = () => {
|
function onClick(): void {
|
||||||
|
if(office === 0) return;
|
||||||
|
if(division === null) return;
|
||||||
if (corp.funds.lt(cost)) {
|
if (corp.funds.lt(cost)) {
|
||||||
dialogBoxCreate("Insufficient funds");
|
dialogBoxCreate("Insufficient funds");
|
||||||
} else {
|
} else {
|
||||||
@ -223,13 +252,14 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
office: office,
|
office: office,
|
||||||
});
|
});
|
||||||
// corp.displayDivisionContent(division, city);
|
// corp.displayDivisionContent(division, city);
|
||||||
corp.rerender();
|
corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
upgrades.push(this.renderUpgrade({
|
upgrades.push(renderUpgrade({
|
||||||
|
key: index,
|
||||||
onClick: onClick,
|
onClick: onClick,
|
||||||
text: `${upgrade[4]} - ${numeralWrapper.formatMoney(cost)}`,
|
text: <>{upgrade[4]} - <Money money={cost} /></>,
|
||||||
tooltip: upgrade[5],
|
tooltip: upgrade[5],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -237,9 +267,16 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
return upgrades;
|
return upgrades;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUpgrade(props) {
|
interface IRenderUpgradeProps {
|
||||||
|
key: string;
|
||||||
|
onClick: () => void;
|
||||||
|
text: JSX.Element;
|
||||||
|
tooltip: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUpgrade(props: IRenderUpgradeProps): React.ReactElement {
|
||||||
return (
|
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.text}
|
||||||
{
|
{
|
||||||
props.tooltip != null &&
|
props.tooltip != null &&
|
||||||
@ -249,27 +286,25 @@ export class IndustryOverview extends BaseReactComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
const division = props.routing.currentDivision;
|
||||||
const division = this.routing().currentDivision;
|
if (division == null) {
|
||||||
if (division == null) {
|
throw new Error(`Routing does not hold reference to the current Industry`);
|
||||||
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 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
|
// React Component for displaying an Industry's warehouse information
|
||||||
// (right-side panel in the Industry UI)
|
// (right-side panel in the Industry UI)
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { BaseReactComponent } from "./BaseReactComponent";
|
|
||||||
|
|
||||||
import { OfficeSpace,
|
import { CorporationConstants } from "../data/Constants";
|
||||||
WarehouseInitialCost,
|
import { OfficeSpace } from "../OfficeSpace";
|
||||||
WarehouseUpgradeBaseCost,
|
|
||||||
ProductProductionCostRatio } from "../Corporation";
|
|
||||||
import { Material } from "../Material";
|
import { Material } from "../Material";
|
||||||
import { Product } from "../Product";
|
import { Product } from "../Product";
|
||||||
import { Warehouse } from "../Warehouse";
|
import { Warehouse } from "../Warehouse";
|
||||||
|
import { DiscontinueProductPopup } from "./DiscontinueProductPopup";
|
||||||
|
import { 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 { numeralWrapper } from "../../ui/numeralFormat";
|
||||||
|
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||||
|
import { createPopup } from "../../ui/React/createPopup";
|
||||||
|
|
||||||
import { isString } from "../../../utils/helpers/isString";
|
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
|
// Creates the UI for a single Product type
|
||||||
function ProductComponent(props) {
|
function ProductComponent(props: IProductProps): React.ReactElement {
|
||||||
const corp = props.corp;
|
const corp = props.corp;
|
||||||
const division = props.division;
|
const division = props.division;
|
||||||
const city = props.city;
|
const city = props.city;
|
||||||
const product = props.product;
|
const product = props.product;
|
||||||
const eventHandler = props.eventHandler;
|
|
||||||
|
|
||||||
// Numeraljs formatters
|
// Numeraljs formatters
|
||||||
const nf = "0.000";
|
const nf = "0.000";
|
||||||
@ -53,23 +73,53 @@ function ProductComponent(props) {
|
|||||||
if (isString(product.sCost)) {
|
if (isString(product.sCost)) {
|
||||||
sellButtonText += (" @ " + product.sCost);
|
sellButtonText += (" @ " + product.sCost);
|
||||||
} else {
|
} 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
|
// Limit Production button
|
||||||
let limitProductionButtonText = "Limit Production";
|
let limitProductionButtonText = "Limit Production";
|
||||||
if (product.prdman[city][0]) {
|
if (product.prdman[city][0]) {
|
||||||
limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")";
|
limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")";
|
||||||
}
|
}
|
||||||
const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city);
|
|
||||||
|
|
||||||
// Discontinue Button
|
function openLimitProductProdutionPopup(): void {
|
||||||
const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product, division);
|
const popupId = "cmpy-mgmt-limit-product-production-popup";
|
||||||
|
createPopup(popupId, LimitProductProductionPopup, {
|
||||||
|
product: product,
|
||||||
|
city: city,
|
||||||
|
popupId: popupId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Market TA button
|
function openDiscontinueProductPopup(): void {
|
||||||
const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division);
|
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
|
// Unfinished Product
|
||||||
if (!product.fin) {
|
if (!product.fin) {
|
||||||
@ -81,18 +131,18 @@ function ProductComponent(props) {
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button className={"std-button"} onClick={sellButtonOnClick}>
|
<button className={"std-button"} onClick={openSellProductPopup}>
|
||||||
{sellButtonText}
|
{sellButtonText}
|
||||||
</button><br />
|
</button><br />
|
||||||
<button className={"std-button"} onClick={limitProductionButtonOnClick}>
|
<button className={"std-button"} onClick={openLimitProductProdutionPopup}>
|
||||||
{limitProductionButtonText}
|
{limitProductionButtonText}
|
||||||
</button>
|
</button>
|
||||||
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
<button className={"std-button"} onClick={openDiscontinueProductPopup}>
|
||||||
Discontinue
|
Discontinue
|
||||||
</button>
|
</button>
|
||||||
{
|
{
|
||||||
division.hasResearch("Market-TA.I") &&
|
division.hasResearch("Market-TA.I") &&
|
||||||
<button className={"std-button"} onClick={marketTaButtonOnClick}>
|
<button className={"std-button"} onClick={openProductMarketTaPopup}>
|
||||||
Market-TA
|
Market-TA
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@ -146,7 +196,7 @@ function ProductComponent(props) {
|
|||||||
</span>
|
</span>
|
||||||
</p><br />
|
</p><br />
|
||||||
<p className={"tooltip"}>
|
<p className={"tooltip"}>
|
||||||
Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)}
|
Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / CorporationConstants.ProductProductionCostRatio)}
|
||||||
<span className={"tooltiptext"}>
|
<span className={"tooltiptext"}>
|
||||||
An estimate of the material cost it takes to create this Product.
|
An estimate of the material cost it takes to create this Product.
|
||||||
</span>
|
</span>
|
||||||
@ -161,18 +211,18 @@ function ProductComponent(props) {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button className={"std-button"} onClick={sellButtonOnClick}>
|
<button className={"std-button"} onClick={openSellProductPopup}>
|
||||||
{sellButtonText}
|
{sellButtonText}
|
||||||
</button><br />
|
</button><br />
|
||||||
<button className={"std-button"} onClick={limitProductionButtonOnClick}>
|
<button className={"std-button"} onClick={openLimitProductProdutionPopup}>
|
||||||
{limitProductionButtonText}
|
{limitProductionButtonText}
|
||||||
</button>
|
</button>
|
||||||
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
<button className={"std-button"} onClick={openDiscontinueProductPopup}>
|
||||||
Discontinue
|
Discontinue
|
||||||
</button>
|
</button>
|
||||||
{
|
{
|
||||||
division.hasResearch("Market-TA.I") &&
|
division.hasResearch("Market-TA.I") &&
|
||||||
<button className={"std-button"} onClick={marketTaButtonOnClick}>
|
<button className={"std-button"} onClick={openProductMarketTaPopup}>
|
||||||
Market-TA
|
Market-TA
|
||||||
</button>
|
</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
|
// Creates the UI for a single Material type
|
||||||
function MaterialComponent(props) {
|
function MaterialComponent(props: IMaterialProps): React.ReactElement {
|
||||||
const corp = props.corp;
|
const corp = props.corp;
|
||||||
const division = props.division;
|
const division = props.division;
|
||||||
const warehouse = props.warehouse;
|
const warehouse = props.warehouse;
|
||||||
const city = props.city;
|
const city = props.city;
|
||||||
const mat = props.mat;
|
const mat = props.mat;
|
||||||
const eventHandler = props.eventHandler;
|
|
||||||
const markupLimit = mat.getMarkupLimit();
|
const markupLimit = mat.getMarkupLimit();
|
||||||
const office = division.offices[city];
|
const office = division.offices[city];
|
||||||
if (!(office instanceof OfficeSpace)) {
|
if (!(office instanceof OfficeSpace)) {
|
||||||
@ -210,10 +267,26 @@ function MaterialComponent(props) {
|
|||||||
// Purchase material button
|
// Purchase material button
|
||||||
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;
|
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;
|
||||||
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
|
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
|
||||||
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
|
|
||||||
|
|
||||||
// Export material button
|
function openPurchaseMaterialPopup(): void {
|
||||||
const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat);
|
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
|
// Sell material button
|
||||||
let sellButtonText;
|
let sellButtonText;
|
||||||
@ -221,7 +294,7 @@ function MaterialComponent(props) {
|
|||||||
if (isString(mat.sllman[1])) {
|
if (isString(mat.sllman[1])) {
|
||||||
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${mat.sllman[1]})`
|
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${mat.sllman[1]})`
|
||||||
} else {
|
} 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) {
|
if (mat.marketTa2) {
|
||||||
@ -230,19 +303,34 @@ function MaterialComponent(props) {
|
|||||||
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit);
|
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit);
|
||||||
} else if (mat.sCost) {
|
} else if (mat.sCost) {
|
||||||
if (isString(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));
|
sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost));
|
||||||
} else {
|
} else {
|
||||||
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.sCost);
|
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.sCost as number);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sellButtonText = "Sell (0.000/0.000)";
|
sellButtonText = "Sell (0.000/0.000)";
|
||||||
}
|
}
|
||||||
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
|
|
||||||
|
|
||||||
// Market TA button
|
function openSellMaterialPopup(): void {
|
||||||
const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division);
|
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 (
|
return (
|
||||||
<div className={"cmpy-mgmt-warehouse-material-div"}>
|
<div className={"cmpy-mgmt-warehouse-material-div"}>
|
||||||
@ -286,7 +374,7 @@ function MaterialComponent(props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{display: "inline-block"}}>
|
<div style={{display: "inline-block"}}>
|
||||||
<button className={purchaseButtonClass} onClick={purchaseButtonOnClick}>
|
<button className={purchaseButtonClass} onClick={openPurchaseMaterialPopup}>
|
||||||
{purchaseButtonText}
|
{purchaseButtonText}
|
||||||
{
|
{
|
||||||
tutorial &&
|
tutorial &&
|
||||||
@ -298,19 +386,19 @@ function MaterialComponent(props) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
corp.unlockUpgrades[0] === 1 &&
|
corp.unlockUpgrades[0] === 1 &&
|
||||||
<button className={"std-button"} onClick={exportButtonOnClick}>
|
<button className={"std-button"} onClick={openExportPopup}>
|
||||||
Export
|
Export
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<button className={"std-button"} onClick={sellButtonOnClick}>
|
<button className={"std-button"} onClick={openSellMaterialPopup}>
|
||||||
{sellButtonText}
|
{sellButtonText}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{
|
{
|
||||||
division.hasResearch("Market-TA.I") &&
|
division.hasResearch("Market-TA.I") &&
|
||||||
<button className={"std-button"} onClick={marketTaButtonOnClick}>
|
<button className={"std-button"} onClick={openMaterialMarketTaPopup}>
|
||||||
Market-TA
|
Market-TA
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@ -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
|
// Returns a boolean indicating whether the given material is relevant for the
|
||||||
// current industry.
|
// current industry.
|
||||||
isRelevantMaterial(matName, division) {
|
function isRelevantMaterial(matName: string, division: IIndustry): boolean {
|
||||||
// Materials that affect Production multiplier
|
// Materials that affect Production multiplier
|
||||||
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"];
|
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"];
|
||||||
|
|
||||||
@ -334,10 +429,12 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderWarehouseUI() {
|
function renderWarehouseUI(): React.ReactElement {
|
||||||
const corp = this.corp();
|
const corp = props.corp;
|
||||||
const division = this.routing().currentDivision; // Validated in render()
|
const division = props.routing.currentDivision; // Validated in render()
|
||||||
const warehouse = division.warehouses[this.props.currentCity]; // 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
|
// General Storage information at the top
|
||||||
const sizeUsageStyle = {
|
const sizeUsageStyle = {
|
||||||
@ -346,15 +443,16 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade Warehouse size button
|
// 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 canAffordUpgrade = (corp.funds.gt(sizeUpgradeCost));
|
||||||
const upgradeWarehouseClass = canAffordUpgrade ? "std-button" : "a-link-button-inactive";
|
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.level;
|
||||||
warehouse.updateSize(corp, division);
|
warehouse.updateSize(corp, division);
|
||||||
corp.funds = corp.funds.minus(sizeUpgradeCost);
|
corp.funds = corp.funds.minus(sizeUpgradeCost);
|
||||||
corp.rerender();
|
corp.rerender(props.player);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Industry material Requirements
|
// Industry material Requirements
|
||||||
@ -416,9 +514,10 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
|
|
||||||
// Smart Supply Checkbox
|
// Smart Supply Checkbox
|
||||||
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
|
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
|
||||||
const smartSupplyOnChange = (e) => {
|
function smartSupplyOnChange(e: React.ChangeEvent<HTMLInputElement>): void {
|
||||||
warehouse.smartSupplyEnabled = e.target.checked;
|
if(warehouse === 0) return;
|
||||||
corp.rerender();
|
SetSmartSupply(warehouse, e.target.checked);
|
||||||
|
corp.rerender(props.player);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create React components for materials
|
// Create React components for materials
|
||||||
@ -426,12 +525,11 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
for (const matName in warehouse.materials) {
|
for (const matName in warehouse.materials) {
|
||||||
if (warehouse.materials[matName] instanceof Material) {
|
if (warehouse.materials[matName] instanceof Material) {
|
||||||
// Only create UI for materials that are relevant for the industry
|
// Only create UI for materials that are relevant for the industry
|
||||||
if (this.isRelevantMaterial(matName, division)) {
|
if (isRelevantMaterial(matName, division)) {
|
||||||
mats.push(<MaterialComponent
|
mats.push(<MaterialComponent
|
||||||
city={this.props.currentCity}
|
city={props.currentCity}
|
||||||
corp={corp}
|
corp={corp}
|
||||||
division={division}
|
division={division}
|
||||||
eventHandler={this.eventHandler()}
|
|
||||||
key={matName}
|
key={matName}
|
||||||
mat={warehouse.materials[matName]}
|
mat={warehouse.materials[matName]}
|
||||||
warehouse={warehouse} />);
|
warehouse={warehouse} />);
|
||||||
@ -443,15 +541,16 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
const products = [];
|
const products = [];
|
||||||
if (division.makesProducts && Object.keys(division.products).length > 0) {
|
if (division.makesProducts && Object.keys(division.products).length > 0) {
|
||||||
for (const productName in division.products) {
|
for (const productName in division.products) {
|
||||||
if (division.products[productName] instanceof Product) {
|
const product = division.products[productName];
|
||||||
|
if (product instanceof Product) {
|
||||||
products.push(<ProductComponent
|
products.push(<ProductComponent
|
||||||
city={this.props.currentCity}
|
player={props.player}
|
||||||
|
city={props.currentCity}
|
||||||
corp={corp}
|
corp={corp}
|
||||||
division={division}
|
division={division}
|
||||||
eventHandler={this.eventHandler()}
|
|
||||||
key={productName}
|
key={productName}
|
||||||
product={division.products[productName]}
|
product={product}
|
||||||
warehouse={warehouse} />);
|
/>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -500,25 +599,36 @@ export class IndustryWarehouse extends BaseReactComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
const division = props.routing.currentDivision;
|
||||||
const division = this.routing().currentDivision;
|
if (division == null) {
|
||||||
if (division == null) {
|
throw new Error(`Routing does not hold reference to the current Industry`);
|
||||||
throw new Error(`Routing does not hold reference to the current Industry`);
|
}
|
||||||
}
|
const warehouse = division.warehouses[props.currentCity];
|
||||||
const warehouse = division.warehouses[this.props.currentCity];
|
|
||||||
|
|
||||||
const newWarehouseOnClick = this.eventHandler().purchaseWarehouse.bind(this.eventHandler(), division, this.props.currentCity);
|
function purchaseWarehouse(division: IIndustry, city: string): void {
|
||||||
|
if (props.corp.funds.lt(CorporationConstants.WarehouseInitialCost)) {
|
||||||
if (warehouse instanceof Warehouse) {
|
dialogBoxCreate("You do not have enough funds to do this!");
|
||||||
return this.renderWarehouseUI();
|
|
||||||
} else {
|
} else {
|
||||||
return (
|
division.warehouses[city] = new Warehouse({
|
||||||
<div className={"cmpy-mgmt-warehouse-panel"}>
|
corp: props.corp,
|
||||||
<button className={"std-button"} onClick={newWarehouseOnClick}>
|
industry: division,
|
||||||
Purchase Warehouse ({numeralWrapper.formatMoney(WarehouseInitialCost)})
|
loc: city,
|
||||||
</button>
|
size: CorporationConstants.WarehouseInitialSize,
|
||||||
</div>
|
});
|
||||||
)
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
58
src/Corporation/ui/IssueDividendsPopup.tsx
Normal file
58
src/Corporation/ui/IssueDividendsPopup.tsx
Normal file
@ -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>
|
||||||
|
</>);
|
||||||
|
}
|
134
src/Corporation/ui/IssueNewSharesPopup.tsx
Normal file
134
src/Corporation/ui/IssueNewSharesPopup.tsx
Normal file
@ -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 />
|
||||||
|
* You can issue at most {numeralWrapper.formatMoney(maxNewShares)} new shares<br />
|
||||||
|
* New shares are sold at a 10% discount<br />
|
||||||
|
* You can only issue new shares once every 12 hours<br />
|
||||||
|
* Issuing new shares causes dilution, resulting in a decrease in stock price and lower dividends per share<br />
|
||||||
|
* 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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
43
src/Corporation/ui/LevelableUpgrade.tsx
Normal file
43
src/Corporation/ui/LevelableUpgrade.tsx
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
53
src/Corporation/ui/LimitProductProductionPopup.tsx
Normal file
53
src/Corporation/ui/LimitProductProductionPopup.tsx
Normal file
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
96
src/Corporation/ui/MainPanel.tsx
Normal file
96
src/Corporation/ui/MainPanel.tsx
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
102
src/Corporation/ui/MakeProductPopup.tsx
Normal file
102
src/Corporation/ui/MakeProductPopup.tsx
Normal file
@ -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>
|
||||||
|
</>);
|
||||||
|
}
|
116
src/Corporation/ui/MaterialMarketTaPopup.tsx
Normal file
116
src/Corporation/ui/MaterialMarketTaPopup.tsx
Normal file
@ -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} />
|
||||||
|
</>);
|
||||||
|
|
||||||
|
}
|
68
src/Corporation/ui/NewIndustryPopup.tsx
Normal file
68
src/Corporation/ui/NewIndustryPopup.tsx
Normal file
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
367
src/Corporation/ui/Overview.tsx
Normal file
367
src/Corporation/ui/Overview.tsx
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
99
src/Corporation/ui/ProductMarketTaPopup.tsx
Normal file
99
src/Corporation/ui/ProductMarketTaPopup.tsx
Normal file
@ -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} />}
|
||||||
|
</>);
|
||||||
|
}
|
130
src/Corporation/ui/PurchaseMaterialPopup.tsx
Normal file
130
src/Corporation/ui/PurchaseMaterialPopup.tsx
Normal file
@ -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} />}
|
||||||
|
</>);
|
||||||
|
}
|
101
src/Corporation/ui/ResearchPopup.tsx
Normal file
101
src/Corporation/ui/ResearchPopup.tsx
Normal file
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
23
src/Corporation/ui/Root.tsx
Normal file
23
src/Corporation/ui/Root.tsx
Normal file
@ -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";
|
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
|
* 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
|
// Stores a reference to the Division instance that the routing is currently on
|
||||||
// This will be null if routing is on the overview page
|
// This will be null if routing is on the overview page
|
||||||
currentDivision: IDivision | null = null;
|
currentDivision: IIndustry | null = null;
|
||||||
|
|
||||||
constructor(corp: ICorporation) {
|
constructor(corp: ICorporation) {
|
||||||
this.corp = corp;
|
this.corp = corp;
|
||||||
|
72
src/Corporation/ui/SellMaterialPopup.tsx
Normal file
72
src/Corporation/ui/SellMaterialPopup.tsx
Normal file
@ -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>
|
||||||
|
</>);
|
||||||
|
}
|
83
src/Corporation/ui/SellProductPopup.tsx
Normal file
83
src/Corporation/ui/SellProductPopup.tsx
Normal file
@ -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>
|
||||||
|
</>);
|
||||||
|
}
|
97
src/Corporation/ui/SellSharesPopup.tsx
Normal file
97
src/Corporation/ui/SellSharesPopup.tsx
Normal file
@ -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>
|
||||||
|
</>);
|
||||||
|
|
||||||
|
}
|
58
src/Corporation/ui/ThrowPartyPopup.tsx
Normal file
58
src/Corporation/ui/ThrowPartyPopup.tsx
Normal file
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
37
src/Corporation/ui/UnlockUpgrade.tsx
Normal file
37
src/Corporation/ui/UnlockUpgrade.tsx
Normal file
@ -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>
|
||||||
|
)
|
||||||
|
}
|
80
src/Corporation/ui/UpgradeOfficeSizePopup.tsx
Normal file
80
src/Corporation/ui/UpgradeOfficeSizePopup.tsx
Normal file
@ -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() {
|
specificContract() {
|
||||||
generateContract({
|
generateContract({
|
||||||
problemType: this.state.codingcontract,
|
problemType: this.state.codingcontract,
|
||||||
@ -1169,6 +1183,16 @@ class DevMenuComponent extends Component {
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
2
src/InteractiveTutorial.d.ts
vendored
2
src/InteractiveTutorial.d.ts
vendored
@ -1,3 +1,3 @@
|
|||||||
export declare function iTutorialNextStep(): void;
|
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};
|
export declare const iTutorialSteps: {[key: string]: number};
|
@ -20,6 +20,18 @@ import { CompanyPosition } from "./Company/CompanyPosition";
|
|||||||
import { CompanyPositions } from "./Company/CompanyPositions";
|
import { CompanyPositions } from "./Company/CompanyPositions";
|
||||||
import { CONSTANTS } from "./Constants";
|
import { CONSTANTS } from "./Constants";
|
||||||
import { DarkWebItems } from "./DarkWeb/DarkWebItems";
|
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 {
|
import {
|
||||||
calculateHackingChance,
|
calculateHackingChance,
|
||||||
calculateHackingExpGain,
|
calculateHackingExpGain,
|
||||||
@ -555,6 +567,39 @@ function NetscriptFunctions(workerScript) {
|
|||||||
return Augmentations[name];
|
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) {
|
const runAfterReset = function(cbScript=null) {
|
||||||
//Run a script after reset
|
//Run a script after reset
|
||||||
if (cbScript && isString(cbScript)) {
|
if (cbScript && isString(cbScript)) {
|
||||||
@ -4097,6 +4142,46 @@ function NetscriptFunctions(workerScript) {
|
|||||||
},
|
},
|
||||||
}, // End Bladeburner
|
}, // 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
|
// Coding Contract API
|
||||||
codingcontract: {
|
codingcontract: {
|
||||||
attempt: function(answer, fn, ip=workerScript.serverIp, { returnReward } = {}) {
|
attempt: function(answer, fn, ip=workerScript.serverIp, { returnReward } = {}) {
|
||||||
|
@ -19,7 +19,8 @@ import { LocationName } from "../Locations/data/LocationNames";
|
|||||||
import { Server } from "../Server/Server";
|
import { Server } from "../Server/Server";
|
||||||
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
|
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
|
||||||
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
|
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
|
||||||
import { Exploit } from "../Exploits/Exploit";
|
import { Exploit } from "../Exploits/Exploit";
|
||||||
|
import { ICorporation } from "../Corporation/ICorporation";
|
||||||
|
|
||||||
export interface IPlayer {
|
export interface IPlayer {
|
||||||
// Class members
|
// Class members
|
||||||
@ -28,7 +29,7 @@ export interface IPlayer {
|
|||||||
bitNodeN: number;
|
bitNodeN: number;
|
||||||
city: CityName;
|
city: CityName;
|
||||||
companyName: string;
|
companyName: string;
|
||||||
corporation: any;
|
corporation: ICorporation;
|
||||||
currentServer: string;
|
currentServer: string;
|
||||||
factions: string[];
|
factions: string[];
|
||||||
factionInvitations: string[];
|
factionInvitations: string[];
|
||||||
|
2171
src/ThirdParty/Treant.js
vendored
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
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
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) {
|
if (Player.corporation instanceof Corporation) {
|
||||||
Engine.hideAllContent();
|
Engine.hideAllContent();
|
||||||
routing.navigateTo(Page.Corporation);
|
routing.navigateTo(Page.Corporation);
|
||||||
Player.corporation.createUI();
|
Player.corporation.createUI(Player);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -537,7 +537,7 @@ const Engine = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Player.corporation instanceof Corporation) {
|
if (Player.corporation instanceof Corporation) {
|
||||||
Player.corporation.clearUI();
|
Player.corporation.clearUI(Player);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearResleevesPage();
|
clearResleevesPage();
|
||||||
@ -856,7 +856,7 @@ const Engine = {
|
|||||||
|
|
||||||
if (Engine.Counters.mechanicProcess <= 0) {
|
if (Engine.Counters.mechanicProcess <= 0) {
|
||||||
if (Player.corporation instanceof Corporation) {
|
if (Player.corporation instanceof Corporation) {
|
||||||
Player.corporation.process();
|
Player.corporation.process(Player);
|
||||||
}
|
}
|
||||||
if (Player.bladeburner instanceof Bladeburner) {
|
if (Player.bladeburner instanceof Bladeburner) {
|
||||||
try {
|
try {
|
||||||
|
@ -641,6 +641,5 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
|
|||||||
|
|
||||||
<!-- Misc Scripts -->
|
<!-- Misc Scripts -->
|
||||||
<script src="src/ThirdParty/raphael.min.js"></script>
|
<script src="src/ThirdParty/raphael.min.js"></script>
|
||||||
<script src="src/ThirdParty/Treant.js"></script>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
Reference in New Issue
Block a user