bitburner-src/src/Corporation/Actions.ts

524 lines
18 KiB
TypeScript
Raw Normal View History

import { IPlayer } from "src/PersonObjects/IPlayer";
import { MaterialSizes } from "./MaterialSizes";
2021-09-02 04:16:48 +02:00
import { ICorporation } from "./ICorporation";
import { IIndustry } from "./IIndustry";
2021-09-13 00:03:07 +02:00
import { IndustryStartingCosts, IndustryResearchTrees } from "./IndustryData";
2021-09-02 04:16:48 +02:00
import { Industry } from "./Industry";
import { CorporationConstants } from "./data/Constants";
import { OfficeSpace } from "./OfficeSpace";
2021-09-02 06:36:33 +02:00
import { Material } from "./Material";
import { Product } from "./Product";
import { Warehouse } from "./Warehouse";
2021-09-02 04:16:48 +02:00
import { CorporationUnlockUpgrade } from "./data/CorporationUnlockUpgrades";
import { CorporationUpgrade } from "./data/CorporationUpgrades";
2021-09-02 06:36:33 +02:00
import { Cities } from "../Locations/Cities";
2021-09-10 06:13:28 +02:00
import { EmployeePositions } from "./EmployeePositions";
import { Employee } from "./Employee";
import { IndustryUpgrades } from "./IndustryUpgrades";
2021-09-10 08:17:55 +02:00
import { ResearchMap } from "./ResearchMap";
import { isRelevantMaterial } from "./ui/Helpers";
import { CityName } from "src/Locations/data/CityNames";
2021-09-02 04:16:48 +02:00
2021-09-09 05:47:34 +02:00
export function NewIndustry(corporation: ICorporation, industry: string, name: string): void {
if (corporation.divisions.find(({ type }) => industry == type))
throw new Error(`You have already expanded into the ${industry} industry!`);
2021-09-05 01:09:30 +02:00
for (let i = 0; i < corporation.divisions.length; ++i) {
if (corporation.divisions[i].name === name) {
throw new Error("This division name is already in use!");
2021-09-02 04:16:48 +02:00
}
2021-09-05 01:09:30 +02:00
}
2021-09-02 04:16:48 +02:00
2021-09-05 01:09:30 +02:00
const cost = IndustryStartingCosts[industry];
if (cost === undefined) {
2021-09-06 21:06:08 +02:00
throw new Error(`Invalid industry: '${industry}'`);
2021-09-05 01:09:30 +02:00
}
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;
2021-09-05 01:09:30 +02:00
corporation.divisions.push(
new Industry({
corp: corporation,
name: name,
type: industry,
}),
);
}
2021-09-02 04:16:48 +02:00
}
2021-09-09 05:47:34 +02:00
export function NewCity(corporation: ICorporation, division: IIndustry, city: string): void {
2021-11-12 03:35:26 +01:00
if (corporation.funds < CorporationConstants.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
}
if (division.offices[city]) {
throw new Error(`You have already expanded into ${city} for ${division.name}`);
}
corporation.funds = corporation.funds - CorporationConstants.OfficeInitialCost;
division.offices[city] = new OfficeSpace({
loc: city,
size: CorporationConstants.OfficeInitialSize,
});
2021-09-02 04:16:48 +02:00
}
2021-09-09 05:47:34 +02:00
export function UnlockUpgrade(corporation: ICorporation, upgrade: CorporationUnlockUpgrade): void {
2021-11-12 03:35:26 +01:00
if (corporation.funds < upgrade[1]) {
2021-09-05 01:09:30 +02:00
throw new Error("Insufficient funds");
}
if (corporation.unlockUpgrades[upgrade[0]] === 1) {
2022-03-19 20:53:28 +01:00
throw new Error(`You have already unlocked the ${upgrade[2]} upgrade!`);
}
2021-09-05 01:09:30 +02:00
corporation.unlock(upgrade);
2021-09-02 04:16:48 +02:00
}
2021-09-09 05:47:34 +02:00
export function LevelUpgrade(corporation: ICorporation, upgrade: CorporationUpgrade): void {
2021-09-05 01:09:30 +02:00
const baseCost = upgrade[1];
const priceMult = upgrade[2];
const level = corporation.upgrades[upgrade[0]];
const cost = baseCost * Math.pow(priceMult, level);
2021-11-12 03:35:26 +01:00
if (corporation.funds < cost) {
2021-09-05 01:09:30 +02:00
throw new Error("Insufficient funds");
} else {
corporation.upgrade(upgrade);
}
2021-09-02 04:16:48 +02:00
}
2021-09-09 05:47:34 +02:00
export function IssueDividends(corporation: ICorporation, percent: number): void {
if (isNaN(percent) || percent < 0 || percent > CorporationConstants.DividendMaxPercentage) {
throw new Error(`Invalid value. Must be an integer between 0 and ${CorporationConstants.DividendMaxPercentage}`);
2021-09-05 01:09:30 +02:00
}
2021-09-02 04:16:48 +02:00
2021-09-05 01:09:30 +02:00
corporation.dividendPercentage = percent * 100;
2021-09-02 06:36:33 +02:00
}
export function SellMaterial(mat: Material, amt: string, price: string): void {
2021-09-05 01:09:30 +02:00
if (price === "") price = "0";
if (amt === "") amt = "0";
let cost = price.replace(/\s+/g, "");
2021-11-16 05:49:33 +01:00
cost = cost.replace(/[^-()\d/*+.MPe]/g, ""); //Sanitize cost
2021-09-05 01:09:30 +02:00
let temp = cost.replace(/MP/g, mat.bCost + "");
try {
temp = eval(temp);
} catch (e) {
throw new Error("Invalid value or expression for sell price field: " + e);
}
if (temp == null || isNaN(parseFloat(temp)) || parseFloat(temp) < 0) {
2021-09-05 01:09:30 +02:00
throw new Error("Invalid value or expression for sell price field");
}
if (cost.includes("MP")) {
mat.sCost = cost; //Dynamically evaluated
} else {
mat.sCost = temp;
}
//Parse quantity
2021-10-12 04:54:28 +02:00
amt = amt.toUpperCase();
2021-09-05 01:09:30 +02:00
if (amt.includes("MAX") || amt.includes("PROD")) {
let q = amt.replace(/\s+/g, "");
q = q.replace(/[^-()\d/*+.MAXPROD]/g, "");
let tempQty = q.replace(/MAX/g, mat.maxsll.toString());
tempQty = tempQty.replace(/PROD/g, mat.prd.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
}
if (tempQty == null || isNaN(parseFloat(tempQty)) || parseFloat(tempQty) < 0) {
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
}
2021-09-05 01:09:30 +02:00
mat.sllman[0] = true;
mat.sllman[1] = q; //Use sanitized input
} 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 q = parseFloat(amt);
if (isNaN(q)) {
q = 0;
}
if (q === 0) {
mat.sllman[0] = false;
mat.sllman[1] = 0;
2021-09-02 06:36:33 +02:00
} else {
2021-09-05 01:09:30 +02:00
mat.sllman[0] = true;
mat.sllman[1] = q;
2021-09-02 06:36:33 +02:00
}
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 SellProduct(product: Product, city: string, amt: string, price: string, all: boolean): void {
2021-09-05 01:09:30 +02:00
//Parse price
if (price.includes("MP")) {
//Dynamically evaluated quantity. First test to make sure its valid
//Sanitize input, then replace dynamic variables with arbitrary numbers
price = price.replace(/\s+/g, "");
price = price.replace(/[^-()\d/*+.MP]/g, "");
let temp = price.replace(/MP/g, "1");
try {
temp = eval(temp);
} catch (e) {
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
}
if (temp == null || isNaN(parseFloat(temp)) || parseFloat(temp) < 0) {
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
}
product.sCost = price; //Use sanitized price
} else {
const cost = parseFloat(price);
if (isNaN(cost)) {
throw new Error("Invalid value for sell price field");
}
product.sCost = cost;
}
2021-09-02 06:36:33 +02:00
2021-09-05 01:09:30 +02:00
// Array of all cities. Used later
const cities = Object.keys(Cities);
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();
2021-09-05 01:09:30 +02:00
if (amt.includes("MAX") || amt.includes("PROD")) {
//Dynamically evaluated quantity. First test to make sure its valid
let qty = amt.replace(/\s+/g, "");
qty = qty.replace(/[^-()\d/*+.MAXPROD]/g, "");
let temp = qty.replace(/MAX/g, product.maxsll.toString());
temp = temp.replace(/PROD/g, product.data[city][1].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
}
if (temp == null || isNaN(parseFloat(temp)) || parseFloat(temp) < 0) {
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
}
if (all) {
for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = true;
product.sllman[tempCity][1] = qty; //Use sanitized input
}
2021-09-02 06:36:33 +02:00
} else {
2021-09-05 01:09:30 +02:00
product.sllman[city][0] = true;
product.sllman[city][1] = qty; //Use sanitized input
2021-09-02 06:36:33 +02: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;
}
if (qty === 0) {
if (all) {
for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = false;
product.sllman[tempCity][1] = "";
2021-09-02 06:36:33 +02:00
}
2021-09-05 01:09:30 +02:00
} else {
product.sllman[city][0] = false;
product.sllman[city][1] = "";
}
} else if (all) {
for (let i = 0; i < cities.length; ++i) {
const tempCity = cities[i];
product.sllman[tempCity][0] = true;
product.sllman[tempCity][1] = qty;
2021-09-05 01:09:30 +02:00
}
} else {
product.sllman[city][0] = true;
product.sllman[city][1] = qty;
}
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
}
2021-09-10 08:17:55 +02:00
export function SetSmartSupplyUseLeftovers(warehouse: Warehouse, material: Material, useLeftover: boolean): void {
2022-01-13 23:21:05 +01:00
if (!Object.keys(warehouse.smartSupplyUseLeftovers).includes(material.name.replace(/ /g, "")))
2021-09-10 08:17:55 +02:00
throw new Error(`Invalid material '${material.name}'`);
2022-01-13 23:21:05 +01:00
warehouse.smartSupplyUseLeftovers[material.name.replace(/ /g, "")] = useLeftover;
2021-09-10 08:17:55 +02:00
}
2021-09-03 22:02:41 +02:00
export function BuyMaterial(material: Material, amt: number): void {
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
}
material.buy = amt;
}
2021-09-10 06:13:28 +02:00
export function BulkPurchase(corp: ICorporation, warehouse: Warehouse, material: Material, amt: number): void {
const matSize = MaterialSizes[material.name];
const maxAmount = (warehouse.size - warehouse.sizeUsed) / matSize;
if (isNaN(amt) || amt < 0) {
throw new Error(`Invalid input amount`);
}
2022-03-30 17:33:45 +02:00
if (amt * matSize <= maxAmount) {
throw new Error(`You do not have enough warehouse size to fit this purchase`);
}
const cost = amt * material.bCost;
if (corp.funds >= cost) {
corp.funds = corp.funds - cost;
material.qty += amt;
} else {
throw new Error(`You cannot afford this purchase.`);
}
}
export function SellShares(corporation: ICorporation, player: IPlayer, numShares: number): number {
if (isNaN(numShares)) throw new Error("Invalid value for number of shares");
if (numShares < 0) throw new Error("Invalid value for number of shares");
if (numShares > corporation.numShares) throw new Error("You don't have that many shares to sell!");
if (!corporation.public) throw new Error("You haven't gone public!");
if (corporation.shareSaleCooldown) throw new Error("Share sale on cooldown!");
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;
corporation.shareSaleCooldown = CorporationConstants.SellSharesCooldown;
player.gainMoney(profit, "corporation");
return profit;
}
export function BuyBackShares(corporation: ICorporation, player: IPlayer, numShares: number): boolean {
if (isNaN(numShares)) throw new Error("Invalid value for number of shares");
if (numShares < 0) throw new Error("Invalid value for number of shares");
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;
if (player.money < numShares * buybackPrice) throw new Error("You cant afford that many shares!");
corporation.numShares += numShares;
corporation.issuedShares -= numShares;
player.loseMoney(numShares * buybackPrice, "corporation");
return true;
}
2021-09-10 06:13:28 +02:00
export function AssignJob(employee: Employee, job: string): void {
if (!Object.values(EmployeePositions).includes(job)) throw new Error(`'${job}' is not a valid job.`);
employee.pos = job;
}
export function UpgradeOfficeSize(corp: ICorporation, office: OfficeSpace, size: number): void {
const initialPriceMult = Math.round(office.size / CorporationConstants.OfficeInitialSize);
const costMultiplier = 1.09;
// Calculate cost to upgrade size by 15 employees
let mult = 0;
for (let i = 0; i < size / CorporationConstants.OfficeInitialSize; ++i) {
mult += Math.pow(costMultiplier, initialPriceMult + i);
}
const cost = CorporationConstants.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;
2021-11-12 03:35:26 +01:00
corp.funds = corp.funds - cost;
2021-09-10 06:13:28 +02:00
}
export function ThrowParty(corp: ICorporation, office: OfficeSpace, costPerEmployee: number): number {
const totalCost = costPerEmployee * office.employees.length;
2021-11-12 03:35:26 +01:00
if (corp.funds < totalCost) return 0;
corp.funds = corp.funds - totalCost;
2021-09-10 06:13:28 +02:00
let mult = 0;
for (let i = 0; i < office.employees.length; ++i) {
mult = office.employees[i].throwParty(costPerEmployee);
}
return mult;
}
export function PurchaseWarehouse(corp: ICorporation, division: IIndustry, city: string): void {
2021-11-12 03:35:26 +01:00
if (corp.funds < CorporationConstants.WarehouseInitialCost) return;
2021-09-10 06:13:28 +02:00
if (division.warehouses[city] instanceof Warehouse) return;
division.warehouses[city] = new Warehouse({
corp: corp,
industry: division,
loc: city,
size: CorporationConstants.WarehouseInitialSize,
});
2021-11-12 03:35:26 +01:00
corp.funds = corp.funds - CorporationConstants.WarehouseInitialCost;
2021-09-10 06:13:28 +02:00
}
export function UpgradeWarehouse(corp: ICorporation, division: IIndustry, warehouse: Warehouse): void {
const sizeUpgradeCost = CorporationConstants.WarehouseUpgradeBaseCost * Math.pow(1.07, warehouse.level + 1);
if (corp.funds < sizeUpgradeCost) return;
++warehouse.level;
warehouse.updateSize(corp, division);
2021-11-12 03:35:26 +01:00
corp.funds = corp.funds - sizeUpgradeCost;
}
export function BuyCoffee(corp: ICorporation, division: IIndustry, office: OfficeSpace): void {
const upgrade = IndustryUpgrades[0];
const cost = office.employees.length * upgrade[1];
2021-11-12 03:35:26 +01:00
if (corp.funds < cost) return;
corp.funds = corp.funds - cost;
division.upgrade(upgrade, {
corporation: corp,
office: office,
});
}
export function HireAdVert(corp: ICorporation, division: IIndustry, office: OfficeSpace): void {
const upgrade = IndustryUpgrades[1];
const cost = upgrade[1] * Math.pow(upgrade[2], division.upgrades[1]);
2021-11-12 03:35:26 +01:00
if (corp.funds < cost) return;
corp.funds = corp.funds - cost;
division.upgrade(upgrade, {
corporation: corp,
office: office,
});
}
export function MakeProduct(
corp: ICorporation,
division: IIndustry,
city: string,
productName: string,
designInvest: number,
marketingInvest: number,
): void {
if (designInvest < 0) {
designInvest = 0;
}
if (marketingInvest < 0) {
marketingInvest = 0;
}
if (productName == null || productName === "") {
throw new Error("You must specify a name for your product!");
}
if (!division.makesProducts) {
throw new Error("You cannot create products for this industry!");
}
if (isNaN(designInvest)) {
throw new Error("Invalid value for design investment");
}
if (isNaN(marketingInvest)) {
throw new Error("Invalid value for marketing investment");
}
2021-11-12 03:35:26 +01:00
if (corp.funds < designInvest + marketingInvest) {
throw new Error("You don't have enough company funds to make this large of an investment");
}
let maxProducts = 3;
if (division.hasResearch("uPgrade: Capacity.II")) {
maxProducts = 5;
} else if (division.hasResearch("uPgrade: Capacity.I")) {
maxProducts = 4;
}
const products = division.products;
if (Object.keys(products).length >= maxProducts) {
throw new Error(`You are already at the max products (${maxProducts}) for division: ${division.name}!`);
}
const product = new Product({
name: productName.replace(/[<>]/g, "").trim(), //Sanitize for HTMl elements
createCity: city,
designCost: designInvest,
advCost: marketingInvest,
});
if (products[product.name] instanceof Product) {
throw new Error(`You already have a product with this name!`);
}
2021-11-12 03:35:26 +01:00
corp.funds = corp.funds - (designInvest + marketingInvest);
products[product.name] = product;
}
2021-09-10 08:17:55 +02:00
export function Research(division: IIndustry, researchName: string): void {
const researchTree = IndustryResearchTrees[division.type];
if (researchTree === undefined) throw new Error(`No research tree for industry '${division.type}'`);
const allResearch = researchTree.getAllNodes();
if (!allResearch.includes(researchName)) throw new Error(`No research named '${researchName}'`);
const research = ResearchMap[researchName];
if (division.sciResearch.qty < research.cost)
throw new Error(`You do not have enough Scientific Research for ${research.name}`);
division.sciResearch.qty -= research.cost;
// Get the Node from the Research Tree and set its 'researched' property
researchTree.research(researchName);
division.researched[researchName] = true;
}
export function ExportMaterial(
divisionName: string,
cityName: string,
material: Material,
amt: string,
division?: Industry,
): void {
2021-09-10 08:17:55 +02:00
// Sanitize amt
2021-10-12 04:54:28 +02:00
let sanitizedAmt = amt.replace(/\s+/g, "").toUpperCase();
2021-09-10 08:17:55 +02:00
sanitizedAmt = sanitizedAmt.replace(/[^-()\d/*+.MAX]/g, "");
let temp = sanitizedAmt.replace(/MAX/g, "1");
try {
temp = eval(temp);
} catch (e) {
throw new Error("Invalid expression entered for export amount: " + e);
}
const n = parseFloat(temp);
if (n == null || isNaN(n) || n < 0) {
throw new Error("Invalid amount entered for export");
}
if (!division || !isRelevantMaterial(material.name, division)) {
throw new Error(`You cannot export material: ${material.name} to division: ${divisionName}!`);
}
2021-09-10 08:17:55 +02:00
const exportObj = { ind: divisionName, city: cityName, amt: sanitizedAmt };
material.exp.push(exportObj);
}
export function CancelExportMaterial(divisionName: string, cityName: string, material: Material, amt: string): void {
for (let i = 0; i < material.exp.length; ++i) {
if (material.exp[i].ind !== divisionName || material.exp[i].city !== cityName || material.exp[i].amt !== amt)
continue;
material.exp.splice(i, 1);
break;
}
}
export function LimitProductProduction(product: Product, cityName: string, qty: number): void {
if (qty < 0 || isNaN(qty)) {
product.prdman[cityName][0] = false;
} else {
product.prdman[cityName][0] = true;
product.prdman[cityName][1] = qty;
}
}
export function LimitMaterialProduction(material: Material, qty: number): void {
if (qty < 0 || isNaN(qty)) {
material.prdman[0] = false;
} else {
material.prdman[0] = true;
material.prdman[1] = qty;
}
}
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;
}