2023-04-07 06:33:51 +02:00
import { constructorsForReviver , Generic_toJSON , Generic_fromJSON , IReviverValue } from "../utils/JSONReviver" ;
2022-12-30 02:28:53 +01:00
import { CityName } from "../Enums" ;
import { IndustryResearchTrees , IndustriesData } from "./IndustryData" ;
import * as corpConstants from "./data/Constants" ;
import { EmployeePositions , IndustryType } from "./data/Enums" ;
2021-09-25 20:42:57 +02:00
import { getRandomInt } from "../utils/helpers/getRandomInt" ;
2021-08-31 08:49:57 +02:00
import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors" ;
import { OfficeSpace } from "./OfficeSpace" ;
import { Product } from "./Product" ;
2021-09-25 20:42:57 +02:00
import { dialogBoxCreate } from "../ui/React/DialogBox" ;
import { isString } from "../utils/helpers/isString" ;
2022-12-25 09:33:13 +01:00
import { MaterialInfo } from "./MaterialInfo" ;
2021-08-31 08:49:57 +02:00
import { Warehouse } from "./Warehouse" ;
2022-09-20 12:47:54 +02:00
import { Corporation } from "./Corporation" ;
2022-12-30 02:28:53 +01:00
import { CorpMaterialName , CorpResearchName , CorpStateName } from "@nsdefs" ;
2021-08-31 08:49:57 +02:00
interface IParams {
2021-09-05 01:09:30 +02:00
name? : string ;
2022-09-20 12:47:54 +02:00
corp? : Corporation ;
2022-12-30 02:28:53 +01:00
type ? : IndustryType ;
2021-08-31 08:49:57 +02:00
}
2022-09-20 12:47:54 +02:00
export class Industry {
2022-10-25 03:54:54 +02:00
name : string ;
type : IndustryType ;
2022-12-30 02:28:53 +01:00
sciResearch = 0 ;
researched : Partial < Record < CorpResearchName , boolean > > = { } ;
reqMats : Partial < Record < CorpMaterialName , number > > = { } ;
2021-08-31 08:49:57 +02:00
2021-09-05 01:09:30 +02:00
//An array of the name of materials being produced
2022-12-30 02:28:53 +01:00
prodMats : CorpMaterialName [ ] ;
2021-08-31 08:49:57 +02:00
2022-12-30 02:28:53 +01:00
products : Partial < Record < string , Product > > = { } ;
2022-10-25 03:54:54 +02:00
makesProducts : boolean ;
2021-08-31 08:49:57 +02:00
2021-09-05 01:09:30 +02:00
awareness = 0 ;
2022-10-25 03:54:54 +02:00
popularity = 0 ;
2021-09-05 01:09:30 +02:00
startingCost = 0 ;
2021-08-31 08:49:57 +02:00
2021-09-05 01:09:30 +02:00
/ * T h e f o l l o w i n g a r e f a c t o r s f o r h o w m u c h p r o d u c t i o n / o t h e r t h i n g s a r e i n c r e a s e d b y
2021-08-31 08:49:57 +02:00
different factors . The production increase always has diminishing returns ,
2022-10-09 07:25:31 +02:00
and they are all represented by exponentials of < 1 ( e . g x ^ 0.5 , x ^ 0.8 )
2021-08-31 08:49:57 +02:00
The number for these represent the exponential . A lower number means more
diminishing returns * /
2022-10-25 03:54:54 +02:00
reFac : number ; //Real estate Factor
sciFac : number ; //Scientific Research Factor, affects quality
hwFac : number ; //Hardware factor
robFac : number ; //Robotics Factor
aiFac : number ; //AI Cores factor;
advFac : number ; //Advertising factor, affects sales
2021-09-05 01:09:30 +02:00
prodMult = 0 ; //Production multiplier
//Financials
2021-11-12 03:35:26 +01:00
lastCycleRevenue : number ;
lastCycleExpenses : number ;
thisCycleRevenue : number ;
thisCycleExpenses : number ;
2021-09-05 01:09:30 +02:00
2022-12-30 02:28:53 +01:00
state : CorpStateName = "START" ;
2021-09-05 01:09:30 +02:00
newInd = true ;
//Maps locations to warehouses. 0 if no warehouse at that location
2022-10-25 03:54:54 +02:00
warehouses : Record < CityName , Warehouse | 0 > ;
2021-09-05 01:09:30 +02:00
//Maps locations to offices. 0 if no office at that location
2022-10-25 16:32:20 +02:00
offices : Record < CityName , OfficeSpace | 0 > = {
2021-09-05 01:09:30 +02:00
[ CityName . Aevum ] : 0 ,
[ CityName . Chongqing ] : 0 ,
[ CityName . Sector12 ] : new OfficeSpace ( {
loc : CityName.Sector12 ,
2022-12-30 02:28:53 +01:00
size : corpConstants.officeInitialSize ,
2021-09-05 01:09:30 +02:00
} ) ,
[ CityName . NewTokyo ] : 0 ,
[ CityName . Ishima ] : 0 ,
[ CityName . Volhaven ] : 0 ,
} ;
2022-06-02 03:43:22 +02:00
numAdVerts = 0 ;
2021-09-05 01:09:30 +02:00
constructor ( params : IParams = { } ) {
2022-10-25 03:54:54 +02:00
this . type = params . type || IndustryType . Agriculture ;
2021-09-05 01:09:30 +02:00
this . name = params . name ? params . name : "" ;
2021-08-31 08:49:57 +02:00
//Financials
2021-11-12 03:35:26 +01:00
this . lastCycleRevenue = 0 ;
this . lastCycleExpenses = 0 ;
this . thisCycleRevenue = 0 ;
this . thisCycleExpenses = 0 ;
2021-09-05 01:09:30 +02:00
this . warehouses = {
[ CityName . Aevum ] : 0 ,
[ CityName . Chongqing ] : 0 ,
[ CityName . Sector12 ] : new Warehouse ( {
corp : params.corp ,
industry : this ,
loc : CityName.Sector12 ,
2022-12-30 02:28:53 +01:00
size : corpConstants.warehouseInitialSize ,
2021-09-05 01:09:30 +02:00
} ) ,
[ CityName . NewTokyo ] : 0 ,
[ CityName . Ishima ] : 0 ,
[ CityName . Volhaven ] : 0 ,
2021-08-31 08:49:57 +02:00
} ;
2022-10-25 03:54:54 +02:00
const data = IndustriesData [ this . type ] ;
if ( ! data ) throw new Error ( ` Invalid industry: " ${ this . type } " ` ) ;
this . startingCost = data . startingCost ;
this . makesProducts = data . product ? true : false ;
2022-12-30 02:28:53 +01:00
this . reFac = data . realEstateFactor ? ? 0 ;
this . sciFac = data . scienceFactor ? ? 0 ;
this . hwFac = data . hardwareFactor ? ? 0 ;
this . robFac = data . robotFactor ? ? 0 ;
this . aiFac = data . aiCoreFactor ? ? 0 ;
this . advFac = data . advertisingFactor ? ? 0 ;
this . reqMats = data . requiredMaterials ;
this . prodMats = data . producedMaterials ? ? [ ] ;
2021-09-05 01:09:30 +02:00
}
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 ;
2022-12-30 02:28:53 +01:00
return corpConstants . maxProductsBase + additional ;
2021-09-05 01:09:30 +02:00
}
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 ;
2022-12-30 02:28:53 +01:00
for ( const city of Object . values ( CityName ) ) {
2021-09-05 01:09:30 +02:00
const warehouse = this . warehouses [ city ] ;
2022-09-27 21:14:34 +02:00
if ( ! warehouse ) continue ;
2021-09-05 01:09:30 +02:00
const materials = warehouse . materials ;
const cityMult =
2022-12-30 02:28:53 +01:00
Math . pow ( 0.002 * materials [ "Real Estate" ] . qty + 1 , this . reFac ) *
2021-09-05 01:09:30 +02:00
Math . pow ( 0.002 * materials . Hardware . qty + 1 , this . hwFac ) *
Math . pow ( 0.002 * materials . Robots . qty + 1 , this . robFac ) *
2022-12-30 02:28:53 +01:00
Math . pow ( 0.002 * materials [ "AI Cores" ] . qty + 1 , this . aiFac ) ;
2021-09-05 01:09:30 +02:00
multSum += Math . pow ( cityMult , 0.73 ) ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
multSum < 1 ? ( this . prodMult = 1 ) : ( this . prodMult = multSum ) ;
}
updateWarehouseSizeUsed ( warehouse : Warehouse ) : void {
warehouse . updateMaterialSizeUsed ( ) ;
2022-01-16 01:45:03 +01:00
for ( const prodName of Object . keys ( this . products ) ) {
2021-09-05 01:09:30 +02:00
if ( this . products . hasOwnProperty ( prodName ) ) {
const prod = this . products [ prodName ] ;
if ( prod === undefined ) continue ;
warehouse . sizeUsed += prod . data [ warehouse . loc ] [ 0 ] * prod . siz ;
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
}
2022-12-30 02:28:53 +01:00
process ( marketCycles = 1 , state : CorpStateName , corporation : Corporation ) : void {
2021-09-05 01:09:30 +02:00
this . state = state ;
//At the start of a cycle, store and reset revenue/expenses
2022-10-09 07:25:31 +02:00
//Then calculate salaries and process the markets
2021-09-05 01:09:30 +02:00
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" ,
) ;
2021-11-12 03:35:26 +01:00
this . thisCycleRevenue = 0 ;
this . thisCycleExpenses = 0 ;
2021-09-05 01:09:30 +02:00
}
2022-12-30 02:28:53 +01:00
this . lastCycleRevenue = this . thisCycleRevenue / ( marketCycles * corpConstants . secondsPerMarketCycle ) ;
this . lastCycleExpenses = this . thisCycleExpenses / ( marketCycles * corpConstants . secondsPerMarketCycle ) ;
2021-11-12 03:35:26 +01:00
this . thisCycleRevenue = 0 ;
this . thisCycleExpenses = 0 ;
2021-09-05 01:09:30 +02:00
// Once you start making revenue, the player should no longer be
// considered new, and therefore no longer needs the 'tutorial' UI elements
2021-11-12 03:35:26 +01:00
if ( this . lastCycleRevenue > 0 ) {
2021-09-05 01:09:30 +02:00
this . newInd = false ;
}
// Process offices (and the employees in them)
let employeeSalary = 0 ;
2022-10-25 16:32:20 +02:00
for ( const officeLoc of Object . values ( CityName ) ) {
2021-09-05 01:09:30 +02:00
const office = this . offices [ officeLoc ] ;
2022-09-27 21:14:34 +02:00
if ( office ) employeeSalary += office . process ( marketCycles , corporation , this ) ;
2021-09-05 01:09:30 +02:00
}
2021-11-12 03:35:26 +01:00
this . thisCycleExpenses = this . thisCycleExpenses + employeeSalary ;
2021-09-05 01:09:30 +02:00
// 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 ) {
2022-04-07 01:30:08 +02:00
const awareness = this . awareness + awarenessGain * marketCycles ;
2022-01-14 11:05:00 +01:00
this . awareness = Math . min ( awareness , Number . MAX_VALUE ) ;
2022-04-07 01:30:08 +02:00
const popularity = this . popularity + popularityGain * marketCycles ;
2022-01-14 11:05:00 +01:00
this . popularity = Math . min ( popularity , Number . MAX_VALUE ) ;
2021-09-05 01:09:30 +02:00
}
return ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
// Process production, purchase, and import/export of materials
let res = this . processMaterials ( marketCycles , corporation ) ;
if ( Array . isArray ( res ) ) {
2021-11-12 03:35:26 +01:00
this . thisCycleRevenue = this . thisCycleRevenue + res [ 0 ] ;
this . thisCycleExpenses = this . thisCycleExpenses + res [ 1 ] ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
// Process creation, production & sale of products
res = this . processProducts ( marketCycles , corporation ) ;
if ( Array . isArray ( res ) ) {
2021-11-12 03:35:26 +01:00
this . thisCycleRevenue = this . thisCycleRevenue + res [ 0 ] ;
this . thisCycleExpenses = this . thisCycleExpenses + res [ 1 ] ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
}
// 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
2022-12-30 02:28:53 +01:00
for ( const city of Object . values ( CityName ) ) {
2021-09-05 01:09:30 +02:00
//If this industry has a warehouse in this city, process the market
//for every material this industry requires or produces
2022-12-30 02:28:53 +01:00
if ( this . warehouses [ city ] ) {
const wh = this . warehouses [ city ] as Warehouse ; // Warehouse type is known due to if check above
for ( const name of Object . keys ( reqMats ) as CorpMaterialName [ ] ) {
2021-09-05 01:09:30 +02:00
if ( reqMats . hasOwnProperty ( name ) ) {
wh . materials [ name ] . processMarket ( ) ;
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
//Produced materials are stored in an array
2022-12-30 02:28:53 +01:00
for ( const matName of prodMats ) wh . materials [ matName ] . processMarket ( ) ;
2021-08-31 08:49:57 +02:00
2021-09-05 01:09:30 +02:00
//Process these twice because these boost production
2022-12-30 02:28:53 +01:00
wh . materials . Hardware . processMarket ( ) ;
wh . materials . Robots . processMarket ( ) ;
wh . materials [ "AI Cores" ] . processMarket ( ) ;
wh . materials [ "Real Estate" ] . processMarket ( ) ;
2021-09-05 01:09:30 +02:00
}
}
}
// Process change in demand and competition for this industry's products
processProductMarket ( marketCycles = 1 ) : void {
// Demand gradually decreases, and competition gradually increases
2022-01-16 01:45:03 +01:00
for ( const name of Object . keys ( this . products ) ) {
2021-09-05 01:09:30 +02:00
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 (
2022-10-25 03:54:54 +02:00
this . type === IndustryType . Pharmaceutical ||
this . type === IndustryType . Software ||
this . type === IndustryType . Robotics
2021-09-05 01:09:30 +02:00
) {
change *= 3 ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
change *= marketCycles ;
product . dmd -= change ;
product . cmp += change ;
product . cmp = Math . min ( product . cmp , 99.99 ) ;
product . dmd = Math . max ( product . dmd , 0.001 ) ;
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
}
//Process production, purchase, and import/export of materials
2022-09-20 12:47:54 +02:00
processMaterials ( marketCycles = 1 , corporation : Corporation ) : [ number , number ] {
2021-09-05 01:09:30 +02:00
let revenue = 0 ,
expenses = 0 ;
this . calculateProductionFactors ( ) ;
2022-12-30 02:28:53 +01:00
for ( const city of Object . values ( CityName ) ) {
2021-09-05 01:09:30 +02:00
const office = this . offices [ city ] ;
if ( office === 0 ) continue ;
2022-09-27 21:14:34 +02:00
if ( this . warehouses [ city ] ) {
2021-09-05 01:09:30 +02:00
const warehouse = this . warehouses [ city ] ;
if ( warehouse === 0 ) continue ;
switch ( this . state ) {
2021-09-07 07:14:55 +02:00
case "PURCHASE" : {
2021-09-05 01:09:30 +02:00
/* Process purchase of materials */
2022-12-30 02:28:53 +01:00
for ( const matName of Object . values ( corpConstants . materialNames ) ) {
2021-09-07 07:14:55 +02:00
if ( ! warehouse . materials . hasOwnProperty ( matName ) ) continue ;
const mat = warehouse . materials [ matName ] ;
let buyAmt = 0 ;
let maxAmt = 0 ;
2021-09-09 05:47:34 +02:00
if ( warehouse . smartSupplyEnabled && Object . keys ( this . reqMats ) . includes ( matName ) ) {
2021-09-07 07:14:55 +02:00
continue ;
2021-09-05 01:09:30 +02:00
}
2022-12-30 02:28:53 +01:00
buyAmt = mat . buy * corpConstants . secondsPerMarketCycle * marketCycles ;
2021-09-07 07:14:55 +02:00
2022-12-30 02:28:53 +01:00
maxAmt = Math . floor ( ( warehouse . size - warehouse . sizeUsed ) / MaterialInfo [ matName ] . size ) ;
2021-10-28 06:06:57 +02:00
2021-09-07 07:14:55 +02:00
buyAmt = Math . min ( buyAmt , maxAmt ) ;
if ( buyAmt > 0 ) {
2023-03-18 02:12:43 +01:00
mat . qlt = Math . max ( 0.1 , ( mat . qlt * mat . qty + 1 * buyAmt ) / ( mat . qty + buyAmt ) ) ;
2021-09-07 07:14:55 +02:00
mat . qty += buyAmt ;
expenses += buyAmt * mat . bCost ;
}
this . updateWarehouseSizeUsed ( warehouse ) ;
2021-09-05 01:09:30 +02:00
} //End process purchase of materials
2021-09-07 07:14:55 +02:00
// smart supply
2022-12-30 02:28:53 +01:00
const smartBuy : Partial < Record < CorpMaterialName , number > > = { } ;
for ( const matName of Object . values ( corpConstants . materialNames ) ) {
2021-09-07 07:14:55 +02:00
if ( ! warehouse . materials . hasOwnProperty ( matName ) ) continue ;
2021-09-09 05:47:34 +02:00
if ( ! warehouse . smartSupplyEnabled || ! Object . keys ( this . reqMats ) . includes ( matName ) ) continue ;
2021-09-07 07:14:55 +02:00
const mat = warehouse . materials [ matName ] ;
//Smart supply tracker is stored as per second rate
const reqMat = this . reqMats [ matName ] ;
2021-09-09 05:47:34 +02:00
if ( reqMat === undefined ) throw new Error ( ` reqMat " ${ matName } " is undefined ` ) ;
2021-09-07 07:14:55 +02:00
mat . buy = reqMat * warehouse . smartSupplyStore ;
2022-12-30 02:28:53 +01:00
let buyAmt = mat . buy * corpConstants . secondsPerMarketCycle * marketCycles ;
const maxAmt = Math . floor ( ( warehouse . size - warehouse . sizeUsed ) / MaterialInfo [ matName ] . size ) ;
2021-09-07 07:14:55 +02:00
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 = 1 e99 ;
2022-12-30 02:28:53 +01:00
for ( const matName of Object . keys ( smartBuy ) as CorpMaterialName [ ] ) {
2021-09-07 07:14:55 +02:00
const buyAmt = smartBuy [ matName ] ;
2021-09-09 05:47:34 +02:00
if ( buyAmt === undefined ) throw new Error ( ` Somehow smartbuy matname is undefined ` ) ;
2021-09-07 07:14:55 +02:00
const reqMat = this . reqMats [ matName ] ;
2021-09-09 05:47:34 +02:00
if ( reqMat === undefined ) throw new Error ( ` reqMat " ${ matName } " is undefined ` ) ;
2021-09-07 07:14:55 +02:00
const amt = buyAmt / reqMat ;
if ( amt < worseAmt ) worseAmt = amt ;
}
// Align all the materials to the smallest amount.
2022-12-30 02:28:53 +01:00
for ( const matName of Object . keys ( smartBuy ) as CorpMaterialName [ ] ) {
2021-09-07 07:14:55 +02:00
const reqMat = this . reqMats [ matName ] ;
2021-09-09 05:47:34 +02:00
if ( reqMat === undefined ) throw new Error ( ` reqMat " ${ matName } " is undefined ` ) ;
2021-09-07 07:14:55 +02:00
smartBuy [ matName ] = worseAmt * reqMat ;
}
// Calculate the total size of all things were trying to buy
let totalSize = 0 ;
2022-12-30 02:28:53 +01:00
for ( const matName of Object . keys ( smartBuy ) as CorpMaterialName [ ] ) {
2021-09-07 07:14:55 +02:00
const buyAmt = smartBuy [ matName ] ;
2021-09-09 05:47:34 +02:00
if ( buyAmt === undefined ) throw new Error ( ` Somehow smartbuy matname is undefined ` ) ;
2022-12-30 02:28:53 +01:00
totalSize += buyAmt * MaterialInfo [ matName ] . size ;
2021-09-07 07:14:55 +02:00
}
// Shrink to the size of available space.
const freeSpace = warehouse . size - warehouse . sizeUsed ;
if ( totalSize > freeSpace ) {
2022-12-30 02:28:53 +01:00
for ( const matName of Object . keys ( smartBuy ) as CorpMaterialName [ ] ) {
2021-09-07 07:14:55 +02:00
const buyAmt = smartBuy [ matName ] ;
2021-09-09 05:47:34 +02:00
if ( buyAmt === undefined ) throw new Error ( ` Somehow smartbuy matname is undefined ` ) ;
smartBuy [ matName ] = Math . floor ( ( buyAmt * freeSpace ) / totalSize ) ;
2021-09-07 07:14:55 +02:00
}
}
2021-09-07 22:46:36 +02:00
// Use the materials already in the warehouse if the option is on.
2022-12-30 02:28:53 +01:00
for ( const matName of Object . keys ( smartBuy ) as CorpMaterialName [ ] ) {
2023-03-18 02:12:43 +01:00
if ( warehouse . smartSupplyOptions [ matName ] === "none" ) continue ;
2021-09-07 07:14:55 +02:00
const mat = warehouse . materials [ matName ] ;
const buyAmt = smartBuy [ matName ] ;
2021-09-09 05:47:34 +02:00
if ( buyAmt === undefined ) throw new Error ( ` Somehow smartbuy matname is undefined ` ) ;
2023-03-18 02:12:43 +01:00
if ( warehouse . smartSupplyOptions [ matName ] === "leftovers" ) {
smartBuy [ matName ] = Math . max ( 0 , buyAmt - mat . qty ) ;
} else {
smartBuy [ matName ] = Math . max ( 0 , buyAmt - mat . imp ) ;
}
2021-09-07 07:14:55 +02:00
}
// buy them
2022-12-30 02:28:53 +01:00
for ( const [ matName , buyAmt ] of Object . entries ( smartBuy ) as [ CorpMaterialName , number ] [ ] ) {
2021-09-07 07:14:55 +02:00
const mat = warehouse . materials [ matName ] ;
2021-09-09 05:47:34 +02:00
if ( buyAmt === undefined ) throw new Error ( ` Somehow smartbuy matname is undefined ` ) ;
2023-03-18 02:12:43 +01:00
if ( mat . qty + buyAmt != 0 ) mat . qlt = ( mat . qlt * mat . qty + 1 * buyAmt ) / ( mat . qty + buyAmt ) ;
else mat . qlt = 1 ;
2021-09-07 07:14:55 +02:00
mat . qty += buyAmt ;
2023-03-18 02:12:43 +01:00
mat . buy = buyAmt / 10 ;
2021-09-07 07:14:55 +02:00
expenses += buyAmt * mat . bCost ;
}
break ;
}
2021-09-05 01:09:30 +02:00
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 ;
}
2022-12-30 02:28:53 +01:00
prod *= corpConstants . secondsPerMarketCycle * marketCycles ; //Convert production from per second to per market cycle
2021-09-05 01:09:30 +02:00
// Calculate net change in warehouse storage making the produced materials will cost
let totalMatSize = 0 ;
for ( let tmp = 0 ; tmp < this . prodMats . length ; ++ tmp ) {
2022-12-30 02:28:53 +01:00
totalMatSize += MaterialInfo [ this . prodMats [ tmp ] ] . size ;
2021-09-05 01:09:30 +02:00
}
2022-12-30 02:28:53 +01:00
for ( const reqMatName of Object . keys ( this . reqMats ) as CorpMaterialName [ ] ) {
2021-09-05 01:09:30 +02:00
const normQty = this . reqMats [ reqMatName ] ;
if ( normQty === undefined ) continue ;
2022-12-30 02:28:53 +01:00
totalMatSize -= MaterialInfo [ reqMatName ] . size * normQty ;
2021-09-05 01:09:30 +02:00
}
// If not enough space in warehouse, limit the amount of produced materials
if ( totalMatSize > 0 ) {
2021-09-09 05:47:34 +02:00
const maxAmt = Math . floor ( ( warehouse . size - warehouse . sizeUsed ) / totalMatSize ) ;
2021-09-05 01:09:30 +02:00
prod = Math . min ( maxAmt , prod ) ;
}
if ( prod < 0 ) {
prod = 0 ;
}
// Keep track of production for smart supply (/s)
2022-12-30 02:28:53 +01:00
warehouse . smartSupplyStore += prod / ( corpConstants . secondsPerMarketCycle * marketCycles ) ;
2021-09-05 01:09:30 +02:00
// Make sure we have enough resource to make our materials
let producableFrac = 1 ;
2022-12-30 02:28:53 +01:00
for ( const reqMatName of Object . keys ( this . reqMats ) as CorpMaterialName [ ] ) {
2021-09-05 01:09:30 +02:00
if ( this . reqMats . hasOwnProperty ( reqMatName ) ) {
const reqMat = this . reqMats [ reqMatName ] ;
if ( reqMat === undefined ) continue ;
const req = reqMat * prod ;
if ( warehouse . materials [ reqMatName ] . qty < req ) {
2021-09-09 05:47:34 +02:00
producableFrac = Math . min ( producableFrac , warehouse . materials [ reqMatName ] . qty / req ) ;
2021-09-05 01:09:30 +02:00
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
}
if ( producableFrac <= 0 ) {
producableFrac = 0 ;
prod = 0 ;
}
// Make our materials if they are producable
if ( producableFrac > 0 && prod > 0 ) {
2023-03-18 02:12:43 +01:00
let avgQlt = 0 ;
let divider = 0 ;
2022-12-30 02:28:53 +01:00
for ( const reqMatName of Object . keys ( this . reqMats ) as CorpMaterialName [ ] ) {
2021-09-05 01:09:30 +02:00
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 -=
2022-12-30 02:28:53 +01:00
reqMatQtyNeeded / ( corpConstants . secondsPerMarketCycle * marketCycles ) ;
2023-03-18 02:12:43 +01:00
avgQlt += warehouse . materials [ reqMatName ] . qlt ;
divider ++ ;
2021-08-31 08:49:57 +02:00
}
2023-03-18 02:12:43 +01:00
avgQlt /= divider ;
avgQlt = Math . max ( avgQlt , 1 ) ;
2021-09-05 01:09:30 +02:00
for ( let j = 0 ; j < this . prodMats . length ; ++ j ) {
2023-03-18 02:12:43 +01:00
let tempQlt =
2021-09-05 01:09:30 +02:00
office . employeeProd [ EmployeePositions . Engineer ] / 90 +
2022-12-30 02:28:53 +01:00
Math . pow ( this . sciResearch , this . sciFac ) +
Math . pow ( warehouse . materials [ "AI Cores" ] . qty , this . aiFac ) / 10 e3 ;
2023-03-18 02:12:43 +01:00
const logQlt = Math . max ( Math . pow ( tempQlt , 0.5 ) , 1 ) ;
tempQlt = Math . min ( tempQlt , avgQlt * logQlt ) ;
warehouse . materials [ this . prodMats [ j ] ] . qlt = Math . max (
1 ,
( warehouse . materials [ this . prodMats [ j ] ] . qlt * warehouse . materials [ this . prodMats [ j ] ] . qty +
tempQlt * prod * producableFrac ) /
( warehouse . materials [ this . prodMats [ j ] ] . qty + prod * producableFrac ) ,
) ;
warehouse . materials [ this . prodMats [ j ] ] . qty += prod * producableFrac ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
} else {
2022-12-30 02:28:53 +01:00
for ( const reqMatName of Object . keys ( this . reqMats ) as CorpMaterialName [ ] ) {
2021-09-05 01:09:30 +02:00
if ( this . reqMats . hasOwnProperty ( reqMatName ) ) {
warehouse . materials [ reqMatName ] . prd = 0 ;
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
}
//Per second
2022-12-30 02:28:53 +01:00
const fooProd = ( prod * producableFrac ) / ( corpConstants . secondsPerMarketCycle * marketCycles ) ;
2021-09-05 01:09:30 +02:00
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
2022-12-30 02:28:53 +01:00
for ( const reqMatName of Object . keys ( this . reqMats ) as CorpMaterialName [ ] ) {
2021-09-05 01:09:30 +02:00
warehouse . materials [ reqMatName ] . prd = 0 ;
}
}
break ;
case "SALE" :
/* Process sale of materials */
2022-12-30 02:28:53 +01:00
for ( const matName of Object . values ( corpConstants . materialNames ) ) {
2021-09-05 01:09:30 +02:00
if ( warehouse . materials . hasOwnProperty ( matName ) ) {
const mat = warehouse . materials [ matName ] ;
if ( mat . sCost < 0 || mat . sllman [ 0 ] === false ) {
mat . sll = 0 ;
continue ;
2021-08-31 08:49:57 +02:00
}
// Sale multipliers
2021-09-05 01:09:30 +02:00
const businessFactor = this . getBusinessFactor ( office ) ; //Business employee productivity
const advertisingFactor = this . getAdvertisingFactors ( ) [ 0 ] ; //Awareness + popularity
const marketFactor = this . getMarketFactor ( mat ) ; //Competition + demand
2021-08-31 08:49:57 +02:00
2023-02-12 08:03:31 +01:00
// Parse player sell-amount input (needed for TA.II and selling)
let sellAmt ;
// The amount gets re-multiplied later, so this is the correct
// amount to calculate with for "MAX".
const adjustedQty = mat . qty / ( corpConstants . secondsPerMarketCycle * marketCycles ) ;
if ( isString ( mat . sllman [ 1 ] ) ) {
//Dynamically evaluated
let tmp = ( mat . sllman [ 1 ] as string ) . replace ( /MAX/g , ( adjustedQty + "" ) . 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 ;
}
} else if ( mat . sllman [ 1 ] === - 1 ) {
//Backwards compatibility, -1 = MAX
sellAmt = adjustedQty ;
} else {
//Player's input value is just a number
sellAmt = mat . sllman [ 1 ] as number ;
}
2021-09-05 01:09:30 +02:00
// Determine the cost that the material will be sold at
const markupLimit = mat . getMarkupLimit ( ) ;
2021-08-31 09:07:20 +02:00
let sCost ;
2021-09-05 01:09:30 +02:00
if ( mat . marketTa2 ) {
// Reverse engineer the 'maxSell' formula
2023-02-12 08:03:31 +01:00
// 1. Set 'maxSell' = sellAmt
2021-09-05 01:09:30 +02:00
// 2. Substitute formula for 'markup'
// 3. Solve for 'sCost'
const numerator = markupLimit ;
2023-02-12 08:03:31 +01:00
const sqrtNumerator = sellAmt ;
2021-09-05 01:09:30 +02:00
const sqrtDenominator =
( mat . qlt + 0.001 ) *
marketFactor *
businessFactor *
corporation . getSalesMultiplier ( ) *
advertisingFactor *
this . getSalesMultiplier ( ) ;
2021-09-09 05:47:34 +02:00
const denominator = Math . sqrt ( sqrtNumerator / sqrtDenominator ) ;
2021-09-05 01:09:30 +02:00
let optimalPrice ;
if ( sqrtDenominator === 0 || denominator === 0 ) {
if ( sqrtNumerator === 0 ) {
2023-02-12 08:03:31 +01:00
optimalPrice = 0 ; // Nothing to sell
2021-08-31 08:49:57 +02:00
} else {
2021-09-05 01:09:30 +02:00
optimalPrice = mat . bCost + markupLimit ;
2021-09-09 05:47:34 +02:00
console . warn ( ` In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost ` ) ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
} 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 ) ;
2021-08-31 08:49:57 +02:00
} else {
2021-09-05 01:09:30 +02:00
sCost = mat . sCost ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
// Calculate how much of the material sells (per second)
2021-08-31 09:07:20 +02:00
let markup = 1 ;
2021-09-05 01:09:30 +02:00
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 = 1 e12 ; //Sell everything, essentially discard
} else {
//Lower prices than market increases sales
markup = mat . bCost / sCost ;
}
2021-08-31 08:49:57 +02:00
}
2022-01-22 13:32:35 +01:00
mat . maxsll =
2021-09-05 01:09:30 +02:00
( mat . qlt + 0.001 ) *
marketFactor *
markup *
businessFactor *
corporation . getSalesMultiplier ( ) *
advertisingFactor *
this . getSalesMultiplier ( ) ;
2023-03-18 02:12:43 +01:00
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 is set to ${ sellAmt } ` ,
) ;
sellAmt = 0 ;
}
sellAmt = Math . min ( mat . maxsll , sellAmt ) ;
sellAmt = Math . max ( sellAmt , 0 ) ;
} 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 ) ;
}
2023-02-12 08:03:31 +01:00
sellAmt = Math . min ( mat . maxsll , sellAmt ) ;
2022-12-30 02:28:53 +01:00
sellAmt = sellAmt * corpConstants . secondsPerMarketCycle * marketCycles ;
2021-09-05 01:09:30 +02:00
sellAmt = Math . min ( mat . qty , sellAmt ) ;
if ( sellAmt < 0 ) {
2021-09-09 05:47:34 +02:00
console . warn ( ` sellAmt calculated to be negative for ${ matName } in ${ city } ` ) ;
2021-09-05 01:09:30 +02:00
mat . sll = 0 ;
continue ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
if ( sellAmt && sCost >= 0 ) {
mat . qty -= sellAmt ;
revenue += sellAmt * sCost ;
2022-12-30 02:28:53 +01:00
mat . sll = sellAmt / ( corpConstants . secondsPerMarketCycle * marketCycles ) ;
2021-08-31 08:49:57 +02:00
} else {
2021-09-05 01:09:30 +02:00
mat . sll = 0 ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
}
} //End processing of sale of materials
break ;
case "EXPORT" :
2022-12-30 02:28:53 +01:00
for ( const matName of Object . values ( corpConstants . materialNames ) ) {
2021-09-05 01:09:30 +02:00
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 ] ;
2023-03-18 02:12:43 +01:00
const expIndustry = corporation . divisions . find ( ( div ) = > div . name === exp . ind ) ;
if ( ! expIndustry ) {
console . error ( ` Invalid export! ${ exp . ind } ` ) ;
continue ;
}
const expWarehouse = expIndustry . warehouses [ exp . city ] ;
if ( ! expWarehouse ) {
console . error ( ` Invalid export! ${ expIndustry . name } ${ exp . city } ` ) ;
continue ;
}
const tempMaterial = expWarehouse . materials [ matName ] ;
let amtStr = exp . amt . replace (
2021-09-05 01:09:30 +02:00
/MAX/g ,
2022-12-30 02:28:53 +01:00
( mat . qty / ( corpConstants . secondsPerMarketCycle * marketCycles ) + "" ) . toUpperCase ( ) ,
2021-09-05 01:09:30 +02:00
) ;
2023-03-18 02:12:43 +01:00
amtStr = amtStr . replace ( /EPROD/g , mat . prd . toString ( ) ) ;
amtStr = amtStr . replace ( /IPROD/g , tempMaterial . prd . toString ( ) ) ;
amtStr = amtStr . replace ( /EINV/g , mat . qty . toString ( ) ) ;
amtStr = amtStr . replace ( /IINV/g , tempMaterial . qty . toString ( ) ) ;
2021-09-05 01:09:30 +02:00
let amt = 0 ;
try {
amt = eval ( amtStr ) ;
} catch ( e ) {
dialogBoxCreate (
2022-08-28 11:33:38 +02:00
` Calculating export for ${ mat . name } in ${ this . name } 's ${ city } division failed with error: ${ e } ` ,
2021-09-05 01:09:30 +02:00
) ;
continue ;
}
if ( isNaN ( amt ) ) {
dialogBoxCreate (
2022-08-28 11:33:38 +02:00
` Error calculating export amount for ${ mat . name } in ${ this . name } 's ${ city } division. ` ,
2021-09-05 01:09:30 +02:00
) ;
continue ;
}
2022-12-30 02:28:53 +01:00
amt = amt * corpConstants . secondsPerMarketCycle * marketCycles ;
2021-09-05 01:09:30 +02:00
if ( mat . qty < amt ) {
amt = mat . qty ;
}
2023-03-18 02:12:43 +01:00
// Make sure theres enough space in warehouse
if ( expWarehouse . sizeUsed >= expWarehouse . size ) {
// Warehouse at capacity. Exporting doesn't
// affect revenue so just return 0's
continue ;
} else {
const maxAmt = Math . floor ( ( expWarehouse . size - expWarehouse . sizeUsed ) / MaterialInfo [ matName ] . size ) ;
amt = Math . min ( maxAmt , amt ) ;
2021-09-05 01:09:30 +02:00
}
2023-03-18 02:12:43 +01:00
if ( amt <= 0 ) {
continue ;
2021-09-05 01:09:30 +02:00
}
2023-03-18 02:12:43 +01:00
expWarehouse . materials [ matName ] . imp += amt / ( corpConstants . secondsPerMarketCycle * marketCycles ) ;
//Pretty sure this can cause some issues if there are multiple sources importing same material to same warehouse
//but this will do for now
expWarehouse . materials [ matName ] . qlt = Math . max (
0.1 ,
( expWarehouse . materials [ matName ] . qlt * expWarehouse . materials [ matName ] . qty + amt * mat . qlt ) /
( expWarehouse . materials [ matName ] . qty + amt ) ,
) ;
expWarehouse . materials [ matName ] . qty += amt ;
mat . qty -= amt ;
mat . totalExp += amt ;
expIndustry . updateWarehouseSizeUsed ( expWarehouse ) ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
//totalExp should be per second
2022-12-30 02:28:53 +01:00
mat . totalExp /= corpConstants . secondsPerMarketCycle * marketCycles ;
2021-09-05 01:09:30 +02:00
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
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
2022-09-27 21:14:34 +02:00
if ( office ) {
2022-12-30 02:28:53 +01:00
this . sciResearch +=
2021-09-05 01:09:30 +02:00
0.004 *
Math . pow ( office . employeeProd [ EmployeePositions . RandD ] , 0.5 ) *
corporation . getScientificResearchMultiplier ( ) *
this . getScientificResearchMultiplier ( ) ;
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
return [ revenue , expenses ] ;
}
//Process production & sale of this industry's FINISHED products (including all of their stats)
2022-09-20 12:47:54 +02:00
processProducts ( marketCycles = 1 , corporation : Corporation ) : [ number , number ] {
2021-09-05 01:09:30 +02:00
let revenue = 0 ;
const expenses = 0 ;
//Create products
if ( this . state === "PRODUCTION" ) {
2022-01-16 01:45:03 +01:00
for ( const prodName of Object . keys ( this . products ) ) {
2021-09-05 01:09:30 +02:00
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 ;
2022-06-02 23:11:34 +02:00
prod . createProduct ( marketCycles , office . employeeProd ) ;
2021-09-05 01:09:30 +02:00
if ( prod . prog >= 100 ) {
2022-06-02 23:11:34 +02:00
prod . finishProduct ( this ) ;
2021-09-05 01:09:30 +02:00
}
break ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
//Produce Products
2022-01-16 01:45:03 +01:00
for ( const prodName of Object . keys ( this . products ) ) {
2021-09-05 01:09:30 +02:00
if ( this . products . hasOwnProperty ( prodName ) ) {
const prod = this . products [ prodName ] ;
2022-09-27 21:14:34 +02:00
if ( prod && prod . fin ) {
2021-09-05 01:09:30 +02:00
revenue += this . processProduct ( marketCycles , prod , corporation ) ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
return [ revenue , expenses ] ;
}
//Processes FINISHED products
2022-09-20 12:47:54 +02:00
processProduct ( marketCycles = 1 , product : Product , corporation : Corporation ) : number {
2021-09-05 01:09:30 +02:00
let totalProfit = 0 ;
2022-12-30 02:28:53 +01:00
for ( const city of Object . values ( CityName ) ) {
2021-09-05 01:09:30 +02:00
const office = this . offices [ city ] ;
if ( office === 0 ) continue ;
const warehouse = this . warehouses [ city ] ;
2022-09-27 21:14:34 +02:00
if ( warehouse ) {
2021-09-05 01:09:30 +02:00
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 ;
}
2022-12-30 02:28:53 +01:00
prod *= corpConstants . secondsPerMarketCycle * marketCycles ;
2021-09-05 01:09:30 +02:00
//Calculate net change in warehouse storage making the Products will cost
let netStorageSize = product . siz ;
2022-12-30 02:28:53 +01:00
for ( const reqMatName of Object . keys ( product . reqMats ) as CorpMaterialName [ ] ) {
2021-09-05 01:09:30 +02:00
if ( product . reqMats . hasOwnProperty ( reqMatName ) ) {
2022-12-30 02:28:53 +01:00
const normQty = product . reqMats [ reqMatName ] as number ;
netStorageSize -= MaterialInfo [ reqMatName ] . size * normQty ;
2021-09-05 01:09:30 +02:00
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
//If there's not enough space in warehouse, limit the amount of Product
if ( netStorageSize > 0 ) {
2021-09-09 05:47:34 +02:00
const maxAmt = Math . floor ( ( warehouse . size - warehouse . sizeUsed ) / netStorageSize ) ;
2021-09-05 01:09:30 +02:00
prod = Math . min ( maxAmt , prod ) ;
}
2021-08-31 08:49:57 +02:00
2022-12-30 02:28:53 +01:00
warehouse . smartSupplyStore += prod / ( corpConstants . secondsPerMarketCycle * marketCycles ) ;
2021-09-05 01:09:30 +02:00
//Make sure we have enough resources to make our Products
let producableFrac = 1 ;
2022-12-30 02:28:53 +01:00
for ( const [ reqMatName , reqQty ] of Object . entries ( product . reqMats ) as [ CorpMaterialName , number ] [ ] ) {
const req = reqQty * prod ;
if ( warehouse . materials [ reqMatName ] . qty < req ) {
producableFrac = Math . min ( producableFrac , warehouse . materials [ reqMatName ] . qty / req ) ;
2021-09-05 01:09:30 +02:00
}
}
2021-08-31 08:49:57 +02:00
2021-09-05 01:09:30 +02:00
//Make our Products if they are producable
if ( producableFrac > 0 && prod > 0 ) {
2023-03-18 02:12:43 +01:00
let avgQlt = 1 ;
2022-12-30 02:28:53 +01:00
for ( const [ reqMatName , reqQty ] of Object . entries ( product . reqMats ) as [ CorpMaterialName , number ] [ ] ) {
const reqMatQtyNeeded = reqQty * prod * producableFrac ;
warehouse . materials [ reqMatName ] . qty -= reqMatQtyNeeded ;
warehouse . materials [ reqMatName ] . prd -=
reqMatQtyNeeded / ( corpConstants . secondsPerMarketCycle * marketCycles ) ;
2023-03-18 02:12:43 +01:00
avgQlt += warehouse . materials [ reqMatName ] . qlt ;
2021-09-05 01:09:30 +02:00
}
2023-03-18 02:12:43 +01:00
avgQlt /= Object . keys ( product . reqMats ) . length ;
const tempEffRat = Math . min ( product . rat , avgQlt * Math . pow ( product . rat , 0.5 ) ) ;
//Effective Rating
product . data [ city ] [ 3 ] =
( product . data [ city ] [ 3 ] * product . data [ city ] [ 0 ] + tempEffRat * prod * producableFrac ) /
( product . data [ city ] [ 0 ] + prod * producableFrac ) ;
2021-09-05 01:09:30 +02:00
//Quantity
product . data [ city ] [ 0 ] += prod * producableFrac ;
}
2021-08-31 08:49:57 +02:00
2021-09-05 01:09:30 +02:00
//Keep track of production Per second
2022-12-30 02:28:53 +01:00
product . data [ city ] [ 1 ] = ( prod * producableFrac ) / ( corpConstants . secondsPerMarketCycle * marketCycles ) ;
2021-09-05 01:09:30 +02:00
break ;
}
case "SALE" : {
//Process sale of Products
product . pCost = 0 ; //Estimated production cost
2022-12-30 02:28:53 +01:00
for ( const [ reqMatName , reqQty ] of Object . entries ( product . reqMats ) as [ CorpMaterialName , number ] [ ] ) {
product . pCost += reqQty * warehouse . materials [ reqMatName ] . bCost ;
2021-09-05 01:09:30 +02:00
}
2021-08-31 08:49:57 +02:00
2021-09-05 01:09:30 +02:00
// Since its a product, its production cost is increased for labor
2022-12-30 02:28:53 +01:00
product . pCost *= corpConstants . baseProductProfitMult ;
2021-09-05 01:09:30 +02:00
// Sale multipliers
const businessFactor = this . getBusinessFactor ( office ) ; //Business employee productivity
const advertisingFactor = this . getAdvertisingFactors ( ) [ 0 ] ; //Awareness + popularity
const marketFactor = this . getMarketFactor ( product ) ; //Competition + demand
2023-02-12 08:03:31 +01:00
// Parse player sell-amount input (needed for TA.II and selling)
let sellAmt ;
// The amount gets re-multiplied later, so this is the correct
// amount to calculate with for "MAX".
const adjustedQty = product . data [ city ] [ 0 ] / ( corpConstants . secondsPerMarketCycle * marketCycles ) ;
if ( product . sllman [ city ] [ 0 ] && isString ( product . sllman [ city ] [ 1 ] ) ) {
//Sell amount is dynamically evaluated
let tmp = product . sllman [ city ] [ 1 ] . replace ( /MAX/g , ( adjustedQty + "" ) . 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 = tmp ;
} else if ( product . sllman [ city ] [ 0 ] && product . sllman [ city ] [ 1 ] > 0 ) {
//Sell amount is manually limited
sellAmt = product . sllman [ city ] [ 1 ] ;
} else if ( product . sllman [ city ] [ 0 ] === false ) {
sellAmt = 0 ;
} else {
sellAmt = adjustedQty ;
}
if ( sellAmt < 0 ) {
sellAmt = 0 ;
}
2021-09-05 01:09:30 +02:00
// Calculate Sale Cost (sCost), which could be dynamically evaluated
2023-03-18 02:12:43 +01:00
const markupLimit = Math . max ( product . data [ city ] [ 3 ] , 0.001 ) / product . mku ;
2021-09-05 01:09:30 +02:00
let sCost ;
if ( product . marketTa2 ) {
// Reverse engineer the 'maxSell' formula
2023-02-12 08:03:31 +01:00
// 1. Set 'maxSell' = sellAmt
2021-09-05 01:09:30 +02:00
// 2. Substitute formula for 'markup'
2022-10-09 07:25:31 +02:00
// 3. Solve for 'sCost', product.pCost = sCost
2021-09-05 01:09:30 +02:00
const numerator = markupLimit ;
2023-02-12 08:03:31 +01:00
const sqrtNumerator = sellAmt ;
2021-09-05 01:09:30 +02:00
const sqrtDenominator =
0.5 *
2023-03-18 02:12:43 +01:00
Math . pow ( product . data [ city ] [ 3 ] , 0.65 ) *
2021-09-05 01:09:30 +02:00
marketFactor *
corporation . getSalesMultiplier ( ) *
businessFactor *
advertisingFactor *
this . getSalesMultiplier ( ) ;
const denominator = Math . sqrt ( sqrtNumerator / sqrtDenominator ) ;
let optimalPrice ;
if ( sqrtDenominator === 0 || denominator === 0 ) {
if ( sqrtNumerator === 0 ) {
2023-02-12 08:03:31 +01:00
optimalPrice = 0 ; // Nothing to sell
2021-09-05 01:09:30 +02:00
} else {
optimalPrice = product . pCost + markupLimit ;
2021-09-09 05:47:34 +02:00
console . warn ( ` In Corporation, found illegal 0s when trying to calculate MarketTA2 sale cost ` ) ;
2021-09-05 01:09:30 +02:00
}
} 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 ;
2023-03-18 02:12:43 +01:00
} else if ( isString ( product . sCost [ city ] ) ) {
const sCostString = product . sCost [ city ] as string ;
2021-09-05 01:09:30 +02:00
if ( product . mku === 0 ) {
console . error ( ` mku is zero, reverting to 1 to avoid Infinity ` ) ;
product . mku = 1 ;
}
2023-03-18 02:12:43 +01:00
sCost = sCostString . replace ( /MP/g , product . pCost + "" ) ;
2022-10-25 18:51:03 +02:00
sCost = Math . max ( product . pCost , eval ( sCost ) ) ;
2021-09-05 01:09:30 +02:00
} else {
2023-03-18 02:12:43 +01:00
sCost = product . sCost [ city ] ;
2021-09-05 01:09:30 +02:00
}
2021-08-31 08:49:57 +02:00
2021-09-05 01:09:30 +02:00
let markup = 1 ;
if ( sCost > product . pCost ) {
if ( sCost - product . pCost > markupLimit ) {
markup = markupLimit / ( sCost - product . pCost ) ;
}
}
2021-08-31 08:49:57 +02:00
2022-04-07 01:30:08 +02:00
product . maxsll =
0.5 *
2023-03-18 02:12:43 +01:00
Math . pow ( product . data [ city ] [ 3 ] , 0.65 ) *
2021-09-05 01:09:30 +02:00
marketFactor *
corporation . getSalesMultiplier ( ) *
Math . pow ( markup , 2 ) *
businessFactor *
advertisingFactor *
this . getSalesMultiplier ( ) ;
2023-02-12 08:03:31 +01:00
sellAmt = Math . min ( product . maxsll , sellAmt ) ;
2022-12-30 02:28:53 +01:00
sellAmt = sellAmt * corpConstants . secondsPerMarketCycle * marketCycles ;
2021-09-05 01:09:30 +02:00
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 ;
2022-12-30 02:28:53 +01:00
product . data [ city ] [ 2 ] = sellAmt / ( corpConstants . secondsPerMarketCycle * marketCycles ) ; //data[2] is sell property
2021-09-05 01:09:30 +02:00
} 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)
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
return totalProfit ;
}
2022-09-23 11:54:04 +02:00
resetImports ( state : string ) : void {
//At the start of the export state, set the imports of everything to 0
if ( state === "EXPORT" ) {
2022-12-30 02:28:53 +01:00
for ( const city of Object . values ( CityName ) ) {
if ( ! this . warehouses [ city ] ) continue ;
2022-09-23 11:54:04 +02:00
const warehouse = this . warehouses [ city ] ;
if ( warehouse === 0 ) continue ;
2022-12-30 02:28:53 +01:00
for ( const matName of Object . values ( corpConstants . materialNames ) ) {
2022-09-23 11:54:04 +02:00
if ( warehouse . materials . hasOwnProperty ( matName ) ) {
const mat = warehouse . materials [ matName ] ;
mat . imp = 0 ;
}
}
}
}
}
2021-09-05 01:09:30 +02:00
discontinueProduct ( product : Product ) : void {
2022-01-16 01:45:03 +01:00
for ( const productName of Object . keys ( this . products ) ) {
2021-09-05 01:09:30 +02:00
if ( this . products . hasOwnProperty ( productName ) ) {
if ( product === this . products [ productName ] ) {
delete this . products [ productName ] ;
}
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
}
2022-06-02 03:43:22 +02:00
getAdVertCost ( ) : number {
return 1 e9 * Math . pow ( 1.06 , this . numAdVerts ) ;
}
2021-09-05 01:09:30 +02:00
2022-09-20 12:47:54 +02:00
applyAdVert ( corporation : Corporation ) : void {
2022-06-02 03:43:22 +02:00
const advMult = corporation . getAdvertisingMultiplier ( ) * this . getAdvertisingMultiplier ( ) ;
2023-03-18 02:12:43 +01:00
const awareness = ( this . awareness + 3 * advMult ) * ( 1.005 * advMult ) ;
2022-06-02 03:43:22 +02:00
this . awareness = Math . min ( awareness , Number . MAX_VALUE ) ;
2022-01-13 18:47:55 +01:00
2023-03-18 02:12:43 +01:00
const popularity = ( this . popularity + 1 * advMult ) * ( ( 1 + getRandomInt ( 1 , 3 ) / 200 ) * advMult ) ;
2022-06-02 03:43:22 +02:00
this . popularity = Math . min ( popularity , Number . MAX_VALUE ) ;
++ this . numAdVerts ;
2021-09-05 01:09:30 +02:00
}
// Returns how much of a material can be produced based of office productivity (employee stats)
2021-09-09 05:47:34 +02:00
getOfficeProductivity ( office : OfficeSpace , params : { forProduct? : boolean } = { } ) : number {
2021-09-05 01:09:30 +02:00
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 ;
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
}
// 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 , 10 e3 ) ;
}
//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 ) ;
2021-09-09 05:47:34 +02:00
const ratioFac = this . awareness === 0 ? 0.01 : Math.max ( ( this . popularity + 0.001 ) / this . awareness , 0.01 ) ;
2021-09-05 01:09:30 +02:00
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
2022-12-30 02:28:53 +01:00
hasResearch ( name : CorpResearchName ) : boolean {
2021-09-05 01:09:30 +02:00
return this . researched [ name ] === true ;
}
updateResearchTree ( ) : void {
const researchTree = IndustryResearchTrees [ this . type ] ;
2021-09-09 05:47:34 +02:00
if ( researchTree === undefined ) throw new Error ( ` Invalid industry " ${ this . type } " ` ) ;
2021-09-05 01:09:30 +02:00
2022-10-09 07:25:31 +02:00
// Since ResearchTree data isn't saved, we'll update the Research Tree data
2021-09-05 01:09:30 +02:00
// based on the stored 'researched' property in the Industry object
2021-09-09 05:47:34 +02:00
if ( Object . keys ( researchTree . researched ) . length !== Object . keys ( this . researched ) . length ) {
2022-12-30 02:28:53 +01:00
for ( const research of Object . keys ( this . researched ) as CorpResearchName [ ] ) {
2021-09-05 01:09:30 +02:00
researchTree . research ( research ) ;
}
2021-08-31 08:49:57 +02:00
}
2021-09-05 01:09:30 +02:00
}
// Get multipliers from Research
getAdvertisingMultiplier ( ) : number {
const researchTree = IndustryResearchTrees [ this . type ] ;
2021-09-09 05:47:34 +02:00
if ( researchTree === undefined ) throw new Error ( ` Invalid industry: " ${ this . type } " ` ) ;
2021-09-05 01:09:30 +02:00
this . updateResearchTree ( ) ;
return researchTree . getAdvertisingMultiplier ( ) ;
}
getEmployeeChaMultiplier ( ) : number {
const researchTree = IndustryResearchTrees [ this . type ] ;
2021-09-09 05:47:34 +02:00
if ( researchTree === undefined ) throw new Error ( ` Invalid industry: " ${ this . type } " ` ) ;
2021-09-05 01:09:30 +02:00
this . updateResearchTree ( ) ;
return researchTree . getEmployeeChaMultiplier ( ) ;
}
getEmployeeCreMultiplier ( ) : number {
const researchTree = IndustryResearchTrees [ this . type ] ;
2021-09-09 05:47:34 +02:00
if ( researchTree === undefined ) throw new Error ( ` Invalid industry: " ${ this . type } " ` ) ;
2021-09-05 01:09:30 +02:00
this . updateResearchTree ( ) ;
return researchTree . getEmployeeCreMultiplier ( ) ;
}
getEmployeeEffMultiplier ( ) : number {
const researchTree = IndustryResearchTrees [ this . type ] ;
2021-09-09 05:47:34 +02:00
if ( researchTree === undefined ) throw new Error ( ` Invalid industry: " ${ this . type } " ` ) ;
2021-09-05 01:09:30 +02:00
this . updateResearchTree ( ) ;
return researchTree . getEmployeeEffMultiplier ( ) ;
}
getEmployeeIntMultiplier ( ) : number {
const researchTree = IndustryResearchTrees [ this . type ] ;
2021-09-09 05:47:34 +02:00
if ( researchTree === undefined ) throw new Error ( ` Invalid industry: " ${ this . type } " ` ) ;
2021-09-05 01:09:30 +02:00
this . updateResearchTree ( ) ;
return researchTree . getEmployeeIntMultiplier ( ) ;
}
getProductionMultiplier ( ) : number {
const researchTree = IndustryResearchTrees [ this . type ] ;
2021-09-09 05:47:34 +02:00
if ( researchTree === undefined ) throw new Error ( ` Invalid industry: " ${ this . type } " ` ) ;
2021-09-05 01:09:30 +02:00
this . updateResearchTree ( ) ;
return researchTree . getProductionMultiplier ( ) ;
}
getProductProductionMultiplier ( ) : number {
const researchTree = IndustryResearchTrees [ this . type ] ;
2021-09-09 05:47:34 +02:00
if ( researchTree === undefined ) throw new Error ( ` Invalid industry: " ${ this . type } " ` ) ;
2021-09-05 01:09:30 +02:00
this . updateResearchTree ( ) ;
return researchTree . getProductProductionMultiplier ( ) ;
}
getSalesMultiplier ( ) : number {
const researchTree = IndustryResearchTrees [ this . type ] ;
2021-09-09 05:47:34 +02:00
if ( researchTree === undefined ) throw new Error ( ` Invalid industry: " ${ this . type } " ` ) ;
2021-09-05 01:09:30 +02:00
this . updateResearchTree ( ) ;
return researchTree . getSalesMultiplier ( ) ;
}
getScientificResearchMultiplier ( ) : number {
const researchTree = IndustryResearchTrees [ this . type ] ;
2021-09-09 05:47:34 +02:00
if ( researchTree === undefined ) throw new Error ( ` Invalid industry: " ${ this . type } " ` ) ;
2021-09-05 01:09:30 +02:00
this . updateResearchTree ( ) ;
return researchTree . getScientificResearchMultiplier ( ) ;
}
getStorageMultiplier ( ) : number {
const researchTree = IndustryResearchTrees [ this . type ] ;
2021-09-09 05:47:34 +02:00
if ( researchTree === undefined ) throw new Error ( ` Invalid industry: " ${ this . type } " ` ) ;
2021-09-05 01:09:30 +02:00
this . updateResearchTree ( ) ;
return researchTree . getStorageMultiplier ( ) ;
}
2022-10-04 12:40:10 +02:00
/** Serialize the current object to a JSON save state. */
2022-07-15 01:00:10 +02:00
toJSON ( ) : IReviverValue {
2021-09-05 01:09:30 +02:00
return Generic_toJSON ( "Industry" , this ) ;
}
2022-10-09 07:25:31 +02:00
/** Initializes a Industry object from a JSON save state. */
2022-07-15 01:00:10 +02:00
static fromJSON ( value : IReviverValue ) : Industry {
2023-01-02 21:25:43 +01:00
const matNameMap = { AICores : "AI Cores" , RealEstate : "Real Estate" } ;
const indNameMap = {
RealEstate : IndustryType.RealEstate ,
2023-03-18 02:12:43 +01:00
Water : IndustryType.Water ,
2023-01-02 21:25:43 +01:00
Computers : IndustryType.Computers ,
Computer : IndustryType.Computers ,
} ;
for ( const [ key , val ] of Object . entries ( indNameMap ) ) if ( value . data . type === key ) value . data . type = val ;
value . data . prodMats = value . data . prodMats . map ( ( matName : string ) = > {
if ( matName in matNameMap ) return matNameMap [ matName as keyof typeof matNameMap ] ;
return matName ;
} ) ;
for ( const matName of Object . keys ( value . data . reqMats ) ) {
if ( matName in matNameMap ) {
value . data . reqMats [ matNameMap [ matName as keyof typeof matNameMap ] ] = value . data . reqMats [ matName ] ;
delete value . data . reqMats [ matName ] ;
}
}
2021-09-05 01:09:30 +02:00
return Generic_fromJSON ( Industry , value . data ) ;
}
2021-08-31 08:49:57 +02:00
}
2023-04-07 06:33:51 +02:00
constructorsForReviver . Industry = Industry ;