2023-06-10 01:34:35 +02:00
|
|
|
import { isInteger } from "lodash";
|
|
|
|
|
2022-10-10 00:42:14 +02:00
|
|
|
import { Player } from "@player";
|
2023-06-10 01:34:35 +02:00
|
|
|
import { CorpResearchName, CorpSmartSupplyOption } from "@nsdefs";
|
|
|
|
|
2022-12-25 09:33:13 +01:00
|
|
|
import { MaterialInfo } from "./MaterialInfo";
|
2022-09-23 11:54:04 +02:00
|
|
|
import { Corporation } from "./Corporation";
|
2023-06-10 01:34:35 +02:00
|
|
|
import { IndustryResearchTrees, IndustriesData } from "./data/IndustryData";
|
2023-05-16 00:06:57 +02:00
|
|
|
import { Division } from "./Division";
|
2022-12-30 02:28:53 +01:00
|
|
|
import * as corpConstants from "./data/Constants";
|
2021-09-02 04:16:48 +02:00
|
|
|
import { OfficeSpace } from "./OfficeSpace";
|
2021-09-02 06:36:33 +02:00
|
|
|
import { Material } from "./Material";
|
|
|
|
import { Product } from "./Product";
|
|
|
|
import { Warehouse } from "./Warehouse";
|
2023-06-12 06:34:20 +02:00
|
|
|
import { IndustryType } from "@enums";
|
2021-09-10 08:17:55 +02:00
|
|
|
import { ResearchMap } from "./ResearchMap";
|
2022-02-12 04:31:50 +01:00
|
|
|
import { isRelevantMaterial } from "./ui/Helpers";
|
2023-06-12 06:34:20 +02:00
|
|
|
import { CityName } from "@enums";
|
2022-12-25 09:35:18 +01:00
|
|
|
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
2023-05-16 00:06:57 +02:00
|
|
|
import { getRecordValues } from "../Types/Record";
|
2021-09-02 04:16:48 +02:00
|
|
|
|
2023-05-17 23:28:24 +02:00
|
|
|
export function NewDivision(corporation: Corporation, industry: IndustryType, name: string): void {
|
2023-05-16 00:06:57 +02:00
|
|
|
if (corporation.divisions.size >= corporation.maxDivisions)
|
2023-03-18 02:12:43 +01:00
|
|
|
throw new Error(`Cannot expand into ${industry} industry, too many divisions!`);
|
2022-02-04 10:34:16 +01:00
|
|
|
|
2023-05-16 00:06:57 +02:00
|
|
|
if (corporation.divisions.has(name)) throw new Error(`Division name ${name} is already in use!`);
|
|
|
|
// "Overview" is forbidden as a division name, see CorporationRoot.tsx for why this would cause issues.
|
|
|
|
if (name === "Overview") throw new Error(`"Overview" is a forbidden division name.`);
|
2021-09-02 04:16:48 +02:00
|
|
|
|
2022-10-25 03:54:54 +02:00
|
|
|
const data = IndustriesData[industry];
|
|
|
|
if (!data) throw new Error(`Invalid industry: '${industry}'`);
|
|
|
|
const cost = data.startingCost;
|
2021-11-12 03:35:26 +01:00
|
|
|
if (corporation.funds < cost) {
|
2021-09-09 05:47:34 +02:00
|
|
|
throw new Error("Not enough money to create a new division in this industry");
|
2021-09-05 01:09:30 +02:00
|
|
|
} else if (name === "") {
|
|
|
|
throw new Error("New division must have a name!");
|
|
|
|
} else {
|
2021-11-12 03:35:26 +01:00
|
|
|
corporation.funds = corporation.funds - cost;
|
2023-05-16 00:06:57 +02:00
|
|
|
corporation.divisions.set(
|
|
|
|
name,
|
|
|
|
new Division({
|
2021-09-05 01:09:30 +02:00
|
|
|
corp: corporation,
|
|
|
|
name: name,
|
|
|
|
type: industry,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
2021-09-02 04:16:48 +02:00
|
|
|
}
|
|
|
|
|
2023-05-17 23:28:24 +02:00
|
|
|
export function removeDivision(corporation: Corporation, name: string) {
|
2023-05-16 00:06:57 +02:00
|
|
|
if (!corporation.divisions.has(name)) throw new Error("There is no division called " + name);
|
|
|
|
corporation.divisions.delete(name);
|
2023-06-07 05:50:23 +02:00
|
|
|
// We also need to remove any exports that were pointing to the old division
|
|
|
|
for (const otherDivision of corporation.divisions.values()) {
|
|
|
|
for (const warehouse of getRecordValues(otherDivision.warehouses)) {
|
|
|
|
for (const material of getRecordValues(warehouse.materials)) {
|
|
|
|
// Work backwards through exports array so splicing doesn't affect the loop
|
|
|
|
for (let i = material.exports.length - 1; i >= 0; i--) {
|
|
|
|
if (material.exports[i].division === name) material.exports.splice(i, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-18 02:12:43 +01:00
|
|
|
}
|
|
|
|
|
2023-05-16 00:06:57 +02:00
|
|
|
export function purchaseOffice(corporation: Corporation, division: Division, city: CityName): void {
|
2022-12-30 02:28:53 +01:00
|
|
|
if (corporation.funds < corpConstants.officeInitialCost) {
|
2021-09-09 05:47:34 +02:00
|
|
|
throw new Error("You don't have enough company funds to open a new office!");
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2022-04-01 13:50:21 +02:00
|
|
|
if (division.offices[city]) {
|
|
|
|
throw new Error(`You have already expanded into ${city} for ${division.name}`);
|
|
|
|
}
|
2023-09-19 14:47:16 +02:00
|
|
|
corporation.addNonIncomeFunds(-corpConstants.officeInitialCost);
|
2022-04-01 13:50:21 +02:00
|
|
|
division.offices[city] = new OfficeSpace({
|
2023-05-16 00:06:57 +02:00
|
|
|
city: city,
|
2022-12-30 02:28:53 +01:00
|
|
|
size: corpConstants.officeInitialSize,
|
2022-04-01 13:50:21 +02:00
|
|
|
});
|
2021-09-02 04:16:48 +02:00
|
|
|
}
|
|
|
|
|
2022-09-20 12:47:54 +02:00
|
|
|
export function IssueDividends(corporation: Corporation, rate: number): void {
|
2022-12-30 02:28:53 +01:00
|
|
|
if (isNaN(rate) || rate < 0 || rate > corpConstants.dividendMaxRate) {
|
|
|
|
throw new Error(`Invalid value. Must be an number between 0 and ${corpConstants.dividendMaxRate}`);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-09-02 04:16:48 +02:00
|
|
|
|
2022-05-30 22:45:36 +02:00
|
|
|
corporation.dividendRate = rate;
|
2021-09-02 06:36:33 +02:00
|
|
|
}
|
|
|
|
|
2022-12-25 09:35:18 +01:00
|
|
|
export function IssueNewShares(corporation: Corporation, amount: number): [number, number, number] {
|
|
|
|
const max = corporation.calculateMaxNewShares();
|
|
|
|
|
|
|
|
// Round to nearest ten-millionth
|
|
|
|
amount = Math.round(amount / 10e6) * 10e6;
|
|
|
|
|
|
|
|
if (isNaN(amount) || amount < 10e6 || amount > max) {
|
|
|
|
throw new Error(`Invalid value. Must be an number between 10m and ${max} (20% of total shares)`);
|
|
|
|
}
|
|
|
|
|
2023-03-18 02:12:43 +01:00
|
|
|
const newSharePrice = Math.round(corporation.sharePrice * 0.8);
|
2022-12-25 09:35:18 +01:00
|
|
|
|
|
|
|
const profit = amount * newSharePrice;
|
2022-12-30 02:28:53 +01:00
|
|
|
corporation.issueNewSharesCooldown = corpConstants.issueNewSharesCooldown;
|
2022-12-25 09:35:18 +01:00
|
|
|
|
|
|
|
const privateOwnedRatio = 1 - (corporation.numShares + corporation.issuedShares) / corporation.totalShares;
|
|
|
|
const maxPrivateShares = Math.round((amount / 2) * privateOwnedRatio);
|
|
|
|
const privateShares = Math.round(getRandomInt(0, maxPrivateShares) / 10e6) * 10e6;
|
|
|
|
|
|
|
|
corporation.issuedShares += amount - privateShares;
|
|
|
|
corporation.totalShares += amount;
|
2023-09-19 14:47:16 +02:00
|
|
|
corporation.addNonIncomeFunds(profit);
|
2022-12-25 09:35:18 +01:00
|
|
|
corporation.immediatelyUpdateSharePrice();
|
|
|
|
|
|
|
|
return [profit, amount, privateShares];
|
|
|
|
}
|
|
|
|
|
2023-05-16 00:06:57 +02:00
|
|
|
export function SellMaterial(material: Material, amount: string, price: string): void {
|
2021-09-05 01:09:30 +02:00
|
|
|
if (price === "") price = "0";
|
2023-05-16 00:06:57 +02:00
|
|
|
if (amount === "") amount = "0";
|
2021-09-05 01:09:30 +02:00
|
|
|
let cost = price.replace(/\s+/g, "");
|
2021-11-16 05:49:33 +01:00
|
|
|
cost = cost.replace(/[^-()\d/*+.MPe]/g, ""); //Sanitize cost
|
2022-10-25 18:51:03 +02:00
|
|
|
let temp = cost.replace(/MP/, "1.234e5");
|
2021-09-05 01:09:30 +02:00
|
|
|
try {
|
2022-10-25 14:51:37 +02:00
|
|
|
if (temp.includes("MP")) throw "Only one reference to MP is allowed in sell price.";
|
2021-09-05 01:09:30 +02:00
|
|
|
temp = eval(temp);
|
|
|
|
} catch (e) {
|
|
|
|
throw new Error("Invalid value or expression for sell price field: " + e);
|
|
|
|
}
|
|
|
|
|
2022-10-25 18:51:03 +02:00
|
|
|
if (temp == null || isNaN(parseFloat(temp))) {
|
2021-09-05 01:09:30 +02:00
|
|
|
throw new Error("Invalid value or expression for sell price field");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cost.includes("MP")) {
|
2023-05-16 00:06:57 +02:00
|
|
|
material.desiredSellPrice = cost; //Dynamically evaluated
|
2021-09-05 01:09:30 +02:00
|
|
|
} else {
|
2023-05-16 00:06:57 +02:00
|
|
|
material.desiredSellPrice = temp;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//Parse quantity
|
2023-05-16 00:06:57 +02:00
|
|
|
amount = amount.toUpperCase();
|
|
|
|
if (amount.includes("MAX") || amount.includes("PROD") || amount.includes("INV")) {
|
|
|
|
let q = amount.replace(/\s+/g, "");
|
2023-03-18 02:12:43 +01:00
|
|
|
q = q.replace(/[^-()\d/*+.MAXPRODINV]/g, "");
|
2023-05-16 00:06:57 +02:00
|
|
|
let tempQty = q.replace(/MAX/g, material.maxSellPerCycle.toString());
|
|
|
|
tempQty = tempQty.replace(/PROD/g, material.productionAmount.toString());
|
|
|
|
tempQty = tempQty.replace(/INV/g, material.productionAmount.toString());
|
2021-09-02 06:36:33 +02:00
|
|
|
try {
|
2021-09-05 01:09:30 +02:00
|
|
|
tempQty = eval(tempQty);
|
|
|
|
} catch (e) {
|
2022-03-16 12:08:00 +01:00
|
|
|
throw new Error("Invalid value or expression for sell quantity field: " + e);
|
2021-09-02 06:36:33 +02:00
|
|
|
}
|
|
|
|
|
2023-03-18 02:12:43 +01:00
|
|
|
if (tempQty == null || isNaN(parseFloat(tempQty))) {
|
2022-03-16 12:08:00 +01:00
|
|
|
throw new Error("Invalid value or expression for sell quantity field");
|
2021-09-02 06:36:33 +02:00
|
|
|
}
|
2023-05-16 00:06:57 +02:00
|
|
|
material.desiredSellAmount = q; //Use sanitized input
|
|
|
|
} else if (isNaN(parseFloat(amount)) || parseFloat(amount) < 0) {
|
2022-03-16 12:08:00 +01:00
|
|
|
throw new Error("Invalid value for sell quantity field! Must be numeric or 'PROD' or 'MAX'");
|
2021-09-05 01:09:30 +02:00
|
|
|
} else {
|
2023-05-16 00:06:57 +02:00
|
|
|
let q = parseFloat(amount);
|
2021-09-05 01:09:30 +02:00
|
|
|
if (isNaN(q)) {
|
|
|
|
q = 0;
|
|
|
|
}
|
2023-05-16 00:06:57 +02:00
|
|
|
material.desiredSellAmount = q;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-02 06:36:33 +02:00
|
|
|
|
2023-05-16 00:06:57 +02:00
|
|
|
export function SellProduct(product: Product, city: CityName, amt: string, price: string, all: boolean): void {
|
2021-09-05 01:09:30 +02:00
|
|
|
//Parse price
|
2023-09-12 10:31:51 +02:00
|
|
|
// initliaze newPrice with oldPrice as default
|
|
|
|
let newPrice = product.cityData[city].desiredSellPrice;
|
2021-09-05 01:09:30 +02:00
|
|
|
if (price.includes("MP")) {
|
|
|
|
//Dynamically evaluated quantity. First test to make sure its valid
|
|
|
|
//Sanitize input, then replace dynamic variables with arbitrary numbers
|
|
|
|
price = price.replace(/\s+/g, "");
|
2022-10-25 14:51:37 +02:00
|
|
|
price = price.replace(/[^-()\d/*+.MPe]/g, "");
|
2022-10-25 18:51:03 +02:00
|
|
|
let temp = price.replace(/MP/, "1.234e5");
|
2021-09-05 01:09:30 +02:00
|
|
|
try {
|
2022-10-25 14:51:37 +02:00
|
|
|
if (temp.includes("MP")) throw "Only one reference to MP is allowed in sell price.";
|
2021-09-05 01:09:30 +02:00
|
|
|
temp = eval(temp);
|
|
|
|
} catch (e) {
|
2022-03-16 12:08:00 +01:00
|
|
|
throw new Error("Invalid value or expression for sell price field: " + e);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2022-10-25 18:51:03 +02:00
|
|
|
if (temp == null || isNaN(parseFloat(temp))) {
|
2022-03-16 12:08:00 +01:00
|
|
|
throw new Error("Invalid value or expression for sell price field.");
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2023-09-12 10:31:51 +02:00
|
|
|
newPrice = price; //Use sanitized price
|
2021-09-05 01:09:30 +02:00
|
|
|
} else {
|
|
|
|
const cost = parseFloat(price);
|
|
|
|
if (isNaN(cost)) {
|
|
|
|
throw new Error("Invalid value for sell price field");
|
|
|
|
}
|
2023-09-12 10:31:51 +02:00
|
|
|
newPrice = cost;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-09-02 06:36:33 +02:00
|
|
|
|
2021-09-05 01:09:30 +02:00
|
|
|
// Parse quantity
|
2021-10-12 04:54:28 +02:00
|
|
|
amt = amt.toUpperCase();
|
2023-09-12 10:31:51 +02:00
|
|
|
//initialize newAmount with old as default
|
|
|
|
let newAmount = product.cityData[city].desiredSellAmount;
|
2023-03-18 02:12:43 +01:00
|
|
|
if (amt.includes("MAX") || amt.includes("PROD") || amt.includes("INV")) {
|
2021-09-05 01:09:30 +02:00
|
|
|
//Dynamically evaluated quantity. First test to make sure its valid
|
|
|
|
let qty = amt.replace(/\s+/g, "");
|
2023-03-18 02:12:43 +01:00
|
|
|
qty = qty.replace(/[^-()\d/*+.MAXPRODINV]/g, "");
|
2023-05-16 00:06:57 +02:00
|
|
|
let temp = qty.replace(/MAX/g, product.maxSellAmount.toString());
|
|
|
|
temp = temp.replace(/PROD/g, product.cityData[city].productionAmount.toString());
|
|
|
|
temp = temp.replace(/INV/g, product.cityData[city].stored.toString());
|
2021-09-05 01:09:30 +02:00
|
|
|
try {
|
|
|
|
temp = eval(temp);
|
|
|
|
} catch (e) {
|
2022-03-16 12:08:00 +01:00
|
|
|
throw new Error("Invalid value or expression for sell quantity field: " + e);
|
2021-09-02 06:36:33 +02:00
|
|
|
}
|
|
|
|
|
2023-03-18 02:12:43 +01:00
|
|
|
if (temp == null || isNaN(parseFloat(temp))) {
|
2022-03-16 12:08:00 +01:00
|
|
|
throw new Error("Invalid value or expression for sell quantity field");
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2023-09-12 10:31:51 +02:00
|
|
|
newAmount = qty; //Use sanitized input
|
2022-01-13 18:47:11 +01:00
|
|
|
} else if (isNaN(parseFloat(amt)) || parseFloat(amt) < 0) {
|
2022-03-16 12:08:00 +01:00
|
|
|
throw new Error("Invalid value for sell quantity field! Must be numeric or 'PROD' or 'MAX'");
|
2021-09-05 01:09:30 +02:00
|
|
|
} else {
|
|
|
|
let qty = parseFloat(amt);
|
|
|
|
if (isNaN(qty)) {
|
|
|
|
qty = 0;
|
|
|
|
}
|
2023-09-12 10:31:51 +02:00
|
|
|
newAmount = qty;
|
|
|
|
}
|
|
|
|
//apply new price and amount to all or just current
|
|
|
|
if (all) {
|
|
|
|
for (const cityName of Object.values(CityName)) {
|
|
|
|
product.cityData[cityName].desiredSellAmount = newAmount;
|
|
|
|
product.cityData[cityName].desiredSellPrice = newPrice;
|
2022-03-30 17:34:03 +02:00
|
|
|
}
|
2023-09-12 10:31:51 +02:00
|
|
|
} else {
|
|
|
|
product.cityData[city].desiredSellAmount = newAmount;
|
|
|
|
product.cityData[city].desiredSellPrice = newPrice;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-09-02 06:36:33 +02:00
|
|
|
}
|
|
|
|
|
2021-09-09 05:47:34 +02:00
|
|
|
export function SetSmartSupply(warehouse: Warehouse, smartSupply: boolean): void {
|
2021-09-05 01:09:30 +02:00
|
|
|
warehouse.smartSupplyEnabled = smartSupply;
|
2021-09-03 22:02:41 +02:00
|
|
|
}
|
|
|
|
|
2023-06-10 01:34:35 +02:00
|
|
|
export function SetSmartSupplyOption(warehouse: Warehouse, material: Material, useOption: CorpSmartSupplyOption): void {
|
2023-03-18 02:12:43 +01:00
|
|
|
warehouse.smartSupplyOptions[material.name] = useOption;
|
2021-09-10 08:17:55 +02:00
|
|
|
}
|
|
|
|
|
2023-07-06 01:36:22 +02:00
|
|
|
export function BuyMaterial(division: Division, material: Material, amt: number): void {
|
|
|
|
if (!isRelevantMaterial(material.name, division)) {
|
|
|
|
throw new Error(`${material.name} is not a relevant material for industry ${division.type}`);
|
|
|
|
}
|
2022-01-13 18:47:11 +01:00
|
|
|
if (isNaN(amt) || amt < 0) {
|
2021-09-09 05:47:34 +02:00
|
|
|
throw new Error(`Invalid amount '${amt}' to buy material '${material.name}'`);
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2023-05-16 00:06:57 +02:00
|
|
|
material.buyAmount = amt;
|
2021-09-05 01:09:30 +02:00
|
|
|
}
|
2021-09-10 06:13:28 +02:00
|
|
|
|
2023-07-06 01:36:22 +02:00
|
|
|
export function BulkPurchase(
|
|
|
|
corp: Corporation,
|
|
|
|
division: Division,
|
|
|
|
warehouse: Warehouse,
|
|
|
|
material: Material,
|
|
|
|
amt: number,
|
|
|
|
): void {
|
|
|
|
if (!isRelevantMaterial(material.name, division)) {
|
|
|
|
throw new Error(`${material.name} is not a relevant material for industry ${division.type}`);
|
|
|
|
}
|
2022-12-30 02:28:53 +01:00
|
|
|
const matSize = MaterialInfo[material.name].size;
|
2022-01-17 11:03:29 +01:00
|
|
|
const maxAmount = (warehouse.size - warehouse.sizeUsed) / matSize;
|
|
|
|
if (isNaN(amt) || amt < 0) {
|
|
|
|
throw new Error(`Invalid input amount`);
|
|
|
|
}
|
2022-04-02 14:17:30 +02:00
|
|
|
if (amt > maxAmount) {
|
2022-01-17 11:03:29 +01:00
|
|
|
throw new Error(`You do not have enough warehouse size to fit this purchase`);
|
|
|
|
}
|
2023-05-16 00:06:57 +02:00
|
|
|
const cost = amt * material.marketPrice;
|
2022-01-17 11:03:29 +01:00
|
|
|
if (corp.funds >= cost) {
|
|
|
|
corp.funds = corp.funds - cost;
|
2023-05-16 00:06:57 +02:00
|
|
|
material.stored += amt;
|
2022-01-17 11:03:29 +01:00
|
|
|
} else {
|
|
|
|
throw new Error(`You cannot afford this purchase.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-20 12:47:54 +02:00
|
|
|
export function SellShares(corporation: Corporation, numShares: number): number {
|
2023-03-18 02:12:43 +01:00
|
|
|
if (isNaN(numShares) || !isInteger(numShares)) throw new Error("Invalid value for number of shares");
|
|
|
|
if (numShares <= 0) throw new Error("Invalid value for number of shares");
|
2022-01-17 11:03:29 +01:00
|
|
|
if (numShares > corporation.numShares) throw new Error("You don't have that many shares to sell!");
|
2023-03-18 02:12:43 +01:00
|
|
|
if (numShares === corporation.numShares) throw new Error("You cant't sell all your shares!");
|
|
|
|
if (numShares > 1e14) throw new Error("Invalid value for number of shares");
|
2022-01-17 11:03:29 +01:00
|
|
|
if (!corporation.public) throw new Error("You haven't gone public!");
|
2022-01-17 13:00:19 +01:00
|
|
|
if (corporation.shareSaleCooldown) throw new Error("Share sale on cooldown!");
|
2022-01-17 11:03:29 +01:00
|
|
|
const stockSaleResults = corporation.calculateShareSale(numShares);
|
|
|
|
const profit = stockSaleResults[0];
|
|
|
|
const newSharePrice = stockSaleResults[1];
|
|
|
|
const newSharesUntilUpdate = stockSaleResults[2];
|
|
|
|
|
|
|
|
corporation.numShares -= numShares;
|
|
|
|
corporation.issuedShares += numShares;
|
|
|
|
corporation.sharePrice = newSharePrice;
|
|
|
|
corporation.shareSalesUntilPriceUpdate = newSharesUntilUpdate;
|
2022-12-30 02:28:53 +01:00
|
|
|
corporation.shareSaleCooldown = corpConstants.sellSharesCooldown;
|
2022-09-06 15:07:12 +02:00
|
|
|
Player.gainMoney(profit, "corporation");
|
2022-01-17 11:03:29 +01:00
|
|
|
return profit;
|
|
|
|
}
|
|
|
|
|
2022-09-20 12:47:54 +02:00
|
|
|
export function BuyBackShares(corporation: Corporation, numShares: number): boolean {
|
2023-03-18 02:12:43 +01:00
|
|
|
if (isNaN(numShares) || !isInteger(numShares)) throw new Error("Invalid value for number of shares");
|
|
|
|
if (numShares <= 0) throw new Error("Invalid value for number of shares");
|
2022-01-17 11:03:29 +01:00
|
|
|
if (numShares > corporation.issuedShares) throw new Error("You don't have that many shares to buy!");
|
|
|
|
if (!corporation.public) throw new Error("You haven't gone public!");
|
|
|
|
const buybackPrice = corporation.sharePrice * 1.1;
|
2022-09-06 15:07:12 +02:00
|
|
|
if (Player.money < numShares * buybackPrice) throw new Error("You cant afford that many shares!");
|
2022-01-17 11:03:29 +01:00
|
|
|
corporation.numShares += numShares;
|
|
|
|
corporation.issuedShares -= numShares;
|
2022-09-06 15:07:12 +02:00
|
|
|
Player.loseMoney(numShares * buybackPrice, "corporation");
|
2022-01-17 11:03:29 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-09-20 12:47:54 +02:00
|
|
|
export function UpgradeOfficeSize(corp: Corporation, office: OfficeSpace, size: number): void {
|
2022-12-30 02:28:53 +01:00
|
|
|
const initialPriceMult = Math.round(office.size / corpConstants.officeInitialSize);
|
2021-09-10 06:13:28 +02:00
|
|
|
const costMultiplier = 1.09;
|
|
|
|
// Calculate cost to upgrade size by 15 employees
|
|
|
|
let mult = 0;
|
2022-12-30 02:28:53 +01:00
|
|
|
for (let i = 0; i < size / corpConstants.officeInitialSize; ++i) {
|
2021-09-10 06:13:28 +02:00
|
|
|
mult += Math.pow(costMultiplier, initialPriceMult + i);
|
|
|
|
}
|
2022-12-30 02:28:53 +01:00
|
|
|
const cost = corpConstants.officeInitialCost * mult;
|
2021-11-12 03:35:26 +01:00
|
|
|
if (corp.funds < cost) return;
|
2021-09-10 06:13:28 +02:00
|
|
|
office.size += size;
|
2023-09-19 14:47:16 +02:00
|
|
|
corp.addNonIncomeFunds(-cost);
|
2021-09-10 06:13:28 +02:00
|
|
|
}
|
|
|
|
|
2023-03-18 02:12:43 +01:00
|
|
|
export function BuyTea(corp: Corporation, office: OfficeSpace): boolean {
|
|
|
|
const cost = office.getTeaCost();
|
|
|
|
if (corp.funds < cost || !office.setTea()) return false;
|
2022-06-02 00:26:32 +02:00
|
|
|
corp.funds -= cost;
|
2022-06-02 00:28:14 +02:00
|
|
|
return true;
|
2022-06-02 00:26:32 +02:00
|
|
|
}
|
|
|
|
|
2022-09-20 12:47:54 +02:00
|
|
|
export function ThrowParty(corp: Corporation, office: OfficeSpace, costPerEmployee: number): number {
|
2022-06-02 00:26:32 +02:00
|
|
|
const mult = 1 + costPerEmployee / 10e6;
|
2023-05-16 00:06:57 +02:00
|
|
|
const cost = costPerEmployee * office.numEmployees;
|
2022-06-02 23:51:36 +02:00
|
|
|
if (corp.funds < cost) {
|
|
|
|
return 0;
|
|
|
|
}
|
2022-06-02 00:26:32 +02:00
|
|
|
|
2022-06-02 23:51:36 +02:00
|
|
|
if (!office.setParty(mult)) {
|
|
|
|
return 0;
|
|
|
|
}
|
2022-06-02 00:26:32 +02:00
|
|
|
corp.funds -= cost;
|
2021-09-10 06:13:28 +02:00
|
|
|
|
|
|
|
return mult;
|
|
|
|
}
|
|
|
|
|
2023-05-16 00:06:57 +02:00
|
|
|
export function purchaseWarehouse(corp: Corporation, division: Division, city: CityName): void {
|
2022-12-30 02:28:53 +01:00
|
|
|
if (corp.funds < corpConstants.warehouseInitialCost) return;
|
2022-09-27 21:14:34 +02:00
|
|
|
if (division.warehouses[city]) return;
|
2023-09-19 14:47:16 +02:00
|
|
|
corp.addNonIncomeFunds(-corpConstants.warehouseInitialCost);
|
2021-09-10 06:13:28 +02:00
|
|
|
division.warehouses[city] = new Warehouse({
|
2023-05-16 00:06:57 +02:00
|
|
|
division: division,
|
2021-09-10 06:13:28 +02:00
|
|
|
loc: city,
|
2022-12-30 02:28:53 +01:00
|
|
|
size: corpConstants.warehouseInitialSize,
|
2021-09-10 06:13:28 +02:00
|
|
|
});
|
|
|
|
}
|
2021-09-10 06:53:39 +02:00
|
|
|
|
2022-04-01 17:19:08 +02:00
|
|
|
export function UpgradeWarehouseCost(warehouse: Warehouse, amt: number): number {
|
|
|
|
return Array.from(Array(amt).keys()).reduce(
|
2022-12-30 02:28:53 +01:00
|
|
|
(acc, index) => acc + corpConstants.warehouseSizeUpgradeCostBase * Math.pow(1.07, warehouse.level + 1 + index),
|
2022-04-01 17:19:08 +02:00
|
|
|
0,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-05-16 00:06:57 +02:00
|
|
|
export function UpgradeWarehouse(corp: Corporation, division: Division, warehouse: Warehouse, amt = 1): void {
|
2022-04-01 17:19:08 +02:00
|
|
|
const sizeUpgradeCost = UpgradeWarehouseCost(warehouse, amt);
|
2022-02-11 16:48:19 +01:00
|
|
|
if (corp.funds < sizeUpgradeCost) return;
|
2022-04-01 17:19:08 +02:00
|
|
|
warehouse.level += amt;
|
2021-09-10 06:53:39 +02:00
|
|
|
warehouse.updateSize(corp, division);
|
2023-09-19 14:47:16 +02:00
|
|
|
corp.addNonIncomeFunds(-sizeUpgradeCost);
|
2021-09-10 06:53:39 +02:00
|
|
|
}
|
|
|
|
|
2023-05-16 00:06:57 +02:00
|
|
|
export function HireAdVert(corp: Corporation, division: Division): void {
|
2022-06-02 03:43:22 +02:00
|
|
|
const cost = division.getAdVertCost();
|
2021-11-12 03:35:26 +01:00
|
|
|
if (corp.funds < cost) return;
|
|
|
|
corp.funds = corp.funds - cost;
|
2022-06-02 03:43:22 +02:00
|
|
|
division.applyAdVert(corp);
|
2021-09-10 06:53:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export function MakeProduct(
|
2022-09-20 12:47:54 +02:00
|
|
|
corp: Corporation,
|
2023-05-16 00:06:57 +02:00
|
|
|
division: Division,
|
2022-10-25 16:32:20 +02:00
|
|
|
city: CityName,
|
2021-09-10 06:53:39 +02:00
|
|
|
productName: string,
|
|
|
|
designInvest: number,
|
|
|
|
marketingInvest: number,
|
|
|
|
): void {
|
2023-06-07 06:30:10 +02:00
|
|
|
// For invalid investment inputs, just use 0
|
|
|
|
if (isNaN(designInvest) || designInvest < 0) designInvest = 0;
|
|
|
|
if (isNaN(marketingInvest) || marketingInvest < 0) marketingInvest = 0;
|
|
|
|
|
|
|
|
if (!division.offices[city]) {
|
|
|
|
throw new Error(`Cannot develop a product in a city without an office!`);
|
2021-09-10 06:53:39 +02:00
|
|
|
}
|
|
|
|
if (productName == null || productName === "") {
|
|
|
|
throw new Error("You must specify a name for your product!");
|
|
|
|
}
|
2022-01-17 10:58:09 +01:00
|
|
|
if (!division.makesProducts) {
|
|
|
|
throw new Error("You cannot create products for this industry!");
|
|
|
|
}
|
2021-11-12 03:35:26 +01:00
|
|
|
if (corp.funds < designInvest + marketingInvest) {
|
2021-09-10 06:53:39 +02:00
|
|
|
throw new Error("You don't have enough company funds to make this large of an investment");
|
|
|
|
}
|
2023-06-01 21:20:54 +02:00
|
|
|
if (division.products.size >= division.maxProducts) {
|
|
|
|
throw new Error(`You are already at the max products (${division.maxProducts}) for division: ${division.name}!`);
|
2022-02-11 16:48:19 +01:00
|
|
|
}
|
|
|
|
|
2021-09-10 06:53:39 +02:00
|
|
|
const product = new Product({
|
2023-06-07 06:30:10 +02:00
|
|
|
name: productName.replace(/[<>]/g, "").trim(), //Sanitize for HTMl elements?
|
2021-09-10 06:53:39 +02:00
|
|
|
createCity: city,
|
2023-05-16 00:06:57 +02:00
|
|
|
designInvestment: designInvest,
|
|
|
|
advertisingInvestment: marketingInvest,
|
2021-09-10 06:53:39 +02:00
|
|
|
});
|
2023-05-16 00:06:57 +02:00
|
|
|
if (division.products.has(product.name)) {
|
2021-09-10 06:53:39 +02:00
|
|
|
throw new Error(`You already have a product with this name!`);
|
|
|
|
}
|
2022-02-11 16:48:19 +01:00
|
|
|
|
2021-11-12 03:35:26 +01:00
|
|
|
corp.funds = corp.funds - (designInvest + marketingInvest);
|
2023-05-16 00:06:57 +02:00
|
|
|
division.products.set(product.name, product);
|
2021-09-10 06:53:39 +02:00
|
|
|
}
|
2021-09-10 08:17:55 +02:00
|
|
|
|
2023-05-31 00:47:48 +02:00
|
|
|
export function Research(researchingDivision: Division, researchName: CorpResearchName): void {
|
2023-05-16 00:06:57 +02:00
|
|
|
const corp = Player.corporation;
|
|
|
|
if (!corp) return;
|
2023-05-31 00:47:48 +02:00
|
|
|
const researchTree = IndustryResearchTrees[researchingDivision.type];
|
|
|
|
if (researchTree === undefined) throw new Error(`No research tree for industry '${researchingDivision.type}'`);
|
2021-09-10 08:17:55 +02:00
|
|
|
const research = ResearchMap[researchName];
|
|
|
|
|
2023-05-31 00:47:48 +02:00
|
|
|
if (researchingDivision.researched.has(researchName)) return;
|
|
|
|
if (researchingDivision.researchPoints < research.cost) {
|
2021-09-10 08:17:55 +02:00
|
|
|
throw new Error(`You do not have enough Scientific Research for ${research.name}`);
|
2023-05-31 00:47:48 +02:00
|
|
|
}
|
|
|
|
researchingDivision.researchPoints -= research.cost;
|
2021-09-10 08:17:55 +02:00
|
|
|
|
|
|
|
// Get the Node from the Research Tree and set its 'researched' property
|
|
|
|
researchTree.research(researchName);
|
2023-05-31 00:47:48 +02:00
|
|
|
// All divisions of the same type as the researching division get the new research.
|
|
|
|
for (const division of corp.divisions.values()) {
|
|
|
|
if (division.type !== researchingDivision.type) continue;
|
|
|
|
division.researched.add(researchName);
|
|
|
|
// Handle researches that need to have their effects manually applied here.
|
|
|
|
// Warehouse size needs to be updated here because it is not recalculated during normal processing.
|
|
|
|
if (researchName == "Drones - Transport") {
|
|
|
|
for (const warehouse of getRecordValues(division.warehouses)) {
|
|
|
|
warehouse.updateSize(corp, division);
|
|
|
|
}
|
2022-09-23 11:54:04 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-10 08:17:55 +02:00
|
|
|
}
|
|
|
|
|
2023-06-07 05:50:23 +02:00
|
|
|
/** Set a new export for a material. Throw on any invalid input. */
|
2022-03-30 17:34:03 +02:00
|
|
|
export function ExportMaterial(
|
2023-06-07 05:50:23 +02:00
|
|
|
targetDivision: Division,
|
|
|
|
targetCity: CityName,
|
2022-03-30 17:34:03 +02:00
|
|
|
material: Material,
|
2023-06-07 05:50:23 +02:00
|
|
|
amount: string,
|
2022-03-30 17:34:03 +02:00
|
|
|
): void {
|
2023-06-07 05:50:23 +02:00
|
|
|
if (!isRelevantMaterial(material.name, targetDivision)) {
|
|
|
|
throw new Error(`You cannot export material: ${material.name} to division: ${targetDivision.name}!`);
|
2021-09-10 08:17:55 +02:00
|
|
|
}
|
2023-06-07 05:50:23 +02:00
|
|
|
if (!targetDivision.warehouses[targetCity]) {
|
|
|
|
throw new Error(`Cannot export to ${targetCity} in division ${targetDivision.name} because there is no warehouse.`);
|
|
|
|
}
|
|
|
|
if (material === targetDivision.warehouses[targetCity]?.materials[material.name]) {
|
|
|
|
throw new Error(`Source and target division/city cannot be the same.`);
|
|
|
|
}
|
|
|
|
for (const existingExport of material.exports) {
|
|
|
|
if (existingExport.division === targetDivision.name && existingExport.city === targetCity) {
|
|
|
|
throw new Error(`Tried to initialize an export to a duplicate warehouse.
|
|
|
|
Target warehouse (division / city): ${existingExport.division} / ${existingExport.city}
|
|
|
|
Existing export amount: ${existingExport.amount}
|
|
|
|
Attempted export amount: ${amount}`);
|
|
|
|
}
|
2021-09-10 08:17:55 +02:00
|
|
|
}
|
2022-02-11 16:48:19 +01:00
|
|
|
|
2023-06-07 05:50:23 +02:00
|
|
|
// Perform sanitization and tests
|
|
|
|
let sanitizedAmt = amount.replace(/\s+/g, "").toUpperCase();
|
|
|
|
sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAXEPRODINV]/g, "");
|
|
|
|
for (const testReplacement of ["(1.23)", "(-1.23)"]) {
|
2023-06-07 19:48:23 +02:00
|
|
|
const replaced = sanitizedAmt.replace(/(MAX|IPROD|EPROD|IINV|EINV)/g, testReplacement);
|
2023-06-07 05:50:23 +02:00
|
|
|
let evaluated, error;
|
|
|
|
try {
|
|
|
|
evaluated = eval(replaced);
|
|
|
|
} catch (e) {
|
|
|
|
error = e;
|
|
|
|
}
|
|
|
|
if (!error && isNaN(evaluated)) error = "evaluated value is NaN";
|
|
|
|
if (error) {
|
|
|
|
throw new Error(`Error while trying to set the exported amount of ${material.name}.
|
|
|
|
Error occurred while testing keyword replacement with ${testReplacement}.
|
|
|
|
Your input: ${amount}
|
|
|
|
Sanitized input: ${sanitizedAmt}
|
|
|
|
Input after replacement: ${replaced}
|
|
|
|
Evaluated value: ${evaluated}
|
|
|
|
Error encountered: ${error}`);
|
|
|
|
}
|
2022-02-11 16:48:19 +01:00
|
|
|
}
|
|
|
|
|
2023-06-07 05:50:23 +02:00
|
|
|
const exportObj = { division: targetDivision.name, city: targetCity, amount: sanitizedAmt };
|
2023-05-16 00:06:57 +02:00
|
|
|
material.exports.push(exportObj);
|
2021-09-10 08:17:55 +02:00
|
|
|
}
|
|
|
|
|
2023-06-07 05:50:23 +02:00
|
|
|
export function CancelExportMaterial(divisionName: string, cityName: CityName, material: Material): void {
|
|
|
|
const index = material.exports.findIndex((exp) => exp.division === divisionName && exp.city === cityName);
|
|
|
|
if (index === -1) return;
|
|
|
|
material.exports.splice(index, 1);
|
2021-09-10 08:17:55 +02:00
|
|
|
}
|
|
|
|
|
2023-05-16 00:06:57 +02:00
|
|
|
export function LimitProductProduction(product: Product, cityName: CityName, quantity: number): void {
|
|
|
|
if (quantity < 0 || isNaN(quantity)) {
|
|
|
|
product.cityData[cityName].productionLimit = null;
|
2021-09-10 08:17:55 +02:00
|
|
|
} else {
|
2023-05-16 00:06:57 +02:00
|
|
|
product.cityData[cityName].productionLimit = quantity;
|
2021-09-10 08:17:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-16 00:06:57 +02:00
|
|
|
export function LimitMaterialProduction(material: Material, quantity: number): void {
|
|
|
|
if (quantity < 0 || isNaN(quantity)) {
|
|
|
|
material.productionLimit = null;
|
2022-04-01 16:28:48 +02:00
|
|
|
} else {
|
2023-05-16 00:06:57 +02:00
|
|
|
material.productionLimit = quantity;
|
2022-04-01 16:28:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-10 08:17:55 +02:00
|
|
|
export function SetMaterialMarketTA1(material: Material, on: boolean): void {
|
|
|
|
material.marketTa1 = on;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function SetMaterialMarketTA2(material: Material, on: boolean): void {
|
|
|
|
material.marketTa2 = on;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function SetProductMarketTA1(product: Product, on: boolean): void {
|
|
|
|
product.marketTa1 = on;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function SetProductMarketTA2(product: Product, on: boolean): void {
|
|
|
|
product.marketTa2 = on;
|
|
|
|
}
|