diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..7dd5e9df2 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-react"] +} diff --git a/css/companymanagement.scss b/css/companymanagement.scss index 8fd71c57c..5fde635a6 100644 --- a/css/companymanagement.scss +++ b/css/companymanagement.scss @@ -56,11 +56,13 @@ .cmpy-mgmt-industry-left-panel, .cmpy-mgmt-industry-right-panel { display: inline-block; - width: 45%; height: 100%; - top: 10px; overflow-y: auto; overflow-x: auto; + overflow: visible; + padding: 2px; + top: 10px; + width: 45%; } .cmpy-mgmt-industry-overview-panel { @@ -115,13 +117,18 @@ background-color: #333; } -/* Upgrades */ +/* Corporation Upgrades */ .cmpy-mgmt-upgrade-container { border: 1px solid #fff; width: 60%; margin: 4px; } +.cmpy-mgmt-upgrade-header { + margin: 6px; + padding: 6px; +} + .cmpy-mgmt-upgrade-div { display: inline-block; border: 1px solid #fff; @@ -136,10 +143,19 @@ background-color: #333; } +/* Industry Upgrades */ +.industry-purchases-and-upgrades-header { + font-size: 14px; + margin: 2px; + padding: 2px; +} + +/* Advertising */ .cmpy-mgmt-advertising-info { font-size: $defaultFontSize * 0.75; } +/* Research */ #corporation-research-popup-box-content { overflow-x: visible !important; } diff --git a/doc/source/basicgameplay/stockmarket.rst b/doc/source/basicgameplay/stockmarket.rst index eb28631ba..e61407b29 100644 --- a/doc/source/basicgameplay/stockmarket.rst +++ b/doc/source/basicgameplay/stockmarket.rst @@ -7,6 +7,11 @@ buy and sell stocks in order to make money. The WSE can be found in the 'City' tab, and is accessible in every city. +Automating the Stock Market +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +You can write scripts to perform automatic and algorithmic trading on the Stock Market. +See :ref:`netscript_tixapi` for more details. + Positions: Long vs Short ^^^^^^^^^^^^^^^^^^^^^^^^ When making a transaction on the stock market, there are two types of positions: diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index e6c945faf..ce6662453 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -6,9 +6,9 @@ Changelog v0.44.1 - 3/4/2019 ------------------ * Duplicate Sleeve changes: -** You can now purchase Augmentations for your Duplicate Sleeves -** Sleeves are now assigned to Shock Recovery task by default -** Shock Recovery and Synchronize tasks are now twice as effective + * You can now purchase Augmentations for your Duplicate Sleeves + * Sleeves are now assigned to Shock Recovery task by default + * Shock Recovery and Synchronize tasks are now twice as effective * Changed documentation so that Netscript functions are own their own pages. Sorry if this is annoying, it was necessary for properly cross-referencing * Officially deprecated the Wiki (the fandom site). Use the 'readthedocs' Documentation instead diff --git a/doc/source/netscript.rst b/doc/source/netscript.rst index 6b0c1b489..f2f1b9aa0 100644 --- a/doc/source/netscript.rst +++ b/doc/source/netscript.rst @@ -5,7 +5,7 @@ Netscript Netscript is the programming language used in the world of Bitburner. When you write scripts in Bitburner, they are written in the Netscript language. -Netscript is simply a subset of `JavaScript `_,. +Netscript is simply a subset of `JavaScript `_. This means that Netscript's syntax is identical to that of JavaScript, but it does not implement some of the features that JavaScript has. diff --git a/package-lock.json b/package-lock.json index f0c79f49b..0fc89cb0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,347 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "7.0.0" + } + }, + "@babel/core": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.3.4.tgz", + "integrity": "sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0", + "@babel/generator": "7.3.4", + "@babel/helpers": "7.3.1", + "@babel/parser": "7.3.4", + "@babel/template": "7.2.2", + "@babel/traverse": "7.3.4", + "@babel/types": "7.3.4", + "convert-source-map": "1.6.0", + "debug": "4.1.1", + "json5": "2.1.0", + "lodash": "4.17.11", + "resolve": "1.5.0", + "semver": "5.5.0", + "source-map": "0.5.7" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "2.1.1" + } + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "1.2.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.4.tgz", + "integrity": "sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==", + "dev": true, + "requires": { + "@babel/types": "7.3.4", + "jsesc": "2.5.2", + "lodash": "4.17.11", + "source-map": "0.5.7", + "trim-right": "1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-builder-react-jsx": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz", + "integrity": "sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==", + "dev": true, + "requires": { + "@babel/types": "7.3.4", + "esutils": "2.0.2" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "7.0.0", + "@babel/template": "7.2.2", + "@babel/types": "7.3.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "7.3.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "dev": true, + "requires": { + "@babel/types": "7.3.4" + } + }, + "@babel/helpers": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.3.1.tgz", + "integrity": "sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==", + "dev": true, + "requires": { + "@babel/template": "7.2.2", + "@babel/traverse": "7.3.4", + "@babel/types": "7.3.4" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "2.4.2", + "esutils": "2.0.2", + "js-tokens": "4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + } + } + }, + "@babel/parser": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", + "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==", + "dev": true + }, + "@babel/plugin-syntax-jsx": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz", + "integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", + "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", + "integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==", + "dev": true, + "requires": { + "@babel/helper-builder-react-jsx": "7.3.0", + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-syntax-jsx": "7.2.0" + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz", + "integrity": "sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-syntax-jsx": "7.2.0" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.2.0.tgz", + "integrity": "sha512-A32OkKTp4i5U6aE88GwwcuV4HAprUgHcTq0sSafLxjr6AW0QahrCRCjxogkbbcdtpbXkuTOlgpjophCxb6sh5g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-syntax-jsx": "7.2.0" + } + }, + "@babel/preset-react": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", + "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "7.0.0", + "@babel/plugin-transform-react-display-name": "7.2.0", + "@babel/plugin-transform-react-jsx": "7.3.0", + "@babel/plugin-transform-react-jsx-self": "7.2.0", + "@babel/plugin-transform-react-jsx-source": "7.2.0" + } + }, + "@babel/template": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0", + "@babel/parser": "7.3.4", + "@babel/types": "7.3.4" + } + }, + "@babel/traverse": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", + "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0", + "@babel/generator": "7.3.4", + "@babel/helper-function-name": "7.1.0", + "@babel/helper-split-export-declaration": "7.0.0", + "@babel/parser": "7.3.4", + "@babel/types": "7.3.4", + "debug": "4.1.1", + "globals": "11.3.0", + "lodash": "4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "2.1.1" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", + "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", + "dev": true, + "requires": { + "esutils": "2.0.2", + "lodash": "4.17.11", + "to-fast-properties": "2.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } + } + }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", @@ -25,6 +366,28 @@ "resolved": "https://registry.npmjs.org/@types/numeral/-/numeral-0.0.25.tgz", "integrity": "sha512-ShHzHkYD+Ldw3eyttptCpUhF1/mkInWwasQkCNXZHOsJMJ/UMa8wXrxSrTJaVk0r4pLK/VnESVM0wFsfQzNEKQ==" }, + "@types/prop-types": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.0.tgz", + "integrity": "sha512-eItQyV43bj4rR3JPV0Skpl1SncRCdziTEK9/v8VwXmV6d/qOUO8/EuWeHBbCZcsfSHfzI5UyMJLCSXtxxznyZg==" + }, + "@types/react": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.6.tgz", + "integrity": "sha512-bN9qDjEMltmHrl0PZRI4IF2AbB7V5UlRfG+OOduckVnRQ4VzXVSzy/1eLAh778IEqhTnW0mmgL9yShfinNverA==", + "requires": { + "@types/prop-types": "15.7.0", + "csstype": "2.6.2" + } + }, + "@types/react-dom": { + "version": "16.8.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.2.tgz", + "integrity": "sha512-MX7n1wq3G/De15RGAAqnmidzhr2Y9O/ClxPxyqaNg96pGyeXUYPSvujgzEVpLo9oIP4Wn1UETl+rxTN02KEpBw==", + "requires": { + "@types/react": "16.8.6" + } + }, "@webassemblyjs/ast": { "version": "1.5.12", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.12.tgz", @@ -642,6 +1005,83 @@ "js-tokens": "3.0.2" } }, + "babel-loader": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.5.tgz", + "integrity": "sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw==", + "dev": true, + "requires": { + "find-cache-dir": "2.0.0", + "loader-utils": "1.1.0", + "mkdirp": "0.5.1", + "util.promisify": "1.0.0" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.0.0.tgz", + "integrity": "sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==", + "dev": true, + "requires": { + "commondir": "1.0.1", + "make-dir": "1.2.0", + "pkg-dir": "3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "3.0.0", + "path-exists": "3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "2.2.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "3.0.0" + } + } + } + }, "bail": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", @@ -2034,6 +2474,15 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", @@ -2309,6 +2758,11 @@ } } }, + "csstype": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.2.tgz", + "integrity": "sha512-Rl7PvTae0pflc1YtxtKbiSqq20Ts6vpIYOD5WBafl4y123DyHUeLrRdQP66sQW8/6gmX8jrYJLXwNeMqYVJcow==" + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -5261,8 +5715,7 @@ "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { "version": "3.6.1", @@ -5940,6 +6393,14 @@ "integrity": "sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA==", "dev": true }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "3.0.2" + } + }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -6800,8 +7261,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -8192,6 +8652,16 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1", + "react-is": "16.8.3" + } + }, "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -8402,6 +8872,33 @@ "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", "dev": true }, + "react": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.3.tgz", + "integrity": "sha512-3UoSIsEq8yTJuSu0luO1QQWYbgGEILm+eJl2QN/VLDi7hL+EN18M3q3oVZwmVzzBJ3DkM7RMdRwBmZZ+b4IzSA==", + "requires": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1", + "prop-types": "15.7.2", + "scheduler": "0.13.3" + } + }, + "react-dom": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.3.tgz", + "integrity": "sha512-ttMem9yJL4/lpItZAQ2NTFAbV7frotHk5DZEHXUOws2rMmrsvh1Na7ThGT0dTzUIl6pqTOi5tYREfL8AEna3lA==", + "requires": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1", + "prop-types": "15.7.2", + "scheduler": "0.13.3" + } + }, + "react-is": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", + "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==" + }, "readable-stream": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", @@ -9179,6 +9676,15 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "scheduler": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz", + "integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==", + "requires": { + "loose-envify": "1.4.0", + "object-assign": "4.1.1" + } + }, "schema-utils": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", @@ -10892,6 +11398,12 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -10962,6 +11474,12 @@ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, "trim-trailing-lines": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz", diff --git a/package.json b/package.json index c85190af6..94a86468a 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ }, "dependencies": { "@types/numeral": "0.0.25", + "@types/react": "^16.8.6", + "@types/react-dom": "^16.8.2", "acorn": "^5.0.0", "acorn-dynamic-import": "^2.0.0", "ajv": "^5.1.5", @@ -31,6 +33,8 @@ "memory-fs": "~0.4.1", "normalize.css": "^8.0.0", "numeral": "2.0.6", + "react": "^16.8.3", + "react-dom": "^16.8.3", "sprintf-js": "^1.1.1", "tapable": "^1.0.0", "uglifyjs-webpack-plugin": "^1.2.5", @@ -39,6 +43,9 @@ }, "description": "A cyberpunk-themed incremental game", "devDependencies": { + "@babel/core": "^7.3.4", + "@babel/preset-react": "^7.0.0", + "babel-loader": "^8.0.5", "beautify-lint": "^1.0.3", "benchmark": "^2.1.1", "bundle-loader": "~0.5.0", diff --git a/src/Constants.ts b/src/Constants.ts index ed1927d30..cb34aeb8a 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -281,17 +281,17 @@ export let CONSTANTS: IMap = { LatestUpdate: ` - v0.44.1 - * Duplicate Sleeve changes: - ** You can now purchase Augmentations for your Duplicate Sleeves - ** Sleeves are now assigned to Shock Recovery task by default - ** Shock Recovery and Synchronize tasks are now twice as effective - - * Changed documentation so that Netscript functions are own their own pages. Sorry if this is annoying, it was necessary for properly cross-referencing - * Officially deprecated the Wiki (the fandom site). Use the 'readthedocs' Documentation instead - * Bug Fix: 'rm' Terminal and Netscript commands now work on non-program files that have '.exe' in the name (by Github user MasonD) - * Bug Fix: The 'Find All Valid Math Expressions' Coding Contract should now properly ignore whitespace in answers - * Bug Fix: The 'Merge Overlapping Intervals' Coding Contract should now properly accept 2D arrays when being attempted through Netscript + v0.45.0 + * Corporation changes: + ** Decreased the time of a full market cycle from 15 seconds to 10 seconds. + ** This means that each Corporation 'state' will now only take 2 seconds, rather than 3 + ** Increased initial salaries for newly-hired employees + ** Increased the cost multiplier for upgrading office size (the cost will increase faster) + ** The stats of your employees now has a slightly larger effect on production & sales + ** Added several new Research upgrades + ** Reduced the amount of Scientific Research needed to unlock the Hi-Tech R&D Laboratory from 10k to 5k + ** Energy Material requirement of the Software industry reduced from 1 to 0.5 + ** Industries now have a maximum number of allowed products, starting at 3. This can be increased through research. ` } diff --git a/src/Corporation/Corporation.js b/src/Corporation/Corporation.js deleted file mode 100644 index 69708e7d3..000000000 --- a/src/Corporation/Corporation.js +++ /dev/null @@ -1,5083 +0,0 @@ -import { AllCorporationStates, - CorporationState } from "./CorporationState"; -import { CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades"; -import { CorporationUpgrades } from "./data/CorporationUpgrades"; -import { EmployeePositions } from "./EmployeePositions"; -import { Industries, - IndustryStartingCosts, - IndustryDescriptions, - IndustryResearchTrees } from "./IndustryData"; -import { IndustryUpgrades } from "./IndustryUpgrades"; -import { Material } from "./Material"; -import { MaterialSizes } from "./MaterialSizes"; -import { Product } from "./Product"; -import { ResearchMap } from "./ResearchMap"; - -import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; -import { CONSTANTS } from "../Constants"; -import { Factions } from "../Faction/Factions"; -import { showLiterature } from "../Literature"; -import { Locations } from "../Locations"; -import { Player } from "../Player"; - -import { numeralWrapper } from "../ui/numeralFormat"; -import { Page, routing } from "../ui/navigationTracking"; - -import { dialogBoxCreate } from "../../utils/DialogBox"; -import { clearSelector } from "../../utils/uiHelpers/clearSelector"; -import { Reviver, - Generic_toJSON, - Generic_fromJSON } from "../../utils/JSONReviver"; -import { appendLineBreaks } from "../../utils/uiHelpers/appendLineBreaks"; -import { createElement } from "../../utils/uiHelpers/createElement"; -import { createPopup } from "../../utils/uiHelpers/createPopup"; -import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton"; -import { formatNumber, generateRandomString } from "../../utils/StringHelperFunctions"; -import { getRandomInt } from "../../utils/helpers/getRandomInt"; -import { isString } from "../../utils/helpers/isString"; -import { KEY } from "../../utils/helpers/keyCodes"; -import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; -import { removeElement } from "../../utils/uiHelpers/removeElement"; -import { removeElementById } from "../../utils/uiHelpers/removeElementById"; -import { yesNoBoxCreate, - yesNoTxtInpBoxCreate, - yesNoBoxGetYesButton, - yesNoBoxGetNoButton, - yesNoTxtInpBoxGetYesButton, - yesNoTxtInpBoxGetNoButton, - yesNoTxtInpBoxGetInput, - yesNoBoxClose, - yesNoTxtInpBoxClose, - yesNoBoxOpen } from "../../utils/YesNoBox"; - -import Decimal from "decimal.js"; - -/* Constants */ -export const INITIALSHARES = 1e9; //Total number of shares you have at your company -export const SHARESPERPRICEUPDATE = 1e6; //When selling large number of shares, price is dynamically updated for every batch of this amount -export const IssueNewSharesCooldown = 216e3; // 12 Hour in terms of game cycles -export const SellSharesCooldown = 18e3; // 1 Hour in terms of game cycles - -export const CyclesPerMarketCycle = 75; -export const CyclesPerIndustryStateCycle = CyclesPerMarketCycle / AllCorporationStates.length; -export const SecsPerMarketCycle = CyclesPerMarketCycle / 5; - -export const Cities = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"]; - -export const WarehouseInitialCost = 5e9; //Initial purchase cost of warehouse -export const WarehouseInitialSize = 100; -export const WarehouseUpgradeBaseCost = 1e9; - -export const OfficeInitialCost = 4e9; -export const OfficeInitialSize = 3; -export const OfficeUpgradeBaseCost = 1e9; - -export const BribeThreshold = 100e12; //Money needed to be able to bribe for faction rep -export const BribeToRepRatio = 1e9; //Bribe Value divided by this = rep gain - -export const ProductProductionCostRatio = 5; //Ratio of material cost of a product to its production cost - -export const DividendMaxPercentage = 50; - -export const CyclesPerEmployeeRaise = 400; // All employees get a raise every X market cycles -export const EmployeeRaiseAmount = 50; // Employee salary increases by this (additive) - -// Delete Research Popup Box when clicking outside of it -$(document).mousedown(function(event) { - const boxId = "corporation-research-popup-box"; - const contentId = "corporation-research-popup-box-content"; - if (researchTreeBoxOpened) { - if ( $(event.target).closest("#" + contentId).get(0) == null ) { - // Delete the box - removeElement(researchTreeBox); - researchTreeBox = null; - researchTreeBoxOpened = false; - } - } -}); - -var empManualAssignmentModeActive = false; -function Industry(params={}) { - this.offices = { //Maps locations to offices. 0 if no office at that location - [Locations.Aevum]: 0, - [Locations.Chongqing]: 0, - [Locations.Sector12]: new OfficeSpace({ - loc:Locations.Sector12, - size:OfficeInitialSize, - }), - [Locations.NewTokyo]: 0, - [Locations.Ishima]: 0, - [Locations.Volhaven]: 0 - }; - - this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location - [Locations.Aevum]: 0, - [Locations.Chonqing]: 0, - [Locations.Sector12]: new Warehouse({ - loc:Locations.Sector12, - size: WarehouseInitialSize, - }), - [Locations.NewTokyo]: 0, - [Locations.Ishima]: 0, - [Locations.Volhaven]: 0 - }; - - this.name = params.name ? params.name : 0; - this.type = params.type ? params.type : 0; - - this.sciResearch = new Material({name: "Scientific Research"}); - this.researched = {}; // Object of acquired Research. Keys = research name - - //A map of the NAME of materials required to create produced materials to - //how many are needed to produce 1 unit of produced materials - this.reqMats = {}; - - //An array of the name of materials being produced - this.prodMats = []; - - this.products = {}; - this.makesProducts = false; - - this.awareness = 0; - this.popularity = 0; //Should always be less than awareness - this.startingCost = 0; - - /* The following are factors for how much production/other things are increased by - different factors. The production increase always has diminishing returns, - and they are all reprsented by exponentials of < 1 (e.g x ^ 0.5, x ^ 0.8) - The number for these represent the exponential. A lower number means more - diminishing returns */ - this.reFac = 0; //Real estate Factor - this.sciFac = 0; //Scientific Research Factor, affects quality - this.hwFac = 0; //Hardware factor - this.robFac = 0; //Robotics Factor - this.aiFac = 0; //AI Cores factor; - this.advFac = 0; //Advertising factor, affects sales - - this.prodMult = 0; //Production multiplier - - //Financials - this.lastCycleRevenue = new Decimal(0); - this.lastCycleExpenses = new Decimal(0); - this.thisCycleRevenue = new Decimal(0); - this.thisCycleExpenses = new Decimal(0); - - //Upgrades - var numUpgrades = Object.keys(IndustryUpgrades).length; - this.upgrades = Array(numUpgrades).fill(0); - - this.state = "START"; - this.newInd = true; - - this.init(); -} - -Industry.prototype.init = function() { - //Set the unique properties of an industry (how much its affected by real estate/scientific research, etc.) - this.startingCost = IndustryStartingCosts[this.type]; - switch (this.type) { - case Industries.Energy: - this.reFac = 0.65; - this.sciFac = 0.7; - this.robFac = 0.05; - this.aiFac = 0.3; - this.advFac = 0.08; - this.reqMats = { - "Hardware": 0.1, - "Metal": 0.2, - }; - this.prodMats = ["Energy"]; - break; - case Industries.Utilities: - case "Utilities": - this.reFac = 0.5; - this.sciFac = 0.6; - this.robFac = 0.4; - this.aiFac = 0.4; - this.advFac = 0.08; - this.reqMats = { - "Hardware": 0.1, - "Metal": 0.1, - } - this.prodMats = ["Water"]; - break; - case Industries.Agriculture: - this.reFac = 0.72; - this.sciFac = 0.5; - this.hwFac = 0.2; - this.robFac = 0.3; - this.aiFac = 0.3; - this.advFac = 0.04; - this.reqMats = { - "Water": 0.5, - "Energy": 0.5, - } - this.prodMats = ["Plants", "Food"]; - break; - case Industries.Fishing: - this.reFac = 0.15; - this.sciFac = 0.35; - this.hwFac = 0.35; - this.robFac = 0.5; - this.aiFac = 0.2; - this.advFac = 0.08; - this.reqMats = { - "Energy": 0.5, - } - this.prodMats = ["Food"]; - break; - case Industries.Mining: - this.reFac = 0.3; - this.sciFac = 0.26; - this.hwFac = 0.4; - this.robFac = 0.45; - this.aiFac = 0.45; - this.advFac = 0.06; - this.reqMats = { - "Energy": 0.8, - } - this.prodMats = ["Metal"]; - break; - case Industries.Food: - //reFac is unique for this bc it diminishes greatly per city. Handle this separately in code? - this.sciFac = 0.12; - this.hwFac = 0.15; - this.robFac = 0.3; - this.aiFac = 0.25; - this.advFac = 0.25; - this.reFac = 0.05; - this.reqMats = { - "Food": 0.5, - "Water": 0.5, - "Energy": 0.2, - } - this.makesProducts = true; - break; - case Industries.Tobacco: - this.reFac = 0.15; - this.sciFac = 0.75; - this.hwFac = 0.15; - this.robFac = 0.2; - this.aiFac = 0.15; - this.advFac = 0.2; - this.reqMats = { - "Plants": 1, - "Water": 0.2, - } - this.makesProducts = true; - break; - case Industries.Chemical: - this.reFac = 0.25; - this.sciFac = 0.75; - this.hwFac = 0.2; - this.robFac = 0.25; - this.aiFac = 0.2; - this.advFac = 0.07; - this.reqMats = { - "Plants": 1, - "Energy": 0.5, - "Water": 0.5, - } - this.prodMats = ["Chemicals"]; - break; - case Industries.Pharmaceutical: - this.reFac = 0.05; - this.sciFac = 0.8; - this.hwFac = 0.15; - this.robFac = 0.25; - this.aiFac = 0.2; - this.advFac = 0.16; - this.reqMats = { - "Chemicals": 2, - "Energy": 1, - "Water": 0.5, - } - this.prodMats = ["Drugs"]; - this.makesProducts = true; - break; - case Industries.Computer: - case "Computer": - this.reFac = 0.2; - this.sciFac = 0.62; - this.robFac = 0.36; - this.aiFac = 0.19; - this.advFac = 0.17; - this.reqMats = { - "Metal": 2, - "Energy": 1, - } - this.prodMats = ["Hardware"]; - this.makesProducts = true; - break; - case Industries.Robotics: - this.reFac = 0.32; - this.sciFac = 0.65; - this.aiFac = 0.36; - this.advFac = 0.18; - this.hwFac = 0.19; - this.reqMats = { - "Hardware": 5, - "Energy": 3, - } - this.prodMats = ["Robots"]; - this.makesProducts = true; - break; - case Industries.Software: - this.sciFac = 0.62; - this.advFac = 0.16; - this.hwFac = 0.25; - this.reFac = 0.1; - this.aiFac = 0.15; - this.robFac = 0.05; - this.reqMats = { - "Hardware": 0.5, - "Energy": 1, - } - this.prodMats = ["AICores"]; - this.makesProducts = true; - break; - case Industries.Healthcare: - this.reFac = 0.1; - this.sciFac = 0.75; - this.advFac = 0.11; - this.hwFac = 0.1; - this.robFac = 0.1; - this.aiFac = 0.1; - this.reqMats = { - "Robots": 10, - "AICores": 5, - "Energy": 5, - "Water": 5, - } - this.makesProducts = true; - break; - case Industries.RealEstate: - this.robFac = 0.6; - this.aiFac = 0.6; - this.advFac = 0.25; - this.sciFac = 0.05; - this.hwFac = 0.05; - this.reqMats = { - "Metal": 5, - "Energy": 5, - "Water": 2, - "Hardware": 4 - } - this.prodMats = ["RealEstate"]; - this.makesProducts = true; - break; - default: - console.log("ERR: Invalid Industry Type passed into Industry.init(): " + this.type); - return; - } -} - -Industry.prototype.getProductDescriptionText = function() { - if (!this.makesProducts) {return;} - switch (this.type) { - case Industries.Food: - return "create and manage restaurants"; - break; - case Industries.Tobacco: - return "create tobacco and tobacco-related products"; - break; - case Industries.Pharmaceutical: - return "develop new pharmaceutical drugs"; - break; - case Industries.Computer: - case "Computer": - return "create new computer hardware and networking infrastructures"; - break; - case Industries.Robotics: - return "build specialized robots and robot-related products"; - break; - case Industries.Software: - return "develop computer software"; - break; - case Industries.Healthcare: - return "build and manage hospitals"; - break; - case Industries.RealEstate: - return "develop and manage real estate properties"; - break; - default: - console.log("ERROR: Invalid industry type in Industry.getProductDescriptionText"); - return ""; - } -} - -//Calculates the values that factor into the production and properties of -//materials/products (such as quality, etc.) -Industry.prototype.calculateProductionFactors = function() { - var multSum = 0; - for (var i = 0; i < Cities.length; ++i) { - var city = Cities[i]; - var warehouse = this.warehouses[city]; - if (!(warehouse instanceof Warehouse)) { - continue; - } - - var materials = warehouse.materials, - office = this.offices[city]; - - var cityMult = Math.pow(0.002 * materials.RealEstate.qty+1, this.reFac) * - Math.pow(0.002 * materials.Hardware.qty+1, this.hwFac) * - Math.pow(0.002 * materials.Robots.qty+1, this.robFac) * - Math.pow(0.002 * materials.AICores.qty+1, this.aiFac); - multSum += Math.pow(cityMult, 0.73); - } - - multSum < 1 ? this.prodMult = 1 : this.prodMult = multSum; -} - -Industry.prototype.updateWarehouseSizeUsed = function(warehouse) { - if (warehouse instanceof Warehouse) { - //This resets the size back to 0 and then accounts for materials - warehouse.updateMaterialSizeUsed(); - } - - for (var prodName in this.products) { - if (this.products.hasOwnProperty(prodName)) { - var prod = this.products[prodName]; - warehouse.sizeUsed += (prod.data[warehouse.loc][0] * prod.siz); - if (prod.data[warehouse.loc][0] > 0) { - warehouse.breakdown += (prodName + ": " + formatNumber(prod.data[warehouse.loc][0] * prod.siz, 0) + "
"); - } - } - } -} - -Industry.prototype.process = function(marketCycles=1, state, company) { - this.state = state; - - //At the start of a cycle, store and reset revenue/expenses - //Then calculate salaries and processs the markets - if (state === "START") { - if (isNaN(this.thisCycleRevenue) || isNaN(this.thisCycleExpenses)) { - console.log("ERROR: NaN in Corporation's computed revenue/expenses"); - console.log(this.thisCycleRevenue.toString()); - console.log(this.thisCycleExpenses.toString()); - dialogBoxCreate("Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer"); - this.thisCycleRevenue = new Decimal(0); - this.thisCycleExpenses = new Decimal(0); - } - this.lastCycleRevenue = this.thisCycleRevenue.dividedBy(marketCycles * SecsPerMarketCycle); - this.lastCycleExpenses = this.thisCycleExpenses.dividedBy(marketCycles * SecsPerMarketCycle); - this.thisCycleRevenue = new Decimal(0); - this.thisCycleExpenses = new Decimal(0); - - //Once you start making revenue, the player should no longer be - //considered new, and therefore no longer needs the 'tutorial' UI elements - if (this.lastCycleRevenue.gt(0)) {this.newInd = false;} - - //Process offices (and the employees in them) - var employeeSalary = 0; - for (var officeLoc in this.offices) { - if (this.offices.hasOwnProperty(officeLoc) && - this.offices[officeLoc] instanceof OfficeSpace) { - employeeSalary += this.offices[officeLoc].process(marketCycles, {industry:this, corporation:company}); - } - } - this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary); - - //Process change in demand/competition of materials/products - this.processMaterialMarket(marketCycles); - this.processProductMarket(marketCycles); - - //Process loss of popularity - this.popularity -= (marketCycles * .0001); - this.popularity = Math.max(0, this.popularity); - - //Process Dreamsense gains - var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4; - if (popularityGain > 0) { - this.popularity += (popularityGain * marketCycles); - this.awareness += (awarenessGain * marketCycles); - } - - return; - } - - //Process production, purchase, and import/export of materials - var res = this.processMaterials(marketCycles, company); - this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); - this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); - - //Process creation, production & sale of products - res = this.processProducts(marketCycles, company); - this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); - this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); - -} - -//Process change in demand and competition for this industry's materials -Industry.prototype.processMaterialMarket = function(marketCycles=1) { - //References to prodMats and reqMats - var reqMats = this.reqMats, prodMats = this.prodMats; - - //Only 'process the market' for materials that this industry deals with - for (var i = 0; i < Cities.length; ++i) { - //If this industry has a warehouse in this city, process the market - //for every material this industry requires or produces - if (this.warehouses[Cities[i]] instanceof Warehouse) { - var wh = this.warehouses[Cities[i]]; - for (var name in reqMats) { - if (reqMats.hasOwnProperty(name)) { - wh.materials[name].processMarket(); - } - } - - //Produced materials are stored in an array - for (var foo = 0; foo < prodMats.length; ++foo) { - wh.materials[prodMats[foo]].processMarket(); - } - - //Process these twice because these boost production - wh.materials["Hardware"].processMarket(); - wh.materials["Robots"].processMarket(); - wh.materials["AICores"].processMarket(); - wh.materials["RealEstate"].processMarket(); - } - } -} - -//Process change in demand and competition for this industry's products -Industry.prototype.processProductMarket = function(marketCycles=1) { - //Demand gradually decreases, and competition gradually increases - for (var name in this.products) { - if (this.products.hasOwnProperty(name)) { - var product = this.products[name]; - var change = getRandomInt(1, 3) * 0.0004; - if (this.type === Industries.Pharmaceutical || this.type === Industries.Software || - this.type === Industries.Robotics) { - change *= 3; - } - change *= marketCycles; - product.dmd -= change; - product.cmp += change; - product.cmp = Math.min(product.cmp, 99.99); - product.dmd = Math.max(product.dmd, 0.001); - } - } -} - -//Process production, purchase, and import/export of materials -Industry.prototype.processMaterials = function(marketCycles=1, company) { - var revenue = 0, expenses = 0, industry = this; - this.calculateProductionFactors(); - - //At the start of the export state, set the imports of everything to 0 - if (this.state === "EXPORT") { - for (var i = 0; i < Cities.length; ++i) { - var city = Cities[i], office = this.offices[city]; - if (!(this.warehouses[city] instanceof Warehouse)) { - continue; - } - var warehouse = this.warehouses[city]; - for (var matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - var mat = warehouse.materials[matName]; - mat.imp = 0; - } - } - } - } - - for (var i = 0; i < Cities.length; ++i) { - var city = Cities[i], office = this.offices[city]; - - if (this.warehouses[city] instanceof Warehouse) { - var warehouse = this.warehouses[city]; - - switch(this.state) { - - case "PURCHASE": - /* Process purchase of materials */ - for (var matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - (function(matName, ind) { - var mat = warehouse.materials[matName]; - var buyAmt, maxAmt; - if (warehouse.smartSupplyEnabled && Object.keys(ind.reqMats).includes(matName)) { - //Smart supply tracker is stored as per second rate - mat.buy = ind.reqMats[matName] * warehouse.smartSupplyStore; - buyAmt = mat.buy * SecsPerMarketCycle * marketCycles; - } else { - buyAmt = (mat.buy * SecsPerMarketCycle * marketCycles); - } - - if (matName == "RealEstate") { - maxAmt = buyAmt; - } else { - maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]); - } - var buyAmt = Math.min(buyAmt, maxAmt); - if (buyAmt > 0) { - mat.qty += buyAmt; - expenses += (buyAmt * mat.bCost); - } - })(matName, industry); - this.updateWarehouseSizeUsed(warehouse); - } - } //End process purchase of materials - break; - - case "PRODUCTION": - warehouse.smartSupplyStore = 0; //Reset smart supply amount - - /* Process production of materials */ - if (this.prodMats.length > 0) { - var mat = warehouse.materials[this.prodMats[0]]; - //Calculate the maximum production of this material based - //on the office's productivity - var maxProd = this.getOfficeProductivity(office) - * this.prodMult // Multiplier from materials - * company.getProductionMultiplier() - * this.getProductionMultiplier(); // Multiplier from Research - let prod; - - if (mat.prdman[0]) { - //Production is manually limited - prod = Math.min(maxProd, mat.prdman[1]); - } else { - prod = maxProd; - } - prod *= (SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle - //Calculate net change in warehouse storage making - //the produced materials will cost - var totalMatSize = 0; - for (var tmp = 0; tmp < this.prodMats.length; ++tmp) { - totalMatSize += (MaterialSizes[this.prodMats[tmp]]); - } - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - var normQty = this.reqMats[reqMatName]; - totalMatSize -= (MaterialSizes[reqMatName] * normQty); - } - } - //If not enough space in warehouse, limit the amount of produced materials - if (totalMatSize > 0) { - var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize); - prod = Math.min(maxAmt, prod); - } - - if (prod < 0) {prod = 0;} - - //Keep track of production for smart supply (/s) - warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles)); - - //Make sure we have enough resource to make our materials - var producableFrac = 1; - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - var req = this.reqMats[reqMatName] * prod; - if (warehouse.materials[reqMatName].qty < req) { - producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); - } - } - } - if (producableFrac <= 0) {producableFrac = 0; prod = 0;} - - //Make our materials if they are producable - if (producableFrac > 0 && prod > 0) { - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac); - warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; - warehouse.materials[reqMatName].prd = 0; - warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles); - } - } - for (var j = 0; j < this.prodMats.length; ++j) { - warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac); - warehouse.materials[this.prodMats[j]].qlt = - (office.employeeProd[EmployeePositions.Engineer] / 100 + - Math.pow(this.sciResearch.qty, this.sciFac) + - Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3); - } - } else { - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - warehouse.materials[reqMatName].prd = 0; - } - } - } - - //Per second - var fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles); - for (var fooI = 0; fooI < this.prodMats.length; ++fooI) { - warehouse.materials[this.prodMats[fooI]].prd = fooProd; - } - } else { - //If this doesn't produce any materials, then it only creates - //Products. Creating products will consume materials. The - //Production of all consumed materials must be set to 0 - for (var reqMatName in this.reqMats) { - if (this.reqMats.hasOwnProperty(reqMatName)) { - warehouse.materials[reqMatName].prd = 0; - } - } - } - break; - - case "SALE": - /* Process sale of materials */ - for (var matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - var mat = warehouse.materials[matName]; - if (mat.sCost < 0 || mat.sllman[0] === false) { - mat.sll = 0; - continue; - } - var mat = warehouse.materials[matName]; - - var sCost; - if (isString(mat.sCost)) { - sCost = mat.sCost.replace(/MP/g, mat.bCost); - sCost = eval(sCost); - } else { - sCost = mat.sCost; - } - - //Calculate how much of the material sells (per second) - let markup = 1, markupLimit = mat.getMarkupLimit(); - if (sCost > mat.bCost) { - //Penalty if difference between sCost and bCost is greater than markup limit - if ((sCost - mat.bCost) > markupLimit) { - markup = Math.pow(markupLimit / (sCost - mat.bCost), 2); - } - } else if (sCost < mat.bCost) { - if (sCost <= 0) { - markup = 1e12; //Sell everything, essentially discard - } else { - //Lower prices than market increases sales - markup = mat.bCost / sCost; - } - } - //var businessFactor = 1 + (office.employeeProd[EmployeePositions.Business] / office.employeeProd["total"]); - var businessFactor = this.getBusinessFactor(office); //Business employee productivity - var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity - var marketFactor = this.getMarketFactor(mat); //Competition + demand - var maxSell = (mat.qlt + .001) - * marketFactor - * markup - * businessFactor - * company.getSalesMultiplier() - * advertisingFactor - * this.getSalesMultiplier(); - var sellAmt; - if (isString(mat.sllman[1])) { - //Dynamically evaluated - var tmp = mat.sllman[1].replace(/MAX/g, maxSell); - tmp = tmp.replace(/PROD/g, mat.prd); - try { - sellAmt = eval(tmp); - } catch(e) { - dialogBoxCreate("Error evaluating your sell amount for material " + mat.name + - " in " + this.name + "'s " + city + " office. The sell amount " + - "is being set to zero"); - sellAmt = 0; - } - sellAmt = Math.min(maxSell, sellAmt); - } else if (mat.sllman[1] === -1) { - //Backwards compatibility, -1 = MAX - sellAmt = maxSell; - } else { - //Player's input value is just a number - sellAmt = Math.min(maxSell, mat.sllman[1]); - } - - sellAmt = (sellAmt * SecsPerMarketCycle * marketCycles); - sellAmt = Math.min(mat.qty, sellAmt); - if (sellAmt < 0) { - console.log("sellAmt calculated to be negative"); - mat.sll = 0; - continue; - } - if (sellAmt && sCost >= 0) { - mat.qty -= sellAmt; - revenue += (sellAmt * sCost); - mat.sll = sellAmt / (SecsPerMarketCycle * marketCycles); - } else { - mat.sll = 0; - } - } - } //End processing of sale of materials - break; - - case "EXPORT": - for (var matName in warehouse.materials) { - if (warehouse.materials.hasOwnProperty(matName)) { - var mat = warehouse.materials[matName]; - mat.totalExp = 0; //Reset export - for (var expI = 0; expI < mat.exp.length; ++expI) { - var exp = mat.exp[expI]; - var amt = exp.amt.replace(/MAX/g, mat.qty / (SecsPerMarketCycle * marketCycles)); - try { - amt = eval(amt); - } catch(e) { - dialogBoxCreate("Calculating export for " + mat.name + " in " + - this.name + "'s " + city + " division failed with " + - "error: " + e); - continue; - } - if (isNaN(amt)) { - dialogBoxCreate("Error calculating export amount for " + mat.name + " in " + - this.name + "'s " + city + " division."); - continue; - } - amt = amt * SecsPerMarketCycle * marketCycles; - - if (mat.qty < amt) { - amt = mat.qty; - } - if (amt === 0) { - break; //None left - } - for (var foo = 0; foo < company.divisions.length; ++foo) { - if (company.divisions[foo].name === exp.ind) { - var expIndustry = company.divisions[foo]; - var expWarehouse = expIndustry.warehouses[exp.city]; - if (!(expWarehouse instanceof Warehouse)) { - console.log("ERROR: Invalid export! " + expIndustry.name + " " + exp.city); - break; - } - - //Make sure theres enough space in warehouse - if (expWarehouse.sizeUsed >= expWarehouse.size) { - return; //Warehouse at capacity - } else { - var maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]); - amt = Math.min(maxAmt, amt); - } - expWarehouse.materials[matName].imp += (amt / (SecsPerMarketCycle * marketCycles)); - expWarehouse.materials[matName].qty += amt; - expWarehouse.materials[matName].qlt = mat.qlt; - mat.qty -= amt; - mat.totalExp += amt; - expIndustry.updateWarehouseSizeUsed(expWarehouse); - break; - } - } - } - //totalExp should be per second - mat.totalExp /= (SecsPerMarketCycle * marketCycles); - } - } - - break; - - case "START": - break; - default: - console.log("ERROR: Invalid state: " + this.state); - break; - } //End switch(this.state) - this.updateWarehouseSizeUsed(warehouse); - - } // End warehouse - - //Produce Scientific Research based on R&D employees - //Scientific Research can be produced without a warehouse - if (office instanceof OfficeSpace) { - this.sciResearch.qty += (.005 - * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5) - * company.getScientificResearchMultiplier() - * this.getScientificResearchMultiplier()); - } - } - return [revenue, expenses]; -} - -//Process production & sale of this industry's FINISHED products (including all of their stats) -Industry.prototype.processProducts = function(marketCycles=1, corporation) { - var revenue = 0, expenses = 0; - - //Create products - if (this.state === "PRODUCTION") { - for (var prodName in this.products) { - if (this.products.hasOwnProperty(prodName)) { - var prod = this.products[prodName]; - if (!prod.fin) { - var city = prod.createCity, office = this.offices[city]; - var total = office.employeeProd[EmployeePositions.Operations] + - office.employeeProd[EmployeePositions.Engineer] + - office.employeeProd[EmployeePositions.Management], ratio; - if (total === 0) { - ratio = 0; - } else { - ratio = office.employeeProd[EmployeePositions.Engineer] / total + - office.employeeProd[EmployeePositions.Operations] / total + - office.employeeProd[EmployeePositions.Management] / total; - } - prod.createProduct(marketCycles, ratio * Math.pow(total, 0.29)); - if (prod.prog >= 100) { - prod.finishProduct(office.employeeProd, this); - } - break; - } - } - } - } - - //Produce Products - for (var prodName in this.products) { - if (this.products.hasOwnProperty(prodName)) { - var prod = this.products[prodName]; - if (prod instanceof Product && prod.fin) { - revenue += this.processProduct(marketCycles, prod, corporation); - } - } - } - return [revenue, expenses]; -} - -//Processes FINISHED products -Industry.prototype.processProduct = function(marketCycles=1, product, corporation) { - var totalProfit = 0; - for (var i = 0; i < Cities.length; ++i) { - var city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city]; - if (warehouse instanceof Warehouse) { - switch(this.state) { - - case "PRODUCTION": - //Calculate the maximum production of this material based - //on the office's productivity - var maxProd = this.getOfficeProductivity(office, {forProduct:true}) - * corporation.getProductionMultiplier() - * this.prodMult // Multiplier from materials - * this.getProductionMultiplier(); // Multiplier from research - let prod; - - //Account for whether production is manually limited - if (product.prdman[city][0]) { - prod = Math.min(maxProd, product.prdman[city][1]); - } else { - prod = maxProd; - } - prod *= (SecsPerMarketCycle * marketCycles); - - //Calculate net change in warehouse storage making the Products will cost - var netStorageSize = product.siz; - for (var reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - var normQty = product.reqMats[reqMatName]; - netStorageSize -= (MaterialSizes[reqMatName] * normQty); - } - } - - //If there's not enough space in warehouse, limit the amount of Product - if (netStorageSize > 0) { - var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / netStorageSize); - prod = Math.min(maxAmt, prod); - } - - warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles)); - - //Make sure we have enough resources to make our Products - var producableFrac = 1; - for (var reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - var req = product.reqMats[reqMatName] * prod; - if (warehouse.materials[reqMatName].qty < req) { - producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); - } - } - } - - //Make our Products if they are producable - if (producableFrac > 0 && prod > 0) { - for (var reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - var reqMatQtyNeeded = (product.reqMats[reqMatName] * prod * producableFrac); - warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; - warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles); - } - } - //Quantity - product.data[city][0] += (prod * producableFrac); - } - - //Keep track of production Per second - product.data[city][1] = prod * producableFrac / (SecsPerMarketCycle * marketCycles); - break; - - case "SALE": - //Process sale of Products - product.pCost = 0; //Estimated production cost - for (var reqMatName in product.reqMats) { - if (product.reqMats.hasOwnProperty(reqMatName)) { - product.pCost += (product.reqMats[reqMatName] * warehouse.materials[reqMatName].bCost); - } - } - - //Since its a product, its production cost is increased for labor - product.pCost *= ProductProductionCostRatio; - - //Calculate Sale Cost (sCost), which could be dynamically evaluated - var sCost; - if (isString(product.sCost)) { - sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku); - sCost = eval(sCost); - } else { - sCost = product.sCost; - } - - var markup = 1, markupLimit = product.rat / product.mku; - if (sCost > product.pCost) { - if ((sCost - product.pCost) > markupLimit) { - markup = markupLimit / (sCost - product.pCost); - } - } - var businessFactor = this.getBusinessFactor(office); //Business employee productivity - var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity - var marketFactor = this.getMarketFactor(product); //Competition + demand - var maxSell = 0.5 - * Math.pow(product.rat, 0.65) - * marketFactor - * corporation.getSalesMultiplier() - * Math.pow(markup, 2) - * businessFactor - * advertisingFactor - * this.getSalesMultiplier(); - var sellAmt; - if (product.sllman[city][0] && isString(product.sllman[city][1])) { - //Sell amount is dynamically evaluated - var tmp = product.sllman[city][1].replace(/MAX/g, maxSell); - tmp = tmp.replace(/PROD/g, product.data[city][1]); - try { - tmp = eval(tmp); - } catch(e) { - dialogBoxCreate("Error evaluating your sell price expression for " + product.name + - " in " + this.name + "'s " + city + " office. Sell price is being set to MAX"); - tmp = maxSell; - } - sellAmt = Math.min(maxSell, tmp); - } else if (product.sllman[city][0] && product.sllman[city][1] > 0) { - //Sell amount is manually limited - sellAmt = Math.min(maxSell, product.sllman[city][1]); - } else { - //Backwards compatibility, -1 = 0 - sellAmt = maxSell; - } - if (sellAmt < 0) { sellAmt = 0; } - sellAmt = sellAmt * SecsPerMarketCycle * marketCycles; - sellAmt = Math.min(product.data[city][0], sellAmt); //data[0] is qty - if (sellAmt && sCost) { - product.data[city][0] -= sellAmt; //data[0] is qty - totalProfit += (sellAmt * sCost); - product.data[city][2] = sellAmt / (SecsPerMarketCycle * marketCycles); //data[2] is sell property - } else { - product.data[city][2] = 0; //data[2] is sell property - } - break; - - case "START": - case "PURCHASE": - case "EXPORT": - break; - default: - console.log("ERROR: Invalid State: " + this.state); - break; - } //End switch(this.state) - } - } - return totalProfit; -} - -Industry.prototype.discontinueProduct = function(product, parentRefs) { - var company = parentRefs.company, industry = parentRefs.industry; - for (var productName in this.products) { - if (this.products.hasOwnProperty(productName)) { - if (product === this.products[productName]) { - delete this.products[productName]; - company.updateUIContent(); - } - } - } -} - -Industry.prototype.upgrade = function(upgrade, refs) { - var corporation = refs.corporation, division = refs.division, - office = refs.office; - var upgN = upgrade[0], basePrice = upgrade[1], priceMult = upgrade[2], - upgradeBenefit = upgrade[3]; - while (this.upgrades.length <= upgN) {this.upgrades.push(0);} - ++this.upgrades[upgN]; - - switch (upgN) { - case 0: //Coffee, 5% energy per employee - for (let i = 0; i < office.employees.length; ++i) { - office.employees[i].ene = Math.min(office.employees[i].ene * 1.05, office.maxEne); - } - break; - case 1: //AdVert.Inc, - var advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier(); - this.awareness += (3 * advMult); - this.popularity += (1 * advMult); - this.awareness *= (1.01 * advMult); - this.popularity *= ((1 + getRandomInt(1, 3) / 100) * advMult); - break; - default: - console.log("ERROR: Un-implemented function index: " + upgN); - break; - } -} - -//Returns how much of a material can be produced based of office productivity (employee stats) -Industry.prototype.getOfficeProductivity = function(office, params) { - var total = office.employeeProd[EmployeePositions.Operations] + - office.employeeProd[EmployeePositions.Engineer] + - office.employeeProd[EmployeePositions.Management], ratio; - if (total === 0) { - ratio = 0; - } else { - ratio = (office.employeeProd[EmployeePositions.Operations] / total) * - (office.employeeProd[EmployeePositions.Engineer] / total) * - (office.employeeProd[EmployeePositions.Management] / total); - ratio = Math.max(0.01, ratio); //Minimum ratio value if you have employees - } - if (params && params.forProduct) { - return ratio * Math.pow(total, 0.22); - } else { - return 2 * ratio * Math.pow(total, 0.3); - } -} - -//Returns a multiplier based on the office' 'Business' employees that affects sales -Industry.prototype.getBusinessFactor = function(office) { - var ratioMult = 1; - if (office.employeeProd["total"] > 0) { - ratioMult = 1 + (office.employeeProd[EmployeePositions.Business] / office.employeeProd["total"]); - } - return ratioMult * Math.pow(1 + office.employeeProd[EmployeePositions.Business], 0.15); -} - -//Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This -//multiplier affects sales. The result is: -// [Total sales mult, total awareness mult, total pop mult, awareness/pop ratio mult] -Industry.prototype.getAdvertisingFactors = function() { - var awarenessFac = Math.pow(this.awareness + 1, this.advFac); - var popularityFac = Math.pow(this.popularity + 1, this.advFac); - var ratioFac = (this.awareness === 0 ? 0.01 : Math.max((this.popularity + .001) / this.awareness, 0.01)); - var totalFac = Math.pow(awarenessFac * popularityFac * ratioFac, 0.85); - return [totalFac, awarenessFac, popularityFac, ratioFac]; -} - -//Returns a multiplier based on a materials demand and competition that affects sales -Industry.prototype.getMarketFactor = function(mat) { - return mat.dmd * (100 - mat.cmp)/100; -} - -// Returns a boolean indicating whether this Industry has the specified Research -Industry.prototype.hasResearch = function(name) { - return (this.researched[name] === true); -} - -Industry.prototype.updateResearchTree = function() { - const researchTree = IndustryResearchTrees[this.type]; - - // Since ResearchTree data isnt saved, we'll update the Research Tree data - // based on the stored 'researched' property in the Industry object - if (Object.keys(researchTree.researched).length !== Object.keys(this.researched).length) { - console.log("Updating Corporation Research Tree Data"); - for (let research in this.researched) { - researchTree.research(research); - } - } -} - -// Get multipliers from Research -Industry.prototype.getAdvertisingMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getAdvertisingMultiplier(); -} - -Industry.prototype.getEmployeeChaMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getEmployeeChaMultiplier(); -} - -Industry.prototype.getEmployeeCreMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getEmployeeCreMultiplier(); -} - -Industry.prototype.getEmployeeEffMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getEmployeeEffMultiplier(); -} - -Industry.prototype.getEmployeeIntMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getEmployeeIntMultiplier(); -} - -Industry.prototype.getProductionMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getProductionMultiplier(); -} - -Industry.prototype.getSalesMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getSalesMultiplier(); -} - -Industry.prototype.getScientificResearchMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getScientificResearchMultiplier(); -} - -Industry.prototype.getStorageMultiplier = function() { - this.updateResearchTree(); - return IndustryResearchTrees[this.type].getStorageMultiplier(); -} - -// Create the Research Tree UI for this Industry -Industry.prototype.createResearchBox = function() { - const boxId = "corporation-research-popup-box"; - - if (researchTreeBoxOpened) { - // It's already opened, so delete it to refresh content - removeElementById(boxId); - researchTreeBox = null; - } - - this.updateResearchTree(); - const researchTree = IndustryResearchTrees[this.type]; - - - // Create the popup first, so that the tree diagram can be added to it - // This is handled by Treant - researchTreeBox = createPopup(boxId, [], { backgroundColor: "black" }); - - // Get the tree's markup (i.e. config) for Treant - const markup = researchTree.createTreantMarkup(); - markup.chart.container = "#" + boxId + "-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, - }, - } - - // Construct the tree with Treant - const treantTree = new 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 (this.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 (this.sciResearch.qty >= research.cost) { - this.sciResearch.qty -= research.cost; - - // Get the Node from the Research Tree and set its 'researched' property - researchTree.research(allResearch[i]); - this.researched[allResearch[i]] = true; - - return this.createResearchBox(); - } else { - dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`); - } - }); - } - - - const boxContent = document.getElementById(`${boxId}-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()}`, - })); - - // Close button - boxContent.appendChild(createPopupCloseButton(researchTreeBox, { - class: "std-button", - display: "block", - innerText: "Close", - })); - } - - researchTreeBoxOpened = true; -} - -Industry.prototype.toJSON = function() { - return Generic_toJSON("Industry", this); -} - -Industry.fromJSON = function(value) { - return Generic_fromJSON(Industry, value.data); -} - -Reviver.constructors.Industry = Industry; - -function Employee(params={}) { - if (!(this instanceof Employee)) { - return new Employee(params); - } - 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.age = params.age ? params.age : getRandomInt(20, 50); - 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.pro = 0; //Productivity, This is calculated - - this.cyclesUntilRaise = CyclesPerEmployeeRaise; - - this.loc = params.loc ? params.loc : ""; - this.pos = EmployeePositions.Unassigned; -} - -//Returns the amount the employee needs to be paid -Employee.prototype.process = function(marketCycles=1, office) { - var gain = 0.001 * marketCycles, - det = gain * Math.random(); - this.age += gain; - this.exp += gain; - if (this.age > 150) { - this.int -= det; - this.eff -= det; - this.cha -= det; - } - - // Employee salaries slowly go up over time - this.cyclesUntilRaise -= marketCycles; - if (this.cyclesUntilRaise <= 0) { - this.salary += EmployeeRaiseAmount; - this.cyclesUntilRaise += CyclesPerEmployeeRaise; - } - - //Training - var 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; - } - - //Weight based on how full office is - //Too many employees = more likely to decrease energy and happiness - var officeCapacityWeight = 0.5 * (office.employees.length / office.size - 0.5); - if (Math.random() < 0.5 - officeCapacityWeight) { - this.ene += det; - this.hap += det; - } else { - this.ene -= det; - this.hap -= det; - } - if (this.ene < office.minEne) {this.ene = office.minEne;} - if (this.hap < office.minHap) {this.hap = office.minHap;} - var salary = this.sal * marketCycles * SecsPerMarketCycle; - return salary; -} - -Employee.prototype.calculateProductivity = function(corporation, industry) { - var 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(); - var prodBase = this.mor * this.hap * this.ene * 1e-6, prodMult; - 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.log("ERROR: Invalid employee position: " + this.pos); - break; - } - return prodBase * prodMult; -} - -//Process benefits from having an office party thrown -Employee.prototype.throwParty = function(money) { - var 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 -Employee.prototype.createUI = function(panel, corporation, industry) { - var 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) + "
" + - "Happiness: " + formatNumber(this.hap, 3) + "
" + - "Energy: " + formatNumber(this.ene, 3) + "
" + - "Age: " + formatNumber(this.age, 3) + "
" + - "Intelligence: " + formatNumber(effInt, 3) + "
" + - "Charisma: " + formatNumber(effCha, 3) + "
" + - "Experience: " + formatNumber(this.exp, 3) + "
" + - "Creativity: " + formatNumber(effCre, 3) + "
" + - "Efficiency: " + formatNumber(effEff, 3) + "
" + - "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s
", - })); - - //Selector for employee position - var selector = createElement("select", {}); - for (var key in EmployeePositions) { - if (EmployeePositions.hasOwnProperty(key)) { - selector.add(createElement("option", { - text: EmployeePositions[key], - value: EmployeePositions[key], - })); - } - } - - selector.addEventListener("change", ()=>{ - this.pos = selector.options[selector.selectedIndex].value; - }); - - //Set initial value of selector - for (var i = 0; i < selector.length; ++i) { - if (selector.options[i].value === this.pos) { - selector.selectedIndex = i; - break; - } - } - panel.appendChild(selector); -} - -Employee.prototype.updateUI = function(panel, corporation, industry) { - var 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(); - if (panel == null) { - console.log("ERROR: Employee.updateUI() called with null panel"); - return; - } - var text = document.getElementById("cmpy-mgmt-employee-" + this.name + "-panel-text"); - if (text == null) { - return this.createUI(panel); - } - text.innerHTML = "Morale: " + formatNumber(this.mor, 3) + "
" + - "Happiness: " + formatNumber(this.hap, 3) + "
" + - "Energy: " + formatNumber(this.ene, 3) + "
" + - "Age: " + formatNumber(this.age, 3) + "
" + - "Intelligence: " + formatNumber(effInt, 3) + "
" + - "Charisma: " + formatNumber(effCha, 3) + "
" + - "Experience: " + formatNumber(this.exp, 3) + "
" + - "Creativity: " + formatNumber(effCre, 3) + "
" + - "Efficiency: " + formatNumber(effEff, 3) + "
" + - "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s
"; -} - -Employee.prototype.toJSON = function() { - return Generic_toJSON("Employee", this); -} - -Employee.fromJSON = function(value) { - return Generic_fromJSON(Employee, value.data); -} - -Reviver.constructors.Employee = Employee; - -var OfficeSpaceTiers = { - Basic: "Basic", - Enhanced: "Enhanced", - Luxurious: "Luxurious", - Extravagant: "Extravagant" -} - -function OfficeSpace(params={}) { - 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; - this.tier = OfficeSpaceTiers.Basic; - - // Min/max energy of employees - this.minEne = 0; - this.maxEne = 100; - - // Min/max Happiness of office - this.minHap = 0; - this.maxHap = 100; - - // Maximum Morale of office - this.maxMor = 100; - - this.employees = []; - this.employeeProd = { - [EmployeePositions.Operations]: 0, - [EmployeePositions.Engineer]: 0, - [EmployeePositions.Business]: 0, - [EmployeePositions.Management]: 0, - [EmployeePositions.RandD]: 0, - total: 0, - }; -} - -OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) { - var corporation = parentRefs.corporation, industry = parentRefs.industry; - - // 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 - var perfMult=1; //Multiplier for employee morale/happiness/energy based on company performance - if (industry.funds < 0 && industry.lastCycleRevenue < 0) { - perfMult = Math.pow(0.99, marketCycles); - } else if (industry.funds > 0 && industry.lastCycleRevenue > 0) { - perfMult = Math.pow(1.01, marketCycles); - } - - const hasAutobrew = industry.hasResearch("AutoBrew"); - const hasAutoparty = industry.hasResearch("AutoPartyManager"); - - var 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(marketCycles, parentRefs); - return salaryPaid; -} - -OfficeSpace.prototype.calculateEmployeeProductivity = function(marketCycles=1, parentRefs) { - var company = parentRefs.corporation, industry = parentRefs.industry; - - //Reset - for (var name in this.employeeProd) { - if (this.employeeProd.hasOwnProperty(name)) { - this.employeeProd[name] = 0; - } - } - - var total = 0; - for (var i = 0; i < this.employees.length; ++i) { - var employee = this.employees[i]; - var prod = employee.calculateProductivity(company, industry); - this.employeeProd[employee.pos] += prod; - total += prod; - } - this.employeeProd["total"] = total; -} - -//Takes care of UI as well -OfficeSpace.prototype.findEmployees = function(parentRefs) { - var company = parentRefs.corporation, division = parentRefs.industry; - if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} - - //Generate three random employees (meh, decent, amazing) - var mult1 = getRandomInt(25, 50)/100, - mult2 = getRandomInt(51, 75)/100, - mult3 = getRandomInt(76, 100)/100; - var int = getRandomInt(50, 100), - cha = getRandomInt(50, 100), - exp = getRandomInt(50, 100), - cre = getRandomInt(50, 100), - eff = getRandomInt(50, 100), - sal = 2.2 * (int + cha + exp + cre + eff); - - var emp1 = new Employee({ - intelligence: int * mult1, - charisma: cha * mult1, - experience: exp * mult1, - creativity: cre * mult1, - efficiency: eff * mult1, - salary: sal * mult1, - }); - - var emp2 = new Employee({ - intelligence: int * mult2, - charisma: cha * mult2, - experience: exp * mult2, - creativity: cre * mult2, - efficiency: eff * mult2, - salary: sal * mult2, - }); - - var emp3 = new Employee({ - intelligence: int * mult3, - charisma: cha * mult3, - experience: exp * mult3, - creativity: cre * mult3, - efficiency: eff * mult3, - salary: sal * mult3, - }); - - var text = createElement("h1", { - innerHTML: "Select one of the following candidates for hire:", - }); - - var createEmpDiv = function(employee, office) { - var div = createElement("div", { - class:"cmpy-mgmt-find-employee-option", - innerHTML: "Intelligence: " + formatNumber(employee.int, 1) + "
" + - "Charisma: " + formatNumber(employee.cha, 1) + "
" + - "Experience: " + formatNumber(employee.exp, 1) + "
" + - "Creativity: " + formatNumber(employee.cre, 1) + "
" + - "Efficiency: " + formatNumber(employee.eff, 1) + "
" + - "Salary: " + numeralWrapper.format(employee.sal, '$0.000a') + " \ s
", - clickListener:()=>{ - office.hireEmployee(employee, parentRefs); - removeElementById("cmpy-mgmt-hire-employee-popup"); - return false; - } - }); - return div; - }; - - var cancelBtn = createElement("a", { - class:"a-link-button", - innerText:"Cancel", - float:"right", - clickListener:()=>{ - removeElementById("cmpy-mgmt-hire-employee-popup"); - return false; - } - }); - - var elems = [text, - createEmpDiv(emp1, this), - createEmpDiv(emp2, this), - createEmpDiv(emp3, this), - cancelBtn]; - - createPopup("cmpy-mgmt-hire-employee-popup", elems); -} - -OfficeSpace.prototype.hireEmployee = function(employee, parentRefs) { - var company = parentRefs.corporation, division = parentRefs.industry; - var yesBtn = yesNoTxtInpBoxGetYesButton(), - noBtn = yesNoTxtInpBoxGetNoButton(); - yesBtn.innerHTML = "Hire"; - noBtn.innerHTML = "Cancel"; - yesBtn.addEventListener("click", ()=>{ - var name = yesNoTxtInpBoxGetInput(); - for (var i = 0; i < this.employees.length; ++i) { - if (this.employees[i].name === name) { - dialogBoxCreate("You already have an employee with this nickname! Please give every employee a unique nickname."); - return false; - } - } - employee.name = name; - this.employees.push(employee); - company.displayDivisionContent(division, currentCityUi); - return yesNoTxtInpBoxClose(); - }); - noBtn.addEventListener("click", ()=>{ - return yesNoTxtInpBoxClose(); - }); - yesNoTxtInpBoxCreate("Give your employee a nickname!"); -} - -OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) { - var company = parentRefs.corporation, division = parentRefs.industry; - if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} - - //Generate three random employees (meh, decent, amazing) - var mult = getRandomInt(76, 100)/100; - var int = getRandomInt(50, 100), - cha = getRandomInt(50, 100), - exp = getRandomInt(50, 100), - cre = getRandomInt(50, 100), - eff = getRandomInt(50, 100), - sal = 2.2 * (int + cha + exp + cre + eff); - - var emp = new Employee({ - intelligence: int * mult, - charisma: cha * mult, - experience: exp * mult, - creativity: cre * mult, - efficiency: eff * mult, - salary: sal * mult, - }); - - var name = generateRandomString(7); - - for (var i = 0; i < this.employees.length; ++i) { - if (this.employees[i].name === name) { - return this.hireRandomEmployee(parentRefs); - } - } - emp.name = name; - this.employees.push(emp); - company.displayDivisionContent(division, currentCityUi); -} - -//Finds the first unassigned employee and assigns its to the specified job -OfficeSpace.prototype.assignEmployeeToJob = function(job) { - for (var 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 -OfficeSpace.prototype.unassignEmployeeFromJob = function(job) { - for (var i = 0; i < this.employees.length; ++i) { - if (this.employees[i].pos === job) { - this.employees[i].pos = EmployeePositions.Unassigned; - return true; - } - } - return false; -} - -OfficeSpace.prototype.toJSON = function() { - return Generic_toJSON("OfficeSpace", this); -} - -OfficeSpace.fromJSON = function(value) { - return Generic_fromJSON(OfficeSpace, value.data); -} - -Reviver.constructors.OfficeSpace = OfficeSpace; - -function Warehouse(params={}) { - this.loc = params.loc ? params.loc : ""; - this.size = params.size ? params.size : 0; - this.level = 0; - this.sizeUsed = 0; - this.smartSupplyEnabled = false; //Whether or not smart supply is enabled - this.breakdown = ""; - - //Stores the amount of product to be produced. Used for Smart Supply unlock. - //The production tracked by smart supply is always based on the previous cycle, - //so it will always trail the "true" production by 1 cycle - this.smartSupplyStore = 0; - - this.materials = { - Water: new Material({name: "Water"}), - Energy: new Material({name: "Energy"}), - Food: new Material({name: "Food"}), - Plants: new Material({name: "Plants"}), - Metal: new Material({name: "Metal"}), - Hardware: new Material({name: "Hardware"}), - Chemicals: new Material({name: "Chemicals"}), - Drugs: new Material({name: "Drugs"}), - Robots: new Material({name: "Robots"}), - AICores: new Material({name: "AI Cores"}), - RealEstate: new Material({name: "Real Estate"}) - } -} - -Warehouse.prototype.updateMaterialSizeUsed = function() { - this.sizeUsed = 0; - this.breakdown = ""; - for (var matName in this.materials) { - if (this.materials.hasOwnProperty(matName)) { - var mat = this.materials[matName]; - if (MaterialSizes.hasOwnProperty(matName)) { - this.sizeUsed += (mat.qty * MaterialSizes[matName]); - if (mat.qty > 0) { - this.breakdown += (matName + ": " + formatNumber(mat.qty * MaterialSizes[matName], 0) + "
"); - } - } - } - } - if (this.sizeUsed > this.size) { - console.log("ERROR: Warehouse size used greater than capacity, something went wrong"); - } -} - -Warehouse.prototype.updateSize = function(corporation, industry) { - //Backwards compatibility - if (this.level == null || this.level === 0) { - this.level = Math.round(this.size / 100); - } - - this.size = (this.level * 100) - * corporation.getStorageMultiplier() - * industry.getStorageMultiplier(); -} - -Warehouse.prototype.createUI = function(parentRefs) { - if (parentRefs.company == null || parentRefs.industry == null) { - console.log("ERROR: Warehouse.createUI called without parentRefs.company or parentRefs.industry"); - return; - } - var company = parentRefs.company, industry = parentRefs.industry; - removeChildrenFromElement(industryWarehousePanel); - industryWarehouseStorageText = createElement("p", { - display:"inline-block", class:"tooltip", - color: this.sizeUsed >= this.size ? "red" : "white", - }); - industryWarehousePanel.appendChild(industryWarehouseStorageText); - - //Upgrade warehouse size button - var upgradeCost = WarehouseUpgradeBaseCost * Math.pow(1.07, this.level+1); - industryWarehouseUpgradeSizeButton = createElement("a", { - innerText:"Upgrade Warehouse Size - " + numeralWrapper.format(upgradeCost, '$0.000a'), - display:"inline-block", - class: company.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button", - clickListener:()=>{ - //Backwards compatibility - if (this.level == null || this.level === 0) { - this.level = Math.round(this.size / 100); - } - - ++this.level; - this.updateSize(company, industry); - company.funds = company.funds.minus(upgradeCost); - this.createUI(parentRefs); - return; - } - }); - industryWarehousePanel.appendChild(industryWarehouseUpgradeSizeButton); - - //Material requirement text - var reqText = "This Industry uses [" + Object.keys(industry.reqMats).join(", ") + - "] in order to "; - if (industry.prodMats.length > 0) { - reqText += "produce [" + industry.prodMats.join(", ") + "] "; - if (industry.makesProducts) { - reqText += " and " + industry.getProductDescriptionText(); - } - } else if (industry.makesProducts) { - reqText += (industry.getProductDescriptionText() + "."); - } - - //Material ratio text for tooltip - var reqRatioText = ". The exact requirements for production are:
"; - for (var matName in industry.reqMats) { - if (industry.reqMats.hasOwnProperty(matName)) { - reqRatioText += ([" *", industry.reqMats[matName], matName].join(" ") + "
"); - } - } - reqRatioText += "in order to create "; - if (industry.prodMats.length > 0) { - reqRatioText += "one of each produced Material (" + industry.prodMats.join(", ") + ") "; - if (industry.makesProducts) { - reqRatioText += "or to create one of its Products"; - } - } else if (industry.makesProducts) { - reqRatioText += "one of its Products"; - } - - reqText += reqRatioText; - - reqText += "

To get started with production, purchase your required " + - "materials or import them from another of your company's divisions.

"; - - industryWarehousePanel.appendChild(createElement("p", { innerHTML: reqText })); - - //Current state - industryWarehouseStateText = createElement("p"); - industryWarehousePanel.appendChild(industryWarehouseStateText); - - //Smart Supply Enable/Disable - if (company.unlockUpgrades[1]) { - if (this.smartSupplyEnabled == null) {this.smartSupplyEnabled = false;} - var smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox"; - industryWarehousePanel.appendChild(createElement("label", { - for:smartSupplyCheckboxId, innerText:"Enable Smart Supply", - color:"white" - })); - industrySmartSupplyCheckbox = createElement("input", { - type:"checkbox", id:smartSupplyCheckboxId, margin:"3px", - changeListener:()=>{ - this.smartSupplyEnabled = industrySmartSupplyCheckbox.checked; - } - }); - industrySmartSupplyCheckbox.checked = this.smartSupplyEnabled; - industryWarehousePanel.appendChild(industrySmartSupplyCheckbox); - } - - //Materials - industryWarehousePanel.appendChild(createElement("p", { - innerHTML: "
Materials:
", - })); - industryWarehouseMaterials = createElement("ul"); - industryWarehousePanel.appendChild(industryWarehouseMaterials); - - //Products - if (industry.makesProducts && Object.keys(industry.products).length > 0) { - industryWarehousePanel.appendChild(createElement("p", { - innerHTML: "
Products:
", - })); - industryWarehouseProducts = createElement("ul"); - industryWarehousePanel.appendChild(industryWarehouseProducts); - } - - this.updateUI(parentRefs); -} - -Warehouse.prototype.updateUI = function(parentRefs) { - if (parentRefs.company == null || parentRefs.industry == null) { - console.log("ERROR: Warehouse.updateUI called without parentRefs.company or parentRefs.industry"); - return; - } - var company = parentRefs.company, industry = parentRefs.industry; - - //Storage text - var storageText = "Storage: " + - (this.sizedUsed >= this.size ? formatNumber(this.sizeUsed, 3) : formatNumber(this.sizeUsed, 3)) + - "/" + formatNumber(this.size, 3); - if (this.breakdown != null && this.breakdown != "") { - storageText += ("" + - this.breakdown + ""); - } - industryWarehouseStorageText.innerHTML = storageText; - - //Upgrade warehouse size button - var upgradeCost = WarehouseUpgradeBaseCost * Math.pow(1.07, this.level+1); - if (company.funds.lt(upgradeCost)) { - industryWarehouseUpgradeSizeButton.className = "a-link-button-inactive"; - } else { - industryWarehouseUpgradeSizeButton.className = "a-link-button"; - } - - //Current state - var stateText = "Current state: "; - switch(industry.state) { - case "START": - stateText += "Preparing..."; - break; - case "PURCHASE": - stateText += "Purchasing materials..."; - break; - case "PRODUCTION": - stateText += "Producing materials and/or products..."; - break; - case "SALE": - stateText += "Selling materials and/or products..."; - break; - case "EXPORT": - stateText += "Exporting materials and/or products..."; - break; - default: - console.log("ERROR: Invalid state: " + industry.state); - break; - } - industryWarehouseStateText.innerText = stateText; - - //Materials - removeChildrenFromElement(industryWarehouseMaterials); - for (var matName in this.materials) { - if (this.materials.hasOwnProperty(matName) && this.materials[matName] instanceof Material) { - if (Object.keys(industry.reqMats).includes(matName) || industry.prodMats.includes(matName) || - matName === "Hardware" || matName === "Robots" || matName === "AICores" || - matName === "RealEstate") { - industryWarehouseMaterials.appendChild(this.createMaterialUI(this.materials[matName], matName, parentRefs)); - } - } - } - - //Products - removeChildrenFromElement(industryWarehouseProducts); - if (industry.makesProducts && Object.keys(industry.products).length > 0) { - for (var productName in industry.products) { - if (industry.products.hasOwnProperty(productName) && industry.products[productName] instanceof Product) { - industryWarehouseProducts.appendChild(this.createProductUI(industry.products[productName], parentRefs)); - } - } - } -} - -Warehouse.prototype.createMaterialUI = function(mat, matName, parentRefs) { - if (parentRefs.company == null || parentRefs.industry == null) { - console.log("ERROR: Warehouse.createMaterialUI called without industry or company parent refs"); - return; - } - var company = parentRefs.company, industry = parentRefs.industry; - var purchasePopupId = "cmpy-mgmt-material-purchase-popup", - sellPopupid = "cmpy-mgmt-material-sell-popup"; - var div = createElement("div", { - class:"cmpy-mgmt-warehouse-material-div", - }); - - var totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp; - - //If Market Research upgrades are unlocked, add competition and demand info - var cmpAndDmdText = ""; - if (company.unlockUpgrades[2] === 1) { - cmpAndDmdText += "
Demand: " + formatNumber(mat.dmd, 3); - } - if (company.unlockUpgrades[3] === 1) { - cmpAndDmdText += "
Competition: " + formatNumber(mat.cmp, 3); - } - var innerTxt = "

" + mat.name + ": " + formatNumber(mat.qty, 3) + - "(" + formatNumber(totalGain, 3) + "/s)" + - "Buy: " + formatNumber(mat.buy, 3) + - "/s
Prod: " + formatNumber(mat.prd, 3) + "/s
Sell: " + formatNumber(mat.sll, 3) + - "/s
Export: " + formatNumber(mat.totalExp, 3) + "/s
Import: " + - formatNumber(mat.imp, 3) + "/s" + cmpAndDmdText + "


" + - "

MP: $" + formatNumber(mat.bCost, 2) + - "Market Price: The price you would pay if " + - "you were to buy this material on the market


" + - "

Quality: " + formatNumber(mat.qlt, 2) + - "The quality of your material. Higher quality " + - "will lead to more sales

"; - - div.appendChild(createElement("p", { - innerHTML: innerTxt, - id: "cmpy-mgmt-warehouse-" + matName + "-text", display:"inline-block", - })); - - var buttonPanel = createElement("div", { - display:"inline-block", - }); - div.appendChild(buttonPanel); - - //Button to set purchase amount - var tutorial = industry.newInd && Object.keys(industry.reqMats).includes(mat.name) && - mat.buy === 0 && mat.imp === 0; - var buyButtonParams = { - innerText: "Buy (" + formatNumber(mat.buy, 3) + ")", display:"inline-block", - class: tutorial ? "a-link-button flashing-button" : "a-link-button", - clickListener:()=>{ - var txt = createElement("p", { - innerHTML: "Enter the amount of " + mat.name + " you would like " + - "to purchase per second. This material's cost changes constantly" - }); - var confirmBtn; - var input = createElement("input", { - margin: "5px", - placeholder: "Purchase amount", - type: "number", - value: mat.buy ? mat.buy : null, - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - confirmBtn = createElement("button", { - innerText:"Confirm", class:"std-button", - clickListener:()=>{ - if (isNaN(input.value)) { - dialogBoxCreate("Invalid amount"); - } else { - mat.buy = parseFloat(input.value); - if (isNaN(mat.buy)) {mat.buy = 0;} - removeElementById(purchasePopupId); - this.createUI(parentRefs); - return false; - } - } - }); - var clearButton = createElement("button", { - innerText:"Clear Purchase", class:"std-button", - clickListener:()=>{ - mat.buy = 0; - removeElementById(purchasePopupId); - this.createUI(parentRefs); - return false; - } - }); - const cancelBtn = createPopupCloseButton(purchasePopupId, { - class: "std-button", - innerText: "Cancel", - }); - - createPopup(purchasePopupId, [txt, input, confirmBtn, clearButton, cancelBtn]); - input.focus(); - } - }; - if (tutorial) { - buyButtonParams.tooltip = "Purchase your required materials to get production started!"; - } - buttonPanel.appendChild(createElement("a", buyButtonParams)); - - //Button to manage exports - if (company.unlockUpgrades[0] === 1) { //Export unlock upgrade - function createExportPopup() { - var popupId = "cmpy-mgmt-export-popup"; - var exportTxt = createElement("p", { - innerText:"Select the industry and city to export this material to, as well as " + - "how much of this material to export per second. You can set the export " + - "amount to 'MAX' to export all of the materials in this warehouse." - }); - - //Select industry and city to export to - var citySelector = createElement("select", {class: "dropdown"}); - var industrySelector = createElement("select", { - class: "dropdown", - changeListener:()=>{ - var industryName = industrySelector.options[industrySelector.selectedIndex].value; - for (var foo = 0; foo < company.divisions.length; ++foo) { - if (company.divisions[foo].name == industryName) { - clearSelector(citySelector); - var selectedIndustry = company.divisions[foo]; - for (var cityName in company.divisions[foo].warehouses) { - if (company.divisions[foo].warehouses[cityName] instanceof Warehouse) { - citySelector.add(createElement("option", { - value:cityName, text:cityName, - })); - } - } - return; - } - } - } - }); - - for (var i = 0; i < company.divisions.length; ++i) { - industrySelector.add(createElement("option", { - text:company.divisions[i].name, value:company.divisions[i].name, - })); //End create element option - } //End for - - var currIndustry = industrySelector.options[industrySelector.selectedIndex].value; - for (var i = 0; i < company.divisions.length; ++i) { - if (company.divisions[i].name == currIndustry) { - for (var cityName in company.divisions[i].warehouses) { - if (company.divisions[i].warehouses.hasOwnProperty(cityName) && - company.divisions[i].warehouses[cityName] instanceof Warehouse) { - citySelector.add(createElement("option", { - value:cityName, text:cityName, - })); - } - } - break; - } - } - - //Select amount to export - var exportAmount = createElement("input", { - class: "text-input", - placeholder:"Export amount / s" - }); - - var exportBtn = createElement("a", { - class:"a-link-button", display:"inline-block", innerText:"Export", - clickListener:()=>{ - var industryName = industrySelector.options[industrySelector.selectedIndex].text, - cityName = citySelector.options[citySelector.selectedIndex].text, - amt = exportAmount.value; - //Sanitize amt - var sanitizedAmt = amt.replace(/\s+/g, ''); - sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, ''); - var temp = sanitizedAmt.replace(/MAX/g, 1); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid expression entered for export amount: " + e); - return false; - } - - if (temp == null || isNaN(temp)) { - dialogBoxCreate("Invalid amount entered for export"); - return; - } - var exportObj = {ind:industryName, city:cityName, amt:sanitizedAmt}; - mat.exp.push(exportObj); - removeElementById(popupId); - return false; - } - }); - - var cancelBtn = createElement("a", { - class:"a-link-button", display:"inline-block", innerText:"Cancel", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - - var currExportsText = createElement("p", { - innerText:"Below is a list of all current exports of this material from this warehouse. " + - "Clicking on one of the exports below will REMOVE that export." - }); - var currExports = []; - for (var i = 0; i < mat.exp.length; ++i) { - (function(i, mat, currExports){ - currExports.push(createElement("div", { - class:"cmpy-mgmt-existing-export", - innerHTML: "Industry: " + mat.exp[i].ind + "
" + - "City: " + mat.exp[i].city + "
" + - "Amount/s: " + mat.exp[i].amt, - clickListener:()=>{ - mat.exp.splice(i, 1); //Remove export object - removeElementById(popupId); - createExportPopup(); - } - })); - })(i, mat, currExports); - } - createPopup(popupId, [exportTxt, industrySelector, citySelector, exportAmount, - exportBtn, cancelBtn, currExportsText].concat(currExports)); - } - buttonPanel.appendChild(createElement("a", { - innerText:"Export", display:"inline-block", class:"a-link-button", - clickListener:()=>{createExportPopup();} - })); - } - - buttonPanel.appendChild(createElement("br", {})); // Force line break - - //Button to set sell amount - var innerTextString; - if (mat.sllman[0]) { - innerTextString = (mat.sllman[1] === -1 ? "Sell (" + formatNumber(mat.sll, 3) + "/MAX)" : - "Sell (" + formatNumber(mat.sll, 3) + "/" + formatNumber(mat.sllman[1], 3) + ")"); - if (mat.sCost) { - if (isString(mat.sCost)) { - var sCost = mat.sCost.replace(/MP/g, mat.bCost); - innerTextString += " @ $" + formatNumber(eval(sCost), 2); - } else { - innerTextString += " @ $" + formatNumber(mat.sCost, 2); - } - } - } else { - innerTextString = "Sell (0.000/0.000)"; - } - - buttonPanel.appendChild(createElement("a", { - innerText: innerTextString, display:"inline-block", class:"a-link-button", - clickListener:()=>{ - var txt = createElement("p", { - innerHTML: "Enter the maximum amount of " + mat.name + " you would like " + - "to sell per second, as well as the price at which you would " + - "like to sell at.

" + - "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

" + - "Setting the sell amount to 'MAX' will result in you always selling the " + - "maximum possible amount of the material.

" + - "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.

" + - "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.", - }); - var br = createElement("br", {}); - var confirmBtn; - var inputQty = createElement("input", { - type: "text", marginTop: "4px", - value: mat.sllman[1] ? mat.sllman[1] : null, placeholder: "Sell amount", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - var inputPx = createElement("input", { - type: "text", marginTop: "4px", - value: mat.sCost ? mat.sCost : null, placeholder: "Sell price", - onkeyup: (e) => { - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - confirmBtn = createElement("button", { - class: "std-button", - innerText: "Confirm", - clickListener: () => { - //Parse price - var cost = inputPx.value.replace(/\s+/g, ''); - cost = cost.replace(/[^-()\d/*+.MP]/g, ''); //Sanitize cost - var temp = cost.replace(/MP/g, mat.bCost); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid value or expression for sell price field: " + e); - return false; - } - - if (temp == null || isNaN(temp)) { - dialogBoxCreate("Invalid value or expression for sell price field"); - return false; - } - - if (cost.includes("MP")) { - mat.sCost = cost; //Dynamically evaluated - } else { - mat.sCost = temp; - } - - //Parse quantity - if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) { - var qty = inputQty.value.replace(/\s+/g, ''); - qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); - var temp = qty.replace(/MAX/g, 1); - temp = temp.replace(/PROD/g, 1); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid value or expression for sell price field: " + e); - return false; - } - - if (temp == null || isNaN(temp)) { - dialogBoxCreate("Invalid value or expression for sell price field"); - return false; - } - - mat.sllman[0] = true; - mat.sllman[1] = qty; //Use sanitized input - } else if (isNaN(inputQty.value)) { - dialogBoxCreate("Invalid value for sell quantity field! Must be numeric or 'MAX'"); - return false; - } else { - var qty = parseFloat(inputQty.value); - if (isNaN(qty)) {qty = 0;} - if (qty === 0) { - mat.sllman[0] = false; - mat.sllman[1] = 0; - } else { - mat.sllman[0] = true; - mat.sllman[1] = qty; - } - } - - this.createUI(parentRefs); - removeElementById(sellPopupid); - return false; - } - }); - const cancelBtn = createPopupCloseButton(sellPopupid, { - class: "std-button", - innerText: "Cancel", - }); - - createPopup(sellPopupid, [txt, br, inputQty, inputPx, confirmBtn, cancelBtn]); - inputQty.focus(); - } - })); - - // Button to use Market-TA research, if you have it - if (industry.hasResearch("Market-TA.I")) { - let marketTaClickListener = () => { - const popupId = "cmpy-mgmt-marketta-popup"; - const markupLimit = mat.getMarkupLimit(); - const ta1 = createElement("p", { - innerHTML: "Market-TA.I
" + - "The maximum sale price you can mark this up to is " + - numeralWrapper.format(mat.bCost + markupLimit, '$0.000a') + - ". This means that if you set the sale price higher than this, " + - "you will begin to experience a loss in number of sales", - }); - const closeBtn = createPopupCloseButton(popupId, { - class: "std-button", - display: "block", - }); - - if (industry.hasResearch("Market-TA.II")) { - let updateTa2Text; - const ta2Text = createElement("p"); - const ta2Input = createElement("input", { - marginTop: "4px", - onkeyup: (e) => { - e.preventDefault(); - updateTa2Text(); - }, - type: "number", - value: mat.bCost, - }); - - // Function that updates the text in ta2Text element - updateTa2Text = function() { - const sCost = parseFloat(ta2Input.value); - let markup = 1; - if (sCost > mat.bCost) { - //Penalty if difference between sCost and bCost is greater than markup limit - if ((sCost - mat.bCost) > markupLimit) { - markup = Math.pow(markupLimit / (sCost - mat.bCost), 2); - } - } else if (sCost < mat.bCost) { - if (sCost <= 0) { - markup = 1e12; //Sell everything, essentially discard - } else { - //Lower prices than market increases sales - markup = mat.bCost / sCost; - } - } - ta2Text.innerHTML = `
Market-TA.II
` + - `If you sell at ${numeralWrapper.format(sCost, "$0.0001")}, ` + - `then you will sell ${formatNumber(markup, 5)}x as much compared ` + - `to if you sold at market price.`; - } - updateTa2Text(); - createPopup(popupId, [ta1, ta2Text, ta2Input, closeBtn]); - } else { - // Market-TA.I only - createPopup(popupId, [ta1, closeBtn]); - } - }; - - buttonPanel.appendChild(createElement("a", { - class: "a-link-button", - clickListener:() => { marketTaClickListener(); }, - display: "inline-block", - innerText: "Market-TA", - - })) - } - - return div; -} - -Warehouse.prototype.createProductUI = function(product, parentRefs) { - var company = parentRefs.company, industry = parentRefs.industry, - city = currentCityUi; - var div = createElement("div", { - class:"cmpy-mgmt-warehouse-product-div" - }); - - //Products being designed TODO - if (!product.fin) { - div.appendChild(createElement("p", { - innerHTML: "Designing " + product.name + "...
" + - formatNumber(product.prog, 2) + "% complete", - })); - return div; - } - - //Completed products - var cmpAndDmdText = ""; - if (company.unlockUpgrades[2] === 1) { - cmpAndDmdText += "
Demand: " + formatNumber(product.dmd, 3); - } - if (company.unlockUpgrades[3] === 1) { - cmpAndDmdText += "
Competition: " + formatNumber(product.cmp, 3); - } - - var totalGain = product.data[city][1] - product.data[city][2]; //Production - sale - div.appendChild(createElement("p", { - innerHTML: "

" + product.name + ": " + formatNumber(product.data[city][0], 3) + //Quantity - "(" + formatNumber(totalGain, 3) + "/s)" + - "Prod: " + formatNumber(product.data[city][1], 3) + "/s
" + - "Sell: " + formatNumber(product.data[city][2], 3) + "/s


" + - "

Rating: " + formatNumber(product.rat, 3) + - "Quality: " + formatNumber(product.qlt, 3) + "
" + - "Performance: " + formatNumber(product.per, 3) + "
" + - "Durability: " + formatNumber(product.dur, 3) + "
" + - "Reliability: " + formatNumber(product.rel, 3) + "
" + - "Aesthetics: " + formatNumber(product.aes, 3) + "
" + - "Features: " + formatNumber(product.fea, 3) + - cmpAndDmdText + "


" + - "

Est. Production Cost: " + numeralWrapper.format(product.pCost / ProductProductionCostRatio, "$0.000a") + - "An estimate of the material cost it takes to create this Product.


" + - "

Est. Market Price: " + numeralWrapper.format(product.pCost + product.rat / product.mku, "$0.000a") + - "An estimate of how much consumers are willing to pay for this product. " + - "Setting the sale price above this may result in less sales. Setting the sale price below this may result " + - "in more sales.

" - })); - var buttonPanel = createElement("div", { - display:"inline-block", - }); - div.appendChild(buttonPanel); - - //Sell button - var sellInnerTextString = (product.sllman[city][1] === -1 ? "Sell (" + formatNumber(product.data[city][2], 3) + "/MAX)" : - "Sell (" + formatNumber(product.data[city][2], 3) + "/" + formatNumber(product.sllman[city][1], 3) + ")"); - if (product.sCost) { - if (isString(product.sCost)) { - sellInnerTextString += (" @ " + product.sCost); - } else { - sellInnerTextString += (" @ " + numeralWrapper.format(product.sCost, "$0.000a")); - } - } - div.appendChild(createElement("a", { - innerText:sellInnerTextString, class:"a-link-button", display:"inline-block",margin:"6px", - clickListener:()=>{ - var popupId = "cmpy-mgmt-sell-product-popup"; - var txt = createElement("p", { - innerHTML:"Enter the maximum amount of " + product.name + " you would like " + - "to sell per second, as well as the price at which you would like to " + - "sell it at.

" + - "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.

" + - "Setting the sell amount to 'MAX' will result in you always selling the " + - "maximum possible amount of the material.

" + - "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.

" + - "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.", - }); - var confirmBtn; - var inputQty = createElement("input", { - type:"text", value:product.sllman[city][1] ? product.sllman[city][1] : null, placeholder: "Sell amount", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - var inputPx = createElement("input", { - type:"text", value: product.sCost ? product.sCost : null, placeholder: "Sell price", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - confirmBtn = createElement("a", { - class:"a-link-button", innerText:"Confirm", - clickListener:()=>{ - //Parse price - if (inputPx.value.includes("MP")) { - //Dynamically evaluated quantity. First test to make sure its valid - //Sanitize input, then replace dynamic variables with arbitrary numbers - var price = inputPx.value.replace(/\s+/g, ''); - price = price.replace(/[^-()\d/*+.MP]/g, ''); - var temp = price.replace(/MP/g, 1); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid value or expression for sell quantity field: " + e); - return false; - } - if (temp == null || isNaN(temp)) { - dialogBoxCreate("Invalid value or expression for sell quantity field."); - return false; - } - product.sCost = price; //Use sanitized price - } else { - var cost = parseFloat(inputPx.value); - if (isNaN(cost)) { - dialogBoxCreate("Invalid value for sell price field"); - return false; - } - product.sCost = cost; - } - - //Parse quantity - if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) { - //Dynamically evaluated quantity. First test to make sure its valid - var qty = inputQty.value.replace(/\s+/g, ''); - qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); - var temp = qty.replace(/MAX/g, 1); - temp = temp.replace(/PROD/g, 1); - try { - temp = eval(temp); - } catch(e) { - dialogBoxCreate("Invalid value or expression for sell price field: " + e); - return false; - } - - if (temp == null || isNaN(temp)) { - dialogBoxCreate("Invalid value or expression for sell price field"); - return false; - } - product.sllman[city][0] = true; - product.sllman[city][1] = qty; //Use sanitized input - } else if (isNaN(inputQty.value)) { - dialogBoxCreate("Invalid value for sell quantity field! Must be numeric"); - return false; - } else { - var qty = parseFloat(inputQty.value); - if (isNaN(qty)) {qty = 0;} - if (qty === 0) { - product.sllman[city][0] = false; - } else { - product.sllman[city][0] = true; - product.sllman[city][1] = qty; - } - } - this.createUI(parentRefs); - removeElementById(popupId); - return false; - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", innerText:"Cancel", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn]); - inputQty.focus(); - } - })); - div.appendChild(createElement("br",{})); //force line break - - //Limit production button - var limitProductionInnerText = "Limit Production"; - if (product.prdman[city][0]) { - limitProductionInnerText += " (" + formatNumber(product.prdman[city][1], 3) + ")"; - } - div.appendChild(createElement("a", { - class:"a-link-button", innerText:limitProductionInnerText,display:"inline-block", - clickListener:()=>{ - var popupId = "cmpy-mgmt-limit-product-production-popup"; - var txt = createElement("p", { - innerText:"Enter a limit to the amount of this product you would " + - "like to product per second. Leave the box empty to set no limit." - }); - var confirmBtn; - var input = createElement("input", { - type:"number", placeholder:"Limit", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - confirmBtn = createElement("a", { - class:"a-link-button", display:"inline-block", innerText:"Limit production", margin:'6px', - clickListener:()=>{ - if (input.value === "") { - product.prdman[city][0] = false; - removeElementById(popupId); - return false; - } - var qty = parseFloat(input.value); - if (isNaN(qty)) { - dialogBoxCreate("Invalid value entered"); - return false; - } - if (qty < 0) { - product.prdman[city][0] = false; - } else { - product.prdman[city][0] = true; - product.prdman[city][1] = qty; - } - removeElementById(popupId); - return false; - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", display:"inline-block", innerText:"Cancel", margin:"6px", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, input, confirmBtn, cancelBtn]); - } - })); - - //Discontinue button - div.appendChild(createElement("a", { - class:'a-link-button', display:"inline-block",innerText:"Discontinue", - clickListener:()=>{ - var popupId = "cmpy-mgmt-discontinue-product-popup"; - var txt = createElement("p", { - innerText:"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", - }); - var confirmBtn = createElement("a", { - class:"a-link-button",innerText:"Discontinue", - clickListener:()=>{ - industry.discontinueProduct(product, parentRefs); - removeElementById(popupId); - return false; - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", innerText:"Cancel", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, confirmBtn, cancelBtn]); - } - })); - return div; -} - -Warehouse.prototype.toJSON = function() { - return Generic_toJSON("Warehouse", this); -} - -Warehouse.fromJSON = function(value) { - return Generic_fromJSON(Warehouse, value.data); -} - -Reviver.constructors.Warehouse = Warehouse; - -function Corporation(params={}) { - this.name = params.name ? params.name : "The Corporation"; - - //A division/business sector is represented by the object: - this.divisions = []; - - //Financial stats - this.funds = new Decimal(150e9); - this.revenue = new Decimal(0); - this.expenses = new Decimal(0); - this.fundingRound = 0; - this.public = false; //Publicly traded - this.totalShares = INITIALSHARES; // Total existing shares - this.numShares = INITIALSHARES; // Total shares owned by player - this.shareSalesUntilPriceUpdate = SHARESPERPRICEUPDATE; - this.shareSaleCooldown = 0; // Game cycles until player can sell shares again - this.issueNewSharesCooldown = 0; // Game cycles until player can issue shares again - this.dividendPercentage = 0; - this.dividendTaxPercentage = 50; - this.issuedShares = 0; - this.sharePrice = 0; - this.storedCycles = 0; - - var numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length, - numUpgrades = Object.keys(CorporationUpgrades).length; - - this.unlockUpgrades = Array(numUnlockUpgrades).fill(0); - this.upgrades = Array(numUpgrades).fill(0); - this.upgradeMultipliers = Array(numUpgrades).fill(1); - - this.state = new CorporationState(); -} - -Corporation.prototype.getState = function() { - return this.state.getState(); -} - -Corporation.prototype.storeCycles = function(numCycles=1) { - this.storedCycles += numCycles; -} - -Corporation.prototype.process = function() { - var corp = this; - if (this.storedCycles >= CyclesPerIndustryStateCycle) { - const state = this.getState(); - const marketCycles = 1; - const gameCycles = (marketCycles * CyclesPerIndustryStateCycle); - this.storedCycles -= gameCycles; - - this.divisions.forEach(function(ind) { - ind.process(marketCycles, state, corp); - }); - - // 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); - }); - var profit = this.revenue.minus(this.expenses); - const cycleProfit = profit.times(marketCycles * SecsPerMarketCycle); - if (isNaN(this.funds)) { - dialogBoxCreate("There was an error calculating your Corporations funds and they got reset to 0. " + - "This is a bug. Please report to game developer.

" + - "(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 > DividendMaxPercentage) { - 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.funds = this.funds.plus(retainedEarnings); - } - } else { - this.funds = this.funds.plus(cycleProfit); - } - - this.updateSharePrice(); - } - - this.state.nextState(); - - if (routing.isOn(Page.Corporation)) {this.updateUIContent();} - } -} - -Corporation.prototype.determineValuation = function() { - var 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; -} - -Corporation.prototype.getInvestment = function() { - var val = this.determineValuation(), percShares; - let roundMultiplier = 4; - switch (this.fundingRound) { - case 0: //Seed - percShares = 0.10; - roundMultiplier = 5; - break; - case 1: //Series A - percShares = 0.35; - roundMultiplier = 4; - break; - case 2: //Series B - percShares = 0.25; - roundMultiplier = 4; - break; - case 3: //Series C - percShares = 0.20; - roundMultiplier = 3.5; - break; - case 4: - return; - } - var funding = val * percShares * roundMultiplier, - investShares = Math.floor(INITIALSHARES * percShares), - yesBtn = yesNoBoxGetYesButton(), - noBtn = yesNoBoxGetNoButton(); - yesBtn.innerHTML = "Accept"; - noBtn.innerHML = "Reject"; - yesBtn.addEventListener("click", ()=>{ - ++this.fundingRound; - this.funds = this.funds.plus(funding); - this.numShares -= investShares; - this.displayCorporationOverviewContent(); - return yesNoBoxClose(); - }); - noBtn.addEventListener("click", ()=>{ - return yesNoBoxClose(); - }); - yesNoBoxCreate("An investment firm has offered you " + numeralWrapper.format(funding, '$0.000a') + - " in funding in exchange for a " + numeralWrapper.format(percShares*100, "0.000a") + - "% stake in the company (" + numeralWrapper.format(investShares, '0.000a') + " shares).

" + - "Do you accept or reject this offer?

" + - "Hint: Investment firms will offer more money if your corporation is turning a profit"); -} - -Corporation.prototype.goPublic = function() { - var goPublicPopupId = "cmpy-mgmt-go-public-popup"; - var initialSharePrice = this.determineValuation() / (this.totalShares); - var txt = createElement("p", { - innerHTML: "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.format(initialSharePrice, '$0.000a') + " per share " + - "(the IPO money will be deposited directly into your Corporation's funds).

" + - "You have a total of " + numeralWrapper.format(this.numShares, "0.000a") + " of shares that you can issue.", - }); - var yesBtn; - var input = createElement("input", { - type:"number", - placeholder: "Shares to issue", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {yesBtn.click();} - } - }); - var br = createElement("br", {}); - yesBtn = createElement("a", { - class:"a-link-button", - innerText:"Go Public", - clickListener:()=>{ - var numShares = Math.round(input.value); - var initialSharePrice = this.determineValuation() / (this.totalShares); - if (isNaN(numShares)) { - dialogBoxCreate("Invalid value for number of issued shares"); - return false; - } - if (numShares > this.numShares) { - dialogBoxCreate("Error: You don't have that many shares to issue!"); - return false; - } - this.public = true; - this.sharePrice = initialSharePrice; - this.issuedShares = numShares; - this.numShares -= numShares; - this.funds = this.funds.plus(numShares * initialSharePrice); - this.displayCorporationOverviewContent(); - removeElementById(goPublicPopupId); - dialogBoxCreate(`You took your ${this.name} public and earned ` + - `${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`); - return false; - } - }); - var noBtn = createElement("a", { - class:"a-link-button", - innerText:"Cancel", - clickListener:()=>{ - removeElementById(goPublicPopupId); - return false; - } - }); - createPopup(goPublicPopupId, [txt, br, input, yesBtn, noBtn]); -} - -Corporation.prototype.getTargetSharePrice = function() { - // 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); -} - -Corporation.prototype.updateSharePrice = function() { - 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;} -} - -Corporation.prototype.immediatelyUpdateSharePrice = function() { - 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] -Corporation.prototype.calculateShareSale = function(numShares) { - let sharesTracker = numShares; - let sharesUntilUpdate = this.shareSalesUntilPriceUpdate; - let sharePrice = this.sharePrice; - let sharesSold = 0; - let profit = 0; - - const maxIterations = Math.ceil(numShares / SHARESPERPRICEUPDATE); - if (isNaN(maxIterations) || maxIterations > 10e6) { - console.error(`Something went wrong or unexpected when calculating share sale. Maxiterations calculated to be ${maxIterations}`); - return; - } - - for (let i = 0; i < maxIterations; ++i) { - if (sharesTracker < sharesUntilUpdate) { - profit += (sharePrice * sharesTracker); - sharesUntilUpdate -= sharesTracker; - break; - } else { - profit += (sharePrice * sharesUntilUpdate); - sharesUntilUpdate = 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]; -} - -Corporation.prototype.convertCooldownToString = function(cd) { - // The cooldown value is based on game cycles. Convert to a simple string - const CyclesPerSecond = 1000 / CONSTANTS.MilliPerCycle; - 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 -Corporation.prototype.unlock = function(upgrade) { - 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 -Corporation.prototype.upgrade = function(upgrade) { - var 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);} - var 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 (var i = 0; i < this.divisions.length; ++i) { - var industry = this.divisions[i]; - for (var city in industry.warehouses) { - if (industry.warehouses.hasOwnProperty(city) && industry.warehouses[city] instanceof Warehouse) { - industry.warehouses[city].updateSize(this, industry); - } - } - } - } - - this.updateCorporationOverviewContent(); -} - -Corporation.prototype.getProductionMultiplier = function() { - var mult = this.upgradeMultipliers[0]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getStorageMultiplier = function() { - var mult = this.upgradeMultipliers[1]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getDreamSenseGain = function() { - var gain = this.upgradeMultipliers[2] - 1; - return gain <= 0 ? 0 : gain; -} - -Corporation.prototype.getAdvertisingMultiplier = function() { - var mult = this.upgradeMultipliers[3]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getEmployeeCreMultiplier = function() { - var mult = this.upgradeMultipliers[4]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getEmployeeChaMultiplier = function() { - var mult = this.upgradeMultipliers[5]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getEmployeeIntMultiplier = function() { - var mult = this.upgradeMultipliers[6]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getEmployeeEffMultiplier = function() { - var mult = this.upgradeMultipliers[7]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getSalesMultiplier = function() { - var mult = this.upgradeMultipliers[8]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -Corporation.prototype.getScientificResearchMultiplier = function() { - var mult = this.upgradeMultipliers[9]; - if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} -} - -//Keep 'global' variables for DOM elements so we don't have to search -//through the DOM tree repeatedly when updating UI -var companyManagementDiv, companyManagementHeaderTabs, companyManagementPanel, - currentCityUi, - corporationUnlockUpgrades, corporationUpgrades, - - sellSharesButton, sellSharesButtonTooltip, - issueNewSharesButton, issueNewSharesButtonTooltip, - - //Industry Overview Panel - industryOverviewPanel, industryOverviewText, - - //Industry Employee Panel - industryEmployeePanel, industryEmployeeText, industryEmployeeHireButton, industryEmployeeAutohireButton, - industryEmployeeManagementUI, industryEmployeeInfo, industryIndividualEmployeeInfo, - industryOfficeUpgradeSizeButton, - - //Industry Warehouse Panel - industryWarehousePanel, industrySmartSupplyCheckbox, industryWarehouseStorageText, - industryWarehouseUpgradeSizeButton, industryWarehouseStateText, - industryWarehouseMaterials, industryWarehouseProducts, - - // Research Tree - researchTreeBoxOpened = false, - researchTreeBox, - - // Tabs - headerTabs, cityTabs; -Corporation.prototype.createUI = function() { - companyManagementDiv = createElement("div", { - id:"cmpy-mgmt-container", - position:"fixed", - class:"generic-menupage-container" - }); - companyManagementHeaderTabs = createElement("div", {id:"cmpy-mgmt-header-tabs"}); - companyManagementDiv.appendChild(companyManagementHeaderTabs); - - //Create division/industry tabs at the top - this.updateUIHeaderTabs(); - - //Create the 'panel' that will have the actual content in the UI - companyManagementPanel = createElement("div", {id:"cmpy-mgmt-panel"}); - companyManagementDiv.appendChild(companyManagementPanel); - document.getElementById("entire-game-container").appendChild(companyManagementDiv); - - this.displayCorporationOverviewContent(); -} - -Corporation.prototype.updateUIHeaderTabs = function() { - if (companyManagementHeaderTabs) { - removeChildrenFromElement(companyManagementHeaderTabs); - } else { - console.log("ERROR: Header tabs div has not yet been created when Corporation.updateUIHeaderTabs() is called"); - return; - } - - //Corporation overview tabs - var cmpyOverviewHdrTab = createElement("button", { - id:"cmpy-mgmt-company-tab", - class:"cmpy-mgmt-header-tab", - innerText:this.name, - checked:true, - clickListener:()=>{ - this.selectHeaderTab(cmpyOverviewHdrTab); - this.displayCorporationOverviewContent(); - return false; - } - }); - companyManagementHeaderTabs.appendChild(cmpyOverviewHdrTab); - - //Tabs for each division - for (var i = 0; i < this.divisions.length; ++i) { - this.createDivisionUIHeaderTab(this.divisions[i]); - } - - //Create a tab to expand into a new industry - companyManagementHeaderTabs.appendChild(createElement("button", { - id:'cmpy-mgmt-expand-industry-tab', - class:"cmpy-mgmt-header-tab", - innerText:"Expand into new Industry", - clickListener: ()=>{ - if (document.getElementById("cmpy-mgmt-expand-industry-popup") != null) {return;} - - var container = createElement("div", { - class:"popup-box-container", - id:"cmpy-mgmt-expand-industry-popup", - }); - var content = createElement("div", {class:"popup-box-content"}); - var txt = createElement("p", { - innerHTML: "Create a new division to expand into a new industry:", - }); - var selector = createElement("select", { - class:"dropdown" - }); - var industryDescription = createElement("p", {}); - var yesBtn; - var nameInput = createElement("input", { - type:"text", - id:"cmpy-mgmt-expand-industry-name-input", - class: "text-input", - display:"block", - maxLength: 30, - pattern:"[a-zA-Z0-9-_]", - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {yesBtn.click();} - } - }); - var nameLabel = createElement("label", { - for:"cmpy-mgmt-expand-industry-name-input", - innerText:"Division name: " - }); - yesBtn = createElement("span", { - class:"popup-box-button", - innerText:"Create Division", - clickListener: ()=>{ - var ind = selector.options[selector.selectedIndex].value, - newDivisionName = nameInput.value; - - for (var i = 0; i < this.divisions.length; ++i) { - if (this.divisions[i].name === newDivisionName) { - dialogBoxCreate("This name is already in use!"); - return false; - } - } - if (this.funds.lt(IndustryStartingCosts[ind])) { - dialogBoxCreate("Not enough money to create a new division in this industry"); - } else if (newDivisionName === "") { - dialogBoxCreate("New division must have a name!"); - } else { - this.funds = this.funds.minus(IndustryStartingCosts[ind]); - var newInd = new Industry({ - name:newDivisionName, - type:ind, - }); - this.divisions.push(newInd); - this.updateUIHeaderTabs(); - this.selectHeaderTab(headerTabs[headerTabs.length-2]); - removeElementById("cmpy-mgmt-expand-industry-popup"); - this.displayDivisionContent(newInd, Locations.Sector12); - } - return false; - } - }); - - const noBtn = createPopupCloseButton(container, {innerText: "Cancel"}); - - //Make an object to keep track of what industries you're already in - var ownedIndustries = {} - for (var i = 0; i < this.divisions.length; ++i) { - ownedIndustries[this.divisions[i].type] = true; - } - - //Add industry types to selector - //Have Agriculture be first as recommended option - if (!ownedIndustries["Agriculture"]) { - selector.add(createElement("option", { - text:Industries["Agriculture"], value:"Agriculture" - })); - } - - for (var key in Industries) { - if (key !== "Agriculture" && Industries.hasOwnProperty(key) && !ownedIndustries[key]) { - var ind = Industries[key]; - selector.add(createElement("option", { - text: ind,value:key, - })); - } - } - - //Initial Industry Description - var ind = selector.options[selector.selectedIndex].value; - industryDescription.innerHTML = (IndustryDescriptions[ind] + "

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

"; - }); - - //Add to DOM - content.appendChild(txt); - content.appendChild(selector); - content.appendChild(industryDescription); - content.appendChild(nameLabel); - content.appendChild(nameInput); - content.appendChild(noBtn); - content.appendChild(yesBtn); - container.appendChild(content); - document.getElementById("entire-game-container").appendChild(container); - container.style.display = "flex"; - nameInput.focus(); - return false; - } - })); - - headerTabs = companyManagementDiv.getElementsByClassName("cmpy-mgmt-header-tab"); -} - -//Updates UI to display which header tab is selected -Corporation.prototype.selectHeaderTab = function(currentTab) { - if (currentTab == null) {return;} - for (var i = 0; i < headerTabs.length; ++i) { - headerTabs[i].className = "cmpy-mgmt-header-tab"; - } - currentTab.className = "cmpy-mgmt-header-tab current"; -} - -Corporation.prototype.createDivisionUIHeaderTab = function(division) { - var tabId = "cmpy-mgmt-" + division.name + "-tab"; - var tab = createElement("button", { - id:tabId, - class:"cmpy-mgmt-header-tab", - innerText:division.name, - clickListener:()=>{ - this.selectHeaderTab(tab); - this.displayDivisionContent(division, Locations.Sector12); - return false; - } - }); - companyManagementHeaderTabs.appendChild(tab); -} - -Corporation.prototype.clearUIPanel = function() { - while(companyManagementPanel.firstChild) { - companyManagementPanel.removeChild(companyManagementPanel.firstChild); - } -} - -Corporation.prototype.updateUIContent = function() { - //Check which of the header tab buttons is checked - if (headerTabs == null) { - console.log("ERROR: headerTabs is null in Corporation.updateUIContent()"); - return; - } - for (var i = 0; i < headerTabs.length; ++i) { - if (headerTabs[i].classList.contains("current")) { - if (i === 0) { - //Corporation overview - this.updateCorporationOverviewContent(); - } else { - //Division - this.updateDivisionContent(this.divisions[i-1]); - } - return; - } - } -} - -Corporation.prototype.displayCorporationOverviewContent = function() { - this.clearUIPanel(); - companyManagementPanel.appendChild(createElement("p", { - id:"cmpy-mgmt-overview-text", - })); - if (headerTabs && headerTabs.length >= 1) { - this.selectHeaderTab(headerTabs[0]); - } - - //Check if player has Corporation Handbook - var homeComp = Player.getHomeComputer(), hasHandbook = false, - handbookFn = "corporation-management-handbook.lit"; - for (var i = 0; i < homeComp.messages.length; ++i) { - if (isString(homeComp.messages[i]) && homeComp.messages[i] === handbookFn) { - hasHandbook = true; - break; - } - } - - companyManagementPanel.appendChild(createElement("a", { - class:"a-link-button", innerText:"Getting Started Guide", display:"inline-block", - 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.", - clickListener:()=>{ - if (!hasHandbook) {homeComp.messages.push(handbookFn);} - showLiterature(handbookFn); - return false; - } - })); - - //Investors - if (this.public) { - //Sell share buttons - var sellShares = createElement("a", { - class:"a-link-button tooltip", innerText:"Sell Shares", display:"inline-block", - clickListener: () => { - var popupId = "cmpy-mgmt-sell-shares-popup"; - var currentStockPrice = this.sharePrice; - var txt = createElement("p", { - innerHTML: "Enter the number of shares you would like to sell. The money from " + - "selling your shares will go directly to you (NOT your Corporation).

" + - "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.

" + - "The current price of your " + - "company's stock is " + numeralWrapper.format(currentStockPrice, "$0.000a"), - }); - var profitIndicator = createElement("p", {}); - var input = createElement("input", { - type:"number", placeholder:"Shares to sell", margin:"5px", - inputListener: ()=> { - var numShares = Math.round(input.value); - if (isNaN(numShares) || numShares <= 0) { - profitIndicator.innerText = "ERROR: Invalid value entered for number of shares to sell" - } else if (numShares > this.numShares) { - profitIndicator.innerText = "You don't have this many shares to sell!"; - } else { - const stockSaleResults = this.calculateShareSale(numShares); - const profit = stockSaleResults[0]; - const newSharePrice = stockSaleResults[1]; - const newSharesUntilUpdate = stockSaleResults[2]; - profitIndicator.innerText = "Sell " + numShares + " shares for a total of " + - numeralWrapper.format(profit, '$0.000a'); - } - } - }); - var confirmBtn = createElement("a", { - class:"a-link-button", innerText:"Sell shares", display:"inline-block", - clickListener:()=>{ - var shares = Math.round(input.value); - if (isNaN(shares) || shares <= 0) { - dialogBoxCreate("ERROR: Invalid value for number of shares"); - } else if (shares > this.numShares) { - dialogBoxCreate("ERROR: You don't have this many shares to sell"); - } else { - const stockSaleResults = this.calculateShareSale(shares); - const profit = stockSaleResults[0]; - const newSharePrice = stockSaleResults[1]; - const newSharesUntilUpdate = stockSaleResults[2]; - - this.numShares -= shares; - if (isNaN(this.issuedShares)) { - console.log("ERROR: Corporation issuedShares is NaN: " + this.issuedShares); - console.log("Converting to number now"); - var res = parseInt(this.issuedShares); - if (isNaN(res)) { - this.issuedShares = 0; - } else { - this.issuedShares = res; - } - } - this.issuedShares += shares; - this.sharePrice = newSharePrice; - this.shareSalesUntilPriceUpdate = newSharesUntilUpdate; - this.shareSaleCooldown = SellSharesCooldown; - Player.gainMoney(profit); - Player.recordMoneySource(profit, "corporation"); - removeElementById(popupId); - dialogBoxCreate(`Sold ${numeralWrapper.formatMoney(shares, "0.000a")} shares for ` + - `${numeralWrapper.formatMoney(profit, "$0.000a")}. ` + - `The corporation's stock price fell to ${numeralWrapper.formatMoney(this.sharePrice)} ` + - `as a result of dilution.`); - return false; - } - - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", innerText:"Cancel", display:"inline-block", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, profitIndicator, input, confirmBtn, cancelBtn]); - } - }); - - sellSharesButtonTooltip = createElement("span", { - class: "tooltiptext", - innerText: "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.", - }); - sellShares.appendChild(sellSharesButtonTooltip); - - //Buyback shares button - var buybackShares = createElement("a", { - class:"a-link-button", innerText:"Buyback shares", display:"inline-block", - tooltip:"Buy back shares you that previously issued or sold at market price.", - clickListener:()=>{ - var popupId = "cmpy-mgmt-buyback-shares-popup"; - const currentStockPrice = this.sharePrice; - const buybackPrice = currentStockPrice * 1.1; - var txt = createElement("p", { - innerHTML: "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.

" + - "To purchase these shares, you must use your own money (NOT your Corporation's funds).

" + - "The current buyback price of your company's stock is " + - numeralWrapper.format(buybackPrice, "$0.000a") + - ". Your company currently has " + formatNumber(this.issuedShares, 3) + " outstanding stock shares", - }); - var costIndicator = createElement("p", {}); - var input = createElement("input", { - type:"number", placeholder:"Shares to buyback", margin:"5px", - inputListener: ()=> { - var numShares = Math.round(input.value); - //TODO add conditional for if player doesn't have enough money - if (isNaN(numShares) || numShares <= 0) { - costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback" - } else if (numShares > this.issuedShares) { - costIndicator.innerText = "There are not this many shares available to buy back. " + - "There are only " + this.issuedShares + " outstanding shares."; - } else { - costIndicator.innerText = "Purchase " + numShares + " shares for a total of " + - numeralWrapper.format(numShares * buybackPrice, '$0.000a'); - } - } - }); - var confirmBtn = createElement("a", { - class:"a-link-button", innerText:"Buy shares", display:"inline-block", - clickListener:()=>{ - var shares = Math.round(input.value); - const tempStockPrice = this.sharePrice; - const buybackPrice = tempStockPrice * 1.1; - if (isNaN(shares) || shares <= 0) { - dialogBoxCreate("ERROR: Invalid value for number of shares"); - } else if (shares > this.issuedShares) { - dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back"); - } else if (shares * buybackPrice > Player.money) { - dialogBoxCreate("ERROR: You do not have enough money to purchase this many shares (you need " + - numeralWrapper.format(shares * buybackPrice, "$0.000a") + ")"); - } else { - this.numShares += shares; - if (isNaN(this.issuedShares)) { - console.log("ERROR: Corporation issuedShares is NaN: " + this.issuedShares); - console.log("Converting to number now"); - var res = parseInt(this.issuedShares); - if (isNaN(res)) { - this.issuedShares = 0; - } else { - this.issuedShares = res; - } - } - this.issuedShares -= shares; - Player.loseMoney(shares * buybackPrice); - removeElementById(popupId); - } - return false; - - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", - innerText:"Cancel", - display:"inline-block", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, costIndicator, input, confirmBtn, cancelBtn]); - } - }); - - companyManagementPanel.appendChild(sellShares); - companyManagementPanel.appendChild(buybackShares); - - sellSharesButton = sellShares; - - // Issue new Shares - appendLineBreaks(companyManagementPanel, 1); - const issueNewShares = createElement("a", { - class: "std-button tooltip", - display: "inline-block", - innerText: "Issue New Shares", - clickListener: () => { - const popupId = "cmpy-mgmt-issue-new-shares-popup"; - const maxNewSharesUnrounded = Math.round(this.totalShares * 0.2); - const maxNewShares = maxNewSharesUnrounded - (maxNewSharesUnrounded % 1e6); - - const descText = createElement("p", { - innerHTML: "You can issue new equity shares (i.e. stocks) in order to raise " + - "capital for your corporation.

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

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

` + - `Stock price decreased to ${numeralWrapper.formatMoney(this.sharePrice)}`); - return false; - } - }); - - const cancelBtn = createPopupCloseButton(popupId, { - class: "std-button", - display: "inline-block", - innerText: "Cancel", - }); - - createPopup(popupId, [descText, dynamicText, newSharesInput, issueBtn, cancelBtn]); - newSharesInput.focus(); - } - }); - issueNewSharesButtonTooltip = createElement("span", { - class: "tooltiptext", - innerText: "Issue new equity shares to raise capital", - }); - issueNewShares.appendChild(issueNewSharesButtonTooltip); - - companyManagementPanel.appendChild(issueNewShares); - - issueNewSharesButton = issueNewShares; - - // Set Stock Dividends - const issueDividends = createElement("a", { - class: "std-button", - display: "inline-block", - innerText: "Issue Dividends", - tooltip: "Manage the dividends that are paid out to shareholders (including yourself)", - clickListener: () => { - const popupId = "cmpy-mgmt-issue-dividends-popup"; - const descText = "Dividends are a distribution of a portion of the corporation's " + - "profits to the shareholders. This includes yourself, as well.

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

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

" + - "Example: Assume your corporation makes $100m / sec in profit and you allocate " + - "40% of that towards dividends. That means your corporation will gain $60m / sec " + - "in funds and the remaining $40m / sec will be paid as dividends. Since your " + - "corporation starts with 1 billion shares, every shareholder will be paid $0.04 per share " + - "per second before taxes."; - const txt = createElement("p", { innerHTML: descText, }); - - let allocateBtn; - const dividendPercentInput = createElement("input", { - margin: "5px", - placeholder: "Dividend %", - type: "number", - onkeyup: (e) => { - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {allocateBtn.click();} - } - }); - - allocateBtn = createElement("button", { - class: "std-button", - display: "inline-block", - innerText: "Allocate Dividend Percentage", - clickListener: () => { - const percentage = Math.round(parseInt(dividendPercentInput.value)); - if (isNaN(percentage) || percentage < 0 || percentage > DividendMaxPercentage) { - return dialogBoxCreate(`Invalid value. Must be an integer between 0 and ${DividendMaxPercentage}`); - } - - this.dividendPercentage = percentage; - - removeElementById(popupId); - return false; - } - }); - - const cancelBtn = createPopupCloseButton(popupId, { - class: "std-button", - display: "inline-block", - innerText: "Cancel", - }); - - createPopup(popupId, [txt, dividendPercentInput, allocateBtn, cancelBtn]); - dividendPercentInput.focus(); - }, - }); - companyManagementPanel.appendChild(issueDividends); - } else { - var findInvestors = createElement("a", { - class: this.fundingRound >= 4 ? "a-link-button-inactive" : "a-link-button tooltip", - innerText: "Find Investors", - display:"inline-block", - clickListener:()=>{ - this.getInvestment(); - } - }); - if (this.fundingRound < 4) { - var findInvestorsTooltip = createElement("span", { - class:"tooltiptext", - innerText:"Search for private investors who will give you startup funding in exchange " + - "for equity (stock shares) in your company" - }); - findInvestors.appendChild(findInvestorsTooltip); - } - - var goPublic = createElement("a", { - class:"a-link-button tooltip", - innerText:"Go Public", - display:"inline-block", - clickListener:()=>{ - this.goPublic(); - return false; - } - }); - var goPublicTooltip = createElement("span", { - class:"tooltiptext", - innerText: "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." - }); - goPublic.appendChild(goPublicTooltip); - - companyManagementPanel.appendChild(findInvestors); - companyManagementPanel.appendChild(goPublic); - } - - appendLineBreaks(companyManagementPanel, 1); - - //If your Corporation is big enough, buy faction influence through bribes - var canBribe = this.determineValuation() >= BribeThreshold; - var bribeFactions = createElement("a", { - class: canBribe ? "a-link-button" : "a-link-button-inactive", - innerText:"Bribe Factions", display:"inline-block", - 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", - clickListener:()=>{ - var popupId = "cmpy-mgmt-bribe-factions-popup"; - var txt = createElement("p", { - innerText:"You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation" - }); - var factionSelector = createElement("select", {margin:"3px"}); - for (var i = 0; i < Player.factions.length; ++i) { - var facName = Player.factions[i]; - factionSelector.add(createElement("option", { - text:facName, value:facName - })); - } - var repGainText = createElement("p"); - var stockSharesInput; - var moneyInput = createElement("input", { - type:"number", placeholder:"Corporation funds", margin:"5px", - inputListener:()=>{ - var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); - var stockPrice = this.sharePrice; - var stockShares = stockSharesInput.value == null || stockSharesInput.value == "" ? 0 : Math.round(parseFloat(stockSharesInput.value)); - if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { - repGainText.innerText = "ERROR: Invalid value(s) entered"; - } else if (this.funds.lt(money)) { - repGainText.innerText = "ERROR: You do not have this much money to bribe with"; - } else if (this.stockShares > this.numShares) { - repGainText.innerText = "ERROR: You do not have this many shares to bribe with"; - } else { - - var totalAmount = Number(money) + (stockShares * stockPrice); - var repGain = totalAmount / BribeToRepRatio; - repGainText.innerText = "You will gain " + formatNumber(repGain, 0) + - " reputation with " + - factionSelector.options[factionSelector.selectedIndex].value + - " with this bribe"; - } - } - }); - stockSharesInput = createElement("input", { - type:"number", placeholder:"Stock Shares", margin: "5px", - inputListener:()=>{ - var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); - var stockPrice = this.sharePrice; - var stockShares = stockSharesInput.value == null || stockSharesInput.value == "" ? 0 : Math.round(stockSharesInput.value); - if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { - repGainText.innerText = "ERROR: Invalid value(s) entered"; - } else if (this.funds.lt(money)) { - repGainText.innerText = "ERROR: You do not have this much money to bribe with"; - } else if (this.stockShares > this.numShares) { - repGainText.innerText = "ERROR: You do not have this many shares to bribe with"; - } else { - var totalAmount = money + (stockShares * stockPrice); - var repGain = totalAmount / BribeToRepRatio; - console.log("repGain: " + repGain); - repGainText.innerText = "You will gain " + formatNumber(repGain, 0) + - " reputation with " + - factionSelector.options[factionSelector.selectedIndex].value + - " with this bribe"; - } - } - }); - var confirmButton = createElement("a", { - class:"a-link-button", innerText:"Bribe", display:"inline-block", - clickListener:()=>{ - var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); - var stockPrice = this.sharePrice; - var stockShares = stockSharesInput.value == null || stockSharesInput.value == ""? 0 : Math.round(parseFloat(stockSharesInput.value)); - var fac = Factions[factionSelector.options[factionSelector.selectedIndex].value]; - if (fac == null) { - dialogBoxCreate("ERROR: You must select a faction to bribe"); - return false; - } - if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { - dialogBoxCreate("ERROR: Invalid value(s) entered"); - } else if (this.funds.lt(money)) { - dialogBoxCreate("ERROR: You do not have this much money to bribe with"); - } else if (stockShares > this.numShares) { - dialogBoxCreate("ERROR: You do not have this many shares to bribe with"); - } else { - var totalAmount = money + (stockShares * stockPrice); - var repGain = totalAmount / BribeToRepRatio; - dialogBoxCreate("You gained " + formatNumber(repGain, 0) + - " reputation with " + fac.name + " by bribing them."); - fac.playerReputation += repGain; - this.funds = this.funds.minus(money); - this.numShares -= stockShares; - removeElementById(popupId); - return false; - } - } - }); - var cancelButton = createElement("a", { - class:"a-link-button", innerText:"Cancel", display:"inline-block", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - - createPopup(popupId, [txt, factionSelector, repGainText, - moneyInput, stockSharesInput, confirmButton, cancelButton]); - } - }); - companyManagementPanel.appendChild(bribeFactions); - - //Update overview text - this.updateCorporationOverviewContent(); - - //Don't show upgrades if player hasn't opened any divisions - if (this.divisions.length <= 0) {return; } - //Corporation Upgrades - var upgradeContainer = createElement("div", { - class:"cmpy-mgmt-upgrade-container", - }); - upgradeContainer.appendChild(createElement("h1", { - innerText:"Unlocks", margin:"6px", padding:"6px", - })); - - //Unlock upgrades - var corp = this; - var numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length, - numUpgrades = Object.keys(CorporationUpgrades).length; - if (this.unlockUpgrades == null || this.upgrades == null) { //Backwards compatibility - this.unlockUpgrades = Array(numUnlockUpgrades).fill(0); - this.upgrades = Array(numUpgrades).fill(0); - } - while (this.unlockUpgrades.length < numUnlockUpgrades) {this.unlockUpgrades.push(0);} - while (this.upgrades.length < numUpgrades) {this.upgrades.push(0);} - while (this.upgradeMultipliers < numUpgrades) {this.upgradeMultipliers.push(1);} - - for (var i = 0; i < numUnlockUpgrades; ++i) { - (function(i, corp) { - if (corp.unlockUpgrades[i] === 0) { - var upgrade = CorporationUnlockUpgrades[i.toString()]; - if (upgrade == null) { - console.log("ERROR: Could not find upgrade index " + i); - return; - } - - upgradeContainer.appendChild(createElement("div", { - class:"cmpy-mgmt-upgrade-div", width:"45%", - innerHTML:upgrade[2] + " - " + numeralWrapper.format(upgrade[1], "$0.000a"), - tooltip: upgrade[3], - clickListener:()=>{ - if (corp.funds.lt(upgrade[1])) { - dialogBoxCreate("Insufficient funds"); - } else { - corp.unlock(upgrade); - corp.displayCorporationOverviewContent(); - } - } - })); - } - })(i, corp); - } - - //Levelable upgrades - upgradeContainer.appendChild(createElement("h1", { - innerText:"Upgrades", margin:"6px", padding:"6px", - })); - - for (var i = 0; i < numUpgrades; ++i) { - (function(i, corp) { - var upgrade = CorporationUpgrades[i.toString()]; - if (upgrade == null) { - console.log("ERROR: Could not find levelable upgrade index " + i); - return; - } - - var baseCost = upgrade[1], priceMult = upgrade[2]; - var cost = baseCost * Math.pow(priceMult, corp.upgrades[i]); - upgradeContainer.appendChild(createElement("div", { - class:"cmpy-mgmt-upgrade-div", width:"45%", - innerHTML:upgrade[4] + " - " + numeralWrapper.format(cost, "$0.000a"), - tooltip:upgrade[5], - clickListener:()=>{ - if (corp.funds.lt(cost)) { - dialogBoxCreate("Insufficient funds"); - } else { - corp.upgrade(upgrade); - corp.displayCorporationOverviewContent(); - } - } - })); - })(i, corp); - } - - companyManagementPanel.appendChild(upgradeContainer); -} - -Corporation.prototype.updateCorporationOverviewContent = function() { - var p = document.getElementById("cmpy-mgmt-overview-text"); - if (p == null) { - console.log("WARNING: Could not find overview text elemtn in updateCorporationOverviewContent()"); - return; - } - - // Formatted text for profit - var profit = this.revenue.minus(this.expenses).toNumber(), - profitStr = profit >= 0 ? numeralWrapper.format(profit, "$0.000a") : "-" + numeralWrapper.format(-1 * profit, "$0.000a"); - - // Formatted text for dividend information, if applicable - let dividendStr = ""; - if (this.dividendPercentage > 0 && profit > 0) { - const totalDividends = (this.dividendPercentage / 100) * profit; - const retainedEarnings = profit - totalDividends; - const dividendsPerShare = totalDividends / this.totalShares; - const playerEarnings = this.numShares * dividendsPerShare; - - dividendStr = `Retained Profits (after dividends): ${numeralWrapper.format(retainedEarnings, "$0.000a")} / s

` + - `Dividend Percentage: ${numeralWrapper.format(this.dividendPercentage / 100, "0%")}
` + - `Dividends per share: ${numeralWrapper.format(dividendsPerShare, "$0.000a")} / s
` + - `Your earnings as a shareholder (Pre-Tax): ${numeralWrapper.format(playerEarnings, "$0.000a")} / s
` + - `Dividend Tax Rate: ${this.dividendTaxPercentage}%
` + - `Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (this.dividendTaxPercentage / 100)), "$0.000a")} / s

`; - } - - var txt = "Total Funds: " + numeralWrapper.format(this.funds.toNumber(), '$0.000a') + "
" + - "Total Revenue: " + numeralWrapper.format(this.revenue.toNumber(), "$0.000a") + " / s
" + - "Total Expenses: " + numeralWrapper.format(this.expenses.toNumber(), "$0.000a") + "/ s
" + - "Total Profits: " + profitStr + " / s
" + - dividendStr + - "Publicly Traded: " + (this.public ? "Yes" : "No") + "
" + - "Owned Stock Shares: " + numeralWrapper.format(this.numShares, '0.000a') + "
" + - "Stock Price: " + (this.public ? "$" + formatNumber(this.sharePrice, 2) : "N/A") + "
" + - "

Total Stock Shares: " + numeralWrapper.format(this.totalShares, "0.000a") + - "" + - `Outstanding Shares: ${numeralWrapper.format(this.issuedShares, "0.000a")}
` + - `Private Shares: ${numeralWrapper.format(this.totalShares - this.issuedShares - this.numShares, "0.000a")}` + - "



"; - - const storedTime = this.storedCycles * CONSTANTS.MilliPerCycle / 1000; - if (storedTime > 15) { - txt += `Bonus Time: ${storedTime} seconds

`; - } - - var prodMult = this.getProductionMultiplier(), - storageMult = this.getStorageMultiplier(), - advMult = this.getAdvertisingMultiplier(), - empCreMult = this.getEmployeeCreMultiplier(), - empChaMult = this.getEmployeeChaMultiplier(), - empIntMult = this.getEmployeeIntMultiplier(), - empEffMult = this.getEmployeeEffMultiplier(), - salesMult = this.getSalesMultiplier(), - sciResMult = this.getScientificResearchMultiplier(); - if (prodMult > 1) {txt += "Production Multiplier: " + formatNumber(prodMult, 3) + "
";} - if (storageMult > 1) {txt += "Storage Multiplier: " + formatNumber(storageMult, 3) + "
";} - if (advMult > 1) {txt += "Advertising Multiplier: " + formatNumber(advMult, 3) + "
";} - if (empCreMult > 1) {txt += "Empl. Creativity Multiplier: " + formatNumber(empCreMult, 3) + "
";} - if (empChaMult > 1) {txt += "Empl. Charisma Multiplier: " + formatNumber(empChaMult, 3) + "
";} - if (empIntMult > 1) {txt += "Empl. Intelligence Multiplier: " + formatNumber(empIntMult, 3) + "
";} - if (empEffMult > 1) {txt += "Empl. Efficiency Multiplier: " + formatNumber(empEffMult, 3) + "
";} - if (salesMult > 1) {txt += "Sales Multiplier: " + formatNumber(salesMult, 3) + "
";} - if (sciResMult > 1) {txt += "Scientific Research Multiplier: " + formatNumber(sciResMult, 3) + "
";} - p.innerHTML = txt; - - // Disable buttons for cooldowns - if (sellSharesButton instanceof Element) { - if (this.shareSaleCooldown <= 0) { - sellSharesButton.className = "std-button tooltip"; - } else { - sellSharesButton.className = "a-link-button-inactive tooltip"; - } - } - - if (sellSharesButtonTooltip instanceof Element) { - if (this.shareSaleCooldown <= 0) { - sellSharesButtonTooltip.innerText = "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."; - } else { - sellSharesButtonTooltip.innerText = "Cannot sell shares for " + this.convertCooldownToString(this.shareSaleCooldown); - } - } - - if (issueNewSharesButton instanceof Element) { - if (this.issueNewSharesCooldown <= 0) { - issueNewSharesButton.className = "std-button tooltip"; - } else { - issueNewSharesButton.className = "a-link-button-inactive tooltip"; - } - } - - if (issueNewSharesButtonTooltip instanceof Element) { - if (this.issueNewSharesCooldown <= 0) { - issueNewSharesButtonTooltip.innerText = "Issue new equity shares to raise capital" - } else { - issueNewSharesButtonTooltip.innerText = "Cannot issue new shares for " + this.convertCooldownToString(this.issueNewSharesCooldown); - } - } -} - -Corporation.prototype.displayDivisionContent = function(division, city) { - this.clearUIPanel(); - currentCityUi = city; - - //Add the city tabs on the left - for (var cityName in division.offices) { - if (division.offices[cityName] instanceof OfficeSpace) { - this.createCityUITab(cityName, division); - } - } - cityTabs = companyManagementPanel.getElementsByClassName("cmpy-mgmt-city-tab"); - if (cityTabs.length > 0) { - this.selectCityTab(document.getElementById("cmpy-mgmt-city-" + city + "-tab"), city); - } - - //Expand into new City button - companyManagementPanel.appendChild(createElement("button", { - class:"cmpy-mgmt-city-tab", innerText:"Expand into new City", display:"inline-block", - clickListener:()=>{ - var popupId = "cmpy-mgmt-expand-city-popup"; - var text = createElement("p", { - innerText: "Would you like to expand into a new city by opening an office? " + - "This would cost " + numeralWrapper.format(OfficeInitialCost, '$0.000a'), - }); - var citySelector = createElement("select", {class: "dropdown", margin:"5px"}); - for (var cityName in division.offices) { - if (division.offices.hasOwnProperty(cityName)) { - if (!(division.offices[cityName] instanceof OfficeSpace)) { - citySelector.add(createElement("option", { - text: cityName, - value: cityName - })); - } - } - } - - var confirmBtn = createElement("a", { - innerText:"Confirm", class:"a-link-button", display:"inline-block", margin:"3px", - clickListener:()=>{ - var city = citySelector.options[citySelector.selectedIndex].value; - if (this.funds.lt(OfficeInitialCost)) { - dialogBoxCreate("You don't have enough company funds to open a new office!"); - } else { - this.funds = this.funds.minus(OfficeInitialCost); - dialogBoxCreate("Opened a new office in " + city + "!"); - division.offices[city] = new OfficeSpace({ - loc:city, - size:OfficeInitialSize, - }); - this.displayDivisionContent(division, city); - } - removeElementById(popupId); - return false; - } - }); - const cancelBtn = createPopupCloseButton(popupId, { - class: "std-button", - innerText: "Cancel", - }); - - createPopup(popupId, [text, citySelector, confirmBtn, cancelBtn]); - return false; - } - })); - companyManagementPanel.appendChild(createElement("br", {})); // Force line break - - //Get office object - var office = division.offices[currentCityUi]; - if (!(office instanceof OfficeSpace)) { - console.log("ERROR: Current city for UI does not have an office space"); - return; - } - - //Left and right panels - var leftPanel = createElement("div", { - class: "cmpy-mgmt-industry-left-panel", - overflow: "visible", - padding: "2px", - }); - var rightPanel = createElement("div", { - class: "cmpy-mgmt-industry-right-panel", - overflow: "visible", - padding: "2px", - }); - companyManagementPanel.appendChild(leftPanel); - companyManagementPanel.appendChild(rightPanel); - - //Different sections (Overview, Employee/Office, and Warehouse) - industryOverviewPanel = createElement("div", { - id:"cmpy-mgmt-industry-overview-panel", class:"cmpy-mgmt-industry-overview-panel" - }); - leftPanel.appendChild(industryOverviewPanel); - - industryEmployeePanel = createElement("div", { - id:"cmpy-mgmt-employee-panel", class:"cmpy-mgmt-employee-panel" - }); - leftPanel.appendChild(industryEmployeePanel); - - industryWarehousePanel = createElement("div", { - id:"cmpy-mgmt-warehouse-panel", class:"cmpy-mgmt-warehouse-panel" - }); - rightPanel.appendChild(industryWarehousePanel); - - //Industry overview text - industryOverviewText = createElement("p", {}); - industryOverviewPanel.appendChild(industryOverviewText); - industryOverviewPanel.appendChild(createElement("br", {})); - - //Industry overview Purchases & Upgrades - var numUpgrades = Object.keys(IndustryUpgrades).length; - while (division.upgrades.length < numUpgrades) {division.upgrades.push(0);} //Backwards compatibility - - var industryOverviewUpgrades = createElement("div", {}); - industryOverviewUpgrades.appendChild(createElement("u", { - innerText:"Purchases & Upgrades", margin:"2px", padding:"2px", - fontSize:"14px", - })); - industryOverviewUpgrades.appendChild(createElement("br", {})); - for (let i = 0; i < numUpgrades; ++i) { - if (division.hasResearch("AutoBrew") && i == 0) { - continue; // AutoBrew disables Coffee upgrades, which is index 0 - } - (function(i, corp, division, office) { - var upgrade = IndustryUpgrades[i.toString()]; - if (upgrade == null) { - console.log("ERROR: Could not find levelable upgrade index: " + i); - return; - } - - var baseCost = upgrade[1], priceMult = upgrade[2], cost = 0; - switch(i) { - case 0: //Coffee, cost is static per employee - cost = office.employees.length * baseCost; - break; - default: - cost = baseCost * Math.pow(priceMult, division.upgrades[i]); - break; - } - industryOverviewUpgrades.appendChild(createElement("div", { - class:"cmpy-mgmt-upgrade-div", display:"inline-block", - innerHTML:upgrade[4] + ' - ' + numeralWrapper.format(cost, "$0.000a"), - tooltip:upgrade[5], - clickListener:()=>{ - if (corp.funds.lt(cost)) { - dialogBoxCreate("Insufficient funds"); - } else { - corp.funds = corp.funds.minus(cost); - division.upgrade(upgrade, { - corporation:corp, - office:office, - }); - corp.displayDivisionContent(division, city); - } - } - })); - industryOverviewUpgrades.appendChild(createElement("br", {})); - - })(i, this, division, office); - } - - - industryOverviewPanel.appendChild(industryOverviewUpgrades); - - //Industry Overview 'Create Product' button if applicable - if (division.makesProducts) { - //Get the text on the button based on Industry type - var createProductButtonText, createProductPopupText; - switch(division.type) { - case Industries.Food: - createProductButtonText = "Build Restaurant"; - createProductPopupText = "Build and manage a new restaurant!" - break; - case Industries.Tobacco: - createProductButtonText = "Create Product"; - createProductPopupText = "Create a new tobacco product!"; - break; - case Industries.Pharmaceutical: - createProductButtonText = "Create Drug"; - createProductPopupText = "Design and develop a new pharmaceutical drug!"; - break; - case Industries.Computer: - case "Computer": - createProductButtonText = "Create Product"; - createProductPopupText = "Design and manufacture a new computer hardware product!"; - break; - case Industries.Robotics: - createProductButtonText = "Design Robot"; - createProductPopupText = "Design and create a new robot or robotic system!"; - break; - case Industries.Software: - createProductButtonText = "Develop Software"; - createProductPopupText = "Develop a new piece of software!"; - break; - case Industries.Healthcare: - createProductButtonText = "Build Hospital"; - createProductPopupText = "Build and manage a new hospital!"; - break; - case Industries.RealEstate: - createProductButtonText = "Develop Property"; - createProductPopupText = "Develop a new piece of real estate property!"; - break; - default: - createProductButtonText = "Create Product"; - return ""; - } - createProductPopupText += "

To begin developing a product, " + - "first choose the city in which it will be designed. The stats of your employees " + - "in the selected city affect the properties of the finished product, such as its " + - "quality, performance, and durability.

" + - "You can also choose to invest money in the design and marketing of " + - "the product. Investing money in its design will result in a superior product. " + - "Investing money in marketing the product will help the product's sales."; - - //Create the button - industryOverviewPanel.appendChild(createElement("a", { - class:"a-link-button", innerText:createProductButtonText, margin:"6px", display:"inline-block", - clickListener:()=>{ - var popupId = "cmpy-mgmt-create-product-popup"; - var txt = createElement("p", { - innerHTML:createProductPopupText, - }); - var designCity = createElement("select", {}); - for (var cityName in division.offices) { - if (division.offices[cityName] instanceof OfficeSpace) { - designCity.add(createElement("option", { - value:cityName, - text:cityName - })); - } - } - var foo = "Product Name"; - if (division.type === Industries.Food) { - foo = "Restaurant Name"; - } else if (division.type === Industries.Healthcare) { - foo = "Hospital Name"; - } else if (division.type === Industries.RealEstate) { - foo = "Property Name"; - } - var productNameInput = createElement("input", { - placeholder:foo, - }); - var lineBreak1 = createElement("br",{}); - var designInvestInput = createElement("input", { - type:"number", - placeholder:"Design investment" - }); - var marketingInvestInput = createElement("input", { - type:"number", - placeholder:"Marketing investment" - }); - var confirmBtn = createElement("a", { - class:"a-link-button", - innerText:"Develop Product", - clickListener:()=>{ - if (designInvestInput.value == null) {designInvestInput.value = 0;} - if (marketingInvestInput.value == null) {marketingInvestInput.value = 0;} - var designInvest = parseFloat(designInvestInput.value), - marketingInvest = parseFloat(marketingInvestInput.value); - if (productNameInput.value == null || productNameInput.value === "") { - dialogBoxCreate("You must specify a name for your product!"); - } else if (isNaN(designInvest)) { - dialogBoxCreate("Invalid value for design investment"); - } else if (isNaN(marketingInvest)) { - dialogBoxCreate("Invalid value for marketing investment"); - } else if (this.funds.lt(designInvest + marketingInvest)) { - dialogBoxCreate("You don't have enough company funds to make this large of an investment"); - } else { - var product = new Product({ - name:productNameInput.value.replace(/[<>]/g, ''), //Sanitize for HTMl elements - createCity:designCity.options[designCity.selectedIndex].value, - designCost: designInvest, - advCost: marketingInvest, - }); - this.funds = this.funds.minus(designInvest + marketingInvest); - division.products[product.name] = product; - removeElementById(popupId); - } - //this.updateUIContent(); - this.displayDivisionContent(division, city); - return false; - } - }) - var cancelBtn = createElement("a", { - class:"a-link-button", - innerText:"Cancel", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }) - createPopup(popupId, [txt, designCity, productNameInput, lineBreak1, - designInvestInput, marketingInvestInput, confirmBtn, cancelBtn]); - } - })); - } - - //Employee and Office Panel - industryEmployeeText = createElement("p", { - id: "cmpy-mgmt-employee-p", - display:"block", - innerHTML: "

Office Space


" + - "Size: " + office.employees.length + " / " + office.size + " employees", - }); - industryEmployeePanel.appendChild(industryEmployeeText); - - //Hire Employee button - if (office.employees.length === 0) { - industryEmployeeHireButton = createElement("a", { - class:"a-link-button",display:"inline-block", - innerText:"Hire Employee", fontSize:"13px", - tooltip:"You'll need to hire some employees to get your operations started! " + - "It's recommended to have at least one employee in every position", - clickListener:()=>{ - office.findEmployees({corporation:this, industry:division}); - return false; - } - }); - //industryEmployeeHireButton.classList.add("flashing-button"); - } else { - industryEmployeeHireButton = createElement("a", { - class:"a-link-button",display:"inline-block", - innerText:"Hire Employee", fontSize:"13px", - clickListener:()=>{ - office.findEmployees({corporation:this, industry:division}); - return false; - } - }); - } - industryEmployeePanel.appendChild(industryEmployeeHireButton); - - //Autohire Employee button - industryEmployeeAutohireButton = createElement("a", { - class:"a-link-button", display:"inline-block", - innerText:"Autohire Employee", fontSize:"13px", - tooltip:"Automatically hires an employee and gives him/her a random name", - clickListener:()=>{ - office.hireRandomEmployee({corporation:this, industry:division}); - return false; - } - }); - industryEmployeePanel.appendChild(industryEmployeeAutohireButton); - - //Upgrade Office Size button - industryEmployeePanel.appendChild(createElement("br", {})); - industryOfficeUpgradeSizeButton = createElement("a", { - class:"a-link-button", innerText:"Upgrade size", - display:"inline-block", margin:"6px", fontSize:"13px", - tooltip:"Upgrade the office's size so that it can hold more employees!", - clickListener:()=>{ - var popupId = "cmpy-mgmt-upgrade-office-size-popup"; - var initialPriceMult = Math.round(office.size / OfficeInitialSize); - var upgradeCost = OfficeInitialCost * Math.pow(1.07, initialPriceMult); - - //Calculate cost to upgrade size by 15 employees - var mult = 0; - for (var i = 0; i < 5; ++i) { - mult += (Math.pow(1.07, initialPriceMult + i)); - } - var upgradeCost15 = OfficeInitialCost * mult; - - //Calculate max upgrade size and cost - var maxMult = (this.funds.dividedBy(OfficeInitialCost)).toNumber(); - var maxNum = 1; - mult = Math.pow(1.07, initialPriceMult); - while(maxNum < 50) { //Hard cap of 50x (extra 150 employees) - if (mult >= maxMult) {break;} - var multIncrease = Math.pow(1.07, initialPriceMult + maxNum); - if (mult + multIncrease > maxMult) { - break; - } else { - mult += multIncrease; - } - ++maxNum; - } - - var upgradeCostMax = OfficeInitialCost * mult; - - var text = createElement("p", { - innerText:"Increase the size of your office space to fit additional employees!" - }); - var text2 = createElement("p", {innerText: "Upgrade size: "}); - - var confirmBtn = createElement("a", { - class: this.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button", - display:"inline-block", margin:"4px", innerText:"by 3", - tooltip:numeralWrapper.format(upgradeCost, "$0.000a"), - clickListener:()=>{ - if (this.funds.lt(upgradeCost)) { - dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); - } else { - office.size += OfficeInitialSize; - this.funds = this.funds.minus(upgradeCost); - dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); - this.updateUIContent(); - } - removeElementById(popupId); - return false; - } - }); - var confirmBtn15 = createElement("a", { - class: this.funds.lt(upgradeCost15) ? "a-link-button-inactive" : "a-link-button", - display:"inline-block", margin:"4px", innerText:"by 15", - tooltip:numeralWrapper.format(upgradeCost15, "$0.000a"), - clickListener:()=>{ - if (this.funds.lt(upgradeCost15)) { - dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); - } else { - office.size += (OfficeInitialSize * 5); - this.funds = this.funds.minus(upgradeCost15); - dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); - this.updateUIContent(); - } - removeElementById(popupId); - return false; - } - }); - var confirmBtnMax = createElement("a", { - class:this.funds.lt(upgradeCostMax) ? "a-link-button-inactive" : "a-link-button", - display:"inline-block", margin:"4px", innerText:"by MAX (" + maxNum*OfficeInitialSize + ")", - tooltip:numeralWrapper.format(upgradeCostMax, "$0.000a"), - clickListener:()=>{ - if (this.funds.lt(upgradeCostMax)) { - dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); - } else { - office.size += (OfficeInitialSize * maxNum); - this.funds = this.funds.minus(upgradeCostMax); - dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); - this.updateUIContent(); - } - removeElementById(popupId); - return false; - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", innerText:"Cancel", display:"inline-block", margin:"4px", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }) - createPopup(popupId, [text, text2, confirmBtn, confirmBtn15, confirmBtnMax, cancelBtn]); - return false; - } - }); - industryEmployeePanel.appendChild(industryOfficeUpgradeSizeButton); - - //Throw Office Party - if (!division.hasResearch("AutoPartyManager")) { - industryEmployeePanel.appendChild(createElement("a", { - class:"a-link-button", display:"inline-block", innerText:"Throw Party", - fontSize:"13px", - tooltip:"Throw an office party to increase your employee's morale and happiness", - clickListener:()=>{ - var popupId = "cmpy-mgmt-throw-office-party-popup"; - var txt = createElement("p", { - innerText:"Enter the amount of money you would like to spend PER EMPLOYEE " + - "on this office party" - }); - var totalCostTxt = createElement("p", { - innerText:"Throwing this party will cost a total of $0" - }); - var confirmBtn; - var input = createElement("input", { - type:"number", margin:"5px", placeholder:"$ / employee", - inputListener:()=>{ - if (isNaN(input.value) || input.value < 0) { - totalCostTxt.innerText = "Invalid value entered!" - } else { - var totalCost = input.value * office.employees.length; - totalCostTxt.innerText = "Throwing this party will cost a total of " + numeralWrapper.format(totalCost, '$0.000a'); - } - }, - onkeyup:(e)=>{ - e.preventDefault(); - if (e.keyCode === KEY.ENTER) {confirmBtn.click();} - } - }); - confirmBtn = createElement("a", { - class:"a-link-button", - display:"inline-block", - innerText:"Throw Party", - clickListener:()=>{ - if (isNaN(input.value) || input.value < 0) { - dialogBoxCreate("Invalid value entered"); - } else { - var totalCost = input.value * office.employees.length; - if (this.funds.lt(totalCost)) { - dialogBoxCreate("You don't have enough company funds to throw this party!"); - } else { - this.funds = this.funds.minus(totalCost); - var mult; - for (var fooit = 0; fooit < office.employees.length; ++fooit) { - mult = office.employees[fooit].throwParty(input.value); - } - dialogBoxCreate("You threw a party for the office! The morale and happiness " + - "of each employee increased by " + formatNumber((mult-1) * 100, 2) + "%."); - removeElementById(popupId); - } - } - return false; - } - }); - var cancelBtn = createElement("a", { - class:"a-link-button", - display:"inline-block", - innerText:"Cancel", - clickListener:()=>{ - removeElementById(popupId); - return false; - } - }); - createPopup(popupId, [txt, totalCostTxt, input, confirmBtn, cancelBtn]); - } - })); - } - - industryEmployeeManagementUI = createElement("div", {}); - industryEmployeeInfo = createElement("p", {margin:"4px", padding:"4px"}); - if (empManualAssignmentModeActive) { - //Employees manually assigned - industryEmployeeManagementUI.appendChild(createElement("a", { - class:"a-link-button", display:"inline-block", margin:"4px", - innerText:"Switch to Auto Mode", - tooltip:"Switch to Automatic Assignment Mode, which will automatically " + - "assign employees to your selected jobs. You simply have to select " + - "the number of assignments for each job", - clickListener:()=>{ - empManualAssignmentModeActive = false; - this.displayDivisionContent(division, city); - } - })); - industryEmployeeManagementUI.appendChild(createElement("br", {})); - - industryIndividualEmployeeInfo = createElement("div", {margin:"4px", padding:"4px"}); - var selector = createElement("select", { - color: "white", backgroundColor:"black", margin:"4px", padding:"4px", - changeListener:()=>{ - var name = selector.options[selector.selectedIndex].text; - for (var i = 0; i < office.employees.length; ++i) { - if (office.employees[i].name === name) { - removeChildrenFromElement(industryIndividualEmployeeInfo); - office.employees[i].createUI(industryIndividualEmployeeInfo, this, division); - return; - } - } - console.log("ERROR: Employee in selector could not be found"); - } - }); - - for (var i = 0; i < office.employees.length; ++i) { - selector.add(createElement("option", {text:office.employees[i].name})); - } - - selector.selectedIndex = -1; - - industryEmployeeManagementUI.appendChild(industryEmployeeInfo); - industryEmployeeManagementUI.appendChild(selector); - industryEmployeeManagementUI.appendChild(industryIndividualEmployeeInfo); - } else { - //Player only manages the number of each occupation, not who gets what job - industryEmployeeManagementUI.appendChild(createElement("a", { - class:"a-link-button", display:"inline-block", margin:"4px", - innerText:"Switch to Manual Mode", - tooltip:"Switch to Manual Assignment Mode, which allows you to " + - "specify which employees should get which jobs", - clickListener:()=>{ - empManualAssignmentModeActive = true; - this.displayDivisionContent(division, city); - } - })); - industryEmployeeManagementUI.appendChild(createElement("br", {})); - - var opCount = 0, engCount = 0, busCount = 0, - mgmtCount = 0, rndCount = 0, unassignedCount = 0, - trainingCount = 0; - for (var i = 0; i < office.employees.length; ++i) { - switch (office.employees[i].pos) { - case EmployeePositions.Operations: - ++opCount; break; - case EmployeePositions.Engineer: - ++engCount; break; - case EmployeePositions.Business: - ++busCount; break; - case EmployeePositions.Management: - ++mgmtCount; break; - case EmployeePositions.RandD: - ++rndCount; break; - case EmployeePositions.Unassigned: - ++unassignedCount; break; - case EmployeePositions.Training: - ++trainingCount; break; - default: - console.log("ERROR: Unrecognized employee position: " + office.employees[i].pos); - break; - } - } - - //Unassigned employee count display - industryEmployeeManagementUI.appendChild(createElement("p", { - display:"inline-block", - innerText:"Unassigned Employees: " + unassignedCount, - })); - industryEmployeeManagementUI.appendChild(createElement("br", {})); - - //General display of employee information (avg morale, avg energy, etc.) - industryEmployeeManagementUI.appendChild(industryEmployeeInfo); - industryEmployeeManagementUI.appendChild(createElement("br", {})); - - var positions = [EmployeePositions.Operations, EmployeePositions.Engineer, - EmployeePositions.Business, EmployeePositions.Management, - EmployeePositions.RandD, EmployeePositions.Training]; - var descriptions = ["Manages supply chain operations. Improves production.", //Operations - "Develops and maintains products and production systems. Improves production.", //Engineer - "Handles sales and finances. Improves sales.", //Business - "Leads and oversees employees and office operations. Improves production.", //Management - "Research new innovative ways to improve the company. Generates Scientific Research", //RandD - "Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations."] //Training - var counts = [opCount, engCount, busCount, mgmtCount, rndCount, trainingCount]; - for (var i = 0; i < positions.length; ++i) { - (function(corp, i) { - var info = createElement("h2", { - display:"inline-block", width:"50%", fontSize:"15px", - innerText: positions[i] + "(" + counts[i] + ")", - tooltip: descriptions[i] - }); - var plusBtn = createElement("a", { - class: unassignedCount > 0 ? "a-link-button" : "a-link-button-inactive", - display:"inline-block", innerText:"+", - clickListener:()=>{ - office.assignEmployeeToJob(positions[i]); - corp.displayDivisionContent(division, city); - } - }); - var minusBtn = createElement("a", { - class: counts[i] > 0 ? "a-link-button" : "a-link-button-inactive", - display:"inline-block", innerText:"-", - clickListener:()=>{ - office.unassignEmployeeFromJob(positions[i]); - corp.displayDivisionContent(division, city); - } - }); - var newline = createElement("br", {}); - industryEmployeeManagementUI.appendChild(info); - industryEmployeeManagementUI.appendChild(plusBtn); - industryEmployeeManagementUI.appendChild(minusBtn); - industryEmployeeManagementUI.appendChild(newline); - })(this, i); - } - } - industryEmployeePanel.appendChild(industryEmployeeManagementUI); - - //Warehouse Panel - var warehouse = division.warehouses[currentCityUi]; - if (warehouse instanceof Warehouse) { - warehouse.createUI({industry:division, company: this}); - } else { - industryWarehousePanel.appendChild(createElement("a", { - innerText:"Purchase Warehouse ($5b)", - class: "a-link-button", - clickListener:()=>{ - if (this.funds.lt(WarehouseInitialCost)) { - dialogBoxCreate("You do not have enough funds to do this!"); - } else { - division.warehouses[currentCityUi] = new Warehouse({ - loc:currentCityUi, - size:WarehouseInitialSize, - }); - this.funds = this.funds.minus(WarehouseInitialCost); - this.displayDivisionContent(division, currentCityUi); - } - return false; - } - })); - } - this.updateDivisionContent(division); -} - -Corporation.prototype.updateDivisionContent = function(division) { - if (!(division instanceof Industry)) { - console.log("ERROR: Invalid 'division' argument in Corporation.updateDivisionContent"); - return; - } - var vechain = (this.unlockUpgrades[4] === 1); - //Industry Overview Text - var profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber(), - profitStr = profit >= 0 ? numeralWrapper.format(profit, "$0.000a") : "-" + numeralWrapper.format(-1 * profit, "$0.000a"); - var advertisingInfo = ""; - if (vechain) { - var advertisingFactors = division.getAdvertisingFactors(); - var awarenessFac = advertisingFactors[1]; - var popularityFac = advertisingFactors[2]; - var ratioFac = advertisingFactors[3]; - var totalAdvertisingFac = advertisingFactors[0]; - advertisingInfo = - "

Advertising Multiplier: x" + formatNumber(totalAdvertisingFac, 3) + - "Total multiplier for this industry's sales due to its awareness and popularity
" + - "Awareness Bonus: x" + formatNumber(Math.pow(awarenessFac, 0.85), 3) + "
" + - "Popularity Bonus: x" + formatNumber(Math.pow(popularityFac, 0.85), 3) + "
" + - "Ratio Multiplier: x" + formatNumber(Math.pow(ratioFac, 0.85), 3) + "


" - - } - - removeChildrenFromElement(industryOverviewText); - industryOverviewText.appendChild(createElement("p", { - innerHTML:"Industry: " + division.type + " (Corp Funds: " + numeralWrapper.format(this.funds.toNumber(), "$0.000a") + ")

" + - "Awareness: " + formatNumber(division.awareness, 3) + "
" + - "Popularity: " + formatNumber(division.popularity, 3) + "
" + - advertisingInfo + "
" + - "Revenue: " + numeralWrapper.format(division.lastCycleRevenue.toNumber(), "$0.000a") + " / s
" + - "Expenses: " + numeralWrapper.format(division.lastCycleExpenses.toNumber(), "$0.000a") + " /s
" + - "Profit: " + profitStr + " / s

" - })); - industryOverviewText.appendChild(createElement("p", { - marginTop:"2px", - innerText:"Production Multiplier: " + formatNumber(division.prodMult, 2), - tooltip:"Production gain from owning production-boosting materials " + - "such as hardware, Robots, AI Cores, and Real Estate" - })); - industryOverviewText.appendChild(createElement("div", { - innerText:"?", class:"help-tip", - clickListener:()=>{ - dialogBoxCreate("Owning Hardware, Robots, AI Cores, and Real Estate " + - "can boost your Industry's production. The effect these " + - "materials have on your production varies between Industries. " + - "For example, Real Estate may be very effective for some Industries, " + - "but ineffective for others.

" + - "This division's production multiplier is calculated by summing " + - "the individual production multiplier of each of its office locations. " + - "This production multiplier is applied to each office. Therefore, it is " + - "beneficial to expand into new cities as this can greatly increase the " + - "production multiplier of your entire Division."); - } - })); - appendLineBreaks(industryOverviewText, 2); - industryOverviewText.appendChild(createElement("p", { - display:"inline-block", - innerText:"Scientific Research: " + formatNumber(division.sciResearch.qty, 3), - tooltip:"Scientific Research increases the quality of the materials and " + - "products that you produce." - })); - industryOverviewText.appendChild(createElement("div", { - class: "help-tip", - innerText: "Research", - clickListener: () => { - division.createResearchBox(); - } - })); - - //Office and Employee List - var office = division.offices[currentCityUi]; - industryEmployeeText.innerHTML = - "

Office Space


" + - "Size: " + office.employees.length + " / " + office.size + " employees"; - if (office.employees.length >= office.size) { - industryEmployeeHireButton.className = "a-link-button-inactive"; - industryEmployeeAutohireButton.className = "a-link-button-inactive tooltip"; - } else if (office.employees.length === 0) { - industryEmployeeHireButton.className = "a-link-button tooltip flashing-button"; - industryEmployeeAutohireButton.className = "a-link-button tooltip"; - } else { - industryEmployeeHireButton.className = "a-link-button"; - industryEmployeeAutohireButton.className = "a-link-button tooltip"; - } - - //Employee Overview stats - //Calculate average morale, happiness, and energy - var totalMorale = 0, totalHappiness = 0, totalEnergy = 0, totalSalary = 0, - avgMorale = 0, avgHappiness = 0, avgEnergy = 0; - for (let i = 0; i < office.employees.length; ++i) { - totalMorale += office.employees[i].mor; - totalHappiness += office.employees[i].hap; - totalEnergy += office.employees[i].ene; - totalSalary += office.employees[i].sal; - } - if (office.employees.length > 0) { - avgMorale = totalMorale / office.employees.length; - avgHappiness = totalHappiness / office.employees.length; - avgEnergy = totalEnergy / office.employees.length; - } - industryEmployeeInfo.innerHTML = - "Avg Employee Morale: " + formatNumber(avgMorale, 3) + "
" + - "Avg Employee Happiness: " + formatNumber(avgHappiness, 3) + "
" + - "Avg Employee Energy: " + formatNumber(avgEnergy, 3) + "
" + - "Total Employee Salary: " + numeralWrapper.format(totalSalary, "$0.000a"); - if (vechain) { //VeChain - Statistics - industryEmployeeInfo.appendChild(createElement("br", {})); - industryEmployeeInfo.appendChild(createElement("p", { - innerText:"Material Production: " + formatNumber(division.getOfficeProductivity(office), 3), - tooltip: "The base amount of material this office can produce. Does not include " + - "production multipliers from upgrades and materials. This value is based off " + - "the productivity of your Operations, Engineering, and Management employees" - })); - industryEmployeeInfo.appendChild(createElement("br", {})); - industryEmployeeInfo.appendChild(createElement("p", { - innerText:"Product Production: " + formatNumber(division.getOfficeProductivity(office, {forProduct:true}), 3), - tooltip: "The base amount of any given Product this office can produce. Does not include " + - "production multipliers from upgrades and materials. This value is based off " + - "the productivity of your Operations, Engineering, and Management employees" - })); - industryEmployeeInfo.appendChild(createElement("br", {})); - industryEmployeeInfo.appendChild(createElement("p", { - innerText: "Business Multiplier: x" + formatNumber(division.getBusinessFactor(office), 3), - tooltip: "The effect this office's 'Business' employees has on boosting sales" - })); - } - - //Warehouse - var warehouse = division.warehouses[currentCityUi]; - if (warehouse instanceof Warehouse) { - warehouse.updateUI({industry:division, company:this}); - } -} - -Corporation.prototype.createCityUITab = function(city, division) { - var tab = createElement("button", { - id:"cmpy-mgmt-city-" + city + "-tab", - class:"cmpy-mgmt-city-tab", - innerText:city, - clickListener:()=>{ - this.selectCityTab(tab, city); - this.displayDivisionContent(division, city); - return false; - } - }); - companyManagementPanel.appendChild(tab); -} - -Corporation.prototype.selectCityTab = function(activeTab, city) { - if (activeTab == null) { - activeTab = document.getElementById("cmpy-mgmt-city-" + city + "-tab"); - if (activeTab == null) {return;} - } - for (var i = 0; i < cityTabs.length; ++i) { - cityTabs[i].className = "cmpy-mgmt-city-tab"; - } - activeTab.className = "cmpy-mgmt-city-tab current"; -} - -Corporation.prototype.clearUI = function() { - //Delete everything - if (companyManagementDiv != null) {removeElementById(companyManagementDiv.id);} - - //Reset global DOM variables - companyManagementDiv = null; - companyManagementPanel = null; - currentCityUi = null; - - corporationUnlockUpgrades = null; - corporationUpgrades = null; - - sellSharesButton = null; - issueNewSharesButton = null; - sellSharesButtonTooltip = null; - issueNewSharesButtonTooltip = null; - - industryOverviewPanel = null; - industryOverviewText = null; - - industryEmployeePanel = null; - industryEmployeeText = null; - industryEmployeeHireButton = null; - industryEmployeeAutohireButton = null; - industryEmployeeManagementUI = null; - industryEmployeeInfo = null; - industryIndividualEmployeeInfo = null; - - industryOfficeUpgradeSizeButton = null; - - industryWarehousePanel = null; - industrySmartSupplyCheckbox = null; - industryWarehouseStorageText = null; - industryWarehouseUpgradeSizeButton = null; - industryWarehouseStateText = null; - industryWarehouseMaterials = null; - industryWarehouseProducts = null; - - researchTreeBoxOpened = false; - researchTreeBox = null; - - companyManagementHeaderTabs = null; - headerTabs = null; - cityTabs = null; - - document.getElementById("character-overview-wrapper").style.visibility = "visible"; -} - -Corporation.prototype.toJSON = function() { - return Generic_toJSON("Corporation", this); -} - -Corporation.fromJSON = function(value) { - return Generic_fromJSON(Corporation, value.data); -} - -Reviver.constructors.Corporation = Corporation; - -export {Corporation}; diff --git a/src/Corporation/Corporation.jsx b/src/Corporation/Corporation.jsx new file mode 100644 index 000000000..3ec1511a4 --- /dev/null +++ b/src/Corporation/Corporation.jsx @@ -0,0 +1,2343 @@ +import { AllCorporationStates, + CorporationState } from "./CorporationState"; +import { CorporationUnlockUpgrades } from "./data/CorporationUnlockUpgrades"; +import { CorporationUpgrades } from "./data/CorporationUpgrades"; +import { EmployeePositions } from "./EmployeePositions"; +import { Industries, + IndustryStartingCosts, + IndustryDescriptions, + IndustryResearchTrees } from "./IndustryData"; +import { IndustryUpgrades } from "./IndustryUpgrades"; +import { Material } from "./Material"; +import { MaterialSizes } from "./MaterialSizes"; +import { Product } from "./Product"; +import { ResearchMap } from "./ResearchMap"; +import { Warehouse } from "./Warehouse"; + +import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; +import { CONSTANTS } from "../Constants"; +import { Factions } from "../Faction/Factions"; +import { showLiterature } from "../Literature"; +import { Locations } from "../Locations"; +import { Player } from "../Player"; + +import { numeralWrapper } from "../ui/numeralFormat"; +import { Page, routing } from "../ui/navigationTracking"; + +import { dialogBoxCreate } from "../../utils/DialogBox"; +import { clearSelector } from "../../utils/uiHelpers/clearSelector"; +import { Reviver, + Generic_toJSON, + Generic_fromJSON } from "../../utils/JSONReviver"; +import { appendLineBreaks } from "../../utils/uiHelpers/appendLineBreaks"; +import { createElement } from "../../utils/uiHelpers/createElement"; +import { createPopup } from "../../utils/uiHelpers/createPopup"; +import { createPopupCloseButton } from "../../utils/uiHelpers/createPopupCloseButton"; +import { formatNumber, generateRandomString } from "../../utils/StringHelperFunctions"; +import { getRandomInt } from "../../utils/helpers/getRandomInt"; +import { isString } from "../../utils/helpers/isString"; +import { KEY } from "../../utils/helpers/keyCodes"; +import { removeChildrenFromElement } from "../../utils/uiHelpers/removeChildrenFromElement"; +import { removeElement } from "../../utils/uiHelpers/removeElement"; +import { removeElementById } from "../../utils/uiHelpers/removeElementById"; +import { yesNoBoxCreate, + yesNoTxtInpBoxCreate, + yesNoBoxGetYesButton, + yesNoBoxGetNoButton, + yesNoTxtInpBoxGetYesButton, + yesNoTxtInpBoxGetNoButton, + yesNoTxtInpBoxGetInput, + yesNoBoxClose, + yesNoTxtInpBoxClose, + yesNoBoxOpen } from "../../utils/YesNoBox"; + +// UI Related Imports +import React from "react"; +import ReactDOM from "react-dom"; +import { CorporationEventHandler } from "./ui/CorporationUIEventHandler"; +import { CorporationRoot } from "./ui/Root"; +import { CorporationRouting } from "./ui/Routing"; + +import Decimal from "decimal.js"; + + +/* Constants */ +export const INITIALSHARES = 1e9; //Total number of shares you have at your company +export const SHARESPERPRICEUPDATE = 1e6; //When selling large number of shares, price is dynamically updated for every batch of this amount +export const IssueNewSharesCooldown = 216e3; // 12 Hour in terms of game cycles +export const SellSharesCooldown = 18e3; // 1 Hour in terms of game cycles + +export const CyclesPerMarketCycle = 50; +export const CyclesPerIndustryStateCycle = CyclesPerMarketCycle / AllCorporationStates.length; +export const SecsPerMarketCycle = CyclesPerMarketCycle / 5; + +export const Cities = ["Aevum", "Chongqing", "Sector-12", "New Tokyo", "Ishima", "Volhaven"]; + +export const WarehouseInitialCost = 5e9; //Initial purchase cost of warehouse +export const WarehouseInitialSize = 100; +export const WarehouseUpgradeBaseCost = 1e9; + +export const OfficeInitialCost = 4e9; +export const OfficeInitialSize = 3; +export const OfficeUpgradeBaseCost = 1e9; + +export const BribeThreshold = 100e12; //Money needed to be able to bribe for faction rep +export const BribeToRepRatio = 1e9; //Bribe Value divided by this = rep gain + +export const ProductProductionCostRatio = 5; //Ratio of material cost of a product to its production cost + +export const DividendMaxPercentage = 50; + +export const EmployeeSalaryMultiplier = 3; // Employee stats multiplied by this to determine initial salary +export const CyclesPerEmployeeRaise = 400; // All employees get a raise every X market cycles +export const EmployeeRaiseAmount = 50; // Employee salary increases by this (additive) + +export const BaseMaxProducts = 3; // Initial value for maximum number of products allowed + +// Delete Research Popup Box when clicking outside of it +let researchTreeBoxOpened = false; +let researchTreeBox = null; +$(document).mousedown(function(event) { + const boxId = "corporation-research-popup-box"; + const contentId = "corporation-research-popup-box-content"; + if (researchTreeBoxOpened) { + if ( $(event.target).closest("#" + contentId).get(0) == null ) { + // Delete the box + removeElement(researchTreeBox); + researchTreeBox = null; + researchTreeBoxOpened = false; + } + } +}); + +var empManualAssignmentModeActive = false; +function Industry(params={}) { + this.offices = { //Maps locations to offices. 0 if no office at that location + [Locations.Aevum]: 0, + [Locations.Chongqing]: 0, + [Locations.Sector12]: new OfficeSpace({ + loc:Locations.Sector12, + size:OfficeInitialSize, + }), + [Locations.NewTokyo]: 0, + [Locations.Ishima]: 0, + [Locations.Volhaven]: 0 + }; + + this.warehouses = { //Maps locations to warehouses. 0 if no warehouse at that location + [Locations.Aevum]: 0, + [Locations.Chonqing]: 0, + [Locations.Sector12]: new Warehouse({ + loc:Locations.Sector12, + size: WarehouseInitialSize, + }), + [Locations.NewTokyo]: 0, + [Locations.Ishima]: 0, + [Locations.Volhaven]: 0 + }; + + this.name = params.name ? params.name : 0; + this.type = params.type ? params.type : 0; + + this.sciResearch = new Material({name: "Scientific Research"}); + this.researched = {}; // Object of acquired Research. Keys = research name + + //A map of the NAME of materials required to create produced materials to + //how many are needed to produce 1 unit of produced materials + this.reqMats = {}; + + //An array of the name of materials being produced + this.prodMats = []; + + this.products = {}; + this.makesProducts = false; + + this.awareness = 0; + this.popularity = 0; //Should always be less than awareness + this.startingCost = 0; + + /* The following are factors for how much production/other things are increased by + different factors. The production increase always has diminishing returns, + and they are all reprsented by exponentials of < 1 (e.g x ^ 0.5, x ^ 0.8) + The number for these represent the exponential. A lower number means more + diminishing returns */ + this.reFac = 0; //Real estate Factor + this.sciFac = 0; //Scientific Research Factor, affects quality + this.hwFac = 0; //Hardware factor + this.robFac = 0; //Robotics Factor + this.aiFac = 0; //AI Cores factor; + this.advFac = 0; //Advertising factor, affects sales + + this.prodMult = 0; //Production multiplier + + //Financials + this.lastCycleRevenue = new Decimal(0); + this.lastCycleExpenses = new Decimal(0); + this.thisCycleRevenue = new Decimal(0); + this.thisCycleExpenses = new Decimal(0); + + //Upgrades + var numUpgrades = Object.keys(IndustryUpgrades).length; + this.upgrades = Array(numUpgrades).fill(0); + + this.state = "START"; + this.newInd = true; + + this.init(); +} + +Industry.prototype.init = function() { + //Set the unique properties of an industry (how much its affected by real estate/scientific research, etc.) + this.startingCost = IndustryStartingCosts[this.type]; + switch (this.type) { + case Industries.Energy: + this.reFac = 0.65; + this.sciFac = 0.7; + this.robFac = 0.05; + this.aiFac = 0.3; + this.advFac = 0.08; + this.reqMats = { + "Hardware": 0.1, + "Metal": 0.2, + }; + this.prodMats = ["Energy"]; + break; + case Industries.Utilities: + case "Utilities": + this.reFac = 0.5; + this.sciFac = 0.6; + this.robFac = 0.4; + this.aiFac = 0.4; + this.advFac = 0.08; + this.reqMats = { + "Hardware": 0.1, + "Metal": 0.1, + } + this.prodMats = ["Water"]; + break; + case Industries.Agriculture: + this.reFac = 0.72; + this.sciFac = 0.5; + this.hwFac = 0.2; + this.robFac = 0.3; + this.aiFac = 0.3; + this.advFac = 0.04; + this.reqMats = { + "Water": 0.5, + "Energy": 0.5, + } + this.prodMats = ["Plants", "Food"]; + break; + case Industries.Fishing: + this.reFac = 0.15; + this.sciFac = 0.35; + this.hwFac = 0.35; + this.robFac = 0.5; + this.aiFac = 0.2; + this.advFac = 0.08; + this.reqMats = { + "Energy": 0.5, + } + this.prodMats = ["Food"]; + break; + case Industries.Mining: + this.reFac = 0.3; + this.sciFac = 0.26; + this.hwFac = 0.4; + this.robFac = 0.45; + this.aiFac = 0.45; + this.advFac = 0.06; + this.reqMats = { + "Energy": 0.8, + } + this.prodMats = ["Metal"]; + break; + case Industries.Food: + //reFac is unique for this bc it diminishes greatly per city. Handle this separately in code? + this.sciFac = 0.12; + this.hwFac = 0.15; + this.robFac = 0.3; + this.aiFac = 0.25; + this.advFac = 0.25; + this.reFac = 0.05; + this.reqMats = { + "Food": 0.5, + "Water": 0.5, + "Energy": 0.2, + } + this.makesProducts = true; + break; + case Industries.Tobacco: + this.reFac = 0.15; + this.sciFac = 0.75; + this.hwFac = 0.15; + this.robFac = 0.2; + this.aiFac = 0.15; + this.advFac = 0.2; + this.reqMats = { + "Plants": 1, + "Water": 0.2, + } + this.makesProducts = true; + break; + case Industries.Chemical: + this.reFac = 0.25; + this.sciFac = 0.75; + this.hwFac = 0.2; + this.robFac = 0.25; + this.aiFac = 0.2; + this.advFac = 0.07; + this.reqMats = { + "Plants": 1, + "Energy": 0.5, + "Water": 0.5, + } + this.prodMats = ["Chemicals"]; + break; + case Industries.Pharmaceutical: + this.reFac = 0.05; + this.sciFac = 0.8; + this.hwFac = 0.15; + this.robFac = 0.25; + this.aiFac = 0.2; + this.advFac = 0.16; + this.reqMats = { + "Chemicals": 2, + "Energy": 1, + "Water": 0.5, + } + this.prodMats = ["Drugs"]; + this.makesProducts = true; + break; + case Industries.Computer: + case "Computer": + this.reFac = 0.2; + this.sciFac = 0.62; + this.robFac = 0.36; + this.aiFac = 0.19; + this.advFac = 0.17; + this.reqMats = { + "Metal": 2, + "Energy": 1, + } + this.prodMats = ["Hardware"]; + this.makesProducts = true; + break; + case Industries.Robotics: + this.reFac = 0.32; + this.sciFac = 0.65; + this.aiFac = 0.36; + this.advFac = 0.18; + this.hwFac = 0.19; + this.reqMats = { + "Hardware": 5, + "Energy": 3, + } + this.prodMats = ["Robots"]; + this.makesProducts = true; + break; + case Industries.Software: + this.sciFac = 0.62; + this.advFac = 0.16; + this.hwFac = 0.25; + this.reFac = 0.1; + this.aiFac = 0.15; + this.robFac = 0.05; + this.reqMats = { + "Hardware": 0.5, + "Energy": 0.5, + } + this.prodMats = ["AICores"]; + this.makesProducts = true; + break; + case Industries.Healthcare: + this.reFac = 0.1; + this.sciFac = 0.75; + this.advFac = 0.11; + this.hwFac = 0.1; + this.robFac = 0.1; + this.aiFac = 0.1; + this.reqMats = { + "Robots": 10, + "AICores": 5, + "Energy": 5, + "Water": 5, + } + this.makesProducts = true; + break; + case Industries.RealEstate: + this.robFac = 0.6; + this.aiFac = 0.6; + this.advFac = 0.25; + this.sciFac = 0.05; + this.hwFac = 0.05; + this.reqMats = { + "Metal": 5, + "Energy": 5, + "Water": 2, + "Hardware": 4 + } + this.prodMats = ["RealEstate"]; + this.makesProducts = true; + break; + default: + console.log("ERR: Invalid Industry Type passed into Industry.init(): " + this.type); + return; + } +} + +Industry.prototype.getProductDescriptionText = function() { + if (!this.makesProducts) {return;} + switch (this.type) { + case Industries.Food: + return "create and manage restaurants"; + case Industries.Tobacco: + return "create tobacco and tobacco-related products"; + case Industries.Pharmaceutical: + return "develop new pharmaceutical drugs"; + case Industries.Computer: + case "Computer": + return "create new computer hardware and networking infrastructures"; + case Industries.Robotics: + return "build specialized robots and robot-related products"; + case Industries.Software: + return "develop computer software"; + case Industries.Healthcare: + return "build and manage hospitals"; + case Industries.RealEstate: + return "develop and manage real estate properties"; + default: + console.log("ERROR: Invalid industry type in Industry.getProductDescriptionText"); + return ""; + } +} + +Industry.prototype.getMaximumNumberProducts = function() { + if (!this.makesProducts) { return 0; } + + // Calculate additional number of allowed Products from Research/Upgrades + let additional = 0; + if (this.hasResearch("uPgrade: Capacity.I")) { ++additional; } + if (this.hasResearch("uPgrade: Capacity.II")) { ++additional; } + + return BaseMaxProducts + additional; +} + +Industry.prototype.hasMaximumNumberProducts = function() { + return (Object.keys(this.products).length >= this.getMaximumNumberProducts()); +} + +//Calculates the values that factor into the production and properties of +//materials/products (such as quality, etc.) +Industry.prototype.calculateProductionFactors = function() { + var multSum = 0; + for (var i = 0; i < Cities.length; ++i) { + var city = Cities[i]; + var warehouse = this.warehouses[city]; + if (!(warehouse instanceof Warehouse)) { + continue; + } + + var materials = warehouse.materials, + office = this.offices[city]; + + var cityMult = Math.pow(0.002 * materials.RealEstate.qty+1, this.reFac) * + Math.pow(0.002 * materials.Hardware.qty+1, this.hwFac) * + Math.pow(0.002 * materials.Robots.qty+1, this.robFac) * + Math.pow(0.002 * materials.AICores.qty+1, this.aiFac); + multSum += Math.pow(cityMult, 0.73); + } + + multSum < 1 ? this.prodMult = 1 : this.prodMult = multSum; +} + +Industry.prototype.updateWarehouseSizeUsed = function(warehouse) { + if (warehouse instanceof Warehouse) { + //This resets the size back to 0 and then accounts for materials + warehouse.updateMaterialSizeUsed(); + } + + for (var prodName in this.products) { + if (this.products.hasOwnProperty(prodName)) { + var prod = this.products[prodName]; + warehouse.sizeUsed += (prod.data[warehouse.loc][0] * prod.siz); + if (prod.data[warehouse.loc][0] > 0) { + warehouse.breakdown += (prodName + ": " + formatNumber(prod.data[warehouse.loc][0] * prod.siz, 0) + "
"); + } + } + } +} + +Industry.prototype.process = function(marketCycles=1, state, company) { + this.state = state; + + //At the start of a cycle, store and reset revenue/expenses + //Then calculate salaries and processs the markets + if (state === "START") { + if (isNaN(this.thisCycleRevenue) || isNaN(this.thisCycleExpenses)) { + console.log("ERROR: NaN in Corporation's computed revenue/expenses"); + console.log(this.thisCycleRevenue.toString()); + console.log(this.thisCycleExpenses.toString()); + dialogBoxCreate("Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer"); + this.thisCycleRevenue = new Decimal(0); + this.thisCycleExpenses = new Decimal(0); + } + this.lastCycleRevenue = this.thisCycleRevenue.dividedBy(marketCycles * SecsPerMarketCycle); + this.lastCycleExpenses = this.thisCycleExpenses.dividedBy(marketCycles * SecsPerMarketCycle); + this.thisCycleRevenue = new Decimal(0); + this.thisCycleExpenses = new Decimal(0); + + //Once you start making revenue, the player should no longer be + //considered new, and therefore no longer needs the 'tutorial' UI elements + if (this.lastCycleRevenue.gt(0)) {this.newInd = false;} + + //Process offices (and the employees in them) + var employeeSalary = 0; + for (var officeLoc in this.offices) { + if (this.offices[officeLoc] instanceof OfficeSpace) { + employeeSalary += this.offices[officeLoc].process(marketCycles, {industry:this, corporation:company}); + } + } + this.thisCycleExpenses = this.thisCycleExpenses.plus(employeeSalary); + + //Process change in demand/competition of materials/products + this.processMaterialMarket(marketCycles); + this.processProductMarket(marketCycles); + + //Process loss of popularity + this.popularity -= (marketCycles * .0001); + this.popularity = Math.max(0, this.popularity); + + //Process Dreamsense gains + var popularityGain = company.getDreamSenseGain(), awarenessGain = popularityGain * 4; + if (popularityGain > 0) { + this.popularity += (popularityGain * marketCycles); + this.awareness += (awarenessGain * marketCycles); + } + + return; + } + + //Process production, purchase, and import/export of materials + var res = this.processMaterials(marketCycles, company); + this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); + this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); + + //Process creation, production & sale of products + res = this.processProducts(marketCycles, company); + this.thisCycleRevenue = this.thisCycleRevenue.plus(res[0]); + this.thisCycleExpenses = this.thisCycleExpenses.plus(res[1]); + +} + +//Process change in demand and competition for this industry's materials +Industry.prototype.processMaterialMarket = function(marketCycles=1) { + //References to prodMats and reqMats + var reqMats = this.reqMats, prodMats = this.prodMats; + + //Only 'process the market' for materials that this industry deals with + for (var i = 0; i < Cities.length; ++i) { + //If this industry has a warehouse in this city, process the market + //for every material this industry requires or produces + if (this.warehouses[Cities[i]] instanceof Warehouse) { + var wh = this.warehouses[Cities[i]]; + for (var name in reqMats) { + if (reqMats.hasOwnProperty(name)) { + wh.materials[name].processMarket(); + } + } + + //Produced materials are stored in an array + for (var foo = 0; foo < prodMats.length; ++foo) { + wh.materials[prodMats[foo]].processMarket(); + } + + //Process these twice because these boost production + wh.materials["Hardware"].processMarket(); + wh.materials["Robots"].processMarket(); + wh.materials["AICores"].processMarket(); + wh.materials["RealEstate"].processMarket(); + } + } +} + +//Process change in demand and competition for this industry's products +Industry.prototype.processProductMarket = function(marketCycles=1) { + //Demand gradually decreases, and competition gradually increases + for (var name in this.products) { + if (this.products.hasOwnProperty(name)) { + var product = this.products[name]; + var change = getRandomInt(1, 3) * 0.0004; + if (this.type === Industries.Pharmaceutical || this.type === Industries.Software || + this.type === Industries.Robotics) { + change *= 3; + } + change *= marketCycles; + product.dmd -= change; + product.cmp += change; + product.cmp = Math.min(product.cmp, 99.99); + product.dmd = Math.max(product.dmd, 0.001); + } + } +} + +//Process production, purchase, and import/export of materials +Industry.prototype.processMaterials = function(marketCycles=1, company) { + var revenue = 0, expenses = 0, industry = this; + this.calculateProductionFactors(); + + //At the start of the export state, set the imports of everything to 0 + if (this.state === "EXPORT") { + for (var i = 0; i < Cities.length; ++i) { + var city = Cities[i], office = this.offices[city]; + if (!(this.warehouses[city] instanceof Warehouse)) { + continue; + } + var warehouse = this.warehouses[city]; + for (var matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + var mat = warehouse.materials[matName]; + mat.imp = 0; + } + } + } + } + + for (var i = 0; i < Cities.length; ++i) { + var city = Cities[i], office = this.offices[city]; + + if (this.warehouses[city] instanceof Warehouse) { + var warehouse = this.warehouses[city]; + + switch(this.state) { + + case "PURCHASE": + /* Process purchase of materials */ + for (var matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + (function(matName, ind) { + var mat = warehouse.materials[matName]; + var buyAmt, maxAmt; + if (warehouse.smartSupplyEnabled && Object.keys(ind.reqMats).includes(matName)) { + //Smart supply tracker is stored as per second rate + mat.buy = ind.reqMats[matName] * warehouse.smartSupplyStore; + buyAmt = mat.buy * SecsPerMarketCycle * marketCycles; + } else { + buyAmt = (mat.buy * SecsPerMarketCycle * marketCycles); + } + + if (matName == "RealEstate") { + maxAmt = buyAmt; + } else { + maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]); + } + var buyAmt = Math.min(buyAmt, maxAmt); + if (buyAmt > 0) { + mat.qty += buyAmt; + expenses += (buyAmt * mat.bCost); + } + })(matName, industry); + this.updateWarehouseSizeUsed(warehouse); + } + } //End process purchase of materials + break; + + case "PRODUCTION": + warehouse.smartSupplyStore = 0; //Reset smart supply amount + + /* Process production of materials */ + if (this.prodMats.length > 0) { + var mat = warehouse.materials[this.prodMats[0]]; + //Calculate the maximum production of this material based + //on the office's productivity + var maxProd = this.getOfficeProductivity(office) + * this.prodMult // Multiplier from materials + * company.getProductionMultiplier() + * this.getProductionMultiplier(); // Multiplier from Research + let prod; + + if (mat.prdman[0]) { + //Production is manually limited + prod = Math.min(maxProd, mat.prdman[1]); + } else { + prod = maxProd; + } + prod *= (SecsPerMarketCycle * marketCycles); //Convert production from per second to per market cycle + //Calculate net change in warehouse storage making + //the produced materials will cost + var totalMatSize = 0; + for (var tmp = 0; tmp < this.prodMats.length; ++tmp) { + totalMatSize += (MaterialSizes[this.prodMats[tmp]]); + } + for (var reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + var normQty = this.reqMats[reqMatName]; + totalMatSize -= (MaterialSizes[reqMatName] * normQty); + } + } + //If not enough space in warehouse, limit the amount of produced materials + if (totalMatSize > 0) { + var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / totalMatSize); + prod = Math.min(maxAmt, prod); + } + + if (prod < 0) {prod = 0;} + + //Keep track of production for smart supply (/s) + warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles)); + + //Make sure we have enough resource to make our materials + var producableFrac = 1; + for (var reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + var req = this.reqMats[reqMatName] * prod; + if (warehouse.materials[reqMatName].qty < req) { + producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); + } + } + } + if (producableFrac <= 0) {producableFrac = 0; prod = 0;} + + //Make our materials if they are producable + if (producableFrac > 0 && prod > 0) { + for (var reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + var reqMatQtyNeeded = (this.reqMats[reqMatName] * prod * producableFrac); + warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; + warehouse.materials[reqMatName].prd = 0; + warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles); + } + } + for (var j = 0; j < this.prodMats.length; ++j) { + warehouse.materials[this.prodMats[j]].qty += (prod * producableFrac); + warehouse.materials[this.prodMats[j]].qlt = + (office.employeeProd[EmployeePositions.Engineer] / 100 + + Math.pow(this.sciResearch.qty, this.sciFac) + + Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3); + } + } else { + for (var reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + warehouse.materials[reqMatName].prd = 0; + } + } + } + + //Per second + var fooProd = prod * producableFrac / (SecsPerMarketCycle * marketCycles); + for (var fooI = 0; fooI < this.prodMats.length; ++fooI) { + warehouse.materials[this.prodMats[fooI]].prd = fooProd; + } + } else { + //If this doesn't produce any materials, then it only creates + //Products. Creating products will consume materials. The + //Production of all consumed materials must be set to 0 + for (var reqMatName in this.reqMats) { + if (this.reqMats.hasOwnProperty(reqMatName)) { + warehouse.materials[reqMatName].prd = 0; + } + } + } + break; + + case "SALE": + /* Process sale of materials */ + for (var matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + var mat = warehouse.materials[matName]; + if (mat.sCost < 0 || mat.sllman[0] === false) { + mat.sll = 0; + continue; + } + var mat = warehouse.materials[matName]; + + var sCost; + if (isString(mat.sCost)) { + sCost = mat.sCost.replace(/MP/g, mat.bCost); + sCost = eval(sCost); + } else { + sCost = mat.sCost; + } + + //Calculate how much of the material sells (per second) + let markup = 1, markupLimit = mat.getMarkupLimit(); + if (sCost > mat.bCost) { + //Penalty if difference between sCost and bCost is greater than markup limit + if ((sCost - mat.bCost) > markupLimit) { + markup = Math.pow(markupLimit / (sCost - mat.bCost), 2); + } + } else if (sCost < mat.bCost) { + if (sCost <= 0) { + markup = 1e12; //Sell everything, essentially discard + } else { + //Lower prices than market increases sales + markup = mat.bCost / sCost; + } + } + var businessFactor = this.getBusinessFactor(office); //Business employee productivity + var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity + var marketFactor = this.getMarketFactor(mat); //Competition + demand + var maxSell = (mat.qlt + .001) + * marketFactor + * markup + * businessFactor + * company.getSalesMultiplier() + * advertisingFactor + * this.getSalesMultiplier(); + var sellAmt; + if (isString(mat.sllman[1])) { + //Dynamically evaluated + var tmp = mat.sllman[1].replace(/MAX/g, maxSell); + tmp = tmp.replace(/PROD/g, mat.prd); + try { + sellAmt = eval(tmp); + } catch(e) { + dialogBoxCreate("Error evaluating your sell amount for material " + mat.name + + " in " + this.name + "'s " + city + " office. The sell amount " + + "is being set to zero"); + sellAmt = 0; + } + sellAmt = Math.min(maxSell, sellAmt); + } else if (mat.sllman[1] === -1) { + //Backwards compatibility, -1 = MAX + sellAmt = maxSell; + } else { + //Player's input value is just a number + sellAmt = Math.min(maxSell, mat.sllman[1]); + } + + sellAmt = (sellAmt * SecsPerMarketCycle * marketCycles); + sellAmt = Math.min(mat.qty, sellAmt); + if (sellAmt < 0) { + console.log("sellAmt calculated to be negative"); + mat.sll = 0; + continue; + } + if (sellAmt && sCost >= 0) { + mat.qty -= sellAmt; + revenue += (sellAmt * sCost); + mat.sll = sellAmt / (SecsPerMarketCycle * marketCycles); + } else { + mat.sll = 0; + } + } + } //End processing of sale of materials + break; + + case "EXPORT": + for (var matName in warehouse.materials) { + if (warehouse.materials.hasOwnProperty(matName)) { + var mat = warehouse.materials[matName]; + mat.totalExp = 0; //Reset export + for (var expI = 0; expI < mat.exp.length; ++expI) { + var exp = mat.exp[expI]; + var amt = exp.amt.replace(/MAX/g, mat.qty / (SecsPerMarketCycle * marketCycles)); + try { + amt = eval(amt); + } catch(e) { + dialogBoxCreate("Calculating export for " + mat.name + " in " + + this.name + "'s " + city + " division failed with " + + "error: " + e); + continue; + } + if (isNaN(amt)) { + dialogBoxCreate("Error calculating export amount for " + mat.name + " in " + + this.name + "'s " + city + " division."); + continue; + } + amt = amt * SecsPerMarketCycle * marketCycles; + + if (mat.qty < amt) { + amt = mat.qty; + } + if (amt === 0) { + break; //None left + } + for (var foo = 0; foo < company.divisions.length; ++foo) { + if (company.divisions[foo].name === exp.ind) { + var expIndustry = company.divisions[foo]; + var expWarehouse = expIndustry.warehouses[exp.city]; + if (!(expWarehouse instanceof Warehouse)) { + console.log("ERROR: Invalid export! " + expIndustry.name + " " + exp.city); + break; + } + + //Make sure theres enough space in warehouse + if (expWarehouse.sizeUsed >= expWarehouse.size) { + return; //Warehouse at capacity + } else { + var maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]); + amt = Math.min(maxAmt, amt); + } + expWarehouse.materials[matName].imp += (amt / (SecsPerMarketCycle * marketCycles)); + expWarehouse.materials[matName].qty += amt; + expWarehouse.materials[matName].qlt = mat.qlt; + mat.qty -= amt; + mat.totalExp += amt; + expIndustry.updateWarehouseSizeUsed(expWarehouse); + break; + } + } + } + //totalExp should be per second + mat.totalExp /= (SecsPerMarketCycle * marketCycles); + } + } + + break; + + case "START": + break; + default: + console.log("ERROR: Invalid state: " + this.state); + break; + } //End switch(this.state) + this.updateWarehouseSizeUsed(warehouse); + + } // End warehouse + + //Produce Scientific Research based on R&D employees + //Scientific Research can be produced without a warehouse + if (office instanceof OfficeSpace) { + this.sciResearch.qty += (.005 + * Math.pow(office.employeeProd[EmployeePositions.RandD], 0.55) + * company.getScientificResearchMultiplier() + * this.getScientificResearchMultiplier()); + } + } + return [revenue, expenses]; +} + +//Process production & sale of this industry's FINISHED products (including all of their stats) +Industry.prototype.processProducts = function(marketCycles=1, corporation) { + var revenue = 0, expenses = 0; + + //Create products + if (this.state === "PRODUCTION") { + for (var prodName in this.products) { + if (this.products.hasOwnProperty(prodName)) { + var prod = this.products[prodName]; + if (!prod.fin) { + var city = prod.createCity, office = this.offices[city]; + var total = office.employeeProd[EmployeePositions.Operations] + + office.employeeProd[EmployeePositions.Engineer] + + office.employeeProd[EmployeePositions.Management], ratio; + if (total === 0) { + ratio = 0; + } else { + ratio = office.employeeProd[EmployeePositions.Engineer] / total + + office.employeeProd[EmployeePositions.Operations] / total + + office.employeeProd[EmployeePositions.Management] / total; + } + prod.createProduct(marketCycles, ratio * Math.pow(total, 0.35)); + if (prod.prog >= 100) { + prod.finishProduct(office.employeeProd, this); + } + break; + } + } + } + } + + //Produce Products + for (var prodName in this.products) { + if (this.products.hasOwnProperty(prodName)) { + var prod = this.products[prodName]; + if (prod instanceof Product && prod.fin) { + revenue += this.processProduct(marketCycles, prod, corporation); + } + } + } + return [revenue, expenses]; +} + +//Processes FINISHED products +Industry.prototype.processProduct = function(marketCycles=1, product, corporation) { + var totalProfit = 0; + for (var i = 0; i < Cities.length; ++i) { + var city = Cities[i], office = this.offices[city], warehouse = this.warehouses[city]; + if (warehouse instanceof Warehouse) { + switch(this.state) { + + case "PRODUCTION": + //Calculate the maximum production of this material based + //on the office's productivity + var maxProd = this.getOfficeProductivity(office, {forProduct:true}) + * corporation.getProductionMultiplier() + * this.prodMult // Multiplier from materials + * this.getProductionMultiplier() // Multiplier from research + * this.getProductProductionMultiplier(); // Multiplier from research + let prod; + + //Account for whether production is manually limited + if (product.prdman[city][0]) { + prod = Math.min(maxProd, product.prdman[city][1]); + } else { + prod = maxProd; + } + prod *= (SecsPerMarketCycle * marketCycles); + + //Calculate net change in warehouse storage making the Products will cost + var netStorageSize = product.siz; + for (var reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + var normQty = product.reqMats[reqMatName]; + netStorageSize -= (MaterialSizes[reqMatName] * normQty); + } + } + + //If there's not enough space in warehouse, limit the amount of Product + if (netStorageSize > 0) { + var maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / netStorageSize); + prod = Math.min(maxAmt, prod); + } + + warehouse.smartSupplyStore += (prod / (SecsPerMarketCycle * marketCycles)); + + //Make sure we have enough resources to make our Products + var producableFrac = 1; + for (var reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + var req = product.reqMats[reqMatName] * prod; + if (warehouse.materials[reqMatName].qty < req) { + producableFrac = Math.min(producableFrac, warehouse.materials[reqMatName].qty / req); + } + } + } + + //Make our Products if they are producable + if (producableFrac > 0 && prod > 0) { + for (var reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + var reqMatQtyNeeded = (product.reqMats[reqMatName] * prod * producableFrac); + warehouse.materials[reqMatName].qty -= reqMatQtyNeeded; + warehouse.materials[reqMatName].prd -= reqMatQtyNeeded / (SecsPerMarketCycle * marketCycles); + } + } + //Quantity + product.data[city][0] += (prod * producableFrac); + } + + //Keep track of production Per second + product.data[city][1] = prod * producableFrac / (SecsPerMarketCycle * marketCycles); + break; + + case "SALE": + //Process sale of Products + product.pCost = 0; //Estimated production cost + for (var reqMatName in product.reqMats) { + if (product.reqMats.hasOwnProperty(reqMatName)) { + product.pCost += (product.reqMats[reqMatName] * warehouse.materials[reqMatName].bCost); + } + } + + //Since its a product, its production cost is increased for labor + product.pCost *= ProductProductionCostRatio; + + //Calculate Sale Cost (sCost), which could be dynamically evaluated + var sCost; + if (isString(product.sCost)) { + sCost = product.sCost.replace(/MP/g, product.pCost + product.rat / product.mku); + sCost = eval(sCost); + } else { + sCost = product.sCost; + } + + var markup = 1, markupLimit = product.rat / product.mku; + if (sCost > product.pCost) { + if ((sCost - product.pCost) > markupLimit) { + markup = markupLimit / (sCost - product.pCost); + } + } + var businessFactor = this.getBusinessFactor(office); //Business employee productivity + var advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity + var marketFactor = this.getMarketFactor(product); //Competition + demand + var maxSell = 0.5 + * Math.pow(product.rat, 0.65) + * marketFactor + * corporation.getSalesMultiplier() + * Math.pow(markup, 2) + * businessFactor + * advertisingFactor + * this.getSalesMultiplier(); + var sellAmt; + if (product.sllman[city][0] && isString(product.sllman[city][1])) { + //Sell amount is dynamically evaluated + var tmp = product.sllman[city][1].replace(/MAX/g, maxSell); + tmp = tmp.replace(/PROD/g, product.data[city][1]); + try { + tmp = eval(tmp); + } catch(e) { + dialogBoxCreate("Error evaluating your sell price expression for " + product.name + + " in " + this.name + "'s " + city + " office. Sell price is being set to MAX"); + tmp = maxSell; + } + sellAmt = Math.min(maxSell, tmp); + } else if (product.sllman[city][0] && product.sllman[city][1] > 0) { + //Sell amount is manually limited + sellAmt = Math.min(maxSell, product.sllman[city][1]); + } else { + //Backwards compatibility, -1 = 0 + sellAmt = maxSell; + } + if (sellAmt < 0) { sellAmt = 0; } + sellAmt = sellAmt * SecsPerMarketCycle * marketCycles; + sellAmt = Math.min(product.data[city][0], sellAmt); //data[0] is qty + if (sellAmt && sCost) { + product.data[city][0] -= sellAmt; //data[0] is qty + totalProfit += (sellAmt * sCost); + product.data[city][2] = sellAmt / (SecsPerMarketCycle * marketCycles); //data[2] is sell property + } else { + product.data[city][2] = 0; //data[2] is sell property + } + break; + + case "START": + case "PURCHASE": + case "EXPORT": + break; + default: + console.log("ERROR: Invalid State: " + this.state); + break; + } //End switch(this.state) + } + } + return totalProfit; +} + +Industry.prototype.discontinueProduct = function(product, parentRefs) { + var company = parentRefs.company, industry = parentRefs.industry; + for (var productName in this.products) { + if (this.products.hasOwnProperty(productName)) { + if (product === this.products[productName]) { + delete this.products[productName]; + company.updateUIContent(); + } + } + } +} + +Industry.prototype.upgrade = function(upgrade, refs) { + var corporation = refs.corporation, division = refs.division, + office = refs.office; + var upgN = upgrade[0], basePrice = upgrade[1], priceMult = upgrade[2], + upgradeBenefit = upgrade[3]; + while (this.upgrades.length <= upgN) {this.upgrades.push(0);} + ++this.upgrades[upgN]; + + switch (upgN) { + case 0: //Coffee, 5% energy per employee + for (let i = 0; i < office.employees.length; ++i) { + office.employees[i].ene = Math.min(office.employees[i].ene * 1.05, office.maxEne); + } + break; + case 1: //AdVert.Inc, + var advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier(); + this.awareness += (3 * advMult); + this.popularity += (1 * advMult); + this.awareness *= (1.01 * advMult); + this.popularity *= ((1 + getRandomInt(1, 3) / 100) * advMult); + break; + default: + console.log("ERROR: Un-implemented function index: " + upgN); + break; + } +} + +//Returns how much of a material can be produced based of office productivity (employee stats) +Industry.prototype.getOfficeProductivity = function(office, params) { + var total = office.employeeProd[EmployeePositions.Operations] + + office.employeeProd[EmployeePositions.Engineer] + + office.employeeProd[EmployeePositions.Management], ratio; + if (total === 0) { + ratio = 0; + } else { + ratio = (office.employeeProd[EmployeePositions.Operations] / total) * + (office.employeeProd[EmployeePositions.Engineer] / total) * + (office.employeeProd[EmployeePositions.Management] / total); + ratio = Math.max(0.01, ratio); //Minimum ratio value if you have employees + } + if (params && params.forProduct) { + return ratio * Math.pow(total, 0.25); + } else { + return 2 * ratio * Math.pow(total, 0.35); + } +} + +//Returns a multiplier based on the office' 'Business' employees that affects sales +Industry.prototype.getBusinessFactor = function(office) { + var ratioMult = 1; + if (office.employeeProd["total"] > 0) { + ratioMult = 1 + (office.employeeProd[EmployeePositions.Business] / office.employeeProd["total"]); + } + return ratioMult * Math.pow(1 + office.employeeProd[EmployeePositions.Business], 0.25); +} + +//Returns a set of multipliers based on the Industry's awareness, popularity, and advFac. This +//multiplier affects sales. The result is: +// [Total sales mult, total awareness mult, total pop mult, awareness/pop ratio mult] +Industry.prototype.getAdvertisingFactors = function() { + var awarenessFac = Math.pow(this.awareness + 1, this.advFac); + var popularityFac = Math.pow(this.popularity + 1, this.advFac); + var ratioFac = (this.awareness === 0 ? 0.01 : Math.max((this.popularity + .001) / this.awareness, 0.01)); + var totalFac = Math.pow(awarenessFac * popularityFac * ratioFac, 0.85); + return [totalFac, awarenessFac, popularityFac, ratioFac]; +} + +//Returns a multiplier based on a materials demand and competition that affects sales +Industry.prototype.getMarketFactor = function(mat) { + return mat.dmd * (100 - mat.cmp)/100; +} + +// Returns a boolean indicating whether this Industry has the specified Research +Industry.prototype.hasResearch = function(name) { + return (this.researched[name] === true); +} + +Industry.prototype.updateResearchTree = function() { + const researchTree = IndustryResearchTrees[this.type]; + + // Since ResearchTree data isnt saved, we'll update the Research Tree data + // based on the stored 'researched' property in the Industry object + if (Object.keys(researchTree.researched).length !== Object.keys(this.researched).length) { + console.log("Updating Corporation Research Tree Data"); + for (let research in this.researched) { + researchTree.research(research); + } + } +} + +// Get multipliers from Research +Industry.prototype.getAdvertisingMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getAdvertisingMultiplier(); +} + +Industry.prototype.getEmployeeChaMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getEmployeeChaMultiplier(); +} + +Industry.prototype.getEmployeeCreMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getEmployeeCreMultiplier(); +} + +Industry.prototype.getEmployeeEffMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getEmployeeEffMultiplier(); +} + +Industry.prototype.getEmployeeIntMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getEmployeeIntMultiplier(); +} + +Industry.prototype.getProductionMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getProductionMultiplier(); +} + +Industry.prototype.getProductProductionMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getProductProductionMultiplier(); +} + +Industry.prototype.getSalesMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getSalesMultiplier(); +} + +Industry.prototype.getScientificResearchMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getScientificResearchMultiplier(); +} + +Industry.prototype.getStorageMultiplier = function() { + this.updateResearchTree(); + return IndustryResearchTrees[this.type].getStorageMultiplier(); +} + +// Create the Research Tree UI for this Industry +Industry.prototype.createResearchBox = function() { + const boxId = "corporation-research-popup-box"; + + if (researchTreeBoxOpened) { + // It's already opened, so delete it to refresh content + removeElementById(boxId); + researchTreeBox = null; + } + + this.updateResearchTree(); + const researchTree = IndustryResearchTrees[this.type]; + + + // Create the popup first, so that the tree diagram can be added to it + // This is handled by Treant + researchTreeBox = createPopup(boxId, [], { backgroundColor: "black" }); + + // Get the tree's markup (i.e. config) for Treant + const markup = researchTree.createTreantMarkup(); + markup.chart.container = "#" + boxId + "-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, + }, + } + + // Construct the tree with Treant + const treantTree = new 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 (this.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 (this.sciResearch.qty >= research.cost) { + this.sciResearch.qty -= research.cost; + + // Get the Node from the Research Tree and set its 'researched' property + researchTree.research(allResearch[i]); + this.researched[allResearch[i]] = true; + + return this.createResearchBox(); + } else { + dialogBoxCreate(`You do not have enough Scientific Research for ${research.name}`); + } + }); + } + + + const boxContent = document.getElementById(`${boxId}-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()}`, + })); + + // Close button + boxContent.appendChild(createPopupCloseButton(researchTreeBox, { + class: "std-button", + display: "block", + innerText: "Close", + })); + } + + researchTreeBoxOpened = true; +} + +Industry.prototype.toJSON = function() { + return Generic_toJSON("Industry", this); +} + +Industry.fromJSON = function(value) { + return Generic_fromJSON(Industry, value.data); +} + +Reviver.constructors.Industry = Industry; + +function Employee(params={}) { + if (!(this instanceof Employee)) { + return new Employee(params); + } + 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.age = params.age ? params.age : getRandomInt(20, 50); + 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.pro = 0; //Productivity, This is calculated + + this.cyclesUntilRaise = CyclesPerEmployeeRaise; + + this.loc = params.loc ? params.loc : ""; + this.pos = EmployeePositions.Unassigned; +} + +//Returns the amount the employee needs to be paid +Employee.prototype.process = function(marketCycles=1, office) { + var gain = 0.001 * marketCycles, + det = gain * Math.random(); + this.age += gain; + this.exp += gain; + if (this.age > 150) { + this.int -= det; + this.eff -= det; + this.cha -= det; + } + + // Employee salaries slowly go up over time + this.cyclesUntilRaise -= marketCycles; + if (this.cyclesUntilRaise <= 0) { + this.salary += EmployeeRaiseAmount; + this.cyclesUntilRaise += CyclesPerEmployeeRaise; + } + + //Training + var 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; + } + + //Weight based on how full office is + //Too many employees = more likely to decrease energy and happiness + var officeCapacityWeight = 0.5 * (office.employees.length / office.size - 0.5); + if (Math.random() < 0.5 - officeCapacityWeight) { + this.ene += det; + this.hap += det; + } else { + this.ene -= det; + this.hap -= det; + } + if (this.ene < office.minEne) {this.ene = office.minEne;} + if (this.hap < office.minHap) {this.hap = office.minHap;} + var salary = this.sal * marketCycles * SecsPerMarketCycle; + return salary; +} + +Employee.prototype.calculateProductivity = function(corporation, industry) { + var 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; + 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.log("ERROR: Invalid employee position: " + this.pos); + break; + } + return prodBase * prodMult; +} + +//Process benefits from having an office party thrown +Employee.prototype.throwParty = function(money) { + var 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 +Employee.prototype.createUI = function(panel, corporation, industry) { + var 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) + "
" + + "Happiness: " + formatNumber(this.hap, 3) + "
" + + "Energy: " + formatNumber(this.ene, 3) + "
" + + "Age: " + formatNumber(this.age, 3) + "
" + + "Intelligence: " + formatNumber(effInt, 3) + "
" + + "Charisma: " + formatNumber(effCha, 3) + "
" + + "Experience: " + formatNumber(this.exp, 3) + "
" + + "Creativity: " + formatNumber(effCre, 3) + "
" + + "Efficiency: " + formatNumber(effEff, 3) + "
" + + "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s
", + })); + + //Selector for employee position + var selector = createElement("select", {}); + for (var key in EmployeePositions) { + if (EmployeePositions.hasOwnProperty(key)) { + selector.add(createElement("option", { + text: EmployeePositions[key], + value: EmployeePositions[key], + })); + } + } + + selector.addEventListener("change", ()=>{ + this.pos = selector.options[selector.selectedIndex].value; + }); + + //Set initial value of selector + for (var i = 0; i < selector.length; ++i) { + if (selector.options[i].value === this.pos) { + selector.selectedIndex = i; + break; + } + } + panel.appendChild(selector); +} + +Employee.prototype.updateUI = function(panel, corporation, industry) { + var 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(); + if (panel == null) { + console.log("ERROR: Employee.updateUI() called with null panel"); + return; + } + var text = document.getElementById("cmpy-mgmt-employee-" + this.name + "-panel-text"); + if (text == null) { + return this.createUI(panel); + } + text.innerHTML = "Morale: " + formatNumber(this.mor, 3) + "
" + + "Happiness: " + formatNumber(this.hap, 3) + "
" + + "Energy: " + formatNumber(this.ene, 3) + "
" + + "Age: " + formatNumber(this.age, 3) + "
" + + "Intelligence: " + formatNumber(effInt, 3) + "
" + + "Charisma: " + formatNumber(effCha, 3) + "
" + + "Experience: " + formatNumber(this.exp, 3) + "
" + + "Creativity: " + formatNumber(effCre, 3) + "
" + + "Efficiency: " + formatNumber(effEff, 3) + "
" + + "Salary: " + numeralWrapper.format(this.sal, "$0.000a") + "/ s
"; +} + +Employee.prototype.toJSON = function() { + return Generic_toJSON("Employee", this); +} + +Employee.fromJSON = function(value) { + return Generic_fromJSON(Employee, value.data); +} + +Reviver.constructors.Employee = Employee; + +var OfficeSpaceTiers = { + Basic: "Basic", + Enhanced: "Enhanced", + Luxurious: "Luxurious", + Extravagant: "Extravagant" +} + +function OfficeSpace(params={}) { + 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; + this.tier = OfficeSpaceTiers.Basic; + + // Min/max energy of employees + this.minEne = 0; + this.maxEne = 100; + + // Min/max Happiness of office + this.minHap = 0; + this.maxHap = 100; + + // Maximum Morale of office + this.maxMor = 100; + + this.employees = []; + this.employeeProd = { + [EmployeePositions.Operations]: 0, + [EmployeePositions.Engineer]: 0, + [EmployeePositions.Business]: 0, + [EmployeePositions.Management]: 0, + [EmployeePositions.RandD]: 0, + total: 0, + }; +} + +OfficeSpace.prototype.process = function(marketCycles=1, parentRefs) { + var corporation = parentRefs.corporation, industry = parentRefs.industry; + + // 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 + var perfMult=1; //Multiplier for employee morale/happiness/energy based on company performance + if (industry.funds < 0 && industry.lastCycleRevenue < 0) { + perfMult = Math.pow(0.99, marketCycles); + } else if (industry.funds > 0 && industry.lastCycleRevenue > 0) { + perfMult = Math.pow(1.01, marketCycles); + } + + const hasAutobrew = industry.hasResearch("AutoBrew"); + const hasAutoparty = industry.hasResearch("AutoPartyManager"); + + var 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(marketCycles, parentRefs); + return salaryPaid; +} + +OfficeSpace.prototype.calculateEmployeeProductivity = function(marketCycles=1, parentRefs) { + var company = parentRefs.corporation, industry = parentRefs.industry; + + //Reset + for (const name in this.employeeProd) { + if (this.employeeProd.hasOwnProperty(name)) { + this.employeeProd[name] = 0; + } + } + + var total = 0; + for (let i = 0; i < this.employees.length; ++i) { + const employee = this.employees[i]; + const prod = employee.calculateProductivity(company, industry); + this.employeeProd[employee.pos] += prod; + total += prod; + } + this.employeeProd["total"] = total; +} + +//Takes care of UI as well +OfficeSpace.prototype.findEmployees = function(parentRefs) { + var company = parentRefs.corporation, division = parentRefs.industry; + if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} + + //Generate three random employees (meh, decent, amazing) + var mult1 = getRandomInt(25, 50)/100, + mult2 = getRandomInt(51, 75)/100, + mult3 = getRandomInt(76, 100)/100; + var int = getRandomInt(50, 100), + cha = getRandomInt(50, 100), + exp = getRandomInt(50, 100), + cre = getRandomInt(50, 100), + eff = getRandomInt(50, 100), + sal = EmployeeSalaryMultiplier * (int + cha + exp + cre + eff); + + var emp1 = new Employee({ + intelligence: int * mult1, + charisma: cha * mult1, + experience: exp * mult1, + creativity: cre * mult1, + efficiency: eff * mult1, + salary: sal * mult1, + }); + + var emp2 = new Employee({ + intelligence: int * mult2, + charisma: cha * mult2, + experience: exp * mult2, + creativity: cre * mult2, + efficiency: eff * mult2, + salary: sal * mult2, + }); + + var emp3 = new Employee({ + intelligence: int * mult3, + charisma: cha * mult3, + experience: exp * mult3, + creativity: cre * mult3, + efficiency: eff * mult3, + salary: sal * mult3, + }); + + var text = createElement("h1", { + innerHTML: "Select one of the following candidates for hire:", + }); + + var createEmpDiv = function(employee, office) { + var div = createElement("div", { + class:"cmpy-mgmt-find-employee-option", + innerHTML: "Intelligence: " + formatNumber(employee.int, 1) + "
" + + "Charisma: " + formatNumber(employee.cha, 1) + "
" + + "Experience: " + formatNumber(employee.exp, 1) + "
" + + "Creativity: " + formatNumber(employee.cre, 1) + "
" + + "Efficiency: " + formatNumber(employee.eff, 1) + "
" + + "Salary: " + numeralWrapper.format(employee.sal, '$0.000a') + " \ s
", + clickListener:()=>{ + office.hireEmployee(employee, parentRefs); + removeElementById("cmpy-mgmt-hire-employee-popup"); + return false; + } + }); + return div; + }; + + var cancelBtn = createElement("a", { + class:"a-link-button", + innerText:"Cancel", + float:"right", + clickListener:()=>{ + removeElementById("cmpy-mgmt-hire-employee-popup"); + return false; + } + }); + + var elems = [text, + createEmpDiv(emp1, this), + createEmpDiv(emp2, this), + createEmpDiv(emp3, this), + cancelBtn]; + + createPopup("cmpy-mgmt-hire-employee-popup", elems); +} + +OfficeSpace.prototype.hireEmployee = function(employee, parentRefs) { + var company = parentRefs.corporation, division = parentRefs.industry; + var yesBtn = yesNoTxtInpBoxGetYesButton(), + noBtn = yesNoTxtInpBoxGetNoButton(); + yesBtn.innerHTML = "Hire"; + noBtn.innerHTML = "Cancel"; + yesBtn.addEventListener("click", () => { + var name = yesNoTxtInpBoxGetInput(); + for (var i = 0; i < this.employees.length; ++i) { + if (this.employees[i].name === name) { + dialogBoxCreate("You already have an employee with this nickname! Please give every employee a unique nickname."); + return false; + } + } + employee.name = name; + this.employees.push(employee); + company.displayDivisionContent(division, currentCityUi); + return yesNoTxtInpBoxClose(); + }); + noBtn.addEventListener("click", ()=>{ + return yesNoTxtInpBoxClose(); + }); + yesNoTxtInpBoxCreate("Give your employee a nickname!"); +} + +OfficeSpace.prototype.hireRandomEmployee = function(parentRefs) { + var company = parentRefs.corporation, division = parentRefs.industry; + if (document.getElementById("cmpy-mgmt-hire-employee-popup") != null) {return;} + + //Generate three random employees (meh, decent, amazing) + var mult = getRandomInt(76, 100)/100; + var int = getRandomInt(50, 100), + cha = getRandomInt(50, 100), + exp = getRandomInt(50, 100), + cre = getRandomInt(50, 100), + eff = getRandomInt(50, 100), + sal = EmployeeSalaryMultiplier * (int + cha + exp + cre + eff); + + var emp = new Employee({ + intelligence: int * mult, + charisma: cha * mult, + experience: exp * mult, + creativity: cre * mult, + efficiency: eff * mult, + salary: sal * mult, + }); + + var name = generateRandomString(7); + + for (var i = 0; i < this.employees.length; ++i) { + if (this.employees[i].name === name) { + return this.hireRandomEmployee(parentRefs); + } + } + emp.name = name; + this.employees.push(emp); + company.displayDivisionContent(division, currentCityUi); +} + +//Finds the first unassigned employee and assigns its to the specified job +OfficeSpace.prototype.assignEmployeeToJob = function(job) { + for (var 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 +OfficeSpace.prototype.unassignEmployeeFromJob = function(job) { + for (var i = 0; i < this.employees.length; ++i) { + if (this.employees[i].pos === job) { + this.employees[i].pos = EmployeePositions.Unassigned; + return true; + } + } + return false; +} + +OfficeSpace.prototype.toJSON = function() { + return Generic_toJSON("OfficeSpace", this); +} + +OfficeSpace.fromJSON = function(value) { + return Generic_fromJSON(OfficeSpace, value.data); +} + +Reviver.constructors.OfficeSpace = OfficeSpace; + +function Corporation(params={}) { + this.name = params.name ? params.name : "The Corporation"; + + //A division/business sector is represented by the object: + this.divisions = []; + + //Financial stats + this.funds = new Decimal(150e9); + this.revenue = new Decimal(0); + this.expenses = new Decimal(0); + this.fundingRound = 0; + this.public = false; //Publicly traded + this.totalShares = INITIALSHARES; // Total existing shares + this.numShares = INITIALSHARES; // Total shares owned by player + this.shareSalesUntilPriceUpdate = SHARESPERPRICEUPDATE; + this.shareSaleCooldown = 0; // Game cycles until player can sell shares again + this.issueNewSharesCooldown = 0; // Game cycles until player can issue shares again + this.dividendPercentage = 0; + this.dividendTaxPercentage = 50; + this.issuedShares = 0; + this.sharePrice = 0; + this.storedCycles = 0; + + var numUnlockUpgrades = Object.keys(CorporationUnlockUpgrades).length, + numUpgrades = Object.keys(CorporationUpgrades).length; + + this.unlockUpgrades = Array(numUnlockUpgrades).fill(0); + this.upgrades = Array(numUpgrades).fill(0); + this.upgradeMultipliers = Array(numUpgrades).fill(1); + + this.state = new CorporationState(); +} + +Corporation.prototype.getState = function() { + return this.state.getState(); +} + +Corporation.prototype.storeCycles = function(numCycles=1) { + this.storedCycles += numCycles; +} + +Corporation.prototype.process = function() { + var corp = this; + if (this.storedCycles >= CyclesPerIndustryStateCycle) { + const state = this.getState(); + const marketCycles = 1; + const gameCycles = (marketCycles * CyclesPerIndustryStateCycle); + this.storedCycles -= gameCycles; + + this.divisions.forEach(function(ind) { + ind.process(marketCycles, state, corp); + }); + + // 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); + }); + var profit = this.revenue.minus(this.expenses); + const cycleProfit = profit.times(marketCycles * SecsPerMarketCycle); + if (isNaN(this.funds)) { + dialogBoxCreate("There was an error calculating your Corporations funds and they got reset to 0. " + + "This is a bug. Please report to game developer.

" + + "(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 > DividendMaxPercentage) { + 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.funds = this.funds.plus(retainedEarnings); + } + } else { + this.funds = this.funds.plus(cycleProfit); + } + + this.updateSharePrice(); + } + + this.state.nextState(); + + if (routing.isOn(Page.Corporation)) { this.rerender(); } + } +} + +Corporation.prototype.determineValuation = function() { + var 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; +} + +Corporation.prototype.getInvestment = function() { + var val = this.determineValuation(), percShares; + let roundMultiplier = 4; + switch (this.fundingRound) { + case 0: //Seed + percShares = 0.10; + roundMultiplier = 5; + break; + case 1: //Series A + percShares = 0.35; + roundMultiplier = 4; + break; + case 2: //Series B + percShares = 0.25; + roundMultiplier = 4; + break; + case 3: //Series C + percShares = 0.20; + roundMultiplier = 3.5; + break; + case 4: + return; + } + var funding = val * percShares * roundMultiplier, + investShares = Math.floor(INITIALSHARES * percShares), + yesBtn = yesNoBoxGetYesButton(), + noBtn = yesNoBoxGetNoButton(); + yesBtn.innerHTML = "Accept"; + noBtn.innerHML = "Reject"; + yesBtn.addEventListener("click", ()=>{ + ++this.fundingRound; + this.funds = this.funds.plus(funding); + this.numShares -= investShares; + this.displayCorporationOverviewContent(); + return yesNoBoxClose(); + }); + noBtn.addEventListener("click", ()=>{ + return yesNoBoxClose(); + }); + yesNoBoxCreate("An investment firm has offered you " + numeralWrapper.format(funding, '$0.000a') + + " in funding in exchange for a " + numeralWrapper.format(percShares*100, "0.000a") + + "% stake in the company (" + numeralWrapper.format(investShares, '0.000a') + " shares).

" + + "Do you accept or reject this offer?

" + + "Hint: Investment firms will offer more money if your corporation is turning a profit"); +} + +Corporation.prototype.goPublic = function() { + var goPublicPopupId = "cmpy-mgmt-go-public-popup"; + var initialSharePrice = this.determineValuation() / (this.totalShares); + var txt = createElement("p", { + innerHTML: "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.format(initialSharePrice, '$0.000a') + " per share " + + "(the IPO money will be deposited directly into your Corporation's funds).

" + + "You have a total of " + numeralWrapper.format(this.numShares, "0.000a") + " of shares that you can issue.", + }); + var yesBtn; + var input = createElement("input", { + type:"number", + placeholder: "Shares to issue", + onkeyup:(e)=>{ + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {yesBtn.click();} + } + }); + var br = createElement("br", {}); + yesBtn = createElement("a", { + class:"a-link-button", + innerText:"Go Public", + clickListener:()=>{ + var numShares = Math.round(input.value); + var initialSharePrice = this.determineValuation() / (this.totalShares); + if (isNaN(numShares)) { + dialogBoxCreate("Invalid value for number of issued shares"); + return false; + } + if (numShares > this.numShares) { + dialogBoxCreate("Error: You don't have that many shares to issue!"); + return false; + } + this.public = true; + this.sharePrice = initialSharePrice; + this.issuedShares = numShares; + this.numShares -= numShares; + this.funds = this.funds.plus(numShares * initialSharePrice); + this.displayCorporationOverviewContent(); + removeElementById(goPublicPopupId); + dialogBoxCreate(`You took your ${this.name} public and earned ` + + `${numeralWrapper.formatMoney(numShares * initialSharePrice)} in your IPO`); + return false; + } + }); + var noBtn = createElement("a", { + class:"a-link-button", + innerText:"Cancel", + clickListener:()=>{ + removeElementById(goPublicPopupId); + return false; + } + }); + createPopup(goPublicPopupId, [txt, br, input, yesBtn, noBtn]); +} + +Corporation.prototype.getTargetSharePrice = function() { + // 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); +} + +Corporation.prototype.updateSharePrice = function() { + 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;} +} + +Corporation.prototype.immediatelyUpdateSharePrice = function() { + 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] +Corporation.prototype.calculateShareSale = function(numShares) { + let sharesTracker = numShares; + let sharesUntilUpdate = this.shareSalesUntilPriceUpdate; + let sharePrice = this.sharePrice; + let sharesSold = 0; + let profit = 0; + + const maxIterations = Math.ceil(numShares / SHARESPERPRICEUPDATE); + if (isNaN(maxIterations) || maxIterations > 10e6) { + console.error(`Something went wrong or unexpected when calculating share sale. Maxiterations calculated to be ${maxIterations}`); + return; + } + + for (let i = 0; i < maxIterations; ++i) { + if (sharesTracker < sharesUntilUpdate) { + profit += (sharePrice * sharesTracker); + sharesUntilUpdate -= sharesTracker; + break; + } else { + profit += (sharePrice * sharesUntilUpdate); + sharesUntilUpdate = 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]; +} + +Corporation.prototype.convertCooldownToString = function(cd) { + // The cooldown value is based on game cycles. Convert to a simple string + const CyclesPerSecond = 1000 / CONSTANTS.MilliPerCycle; + 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 +Corporation.prototype.unlock = function(upgrade) { + 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 +Corporation.prototype.upgrade = function(upgrade) { + var 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);} + var 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 (var i = 0; i < this.divisions.length; ++i) { + var industry = this.divisions[i]; + for (var city in industry.warehouses) { + if (industry.warehouses.hasOwnProperty(city) && industry.warehouses[city] instanceof Warehouse) { + industry.warehouses[city].updateSize(this, industry); + } + } + } + } + + this.updateCorporationOverviewContent(); +} + +Corporation.prototype.getProductionMultiplier = function() { + var mult = this.upgradeMultipliers[0]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getStorageMultiplier = function() { + var mult = this.upgradeMultipliers[1]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getDreamSenseGain = function() { + var gain = this.upgradeMultipliers[2] - 1; + return gain <= 0 ? 0 : gain; +} + +Corporation.prototype.getAdvertisingMultiplier = function() { + var mult = this.upgradeMultipliers[3]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getEmployeeCreMultiplier = function() { + var mult = this.upgradeMultipliers[4]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getEmployeeChaMultiplier = function() { + var mult = this.upgradeMultipliers[5]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getEmployeeIntMultiplier = function() { + var mult = this.upgradeMultipliers[6]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getEmployeeEffMultiplier = function() { + var mult = this.upgradeMultipliers[7]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getSalesMultiplier = function() { + var mult = this.upgradeMultipliers[8]; + if (isNaN(mult) || mult < 1) {return 1;} else {return mult;} +} + +Corporation.prototype.getScientificResearchMultiplier = function() { + var 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 +Corporation.prototype.getStarterGuide = function() { + // Check if player already has Corporation Handbook + let homeComp = Player.getHomeComputer(), + hasHandbook = false, + handbookFn = "corporation-management-handbook.lit"; + 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 false; +} + +let corpRouting; +let eventHandler; +let companyManagementDiv; +Corporation.prototype.createUI = function() { + companyManagementDiv = createElement("div", { + id:"cmpy-mgmt-container", + position:"fixed", + class:"generic-menupage-container" + }); + document.getElementById("entire-game-container").appendChild(companyManagementDiv); + + corpRouting = new CorporationRouting(this); + eventHandler = new CorporationEventHandler(this, corpRouting); + + this.rerender(); +} + +Corporation.prototype.rerender = function() { + if (companyManagementDiv == null || corpRouting == null || eventHandler == null) { + console.warn(`Corporation.rerender() called when companyManagementDiv, corpRouting, or eventHandler is null`); + return; + } + if (!routing.isOn(Page.Corporation)) { return; } + + console.log("Re-rendering..."); + + ReactDOM.render(, companyManagementDiv); +} + +Corporation.prototype.clearUI = function() { + if (companyManagementDiv instanceof HTMLElement) { + ReactDOM.unmountComponentAtNode(companyManagementDiv); + removeElementById(companyManagementDiv.id); + } + + companyManagementDiv = null; + document.getElementById("character-overview-wrapper").style.visibility = "visible"; +} + +Corporation.prototype.toJSON = function() { + return Generic_toJSON("Corporation", this); +} + +Corporation.fromJSON = function(value) { + return Generic_fromJSON(Corporation, value.data); +} + +Reviver.constructors.Corporation = Corporation; + +export {Corporation, Industry, OfficeSpace, Warehouse}; diff --git a/src/Corporation/IndustryData.ts b/src/Corporation/IndustryData.ts index 994f4aeb6..699e24a53 100644 --- a/src/Corporation/IndustryData.ts +++ b/src/Corporation/IndustryData.ts @@ -1,5 +1,6 @@ import { ResearchTree } from "./ResearchTree"; -import { getBaseResearchTreeCopy } from "./data/BaseResearchTree"; +import { getBaseResearchTreeCopy, + getProductIndustryResearchTreeCopy } from "./data/BaseResearchTree"; import { numeralWrapper } from "../ui/numeralFormat"; @@ -112,15 +113,15 @@ export let IndustryResearchTrees: IIndustryMap = { Agriculture: getBaseResearchTreeCopy(), Fishing: getBaseResearchTreeCopy(), Mining: getBaseResearchTreeCopy(), - Food: getBaseResearchTreeCopy(), - Tobacco: getBaseResearchTreeCopy(), + Food: getProductIndustryResearchTreeCopy(), + Tobacco: getProductIndustryResearchTreeCopy(), Chemical: getBaseResearchTreeCopy(), - Pharmaceutical: getBaseResearchTreeCopy(), - Computer: getBaseResearchTreeCopy(), - Robotics: getBaseResearchTreeCopy(), - Software: getBaseResearchTreeCopy(), - Healthcare: getBaseResearchTreeCopy(), - RealEstate: getBaseResearchTreeCopy(), + Pharmaceutical: getProductIndustryResearchTreeCopy(), + Computer: getProductIndustryResearchTreeCopy(), + Robotics: getProductIndustryResearchTreeCopy(), + Software: getProductIndustryResearchTreeCopy(), + Healthcare: getProductIndustryResearchTreeCopy(), + RealEstate: getProductIndustryResearchTreeCopy(), } export function resetIndustryResearchTrees() { diff --git a/src/Corporation/Material.ts b/src/Corporation/Material.ts index 40a75cbe3..30c82c139 100644 --- a/src/Corporation/Material.ts +++ b/src/Corporation/Material.ts @@ -12,7 +12,6 @@ export class Material { return Generic_fromJSON(Material, value.data); } - // Name of material name: string = "InitName"; @@ -64,6 +63,10 @@ export class Material { prdman: any[] = [false, 0]; // Production sllman: any[] = [false, 0]; // Sale + // Flags that signal whether automatic sale pricing through Market TA is enabled + marketTa1: boolean = false; + marketTa2: boolean = false; + constructor(params: IConstructorParams = {}) { if (params.name) { this.name = params.name; } this.init(); diff --git a/src/Corporation/Research.ts b/src/Corporation/Research.ts index dcce1e50c..ee017ec06 100644 --- a/src/Corporation/Research.ts +++ b/src/Corporation/Research.ts @@ -8,6 +8,7 @@ export interface IConstructorParams { employeeEffMult?: number; employeeIntMult?: number; productionMult?: number; + productProductionMult?: number; salesMult?: number; sciResearchMult?: number; storageMult?: number; @@ -30,6 +31,7 @@ export class Research { employeeEffMult: number = 1; employeeIntMult: number = 1; productionMult: number = 1; + productProductionMult: number = 1; salesMult: number = 1; sciResearchMult: number = 1; storageMult: number = 1; @@ -38,14 +40,15 @@ export class Research { this.name = p.name; this.cost = p.cost; this.desc = p.desc; - if (p.advertisingMult) { this.advertisingMult = p.advertisingMult; } - if (p.employeeChaMult) { this.employeeChaMult = p.employeeChaMult; } - if (p.employeeCreMult) { this.employeeCreMult = p.employeeCreMult; } - if (p.employeeEffMult) { this.employeeEffMult = p.employeeEffMult; } - if (p.employeeIntMult) { this.employeeIntMult = p.employeeIntMult; } - if (p.productionMult) { this.productionMult = p.productionMult; } - if (p.salesMult) { this.salesMult = p.salesMult; } - if (p.sciResearchMult) { this.sciResearchMult = p.sciResearchMult; } - if (p.storageMult) { this.storageMult = p.storageMult; } + if (p.advertisingMult) { this.advertisingMult = p.advertisingMult; } + if (p.employeeChaMult) { this.employeeChaMult = p.employeeChaMult; } + if (p.employeeCreMult) { this.employeeCreMult = p.employeeCreMult; } + if (p.employeeEffMult) { this.employeeEffMult = p.employeeEffMult; } + if (p.employeeIntMult) { this.employeeIntMult = p.employeeIntMult; } + if (p.productionMult) { this.productionMult = p.productionMult; } + if (p.productProductionMult) { this.productProductionMult = p.productProductionMult; } + if (p.salesMult) { this.salesMult = p.salesMult; } + if (p.sciResearchMult) { this.sciResearchMult = p.sciResearchMult; } + if (p.storageMult) { this.storageMult = p.storageMult; } } } diff --git a/src/Corporation/ResearchTree.ts b/src/Corporation/ResearchTree.ts index 7e77aa978..9cbdd8afa 100644 --- a/src/Corporation/ResearchTree.ts +++ b/src/Corporation/ResearchTree.ts @@ -185,6 +185,10 @@ export class ResearchTree { return this.getMultiplierHelper("productionMult"); } + getProductProductionMultiplier(): number { + return this.getMultiplierHelper("productProductionMult"); + } + getSalesMultiplier(): number { return this.getMultiplierHelper("salesMult"); } diff --git a/src/Corporation/Warehouse.ts b/src/Corporation/Warehouse.ts new file mode 100644 index 000000000..a6bf7ce27 --- /dev/null +++ b/src/Corporation/Warehouse.ts @@ -0,0 +1,100 @@ +import { Material } from "./Material"; +import { MaterialSizes } from "./MaterialSizes"; +import { IMap } from "../types"; +import { numeralWrapper } from "../ui/numeralFormat"; +import { Generic_fromJSON, + Generic_toJSON, + Reviver } from "../../utils/JSONReviver"; + +interface IConstructorParams { + loc?: string; + size?: number; +} + +interface IParent { + getStorageMultiplier(): number; +} + +export class Warehouse { + // Initiatizes a Warehouse object from a JSON save state. + static fromJSON(value: any): Warehouse { + return Generic_fromJSON(Warehouse, value.data); + } + + // Text that describes how the space in this Warehouse is being used + // Used to create a tooltip in the UI + breakdown: string = ""; + + // Warehouse's level, which affects its maximum size + level: number = 0; + + // City that this Warehouse is in + loc: string; + + // Map of Materials held by this Warehouse + materials: IMap; + + // Maximum amount warehouse can hold + size: number; + + // Amount of space currently used by warehouse + sizeUsed: number = 0; + + // Whether Smart Supply is enabled for this Industry (the Industry that this Warehouse is for) + smartSupplyEnabled: boolean = false; + + // Stores the amount of product to be produced. Used for Smart Supply unlock. + // The production tracked by smart supply is always based on the previous cycle, + // so it will always trail the "true" production by 1 cycle + smartSupplyStore: number = 0; + + constructor(params: IConstructorParams = {}) { + this.loc = params.loc ? params.loc : ""; + this.size = params.size ? params.size : 0; + + this.materials = { + Water: new Material({name: "Water"}), + Energy: new Material({name: "Energy"}), + Food: new Material({name: "Food"}), + Plants: new Material({name: "Plants"}), + Metal: new Material({name: "Metal"}), + Hardware: new Material({name: "Hardware"}), + Chemicals: new Material({name: "Chemicals"}), + Drugs: new Material({name: "Drugs"}), + Robots: new Material({name: "Robots"}), + AICores: new Material({name: "AI Cores"}), + RealEstate: new Material({name: "Real Estate"}) + } + } + + // Re-calculate how much space is being used by this Warehouse + updateMaterialSizeUsed() { + this.sizeUsed = 0; + this.breakdown = ""; + for (const matName in this.materials) { + var mat = this.materials[matName]; + if (MaterialSizes.hasOwnProperty(matName)) { + this.sizeUsed += (mat.qty * MaterialSizes[matName]); + if (mat.qty > 0) { + this.breakdown += (matName + ": " + numeralWrapper.format(mat.qty * MaterialSizes[matName], "0,0") + "
"); + } + } + } + if (this.sizeUsed > this.size) { + console.warn("Warehouse size used greater than capacity, something went wrong"); + } + } + + updateSize(corporation: IParent, industry: IParent) { + this.size = (this.level * 100) + * corporation.getStorageMultiplier() + * industry.getStorageMultiplier(); + } + + // Serialize the current object to a JSON save state. + toJSON(): any { + return Generic_toJSON("Warehouse", this); + } +} + +Reviver.constructors.Warehouse = Warehouse; diff --git a/src/Corporation/data/BaseResearchTree.ts b/src/Corporation/data/BaseResearchTree.ts index bdd475d86..a4e535e1c 100644 --- a/src/Corporation/data/BaseResearchTree.ts +++ b/src/Corporation/data/BaseResearchTree.ts @@ -14,14 +14,14 @@ function makeNode(name: string): Node { return new Node({ text: research.name, cost: research.cost }); } - -export function getBaseResearchTreeCopy(): ResearchTree { - const baseResearchTree: ResearchTree = new ResearchTree(); - +// Creates the Nodes for the BaseResearchTree. +// Return the Root Node +function createBaseResearchTreeNodes(): Node { const rootNode: Node = makeNode("Hi-Tech R&D Laboratory"); const autoBrew: Node = makeNode("AutoBrew"); const autoParty: Node = makeNode("AutoPartyManager"); const autoDrugs: Node = makeNode("Automatic Drug Administration"); + const bulkPurchasing: Node = makeNode("Bulk Purchasing"); const cph4: Node = makeNode("CPH4 Injections"); const drones: Node = makeNode("Drones"); const dronesAssembly: Node = makeNode("Drones - Assembly"); @@ -47,13 +47,39 @@ export function getBaseResearchTreeCopy(): ResearchTree { rootNode.addChild(autoBrew); rootNode.addChild(autoParty); rootNode.addChild(autoDrugs); + rootNode.addChild(bulkPurchasing); rootNode.addChild(drones); rootNode.addChild(joywire); rootNode.addChild(marketta1); rootNode.addChild(overclock); rootNode.addChild(scAssemblers); - baseResearchTree.setRoot(rootNode); + return rootNode; +} + +export function getBaseResearchTreeCopy(): ResearchTree { + const baseResearchTree: ResearchTree = new ResearchTree(); + baseResearchTree.setRoot(createBaseResearchTreeNodes()); return baseResearchTree; } + +// Base Research Tree for Industry's that make products +export function getProductIndustryResearchTreeCopy(): ResearchTree { + const researchTree: ResearchTree = new ResearchTree(); + const root = createBaseResearchTreeNodes(); + + const upgradeFulcrum = makeNode("uPgrade: Fulcrum"); + const upgradeCapacity1 = makeNode("uPgrade: Capacity.I"); + const upgradeCapacity2 = makeNode("uPgrade: Capacity.II"); + const upgradeDashboard = makeNode("uPgrade: Dashboard"); + + upgradeCapacity1.addChild(upgradeCapacity2); + upgradeFulcrum.addChild(upgradeCapacity1); + upgradeFulcrum.addChild(upgradeDashboard); + root.addChild(upgradeFulcrum); + + researchTree.setRoot(root); + + return researchTree; +} diff --git a/src/Corporation/data/ResearchMetadata.ts b/src/Corporation/data/ResearchMetadata.ts index 557766858..ed61a0a97 100644 --- a/src/Corporation/data/ResearchMetadata.ts +++ b/src/Corporation/data/ResearchMetadata.ts @@ -24,6 +24,12 @@ export const researchMetadata: IConstructorParams[] = [ desc: "Research how to automatically administer performance-enhacing drugs to all of " + "your employees. This unlocks Drug-related Research.", }, + { + name: "Bulk Purchasing", + cost: 5e3, + desc: "Research the art of buying materials in bulk. This allows you to purchase " + + "any amount of a material instantly.", + }, { name: "CPH4 Injections", cost: 25e3, @@ -64,7 +70,7 @@ export const researchMetadata: IConstructorParams[] = [ }, { name: "Hi-Tech R&D Laboratory", - cost: 10e3, + cost: 5e3, desc: "Construct a cutting edge facility dedicated to advanced research and " + "and development. This allows you to spend Scientific Research " + "on powerful upgrades. It also globally increases Scientific Research " + @@ -118,7 +124,38 @@ export const researchMetadata: IConstructorParams[] = [ "control confidence and enthusiasm. This research increases the max " + "morale of all employees by 10.", }, - - - + { + name: "sudo.Assist", + cost: 15e3, + desc: "Develop a virtual assistant AI to handle and manage administrative " + + "issues for your corporation.", + }, + { + name: "uPgrade: Capacity.I", + cost: 20e3, + desc: "Expand the industry's capacity for designing and manufacturing its " + + "various products. This increases the industry's maximum number of products " + + "by 1 (from 3 to 4).", + }, + { + name: "uPgrade: Capacity.II", + cost: 30e3, + desc: "Expand the industry's capacity for designing and manufacturing its " + + "various products. This increases the industry's maximum number of products " + + "by 1 (from 4 to 5).", + }, + { + name: "uPgrade: Dashboard", + cost: 5e3, + desc: "Improve the software used to manage the industry's production line " + + "for its various products. This allows you to manage the production and " + + "sale of a product before it's finished being designed.", + }, + { + name: "uPgrade: Fulcrum", + cost: 10e3, + desc: "Streamline the manufacturing of this industry's various products. " + + "This research increases the production of your products by 5%", + productProductionMult: 1.05, + }, ]; diff --git a/src/Corporation/ui/BaseReactComponent.js b/src/Corporation/ui/BaseReactComponent.js new file mode 100644 index 000000000..a6b5d2f9d --- /dev/null +++ b/src/Corporation/ui/BaseReactComponent.js @@ -0,0 +1,22 @@ +// 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; + } + + render() {} +} diff --git a/src/Corporation/ui/CityTabs.jsx b/src/Corporation/ui/CityTabs.jsx new file mode 100644 index 000000000..fd99e8323 --- /dev/null +++ b/src/Corporation/ui/CityTabs.jsx @@ -0,0 +1,56 @@ +// 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`) + } + + super(props); + } + + renderTab(props) { + let className = "cmpy-mgmt-city-tab"; + if (props.current) { + className += " current"; + } + + return ( + + ) + } + + render() { + 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()); + tabs.push(this.renderTab({ + current: false, + key: "Expand into new City", + onClick: newCityOnClick, + })); + + return tabs; + } +} diff --git a/src/Corporation/ui/CorporationUIEventHandler.js b/src/Corporation/ui/CorporationUIEventHandler.js new file mode 100644 index 000000000..be7ee277d --- /dev/null +++ b/src/Corporation/ui/CorporationUIEventHandler.js @@ -0,0 +1,1478 @@ +// Creates a class for handling UI events, such as clicks and keyboard events +import { CorporationRouting } from "./Routing"; +import { Corporation, + Industry, + OfficeSpace, + Warehouse, + DividendMaxPercentage, + IssueNewSharesCooldown, + OfficeInitialCost, + OfficeInitialSize, + SellSharesCooldown, + WarehouseInitialCost, + WarehouseInitialSize } from "../Corporation"; + +import { Industries, + IndustryStartingCosts, + IndustryDescriptions, + IndustryResearchTrees } from "../IndustryData"; + +import { Player } from "../../Player"; + +import { numeralWrapper } from "../../ui/numeralFormat"; + +import { dialogBoxCreate } from "../../../utils/DialogBox"; + +import { KEY } from "../../../utils/helpers/keyCodes"; + +import { clearSelector } from "../../../utils/uiHelpers/clearSelector"; +import { createElement } from "../../../utils/uiHelpers/createElement"; +import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement"; +import { createPopup } from "../../../utils/uiHelpers/createPopup"; +import { createPopupCloseButton } from "../../../utils/uiHelpers/createPopupCloseButton"; +import { getSelectText, + getSelectValue } from "../../../utils/uiHelpers/getSelectData"; +import { removeElementById } from "../../../utils/uiHelpers/removeElementById"; + +export class CorporationEventHandler { + constructor(corp, routing) { + if (!(corp instanceof Corporation)) { + throw new Error(`CorporationEventHandler constructed without proper Corporation instance`); + } + if (!(routing instanceof CorporationRouting)) { + throw new Error(`CorporationEventHandler constructed without proper CorporationRouting instance`); + } + + this.corp = corp; + this.routing = routing; + } + + // Create a popup that lets the player bribe factions + // This is created when the player clicks the "Bribe Factions" button in the overview panel + createBribeFactionsPopup() { + const popupId = "cmpy-mgmt-bribe-factions-popup"; + const txt = createElement("p", { + innerText:"You can use Corporation funds or stock shares to bribe Faction Leaders in exchange for faction reputation" + }); + const factionSelector = createElement("select", { margin:"3px" }); + for (let i = 0; i < Player.factions.length; ++i) { + const facName = Player.factions[i]; + factionSelector.add(createElement("option", { + text: facName, value: facName + })); + } + var repGainText = createElement("p"); + var stockSharesInput; + var moneyInput = createElement("input", { + type:"number", placeholder:"Corporation funds", margin:"5px", + inputListener:()=>{ + var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); + var stockPrice = this.corp.sharePrice; + var stockShares = stockSharesInput.value == null || stockSharesInput.value == "" ? 0 : Math.round(parseFloat(stockSharesInput.value)); + if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { + repGainText.innerText = "ERROR: Invalid value(s) entered"; + } else if (this.corp.funds.lt(money)) { + repGainText.innerText = "ERROR: You do not have this much money to bribe with"; + } else if (this.corp.stockShares > this.corp.numShares) { + repGainText.innerText = "ERROR: You do not have this many shares to bribe with"; + } else { + + var totalAmount = Number(money) + (stockShares * stockPrice); + var repGain = totalAmount / BribeToRepRatio; + repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") + + " reputation with " + + factionSelector.options[factionSelector.selectedIndex].value + + " with this bribe"; + } + } + }); + stockSharesInput = createElement("input", { + type:"number", placeholder:"Stock Shares", margin: "5px", + inputListener:()=>{ + var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); + var stockPrice = this.corp.sharePrice; + var stockShares = stockSharesInput.value == null || stockSharesInput.value == "" ? 0 : Math.round(stockSharesInput.value); + if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { + repGainText.innerText = "ERROR: Invalid value(s) entered"; + } else if (this.corp.funds.lt(money)) { + repGainText.innerText = "ERROR: You do not have this much money to bribe with"; + } else if (this.corp.stockShares > this.corp.numShares) { + repGainText.innerText = "ERROR: You do not have this many shares to bribe with"; + } else { + var totalAmount = money + (stockShares * stockPrice); + var repGain = totalAmount / BribeToRepRatio; + console.log("repGain: " + repGain); + repGainText.innerText = "You will gain " + numeralWrapper.formatNumber(repGain, "0,0") + + " reputation with " + + factionSelector.options[factionSelector.selectedIndex].value + + " with this bribe"; + } + } + }); + var confirmButton = createElement("a", { + class:"a-link-button", innerText:"Bribe", display:"inline-block", + clickListener:()=>{ + var money = moneyInput.value == null || moneyInput.value == "" ? 0 : parseFloat(moneyInput.value); + var stockPrice = this.corp.sharePrice; + var stockShares = stockSharesInput.value == null || stockSharesInput.value == ""? 0 : Math.round(parseFloat(stockSharesInput.value)); + var fac = Factions[factionSelector.options[factionSelector.selectedIndex].value]; + if (fac == null) { + dialogBoxCreate("ERROR: You must select a faction to bribe"); + return false; + } + if (isNaN(money) || isNaN(stockShares) || money < 0 || stockShares < 0) { + dialogBoxCreate("ERROR: Invalid value(s) entered"); + } else if (this.corp.funds.lt(money)) { + dialogBoxCreate("ERROR: You do not have this much money to bribe with"); + } else if (stockShares > this.corp.numShares) { + dialogBoxCreate("ERROR: You do not have this many shares to bribe with"); + } else { + var totalAmount = money + (stockShares * stockPrice); + var repGain = totalAmount / BribeToRepRatio; + dialogBoxCreate("You gained " + formatNumber(repGain, 0) + + " reputation with " + fac.name + " by bribing them."); + fac.playerReputation += repGain; + this.corp.funds = this.corp.funds.minus(money); + this.corp.numShares -= stockShares; + removeElementById(popupId); + return false; + } + } + }); + const cancelButton = createPopupCloseButton(popupId, { + display: "inline-block", + innerText: "Cancel", + }) + + createPopup(popupId, [txt, factionSelector, repGainText, + moneyInput, stockSharesInput, confirmButton, cancelButton]); + } + + // Create a popup that lets the player buyback shares + // This is created when the player clicks the "Buyback Shares" button in the overview panel + createBuybackSharesPopup() { + const popupId = "cmpy-mgmt-buyback-shares-popup"; + const currentStockPrice = this.corp.sharePrice; + const buybackPrice = currentStockPrice * 1.1; + const txt = createElement("p", { + innerHTML: "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.

" + + "To purchase these shares, you must use your own money (NOT your Corporation's funds).

" + + "The current buyback price of your company's stock is " + + numeralWrapper.format(buybackPrice, "$0.000a") + + ". Your company currently has " + numeralWrapper.formatBigNumber(this.corp.issuedShares) + " outstanding stock shares", + }); + var costIndicator = createElement("p", {}); + var input = createElement("input", { + type:"number", placeholder:"Shares to buyback", margin:"5px", + inputListener: ()=> { + var numShares = Math.round(input.value); + //TODO add conditional for if player doesn't have enough money + if (isNaN(numShares) || numShares <= 0) { + costIndicator.innerText = "ERROR: Invalid value entered for number of shares to buyback" + } else if (numShares > this.corp.issuedShares) { + costIndicator.innerText = "There are not this many shares available to buy back. " + + "There are only " + numeralWrapper.formatBigNumber(this.corp.issuedShares) + + " outstanding shares."; + } else { + costIndicator.innerText = "Purchase " + numShares + " shares for a total of " + + numeralWrapper.format(numShares * buybackPrice, '$0.000a'); + } + } + }); + var confirmBtn = createElement("a", { + class:"a-link-button", innerText:"Buy shares", display:"inline-block", + clickListener: () => { + var shares = Math.round(input.value); + const tempStockPrice = this.corp.sharePrice; + const buybackPrice = tempStockPrice * 1.1; + if (isNaN(shares) || shares <= 0) { + dialogBoxCreate("ERROR: Invalid value for number of shares"); + } else if (shares > this.corp.issuedShares) { + dialogBoxCreate("ERROR: There are not this many oustanding shares to buy back"); + } else if (shares * buybackPrice > Player.money) { + dialogBoxCreate("ERROR: You do not have enough money to purchase this many shares (you need " + + numeralWrapper.format(shares * buybackPrice, "$0.000a") + ")"); + } else { + this.corp.numShares += shares; + if (isNaN(this.corp.issuedShares)) { + console.warn("Corporation issuedShares is NaN: " + this.corp.issuedShares); + console.warn("Converting to number now"); + const res = parseInt(this.corp.issuedShares); + if (isNaN(res)) { + this.corp.issuedShares = 0; + } else { + this.corp.issuedShares = res; + } + } + this.corp.issuedShares -= shares; + Player.loseMoney(shares * buybackPrice); + removeElementById(popupId); + this.rerender(); + } + return false; + + } + }); + var cancelBtn = createPopupCloseButton(popupId, { + class: "std-button", + display: "inline-block", + innerText: "Cancel", + }); + + createPopup(popupId, [txt, costIndicator, input, confirmBtn, cancelBtn]); + input.focus(); + } + + // Create a popup that lets the player discontinue a product + createDiscontinueProductPopup(product) { + const popupId = "cmpy-mgmt-discontinue-product-popup"; + const txt = createElement("p", { + innerText:"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", + }); + const confirmBtn = createElement("a", { + class:"a-link-button",innerText:"Discontinue", + clickListener:()=>{ + industry.discontinueProduct(product, parentRefs); + removeElementById(popupId); + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + + createPopup(popupId, [txt, confirmBtn, cancelBtn]); + } + + // Create a popup that lets the player manage exports + createExportMaterialPopup(mat) { + const corp = this.corp; + + const popupId = "cmpy-mgmt-export-popup"; + const exportTxt = createElement("p", { + innerText:"Select the industry and city to export this material to, as well as " + + "how much of this material to export per second. You can set the export " + + "amount to 'MAX' to export all of the materials in this warehouse." + }); + + //Select industry and city to export to + const citySelector = createElement("select", {class: "dropdown"}); + const industrySelector = createElement("select", { + class: "dropdown", + changeListener: () => { + const industryName = getSelectValue(industrySelector); + for (let i = 0; i < corp.divisions.length; ++i) { + if (corp.divisions[i].name == industryName) { + clearSelector(citySelector); + for (const cityName in corp.divisions[i].warehouses) { + if (corp.divisions[i].warehouses[cityName] instanceof Warehouse) { + citySelector.add(createElement("option", { + value:cityName, text:cityName, + })); + } + } + return; + } + } + } + }); + + for (let i = 0; i < corp.divisions.length; ++i) { + industrySelector.add(createOptionElement(corp.divisions[i].name)); + } + + // Force change listener to initialize citySelector + industrySelector.dispatchEvent(new Event("change")); + + //Select amount to export + const exportAmount = createElement("input", { + class: "text-input", + placeholder:"Export amount / s" + }); + + const exportBtn = createElement("a", { + class: "std-button", display:"inline-block", innerText:"Export", + clickListener: () => { + const industryName = getSelectText(industrySelector); + const cityName = citySelector.options[citySelector.selectedIndex].text; + const amt = exportAmount.value; + + // Sanitize amt + let sanitizedAmt = amt.replace(/\s+/g, ''); + sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, ''); + let temp = sanitizedAmt.replace(/MAX/g, 1); + try { + temp = eval(temp); + } catch(e) { + dialogBoxCreate("Invalid expression entered for export amount: " + e); + return false; + } + + if (temp == null || isNaN(temp)) { + dialogBoxCreate("Invalid amount entered for export"); + return; + } + var exportObj = {ind:industryName, city:cityName, amt:sanitizedAmt}; + mat.exp.push(exportObj); + removeElementById(popupId); + return false; + } + }); + + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + + const currExportsText = createElement("p", { + innerText:"Below is a list of all current exports of this material from this warehouse. " + + "Clicking on one of the exports below will REMOVE that export." + }); + const currExports = []; + for (var i = 0; i < mat.exp.length; ++i) { + (function(i, mat, currExports){ + currExports.push(createElement("div", { + class:"cmpy-mgmt-existing-export", + innerHTML: "Industry: " + mat.exp[i].ind + "
" + + "City: " + mat.exp[i].city + "
" + + "Amount/s: " + mat.exp[i].amt, + clickListener:()=>{ + mat.exp.splice(i, 1); //Remove export object + removeElementById(popupId); + createExportPopup(); + } + })); + })(i, mat, currExports); + } + createPopup(popupId, [exportTxt, industrySelector, citySelector, exportAmount, + exportBtn, cancelBtn, currExportsText].concat(currExports)); + } + + // Create a popup that lets the player issue & manage dividends + // This is created when the player clicks the "Issue Dividends" button in the overview panel + createIssueDividendsPopup() { + const popupId = "cmpy-mgmt-issue-dividends-popup"; + const descText = "Dividends are a distribution of a portion of the corporation's " + + "profits to the shareholders. This includes yourself, as well.

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

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

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

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

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

` + + `Stock price decreased to ${numeralWrapper.formatMoney(this.corp.sharePrice)}`); + + this.rerender(); + return false; + } + }); + + const cancelBtn = createPopupCloseButton(popupId, { + class: "std-button", + display: "inline-block", + innerText: "Cancel", + }); + + createPopup(popupId, [descText, dynamicText, newSharesInput, issueBtn, cancelBtn]); + newSharesInput.focus(); + } + + // Create a popup that lets the player limit the production of a product + createLimitProductProdutionPopup(product) { + const popupId = "cmpy-mgmt-limit-product-production-popup"; + const txt = createElement("p", { + innerText:"Enter a limit to the amount of this product you would " + + "like to product per second. Leave the box empty to set no limit." + }); + let confirmBtn; + const input = createElement("input", { + type:"number", placeholder:"Limit", + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) { confirmBtn.click(); } + } + }); + confirmBtn = createElement("a", { + class: "std-button", + display:"inline-block", + innerText:"Limit production", + margin:'6px', + clickListener: () => { + if (input.value === "") { + product.prdman[city][0] = false; + removeElementById(popupId); + return false; + } + var qty = parseFloat(input.value); + if (isNaN(qty)) { + dialogBoxCreate("Invalid value entered"); + return false; + } + if (qty < 0) { + product.prdman[city][0] = false; + } else { + product.prdman[city][0] = true; + product.prdman[city][1] = qty; + } + removeElementById(popupId); + this.rerender(); + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + cancelBtn.style.margin = "6px"; + + createPopup(popupId, [txt, input, confirmBtn, cancelBtn]); + input.focus(); + } + + // Create a popup that lets the player create a product for their current industry + createMakeProductPopup(popupText, division) { + if (division.hasMaximumNumberProducts()) { return; } + + const popupId = "cmpy-mgmt-create-product-popup"; + const txt = createElement("p", { + innerHTML: popupText, + }); + const designCity = createElement("select"); + for (const cityName in division.offices) { + if (division.offices[cityName] instanceof OfficeSpace) { + designCity.add(createElement("option", { + value: cityName, + text: cityName + })); + } + } + let productNamePlaceholder = "Product Name"; + if (division.type === Industries.Food) { + productNamePlaceholder = "Restaurant Name"; + } else if (division.type === Industries.Healthcare) { + productNamePlaceholder = "Hospital Name"; + } else if (division.type === Industries.RealEstate) { + productNamePlaceholder = "Property Name"; + } + var productNameInput = createElement("input", { + placeholder: productNamePlaceholder, + }); + var lineBreak1 = createElement("br"); + var designInvestInput = createElement("input", { + type: "number", + placeholder: "Design investment" + }); + var marketingInvestInput = createElement("input", { + type: "number", + placeholder: "Marketing investment" + }); + const confirmBtn = createElement("a", { + class: "std-button", + innerText: "Develop Product", + clickListener: () => { + if (designInvestInput.value == null) { designInvestInput.value = 0; } + if (marketingInvestInput.value == null) { marketingInvestInput.value = 0; } + var designInvest = parseFloat(designInvestInput.value), + marketingInvest = parseFloat(marketingInvestInput.value); + if (productNameInput.value == null || productNameInput.value === "") { + dialogBoxCreate("You must specify a name for your product!"); + } else if (isNaN(designInvest)) { + dialogBoxCreate("Invalid value for design investment"); + } else if (isNaN(marketingInvest)) { + dialogBoxCreate("Invalid value for marketing investment"); + } else if (this.corp.funds.lt(designInvest + marketingInvest)) { + dialogBoxCreate("You don't have enough company funds to make this large of an investment"); + } else { + const product = new Product({ + name:productNameInput.value.replace(/[<>]/g, ''), //Sanitize for HTMl elements + createCity:designCity.options[designCity.selectedIndex].value, + designCost: designInvest, + advCost: marketingInvest, + }); + this.corp.funds = this.corp.funds.minus(designInvest + marketingInvest); + division.products[product.name] = product; + removeElementById(popupId); + } + this.rerender(); + //this.updateUIContent(); + //this.displayDivisionContent(division, city); + return false; + } + }) + const cancelBtn = createPopupCloseButton(popupid, { + class: "std-button", + innerText: "Cancel", + }); + + createPopup(popupId, [txt, designCity, productNameInput, lineBreak1, + designInvestInput, marketingInvestInput, confirmBtn, cancelBtn]); + productNameInput.focus(); + } + + // Create a popup that lets the player use the Market TA research + createMarketTaPopup(mat, industry) { + const corp = this.corp; + + const popupId = "cmpy-mgmt-marketta-popup"; + const markupLimit = mat.getMarkupLimit(); + const ta1 = createElement("p", { + innerHTML: "Market-TA.I
" + + "The maximum sale price you can mark this up to is " + + numeralWrapper.formatMoney(mat.bCost + markupLimit) + + ". This means that if you set the sale price higher than this, " + + "you will begin to experience a loss in number of sales", + }); + const closeBtn = createPopupCloseButton(popupId, { + class: "std-button", + display: "block", + }); + + if (industry.hasResearch("Market-TA.II")) { + let updateTa2Text; + const ta2Text = createElement("p"); + const ta2Input = createElement("input", { + marginTop: "4px", + onkeyup: (e) => { + e.preventDefault(); + updateTa2Text(); + }, + type: "number", + value: mat.bCost, + }); + + // Function that updates the text in ta2Text element + updateTa2Text = function() { + const sCost = parseFloat(ta2Input.value); + let markup = 1; + if (sCost > mat.bCost) { + //Penalty if difference between sCost and bCost is greater than markup limit + if ((sCost - mat.bCost) > markupLimit) { + markup = Math.pow(markupLimit / (sCost - mat.bCost), 2); + } + } else if (sCost < mat.bCost) { + if (sCost <= 0) { + markup = 1e12; //Sell everything, essentially discard + } else { + //Lower prices than market increases sales + markup = mat.bCost / sCost; + } + } + ta2Text.innerHTML = `
Market-TA.II
` + + `If you sell at ${numeralWrapper.formatMoney(sCost)}, ` + + `then you will sell ${formatNumber(markup, 5)}x as much compared ` + + `to if you sold at market price.`; + } + updateTa2Text(); + createPopup(popupId, [ta1, ta2Text, ta2Input, closeBtn]); + } else { + // Market-TA.I only + createPopup(popupId, [ta1, closeBtn]); + } + } + + // Create a popup that lets the player expand into a new city (for the current industry) + createNewCityPopup(division) { + const popupId = "cmpy-mgmt-expand-city-popup"; + const text = createElement("p", { + innerText: "Would you like to expand into a new city by opening an office? " + + "This would cost " + numeralWrapper.format(OfficeInitialCost, '$0.000a'), + }); + const citySelector = createElement("select", { class: "dropdown", margin:"5px" }); + for (const cityName in division.offices) { + if (division.offices.hasOwnProperty(cityName)) { + if (!(division.offices[cityName] instanceof OfficeSpace)) { + citySelector.add(createElement("option", { + text: cityName, + value: cityName + })); + } + } + } + + const confirmBtn = createElement("a", { + class:"std-button", + display:"inline-block", + innerText: "Confirm", + margin:"3px", + clickListener: () => { + let city = citySelector.options[citySelector.selectedIndex].value; + if (this.corp.funds.lt(OfficeInitialCost)) { + dialogBoxCreate("You don't have enough company funds to open a new office!"); + } else { + this.corp.funds = this.corp.funds.minus(OfficeInitialCost); + dialogBoxCreate("Opened a new office in " + city + "!"); + division.offices[city] = new OfficeSpace({ + loc: city, + size: OfficeInitialSize, + }); + this.corp.displayDivisionContent(division, city); + } + removeElementById(popupId); + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { + class: "std-button", + innerText: "Cancel", + }); + + createPopup(popupId, [text, citySelector, confirmBtn, cancelBtn]); + } + + // Create a popup that lets the player create a new industry. + // This is created when the player clicks the "Expand into new Industry" header tab + createNewIndustryPopup() { + const popupId = "cmpy-mgmt-expand-industry-popup"; + if (document.getElementById(popupId) != null) { return; } + + var txt = createElement("p", { + innerHTML: "Create a new division to expand into a new industry:", + }); + var selector = createElement("select", { + class:"dropdown" + }); + var industryDescription = createElement("p", {}); + var yesBtn; + var nameInput = createElement("input", { + type:"text", + id:"cmpy-mgmt-expand-industry-name-input", + class: "text-input", + display:"block", + maxLength: 30, + pattern:"[a-zA-Z0-9-_]", + onkeyup:(e)=>{ + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {yesBtn.click();} + } + }); + var nameLabel = createElement("label", { + for:"cmpy-mgmt-expand-industry-name-input", + innerText:"Division name: " + }); + yesBtn = createElement("span", { + class:"popup-box-button", + innerText:"Create Division", + clickListener: ()=>{ + const ind = selector.options[selector.selectedIndex].value; + const newDivisionName = nameInput.value; + + for (let i = 0; i < this.corp.divisions.length; ++i) { + if (this.corp.divisions[i].name === newDivisionName) { + dialogBoxCreate("This name is already in use!"); + return false; + } + } + if (this.corp.funds.lt(IndustryStartingCosts[ind])) { + dialogBoxCreate("Not enough money to create a new division in this industry"); + } else if (newDivisionName === "") { + dialogBoxCreate("New division must have a name!"); + } else { + this.corp.funds = this.corp.funds.minus(IndustryStartingCosts[ind]); + var newInd = new Industry({ + name:newDivisionName, + type:ind, + }); + this.corp.divisions.push(newInd); + // this.corp.updateUIHeaderTabs(); + // this.corp.selectHeaderTab(headerTabs[headerTabs.length-2]); + removeElementById("cmpy-mgmt-expand-industry-popup"); + this.rerender(); + // this.corp.displayDivisionContent(newInd, Locations.Sector12); + } + return false; + } + }); + + const noBtn = createPopupCloseButton(popupId, { + display: "inline-block", + innerText: "Cancel", + }); + + //Make an object to keep track of what industries you're already in + const ownedIndustries = {}; + for (let i = 0; i < this.corp.divisions.length; ++i) { + ownedIndustries[this.corp.divisions[i].type] = true; + } + + //Add industry types to selector + //Have Agriculture be first as recommended option + if (!ownedIndustries["Agriculture"]) { + selector.add(createElement("option", { + text:Industries["Agriculture"], value:"Agriculture" + })); + } + + for (var key in Industries) { + if (key !== "Agriculture" && Industries.hasOwnProperty(key) && !ownedIndustries[key]) { + var ind = Industries[key]; + selector.add(createElement("option", { + text: ind,value:key, + })); + } + } + + //Initial Industry Description + var ind = selector.options[selector.selectedIndex].value; + industryDescription.innerHTML = (IndustryDescriptions[ind] + "

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

"; + }); + + //Add to DOM + const elems = []; + elems.push(txt); + elems.push(selector); + elems.push(industryDescription); + elems.push(nameLabel); + elems.push(nameInput); + elems.push(noBtn); + elems.push(yesBtn); + + createPopup(popupId, elems); + nameInput.focus(); + + return false; + } + + // Create a popup that lets the player purchase a Material + createPurchaseMaterialPopup(mat, industry) { + const corp = this.corp; + + const purchasePopupId = "cmpy-mgmt-material-purchase-popup"; + const txt = createElement("p", { + innerHTML: "Enter the amount of " + mat.name + " you would like " + + "to purchase per second. This material's cost changes constantly" + }); + let confirmBtn; + let input = createElement("input", { + margin: "5px", + placeholder: "Purchase amount", + type: "number", + value: mat.buy ? mat.buy : null, + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + confirmBtn = createElement("button", { + innerText: "Confirm", class: "std-button", + clickListener: () => { + if (isNaN(input.value)) { + dialogBoxCreate("Invalid amount"); + } else { + mat.buy = parseFloat(input.value); + if (isNaN(mat.buy)) {mat.buy = 0;} + removeElementById(purchasePopupId); + return false; + } + } + }); + const clearButton = createElement("button", { + innerText: "Clear Purchase", class: "std-button", + clickListener: () => { + mat.buy = 0; + removeElementById(purchasePopupId); + return false; + } + }); + const cancelBtn = createPopupCloseButton(purchasePopupId, { + class: "std-button", + innerText: "Cancel", + }); + + const elems = [txt, input, confirmBtn, clearButton, cancelBtn]; + + if (industry.hasResearch("Bulk Purchasing")) { + const bulkPurchaseInfo = createElement("p", { + innerText: "Enter the amount of " + mat.name + " you would like " + + "to bulk purchase. This purchases the specified amount instantly " + + "(all at once).", + }); + + let bulkPurchaseCostTxt = createElement("p"); + function updateBulkPurchaseText(amount) { + const cost = parseFloat(amount) * mat.bCost; + if (isNaN(cost)) { + dialogBoxCreate(`Bulk Purchase Cost calculated to be NaN. This is either due to ` + + `invalid input, or it is a bug (in which case you should report to dev)`); + return; + } + + bulkPurchaseCostTxt.innerText = `Purchasing ${numeralWrapper.format(amt, "0,0.00")} of ` + + `${mat.name} will cost ${numeralWrapper.formatMoney(cost)}`; + } + + let bulkPurchaseConfirmBtn; + const bulkPurchaseInput = createElement("input", { + margin: "5px", + placeholder: "Bulk Purchase amount", + type: "number", + onkeyup: (e) => { + e.preventDefault(); + bulkPurchaseUpdateCostTxt(); + if (e.keyCode === KEY.ENTER) {bulkPurchaseConfirmBtn.click();} + } + }); + + bulkPurchaseConfirmBtn = createElement("button", { + class: "std-button", + innerText: "Confirm Bulk Purchase", + clickListener: () => { + const amount = parseFloat(input.value); + if (isNaN(amount)) { + dialogBoxCreate("Invalid input amount"); + } else { + const cost = amount * mat.bCost; + if (corp.funds.gt(cost)) { + corp.funds = corp.funds.minus(cost); + mat.qty += amount; + } else { + dialogBoxCreate(`You cannot afford this purchase.`); + return false; + } + + removeElementById(purchasePopupId); + return false; + } + } + }) + + elems.push(bulkPurchaseInfo); + elems.push(bulkPurchaseCostTxt); + elems.push(bulkPurchaseInput); + } + + createPopup(purchasePopupId, elems); + input.focus(); + } + + // Create a popup that let the player manage sales of a material + createSellMaterialPopup(mat) { + const sellPopupId = "cmpy-mgmt-material-sell-popup"; + const txt = createElement("p", { + innerHTML: "Enter the maximum amount of " + mat.name + " you would like " + + "to sell per second, as well as the price at which you would " + + "like to sell at.

" + + "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

" + + "Setting the sell amount to 'MAX' will result in you always selling the " + + "maximum possible amount of the material.

" + + "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.

" + + "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.", + }); + const br = createElement("br"); + let confirmBtn; + const inputQty = createElement("input", { + type: "text", marginTop: "4px", + value: mat.sllman[1] ? mat.sllman[1] : null, + placeholder: "Sell amount", + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + const inputPx = createElement("input", { + type: "text", marginTop: "4px", + value: mat.sCost ? mat.sCost : null, placeholder: "Sell price", + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + confirmBtn = createElement("button", { + class: "std-button", + innerText: "Confirm", + clickListener: () => { + //Parse price + let cost = inputPx.value.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) { + dialogBoxCreate("Invalid value or expression for sell price field: " + e); + return false; + } + + if (temp == null || isNaN(temp)) { + dialogBoxCreate("Invalid value or expression for sell price field"); + return false; + } + + if (cost.includes("MP")) { + mat.sCost = cost; //Dynamically evaluated + } else { + mat.sCost = temp; + } + + //Parse quantity + if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) { + let qty = inputQty.value.replace(/\s+/g, ''); + qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); + let tempQty = qty.replace(/MAX/g, 1); + tempQty = tempQty.replace(/PROD/g, 1); + try { + tempQty = eval(tempQty); + } catch(e) { + dialogBoxCreate("Invalid value or expression for sell price field: " + e); + return false; + } + + if (tempQty == null || isNaN(tempQty)) { + dialogBoxCreate("Invalid value or expression for sell price field"); + return false; + } + + mat.sllman[0] = true; + mat.sllman[1] = qty; //Use sanitized input + } else if (isNaN(inputQty.value)) { + dialogBoxCreate("Invalid value for sell quantity field! Must be numeric or 'MAX'"); + return false; + } else { + var qty = parseFloat(inputQty.value); + if (isNaN(qty)) {qty = 0;} + if (qty === 0) { + mat.sllman[0] = false; + mat.sllman[1] = 0; + } else { + mat.sllman[0] = true; + mat.sllman[1] = qty; + } + } + + removeElementById(sellPopupId); + this.rerender(); + return false; + } + }); + const cancelBtn = createPopupCloseButton(sellPopupId, { + class: "std-button", + innerText: "Cancel", + }); + + createPopup(sellPopupId, [txt, br, inputQty, inputPx, confirmBtn, cancelBtn]); + inputQty.focus(); + } + + // Create a popup that lets the player manage sales of the product + createSellProductPopup(product) { + const popupId = "cmpy-mgmt-sell-product-popup"; + const txt = createElement("p", { + innerHTML:"Enter the maximum amount of " + product.name + " you would like " + + "to sell per second, as well as the price at which you would like to " + + "sell it at.

" + + "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.

" + + "Setting the sell amount to 'MAX' will result in you always selling the " + + "maximum possible amount of the material.

" + + "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.

" + + "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.", + }); + let confirmBtn; + const inputQty = createElement("input", { + placeholder: "Sell amount", + type: "text", + value:product.sllman[city][1] ? product.sllman[city][1] : null, + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + const inputPx = createElement("input", { + placeholder: "Sell price", + type: "text", + value: product.sCost ? product.sCost : null, + onkeyup: (e) => { + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + confirmBtn = createElement("a", { + class: "std-button", + innerText: "Confirm", + clickListener: () => { + //Parse price + if (inputPx.value.includes("MP")) { + //Dynamically evaluated quantity. First test to make sure its valid + //Sanitize input, then replace dynamic variables with arbitrary numbers + var price = inputPx.value.replace(/\s+/g, ''); + price = price.replace(/[^-()\d/*+.MP]/g, ''); + var temp = price.replace(/MP/g, 1); + try { + temp = eval(temp); + } catch(e) { + dialogBoxCreate("Invalid value or expression for sell quantity field: " + e); + return false; + } + if (temp == null || isNaN(temp)) { + dialogBoxCreate("Invalid value or expression for sell quantity field."); + return false; + } + product.sCost = price; //Use sanitized price + } else { + var cost = parseFloat(inputPx.value); + if (isNaN(cost)) { + dialogBoxCreate("Invalid value for sell price field"); + return false; + } + product.sCost = cost; + } + + //Parse quantity + if (inputQty.value.includes("MAX") || inputQty.value.includes("PROD")) { + //Dynamically evaluated quantity. First test to make sure its valid + var qty = inputQty.value.replace(/\s+/g, ''); + qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, ''); + var temp = qty.replace(/MAX/g, 1); + temp = temp.replace(/PROD/g, 1); + try { + temp = eval(temp); + } catch(e) { + dialogBoxCreate("Invalid value or expression for sell price field: " + e); + return false; + } + + if (temp == null || isNaN(temp)) { + dialogBoxCreate("Invalid value or expression for sell price field"); + return false; + } + product.sllman[city][0] = true; + product.sllman[city][1] = qty; //Use sanitized input + } else if (isNaN(inputQty.value)) { + dialogBoxCreate("Invalid value for sell quantity field! Must be numeric"); + return false; + } else { + var qty = parseFloat(inputQty.value); + if (isNaN(qty)) {qty = 0;} + if (qty === 0) { + product.sllman[city][0] = false; + } else { + product.sllman[city][0] = true; + product.sllman[city][1] = qty; + } + } + + removeElementById(popupId); + this.rerender(); + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + + createPopup(popupId, [txt, inputQty, inputPx, confirmBtn, cancelBtn]); + inputQty.focus(); + } + + // 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 + createSellSharesPopup() { + const popupId = "cmpy-mgmt-sell-shares-popup"; + const currentStockPrice = this.corp.sharePrice; + const txt = createElement("p", { + innerHTML: "Enter the number of shares you would like to sell. The money from " + + "selling your shares will go directly to you (NOT your Corporation).

" + + "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.

" + + "The current price of your " + + "company's stock is " + numeralWrapper.format(currentStockPrice, "$0.000a"), + }); + const profitIndicator = createElement("p"); + const input = createElement("input", { + type:"number", placeholder:"Shares to sell", margin:"5px", + inputListener: ()=> { + var numShares = Math.round(input.value); + if (isNaN(numShares) || numShares <= 0) { + profitIndicator.innerText = "ERROR: Invalid value entered for number of shares to sell" + } else if (numShares > this.corp.numShares) { + profitIndicator.innerText = "You don't have this many shares to sell!"; + } else { + const stockSaleResults = this.corp.calculateShareSale(numShares); + const profit = stockSaleResults[0]; + const newSharePrice = stockSaleResults[1]; + const newSharesUntilUpdate = stockSaleResults[2]; + profitIndicator.innerText = "Sell " + numShares + " shares for a total of " + + numeralWrapper.format(profit, '$0.000a'); + } + } + }); + const confirmBtn = createElement("a", { + class:"a-link-button", innerText:"Sell shares", display:"inline-block", + clickListener:()=>{ + var shares = Math.round(input.value); + if (isNaN(shares) || shares <= 0) { + dialogBoxCreate("ERROR: Invalid value for number of shares"); + } else if (shares > this.corp.numShares) { + dialogBoxCreate("ERROR: You don't have this many shares to sell"); + } else { + const stockSaleResults = this.corp.calculateShareSale(shares); + const profit = stockSaleResults[0]; + const newSharePrice = stockSaleResults[1]; + const newSharesUntilUpdate = stockSaleResults[2]; + + this.corp.numShares -= shares; + if (isNaN(this.corp.issuedShares)) { + console.log("ERROR: Corporation issuedShares is NaN: " + this.corp.issuedShares); + console.log("Converting to number now"); + var res = parseInt(this.corp.issuedShares); + if (isNaN(res)) { + this.corp.issuedShares = 0; + } else { + this.corp.issuedShares = res; + } + } + this.corp.issuedShares += shares; + this.corp.sharePrice = newSharePrice; + this.corp.shareSalesUntilPriceUpdate = newSharesUntilUpdate; + this.corp.shareSaleCooldown = SellSharesCooldown; + Player.gainMoney(profit); + Player.recordMoneySource(profit, "corporation"); + removeElementById(popupId); + dialogBoxCreate(`Sold ${numeralWrapper.formatMoney(shares, "0.000a")} shares for ` + + `${numeralWrapper.formatMoney(profit, "$0.000a")}. ` + + `The corporation's stock price fell to ${numeralWrapper.formatMoney(this.corp.sharePrice)} ` + + `as a result of dilution.`); + + this.rerender(); + return false; + } + + } + }); + const cancelBtn = createPopupCloseButton(popupId, { + class: "std-button", + display: "inline-block", + innerText: "Cancel", + }); + + createPopup(popupId, [txt, profitIndicator, input, confirmBtn, cancelBtn]); + input.focus(); + } + + // Creates a popup that lets the player throw an office party + createThrowOfficePartyPopup(office) { + const popupId = "cmpy-mgmt-throw-office-party-popup"; + const txt = createElement("p", { + innerText:"Enter the amount of money you would like to spend PER EMPLOYEE " + + "on this office party" + }); + const totalCostTxt = createElement("p", { + innerText:"Throwing this party will cost a total of $0" + }); + let confirmBtn; + const input = createElement("input", { + type: "number", margin: "5px", placeholder: "$ / employee", + inputListener: () => { + if (isNaN(input.value) || input.value < 0) { + totalCostTxt.innerText = "Invalid value entered!" + } else { + const totalCost = input.value * office.employees.length; + totalCostTxt.innerText = "Throwing this party will cost a total of " + numeralWrapper.format(totalCost, '$0.000a'); + } + }, + onkeyup:(e)=>{ + e.preventDefault(); + if (e.keyCode === KEY.ENTER) {confirmBtn.click();} + } + }); + confirmBtn = createElement("a", { + class: "std-button", + innerText: "Throw Party", + clickListener:()=>{ + if (isNaN(input.value) || input.value < 0) { + dialogBoxCreate("Invalid value entered"); + } else { + var totalCost = input.value * office.employees.length; + if (this.corp.funds.lt(totalCost)) { + dialogBoxCreate("You don't have enough company funds to throw this.corp party!"); + } else { + this.corp.funds = this.funds.minus(totalCost); + var mult; + for (let fooit = 0; fooit < office.employees.length; ++fooit) { + mult = office.employees[fooit].throwParty(input.value); + } + dialogBoxCreate("You threw a party for the office! The morale and happiness " + + "of each employee increased by " + formatNumber((mult-1) * 100, 2) + "%."); + removeElementById(popupId); + } + } + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + + createPopup(popupId, [txt, totalCostTxt, input, confirmBtn, cancelBtn]); + input.focus(); + } + + // Creates a popup that lets the player upgrade the current OfficeSpace's size + createUpgradeOfficeSizePopup(office) { + const popupId = "cmpy-mgmt-upgrade-office-size-popup"; + const initialPriceMult = Math.round(office.size / OfficeInitialSize); + const costMultiplier = 1.09; + const upgradeCost = 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 = OfficeInitialCost * mult; + + //Calculate max upgrade size and cost + let maxMult = (this.corp.funds.dividedBy(OfficeInitialCost)).toNumber(); + let maxNum = 1; + mult = Math.pow(costMultiplier, initialPriceMult); + while(maxNum < 50) { //Hard cap of 50x (extra 150 employees) + if (mult >= maxMult) {break;} + let multIncrease = Math.pow(costMultiplier, initialPriceMult + maxNum); + if (mult + multIncrease > maxMult) { + break; + } else { + mult += multIncrease; + } + ++maxNum; + } + const upgradeCostMax = OfficeInitialCost * mult; + + const text = createElement("p", { + innerText:"Increase the size of your office space to fit additional employees!" + }); + const text2 = createElement("p", { innerText: "Upgrade size: " }); + + const confirmBtn = createElement("a", { + class: this.corp.funds.lt(upgradeCost) ? "a-link-button-inactive" : "a-link-button", + display:"inline-block", margin:"4px", innerText:"by 3", + tooltip:numeralWrapper.format(upgradeCost, "$0.000a"), + clickListener:()=>{ + if (this.corp.funds.lt(upgradeCost)) { + dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); + } else { + office.size += OfficeInitialSize; + this.corp.funds = this.corp.funds.minus(upgradeCost); + dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); + this.rerender(); + } + removeElementById(popupId); + return false; + } + }); + const confirmBtn15 = createElement("a", { + class: this.corp.funds.lt(upgradeCost15) ? "a-link-button-inactive" : "a-link-button", + display:"inline-block", margin:"4px", innerText:"by 15", + tooltip:numeralWrapper.format(upgradeCost15, "$0.000a"), + clickListener:()=>{ + if (this.corp.funds.lt(upgradeCost15)) { + dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); + } else { + office.size += (OfficeInitialSize * 5); + this.corp.funds = this.corp.funds.minus(upgradeCost15); + dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); + this.rerender(); + } + removeElementById(popupId); + return false; + } + }); + const confirmBtnMax = createElement("a", { + class:this.corp.funds.lt(upgradeCostMax) ? "a-link-button-inactive" : "a-link-button", + display:"inline-block", margin:"4px", innerText:"by MAX (" + maxNum*OfficeInitialSize + ")", + tooltip:numeralWrapper.format(upgradeCostMax, "$0.000a"), + clickListener:()=>{ + if (this.corp.funds.lt(upgradeCostMax)) { + dialogBoxCreate("You don't have enough company funds to purchase this upgrade!"); + } else { + office.size += (OfficeInitialSize * maxNum); + this.corp.funds = this.corp.funds.minus(upgradeCostMax); + dialogBoxCreate("Office space increased! It can now hold " + office.size + " employees"); + this.rerender(); + } + removeElementById(popupId); + return false; + } + }); + const cancelBtn = createPopupCloseButton(popupId, { innerText: "Cancel" }); + cancelBtn.style.margin = "4px"; + + createPopup(popupId, [text, text2, confirmBtn, confirmBtn15, confirmBtnMax, cancelBtn]); + } + + // Purchases a new Warehouse + purchaseWarehouse(division, city) { + const corp = this.corp; + if (corp.funds.lt(WarehouseInitialCost)) { + dialogBoxCreate("You do not have enough funds to do this!"); + } else { + division.warehouses[city] = new Warehouse({ + loc: city, + size: WarehouseInitialSize, + }); + corp.funds = corp.funds.minus(WarehouseInitialCost); + this.rerender(); + } + } + + rerender() { + this.corp.rerender(); + } +} diff --git a/src/Corporation/ui/HeaderTabs.jsx b/src/Corporation/ui/HeaderTabs.jsx new file mode 100644 index 000000000..a0e6fd1da --- /dev/null +++ b/src/Corporation/ui/HeaderTabs.jsx @@ -0,0 +1,80 @@ +// 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"; + +import { overviewPage } from "./Routing"; + +function HeaderTab(props) { + let className = "cmpy-mgmt-header-tab"; + if (props.current) { + className += " current"; + } + + return ( + + ) +} + +export class HeaderTabs extends BaseReactComponent { + renderTab(props) { + return ( + + ) + } + + 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 ( +
+ { + 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" + }) + } +
+ ) + } +} diff --git a/src/Corporation/ui/Industry.jsx b/src/Corporation/ui/Industry.jsx new file mode 100644 index 000000000..c6787e3ed --- /dev/null +++ b/src/Corporation/ui/Industry.jsx @@ -0,0 +1,34 @@ +// 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 ( +
+
+ + +
+ +
+ +
+
+ ) + + } +} diff --git a/src/Corporation/ui/IndustryOffice.jsx b/src/Corporation/ui/IndustryOffice.jsx new file mode 100644 index 000000000..56707f8b5 --- /dev/null +++ b/src/Corporation/ui/IndustryOffice.jsx @@ -0,0 +1,557 @@ +// 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 { OfficeSpace } from "../Corporation"; +import { EmployeePositions } from "../EmployeePositions"; + +import { numeralWrapper } from "../../ui/numeralFormat"; + +import { getSelectText } from "../../../utils/uiHelpers/getSelectData"; + +export class IndustryOffice extends BaseReactComponent { + constructor(props) { + super(props); + + this.state = { + 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, + } + + this.updateEmployeeCount(); // This function validates division and office refs + } + + updateEmployeeCount() { + const division = this.routing().currentDivision; + if (division == null) { + throw new Error(`Routing does not hold reference to the current Industry`); + } + const office = division.offices[this.props.currentCity]; + if (!(office instanceof OfficeSpace)) { + throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`); + } + + // Calculate how many NEW emplyoees we need to account for + const currentNumEmployees = office.employees.length; + const newEmployees = currentNumEmployees - this.state.numEmployees; + + // Record the number of employees in each position, for NEW employees only + for (let i = this.state.numEmployees; i < office.employees.length; ++i) { + switch (office.employees[i].pos) { + case EmployeePositions.Operations: + ++this.state.numOperations; + break; + case EmployeePositions.Engineer: + ++this.state.numEngineers; + break; + case EmployeePositions.Business: + ++this.state.numBusiness; + break; + case EmployeePositions.Management: + ++this.state.numManagement; + break; + case EmployeePositions.RandD: + ++this.state.numResearch; + break; + case EmployeePositions.Unassigned: + ++this.state.numUnassigned; + break; + case EmployeePositions.Training: + ++this.state.numTraining; + break; + default: + console.error("Unrecognized employee position: " + office.employees[i].pos); + break; + } + } + + this.state.numEmployees = currentNumEmployees; + } + + // Renders the "Employee Management" section of the Office UI + renderEmployeeManagement() { + this.updateEmployeeCount(); + + if (this.state.employeeManualAssignMode) { + return this.renderManualEmployeeManagement(); + } else { + return this.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 + + const switchModeOnClick = () => { + this.state.employeeManualAssignMode = true; + this.corp().rerender(); + } + + // Calculate average morale, happiness, and energy. Also salary + // TODO is this efficient? + let totalMorale = 0, totalHappiness = 0, totalEnergy = 0, totalSalary = 0; + for (let i = 0; i < office.employees.length; ++i) { + totalMorale += office.employees[i].mor; + totalHappiness += office.employees[i].hap; + totalEnergy += office.employees[i].ene; + totalSalary += office.employees[i].sal; + } + + let avgMorale = 0, avgHappiness = 0, avgEnergy = 0; + if (office.employees.length > 0) { + avgMorale = totalMorale / office.employees.length; + avgHappiness = totalHappiness / office.employees.length; + avgEnergy = totalEnergy / office.employees.length; + } + + // Helper functions for (re-)assigning employees to different positions + const assignEmployee = (to) => { + if (this.state.numUnassigned >= 0) { + console.warn("Cannot assign employee. No unassigned employees available"); + return; + } + + switch (to) { + case EmployeePositions.Operations: + ++this.state.numOperations; + break; + case EmployeePositions.Engineer: + ++this.state.numEngineers; + break; + case EmployeePositions.Business: + ++this.state.numBusiness; + break; + case EmployeePositions.Management: + ++this.state.numManagement; + break; + case EmployeePositions.RandD: + ++this.state.numResearch; + break; + case EmployeePositions.Unassigned: + ++this.state.numUnassigned; + break; + case EmployeePositions.Training: + ++this.state.numTraining; + break; + default: + console.error("Unrecognized employee position: " + to); + break; + } + --this.state.numUnassigned; + + office.assignEmployeeToJob(to); + this.corp().rerender(); + } + + const unassignEmployee = (from) => { + function logWarning(pos) { + 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; + break; + case EmployeePositions.Engineer: + if (this.state.numEngineers <= 0) { return logWarning(EmployeePositions.Operations); } + --this.state.numEngineers; + break; + case EmployeePositions.Business: + if (this.state.numBusiness <= 0) { return logWarning(EmployeePositions.Operations); } + --this.state.numBusiness; + break; + case EmployeePositions.Management: + if (this.state.numManagement <= 0) { return logWarning(EmployeePositions.Operations); } + --this.state.numManagement; + break; + case EmployeePositions.RandD: + if (this.state.numResearch <= 0) { return logWarning(EmployeePositions.Operations); } + --this.state.numResearch; + 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; + break; + default: + console.error("Unrecognized employee position: " + from); + break; + } + ++this.state.numUnassigned; + + office.unassignEmployeeFromJob(from); + this.corp().rerender(); + } + + const positionHeaderStyle = { + fontSize: "15px", + margin: "5px 0px 5px 0px", + width: "50%", + } + const assignButtonClass = this.state.numUnassigned > 0 ? "std-button" : "a-link-button-inactive"; + + const operationAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.Operations); + this.corp().rerender(); + } + const operationUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.Operations); + this.corp().rerender(); + } + const operationUnassignButtonClass = this.state.numOperations > 0 ? "std-button" : "a-link-button-inactive"; + + const engineerAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.Engineer); + this.corp().rerender(); + } + const engineerUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.Engineer); + this.corp().rerender(); + } + const engineerUnassignButtonClass = this.state.numEngineers > 0 ? "std-button" : "a-link-button-inactive"; + + const businessAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.Business); + this.corp().rerender(); + } + const businessUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.Business); + this.corp().rerender(); + } + const businessUnassignButtonClass = this.state.numBusiness > 0 ? "std-button" : "a-link-button-inactive"; + + const managementAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.Management); + this.corp().rerender(); + } + const managementUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.Management); + this.corp().rerender(); + } + const managementUnassignButtonClass = this.state.numManagement > 0 ? "std-button" : "a-link-button-inactive"; + + const rndAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.RandD); + this.corp().rerender(); + } + const rndUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.RandD); + this.corp().rerender(); + } + const rndUnassignButtonClass = this.state.numResearch > 0 ? "std-button" : "a-link-button-inactive"; + + const trainingAssignButtonOnClick = () => { + assignEmployee(EmployeePositions.Training); + this.corp().rerender(); + } + const trainingUnassignButtonOnClick = () => { + unassignEmployee(EmployeePositions.Training); + this.corp().rerender(); + } + const trainingUnassignButtonClass = this.state.numTraining > 0 ? "std-button" : "a-link-button-inactive"; + + return ( +
+ + +

Unassigned Employees: {this.state.numUnassigned}

+
+ +

Avg Employee Morale: {numeralWrapper.format(avgMorale, "0.000")}

+

Avg Happiness Morale: {numeralWrapper.format(avgHappiness, "0.000")}

+

Avg Energy Morale: {numeralWrapper.format(avgEnergy, "0.000")}

+

Total Employee Salary: {numeralWrapper.formatMoney(totalSalary)}

+ { + vechain && +
+

+ Material Production: {numeralWrapper.format(division.getOfficeProductivity(office), "0.000")} + + The base amount of material this office can produce. Does not include + production multipliers from upgrades and materials. This value is based off + the productivity of your Operations, Engineering, and Management employees + +


+

+ Product Production: {numeralWrapper.format(division.getOfficeProductivity(office, {forProduct:true}), "0.000")} + + The base amount of any given Product this office can produce. Does not include + production multipliers from upgrades and materials. This value is based off + the productivity of your Operations, Engineering, and Management employees + +


+

+ Business Multiplier: x" ${numeralWrapper.format(division.getBusinessFactor(office), "0.000")} + + The effect this office's 'Business' employees has on boosting sales + +


+
+ } + +

+ {EmployeePositions.Operations} + + Manages supply chain operations. Improves production. + +

+ + +
+ +

+ {EmployeePositions.Engineer} + + Develops and maintains products and production systems. Improves production. + +

+ + +
+ +

+ {EmployeePositions.Business} + + Handles sales and finances. Improves sales. + +

+ + +
+ +

+ {EmployeePositions.Management} + + Leads and oversees employees and office operations. Improves production. + +

+ + +
+ +

+ {EmployeePositions.RandD} + + Research new innovative ways to improve the company. Generates Scientific Research + +

+ + +
+ +

+ {EmployeePositions.Training} + + Set employee to training, which will increase some of their stats. Employees in training do not affect any company operations. + +

+ + +
+ ) + } + + renderManualEmployeeManagement() { + const corp = this.corp(); + const division = this.routing().currentDivision; // Validated in constructor + const office = division.offices[this.props.currentCity]; // Validated in constructor + + const switchModeOnClick = () => { + this.state.employeeManualAssignMode = false; + this.corp().rerender(); + } + + const employeeInfoDivStyle = { + color: "white", + margin: "4px", + padding: "4px", + } + + // Employee Selector + const employees = []; + for (let i = 0; i < office.employees.length; ++i) { + employees.push() + } + + const employeeSelectorOnChange = (e) => { + 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]; + } + } + } + + const employeeSelectorStyle = { + backgroundColor: "black", + color: "white", + margin: "4px", + padding: "4px", + } + + // Employee Positions Selector + const employeePositions = []; + const positionNames = Object.values(EmployeePositions); + for (let i = 0; i < positionNames.length; ++i) { + employeePositions.push(); + } + + const employeePositionSelectorOnChange = (e) => { + const pos = getSelectText(e.target); + this.state.employee.pos = pos; + } + + // Numeraljs formatter + const nf = "0.000"; + + // Employee stats (after applying multipliers) + const emp = this.state.employee; + const effCre = emp ? emp.cre * corp.getEmployeeCreMultiplier() * division.getEmployeeCreMultiplier() : 0; + const effCha = emp ? emp.cha * corp.getEmployeeChaMultiplier() * division.getEmployeeChaMultiplier() : 0; + const effInt = emp ? emp.int * corp.getEmployeeIntMultiplier() * division.getEmployeeIntMultiplier() : 0; + const effEff = emp ? emp.eff * corp.getEmployeeEffMultiplier() * division.getEmployeeEffMultiplier() : 0; + + return ( +
+ + +
+ { + this.state.employee != null && +

+ Morale: {numeralWrapper.format(this.state.employee.mor, nf)} +
+ Happiness: {numeralWrapper.format(this.state.employee.hap, nf)} +
+ Energy: {numeralWrapper.format(this.state.employee.ene, nf)} +
+ Age: {numeralWrapper.format(this.state.employee.age, nf)} +
+ Intelligence: {numeralWrapper.format(effInt, nf)} +
+ Charisma: {numeralWrapper.format(effCha, nf)} +
+ Experience: {numeralWrapper.format(this.state.employee.exp, nf)} +
+ Creativity: {numeralWrapper.format(effCre, nf)} +
+ Efficiency: {numeralWrapper.format(effEff, nf)} +
+ Salary: {numeralWrapper.formatMoney(this.state.employee.sal)} +

+ } + { + this.state.employee != null && + + } +
+ + +
+ ) + } + + 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 = "std-button tooltip"; + if (office.employees.length === 0) { + hireEmployeeButtonClass += " flashing-button"; + } + const hireEmployeeButtonOnClick = () => { + office.findEmployees({ corporation: corp, industry: division }); + } + + // Autohire employee button + const autohireEmployeeButtonOnClick = () => { + office.hireRandomEmployee({ corporation: corp, industry: division }); + 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 ( +
+

Office Space

+

Size: {office.employees.length} / {office.size} employees

+ + +
+ + { + !division.hasResearch("AutoPartyManager") && + + } +
+ + {this.renderEmployeeManagement()} +
+ ) + } +} diff --git a/src/Corporation/ui/IndustryOverview.jsx b/src/Corporation/ui/IndustryOverview.jsx new file mode 100644 index 000000000..086c5df2a --- /dev/null +++ b/src/Corporation/ui/IndustryOverview.jsx @@ -0,0 +1,261 @@ +// 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 { Industries } from "../IndustryData"; +import { IndustryUpgrades } from "../IndustryUpgrades"; +import { numeralWrapper } from "../../ui/numeralFormat"; +import { dialogBoxCreate } from "../../../utils/DialogBox"; + +export class IndustryOverview extends BaseReactComponent { + renderMakeProductButton() { + const corp = this.corp(); + const division = this.routing().currentDivision; // Validated inside render() + + var createProductButtonText, createProductPopupText; + switch(division.type) { + case Industries.Food: + createProductButtonText = "Build Restaurant"; + createProductPopupText = "Build and manage a new restaurant!" + break; + case Industries.Tobacco: + createProductButtonText = "Create Product"; + createProductPopupText = "Create a new tobacco product!"; + break; + case Industries.Pharmaceutical: + createProductButtonText = "Create Drug"; + createProductPopupText = "Design and develop a new pharmaceutical drug!"; + break; + case Industries.Computer: + case "Computer": + createProductButtonText = "Create Product"; + createProductPopupText = "Design and manufacture a new computer hardware product!"; + break; + case Industries.Robotics: + createProductButtonText = "Design Robot"; + createProductPopupText = "Design and create a new robot or robotic system!"; + break; + case Industries.Software: + createProductButtonText = "Develop Software"; + createProductPopupText = "Develop a new piece of software!"; + break; + case Industries.Healthcare: + createProductButtonText = "Build Hospital"; + createProductPopupText = "Build and manage a new hospital!"; + break; + case Industries.RealEstate: + createProductButtonText = "Develop Property"; + createProductPopupText = "Develop a new piece of real estate property!"; + break; + default: + createProductButtonText = "Create Product"; + createProductPopupText = "Create a new product!"; + return ""; + } + createProductPopupText += "

To begin developing a product, " + + "first choose the city in which it will be designed. The stats of your employees " + + "in the selected city affect the properties of the finished product, such as its " + + "quality, performance, and durability.

" + + "You can also choose to invest money in the design and marketing of " + + "the product. Investing money in its design will result in a superior product. " + + "Investing money in marketing the product will help the product's sales."; + + 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", + } + + return ( + + ) + } + + renderText() { + const corp = this.corp(); + const division = this.routing().currentDivision; // Validated inside render() + + 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; + let advertisingTooltip; + const advertisingFactors = division.getAdvertisingFactors(); + const awarenessFac = advertisingFactors[1]; + const popularityFac = advertisingFactors[2]; + const ratioFac = advertisingFactors[3]; + 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 = () => { + dialogBoxCreate("Owning Hardware, Robots, AI Cores, and Real Estate " + + "can boost your Industry's production. The effect these " + + "materials have on your production varies between Industries. " + + "For example, Real Estate may be very effective for some Industries, " + + "but ineffective for others.

" + + "This division's production multiplier is calculated by summing " + + "the individual production multiplier of each of its office locations. " + + "This production multiplier is applied to each office. Therefore, it is " + + "beneficial to expand into new cities as this can greatly increase the " + + "production multiplier of your entire Division."); + } + + return ( +
+ {genInfo} +

+ {awareness}
+ {popularity}
+ { + (advertisingInfo !== false) && +

Advertising Multiplier: {numeralWrapper.format(totalAdvertisingFac, "0.000")} + + Total multiplier for this industrys sales due to its awareness and popularity +
+ Awareness Bonus: x{formatNumber(Math.pow(awarenessFac, 0.85), 3)} +
+ Popularity Bonus: x{formatNumber(Math.pow(popularityFac, 0.85), 3)} +
+ Ratio Multiplier: x{formatNumber(Math.pow(ratioFac, 0.85), 3)} +
+

+ } + {advertisingInfo} +

+ {revenue}
+ {expenses}
+ {profitStr} +

+

+ Production Multiplier: {numeralWrapper.format(division.prodMult, "0.00")} + + Production gain from owning production-boosting materials + such as hardware, Robots, AI Cores, and Real Estate + +

+
?
+

+

+ Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000")} + + Scientific Research increases the quality of the materials and + products that you produce. + +

+ +
?
+
+ ) + } + + renderUpgrades() { + const corp = this.corp(); + const division = this.routing().currentDivision; // Validated inside render() + const office = division.offices[this.props.currentCity]; + if (!(office instanceof OfficeSpace)) { + throw new Error(`Current City (${this.props.currentCity}) for UI does not have an OfficeSpace object`); + } + + const upgrades = []; + for (const index in IndustryUpgrades) { + const upgrade = IndustryUpgrades[index]; + + // AutoBrew research disables the Coffee upgrade + if (division.hasResearch("AutoBrew") && upgrade[4] === "Coffee") { continue; } + + const i = upgrade[0]; + const baseCost = upgrade[1]; + const priceMult = upgrade[2]; + let cost = 0; + switch (i) { + case 0: //Coffee, cost is static per employee + cost = office.employees.length * baseCost; + break; + default: + cost = baseCost * Math.pow(priceMult, division.upgrades[i]); + break; + } + + const onClick = () => { + if (corp.funds.lt(cost)) { + dialogBoxCreate("Insufficient funds"); + } else { + corp.funds = corp.funds.minus(cost); + division.upgrade(upgrade, { + corporation: corp, + office: office, + }); + // corp.displayDivisionContent(division, city); + corp.rerender(); + } + } + + upgrades.push(this.renderUpgrade({ + onClick: onClick, + text: `${upgrade[4]} - ${numeralWrapper.formatMoney(cost)}`, + tooltip: upgrade[5], + })); + } + + return upgrades; + } + + renderUpgrade(props) { + return ( +
+ {props.text} + { + props.tooltip != null && + {props.tooltip} + } +
+ ) + } + + 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 ( +
+ {this.renderText()} +
+ + Purchases & Upgrades
+ {this.renderUpgrades()}
+ + { + division.makesProducts && + {makeProductButton} + } +
+ ) + } +} diff --git a/src/Corporation/ui/IndustryWarehouse.jsx b/src/Corporation/ui/IndustryWarehouse.jsx new file mode 100644 index 000000000..3c643ed6e --- /dev/null +++ b/src/Corporation/ui/IndustryWarehouse.jsx @@ -0,0 +1,486 @@ +// 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 { Material } from "../Material"; +import { Product } from "../Product"; + +import { Warehouse, + WarehouseInitialCost, + WarehouseUpgradeBaseCost } from "../Corporation"; + +import { numeralWrapper } from "../../ui/numeralFormat"; + +import { isString } from "../../../utils/helpers/isString"; + +// Creates the UI for a single Product type +function ProductComponent(props) { + const corp = props.corp; + const division = props.division; + const warehouse = props.warehouse; + const product = props.product; + const eventHandler = props.eventHandler; + + const nf = "0.000"; // Numeraljs formatter + + const hasUpgradeDashboard = division.hasResearch("uPgrade: Dashboard"); + + // Total product gain = production - sale + const totalGain = totalGain = product.data[city][1] - product.data[city][2]; + + // Sell button + const sellButtonText = product.sllman[city][1] === -1 + ? "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/MAX)" + : "Sell (" + numeralWrapper.format(product.data[city][2], nf) + "/" + numeralWrapper.format(product.sllman[city][1], nf) + ")"; + if (product.sCost) { + if (isString(product.sCost)) { + sellButtonText += (" @ " + product.sCost); + } else { + sellButtonText += (" @ " + numeralWrapper.format(product.sCost, "$0.000a")); + } + } + const sellButtonOnClick = eventHandler.createSellProductPopup.bind(eventHandler, product); + + // Limit Production button + const limitProductionButtonText = "Limit Production"; + if (product.prdman[city][0]) { + limitProductionButtonText += " (" + numeralWrapper.format(product.prdman[city][1], nf) + ")"; + } + const limitProductionButtonOnClick = eventHandler.createLimitProductProdutionPopup.bind(eventHandler, product); + + // Discontinue Button + const discontinueButtonOnClick = eventHandler.createDiscontinueProductPopup.bind(eventHandler, product); + + // Unfinished Product + if (!product.fin) { + if (hasUpgradeDashboard) { + return ( +
+

Designing {product.name}...

+

{numeralWrapper.format(product.prog, "0.00")}% complete

+
+ +
+
+ + +
+
+ ) + } else { + return ( +
+

Designing {product.name}...

+

{numeralWrapper.format(product.prog, "0.00")}% complete

+
+ ); + } + } + + return ( +
+

+ {product.name}: {numeralWrapper.format(product.data[city][0], nf)} ({numeralWrapper.format(totalGain, nf)}/s) + + Prod: {numeralWrapper.format(product.data[city][1], nf)}/s +
+ Sell: {numeralWrapper.format(product.data[city][2], nf)} /s +
+

+

+ Rating: {numeralWrapper.format(product.rat, nf)} + + Quality: {numeralWrapper.format(product.qlt, nf)}
+ Performance: {numeralWrapper.format(product.per, nf)}
+ Durability: {numeralWrapper.format(product.dur, nf)}
+ Reliability: {numeralWrapper.format(product.rel, nf)}
+ Aesthetics: {numeralWrapper.format(product.aes, nf)}
+ Features: {numeralWrapper.format(product.fea, nf)} + { + corp.unlockUpgrades[2] === 1 &&
+ } + { + corp.unlockUpgrades[2] === 1 && + "Demand: " + numeralWrapper.format(product.dmd, nf) + } + { + corp.unlockUpgrades[3] === 1 &&
+ } + { + corp.unlockUpgrades[3] === 1 && + "Competition: " + numeralWrapper.format(product.cmp, nf) + } + +
+

+

+ Est. Production Cost: {numeralWrapper.formatMoney(product.pCost / ProductProductionCostRatio)} + + An estimate of the material cost it takes to create this Product. + +

+

+ Est. Market Price: {numeralWrapper.formatMoney(product.pCost + product.rat / product.mku)} + + An estimate of how much consumers are willing to pay for this product. + Setting the sale price above this may result in less sales. Setting the sale price below this may result + in more sales. + +

+ +
+
+ + +
+
+ ) +} + +// Creates the UI for a single Material type +function MaterialComponent(props) { + const corp = props.corp; + const division = props.division; + const warehouse = props.warehouse; + const mat = props.mat; + const eventHandler = props.eventHandler; + + // Numeraljs formatter + const nf = "0.000"; + + // Total gain or loss of this material (per second) + const totalGain = mat.buy + mat.prd + mat.imp - mat.sll - mat.totalExp; + + // Competition and demand info, if they're unlocked + let cmpAndDmdText = ""; + if (corp.unlockUpgrades[2] === 1) { + cmpAndDmdText += "
Demand: " + numeralWrapper.format(mat.dmd, nf); + } + if (corp.unlockUpgrades[3] === 1) { + cmpAndDmdText += "
Competition: " + numeralWrapper.format(mat.cmp, nf); + } + + // Flag that determines whether this industry is "new" and the current material should be + // marked with flashing-red lights + const tutorial = division.newInd && Object.keys(division.reqMats).includes(mat.name) && + mat.buy === 0 && mat.imp === 0; + + // Purchase material button + const purchaseButtonText = `Buy (${numeralWrapper.format(mat.buy, nf)})`; + const purchaseButtonClass = tutorial ? "std-button flashing-button tooltip" : "std-button"; + const purchaseButtonOnClick = eventHandler.createPurchaseMaterialPopup.bind(eventHandler, mat, division); + + // Export material button + const exportButtonOnClick = eventHandler.createExportMaterialPopup.bind(eventHandler, mat); + + // Sell material button + let sellButtonText; + if (mat.sllman[0]) { + sellButtonText = (mat.sllman[1] === -1 ? "Sell (" + numeralWrapper.format(mat.sll, nf) + "/MAX)" : + "Sell (" + numeralWrapper.format(mat.sll, nf) + "/" + numeralWrapper.format(mat.sllman[1], nf) + ")"); + if (mat.sCost) { + if (isString(mat.sCost)) { + var sCost = mat.sCost.replace(/MP/g, mat.bCost); + sellButtonText += " @ $" + numeralWrapper.format(eval(sCost), "0.00"); + } else { + sellButtonText += " @ $" + numeralWrapper.format(mat.sCost, "0.00"); + } + } + } else { + sellButtonText = "Sell (0.000/0.000)"; + } + const sellButtonOnClick = eventHandler.createSellMaterialPopup.bind(eventHandler, mat); + + // Market TA button + const marketTaButtonOnClick = eventHandler.createMarketTaPopup.bind(eventHandler, mat, division); + + return ( +
+
+

+ {mat.name}: {numeralWrapper.format(mat.qty, nf)} ({numeralWrapper.format(totalGain, nf)}/s) + + Buy: {numeralWrapper.format(mat.buy, nf)}
+ Prod: {numeralWrapper.format(mat.prd, nf)}
+ Sell: {numeralWrapper.format(mat.sll, nf)}
+ Export: {numeralWrapper.format(mat.totalExp, nf)}
+ Import: {numeralWrapper.format(mat.imp, nf)} + { + corp.unlockUpgrades[2] === 1 &&
+ } + { + corp.unlockUpgrades[2] === 1 && + "Demand: " + numeralWrapper.format(mat.dmd, nf) + } + { + corp.unlockUpgrades[3] === 1 &&
+ } + { + corp.unlockUpgrades[3] === 1 && + "Competition: " + numeralWrapper.format(mat.cmp, nf) + } +
+


+

+ MP: {numeralWrapper.formatMoney(mat.bCost)} + + Market Price: The price you would pay if you were to buy this material on the market + +


+

+ Quality: {numeralWrapper.format(mat.qlt, "0.00")} + + The quality of your material. Higher quality will lead to more sales + +

+
+ +
+ + + { + corp.unlockUpgrades[0] === 1 && + + } +
+ + + + { + division.hasResearch("Market-TA.I") && + + } + +
+
+ ) +} + +export class IndustryWarehouse extends BaseReactComponent { + renderWarehouseUI() { + const corp = this.corp(); + const division = this.routing().currentDivision; // Validated in render() + const warehouse = division.warehouses[this.props.currentCity]; // Validated in render() + + // General Storage information at the top + const sizeUsageStyle = { + color: warehouse.sizeUsed >= warehouse.size ? "red" : "white", + margin: "5px", + } + + // Upgrade Warehouse size button + const sizeUpgradeCost = 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 = () => { + ++warehouse.level; + warehouse.updateSize(corp, division); + corp.funds = corp.funds.minus(sizeUpgradeCost); + warehouse.createUI(parentRefs); + return; + } + + // Industry material Requirements + let generalReqsText = "This Industry uses [" + Object.keys(division.reqMats).join(", ") + + "] in order to "; + if (division.prodMats.length > 0) { + generalReqsText += "produce [" + division.prodMats.join(", ") + "] "; + if (division.makesProducts) { + generalReqsText += " and " + division.getProductDescriptionText(); + } + } else if (division.makesProducts) { + generalReqsText += (division.getProductDescriptionText() + "."); + } + + const ratioLines = []; + for (const matName in division.reqMats) { + if (division.reqMats.hasOwnProperty(matName)) { + const text = [" *", division.reqMats[matName], matName].join(" "); + ratioLines.push(( +
+

{text}

+
+ )); + } + } + + let createdItemsText = "in order to create "; + if (division.prodMats.length > 0) { + createdItemsText += "one of each produced Material (" + division.prodMats.join(", ") + ") "; + if (division.makesProducts) { + createdItemsText += "or to create one of its Products"; + } + } else if (division.makesProducts) { + createdItemsText += "one of its Products"; + } + + // Current State: + let stateText; + switch(division.state) { + case "START": + stateText = "Current state: Preparing..."; + break; + case "PURCHASE": + stateText = "Current state: Purchasing materials..."; + break; + case "PRODUCTION": + stateText = "Current state: Producing materials and/or products..."; + break; + case "SALE": + stateText = "Current state: Selling materials and/or products..."; + break; + case "EXPORT": + stateText = "Current state: Exporting materials and/or products..."; + break; + default: + console.error(`Invalid state: ${division.state}`); + break; + } + + // Smart Supply Checkbox + const smartSupplyCheckboxId = "cmpy-mgmt-smart-supply-checkbox"; + const smartSupplyOnChange = (e) => { + warehouse.smartSupplyEnabled = e.target.value; + } + + // Materials that affect Production multiplier + const prodMultiplierMats = ["Hardware", "Robots", "AICores", "RealEstate"]; + + // Returns a boolean indicating whether the given material is relevant for the + // current industry. + function isRelevantMaterial(matName) { + if (Object.keys(division.reqMats).includes(matName)) { return true; } + if (division.prodMats.includes(matName)) { return true; } + if (prodMultiplierMats.includes(matName)) { return true; } + + return false; + } + + // Create React components for materials + const mats = []; + for (const matName in warehouse.materials) { + if (warehouse.materials[matName] instanceof Material) { + // Only create UI for materials that are relevant for the industry + if (isRelevantMaterial(matName)) { + mats.push(MaterialComponent({ + corp: corp, + division: division, + eventHandler: this.eventHandler(), + key: matName, + mat: warehouse.materials[matName], + warehouse: warehouse, + })); + } + } + } + + // Create React components for products + const products = []; + if (division.makesProducts && Object.keys(division.products).length > 0) { + for (const productName in division.products) { + if (division.products[productName] instanceof Product) { + products.push({ + corp: corp, + division: division, + eventHandler: this.eventHandler(), + key: productName, + product: division.products[productName], + warehouse: warehouse, + }) + } + } + } + + return ( +
+

+ Storage: {numeralWrapper.format(warehouse.sizeUsed, "0.000")} / {numeralWrapper.format(warehouse.size, "0.000")} + + {warehouse.breakdown} + +

+ + + +

{generalReqsText}. The exact requirements for production are:


+ {ratioLines}
+

{createdItemsText}

+

+ To get started with production, purchase your required materials + or import them from another of your company's divisions. +


+ +

{stateText}

+ + { + corp.unlockUpgrades[1] && +
+ + +
+ } + + {mats} + + {products} + +
+ ) + } + + render() { + const corp = this.corp(); + 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 newWarehouseOnClick = this.eventHandler().purchaseWarehouse.bind(this.eventHandler(), division, this.props.currentCity); + + if (warehouse instanceof Warehouse) { + return this.renderWarehouseUI(); + } else { + return ( + + ) + } + } +} diff --git a/src/Corporation/ui/LevelableUpgrade.jsx b/src/Corporation/ui/LevelableUpgrade.jsx new file mode 100644 index 000000000..1c79c0d37 --- /dev/null +++ b/src/Corporation/ui/LevelableUpgrade.jsx @@ -0,0 +1,35 @@ +// 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 = () => { + if (this.corp().funds.lt(cost)) { + dialogBoxCreate("Insufficient funds"); + } else { + this.corp().upgrade(data); + //this.corp().displayCorporationOverviewContent(); + } + } + + return ( +
+ {text} + {tooltip} +
+ ) + } +} diff --git a/src/Corporation/ui/MainPanel.jsx b/src/Corporation/ui/MainPanel.jsx new file mode 100644 index 000000000..d737c8baa --- /dev/null +++ b/src/Corporation/ui/MainPanel.jsx @@ -0,0 +1,91 @@ +// 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 { overviewPage } from "./Routing"; + +import { Cities } from "../../Locations/Cities"; + +export class MainPanel extends BaseReactComponent { + constructor(props) { + super(props); + + this.state = { + division: "", + city: Cities.Sector12, + } + } + + // 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 = Cities.Sector12; + } + + return this.renderDivisionPage(); + } + } + + renderOverviewPage() { + return ( +
+ +
+ ) + } + + 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) { + onClicks[cityName] = () => { + this.state.city = cityName; + this.corp().rerender(); + } + } + const cityTabs = ( + + ) + + // Rest of Industry UI + const industry = ( + + ) + + return ( +
+ {cityTabs} + {industry} +
+ ) + } + + render() { + return this.renderContent(); + } +} diff --git a/src/Corporation/ui/Overview.jsx b/src/Corporation/ui/Overview.jsx new file mode 100644 index 000000000..6ae424863 --- /dev/null +++ b/src/Corporation/ui/Overview.jsx @@ -0,0 +1,323 @@ +// 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"; + +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 ( + + {props.text} + { + hasTooltip && + + {props.tooltip} + + } + + ) + + } + + // 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

` + + `Dividend Percentage: ${numeralWrapper.format(this.corp().dividendPercentage / 100, "0%")}
` + + `Dividends per share: ${numeralWrapper.format(dividendsPerShare, "$0.000a")} / s
` + + `Your earnings as a shareholder (Pre-Tax): ${numeralWrapper.format(playerEarnings, "$0.000a")} / s
` + + `Dividend Tax Rate: ${this.corp().dividendTaxPercentage}%
` + + `Your earnings as a shareholder (Post-Tax): ${numeralWrapper.format(playerEarnings * (1 - (this.corp().dividendTaxPercentage / 100)), "$0.000a")} / s
`; + } + + let txt = "Total Funds: " + numeralWrapper.format(this.corp().funds.toNumber(), '$0.000a') + "
" + + "Total Revenue: " + numeralWrapper.format(this.corp().revenue.toNumber(), "$0.000a") + " / s
" + + "Total Expenses: " + numeralWrapper.format(this.corp().expenses.toNumber(), "$0.000a") + "/ s
" + + "Total Profits: " + profitStr + " / s
" + + dividendStr + + "Publicly Traded: " + (this.corp().public ? "Yes" : "No") + "
" + + "Owned Stock Shares: " + numeralWrapper.format(this.corp().numShares, '0.000a') + "
" + + "Stock Price: " + (this.corp().public ? "$" + numeralWrapper.formatMoney(this.corp().sharePrice) : "N/A") + "
" + + "

Total Stock Shares: " + numeralWrapper.format(this.corp().totalShares, "0.000a") + + "" + + `Outstanding Shares: ${numeralWrapper.format(this.corp().issuedShares, "0.000a")}
` + + `Private Shares: ${numeralWrapper.format(this.corp().totalShares - this.corp().issuedShares - this.corp().numShares, "0.000a")}` + + "



"; + + const storedTime = this.corp().storedCycles * CONSTANTS.MilliPerCycle / 1000; + if (storedTime > 15) { + txt += `Bonus Time: ${storedTime} seconds

`; + } + + 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") + "
";} + if (storageMult > 1) {txt += "Storage Multiplier: " + numeralWrapper.format(storageMult, "0.000") + "
";} + if (advMult > 1) {txt += "Advertising Multiplier: " + numeralWrapper.format(advMult, "0.000") + "
";} + if (empCreMult > 1) {txt += "Empl. Creativity Multiplier: " + numeralWrapper.format(empCreMult, "0.000") + "
";} + if (empChaMult > 1) {txt += "Empl. Charisma Multiplier: " + numeralWrapper.format(empChaMult, "0.000") + "
";} + if (empIntMult > 1) {txt += "Empl. Intelligence Multiplier: " + numeralWrapper.format(empIntMult, "0.000") + "
";} + if (empEffMult > 1) {txt += "Empl. Efficiency Multiplier: " + numeralWrapper.format(empEffMult, "0.000") + "
";} + if (salesMult > 1) {txt += "Sales Multiplier: " + numeralWrapper.format(salesMult, "0.000") + "
";} + if (sciResMult > 1) {txt += "Scientific Research Multiplier: " + numeralWrapper.format(sciResMult, "0.000") + "
";} + + 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 ( +
+ {generalBtns.getStarterGuide} + {findInvestorsBtn} + {goPublicBtn} +
+ {generalBtns.bribeFactions} +
+ ) + + } + + // 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: sellSharesOnClick, + 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 ( +
+ {generalBtns.getStarterGuide} + {sellSharesBtn} + {buybackSharesBtn} +
+ {issueNewSharesBtn} + {issueDividendsBtn} +
+ {generalBtns.bribeFactions} +
+ ) + } + + // 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 ( +
+

Unlocks

+ {unlockUpgrades} + +

Upgrades

+ { + levelableUpgradeProps.map((data) => { + return this.renderLevelableUpgrade(data); + }) + } +
+ ) + } + + renderUnlockUpgrade(data) { + return ( + + ) + } + + renderLevelableUpgrade(data) { + return ( + + ) + + } + + render() { + return ( +
+

+ {this.renderButtons()} +
+ {this.renderUpgrades()} +
+ ) + } +} diff --git a/src/Corporation/ui/Root.jsx b/src/Corporation/ui/Root.jsx new file mode 100644 index 000000000..f393de20e --- /dev/null +++ b/src/Corporation/ui/Root.jsx @@ -0,0 +1,17 @@ +// 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 ( +
+ + +
+ ) + } +} diff --git a/src/Corporation/ui/Routing.ts b/src/Corporation/ui/Routing.ts new file mode 100644 index 000000000..5e2c023fb --- /dev/null +++ b/src/Corporation/ui/Routing.ts @@ -0,0 +1,96 @@ +import { IMap } from "../../types"; + +export const overviewPage: string = "Overview"; + +// Interfaces for whatever's required to sanitize routing with Corporation Data +interface IOfficeSpace { + +} + +interface IDivision { + name: string; + offices: IMap +} + +interface ICorporation { + divisions: IDivision[]; +} + +/** + * Keeps track of what content is currently being displayed for the Corporation UI + */ +export class CorporationRouting { + private currentPage: string = overviewPage; + + // Stores a reference to the Corporation instance + private corp: ICorporation; + + // 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; + + constructor(corp: ICorporation) { + this.corp = corp; + } + + current(): string { + return this.currentPage; + } + + /** + * Checks that the specified page has a valid value + */ + isValidPage(page: string): boolean { + if (page === overviewPage) { return true; } + + for (const division of this.corp.divisions) { + if (division.name === page) { return true; } + } + + return false; + } + + /** + * Returns a boolean indicating whether or not the player is on the given page + */ + isOn(page: string): boolean { + if (!this.isValidPage(page)) { return false; } + + return page === this.currentPage; + } + + isOnOverviewPage(): boolean { + return this.currentPage === overviewPage; + } + + /** + * Routes to the specified page + */ + routeTo(page: string): void { + if (!this.isValidPage(page)) { return; } + + + this.currentDivision = null; + if (page !== overviewPage) { + // Iterate through Corporation data to get a reference to the current division + for (let i = 0; i < this.corp.divisions.length; ++i) { + if (this.corp.divisions[i].name === page) { + this.currentDivision = this.corp.divisions[i]; + }; + } + + // 'currentDivision' should not be null, since the routing is either on + // the overview page or a division page + if (this.currentDivision == null) { + console.warn(`Routing could not find division ${page}`); + } + } + + this.currentPage = page; + } + + routeToOverviewPage(): void { + this.currentPage = overviewPage; + this.currentDivision = null; + } +} diff --git a/src/Corporation/ui/UnlockUpgrade.jsx b/src/Corporation/ui/UnlockUpgrade.jsx new file mode 100644 index 000000000..1ead8b199 --- /dev/null +++ b/src/Corporation/ui/UnlockUpgrade.jsx @@ -0,0 +1,29 @@ +// 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 = () => { + if (this.corp().funds.lt(data[1])) { + dialogBoxCreate("Insufficient funds"); + } else { + this.corp().unlock(data); + //this.corp().displayCorporationOverviewContent(); + } + } + + return ( +
+ {text} + {tooltip} +
+ ) + } +} diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 38e985350..5ad3c11b7 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -44,6 +44,7 @@ import { AllServers, import { Server } from "./Server/Server"; import { GetServerByHostname, getServer, + getServerOnNetwork, numCycleForGrowth, processSingleServerGrowth } from "./Server/ServerHelpers"; import { getPurchaseServerCost, diff --git a/src/Prestige.js b/src/Prestige.js index 0f12c1e97..c764c5a37 100755 --- a/src/Prestige.js +++ b/src/Prestige.js @@ -26,10 +26,10 @@ import {Player} from "./Player"; import { AllServers, AddToAllServers, + initForeignServers, prestigeAllServers } from "./Server/AllServers"; import { Server } from "./Server/Server" -import { initForeignServers, - prestigeHomeComputer } from "./Server/ServerHelpers"; +import { prestigeHomeComputer } from "./Server/ServerHelpers"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; import { SpecialServerIps, SpecialServerIpsMap, diff --git a/src/ScriptEditor/CodeMirrorNetscriptMode.js b/src/ScriptEditor/CodeMirrorNetscriptMode.js index a457891d2..ead49e12f 100644 --- a/src/ScriptEditor/CodeMirrorNetscriptMode.js +++ b/src/ScriptEditor/CodeMirrorNetscriptMode.js @@ -208,6 +208,7 @@ CodeMirror.defineMode("netscript", function(config, parserConfig) { "getSkillNames": atom, "startAction": atom, "stopBladeburnerAction": atom, + "getCurrentAction": atom, "getActionTime": atom, "getActionEstimatedSuccessChance": atom, "getActionCountRemaining": atom, diff --git a/src/engine.js b/src/engine.js index de21680f4..2f4ae4f79 100644 --- a/src/engine.js +++ b/src/engine.js @@ -53,9 +53,9 @@ import { getCurrentEditor, loadAllRunningScripts, scriptEditorInit, updateScriptEditorContent } from "./Script/ScriptHelpers"; -import { AllServers } from "./Server/AllServers"; +import { AllServers, + initForeignServers } from "./Server/AllServers"; import { Server } from "./Server/Server"; -import { initForeignServers } from "./Server/ServerHelpers"; import {Settings} from "./Settings/Settings"; import { initSourceFiles, SourceFiles } from "./SourceFile"; import { updateSourceFileFlags } from "./SourceFile/SourceFileFlags"; @@ -458,8 +458,8 @@ const Engine = { if (Player.corporation instanceof Corporation) { Engine.hideAllContent(); document.getElementById("character-overview-wrapper").style.visibility = "hidden"; - Player.corporation.createUI(); routing.navigateTo(Page.Corporation); + Player.corporation.createUI(); } }, @@ -943,9 +943,7 @@ const Engine = { } if (Engine.Counters.updateDisplaysMed <= 0) { - if (routing.isOn(Page.Corporation)) { - Player.corporation.updateUIContent(); - } else if (routing.isOn(Page.CharacterInfo)) { + if (routing.isOn(Page.CharacterInfo)) { Engine.updateCharacterInfo(); } Engine.Counters.updateDisplaysMed = 9; diff --git a/tsconfig.json b/tsconfig.json index 3e238cf10..831898523 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "baseUrl" : ".", + "jsx": "react", "lib" : ["es2016", "dom"], "module": "commonjs", "target": "es6", diff --git a/webpack.config.js b/webpack.config.js index 880eef2f7..5808115ad 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -84,6 +84,13 @@ module.exports = (env, argv) => { loader: 'ts-loader', exclude: /node_modules/ }, + { + test: /\.(jsx)$/, + exclude: /node_modules/, + use: { + loader: "babel-loader" + } + }, { test: /\.s?css$/, use: [ @@ -125,7 +132,8 @@ module.exports = (env, argv) => { extensions: [ ".tsx", ".ts", - ".js" + ".js", + ".jsx", ] } };