mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-12-25 15:42:28 +01:00
1455 lines
55 KiB
TypeScript
1455 lines
55 KiB
TypeScript
import { Reviver, Generic_toJSON, Generic_fromJSON } from "../utils/JSONReviver";
|
|
import { CityName } from "../Locations/data/CityNames";
|
|
import { Industries, IndustryStartingCosts, IndustryResearchTrees } from "./IndustryData";
|
|
import { CorporationConstants } from "./data/Constants";
|
|
import { EmployeePositions } from "./EmployeePositions";
|
|
import { Material } from "./Material";
|
|
import { getRandomInt } from "../utils/helpers/getRandomInt";
|
|
import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors";
|
|
import { OfficeSpace } from "./OfficeSpace";
|
|
import { Product } from "./Product";
|
|
import { dialogBoxCreate } from "../ui/React/DialogBox";
|
|
import { isString } from "../utils/helpers/isString";
|
|
import { MaterialSizes } from "./MaterialSizes";
|
|
import { Warehouse } from "./Warehouse";
|
|
import { ICorporation } from "./ICorporation";
|
|
import { IIndustry } from "./IIndustry";
|
|
import { IndustryUpgrade, IndustryUpgrades } from "./IndustryUpgrades";
|
|
|
|
interface IParams {
|
|
name?: string;
|
|
corp?: ICorporation;
|
|
type?: string;
|
|
}
|
|
|
|
export class Industry implements IIndustry {
|
|
name = "";
|
|
type = Industries.Agriculture;
|
|
sciResearch = new Material({ name: "Scientific Research" });
|
|
researched: { [key: string]: boolean | undefined } = {};
|
|
reqMats: { [key: string]: number | undefined } = {};
|
|
|
|
//An array of the name of materials being produced
|
|
prodMats: string[] = [];
|
|
|
|
products: { [key: string]: Product | undefined } = {};
|
|
makesProducts = false;
|
|
|
|
awareness = 0;
|
|
popularity = 0; //Should always be less than awareness
|
|
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 */
|
|
reFac = 0; //Real estate Factor
|
|
sciFac = 0; //Scientific Research Factor, affects quality
|
|
hwFac = 0; //Hardware factor
|
|
robFac = 0; //Robotics Factor
|
|
aiFac = 0; //AI Cores factor;
|
|
advFac = 0; //Advertising factor, affects sales
|
|
|
|
prodMult = 0; //Production multiplier
|
|
|
|
//Financials
|
|
lastCycleRevenue: number;
|
|
lastCycleExpenses: number;
|
|
thisCycleRevenue: number;
|
|
thisCycleExpenses: number;
|
|
|
|
//Upgrades
|
|
upgrades: number[] = Array(Object.keys(IndustryUpgrades).length).fill(0);
|
|
|
|
state = "START";
|
|
newInd = true;
|
|
|
|
//Maps locations to warehouses. 0 if no warehouse at that location
|
|
warehouses: { [key: string]: Warehouse | 0 };
|
|
|
|
//Maps locations to offices. 0 if no office at that location
|
|
offices: { [key: string]: OfficeSpace | 0 } = {
|
|
[CityName.Aevum]: 0,
|
|
[CityName.Chongqing]: 0,
|
|
[CityName.Sector12]: new OfficeSpace({
|
|
loc: CityName.Sector12,
|
|
size: CorporationConstants.OfficeInitialSize,
|
|
}),
|
|
[CityName.NewTokyo]: 0,
|
|
[CityName.Ishima]: 0,
|
|
[CityName.Volhaven]: 0,
|
|
};
|
|
|
|
constructor(params: IParams = {}) {
|
|
this.name = params.name ? params.name : "";
|
|
this.type = params.type ? params.type : Industries.Agriculture;
|
|
|
|
//Financials
|
|
this.lastCycleRevenue = 0;
|
|
this.lastCycleExpenses = 0;
|
|
this.thisCycleRevenue = 0;
|
|
this.thisCycleExpenses = 0;
|
|
|
|
this.warehouses = {
|
|
[CityName.Aevum]: 0,
|
|
[CityName.Chongqing]: 0,
|
|
[CityName.Sector12]: new Warehouse({
|
|
corp: params.corp,
|
|
industry: this,
|
|
loc: CityName.Sector12,
|
|
size: CorporationConstants.WarehouseInitialSize,
|
|
}),
|
|
[CityName.NewTokyo]: 0,
|
|
[CityName.Ishima]: 0,
|
|
[CityName.Volhaven]: 0,
|
|
};
|
|
|
|
this.init();
|
|
}
|
|
|
|
init(): void {
|
|
//Set the unique properties of an industry (how much its affected by real estate/scientific research, etc.)
|
|
const startingCost = IndustryStartingCosts[this.type];
|
|
if (startingCost === undefined) throw new Error(`Invalid industry: "${this.type}"`);
|
|
this.startingCost = startingCost;
|
|
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.15;
|
|
this.aiFac = 0.18;
|
|
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.error(`Invalid Industry Type passed into Industry.init(): ${this.type}`);
|
|
return;
|
|
}
|
|
}
|
|
|
|
getProductDescriptionText(): string {
|
|
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.error("Invalid industry type in Industry.getProductDescriptionText");
|
|
return "";
|
|
}
|
|
}
|
|
|
|
getMaximumNumberProducts(): number {
|
|
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 CorporationConstants.BaseMaxProducts + additional;
|
|
}
|
|
|
|
hasMaximumNumberProducts(): boolean {
|
|
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.)
|
|
calculateProductionFactors(): void {
|
|
let multSum = 0;
|
|
for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
|
|
const city = CorporationConstants.Cities[i];
|
|
const warehouse = this.warehouses[city];
|
|
if (!(warehouse instanceof Warehouse)) {
|
|
continue;
|
|
}
|
|
|
|
const materials = warehouse.materials;
|
|
|
|
const 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);
|
|
}
|
|
|
|
updateWarehouseSizeUsed(warehouse: Warehouse): void {
|
|
warehouse.updateMaterialSizeUsed();
|
|
|
|
for (const prodName of Object.keys(this.products)) {
|
|
if (this.products.hasOwnProperty(prodName)) {
|
|
const prod = this.products[prodName];
|
|
if (prod === undefined) continue;
|
|
warehouse.sizeUsed += prod.data[warehouse.loc][0] * prod.siz;
|
|
}
|
|
}
|
|
}
|
|
|
|
process(marketCycles = 1, state: string, corporation: ICorporation): void {
|
|
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.error("NaN in Corporation's computed revenue/expenses");
|
|
dialogBoxCreate(
|
|
"Something went wrong when compting Corporation's revenue/expenses. This is a bug. Please report to game developer",
|
|
);
|
|
this.thisCycleRevenue = 0;
|
|
this.thisCycleExpenses = 0;
|
|
}
|
|
this.lastCycleRevenue = this.thisCycleRevenue / (marketCycles * CorporationConstants.SecsPerMarketCycle);
|
|
this.lastCycleExpenses = this.thisCycleExpenses / (marketCycles * CorporationConstants.SecsPerMarketCycle);
|
|
this.thisCycleRevenue = 0;
|
|
this.thisCycleExpenses = 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 > 0) {
|
|
this.newInd = false;
|
|
}
|
|
|
|
// Process offices (and the employees in them)
|
|
let employeeSalary = 0;
|
|
for (const officeLoc of Object.keys(this.offices)) {
|
|
const office = this.offices[officeLoc];
|
|
if (office === 0) continue;
|
|
if (office instanceof OfficeSpace) {
|
|
employeeSalary += office.process(marketCycles, corporation, this);
|
|
}
|
|
}
|
|
this.thisCycleExpenses = this.thisCycleExpenses + employeeSalary;
|
|
|
|
// Process change in demand/competition of materials/products
|
|
this.processMaterialMarket();
|
|
this.processProductMarket(marketCycles);
|
|
|
|
// Process loss of popularity
|
|
this.popularity -= marketCycles * 0.0001;
|
|
this.popularity = Math.max(0, this.popularity);
|
|
|
|
// Process Dreamsense gains
|
|
const popularityGain = corporation.getDreamSenseGain(),
|
|
awarenessGain = popularityGain * 4;
|
|
if (popularityGain > 0) {
|
|
const awareness = this.awareness + awarenessGain * marketCycles;
|
|
this.awareness = Math.min(awareness, Number.MAX_VALUE);
|
|
|
|
const popularity = this.popularity + popularityGain * marketCycles;
|
|
this.popularity = Math.min(popularity, Number.MAX_VALUE);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Process production, purchase, and import/export of materials
|
|
let res = this.processMaterials(marketCycles, corporation);
|
|
if (Array.isArray(res)) {
|
|
this.thisCycleRevenue = this.thisCycleRevenue + res[0];
|
|
this.thisCycleExpenses = this.thisCycleExpenses + res[1];
|
|
}
|
|
|
|
// Process creation, production & sale of products
|
|
res = this.processProducts(marketCycles, corporation);
|
|
if (Array.isArray(res)) {
|
|
this.thisCycleRevenue = this.thisCycleRevenue + res[0];
|
|
this.thisCycleExpenses = this.thisCycleExpenses + res[1];
|
|
}
|
|
}
|
|
|
|
// Process change in demand and competition for this industry's materials
|
|
processMaterialMarket(): void {
|
|
//References to prodMats and reqMats
|
|
const reqMats = this.reqMats,
|
|
prodMats = this.prodMats;
|
|
|
|
//Only 'process the market' for materials that this industry deals with
|
|
for (let i = 0; i < CorporationConstants.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[CorporationConstants.Cities[i]] instanceof Warehouse) {
|
|
const wh = this.warehouses[CorporationConstants.Cities[i]];
|
|
if (wh === 0) continue;
|
|
for (const name of Object.keys(reqMats)) {
|
|
if (reqMats.hasOwnProperty(name)) {
|
|
wh.materials[name].processMarket();
|
|
}
|
|
}
|
|
|
|
//Produced materials are stored in an array
|
|
for (let 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
|
|
processProductMarket(marketCycles = 1): void {
|
|
// Demand gradually decreases, and competition gradually increases
|
|
for (const name of Object.keys(this.products)) {
|
|
if (this.products.hasOwnProperty(name)) {
|
|
const product = this.products[name];
|
|
if (product === undefined) continue;
|
|
let change = getRandomInt(0, 3) * 0.0004;
|
|
if (change === 0) continue;
|
|
|
|
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
|
|
processMaterials(marketCycles = 1, corporation: ICorporation): [number, number] {
|
|
let revenue = 0,
|
|
expenses = 0;
|
|
this.calculateProductionFactors();
|
|
|
|
//At the start of the export state, set the imports of everything to 0
|
|
if (this.state === "EXPORT") {
|
|
for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
|
|
const city = CorporationConstants.Cities[i];
|
|
if (!(this.warehouses[city] instanceof Warehouse)) {
|
|
continue;
|
|
}
|
|
const warehouse = this.warehouses[city];
|
|
if (warehouse === 0) continue;
|
|
for (const matName of Object.keys(warehouse.materials)) {
|
|
if (warehouse.materials.hasOwnProperty(matName)) {
|
|
const mat = warehouse.materials[matName];
|
|
mat.imp = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
|
|
const city = CorporationConstants.Cities[i];
|
|
const office = this.offices[city];
|
|
if (office === 0) continue;
|
|
|
|
if (this.warehouses[city] instanceof Warehouse) {
|
|
const warehouse = this.warehouses[city];
|
|
if (warehouse === 0) continue;
|
|
|
|
switch (this.state) {
|
|
case "PURCHASE": {
|
|
/* Process purchase of materials */
|
|
for (const matName of Object.keys(warehouse.materials)) {
|
|
if (!warehouse.materials.hasOwnProperty(matName)) continue;
|
|
const mat = warehouse.materials[matName];
|
|
let buyAmt = 0;
|
|
let maxAmt = 0;
|
|
if (warehouse.smartSupplyEnabled && Object.keys(this.reqMats).includes(matName)) {
|
|
continue;
|
|
}
|
|
buyAmt = mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles;
|
|
|
|
maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]);
|
|
|
|
buyAmt = Math.min(buyAmt, maxAmt);
|
|
if (buyAmt > 0) {
|
|
mat.qty += buyAmt;
|
|
expenses += buyAmt * mat.bCost;
|
|
}
|
|
this.updateWarehouseSizeUsed(warehouse);
|
|
} //End process purchase of materials
|
|
|
|
// smart supply
|
|
const smartBuy: { [key: string]: number | undefined } = {};
|
|
for (const matName of Object.keys(warehouse.materials)) {
|
|
if (!warehouse.materials.hasOwnProperty(matName)) continue;
|
|
if (!warehouse.smartSupplyEnabled || !Object.keys(this.reqMats).includes(matName)) continue;
|
|
const mat = warehouse.materials[matName];
|
|
|
|
//Smart supply tracker is stored as per second rate
|
|
const reqMat = this.reqMats[matName];
|
|
if (reqMat === undefined) throw new Error(`reqMat "${matName}" is undefined`);
|
|
mat.buy = reqMat * warehouse.smartSupplyStore;
|
|
let buyAmt = mat.buy * CorporationConstants.SecsPerMarketCycle * marketCycles;
|
|
const maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / MaterialSizes[matName]);
|
|
buyAmt = Math.min(buyAmt, maxAmt);
|
|
if (buyAmt > 0) smartBuy[matName] = buyAmt;
|
|
}
|
|
|
|
// Find which material were trying to create the least amount of product with.
|
|
let worseAmt = 1e99;
|
|
for (const matName of Object.keys(smartBuy)) {
|
|
const buyAmt = smartBuy[matName];
|
|
if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);
|
|
const reqMat = this.reqMats[matName];
|
|
if (reqMat === undefined) throw new Error(`reqMat "${matName}" is undefined`);
|
|
const amt = buyAmt / reqMat;
|
|
if (amt < worseAmt) worseAmt = amt;
|
|
}
|
|
|
|
// Align all the materials to the smallest amount.
|
|
for (const matName of Object.keys(smartBuy)) {
|
|
const reqMat = this.reqMats[matName];
|
|
if (reqMat === undefined) throw new Error(`reqMat "${matName}" is undefined`);
|
|
smartBuy[matName] = worseAmt * reqMat;
|
|
}
|
|
|
|
// Calculate the total size of all things were trying to buy
|
|
let totalSize = 0;
|
|
for (const matName of Object.keys(smartBuy)) {
|
|
const buyAmt = smartBuy[matName];
|
|
if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);
|
|
totalSize += buyAmt * MaterialSizes[matName];
|
|
}
|
|
|
|
// Shrink to the size of available space.
|
|
const freeSpace = warehouse.size - warehouse.sizeUsed;
|
|
if (totalSize > freeSpace) {
|
|
for (const matName of Object.keys(smartBuy)) {
|
|
const buyAmt = smartBuy[matName];
|
|
if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);
|
|
smartBuy[matName] = Math.floor((buyAmt * freeSpace) / totalSize);
|
|
}
|
|
}
|
|
|
|
// Use the materials already in the warehouse if the option is on.
|
|
for (const matName of Object.keys(smartBuy)) {
|
|
if (!warehouse.smartSupplyUseLeftovers[matName]) continue;
|
|
const mat = warehouse.materials[matName];
|
|
const buyAmt = smartBuy[matName];
|
|
if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);
|
|
smartBuy[matName] = Math.max(0, buyAmt - mat.qty);
|
|
}
|
|
|
|
// buy them
|
|
for (const matName of Object.keys(smartBuy)) {
|
|
const mat = warehouse.materials[matName];
|
|
const buyAmt = smartBuy[matName];
|
|
if (buyAmt === undefined) throw new Error(`Somehow smartbuy matname is undefined`);
|
|
mat.qty += buyAmt;
|
|
expenses += buyAmt * mat.bCost;
|
|
}
|
|
break;
|
|
}
|
|
case "PRODUCTION":
|
|
warehouse.smartSupplyStore = 0; //Reset smart supply amount
|
|
|
|
/* Process production of materials */
|
|
if (this.prodMats.length > 0) {
|
|
const mat = warehouse.materials[this.prodMats[0]];
|
|
//Calculate the maximum production of this material based
|
|
//on the office's productivity
|
|
const maxProd =
|
|
this.getOfficeProductivity(office) *
|
|
this.prodMult * // Multiplier from materials
|
|
corporation.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 *= CorporationConstants.SecsPerMarketCycle * marketCycles; //Convert production from per second to per market cycle
|
|
|
|
// Calculate net change in warehouse storage making the produced materials will cost
|
|
let totalMatSize = 0;
|
|
for (let tmp = 0; tmp < this.prodMats.length; ++tmp) {
|
|
totalMatSize += MaterialSizes[this.prodMats[tmp]];
|
|
}
|
|
for (const reqMatName of Object.keys(this.reqMats)) {
|
|
const normQty = this.reqMats[reqMatName];
|
|
if (normQty === undefined) continue;
|
|
totalMatSize -= MaterialSizes[reqMatName] * normQty;
|
|
}
|
|
// If not enough space in warehouse, limit the amount of produced materials
|
|
if (totalMatSize > 0) {
|
|
const 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 / (CorporationConstants.SecsPerMarketCycle * marketCycles);
|
|
|
|
// Make sure we have enough resource to make our materials
|
|
let producableFrac = 1;
|
|
for (const reqMatName of Object.keys(this.reqMats)) {
|
|
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
|
const reqMat = this.reqMats[reqMatName];
|
|
if (reqMat === undefined) continue;
|
|
const req = reqMat * 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 (const reqMatName of Object.keys(this.reqMats)) {
|
|
const reqMat = this.reqMats[reqMatName];
|
|
if (reqMat === undefined) continue;
|
|
const reqMatQtyNeeded = reqMat * prod * producableFrac;
|
|
warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
|
|
warehouse.materials[reqMatName].prd = 0;
|
|
warehouse.materials[reqMatName].prd -=
|
|
reqMatQtyNeeded / (CorporationConstants.SecsPerMarketCycle * marketCycles);
|
|
}
|
|
for (let 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] / 90 +
|
|
Math.pow(this.sciResearch.qty, this.sciFac) +
|
|
Math.pow(warehouse.materials["AICores"].qty, this.aiFac) / 10e3;
|
|
}
|
|
} else {
|
|
for (const reqMatName of Object.keys(this.reqMats)) {
|
|
if (this.reqMats.hasOwnProperty(reqMatName)) {
|
|
warehouse.materials[reqMatName].prd = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Per second
|
|
const fooProd = (prod * producableFrac) / (CorporationConstants.SecsPerMarketCycle * marketCycles);
|
|
for (let 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 (const reqMatName of Object.keys(this.reqMats)) {
|
|
warehouse.materials[reqMatName].prd = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "SALE":
|
|
/* Process sale of materials */
|
|
for (const matName of Object.keys(warehouse.materials)) {
|
|
if (warehouse.materials.hasOwnProperty(matName)) {
|
|
const mat = warehouse.materials[matName];
|
|
if (mat.sCost < 0 || mat.sllman[0] === false) {
|
|
mat.sll = 0;
|
|
continue;
|
|
}
|
|
|
|
// Sale multipliers
|
|
const businessFactor = this.getBusinessFactor(office); //Business employee productivity
|
|
const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
|
|
const marketFactor = this.getMarketFactor(mat); //Competition + demand
|
|
|
|
// Determine the cost that the material will be sold at
|
|
const markupLimit = mat.getMarkupLimit();
|
|
let sCost;
|
|
if (mat.marketTa2) {
|
|
const prod = mat.prd;
|
|
|
|
// Reverse engineer the 'maxSell' formula
|
|
// 1. Set 'maxSell' = prod
|
|
// 2. Substitute formula for 'markup'
|
|
// 3. Solve for 'sCost'
|
|
const numerator = markupLimit;
|
|
const sqrtNumerator = prod;
|
|
const sqrtDenominator =
|
|
(mat.qlt + 0.001) *
|
|
marketFactor *
|
|
businessFactor *
|
|
corporation.getSalesMultiplier() *
|
|
advertisingFactor *
|
|
this.getSalesMultiplier();
|
|
const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
|
|
let optimalPrice;
|
|
if (sqrtDenominator === 0 || denominator === 0) {
|
|
if (sqrtNumerator === 0) {
|
|
optimalPrice = 0; // No production
|
|
} else {
|
|
optimalPrice = mat.bCost + markupLimit;
|
|
console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
|
|
}
|
|
} else {
|
|
optimalPrice = numerator / denominator + mat.bCost;
|
|
}
|
|
|
|
// We'll store this "Optimal Price" in a property so that we don't have
|
|
// to re-calculate it for the UI
|
|
mat.marketTa2Price = optimalPrice;
|
|
|
|
sCost = optimalPrice;
|
|
} else if (mat.marketTa1) {
|
|
sCost = mat.bCost + markupLimit;
|
|
} else if (isString(mat.sCost)) {
|
|
sCost = (mat.sCost as string).replace(/MP/g, mat.bCost + "");
|
|
sCost = eval(sCost);
|
|
} else {
|
|
sCost = mat.sCost;
|
|
}
|
|
|
|
// Calculate how much of the material sells (per second)
|
|
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;
|
|
}
|
|
}
|
|
|
|
mat.maxsll =
|
|
(mat.qlt + 0.001) *
|
|
marketFactor *
|
|
markup *
|
|
businessFactor *
|
|
corporation.getSalesMultiplier() *
|
|
advertisingFactor *
|
|
this.getSalesMultiplier();
|
|
let sellAmt;
|
|
if (isString(mat.sllman[1])) {
|
|
//Dynamically evaluated
|
|
let tmp = (mat.sllman[1] as string).replace(/MAX/g, (mat.maxsll + "").toUpperCase());
|
|
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(mat.maxsll, sellAmt);
|
|
} else if (mat.sllman[1] === -1) {
|
|
//Backwards compatibility, -1 = MAX
|
|
sellAmt = mat.maxsll;
|
|
} else {
|
|
//Player's input value is just a number
|
|
sellAmt = Math.min(mat.maxsll, mat.sllman[1] as number);
|
|
}
|
|
|
|
sellAmt = sellAmt * CorporationConstants.SecsPerMarketCycle * marketCycles;
|
|
sellAmt = Math.min(mat.qty, sellAmt);
|
|
if (sellAmt < 0) {
|
|
console.warn(`sellAmt calculated to be negative for ${matName} in ${city}`);
|
|
mat.sll = 0;
|
|
continue;
|
|
}
|
|
if (sellAmt && sCost >= 0) {
|
|
mat.qty -= sellAmt;
|
|
revenue += sellAmt * sCost;
|
|
mat.sll = sellAmt / (CorporationConstants.SecsPerMarketCycle * marketCycles);
|
|
} else {
|
|
mat.sll = 0;
|
|
}
|
|
}
|
|
} //End processing of sale of materials
|
|
break;
|
|
|
|
case "EXPORT":
|
|
for (const matName of Object.keys(warehouse.materials)) {
|
|
if (warehouse.materials.hasOwnProperty(matName)) {
|
|
const mat = warehouse.materials[matName];
|
|
mat.totalExp = 0; //Reset export
|
|
for (let expI = 0; expI < mat.exp.length; ++expI) {
|
|
const exp = mat.exp[expI];
|
|
const amtStr = exp.amt.replace(
|
|
/MAX/g,
|
|
(mat.qty / (CorporationConstants.SecsPerMarketCycle * marketCycles) + "").toUpperCase(),
|
|
);
|
|
let amt = 0;
|
|
try {
|
|
amt = eval(amtStr);
|
|
} 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 * CorporationConstants.SecsPerMarketCycle * marketCycles;
|
|
|
|
if (mat.qty < amt) {
|
|
amt = mat.qty;
|
|
}
|
|
if (amt === 0) {
|
|
break; //None left
|
|
}
|
|
for (let foo = 0; foo < corporation.divisions.length; ++foo) {
|
|
if (corporation.divisions[foo].name === exp.ind) {
|
|
const expIndustry = corporation.divisions[foo];
|
|
const expWarehouse = expIndustry.warehouses[exp.city];
|
|
if (!(expWarehouse instanceof Warehouse)) {
|
|
console.error(`Invalid export! ${expIndustry.name} ${exp.city}`);
|
|
break;
|
|
}
|
|
|
|
// Make sure theres enough space in warehouse
|
|
if (expWarehouse.sizeUsed >= expWarehouse.size) {
|
|
// Warehouse at capacity. Exporting doesnt
|
|
// affect revenue so just return 0's
|
|
return [0, 0];
|
|
} else {
|
|
const maxAmt = Math.floor((expWarehouse.size - expWarehouse.sizeUsed) / MaterialSizes[matName]);
|
|
amt = Math.min(maxAmt, amt);
|
|
}
|
|
expWarehouse.materials[matName].imp +=
|
|
amt / (CorporationConstants.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 /= CorporationConstants.SecsPerMarketCycle * marketCycles;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case "START":
|
|
break;
|
|
default:
|
|
console.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 +=
|
|
0.004 *
|
|
Math.pow(office.employeeProd[EmployeePositions.RandD], 0.5) *
|
|
corporation.getScientificResearchMultiplier() *
|
|
this.getScientificResearchMultiplier();
|
|
}
|
|
}
|
|
return [revenue, expenses];
|
|
}
|
|
|
|
//Process production & sale of this industry's FINISHED products (including all of their stats)
|
|
processProducts(marketCycles = 1, corporation: ICorporation): [number, number] {
|
|
let revenue = 0;
|
|
const expenses = 0;
|
|
|
|
//Create products
|
|
if (this.state === "PRODUCTION") {
|
|
for (const prodName of Object.keys(this.products)) {
|
|
const prod = this.products[prodName];
|
|
if (prod === undefined) continue;
|
|
if (!prod.fin) {
|
|
const city = prod.createCity;
|
|
const office = this.offices[city];
|
|
if (office === 0) continue;
|
|
|
|
// Designing/Creating a Product is based mostly off Engineers
|
|
const engrProd = office.employeeProd[EmployeePositions.Engineer];
|
|
const mgmtProd = office.employeeProd[EmployeePositions.Management];
|
|
const opProd = office.employeeProd[EmployeePositions.Operations];
|
|
const total = engrProd + mgmtProd + opProd;
|
|
if (total <= 0) {
|
|
break;
|
|
}
|
|
|
|
// Management is a multiplier for the production from Engineers
|
|
const mgmtFactor = 1 + mgmtProd / (1.2 * total);
|
|
|
|
const progress = (Math.pow(engrProd, 0.34) + Math.pow(opProd, 0.2)) * mgmtFactor;
|
|
|
|
prod.createProduct(marketCycles, progress);
|
|
if (prod.prog >= 100) {
|
|
prod.finishProduct(office.employeeProd, this);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Produce Products
|
|
for (const prodName of Object.keys(this.products)) {
|
|
if (this.products.hasOwnProperty(prodName)) {
|
|
const prod = this.products[prodName];
|
|
if (prod instanceof Product && prod.fin) {
|
|
revenue += this.processProduct(marketCycles, prod, corporation);
|
|
}
|
|
}
|
|
}
|
|
return [revenue, expenses];
|
|
}
|
|
|
|
//Processes FINISHED products
|
|
processProduct(marketCycles = 1, product: Product, corporation: ICorporation): number {
|
|
let totalProfit = 0;
|
|
for (let i = 0; i < CorporationConstants.Cities.length; ++i) {
|
|
const city = CorporationConstants.Cities[i];
|
|
const office = this.offices[city];
|
|
if (office === 0) continue;
|
|
const 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
|
|
const 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 *= CorporationConstants.SecsPerMarketCycle * marketCycles;
|
|
|
|
//Calculate net change in warehouse storage making the Products will cost
|
|
let netStorageSize = product.siz;
|
|
for (const reqMatName of Object.keys(product.reqMats)) {
|
|
if (product.reqMats.hasOwnProperty(reqMatName)) {
|
|
const normQty = product.reqMats[reqMatName];
|
|
netStorageSize -= MaterialSizes[reqMatName] * normQty;
|
|
}
|
|
}
|
|
|
|
//If there's not enough space in warehouse, limit the amount of Product
|
|
if (netStorageSize > 0) {
|
|
const maxAmt = Math.floor((warehouse.size - warehouse.sizeUsed) / netStorageSize);
|
|
prod = Math.min(maxAmt, prod);
|
|
}
|
|
|
|
warehouse.smartSupplyStore += prod / (CorporationConstants.SecsPerMarketCycle * marketCycles);
|
|
|
|
//Make sure we have enough resources to make our Products
|
|
let producableFrac = 1;
|
|
for (const reqMatName of Object.keys(product.reqMats)) {
|
|
if (product.reqMats.hasOwnProperty(reqMatName)) {
|
|
const 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 (const reqMatName of Object.keys(product.reqMats)) {
|
|
if (product.reqMats.hasOwnProperty(reqMatName)) {
|
|
const reqMatQtyNeeded = product.reqMats[reqMatName] * prod * producableFrac;
|
|
warehouse.materials[reqMatName].qty -= reqMatQtyNeeded;
|
|
warehouse.materials[reqMatName].prd -=
|
|
reqMatQtyNeeded / (CorporationConstants.SecsPerMarketCycle * marketCycles);
|
|
}
|
|
}
|
|
//Quantity
|
|
product.data[city][0] += prod * producableFrac;
|
|
}
|
|
|
|
//Keep track of production Per second
|
|
product.data[city][1] = (prod * producableFrac) / (CorporationConstants.SecsPerMarketCycle * marketCycles);
|
|
break;
|
|
}
|
|
case "SALE": {
|
|
//Process sale of Products
|
|
product.pCost = 0; //Estimated production cost
|
|
for (const reqMatName of Object.keys(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 *= CorporationConstants.ProductProductionCostRatio;
|
|
|
|
// Sale multipliers
|
|
const businessFactor = this.getBusinessFactor(office); //Business employee productivity
|
|
const advertisingFactor = this.getAdvertisingFactors()[0]; //Awareness + popularity
|
|
const marketFactor = this.getMarketFactor(product); //Competition + demand
|
|
|
|
// Calculate Sale Cost (sCost), which could be dynamically evaluated
|
|
const markupLimit = product.rat / product.mku;
|
|
let sCost;
|
|
if (product.marketTa2) {
|
|
const prod = product.data[city][1];
|
|
|
|
// Reverse engineer the 'maxSell' formula
|
|
// 1. Set 'maxSell' = prod
|
|
// 2. Substitute formula for 'markup'
|
|
// 3. Solve for 'sCost'roduct.pCost = sCost
|
|
const numerator = markupLimit;
|
|
const sqrtNumerator = prod;
|
|
const sqrtDenominator =
|
|
0.5 *
|
|
Math.pow(product.rat, 0.65) *
|
|
marketFactor *
|
|
corporation.getSalesMultiplier() *
|
|
businessFactor *
|
|
advertisingFactor *
|
|
this.getSalesMultiplier();
|
|
const denominator = Math.sqrt(sqrtNumerator / sqrtDenominator);
|
|
let optimalPrice;
|
|
if (sqrtDenominator === 0 || denominator === 0) {
|
|
if (sqrtNumerator === 0) {
|
|
optimalPrice = 0; // No production
|
|
} else {
|
|
optimalPrice = product.pCost + markupLimit;
|
|
console.warn(`In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost`);
|
|
}
|
|
} else {
|
|
optimalPrice = numerator / denominator + product.pCost;
|
|
}
|
|
|
|
// Store this "optimal Price" in a property so we don't have to re-calculate for UI
|
|
product.marketTa2Price[city] = optimalPrice;
|
|
sCost = optimalPrice;
|
|
} else if (product.marketTa1) {
|
|
sCost = product.pCost + markupLimit;
|
|
} else if (isString(product.sCost)) {
|
|
const sCostString = product.sCost as string;
|
|
if (product.mku === 0) {
|
|
console.error(`mku is zero, reverting to 1 to avoid Infinity`);
|
|
product.mku = 1;
|
|
}
|
|
sCost = sCostString.replace(/MP/g, product.pCost + product.rat / product.mku + "");
|
|
sCost = eval(sCost);
|
|
} else {
|
|
sCost = product.sCost;
|
|
}
|
|
|
|
let markup = 1;
|
|
if (sCost > product.pCost) {
|
|
if (sCost - product.pCost > markupLimit) {
|
|
markup = markupLimit / (sCost - product.pCost);
|
|
}
|
|
}
|
|
|
|
product.maxsll =
|
|
0.5 *
|
|
Math.pow(product.rat, 0.65) *
|
|
marketFactor *
|
|
corporation.getSalesMultiplier() *
|
|
Math.pow(markup, 2) *
|
|
businessFactor *
|
|
advertisingFactor *
|
|
this.getSalesMultiplier();
|
|
let sellAmt;
|
|
if (product.sllman[city][0] && isString(product.sllman[city][1])) {
|
|
//Sell amount is dynamically evaluated
|
|
let tmp = product.sllman[city][1].replace(/MAX/g, (product.maxsll + "").toUpperCase());
|
|
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 = product.maxsll;
|
|
}
|
|
sellAmt = Math.min(product.maxsll, tmp);
|
|
} else if (product.sllman[city][0] && product.sllman[city][1] > 0) {
|
|
//Sell amount is manually limited
|
|
sellAmt = Math.min(product.maxsll, product.sllman[city][1]);
|
|
} else if (product.sllman[city][0] === false) {
|
|
sellAmt = 0;
|
|
} else {
|
|
sellAmt = product.maxsll;
|
|
}
|
|
if (sellAmt < 0) {
|
|
sellAmt = 0;
|
|
}
|
|
sellAmt = sellAmt * CorporationConstants.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 / (CorporationConstants.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.error(`Invalid State: ${this.state}`);
|
|
break;
|
|
} //End switch(this.state)
|
|
}
|
|
}
|
|
return totalProfit;
|
|
}
|
|
|
|
discontinueProduct(product: Product): void {
|
|
for (const productName of Object.keys(this.products)) {
|
|
if (this.products.hasOwnProperty(productName)) {
|
|
if (product === this.products[productName]) {
|
|
delete this.products[productName];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
upgrade(upgrade: IndustryUpgrade, refs: { corporation: ICorporation; office: OfficeSpace }): void {
|
|
const corporation = refs.corporation;
|
|
const office = refs.office;
|
|
const upgN = upgrade[0];
|
|
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,
|
|
const advMult = corporation.getAdvertisingMultiplier() * this.getAdvertisingMultiplier();
|
|
const awareness = (this.awareness + 3 * advMult) * (1.01 * advMult);
|
|
this.awareness = Math.min(awareness, Number.MAX_VALUE);
|
|
|
|
const popularity = (this.popularity + 1 * advMult) * ((1 + getRandomInt(1, 3) / 100) * advMult);
|
|
this.popularity = Math.min(popularity, Number.MAX_VALUE);
|
|
break;
|
|
}
|
|
default: {
|
|
console.error(`Un-implemented function index: ${upgN}`);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns how much of a material can be produced based of office productivity (employee stats)
|
|
getOfficeProductivity(office: OfficeSpace, params: { forProduct?: boolean } = {}): number {
|
|
const opProd = office.employeeProd[EmployeePositions.Operations];
|
|
const engrProd = office.employeeProd[EmployeePositions.Engineer];
|
|
const mgmtProd = office.employeeProd[EmployeePositions.Management];
|
|
const total = opProd + engrProd + mgmtProd;
|
|
|
|
if (total <= 0) return 0;
|
|
|
|
// Management is a multiplier for the production from Operations and Engineers
|
|
const mgmtFactor = 1 + mgmtProd / (1.2 * total);
|
|
|
|
// For production, Operations is slightly more important than engineering
|
|
// Both Engineering and Operations have diminishing returns
|
|
const prod = (Math.pow(opProd, 0.4) + Math.pow(engrProd, 0.3)) * mgmtFactor;
|
|
|
|
// Generic multiplier for the production. Used for game-balancing purposes
|
|
const balancingMult = 0.05;
|
|
|
|
if (params && params.forProduct) {
|
|
// Products are harder to create and therefore have less production
|
|
return 0.5 * balancingMult * prod;
|
|
} else {
|
|
return balancingMult * prod;
|
|
}
|
|
}
|
|
|
|
// Returns a multiplier based on the office' 'Business' employees that affects sales
|
|
getBusinessFactor(office: OfficeSpace): number {
|
|
const businessProd = 1 + office.employeeProd[EmployeePositions.Business];
|
|
|
|
return calculateEffectWithFactors(businessProd, 0.26, 10e3);
|
|
}
|
|
|
|
//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]
|
|
getAdvertisingFactors(): [number, number, number, number] {
|
|
const awarenessFac = Math.pow(this.awareness + 1, this.advFac);
|
|
const popularityFac = Math.pow(this.popularity + 1, this.advFac);
|
|
const ratioFac = this.awareness === 0 ? 0.01 : Math.max((this.popularity + 0.001) / this.awareness, 0.01);
|
|
const 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
|
|
getMarketFactor(mat: { dmd: number; cmp: number }): number {
|
|
return Math.max(0.1, (mat.dmd * (100 - mat.cmp)) / 100);
|
|
}
|
|
|
|
// Returns a boolean indicating whether this Industry has the specified Research
|
|
hasResearch(name: string): boolean {
|
|
return this.researched[name] === true;
|
|
}
|
|
|
|
updateResearchTree(): void {
|
|
const researchTree = IndustryResearchTrees[this.type];
|
|
if (researchTree === undefined) throw new Error(`Invalid industry "${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) {
|
|
for (const research of Object.keys(this.researched)) {
|
|
researchTree.research(research);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get multipliers from Research
|
|
getAdvertisingMultiplier(): number {
|
|
const researchTree = IndustryResearchTrees[this.type];
|
|
if (researchTree === undefined) throw new Error(`Invalid industry: "${this.type}"`);
|
|
this.updateResearchTree();
|
|
return researchTree.getAdvertisingMultiplier();
|
|
}
|
|
|
|
getEmployeeChaMultiplier(): number {
|
|
const researchTree = IndustryResearchTrees[this.type];
|
|
if (researchTree === undefined) throw new Error(`Invalid industry: "${this.type}"`);
|
|
this.updateResearchTree();
|
|
return researchTree.getEmployeeChaMultiplier();
|
|
}
|
|
|
|
getEmployeeCreMultiplier(): number {
|
|
const researchTree = IndustryResearchTrees[this.type];
|
|
if (researchTree === undefined) throw new Error(`Invalid industry: "${this.type}"`);
|
|
this.updateResearchTree();
|
|
return researchTree.getEmployeeCreMultiplier();
|
|
}
|
|
|
|
getEmployeeEffMultiplier(): number {
|
|
const researchTree = IndustryResearchTrees[this.type];
|
|
if (researchTree === undefined) throw new Error(`Invalid industry: "${this.type}"`);
|
|
this.updateResearchTree();
|
|
return researchTree.getEmployeeEffMultiplier();
|
|
}
|
|
|
|
getEmployeeIntMultiplier(): number {
|
|
const researchTree = IndustryResearchTrees[this.type];
|
|
if (researchTree === undefined) throw new Error(`Invalid industry: "${this.type}"`);
|
|
this.updateResearchTree();
|
|
return researchTree.getEmployeeIntMultiplier();
|
|
}
|
|
|
|
getProductionMultiplier(): number {
|
|
const researchTree = IndustryResearchTrees[this.type];
|
|
if (researchTree === undefined) throw new Error(`Invalid industry: "${this.type}"`);
|
|
this.updateResearchTree();
|
|
return researchTree.getProductionMultiplier();
|
|
}
|
|
|
|
getProductProductionMultiplier(): number {
|
|
const researchTree = IndustryResearchTrees[this.type];
|
|
if (researchTree === undefined) throw new Error(`Invalid industry: "${this.type}"`);
|
|
this.updateResearchTree();
|
|
return researchTree.getProductProductionMultiplier();
|
|
}
|
|
|
|
getSalesMultiplier(): number {
|
|
const researchTree = IndustryResearchTrees[this.type];
|
|
if (researchTree === undefined) throw new Error(`Invalid industry: "${this.type}"`);
|
|
this.updateResearchTree();
|
|
return researchTree.getSalesMultiplier();
|
|
}
|
|
|
|
getScientificResearchMultiplier(): number {
|
|
const researchTree = IndustryResearchTrees[this.type];
|
|
if (researchTree === undefined) throw new Error(`Invalid industry: "${this.type}"`);
|
|
this.updateResearchTree();
|
|
return researchTree.getScientificResearchMultiplier();
|
|
}
|
|
|
|
getStorageMultiplier(): number {
|
|
const researchTree = IndustryResearchTrees[this.type];
|
|
if (researchTree === undefined) throw new Error(`Invalid industry: "${this.type}"`);
|
|
this.updateResearchTree();
|
|
return researchTree.getStorageMultiplier();
|
|
}
|
|
|
|
/**
|
|
* Serialize the current object to a JSON save state.
|
|
*/
|
|
toJSON(): any {
|
|
return Generic_toJSON("Industry", this);
|
|
}
|
|
|
|
/**
|
|
* Initiatizes a Industry object from a JSON save state.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
static fromJSON(value: any): Industry {
|
|
return Generic_fromJSON(Industry, value.data);
|
|
}
|
|
}
|
|
|
|
Reviver.constructors.Industry = Industry;
|