mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-22 06:02:26 +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
|
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 -->
|
||||
<script src="src/ThirdParty/raphael.min.js"></script>
|
||||
<script src="src/ThirdParty/Treant.js"></script>
|
||||
|
||||
</html>
|
||||
|
11
package-lock.json
generated
11
package-lock.json
generated
@ -48,6 +48,7 @@
|
||||
"react-modal": "^3.12.1",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"tapable": "^1.0.0",
|
||||
"treant-js": "^1.0.1",
|
||||
"uuid": "^3.2.1",
|
||||
"w3c-blob": "0.0.1"
|
||||
},
|
||||
@ -21630,6 +21631,11 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/treant-js": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/treant-js/-/treant-js-1.0.1.tgz",
|
||||
"integrity": "sha1-aRdSt+9maSCzQiP8ZlJUaTtG/VQ="
|
||||
},
|
||||
"node_modules/trim": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
|
||||
@ -41190,6 +41196,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"treant-js": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/treant-js/-/treant-js-1.0.1.tgz",
|
||||
"integrity": "sha1-aRdSt+9maSCzQiP8ZlJUaTtG/VQ="
|
||||
},
|
||||
"trim": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
|
||||
|
@ -45,6 +45,7 @@
|
||||
"react-modal": "^3.12.1",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"tapable": "^1.0.0",
|
||||
"treant-js": "^1.0.1",
|
||||
"uuid": "^3.2.1",
|
||||
"w3c-blob": "0.0.1"
|
||||
},
|
||||
|
228
src/Corporation/Actions.ts
Normal file
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";
|
||||
|
||||
export type IndustryUpgrade = [number, number, number, number, string, string];
|
||||
|
||||
// Industry upgrades
|
||||
// The data structure is an array with the following format:
|
||||
// [index in array, base price, price mult, benefit mult (if applicable), name, desc]
|
||||
export const IndustryUpgrades: IMap<any[]> = {
|
||||
export const IndustryUpgrades: IMap<IndustryUpgrade> = {
|
||||
"0": [0, 500e3, 1, 1.05,
|
||||
"Coffee", "Provide your employees with coffee, increasing their energy by 5%."],
|
||||
"1": [1, 1e9, 1.06, 1.03,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Generic_fromJSON,
|
||||
Generic_toJSON,
|
||||
Reviver } from "../../utils/JSONReviver";
|
||||
import { Export } from "./Export";
|
||||
|
||||
interface IConstructorParams {
|
||||
name?: string;
|
||||
@ -43,7 +44,7 @@ export class Material {
|
||||
imp = 0;
|
||||
|
||||
// Exports of this material to another warehouse/industry
|
||||
exp: any[] = [];
|
||||
exp: Export[] = [];
|
||||
|
||||
// Total amount of this material exported in the last cycle
|
||||
totalExp = 0;
|
||||
@ -52,12 +53,12 @@ export class Material {
|
||||
bCost = 0;
|
||||
|
||||
// Cost / sec to sell this material
|
||||
sCost = 0;
|
||||
sCost: string | number = 0;
|
||||
|
||||
// Flags to keep track of whether production and/or sale of this material is limited
|
||||
// [Whether production/sale is limited, limit amount]
|
||||
prdman: any[] = [false, 0]; // Production
|
||||
sllman: any[] = [false, 0]; // Sale
|
||||
prdman: [boolean, number] = [false, 0]; // Production
|
||||
sllman: [boolean, string | number] = [false, 0]; // Sale
|
||||
|
||||
// Flags that signal whether automatic sale pricing through Market TA is enabled
|
||||
marketTa1 = false;
|
||||
|
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 { MaterialSizes } from "./MaterialSizes";
|
||||
import { IIndustry } from "./IIndustry";
|
||||
import { ProductRatingWeights,
|
||||
IProductRatingWeight } from "./ProductRatingWeights";
|
||||
|
||||
@ -30,17 +31,6 @@ interface IConstructorParams {
|
||||
req?: IMap<number>;
|
||||
}
|
||||
|
||||
// Interface for an Industry object - Used for type checking method arguments
|
||||
interface IIndustry {
|
||||
awareness: number;
|
||||
popularity: number;
|
||||
reqMats: IMap<number>;
|
||||
sciFac: number;
|
||||
sciResearch: any;
|
||||
type: string;
|
||||
}
|
||||
|
||||
|
||||
export class Product {
|
||||
|
||||
// Product name
|
||||
@ -60,7 +50,7 @@ export class Product {
|
||||
pCost = 0;
|
||||
|
||||
// Sell cost
|
||||
sCost = 0;
|
||||
sCost: string | number = 0;
|
||||
|
||||
// Variables for handling the creation process of this Product
|
||||
fin = false; // Whether this Product has finished being created
|
||||
@ -137,7 +127,7 @@ export class Product {
|
||||
}
|
||||
|
||||
// @param industry - Industry object. Reference to industry that makes this Product
|
||||
finishProduct(employeeProd: IMap<number>, industry: IIndustry): void {
|
||||
finishProduct(employeeProd: {[key: string]: number}, industry: IIndustry): void {
|
||||
this.fin = true;
|
||||
|
||||
//Calculate properties
|
||||
@ -199,7 +189,9 @@ export class Product {
|
||||
//For now, just set it to be the same as the requirements to make materials
|
||||
for (const matName in industry.reqMats) {
|
||||
if (industry.reqMats.hasOwnProperty(matName)) {
|
||||
this.reqMats[matName] = industry.reqMats[matName];
|
||||
const reqMat = industry.reqMats[matName];
|
||||
if(reqMat === undefined) continue;
|
||||
this.reqMats[matName] = reqMat;
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,18 +199,10 @@ export class Product {
|
||||
//For now, just set it to be the same size as the requirements to make materials
|
||||
this.siz = 0;
|
||||
for (const matName in industry.reqMats) {
|
||||
this.siz += MaterialSizes[matName] * industry.reqMats[matName];
|
||||
const reqMat = industry.reqMats[matName];
|
||||
if(reqMat === undefined) continue;
|
||||
this.siz += MaterialSizes[matName] * reqMat;
|
||||
}
|
||||
|
||||
//Delete unneeded variables
|
||||
// @ts-ignore
|
||||
delete this.prog;
|
||||
// @ts-ignore
|
||||
delete this.createCity;
|
||||
// @ts-ignore
|
||||
delete this.designCost;
|
||||
// @ts-ignore
|
||||
delete this.advCost;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Material } from "./Material";
|
||||
import { ICorporation } from "./ICorporation";
|
||||
import { IIndustry } from "./IIndustry";
|
||||
import { MaterialSizes } from "./MaterialSizes";
|
||||
import { IMap } from "../types";
|
||||
import { numeralWrapper } from "../ui/numeralFormat";
|
||||
@ -7,13 +9,9 @@ import { Generic_fromJSON,
|
||||
Reviver } from "../../utils/JSONReviver";
|
||||
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
|
||||
|
||||
interface IParent {
|
||||
getStorageMultiplier(): number;
|
||||
}
|
||||
|
||||
interface IConstructorParams {
|
||||
corp?: IParent;
|
||||
industry?: IParent;
|
||||
corp?: ICorporation;
|
||||
industry?: IIndustry;
|
||||
loc?: string;
|
||||
size?: number;
|
||||
}
|
||||
@ -92,7 +90,7 @@ export class Warehouse {
|
||||
}
|
||||
}
|
||||
|
||||
updateSize(corporation: IParent, industry: IParent): void {
|
||||
updateSize(corporation: ICorporation, industry: IIndustry): void {
|
||||
try {
|
||||
this.size = (this.level * 100)
|
||||
* corporation.getStorageMultiplier()
|
||||
|
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";
|
||||
|
||||
export type CorporationUnlockUpgrade = [number, number, string, string];
|
||||
|
||||
// Corporation Unlock Upgrades
|
||||
// Upgrades for entire corporation, unlocks features, either you have it or you dont
|
||||
// The data structure is an array with the following format:
|
||||
// [index in Corporation feature upgrades array, price, name, description]
|
||||
export const CorporationUnlockUpgrades: IMap<any[]> = {
|
||||
export const CorporationUnlockUpgrades: IMap<CorporationUnlockUpgrade> = {
|
||||
//Lets you export goods
|
||||
"0": [0, 20e9, "Export",
|
||||
"Develop infrastructure to export your materials to your other facilities. " +
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { IMap } from "../../types";
|
||||
|
||||
export type CorporationUpgrade = [number, number, number, number, string, string];
|
||||
|
||||
// Corporation Upgrades
|
||||
// Upgrades for entire corporation, levelable upgrades
|
||||
// The data structure is an array with the following format
|
||||
// [index in Corporation upgrades array, base price, price mult, benefit mult (additive), name, desc]
|
||||
export const CorporationUpgrades: IMap<any[]> = {
|
||||
export const CorporationUpgrades: IMap<CorporationUpgrade> = {
|
||||
//Smart factories, increases production
|
||||
"0": [0, 2e9, 1.06, 0.03,
|
||||
"Smart Factories", "Advanced AI automatically optimizes the operation and productivity " +
|
||||
|
@ -1,20 +0,0 @@
|
||||
// Base class for React Components for Corporation UI
|
||||
// Contains a few helper functions that let derived classes easily
|
||||
// access Corporation properties
|
||||
import React from "react";
|
||||
|
||||
const Component = React.Component;
|
||||
|
||||
export class BaseReactComponent extends Component {
|
||||
corp() {
|
||||
return this.props.corp;
|
||||
}
|
||||
|
||||
eventHandler() {
|
||||
return this.props.eventHandler;
|
||||
}
|
||||
|
||||
routing() {
|
||||
return this.props.routing;
|
||||
}
|
||||
}
|
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
|
||||
// (bottom-left panel in the Industry UI)
|
||||
import React from "react";
|
||||
import { BaseReactComponent } from "./BaseReactComponent";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { OfficeSpace } from "../Corporation";
|
||||
import { OfficeSpace } from "../OfficeSpace";
|
||||
import { Employee } from "../Employee";
|
||||
import { EmployeePositions } from "../EmployeePositions";
|
||||
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
import { getSelectText } from "../../../utils/uiHelpers/getSelectData";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { UpgradeOfficeSizePopup } from "./UpgradeOfficeSizePopup";
|
||||
import { HireEmployeePopup } from "./HireEmployeePopup";
|
||||
import { ThrowPartyPopup } from "./ThrowPartyPopup";
|
||||
import { ICorporation } from "../ICorporation";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { CorporationRouting } from "./Routing";
|
||||
|
||||
export class IndustryOffice extends BaseReactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
interface IProps {
|
||||
routing: CorporationRouting;
|
||||
corp: ICorporation;
|
||||
currentCity: string;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
city: "",
|
||||
division: "",
|
||||
employeeManualAssignMode: false,
|
||||
employee: null, // Reference to employee being referenced if in Manual Mode
|
||||
numEmployees: 0,
|
||||
numOperations: 0,
|
||||
numEngineers: 0,
|
||||
numBusiness: 0,
|
||||
numManagement: 0,
|
||||
numResearch: 0,
|
||||
numUnassigned: 0,
|
||||
numTraining: 0,
|
||||
}
|
||||
export function IndustryOffice(props: IProps): React.ReactElement {
|
||||
const [employeeManualAssignMode, setEmployeeManualAssignMode] = useState(false);
|
||||
const [city, setCity] = useState("");
|
||||
const [divisionName, setDivisionName] = useState("");
|
||||
const [employee, setEmployee] = useState<Employee | null>(null);
|
||||
const [numEmployees, setNumEmployees] = useState(0);
|
||||
const [numOperations, setNumOperations] = useState(0);
|
||||
const [numEngineers, setNumEngineers] = useState(0);
|
||||
const [numBusiness, setNumBusiness] = useState(0);
|
||||
const [numManagement, setNumManagement] = useState(0);
|
||||
const [numResearch, setNumResearch] = useState(0);
|
||||
const [numUnassigned, setNumUnassigned] = useState(0);
|
||||
const [numTraining, setNumTraining] = useState(0);
|
||||
|
||||
this.updateEmployeeCount(); // This function validates division and office refs
|
||||
function resetEmployeeCount(): void {
|
||||
setNumEmployees(0);
|
||||
setNumOperations(0);
|
||||
setNumEngineers(0);
|
||||
setNumBusiness(0);
|
||||
setNumManagement(0);
|
||||
setNumResearch(0);
|
||||
setNumUnassigned(0);
|
||||
setNumTraining(0);
|
||||
}
|
||||
|
||||
resetEmployeeCount() {
|
||||
this.state.numEmployees = 0;
|
||||
this.state.numOperations = 0;
|
||||
this.state.numEngineers = 0;
|
||||
this.state.numBusiness = 0;
|
||||
this.state.numManagement = 0;
|
||||
this.state.numResearch = 0;
|
||||
this.state.numUnassigned = 0;
|
||||
this.state.numTraining = 0;
|
||||
}
|
||||
|
||||
updateEmployeeCount() {
|
||||
const division = this.routing().currentDivision;
|
||||
function updateEmployeeCount(): void {
|
||||
const division = props.routing.currentDivision;
|
||||
if (division == null) {
|
||||
throw new Error(`Routing does not hold reference to the current Industry`);
|
||||
}
|
||||
const office = division.offices[this.props.currentCity];
|
||||
const office = division.offices[props.currentCity];
|
||||
if (!(office instanceof OfficeSpace)) {
|
||||
throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`);
|
||||
throw new Error(`Current City (${props.currentCity}) for UI does not have an OfficeSpace object`);
|
||||
}
|
||||
|
||||
// If we're in a new city, we have to reset the state
|
||||
if (division.name !== this.state.division || this.props.currentCity !== this.state.city) {
|
||||
this.resetEmployeeCount();
|
||||
this.state.division = division.name;
|
||||
this.state.city = this.props.currentCity;
|
||||
if (division.name !== divisionName || props.currentCity !== city) {
|
||||
resetEmployeeCount();
|
||||
setDivisionName(division.name);
|
||||
setCity(props.currentCity);
|
||||
}
|
||||
|
||||
// Calculate how many NEW emplyoees we need to account for
|
||||
// Calculate how many NEW employees we need to account for
|
||||
const currentNumEmployees = office.employees.length;
|
||||
|
||||
let newOperations = numOperations;
|
||||
let newEngineers = numEngineers;
|
||||
let newBusiness = numBusiness;
|
||||
let newManagement = numManagement;
|
||||
let newResearch = numResearch;
|
||||
let newUnassigned = numUnassigned;
|
||||
let newTraining = numTraining;
|
||||
|
||||
// Record the number of employees in each position, for NEW employees only
|
||||
for (let i = this.state.numEmployees; i < office.employees.length; ++i) {
|
||||
for (let i = numEmployees; i < office.employees.length; ++i) {
|
||||
switch (office.employees[i].pos) {
|
||||
case EmployeePositions.Operations:
|
||||
++this.state.numOperations;
|
||||
newOperations++;
|
||||
break;
|
||||
case EmployeePositions.Engineer:
|
||||
++this.state.numEngineers;
|
||||
newEngineers++;
|
||||
break;
|
||||
case EmployeePositions.Business:
|
||||
++this.state.numBusiness;
|
||||
newBusiness++;
|
||||
break;
|
||||
case EmployeePositions.Management:
|
||||
++this.state.numManagement;
|
||||
newManagement++;
|
||||
break;
|
||||
case EmployeePositions.RandD:
|
||||
++this.state.numResearch;
|
||||
newResearch++;
|
||||
break;
|
||||
case EmployeePositions.Unassigned:
|
||||
++this.state.numUnassigned;
|
||||
newUnassigned++;
|
||||
break;
|
||||
case EmployeePositions.Training:
|
||||
++this.state.numTraining;
|
||||
newTraining++;
|
||||
break;
|
||||
default:
|
||||
console.error("Unrecognized employee position: " + office.employees[i].pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(newOperations !== numOperations) setNumOperations(newOperations);
|
||||
if(newEngineers !== numEngineers) setNumEngineers(newEngineers);
|
||||
if(newBusiness !== numBusiness) setNumBusiness(newBusiness);
|
||||
if(newManagement !== numManagement) setNumManagement(newManagement);
|
||||
if(newResearch !== numResearch) setNumResearch(newResearch);
|
||||
if(newUnassigned !== numUnassigned) setNumUnassigned(newUnassigned);
|
||||
if(newTraining !== numTraining) setNumTraining(newTraining);
|
||||
|
||||
this.state.numEmployees = currentNumEmployees;
|
||||
if(currentNumEmployees !== numEmployees) setNumEmployees(currentNumEmployees);
|
||||
}
|
||||
|
||||
// Renders the "Employee Management" section of the Office UI
|
||||
renderEmployeeManagement() {
|
||||
this.updateEmployeeCount();
|
||||
updateEmployeeCount();
|
||||
|
||||
if (this.state.employeeManualAssignMode) {
|
||||
return this.renderManualEmployeeManagement();
|
||||
// Renders the "Employee Management" section of the Office UI
|
||||
function renderEmployeeManagement(): React.ReactElement {
|
||||
updateEmployeeCount();
|
||||
|
||||
if (employeeManualAssignMode) {
|
||||
return renderManualEmployeeManagement();
|
||||
} else {
|
||||
return this.renderAutomaticEmployeeManagement();
|
||||
return renderAutomaticEmployeeManagement();
|
||||
}
|
||||
}
|
||||
|
||||
renderAutomaticEmployeeManagement() {
|
||||
const division = this.routing().currentDivision; // Validated in constructor
|
||||
const office = division.offices[this.props.currentCity]; // Validated in constructor
|
||||
const vechain = (this.corp().unlockUpgrades[4] === 1); // Has Vechain upgrade
|
||||
function renderAutomaticEmployeeManagement(): React.ReactElement {
|
||||
const division = props.routing.currentDivision; // Validated in constructor
|
||||
if(division === null) return(<></>);
|
||||
const office = division.offices[props.currentCity]; // Validated in constructor
|
||||
if(office === 0) return (<></>);
|
||||
const vechain = (props.corp.unlockUpgrades[4] === 1); // Has Vechain upgrade
|
||||
|
||||
const switchModeOnClick = () => {
|
||||
this.state.employeeManualAssignMode = true;
|
||||
this.corp().rerender();
|
||||
function switchModeOnClick(): void {
|
||||
setEmployeeManualAssignMode(true);
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
|
||||
// Calculate average morale, happiness, and energy. Also salary
|
||||
@ -135,87 +160,91 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
}
|
||||
|
||||
// Helper functions for (re-)assigning employees to different positions
|
||||
const assignEmployee = (to) => {
|
||||
if (this.state.numUnassigned <= 0) {
|
||||
function assignEmployee(to: string): void {
|
||||
if(office === 0) return;
|
||||
if(division === null) return;
|
||||
if (numUnassigned <= 0) {
|
||||
console.warn("Cannot assign employee. No unassigned employees available");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (to) {
|
||||
case EmployeePositions.Operations:
|
||||
++this.state.numOperations;
|
||||
setNumOperations(n => n+1);
|
||||
break;
|
||||
case EmployeePositions.Engineer:
|
||||
++this.state.numEngineers;
|
||||
setNumEngineers(n => n+1);
|
||||
break;
|
||||
case EmployeePositions.Business:
|
||||
++this.state.numBusiness;
|
||||
setNumBusiness(n => n+1);
|
||||
break;
|
||||
case EmployeePositions.Management:
|
||||
++this.state.numManagement;
|
||||
setNumManagement(n => n+1);
|
||||
break;
|
||||
case EmployeePositions.RandD:
|
||||
++this.state.numResearch;
|
||||
setNumResearch(n => n+1);
|
||||
break;
|
||||
case EmployeePositions.Unassigned:
|
||||
++this.state.numUnassigned;
|
||||
setNumUnassigned(n => n+1);
|
||||
break;
|
||||
case EmployeePositions.Training:
|
||||
++this.state.numTraining;
|
||||
setNumTraining(n => n+1);
|
||||
break;
|
||||
default:
|
||||
console.error("Unrecognized employee position: " + to);
|
||||
break;
|
||||
}
|
||||
--this.state.numUnassigned;
|
||||
setNumUnassigned(n => n-1);
|
||||
|
||||
office.assignEmployeeToJob(to);
|
||||
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
|
||||
this.corp().rerender();
|
||||
office.calculateEmployeeProductivity(props.corp, division);
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
|
||||
const unassignEmployee = (from) => {
|
||||
function logWarning(pos) {
|
||||
function unassignEmployee(from: string): void {
|
||||
if(office === 0) return;
|
||||
if(division === null) return;
|
||||
function logWarning(pos: string): void {
|
||||
console.warn(`Cannot unassign from ${pos} because there is nobody assigned to that position`);
|
||||
}
|
||||
|
||||
switch (from) {
|
||||
case EmployeePositions.Operations:
|
||||
if (this.state.numOperations <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
--this.state.numOperations;
|
||||
if (numOperations <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
setNumOperations(n => n-1);
|
||||
break;
|
||||
case EmployeePositions.Engineer:
|
||||
if (this.state.numEngineers <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
--this.state.numEngineers;
|
||||
if (numEngineers <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
setNumEngineers(n => n-1);
|
||||
break;
|
||||
case EmployeePositions.Business:
|
||||
if (this.state.numBusiness <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
--this.state.numBusiness;
|
||||
if (numBusiness <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
setNumBusiness(n => n-1);
|
||||
break;
|
||||
case EmployeePositions.Management:
|
||||
if (this.state.numManagement <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
--this.state.numManagement;
|
||||
if (numManagement <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
setNumManagement(n => n-1);
|
||||
break;
|
||||
case EmployeePositions.RandD:
|
||||
if (this.state.numResearch <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
--this.state.numResearch;
|
||||
if (numResearch <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
setNumResearch(n => n-1);
|
||||
break;
|
||||
case EmployeePositions.Unassigned:
|
||||
console.warn(`Tried to unassign from the Unassigned position`);
|
||||
break;
|
||||
case EmployeePositions.Training:
|
||||
if (this.state.numTraining <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
--this.state.numTraining;
|
||||
if (numTraining <= 0) { return logWarning(EmployeePositions.Operations); }
|
||||
setNumTraining(n => n-1);
|
||||
break;
|
||||
default:
|
||||
console.error("Unrecognized employee position: " + from);
|
||||
break;
|
||||
}
|
||||
++this.state.numUnassigned;
|
||||
setNumUnassigned(n => n+1);
|
||||
|
||||
office.unassignEmployeeFromJob(from);
|
||||
office.calculateEmployeeProductivity({ corporation: this.corp(), industry:division });
|
||||
this.corp().rerender();
|
||||
office.calculateEmployeeProductivity(props.corp, division);
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
|
||||
const positionHeaderStyle = {
|
||||
@ -223,67 +252,67 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
margin: "5px 0px 5px 0px",
|
||||
width: "50%",
|
||||
}
|
||||
const assignButtonClass = this.state.numUnassigned > 0 ? "std-button" : "a-link-button-inactive";
|
||||
const assignButtonClass = numUnassigned > 0 ? "std-button" : "a-link-button-inactive";
|
||||
|
||||
const operationAssignButtonOnClick = () => {
|
||||
function operationAssignButtonOnClick(): void {
|
||||
assignEmployee(EmployeePositions.Operations);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const operationUnassignButtonOnClick = () => {
|
||||
function operationUnassignButtonOnClick(): void {
|
||||
unassignEmployee(EmployeePositions.Operations);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const operationUnassignButtonClass = this.state.numOperations > 0 ? "std-button" : "a-link-button-inactive";
|
||||
const operationUnassignButtonClass = numOperations > 0 ? "std-button" : "a-link-button-inactive";
|
||||
|
||||
const engineerAssignButtonOnClick = () => {
|
||||
function engineerAssignButtonOnClick(): void {
|
||||
assignEmployee(EmployeePositions.Engineer);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const engineerUnassignButtonOnClick = () => {
|
||||
function engineerUnassignButtonOnClick(): void {
|
||||
unassignEmployee(EmployeePositions.Engineer);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const engineerUnassignButtonClass = this.state.numEngineers > 0 ? "std-button" : "a-link-button-inactive";
|
||||
const engineerUnassignButtonClass = numEngineers > 0 ? "std-button" : "a-link-button-inactive";
|
||||
|
||||
const businessAssignButtonOnClick = () => {
|
||||
function businessAssignButtonOnClick(): void {
|
||||
assignEmployee(EmployeePositions.Business);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const businessUnassignButtonOnClick = () => {
|
||||
function businessUnassignButtonOnClick(): void {
|
||||
unassignEmployee(EmployeePositions.Business);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const businessUnassignButtonClass = this.state.numBusiness > 0 ? "std-button" : "a-link-button-inactive";
|
||||
const businessUnassignButtonClass = numBusiness > 0 ? "std-button" : "a-link-button-inactive";
|
||||
|
||||
const managementAssignButtonOnClick = () => {
|
||||
function managementAssignButtonOnClick(): void {
|
||||
assignEmployee(EmployeePositions.Management);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const managementUnassignButtonOnClick = () => {
|
||||
function managementUnassignButtonOnClick(): void {
|
||||
unassignEmployee(EmployeePositions.Management);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const managementUnassignButtonClass = this.state.numManagement > 0 ? "std-button" : "a-link-button-inactive";
|
||||
const managementUnassignButtonClass = numManagement > 0 ? "std-button" : "a-link-button-inactive";
|
||||
|
||||
const rndAssignButtonOnClick = () => {
|
||||
function rndAssignButtonOnClick(): void {
|
||||
assignEmployee(EmployeePositions.RandD);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const rndUnassignButtonOnClick = () => {
|
||||
function rndUnassignButtonOnClick(): void {
|
||||
unassignEmployee(EmployeePositions.RandD);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const rndUnassignButtonClass = this.state.numResearch > 0 ? "std-button" : "a-link-button-inactive";
|
||||
const rndUnassignButtonClass = numResearch > 0 ? "std-button" : "a-link-button-inactive";
|
||||
|
||||
const trainingAssignButtonOnClick = () => {
|
||||
function trainingAssignButtonOnClick(): void {
|
||||
assignEmployee(EmployeePositions.Training);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const trainingUnassignButtonOnClick = () => {
|
||||
function trainingUnassignButtonOnClick(): void {
|
||||
unassignEmployee(EmployeePositions.Training);
|
||||
this.corp().rerender();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
const trainingUnassignButtonClass = this.state.numTraining > 0 ? "std-button" : "a-link-button-inactive";
|
||||
const trainingUnassignButtonClass = numTraining > 0 ? "std-button" : "a-link-button-inactive";
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -295,7 +324,7 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<p><strong>Unassigned Employees: {this.state.numUnassigned}</strong></p>
|
||||
<p><strong>Unassigned Employees: {numUnassigned}</strong></p>
|
||||
<br />
|
||||
|
||||
<p>Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}</p>
|
||||
@ -344,7 +373,7 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
}
|
||||
|
||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||
{EmployeePositions.Operations} ({this.state.numOperations})
|
||||
{EmployeePositions.Operations} ({numOperations})
|
||||
<span className={"tooltiptext"}>
|
||||
Manages supply chain operations. Improves the amount of Materials and Products you produce.
|
||||
</span>
|
||||
@ -354,7 +383,7 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
<br />
|
||||
|
||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||
{EmployeePositions.Engineer} ({this.state.numEngineers})
|
||||
{EmployeePositions.Engineer} ({numEngineers})
|
||||
<span className={"tooltiptext"}>
|
||||
Develops and maintains products and production systems. Increases the quality of
|
||||
everything you produce. Also increases the amount you produce (not as much
|
||||
@ -366,7 +395,7 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
<br />
|
||||
|
||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||
{EmployeePositions.Business} ({this.state.numBusiness})
|
||||
{EmployeePositions.Business} ({numBusiness})
|
||||
<span className={"tooltiptext"}>
|
||||
Handles sales and finances. Improves the amount of Materials and Products you can sell.
|
||||
</span>
|
||||
@ -376,7 +405,7 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
<br />
|
||||
|
||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||
{EmployeePositions.Management} ({this.state.numManagement})
|
||||
{EmployeePositions.Management} ({numManagement})
|
||||
<span className={"tooltiptext"}>
|
||||
Leads and oversees employees and office operations. Improves the effectiveness of
|
||||
Engineer and Operations employees
|
||||
@ -387,7 +416,7 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
<br />
|
||||
|
||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||
{EmployeePositions.RandD} ({this.state.numResearch})
|
||||
{EmployeePositions.RandD} ({numResearch})
|
||||
<span className={"tooltiptext"}>
|
||||
Research new innovative ways to improve the company. Generates Scientific Research
|
||||
</span>
|
||||
@ -397,7 +426,7 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
<br />
|
||||
|
||||
<h2 className={"tooltip"} style={positionHeaderStyle}>
|
||||
{EmployeePositions.Training} ({this.state.numTraining})
|
||||
{EmployeePositions.Training} ({numTraining})
|
||||
<span className={"tooltiptext"}>
|
||||
Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations.
|
||||
</span>
|
||||
@ -408,14 +437,16 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
)
|
||||
}
|
||||
|
||||
renderManualEmployeeManagement() {
|
||||
const corp = this.corp();
|
||||
const division = this.routing().currentDivision; // Validated in constructor
|
||||
const office = division.offices[this.props.currentCity]; // Validated in constructor
|
||||
function renderManualEmployeeManagement(): React.ReactElement {
|
||||
const corp = props.corp;
|
||||
const division = props.routing.currentDivision; // Validated in constructor
|
||||
if(division === null) return (<></>);
|
||||
const office = division.offices[props.currentCity]; // Validated in constructor
|
||||
if(office === 0) return (<></>);
|
||||
|
||||
const switchModeOnClick = () => {
|
||||
this.state.employeeManualAssignMode = false;
|
||||
this.corp().rerender();
|
||||
function switchModeOnClick(): void {
|
||||
setEmployeeManualAssignMode(false);
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
|
||||
const employeeInfoDivStyle = {
|
||||
@ -430,21 +461,22 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
employees.push(<option key={office.employees[i].name}>{office.employees[i].name}</option>)
|
||||
}
|
||||
|
||||
const employeeSelectorOnChange = (e) => {
|
||||
function employeeSelectorOnChange(e: React.ChangeEvent<HTMLSelectElement>): void {
|
||||
if(office === 0) return;
|
||||
const name = getSelectText(e.target);
|
||||
for (let i = 0; i < office.employees.length; ++i) {
|
||||
if (name === office.employees[i].name) {
|
||||
this.state.employee = office.employees[i];
|
||||
setEmployee(office.employees[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
corp.rerender();
|
||||
corp.rerender(props.player);
|
||||
}
|
||||
|
||||
// Employee Positions Selector
|
||||
const emp = this.state.employee;
|
||||
let employeePositionSelectorInitialValue = null;
|
||||
const emp = employee;
|
||||
let employeePositionSelectorInitialValue = "";
|
||||
const employeePositions = [];
|
||||
const positionNames = Object.values(EmployeePositions);
|
||||
for (let i = 0; i < positionNames.length; ++i) {
|
||||
@ -454,11 +486,12 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
}
|
||||
}
|
||||
|
||||
const employeePositionSelectorOnChange = (e) => {
|
||||
function employeePositionSelectorOnChange(e: React.ChangeEvent<HTMLSelectElement>): void {
|
||||
if(employee === null) return;
|
||||
const pos = getSelectText(e.target);
|
||||
this.state.employee.pos = pos;
|
||||
this.resetEmployeeCount();
|
||||
corp.rerender();
|
||||
employee.pos = pos;
|
||||
resetEmployeeCount();
|
||||
corp.rerender(props.player);
|
||||
}
|
||||
|
||||
// Numeraljs formatter
|
||||
@ -486,29 +519,29 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
{employees}
|
||||
</select>
|
||||
{
|
||||
this.state.employee != null &&
|
||||
employee != null &&
|
||||
<p>
|
||||
Morale: {numeralWrapper.format(this.state.employee.mor, nf)}
|
||||
Morale: {numeralWrapper.format(employee.mor, nf)}
|
||||
<br />
|
||||
Happiness: {numeralWrapper.format(this.state.employee.hap, nf)}
|
||||
Happiness: {numeralWrapper.format(employee.hap, nf)}
|
||||
<br />
|
||||
Energy: {numeralWrapper.format(this.state.employee.ene, nf)}
|
||||
Energy: {numeralWrapper.format(employee.ene, nf)}
|
||||
<br />
|
||||
Intelligence: {numeralWrapper.format(effInt, nf)}
|
||||
<br />
|
||||
Charisma: {numeralWrapper.format(effCha, nf)}
|
||||
<br />
|
||||
Experience: {numeralWrapper.format(this.state.employee.exp, nf)}
|
||||
Experience: {numeralWrapper.format(employee.exp, nf)}
|
||||
<br />
|
||||
Creativity: {numeralWrapper.format(effCre, nf)}
|
||||
<br />
|
||||
Efficiency: {numeralWrapper.format(effEff, nf)}
|
||||
<br />
|
||||
Salary: {numeralWrapper.formatMoney(this.state.employee.sal)}
|
||||
Salary: {numeralWrapper.formatMoney(employee.sal)}
|
||||
</p>
|
||||
}
|
||||
{
|
||||
this.state.employee != null &&
|
||||
employee != null &&
|
||||
<select onChange={employeePositionSelectorOnChange} value={employeePositionSelectorInitialValue}>
|
||||
{employeePositions}
|
||||
</select>
|
||||
@ -518,89 +551,110 @@ export class IndustryOffice extends BaseReactComponent {
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const corp = this.corp();
|
||||
const division = this.routing().currentDivision; // Validated in constructor
|
||||
const office = division.offices[this.props.currentCity]; // Validated in constructor
|
||||
|
||||
const buttonStyle = {
|
||||
fontSize: "13px",
|
||||
}
|
||||
|
||||
// Hire Employee button
|
||||
let hireEmployeeButtonClass = "tooltip";
|
||||
if (office.atCapacity()) {
|
||||
hireEmployeeButtonClass += " a-link-button-inactive";
|
||||
} else {
|
||||
hireEmployeeButtonClass += " std-button";
|
||||
if (office.employees.length === 0) {
|
||||
hireEmployeeButtonClass += " flashing-button";
|
||||
}
|
||||
}
|
||||
|
||||
const hireEmployeeButtonOnClick = () => {
|
||||
office.findEmployees({ corporation: corp, industry: division });
|
||||
}
|
||||
|
||||
// Autohire employee button
|
||||
let autohireEmployeeButtonClass = "tooltip";
|
||||
if (office.atCapacity()) {
|
||||
autohireEmployeeButtonClass += " a-link-button-inactive";
|
||||
} else {
|
||||
autohireEmployeeButtonClass += " std-button";
|
||||
}
|
||||
const autohireEmployeeButtonOnClick = () => {
|
||||
if (office.atCapacity()) { return; }
|
||||
office.hireRandomEmployee();
|
||||
this.corp().rerender();
|
||||
}
|
||||
|
||||
// Upgrade Office Size Button
|
||||
const upgradeOfficeSizeOnClick = this.eventHandler().createUpgradeOfficeSizePopup.bind(this.eventHandler(), office);
|
||||
|
||||
// Throw Office Party
|
||||
const throwOfficePartyOnClick = this.eventHandler().createThrowOfficePartyPopup.bind(this.eventHandler(), office);
|
||||
|
||||
return (
|
||||
<div className={"cmpy-mgmt-employee-panel"}>
|
||||
<h1 style={{ margin: "4px 0px 5px 0px" }}>Office Space</h1>
|
||||
<p>Size: {office.employees.length} / {office.size} employees</p>
|
||||
<button className={hireEmployeeButtonClass} onClick={hireEmployeeButtonOnClick} style={buttonStyle}>
|
||||
Hire Employee
|
||||
{
|
||||
office.employees.length === 0 &&
|
||||
<span className={"tooltiptext"}>
|
||||
You'll need to hire some employees to get your operations started!
|
||||
It's recommended to have at least one employee in every position
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
<button className={autohireEmployeeButtonClass} onClick={autohireEmployeeButtonOnClick} style={buttonStyle}>
|
||||
Autohire Employee
|
||||
<span className={"tooltiptext"}>
|
||||
Automatically hires an employee and gives him/her a random name
|
||||
</span>
|
||||
</button>
|
||||
<br />
|
||||
<button className={"std-button tooltip"} onClick={upgradeOfficeSizeOnClick} style={buttonStyle}>
|
||||
Upgrade size
|
||||
<span className={"tooltiptext"}>
|
||||
Upgrade the office's size so that it can hold more employees!
|
||||
</span>
|
||||
</button>
|
||||
{
|
||||
!division.hasResearch("AutoPartyManager") &&
|
||||
<button className={"std-button tooltip"} onClick={throwOfficePartyOnClick} style={buttonStyle}>
|
||||
Throw Party
|
||||
<span className={"tooltiptext"}>
|
||||
"Throw an office party to increase your employee's morale and happiness"
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
<br />
|
||||
|
||||
{this.renderEmployeeManagement()}
|
||||
</div>
|
||||
)
|
||||
const division = props.routing.currentDivision; // Validated in constructor
|
||||
if(division === null) return (<></>);
|
||||
const office = division.offices[props.currentCity]; // Validated in constructor
|
||||
if(office === 0) return (<></>);
|
||||
const buttonStyle = {
|
||||
fontSize: "13px",
|
||||
}
|
||||
|
||||
// Hire Employee button
|
||||
let hireEmployeeButtonClass = "tooltip";
|
||||
if (office.atCapacity()) {
|
||||
hireEmployeeButtonClass += " a-link-button-inactive";
|
||||
} else {
|
||||
hireEmployeeButtonClass += " std-button";
|
||||
if (office.employees.length === 0) {
|
||||
hireEmployeeButtonClass += " flashing-button";
|
||||
}
|
||||
}
|
||||
|
||||
function openHireEmployeePopup(): void {
|
||||
if(office === 0) return;
|
||||
const popupId = "cmpy-mgmt-hire-employee-popup";
|
||||
createPopup(popupId, HireEmployeePopup, {
|
||||
office: office,
|
||||
corp: props.corp,
|
||||
popupId: popupId,
|
||||
player: props.player,
|
||||
});
|
||||
}
|
||||
|
||||
// Autohire employee button
|
||||
let autohireEmployeeButtonClass = "tooltip";
|
||||
if (office.atCapacity()) {
|
||||
autohireEmployeeButtonClass += " a-link-button-inactive";
|
||||
} else {
|
||||
autohireEmployeeButtonClass += " std-button";
|
||||
}
|
||||
function autohireEmployeeButtonOnClick(): void {
|
||||
if(office === 0) return;
|
||||
if (office.atCapacity()) return;
|
||||
office.hireRandomEmployee();
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
|
||||
function openUpgradeOfficeSizePopup(): void {
|
||||
if(office === 0) return;
|
||||
const popupId = "cmpy-mgmt-upgrade-office-size-popup";
|
||||
createPopup(popupId, UpgradeOfficeSizePopup, {
|
||||
office: office,
|
||||
corp: props.corp,
|
||||
popupId: popupId,
|
||||
player: props.player,
|
||||
});
|
||||
}
|
||||
|
||||
function openThrowPartyPopup(): void {
|
||||
if(office === 0) return;
|
||||
const popupId = "cmpy-mgmt-throw-office-party-popup";
|
||||
createPopup(popupId, ThrowPartyPopup, {
|
||||
office: office,
|
||||
corp: props.corp,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"cmpy-mgmt-employee-panel"}>
|
||||
<h1 style={{ margin: "4px 0px 5px 0px" }}>Office Space</h1>
|
||||
<p>Size: {office.employees.length} / {office.size} employees</p>
|
||||
<button className={hireEmployeeButtonClass} onClick={openHireEmployeePopup} style={buttonStyle}>
|
||||
Hire Employee
|
||||
{
|
||||
office.employees.length === 0 &&
|
||||
<span className={"tooltiptext"}>
|
||||
You'll need to hire some employees to get your operations started!
|
||||
It's recommended to have at least one employee in every position
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
<button className={autohireEmployeeButtonClass} onClick={autohireEmployeeButtonOnClick} style={buttonStyle}>
|
||||
Autohire Employee
|
||||
<span className={"tooltiptext"}>
|
||||
Automatically hires an employee and gives him/her a random name
|
||||
</span>
|
||||
</button>
|
||||
<br />
|
||||
<button className={"std-button tooltip"} onClick={openUpgradeOfficeSizePopup} style={buttonStyle}>
|
||||
Upgrade size
|
||||
<span className={"tooltiptext"}>
|
||||
Upgrade the office's size so that it can hold more employees!
|
||||
</span>
|
||||
</button>
|
||||
{
|
||||
!division.hasResearch("AutoPartyManager") &&
|
||||
<button className={"std-button tooltip"} onClick={openThrowPartyPopup} style={buttonStyle}>
|
||||
Throw Party
|
||||
<span className={"tooltiptext"}>
|
||||
"Throw an office party to increase your employee's morale and happiness"
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
<br />
|
||||
|
||||
{renderEmployeeManagement()}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,20 +1,34 @@
|
||||
// React Component for displaying an Industry's overview information
|
||||
// (top-left panel in the Industry UI)
|
||||
import React from "react";
|
||||
import { BaseReactComponent } from "./BaseReactComponent";
|
||||
|
||||
import { OfficeSpace } from "../Corporation";
|
||||
import { OfficeSpace } from "../OfficeSpace";
|
||||
import { Industries } from "../IndustryData";
|
||||
import { IndustryUpgrades } from "../IndustryUpgrades";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
|
||||
import { MakeProductPopup } from "./MakeProductPopup";
|
||||
import { ResearchPopup } from "./ResearchPopup";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
import { Money } from "../../ui/React/Money";
|
||||
import { ICorporation } from "../ICorporation";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { CorporationRouting } from "./Routing";
|
||||
|
||||
export class IndustryOverview extends BaseReactComponent {
|
||||
renderMakeProductButton() {
|
||||
const division = this.routing().currentDivision; // Validated inside render()
|
||||
interface IProps {
|
||||
routing: CorporationRouting;
|
||||
corp: ICorporation;
|
||||
currentCity: string;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
var createProductButtonText, createProductPopupText;
|
||||
export function IndustryOverview(props: IProps): React.ReactElement {
|
||||
function renderMakeProductButton(): React.ReactElement {
|
||||
const division = props.routing.currentDivision; // Validated inside render()
|
||||
if(division === null) return (<></>);
|
||||
let createProductButtonText = "";
|
||||
let createProductPopupText = "";
|
||||
switch(division.type) {
|
||||
case Industries.Food:
|
||||
createProductButtonText = "Build Restaurant";
|
||||
@ -52,7 +66,7 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
default:
|
||||
createProductButtonText = "Create Product";
|
||||
createProductPopupText = "Create a new product!";
|
||||
return "";
|
||||
return (<></>);
|
||||
}
|
||||
createProductPopupText += "<br><br>To begin developing a product, " +
|
||||
"first choose the city in which it will be designed. The stats of your employees " +
|
||||
@ -65,14 +79,24 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
const hasMaxProducts = division.hasMaximumNumberProducts();
|
||||
|
||||
const className = hasMaxProducts ? "a-link-button-inactive tooltip" : "std-button";
|
||||
const onClick = this.eventHandler().createMakeProductPopup.bind(this.eventHandler(), createProductPopupText, division);
|
||||
const buttonStyle = {
|
||||
margin: "6px",
|
||||
display: "inline-block",
|
||||
}
|
||||
|
||||
function openMakeProductPopup(): void {
|
||||
if(division === null) return;
|
||||
const popupId = "cmpy-mgmt-create-product-popup";
|
||||
createPopup(popupId, MakeProductPopup, {
|
||||
popupText: createProductPopupText,
|
||||
division: division,
|
||||
corp: props.corp,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={className} onClick={onClick} style={buttonStyle}>
|
||||
<button className={className} onClick={openMakeProductPopup} style={buttonStyle}>
|
||||
{createProductButtonText}
|
||||
{
|
||||
hasMaxProducts &&
|
||||
@ -84,17 +108,13 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
)
|
||||
}
|
||||
|
||||
renderText() {
|
||||
const corp = this.corp();
|
||||
const division = this.routing().currentDivision; // Validated inside render()
|
||||
|
||||
function renderText(): React.ReactElement {
|
||||
const corp = props.corp;
|
||||
const division = props.routing.currentDivision; // Validated inside render()
|
||||
if(division === null) return (<></>);
|
||||
const vechain = (corp.unlockUpgrades[4] === 1);
|
||||
const profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber();
|
||||
|
||||
const genInfo = `Industry: ${division.type} (Corp Funds: ${numeralWrapper.formatMoney(corp.funds.toNumber())})`;
|
||||
const awareness = `Awareness: ${numeralWrapper.format(division.awareness, "0.000")}`;
|
||||
const popularity = `Popularity: ${numeralWrapper.format(division.popularity, "0.000")}`;
|
||||
|
||||
let advertisingInfo = false;
|
||||
const advertisingFactors = division.getAdvertisingFactors();
|
||||
const awarenessFac = advertisingFactors[1];
|
||||
@ -103,15 +123,12 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
const totalAdvertisingFac = advertisingFactors[0];
|
||||
if (vechain) { advertisingInfo = true; }
|
||||
|
||||
const revenue = `Revenue: ${numeralWrapper.formatMoney(division.lastCycleRevenue.toNumber())} / s`;
|
||||
const expenses = `Expenses: ${numeralWrapper.formatMoney(division.lastCycleExpenses.toNumber())} /s`;
|
||||
const profitStr = `Profit: ${numeralWrapper.formatMoney(profit)} / s`;
|
||||
|
||||
const productionMultHelpTipOnClick = () => {
|
||||
function productionMultHelpTipOnClick(): void {
|
||||
if(division === null) return;
|
||||
// Wrapper for createProgressBarText()
|
||||
// Converts the industry's "effectiveness factors"
|
||||
// into a graphic (string) depicting how high that effectiveness is
|
||||
function convertEffectFacToGraphic(fac) {
|
||||
function convertEffectFacToGraphic(fac: number): string {
|
||||
return createProgressBarText({
|
||||
progress: fac,
|
||||
totalTicks: 20,
|
||||
@ -136,12 +153,21 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
`Real Estate: ${convertEffectFacToGraphic(division.reFac)}`);
|
||||
}
|
||||
|
||||
function openResearchPopup(): void {
|
||||
if(division === null) return;
|
||||
const popupId = "corporation-research-popup-box";
|
||||
createPopup(popupId, ResearchPopup, {
|
||||
industry: division,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{genInfo}
|
||||
Industry: {division.type} (Corp Funds: <Money money={corp.funds.toNumber()} />)
|
||||
<br /> <br />
|
||||
{awareness} <br />
|
||||
{popularity} <br />
|
||||
Awareness: {numeralWrapper.format(division.awareness, "0.000")} <br />
|
||||
Popularity: {numeralWrapper.format(division.popularity, "0.000")} <br />
|
||||
{
|
||||
(advertisingInfo !== false) &&
|
||||
<p className={"tooltip"}>Advertising Multiplier: x{numeralWrapper.format(totalAdvertisingFac, "0.000")}
|
||||
@ -158,9 +184,9 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
}
|
||||
{advertisingInfo}
|
||||
<br /><br />
|
||||
{revenue} <br />
|
||||
{expenses} <br />
|
||||
{profitStr}
|
||||
Revenue: <Money money={division.lastCycleRevenue.toNumber()} /> / s <br />
|
||||
Expenses: <Money money={division.lastCycleExpenses.toNumber()} /> /s <br />
|
||||
Profit: <Money money={profit} /> / s
|
||||
<br /> <br />
|
||||
<p className={"tooltip"}>
|
||||
Production Multiplier: {numeralWrapper.format(division.prodMult, "0.00")}
|
||||
@ -178,19 +204,20 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
products that you produce.
|
||||
</span>
|
||||
</p>
|
||||
<button className={"help-tip"} onClick={division.createResearchBox.bind(division)}>
|
||||
<button className={"help-tip"} onClick={openResearchPopup}>
|
||||
Research
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderUpgrades() {
|
||||
const corp = this.corp();
|
||||
const division = this.routing().currentDivision; // Validated inside render()
|
||||
const office = division.offices[this.props.currentCity];
|
||||
function renderUpgrades(): React.ReactElement[] {
|
||||
const corp = props.corp;
|
||||
const division = props.routing.currentDivision; // Validated inside render()
|
||||
if(division === null) return ([<></>]);
|
||||
const office = division.offices[props.currentCity];
|
||||
if (!(office instanceof OfficeSpace)) {
|
||||
throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`);
|
||||
throw new Error(`Current City (${props.currentCity}) for UI does not have an OfficeSpace object`);
|
||||
}
|
||||
|
||||
const upgrades = [];
|
||||
@ -213,7 +240,9 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
break;
|
||||
}
|
||||
|
||||
const onClick = () => {
|
||||
function onClick(): void {
|
||||
if(office === 0) return;
|
||||
if(division === null) return;
|
||||
if (corp.funds.lt(cost)) {
|
||||
dialogBoxCreate("Insufficient funds");
|
||||
} else {
|
||||
@ -223,13 +252,14 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
office: office,
|
||||
});
|
||||
// corp.displayDivisionContent(division, city);
|
||||
corp.rerender();
|
||||
corp.rerender(props.player);
|
||||
}
|
||||
}
|
||||
|
||||
upgrades.push(this.renderUpgrade({
|
||||
upgrades.push(renderUpgrade({
|
||||
key: index,
|
||||
onClick: onClick,
|
||||
text: `${upgrade[4]} - ${numeralWrapper.formatMoney(cost)}`,
|
||||
text: <>{upgrade[4]} - <Money money={cost} /></>,
|
||||
tooltip: upgrade[5],
|
||||
}));
|
||||
}
|
||||
@ -237,9 +267,16 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
return upgrades;
|
||||
}
|
||||
|
||||
renderUpgrade(props) {
|
||||
interface IRenderUpgradeProps {
|
||||
key: string;
|
||||
onClick: () => void;
|
||||
text: JSX.Element;
|
||||
tooltip: string;
|
||||
}
|
||||
|
||||
function renderUpgrade(props: IRenderUpgradeProps): React.ReactElement {
|
||||
return (
|
||||
<div className={"cmpy-mgmt-upgrade-div tooltip"} onClick={props.onClick} key={props.text}>
|
||||
<div className={"cmpy-mgmt-upgrade-div tooltip"} onClick={props.onClick} key={props.key}>
|
||||
{props.text}
|
||||
{
|
||||
props.tooltip != null &&
|
||||
@ -249,27 +286,25 @@ export class IndustryOverview extends BaseReactComponent {
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const division = this.routing().currentDivision;
|
||||
if (division == null) {
|
||||
throw new Error(`Routing does not hold reference to the current Industry`);
|
||||
}
|
||||
|
||||
const makeProductButton = this.renderMakeProductButton();
|
||||
|
||||
return (
|
||||
<div className={"cmpy-mgmt-industry-overview-panel"}>
|
||||
{this.renderText()}
|
||||
<br />
|
||||
|
||||
<u className={"industry-purchases-and-upgrades-header"}>Purchases & Upgrades</u><br />
|
||||
{this.renderUpgrades()} <br />
|
||||
|
||||
{
|
||||
division.makesProducts &&
|
||||
makeProductButton
|
||||
}
|
||||
</div>
|
||||
)
|
||||
const division = props.routing.currentDivision;
|
||||
if (division == null) {
|
||||
throw new Error(`Routing does not hold reference to the current Industry`);
|
||||
}
|
||||
|
||||
const makeProductButton = renderMakeProductButton();
|
||||
|
||||
return (
|
||||
<div className={"cmpy-mgmt-industry-overview-panel"}>
|
||||
{renderText()}
|
||||
<br />
|
||||
|
||||
<u className={"industry-purchases-and-upgrades-header"}>Purchases & Upgrades</u><br />
|
||||
{renderUpgrades()} <br />
|
||||
|
||||
{
|
||||
division.makesProducts &&
|
||||
makeProductButton
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,27 +1,47 @@
|
||||
// React Component for displaying an Industry's warehouse information
|
||||
// (right-side panel in the Industry UI)
|
||||
import React from "react";
|
||||
import { BaseReactComponent } from "./BaseReactComponent";
|
||||
|
||||
import { OfficeSpace,
|
||||
WarehouseInitialCost,
|
||||
WarehouseUpgradeBaseCost,
|
||||
ProductProductionCostRatio } from "../Corporation";
|
||||
import { CorporationConstants } from "../data/Constants";
|
||||
import { OfficeSpace } from "../OfficeSpace";
|
||||
import { Material } from "../Material";
|
||||
import { Product } from "../Product";
|
||||
import { Warehouse } from "../Warehouse";
|
||||
import { DiscontinueProductPopup } from "./DiscontinueProductPopup";
|
||||
import { ExportPopup } from "./ExportPopup";
|
||||
import { LimitProductProductionPopup } from "./LimitProductProductionPopup";
|
||||
import { MaterialMarketTaPopup } from "./MaterialMarketTaPopup";
|
||||
import { SellMaterialPopup } from "./SellMaterialPopup";
|
||||
import { SellProductPopup } from "./SellProductPopup";
|
||||
import { PurchaseMaterialPopup } from "./PurchaseMaterialPopup";
|
||||
import { ProductMarketTaPopup } from "./ProductMarketTaPopup";
|
||||
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
import { dialogBoxCreate } from "../../../utils/DialogBox";
|
||||
import { createPopup } from "../../ui/React/createPopup";
|
||||
|
||||
import { isString } from "../../../utils/helpers/isString";
|
||||
import { ICorporation } from "../ICorporation";
|
||||
import { IIndustry } from "../IIndustry";
|
||||
import { CorporationRouting } from "./Routing";
|
||||
import { IPlayer } from "../../PersonObjects/IPlayer";
|
||||
import { SetSmartSupply } from "../Actions";
|
||||
|
||||
|
||||
interface IProductProps {
|
||||
corp: ICorporation;
|
||||
division: IIndustry;
|
||||
city: string;
|
||||
product: Product;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
// Creates the UI for a single Product type
|
||||
function ProductComponent(props) {
|
||||
function ProductComponent(props: IProductProps): React.ReactElement {
|
||||
const corp = props.corp;
|
||||
const division = props.division;
|
||||
const city = props.city;
|
||||
const product = props.product;
|
||||
const eventHandler = props.eventHandler;
|
||||
|
||||
// Numeraljs formatters
|
||||
const nf = "0.000";
|
||||
@ -53,23 +73,53 @@ function ProductComponent(props) {
|
||||
if (isString(product.sCost)) {
|
||||
sellButtonText += (" @ " + product.sCost);
|
||||
} else {
|
||||
sellButtonText += (" @ " + numeralWrapper.format(product.sCost, "$0.000a"));
|
||||
sellButtonText += (" @ " + numeralWrapper.formatMoney(product.sCost as number));
|
||||
}
|
||||
}
|
||||
const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product, city);
|
||||
|
||||
function openSellProductPopup(): void {
|
||||
const popupId = "cmpy-mgmt-limit-product-production-popup";
|
||||
createPopup(popupId, SellProductPopup, {
|
||||
product: product,
|
||||
city: city,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
// Limit Production button
|
||||
let limitProductionButtonText = "Limit Production";
|
||||
if (product.prdman[city][0]) {
|
||||
limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")";
|
||||
}
|
||||
const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product, city);
|
||||
|
||||
// Discontinue Button
|
||||
const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product, division);
|
||||
function openLimitProductProdutionPopup(): void {
|
||||
const popupId = "cmpy-mgmt-limit-product-production-popup";
|
||||
createPopup(popupId, LimitProductProductionPopup, {
|
||||
product: product,
|
||||
city: city,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
// Market TA button
|
||||
const marketTaButtonOnClick = eventHandler.createProductMarketTaPopup.bind(eventHandler, product, division);
|
||||
function openDiscontinueProductPopup(): void {
|
||||
const popupId = "cmpy-mgmt-discontinue-product-popup";
|
||||
createPopup(popupId, DiscontinueProductPopup, {
|
||||
product: product,
|
||||
industry: division,
|
||||
corp: props.corp,
|
||||
popupId: popupId,
|
||||
player: props.player,
|
||||
});
|
||||
}
|
||||
|
||||
function openProductMarketTaPopup(): void {
|
||||
const popupId = "cmpy-mgmt-marketta-popup";
|
||||
createPopup(popupId, ProductMarketTaPopup, {
|
||||
product: product,
|
||||
industry: division,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
// Unfinished Product
|
||||
if (!product.fin) {
|
||||
@ -81,18 +131,18 @@ function ProductComponent(props) {
|
||||
<br />
|
||||
|
||||
<div>
|
||||
<button className={"std-button"} onClick={sellButtonOnClick}>
|
||||
<button className={"std-button"} onClick={openSellProductPopup}>
|
||||
{sellButtonText}
|
||||
</button><br />
|
||||
<button className={"std-button"} onClick={limitProductionButtonOnClick}>
|
||||
<button className={"std-button"} onClick={openLimitProductProdutionPopup}>
|
||||
{limitProductionButtonText}
|
||||
</button>
|
||||
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
||||
<button className={"std-button"} onClick={openDiscontinueProductPopup}>
|
||||
Discontinue
|
||||
</button>
|
||||
{
|
||||
division.hasResearch("Market-TA.I") &&
|
||||
<button className={"std-button"} onClick={marketTaButtonOnClick}>
|
||||
<button className={"std-button"} onClick={openProductMarketTaPopup}>
|
||||
Market-TA
|
||||
</button>
|
||||
}
|
||||
@ -146,7 +196,7 @@ function ProductComponent(props) {
|
||||
</span>
|
||||
</p><br />
|
||||
<p className={"tooltip"}>
|
||||
Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)}
|
||||
Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / CorporationConstants.ProductProductionCostRatio)}
|
||||
<span className={"tooltiptext"}>
|
||||
An estimate of the material cost it takes to create this Product.
|
||||
</span>
|
||||
@ -161,18 +211,18 @@ function ProductComponent(props) {
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<button className={"std-button"} onClick={sellButtonOnClick}>
|
||||
<button className={"std-button"} onClick={openSellProductPopup}>
|
||||
{sellButtonText}
|
||||
</button><br />
|
||||
<button className={"std-button"} onClick={limitProductionButtonOnClick}>
|
||||
<button className={"std-button"} onClick={openLimitProductProdutionPopup}>
|
||||
{limitProductionButtonText}
|
||||
</button>
|
||||
<button className={"std-button"} onClick={discontinueButtonOnClick}>
|
||||
<button className={"std-button"} onClick={openDiscontinueProductPopup}>
|
||||
Discontinue
|
||||
</button>
|
||||
{
|
||||
division.hasResearch("Market-TA.I") &&
|
||||
<button className={"std-button"} onClick={marketTaButtonOnClick}>
|
||||
<button className={"std-button"} onClick={openProductMarketTaPopup}>
|
||||
Market-TA
|
||||
</button>
|
||||
}
|
||||
@ -181,14 +231,21 @@ function ProductComponent(props) {
|
||||
)
|
||||
}
|
||||
|
||||
interface IMaterialProps {
|
||||
corp: ICorporation;
|
||||
division: IIndustry;
|
||||
warehouse: Warehouse;
|
||||
city: string;
|
||||
mat: Material;
|
||||
}
|
||||
|
||||
// Creates the UI for a single Material type
|
||||
function MaterialComponent(props) {
|
||||
function MaterialComponent(props: IMaterialProps): React.ReactElement {
|
||||
const corp = props.corp;
|
||||
const division = props.division;
|
||||
const warehouse = props.warehouse;
|
||||
const city = props.city;
|
||||
const mat = props.mat;
|
||||
const eventHandler = props.eventHandler;
|
||||
const markupLimit = mat.getMarkupLimit();
|
||||
const office = division.offices[city];
|
||||
if (!(office instanceof OfficeSpace)) {
|
||||
@ -210,10 +267,26 @@ function MaterialComponent(props) {
|
||||
// Purchase material button
|
||||
const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nfB)})`;
|
||||
const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button";
|
||||
const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division, warehouse);
|
||||
|
||||
// Export material button
|
||||
const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat);
|
||||
function openPurchaseMaterialPopup(): void {
|
||||
const popupId = "cmpy-mgmt-material-purchase-popup";
|
||||
createPopup(popupId, PurchaseMaterialPopup, {
|
||||
mat: mat,
|
||||
industry: division,
|
||||
warehouse: warehouse,
|
||||
corp: props.corp,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
function openExportPopup(): void {
|
||||
const popupId = "cmpy-mgmt-export-popup";
|
||||
createPopup(popupId, ExportPopup, {
|
||||
mat: mat,
|
||||
corp: props.corp,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
// Sell material button
|
||||
let sellButtonText;
|
||||
@ -221,7 +294,7 @@ function MaterialComponent(props) {
|
||||
if (isString(mat.sllman[1])) {
|
||||
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${mat.sllman[1]})`
|
||||
} else {
|
||||
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1], nfB)})`;
|
||||
sellButtonText = `Sell (${numeralWrapper.format(mat.sll, nfB)}/${numeralWrapper.format(mat.sllman[1] as number, nfB)})`;
|
||||
}
|
||||
|
||||
if (mat.marketTa2) {
|
||||
@ -230,19 +303,34 @@ function MaterialComponent(props) {
|
||||
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.bCost + markupLimit);
|
||||
} else if (mat.sCost) {
|
||||
if (isString(mat.sCost)) {
|
||||
var sCost = mat.sCost.replace(/MP/g, mat.bCost);
|
||||
const sCost = (mat.sCost as string).replace(/MP/g, mat.bCost+'');
|
||||
sellButtonText += " @ " + numeralWrapper.formatMoney(eval(sCost));
|
||||
} else {
|
||||
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.sCost);
|
||||
sellButtonText += " @ " + numeralWrapper.formatMoney(mat.sCost as number);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sellButtonText = "Sell (0.000/0.000)";
|
||||
}
|
||||
const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat);
|
||||
|
||||
// Market TA button
|
||||
const marketTaButtonOnClick = eventHandler.createMaterialMarketTaPopup.bind(eventHandler, mat, division);
|
||||
function openSellMaterialPopup(): void {
|
||||
const popupId = "cmpy-mgmt-material-sell-popup";
|
||||
createPopup(popupId, SellMaterialPopup, {
|
||||
mat: mat,
|
||||
corp: props.corp,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
function openMaterialMarketTaPopup(): void {
|
||||
const popupId = "cmpy-mgmt-export-popup";
|
||||
createPopup(popupId, MaterialMarketTaPopup, {
|
||||
mat: mat,
|
||||
industry: division,
|
||||
corp: props.corp,
|
||||
popupId: popupId,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"cmpy-mgmt-warehouse-material-div"}>
|
||||
@ -286,7 +374,7 @@ function MaterialComponent(props) {
|
||||
</div>
|
||||
|
||||
<div style={{display: "inline-block"}}>
|
||||
<button className={purchaseButtonClass} onClick={purchaseButtonOnClick}>
|
||||
<button className={purchaseButtonClass} onClick={openPurchaseMaterialPopup}>
|
||||
{purchaseButtonText}
|
||||
{
|
||||
tutorial &&
|
||||
@ -298,19 +386,19 @@ function MaterialComponent(props) {
|
||||
|
||||
{
|
||||
corp.unlockUpgrades[0] === 1 &&
|
||||
<button className={"std-button"} onClick={exportButtonOnClick}>
|
||||
<button className={"std-button"} onClick={openExportPopup}>
|
||||
Export
|
||||
</button>
|
||||
}
|
||||
<br />
|
||||
|
||||
<button className={"std-button"} onClick={sellButtonOnClick}>
|
||||
<button className={"std-button"} onClick={openSellMaterialPopup}>
|
||||
{sellButtonText}
|
||||
</button>
|
||||
|
||||
{
|
||||
division.hasResearch("Market-TA.I") &&
|
||||
<button className={"std-button"} onClick={marketTaButtonOnClick}>
|
||||
<button className={"std-button"} onClick={openMaterialMarketTaPopup}>
|
||||
Market-TA
|
||||
</button>
|
||||
}
|
||||
@ -320,10 +408,17 @@ function MaterialComponent(props) {
|
||||
)
|
||||
}
|
||||
|
||||
export class IndustryWarehouse extends BaseReactComponent {
|
||||
interface IProps {
|
||||
corp: ICorporation;
|
||||
routing: CorporationRouting;
|
||||
currentCity: string;
|
||||
player: IPlayer;
|
||||
}
|
||||
|
||||
export function IndustryWarehouse(props: IProps): React.ReactElement {
|
||||
// Returns a boolean indicating whether the given material is relevant for the
|
||||
// current industry.
|
||||
isRelevantMaterial(matName, division) {
|
||||
function isRelevantMaterial(matName: string, division: IIndustry): boolean {
|
||||
// Materials that affect Production multiplier
|
||||
const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"];
|
||||
|
||||
@ -334,10 +429,12 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
return false;
|
||||
}
|
||||
|
||||
renderWarehouseUI() {
|
||||
const corp = this.corp();
|
||||
const division = this.routing().currentDivision; // Validated in render()
|
||||
const warehouse = division.warehouses[this.props.currentCity]; // Validated in render()
|
||||
function renderWarehouseUI(): React.ReactElement {
|
||||
const corp = props.corp;
|
||||
const division = props.routing.currentDivision; // Validated in render()
|
||||
if(division === null) return (<></>);
|
||||
const warehouse = division.warehouses[props.currentCity]; // Validated in render()
|
||||
if(warehouse === 0) return (<></>);
|
||||
|
||||
// General Storage information at the top
|
||||
const sizeUsageStyle = {
|
||||
@ -346,15 +443,16 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
}
|
||||
|
||||
// Upgrade Warehouse size button
|
||||
const sizeUpgradeCost = WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
|
||||
const sizeUpgradeCost = CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
|
||||
const canAffordUpgrade = (corp.funds.gt(sizeUpgradeCost));
|
||||
const upgradeWarehouseClass = canAffordUpgrade ? "std-button" : "a-link-button-inactive";
|
||||
const upgradeWarehouseOnClick = () => {
|
||||
function upgradeWarehouseOnClick(): void {
|
||||
if(division === null) return;
|
||||
if(warehouse === 0) return;
|
||||
++warehouse.level;
|
||||
warehouse.updateSize(corp, division);
|
||||
corp.funds = corp.funds.minus(sizeUpgradeCost);
|
||||
corp.rerender();
|
||||
return;
|
||||
corp.rerender(props.player);
|
||||
}
|
||||
|
||||
// Industry material Requirements
|
||||
@ -416,9 +514,10 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
|
||||
// Smart Supply Checkbox
|
||||
const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox";
|
||||
const smartSupplyOnChange = (e) => {
|
||||
warehouse.smartSupplyEnabled = e.target.checked;
|
||||
corp.rerender();
|
||||
function smartSupplyOnChange(e: React.ChangeEvent<HTMLInputElement>): void {
|
||||
if(warehouse === 0) return;
|
||||
SetSmartSupply(warehouse, e.target.checked);
|
||||
corp.rerender(props.player);
|
||||
}
|
||||
|
||||
// Create React components for materials
|
||||
@ -426,12 +525,11 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
for (const matName in warehouse.materials) {
|
||||
if (warehouse.materials[matName] instanceof Material) {
|
||||
// Only create UI for materials that are relevant for the industry
|
||||
if (this.isRelevantMaterial(matName, division)) {
|
||||
if (isRelevantMaterial(matName, division)) {
|
||||
mats.push(<MaterialComponent
|
||||
city={this.props.currentCity}
|
||||
city={props.currentCity}
|
||||
corp={corp}
|
||||
division={division}
|
||||
eventHandler={this.eventHandler()}
|
||||
key={matName}
|
||||
mat={warehouse.materials[matName]}
|
||||
warehouse={warehouse} />);
|
||||
@ -443,15 +541,16 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
const products = [];
|
||||
if (division.makesProducts && Object.keys(division.products).length > 0) {
|
||||
for (const productName in division.products) {
|
||||
if (division.products[productName] instanceof Product) {
|
||||
const product = division.products[productName];
|
||||
if (product instanceof Product) {
|
||||
products.push(<ProductComponent
|
||||
city={this.props.currentCity}
|
||||
player={props.player}
|
||||
city={props.currentCity}
|
||||
corp={corp}
|
||||
division={division}
|
||||
eventHandler={this.eventHandler()}
|
||||
key={productName}
|
||||
product={division.products[productName]}
|
||||
warehouse={warehouse} />);
|
||||
product={product}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -500,25 +599,36 @@ export class IndustryWarehouse extends BaseReactComponent {
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const division = this.routing().currentDivision;
|
||||
if (division == null) {
|
||||
throw new Error(`Routing does not hold reference to the current Industry`);
|
||||
}
|
||||
const warehouse = division.warehouses[this.props.currentCity];
|
||||
const division = props.routing.currentDivision;
|
||||
if (division == null) {
|
||||
throw new Error(`Routing does not hold reference to the current Industry`);
|
||||
}
|
||||
const warehouse = division.warehouses[props.currentCity];
|
||||
|
||||
const newWarehouseOnClick = this.eventHandler().purchaseWarehouse.bind(this.eventHandler(), division, this.props.currentCity);
|
||||
|
||||
if (warehouse instanceof Warehouse) {
|
||||
return this.renderWarehouseUI();
|
||||
function purchaseWarehouse(division: IIndustry, city: string): void {
|
||||
if (props.corp.funds.lt(CorporationConstants.WarehouseInitialCost)) {
|
||||
dialogBoxCreate("You do not have enough funds to do this!");
|
||||
} else {
|
||||
return (
|
||||
<div className={"cmpy-mgmt-warehouse-panel"}>
|
||||
<button className={"std-button"} onClick={newWarehouseOnClick}>
|
||||
Purchase Warehouse ({numeralWrapper.formatMoney(WarehouseInitialCost)})
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
division.warehouses[city] = new Warehouse({
|
||||
corp: props.corp,
|
||||
industry: division,
|
||||
loc: city,
|
||||
size: CorporationConstants.WarehouseInitialSize,
|
||||
});
|
||||
props.corp.funds = props.corp.funds.minus(CorporationConstants.WarehouseInitialCost);
|
||||
props.corp.rerender(props.player);
|
||||
}
|
||||
}
|
||||
|
||||
if (warehouse instanceof Warehouse) {
|
||||
return renderWarehouseUI();
|
||||
} else {
|
||||
return (
|
||||
<div className={"cmpy-mgmt-warehouse-panel"}>
|
||||
<button className={"std-button"} onClick={() => purchaseWarehouse(division, props.currentCity)}>
|
||||
Purchase Warehouse ({numeralWrapper.formatMoney(CorporationConstants.WarehouseInitialCost)})
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
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";
|
||||
|
||||
// Interfaces for whatever's required to sanitize routing with Corporation Data
|
||||
interface IOfficeSpace {
|
||||
loc: string;
|
||||
cost: number;
|
||||
size: number;
|
||||
comf: number;
|
||||
beau: number;
|
||||
tier: any;
|
||||
minEne: number;
|
||||
maxEne: number;
|
||||
minHap: number;
|
||||
maxHap: number;
|
||||
maxMor: number;
|
||||
employees: any;
|
||||
employeeProd: any;
|
||||
}
|
||||
|
||||
interface IDivision {
|
||||
name: string;
|
||||
offices: IMap<IOfficeSpace>;
|
||||
}
|
||||
|
||||
interface ICorporation {
|
||||
divisions: IDivision[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps track of what content is currently being displayed for the Corporation UI
|
||||
*/
|
||||
@ -39,7 +14,7 @@ export class CorporationRouting {
|
||||
|
||||
// Stores a reference to the Division instance that the routing is currently on
|
||||
// This will be null if routing is on the overview page
|
||||
currentDivision: IDivision | null = null;
|
||||
currentDivision: IIndustry | null = null;
|
||||
|
||||
constructor(corp: ICorporation) {
|
||||
this.corp = corp;
|
||||
|
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() {
|
||||
generateContract({
|
||||
problemType: this.state.codingcontract,
|
||||
@ -1169,6 +1183,16 @@ class DevMenuComponent extends Component {
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button className="std-button" onClick={this.finishCorporationProducts}>Finish products</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button className="std-button" onClick={this.addCorporationResearch}>Tons of research</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
2
src/InteractiveTutorial.d.ts
vendored
2
src/InteractiveTutorial.d.ts
vendored
@ -1,3 +1,3 @@
|
||||
export declare function iTutorialNextStep(): void;
|
||||
export declare const ITutorial: {isRunning: boolean, currStep: number};
|
||||
export declare const ITutorial: {isRunning: boolean; currStep: number};
|
||||
export declare const iTutorialSteps: {[key: string]: number};
|
@ -20,6 +20,18 @@ import { CompanyPosition } from "./Company/CompanyPosition";
|
||||
import { CompanyPositions } from "./Company/CompanyPositions";
|
||||
import { CONSTANTS } from "./Constants";
|
||||
import { DarkWebItems } from "./DarkWeb/DarkWebItems";
|
||||
import {
|
||||
NewIndustry,
|
||||
NewCity,
|
||||
UnlockUpgrade,
|
||||
LevelUpgrade,
|
||||
IssueDividends,
|
||||
SellMaterial,
|
||||
SellProduct,
|
||||
SetSmartSupply,
|
||||
BuyMaterial } from "./Corporation/Actions";
|
||||
import { CorporationUnlockUpgrades } from "./Corporation/data/CorporationUnlockUpgrades";
|
||||
import { CorporationUpgrades } from "./Corporation/data/CorporationUpgrades";
|
||||
import {
|
||||
calculateHackingChance,
|
||||
calculateHackingExpGain,
|
||||
@ -555,6 +567,39 @@ function NetscriptFunctions(workerScript) {
|
||||
return Augmentations[name];
|
||||
}
|
||||
|
||||
function getDivision(divisionName) {
|
||||
const division = Player.corporation.divisions.find(div => div.name === divisionName);
|
||||
if(division === undefined)
|
||||
throw new Error(`No division named '${divisionName}'`);
|
||||
return division;
|
||||
}
|
||||
|
||||
function getWarehouse(divisionName, cityName) {
|
||||
const division = getDivision(divisionName);
|
||||
if(!(cityName in division.warehouses))
|
||||
throw new Error(`Invalid city name '${cityName}'`);
|
||||
const warehouse = division.warehouses[cityName];
|
||||
if(warehouse === 0)
|
||||
throw new Error(`${division.name} has not expanded to '${cityName}'`);
|
||||
return warehouse;
|
||||
}
|
||||
|
||||
function getMaterial(divisionName, cityName, materialName) {
|
||||
const warehouse = getWarehouse(divisionName, cityName);
|
||||
const material = warehouse.materials[materialName];
|
||||
if(material === undefined)
|
||||
throw new Error(`Invalid material name: '${materialName}'`);
|
||||
return material;
|
||||
}
|
||||
|
||||
function getProduct(divisionName, productName) {
|
||||
const division = getDivision(divisionName);
|
||||
const product = division.products[productName];
|
||||
if(product === undefined)
|
||||
throw new Error(`Invalid product name: '${productName}'`);
|
||||
return product;
|
||||
}
|
||||
|
||||
const runAfterReset = function(cbScript=null) {
|
||||
//Run a script after reset
|
||||
if (cbScript && isString(cbScript)) {
|
||||
@ -4097,6 +4142,46 @@ function NetscriptFunctions(workerScript) {
|
||||
},
|
||||
}, // End Bladeburner
|
||||
|
||||
// corporation: {
|
||||
// expandIndustry: function(industryName, divisionName) {
|
||||
// NewIndustry(Player.corporation, industryName, divisionName);
|
||||
// },
|
||||
// expandCity: function(divisionName, cityName) {
|
||||
// const division = getDivision(divisionName);
|
||||
// NewCity(Player.corporation, division, cityName);
|
||||
// },
|
||||
// unlockUpgrade: function(upgradeName) {
|
||||
// const upgrade = Object.values(CorporationUnlockUpgrades).
|
||||
// find(upgrade => upgrade[2] === upgradeName);
|
||||
// if(upgrade === undefined) throw new Error("No upgrade named '${upgradeName}'")
|
||||
// UnlockUpgrade(Player.corporation, upgrade);
|
||||
// },
|
||||
// levelUpgrade: function(upgradeName) {
|
||||
// const upgrade = Object.values(CorporationUpgrades).
|
||||
// find(upgrade => upgrade[4] === upgradeName);
|
||||
// if(upgrade === undefined) throw new Error("No upgrade named '${upgradeName}'")
|
||||
// LevelUpgrade(Player.corporation, upgrade);
|
||||
// },
|
||||
// issueDividends: function(percent) {
|
||||
// IssueDividends(Player.corporation, percent);
|
||||
// },
|
||||
// sellMaterial: function(divisionName, cityName, materialName, amt, price) {
|
||||
// const material = getMaterial(divisionName, cityName, materialName);
|
||||
// SellMaterial(material, amt, price);
|
||||
// },
|
||||
// sellProduct: function(divisionName, cityName, productName, amt, price, all) {
|
||||
// const product = getProduct(divisionName, productName);
|
||||
// SellProduct(product, cityName, amt, price, all);
|
||||
// },
|
||||
// setSmartSupply: function(divisionName, cityName, enabled) {
|
||||
// const warehouse = getWarehouse(divisionName, cityName);
|
||||
// SetSmartSupply(warehouse, enabled);
|
||||
// },
|
||||
// BuyMaterial: function(divisionName, cityName, materialName, amt) {
|
||||
|
||||
// },
|
||||
// }, // End Corporation API
|
||||
|
||||
// Coding Contract API
|
||||
codingcontract: {
|
||||
attempt: function(answer, fn, ip=workerScript.serverIp, { returnReward } = {}) {
|
||||
|
@ -19,7 +19,8 @@ import { LocationName } from "../Locations/data/LocationNames";
|
||||
import { Server } from "../Server/Server";
|
||||
import { IPlayerOwnedSourceFile } from "../SourceFile/PlayerOwnedSourceFile";
|
||||
import { MoneySourceTracker } from "../utils/MoneySourceTracker";
|
||||
import { Exploit } from "../Exploits/Exploit";
|
||||
import { Exploit } from "../Exploits/Exploit";
|
||||
import { ICorporation } from "../Corporation/ICorporation";
|
||||
|
||||
export interface IPlayer {
|
||||
// Class members
|
||||
@ -28,7 +29,7 @@ export interface IPlayer {
|
||||
bitNodeN: number;
|
||||
city: CityName;
|
||||
companyName: string;
|
||||
corporation: any;
|
||||
corporation: ICorporation;
|
||||
currentServer: string;
|
||||
factions: string[];
|
||||
factionInvitations: string[];
|
||||
|
2171
src/ThirdParty/Treant.js
vendored
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) {
|
||||
Engine.hideAllContent();
|
||||
routing.navigateTo(Page.Corporation);
|
||||
Player.corporation.createUI();
|
||||
Player.corporation.createUI(Player);
|
||||
}
|
||||
},
|
||||
|
||||
@ -537,7 +537,7 @@ const Engine = {
|
||||
}
|
||||
|
||||
if (Player.corporation instanceof Corporation) {
|
||||
Player.corporation.clearUI();
|
||||
Player.corporation.clearUI(Player);
|
||||
}
|
||||
|
||||
clearResleevesPage();
|
||||
@ -856,7 +856,7 @@ const Engine = {
|
||||
|
||||
if (Engine.Counters.mechanicProcess <= 0) {
|
||||
if (Player.corporation instanceof Corporation) {
|
||||
Player.corporation.process();
|
||||
Player.corporation.process(Player);
|
||||
}
|
||||
if (Player.bladeburner instanceof Bladeburner) {
|
||||
try {
|
||||
|
@ -641,6 +641,5 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
|
||||
|
||||
<!-- Misc Scripts -->
|
||||
<script src="src/ThirdParty/raphael.min.js"></script>
|
||||
<script src="src/ThirdParty/Treant.js"></script>
|
||||
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user